diff --git a/.docker/lint-xml-configuration/Dockerfile b/.docker/lint-xml-configuration/Dockerfile deleted file mode 100644 index 117737d6f1e..00000000000 --- a/.docker/lint-xml-configuration/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM alpine:latest - -RUN apk add --no-cache \ - bash \ - libxml2-utils diff --git a/.editorconfig b/.editorconfig index 536f29a3893..2ac65f2f5c7 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,8 +7,11 @@ indent_style = space indent_size = 4 charset = utf-8 -[*.yml] +[*.yaml] indent_size = 2 [tests/_files/*_result_cache.txt] insert_final_newline = false + +[*.phpt] +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes index 4962be9f930..89d2d3aafb4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,16 +1,15 @@ -/.docker export-ignore -/.github export-ignore -/.phive export-ignore -/.psalm export-ignore -/build export-ignore -/tools export-ignore -/tools/* binary -/tests export-ignore -/.editorconfig export-ignore -/.gitattributes export-ignore -/.gitignore export-ignore -/.php_cs.dist export-ignore -/build.xml export-ignore -/phpunit.xml export-ignore +/.github export-ignore +/.phive export-ignore +/build export-ignore +/tools export-ignore +/tools/* binary +/tests export-ignore +/.editorconfig export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.php-cs-fixer.dist.php export-ignore +/build.xml export-ignore +/phpstan.neon export-ignore +/phpunit.xml export-ignore *.php diff=php diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 6f593dfdcb8..24f5ca5019e 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,32 +1,109 @@ # Contributing to PHPUnit -## Contributor Code of Conduct +## Welcome! + +We look forward to your contributions! Here are some examples how you can contribute: + +* [Report a bug](https://github.com/sebastianbergmann/phpunit/issues/new?labels=type/bug&template=BUG.md) +* [Propose a new feature](https://github.com/sebastianbergmann/phpunit/issues/new?labels=type/enhancement&template=FEATURE_REQUEST.md) +* [Send a pull request](https://github.com/sebastianbergmann/phpunit/pulls) + + +## We have a Code of Conduct Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. -## Workflow -* Fork the project. -* Make your bug fix or feature addition. -* Add tests for it. This is important so we don't break it in a future version unintentionally. -* Send a pull request. Bonus points for topic branches. +## Any contributions you make will be under the BSD-3-Clause License + +When you submit code changes, your submissions are understood to be under the same [BSD-3-Clause License](https://github.com/sebastianbergmann/phpunit/blob/main/LICENSE) that covers the project. By contributing to this project, you agree that your contributions will be licensed under its BSD-3-Clause License. + +### Do Not Violate Copyright + +Only submit a pull request with your own original code. Do NOT submit a pull request containing code which you have largely copied from +another project, unless you wrote the respective code yourself. + +Open Source does not mean that copyright does not apply. Copyright infringements will not be tolerated and can lead to you being banned from this project and repository. + +### Do Not Submit AI-Generated Pull Requests + +The same goes for (largely) AI-generated pull requests. These are not welcome as they will be based on copyrighted code from others +without accreditation and without taking the license of the original code into account, let alone getting permission +for the use of the code or for re-licensing. + +Aside from that, the experience is that AI-generated pull requests will be incorrect 100% of the time and cost reviewers too much time. +Submitting a (largely) AI-generated pull request will lead to you being banned from this project and repository. + +## Write bug reports with detail, background, and sample code + +[This is an example](https://github.com/sebastianbergmann/phpunit/issues/4376) of a bug report I wrote, and I think it's not too bad. + +In your bug report, please provide the following: + +* A quick summary and/or background +* Steps to reproduce + * Be specific! + * Give sample code if you can. +* What you expected would happen +* What actually happens +* Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) + +Please do not report a bug for a [version of PHPUnit that is no longer supported](https://phpunit.de/supported-versions.html). Please do not report a bug if you are using a [version of PHP that is not supported by the version of PHPUnit](https://phpunit.de/supported-versions.html) you are using. + +Please do not report an issue if you are not using PHPUnit directly, but rather a third-party wrapper around it. + +Please do not report an issue if you are using a third-party extension such as alternative output printers. + +Please post code and output as text ([using proper markup](https://guides.github.com/features/mastering-markdown/)). Do not post screenshots of code or output. + +Please include the output of `composer info | sort` if you installed PHPUnit using Composer. + +Please use the most specific issue tracker to search for existing tickets and to open new tickets: + +* [General problems](https://github.com/sebastianbergmann/phpunit/issues) +* [Code Coverage](https://github.com/sebastianbergmann/php-code-coverage/issues) +* [Documentation](https://github.com/sebastianbergmann/phpunit-documentation-english/issues) +* [Website](https://github.com/sebastianbergmann/phpunit-website/issues) + + +## Workflow for Pull Requests + +1. Fork the repository. +2. Create your branch from `main` if you plan to implement new functionality or change existing code significantly; create your branch from the oldest branch that is affected by the bug if you plan to fix a bug. +3. Implement your change and add tests for it. +4. Ensure the test suite passes. +5. Ensure the code complies with our coding guidelines (see below). +6. Send that pull request! -Please make sure that you have [set up your user name and email address](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup) for use with Git. Strings such as `silly nick name ` look really stupid in the commit history of a project. +Please make sure you have [set up your username and email address](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup) for use with Git. Strings such as `silly nick name ` look really stupid in the commit history of a project. -Pull requests for bug fixes must be made for the oldest branch that is [supported](https://phpunit.de/supported-versions.html). Pull requests for new features must be based on the `master` branch. +We encourage you to [sign your Git commits with your GPG key](https://docs.github.com/en/github/authenticating-to-github/signing-commits). + +Pull requests for bug fixes must be made for the oldest branch that is [supported](https://phpunit.de/supported-versions.html). Pull requests for new features must be based on the `main` branch. We are trying to keep backwards compatibility breaks in PHPUnit to an absolute minimum. Please take this into account when proposing changes. Due to time constraints, we are not always able to respond as quickly as we would like. Please do not take delays personal and feel free to remind us if you feel that we forgot to respond. + ## Coding Guidelines -This project comes with a configuration file and an executable for [php-cs-fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) (`.php_cs`) that you can use to (re)format your source code for compliance with this project's coding guidelines: +This project comes with a configuration file (located at `/.php-cs-fixer.dist.php` in the repository) and an executable for [php-cs-fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) (located at `/tools/php-cs-fixer` in the repository) that you can use to (re)format your source code for compliance with this project's coding guidelines: ```bash $ ./tools/php-cs-fixer fix ``` +Please understand that we will not accept a pull request when its changes violate this project's coding guidelines. + +## Static Analysis + +This project comes with a configuration file (located at `/phpstan.neon` in the repository) and an executable for [PHPStan](https://phpstan.org/) (located at `/tools/phpstan` in the repository) that you can use to perform static analysis: + +```bash +$ ./tools/phpstan +``` + ## Using PHPUnit from a Git checkout The following commands can be used to perform the initial checkout of PHPUnit: @@ -37,7 +114,7 @@ $ git clone git://github.com/sebastianbergmann/phpunit.git $ cd phpunit ``` -Retrieve PHPUnit's dependencies using [Composer](https://getcomposer.org/): +Install PHPUnit's dependencies using [Composer](https://getcomposer.org/): ```bash $ ./tools/composer install @@ -49,6 +126,7 @@ The `phpunit` script can be used to invoke the PHPUnit test runner: $ ./phpunit --version ``` + ## Running PHPUnit's own test suite After following the steps shown above, PHPUnit's own test suite is run like this: @@ -56,13 +134,3 @@ After following the steps shown above, PHPUnit's own test suite is run like this ```bash $ ./phpunit ``` - -## Reporting issues - -Please use the most specific issue tracker to search for existing tickets and to open new tickets: - -* [General problems](https://github.com/sebastianbergmann/phpunit/issues) -* [Code Coverage](https://github.com/sebastianbergmann/php-code-coverage/issues) -* [Documentation](https://github.com/sebastianbergmann/phpunit-documentation-english/issues) -* [Website](https://github.com/sebastianbergmann/phpunit-website/issues) - diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index f0e24c11979..292fadb829f 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,5 @@ github: sebastianbergmann -custom: https://phpunit.de/donate.html +liberapay: sebastianbergmann +thanks_dev: u/gh/sebastianbergmann +tidelift: "packagist/phpunit/phpunit" +custom: https://phpunit.de/sponsors.html diff --git a/.github/ISSUE_TEMPLATE/1_BUG.md b/.github/ISSUE_TEMPLATE/1_BUG.md new file mode 100644 index 00000000000..581c291213a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1_BUG.md @@ -0,0 +1,41 @@ +--- +name: 🐞 Bug Report +about: Something is broken? +labels: type/bug +--- + + + +| Q | A +| --------------------| --------------- +| PHPUnit version | x.y.z +| PHP version | x.y.z +| Installation Method | Composer / PHAR + +#### Summary + + + +#### Current behavior + + + +#### How to reproduce + + + +#### Expected behavior + + diff --git a/.github/ISSUE_TEMPLATE/2_COMPATIBILITY.md b/.github/ISSUE_TEMPLATE/2_COMPATIBILITY.md new file mode 100644 index 00000000000..f3573a1bc7a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2_COMPATIBILITY.md @@ -0,0 +1,39 @@ +--- +name: ⚠️ PHP Compatibility Issue +about: A change in PHP requires adaption in PHPUnit? +labels: type/change-in-php-requires-adaptation +--- + + + +| Q | A +| --------------------| --------------- +| PHPUnit version | x.y.z +| PHP version | x.y.z +| Installation Method | Composer / PHAR + +#### Summary + + + +#### Current behavior + + + +#### How to reproduce + + + +#### Expected behavior + + diff --git a/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md b/.github/ISSUE_TEMPLATE/3_FEATURE_REQUEST.md similarity index 100% rename from .github/ISSUE_TEMPLATE/FEATURE_REQUEST.md rename to .github/ISSUE_TEMPLATE/3_FEATURE_REQUEST.md diff --git a/.github/ISSUE_TEMPLATE/BUG.md b/.github/ISSUE_TEMPLATE/BUG.md deleted file mode 100644 index 1d9aeaab0ca..00000000000 --- a/.github/ISSUE_TEMPLATE/BUG.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: 🐞 Bug Report -about: Something is broken? -labels: type/bug ---- - - - -| Q | A -| --------------------| --------------- -| PHPUnit version | x.y.z -| PHP version | x.y.z -| Installation Method | Composer / PHAR - -#### Summary - - - -#### Current behavior - - - -#### How to reproduce - - - -#### Expected behavior - - diff --git a/.github/PULL_REQUEST_TEMPLATE/IMPROVEMENT.md b/.github/PULL_REQUEST_TEMPLATE/IMPROVEMENT.md index 2bb6530dd36..80ac18492aa 100644 --- a/.github/PULL_REQUEST_TEMPLATE/IMPROVEMENT.md +++ b/.github/PULL_REQUEST_TEMPLATE/IMPROVEMENT.md @@ -5,5 +5,5 @@ labels: type/enhancement --- diff --git a/.github/PULL_REQUEST_TEMPLATE/NEW_FEATURE.md b/.github/PULL_REQUEST_TEMPLATE/NEW_FEATURE.md index 0687ac447bf..108bb29f002 100644 --- a/.github/PULL_REQUEST_TEMPLATE/NEW_FEATURE.md +++ b/.github/PULL_REQUEST_TEMPLATE/NEW_FEATURE.md @@ -5,5 +5,5 @@ labels: type/enhancement --- diff --git a/.github/img/bubble-shooter.png b/.github/img/bubble-shooter.png new file mode 100755 index 00000000000..f358c1c1ed0 Binary files /dev/null and b/.github/img/bubble-shooter.png differ diff --git a/.github/img/in2it.svg b/.github/img/in2it.svg new file mode 100644 index 00000000000..a58fb6f6d58 --- /dev/null +++ b/.github/img/in2it.svg @@ -0,0 +1,39 @@ + + + + + Produced by OmniGraffle 7.18.2\n2021-01-25 13:37:49 +0000 + + In2it Logo White SVG + + Layer 1 + + + + + + 2 + + + + in + + + + + + + + + it + + + + + + + + + + + diff --git a/.github/img/lambdatest.svg b/.github/img/lambdatest.svg new file mode 100644 index 00000000000..f49a7948563 --- /dev/null +++ b/.github/img/lambdatest.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/.github/img/phpunit.svg b/.github/img/phpunit.svg new file mode 100755 index 00000000000..8fe04bd143c --- /dev/null +++ b/.github/img/phpunit.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.github/img/roave.svg b/.github/img/roave.svg new file mode 100644 index 00000000000..9911ef12eeb --- /dev/null +++ b/.github/img/roave.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.github/img/testmo.svg b/.github/img/testmo.svg new file mode 100644 index 00000000000..791908c2977 --- /dev/null +++ b/.github/img/testmo.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.github/img/tideways.svg b/.github/img/tideways.svg new file mode 100644 index 00000000000..74b6b993cd4 --- /dev/null +++ b/.github/img/tideways.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.github/img/typo3.svg b/.github/img/typo3.svg new file mode 100644 index 00000000000..3ceb2f2ecaf --- /dev/null +++ b/.github/img/typo3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/.github/img/vema.svg b/.github/img/vema.svg new file mode 100644 index 00000000000..4555ab42774 --- /dev/null +++ b/.github/img/vema.svg @@ -0,0 +1,18 @@ + + + + + + + + + diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000000..0ac09bf3ac5 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,478 @@ +# https://docs.github.com/en/actions + +on: + pull_request: null + push: null + +name: CI + +env: + COMPOSER_ROOT_VERSION: "13.0.x-dev" + PHP_VERSION: 8.4 + +permissions: + contents: read + +jobs: + dependency-validation: + name: Dependency Validation + + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 1 + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Use local branch + shell: bash + run: | + BRANCH=$([ "${{ github.event_name }}" == "pull_request" ] && echo "${{ github.head_ref }}" || echo "${{ github.ref_name }}") + git branch -D $BRANCH 2>/dev/null || true + git branch $BRANCH HEAD + git checkout $BRANCH + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ env.PHP_VERSION }} + extensions: none, ctype, curl, dom, json, libxml, mbstring, openssl, phar, tokenizer, xml, xmlwriter, pcntl + coverage: none + tools: none + + - name: Ensure that composer.json is valid + run: ./tools/composer validate --no-ansi --strict composer.json + + - name: Get Composer cache directory + id: composer-cache + shell: bash + run: | + echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Cache Composer cache directory + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Ensure that dependencies can be installed + run: ./tools/composer install --no-ansi --dry-run + + coding-guidelines: + name: Coding Guidelines + + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 1 + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Use local branch + shell: bash + run: | + BRANCH=$([ "${{ github.event_name }}" == "pull_request" ] && echo "${{ github.head_ref }}" || echo "${{ github.ref_name }}") + git branch -D $BRANCH 2>/dev/null || true + git branch $BRANCH HEAD + git checkout $BRANCH + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ env.PHP_VERSION }} + extensions: none, iconv, json, phar, tokenizer + coverage: none + tools: none + + - name: Run PHP-CS-Fixer + run: ./tools/php-cs-fixer check --show-progress=dots --using-cache=no --verbose + + static-analysis: + name: Static Analysis + + needs: + - dependency-validation + + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 1 + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Use local branch + shell: bash + run: | + BRANCH=$([ "${{ github.event_name }}" == "pull_request" ] && echo "${{ github.head_ref }}" || echo "${{ github.ref_name }}") + git branch -D $BRANCH 2>/dev/null || true + git branch $BRANCH HEAD + git checkout $BRANCH + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ env.PHP_VERSION }} + coverage: none + tools: none + + - name: Get Composer cache directory + id: composer-cache + shell: bash + run: | + echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Cache Composer cache directory + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies with Composer + run: ./tools/composer install --no-interaction --no-ansi --no-progress + + - name: Run PHPStan + run: ./tools/phpstan analyse --no-progress --error-format=github + + unit-tests: + name: Unit Tests + + needs: + - dependency-validation + + runs-on: ${{ matrix.os }} + timeout-minutes: 10 + + env: + PHP_EXTENSIONS: none, ctype, curl, dom, json, libxml, mbstring, openssl, phar, tokenizer, xml, xmlwriter, pcntl + PHP_INI_VALUES: memory_limit=-1, zend.assertions=1, error_reporting=-1, log_errors_max_len=0, display_errors=On + + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + + php-version: + - "8.4" + - "8.5" + - "8.6" + + steps: + - name: Configure Git to avoid issues with line endings + if: matrix.os == 'windows-latest' + run: git config --global core.autocrlf false + + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 1 + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Use local branch + shell: bash + run: | + BRANCH=$([ "${{ github.event_name }}" == "pull_request" ] && echo "${{ github.head_ref }}" || echo "${{ github.ref_name }}") + git branch -D $BRANCH 2>/dev/null || true + git branch $BRANCH HEAD + git checkout $BRANCH + + - name: Install PHP with extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: ${{ env.PHP_EXTENSIONS }} + ini-values: ${{ env.PHP_INI_VALUES }} + tools: none + + - name: Get Composer cache directory + id: composer-cache + shell: bash + run: | + echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Cache Composer cache directory + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies with Composer + run: php ./tools/composer install --no-ansi --no-interaction --no-progress + + - name: Run tests with PHPUnit + run: php ./phpunit --testsuite unit --order-by depends,random --display-all-issues + + end-to-end-tests: + name: End-to-End Tests + + needs: + - unit-tests + + runs-on: ${{ matrix.os }} + timeout-minutes: 10 + + env: + PHP_EXTENSIONS: none, ctype, curl, dom, json, libxml, mbstring, openssl, pdo, phar, tokenizer, xml, xmlwriter, pcntl + PHP_INI_VALUES: zend.assertions=1, error_reporting=-1, log_errors_max_len=0, display_errors=On + + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + + php-version: + - "8.4" + - "8.5" + - "8.6" + + steps: + - name: Configure Git to avoid issues with line endings + if: matrix.os == 'windows-latest' + run: git config --global core.autocrlf false + + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 1 + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Use local branch + shell: bash + run: | + BRANCH=$([ "${{ github.event_name }}" == "pull_request" ] && echo "${{ github.head_ref }}" || echo "${{ github.ref_name }}") + git branch -D $BRANCH 2>/dev/null || true + git branch $BRANCH HEAD + git checkout $BRANCH + + - name: Install PHP with extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: ${{ env.PHP_EXTENSIONS }} + ini-values: ${{ env.PHP_INI_VALUES }} + tools: none + + - name: Get Composer cache directory + id: composer-cache + shell: bash + run: | + echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Cache Composer cache directory + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies with Composer + run: php ./tools/composer install --no-ansi --no-interaction --no-progress + + - name: Run tests with PHPUnit + run: php ./phpunit --testsuite end-to-end --order-by depends,random --display-all-issues + + code-coverage: + name: Code Coverage + + needs: + - end-to-end-tests + + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 1 + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Use local branch + shell: bash + run: | + BRANCH=$([ "${{ github.event_name }}" == "pull_request" ] && echo "${{ github.head_ref }}" || echo "${{ github.ref_name }}") + git branch -D $BRANCH 2>/dev/null || true + git branch $BRANCH HEAD + git checkout $BRANCH + + - name: Install PHP with extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ env.PHP_VERSION }} + coverage: xdebug + extensions: none, ctype, curl, dom, json, libxml, mbstring, pdo, phar, tokenizer, xml, xmlwriter, pcntl + ini-values: zend.assertions=1, error_reporting=-1, log_errors_max_len=0, display_errors=On + tools: none + + - name: Get Composer cache directory + id: composer-cache + shell: bash + run: | + echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Cache Composer cache directory + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies with Composer + run: ./tools/composer install --no-ansi --no-interaction --no-progress + + - name: Collect code coverage with PHPUnit + run: ./phpunit --log-junit test-results.xml --coverage-openclover=code-coverage.xml + + - name: Upload test results to Codecov.io + if: ${{ !cancelled() }} + uses: codecov/test-results-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + disable_search: true + files: ./test-results.xml + + - name: Upload code coverage data to Codecov.io + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + disable_search: true + files: ./code-coverage.xml + + build-phar: + name: Build PHAR + + needs: + - end-to-end-tests + + runs-on: ubuntu-latest + timeout-minutes: 10 + + env: + PHP_EXTENSIONS: none, ctype, dom, json, fileinfo, iconv, libxml, mbstring, phar, tokenizer, xml, xmlwriter, pcntl + PHP_INI_VALUES: phar.readonly=0, zend.assertions=1 + + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 1 + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Use local branch + shell: bash + run: | + BRANCH=$([ "${{ github.event_name }}" == "pull_request" ] && echo "${{ github.head_ref }}" || echo "${{ github.ref_name }}") + git branch -D $BRANCH 2>/dev/null || true + git branch $BRANCH HEAD + git checkout $BRANCH + + - name: Install PHP with extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ env.PHP_VERSION }} + coverage: none + extensions: ${{ env.PHP_EXTENSIONS }} + ini-values: ${{ env.PHP_INI_VALUES }} + tools: none + + - name: Install java + uses: actions/setup-java@v4 + with: + distribution: zulu + java-version: 11 + + - name: Build PHAR + run: ant phar-snapshot + + - name: Check whether PHAR is scoped + run: grep -q PHPUnitPHAR\\\\DeepCopy\\\\Exception\\\\CloneException build/artifacts/phpunit-snapshot.phar || (echo "phpunit-snapshot.phar is not scoped." && false) + + - name: Upload PHAR + uses: actions/upload-artifact@v4 + with: + name: phpunit-snapshot-phar + overwrite: true + path: ./build/artifacts/phpunit-snapshot.phar + retention-days: 7 + + test-phar: + name: Test PHAR + + needs: + - build-phar + + runs-on: ubuntu-latest + timeout-minutes: 10 + + env: + PHP_EXTENSIONS: none, ctype, curl, dom, json, fileinfo, iconv, libxml, mbstring, phar, tokenizer, xml, xmlwriter, pcntl + PHP_INI_VALUES: phar.readonly=0, zend.assertions=1 + + strategy: + fail-fast: false + matrix: + php-version: + - "8.4" + - "8.5" + - "8.6" + + coverage: + - pcov + - xdebug + + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 1 + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Use local branch + shell: bash + run: | + BRANCH=$([ "${{ github.event_name }}" == "pull_request" ] && echo "${{ github.head_ref }}" || echo "${{ github.ref_name }}") + git branch -D $BRANCH 2>/dev/null || true + git branch $BRANCH HEAD + git checkout $BRANCH + + - name: Install PHP with extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + coverage: ${{ matrix.coverage }} + extensions: ${{ env.PHP_EXTENSIONS }} + ini-values: ${{ env.PHP_INI_VALUES }} + tools: none + + - name: Install java + uses: actions/setup-java@v4 + with: + distribution: zulu + java-version: 11 + + - name: Download PHAR + uses: actions/download-artifact@v5 + with: + name: phpunit-snapshot-phar + path: ./build/artifacts/ + + - name: Make PHAR executable + run: chmod +x ./build/artifacts/phpunit-snapshot.phar + + - name: Run PHAR-specific tests + run: ant run-phar-specific-tests diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 49f4dfed487..00000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,183 +0,0 @@ -# https://help.github.com/en/categories/automating-your-workflow-with-github-actions - -on: - - pull_request - - push - -name: CI - -env: - COMPOSER_ROOT_VERSION: "9.3-dev" - -jobs: - coding-guidelines: - name: Coding Guidelines - - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install PHP - uses: shivammathur/setup-php@v2 - with: - php-version: 7.4 - coverage: none - - - name: Run friendsofphp/php-cs-fixer - run: ./tools/php-cs-fixer fix --diff-format=udiff --dry-run --show-progress=dots --using-cache=no --verbose - - type-checker: - name: Type Checker - - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install PHP - uses: shivammathur/setup-php@v2 - with: - php-version: 7.4 - coverage: none - - - name: Update dependencies with composer - run: ./tools/composer update --no-interaction --no-ansi --no-progress - - - name: Run vimeo/psalm on public API - run: ./tools/psalm --config=.psalm/static-analysis.xml --no-progress --show-info=false - - - name: Run vimeo/psalm on internal code - run: ./tools/psalm --config=.psalm/config.xml --no-progress --shepherd --show-info=false --stats - - backward-compatibility: - name: Backward Compatibility - - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Fetch tags - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - - - name: Install PHP with extensions - uses: shivammathur/setup-php@v2 - with: - php-version: 7.4 - coverage: none - extensions: intl - - - name: Run roave/backward-compatibility-check - run: ./tools/roave-backward-compatibility-check --from=9.2.4 - - lint-xml-configuration: - name: Lint XML Configuration - - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Build Docker image - uses: ./.docker/lint-xml-configuration - - - name: Lint XML configuration files - uses: ./.docker/lint-xml-configuration - with: - args: bash ./build/scripts/lint-xml-configuration - - tests: - name: Tests - - runs-on: ${{ matrix.os }} - - env: - PHP_EXTENSIONS: dom, json, libxml, mbstring, pdo_sqlite, soap, xml, xmlwriter - PHP_INI_VALUES: assert.exception=1, zend.assertions=1 - - strategy: - fail-fast: false - matrix: - os: - - ubuntu-latest - - windows-latest - - php-version: - - "7.3" - - "7.4" - - "8.0" - - compiler: - - default - - dependencies: - - lowest - - highest - - include: - - os: ubuntu-latest - php-version: "8.0" - compiler: jit - dependencies: highest - - steps: - - name: Configure git to avoid issues with line endings - if: matrix.os == 'windows-latest' - run: git config --global core.autocrlf false - - - name: Checkout - uses: actions/checkout@v2 - - - name: Override PHP ini values for JIT compiler - if: matrix.compiler == 'jit' - run: echo "::set-env name=PHP_INI_VALUES::assert.exception=1, zend.assertions=1, opcache.enable=1, opcache.enable_cli=1, opcache.optimization_level=-1, opcache.jit=1255, opcache.jit_buffer_size=4096M" - - - name: Install PHP with extensions - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - coverage: pcov - extensions: ${{ env.PHP_EXTENSIONS }} - ini-values: ${{ env.PHP_INI_VALUES }} - - - name: Determine composer cache directory on Linux - if: matrix.os == 'ubuntu-latest' - run: echo "::set-env name=COMPOSER_CACHE_DIR::$(./tools/composer config cache-dir)" - - - name: Determine composer cache directory on Windows - if: matrix.os == 'windows-latest' - run: ECHO "::set-env name=COMPOSER_CACHE_DIR::~\AppData\Local\Composer" - - - name: Cache dependencies installed with composer - uses: actions/cache@v1 - with: - path: ${{ env.COMPOSER_CACHE_DIR }} - key: php${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.json') }} - restore-keys: | - php${{ matrix.php-version }}-composer-${{ matrix.dependencies }}- - - - name: Install lowest dependencies with composer - if: matrix.dependencies == 'lowest' - run: php ./tools/composer update --no-ansi --no-interaction --no-progress --prefer-lowest - - - name: Install highest dependencies with composer - if: matrix.dependencies == 'highest' - run: php ./tools/composer update --no-ansi --no-interaction --no-progress - - - name: Run sanity check - run: bash ./build/scripts/sanity-check - - - name: Run tests with phpunit - run: php ./phpunit --coverage-clover=coverage.xml - - - name: Send code coverage report to Codecov.io - uses: codecov/codecov-action@v1 - with: - token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml new file mode 100644 index 00000000000..ca4e48e80b8 --- /dev/null +++ b/.github/workflows/nightly.yaml @@ -0,0 +1,156 @@ +# https://docs.github.com/en/actions + +on: + schedule: + - cron: "15 0 * * *" + workflow_dispatch: ~ + +name: Nightly + +permissions: + contents: read + +jobs: + run-tests: + name: Tests + + runs-on: ${{ matrix.os }} + timeout-minutes: 10 + + env: + PHP_EXTENSIONS: none, ctype, curl, dom, json, libxml, mbstring, openssl, pdo, phar, tokenizer, xml, xmlwriter + PHP_INI_VALUES: memory_limit=-1, zend.assertions=1, error_reporting=-1, log_errors_max_len=0, display_errors=On + + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + + phpunit-branch: + - main + - 12.5 + - 12.4 + - 11.5 + - 10.5 + - 9.6 + - 8.5 + + php-version: + - 7.2 + - 7.3 + - 7.4 + - 8.0 + - 8.1 + - 8.2 + - 8.3 + - 8.4 + - 8.5 + - 8.6 + + exclude: + - phpunit-branch: 8.5 + php-version: 7.2 + - phpunit-branch: 8.5 + php-version: 7.3 + - phpunit-branch: 9.6 + php-version: 7.2 + - phpunit-branch: 9.6 + php-version: 7.3 + - phpunit-branch: 10.5 + php-version: 7.2 + - phpunit-branch: 10.5 + php-version: 7.3 + - phpunit-branch: 10.5 + php-version: 7.4 + - phpunit-branch: 10.5 + php-version: 8.0 + - phpunit-branch: 11.5 + php-version: 7.2 + - phpunit-branch: 11.5 + php-version: 7.3 + - phpunit-branch: 11.5 + php-version: 7.4 + - phpunit-branch: 11.5 + php-version: 8.0 + - phpunit-branch: 11.5 + php-version: 8.1 + - phpunit-branch: 12.4 + php-version: 7.2 + - phpunit-branch: 12.4 + php-version: 7.3 + - phpunit-branch: 12.4 + php-version: 7.4 + - phpunit-branch: 12.4 + php-version: 8.0 + - phpunit-branch: 12.4 + php-version: 8.1 + - phpunit-branch: 12.4 + php-version: 8.2 + - phpunit-branch: 12.5 + php-version: 7.2 + - phpunit-branch: 12.5 + php-version: 7.3 + - phpunit-branch: 12.5 + php-version: 7.4 + - phpunit-branch: 12.5 + php-version: 8.0 + - phpunit-branch: 12.5 + php-version: 8.1 + - phpunit-branch: 12.5 + php-version: 8.2 + - phpunit-branch: main + php-version: 7.2 + - phpunit-branch: main + php-version: 7.3 + - phpunit-branch: main + php-version: 7.4 + - phpunit-branch: main + php-version: 8.0 + - phpunit-branch: main + php-version: 8.1 + - phpunit-branch: main + php-version: 8.2 + - phpunit-branch: main + php-version: 8.3 + + steps: + - name: Configure Git to avoid issues with line endings + if: matrix.os == 'windows-latest' + run: git config --global core.autocrlf false + + - name: Checkout + uses: actions/checkout@v5 + with: + ref: ${{ matrix.phpunit-branch }} + + - name: Install PHP with extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: ${{ env.PHP_EXTENSIONS }} + ini-values: ${{ env.PHP_INI_VALUES }} + tools: none + + - name: Get Composer cache directory + id: composer-cache + shell: bash + run: | + echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + + - name: Cache Composer cache directory + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies with Composer + run: php ./tools/composer install --no-ansi --no-interaction --no-progress + + - name: Run unit tests with PHPUnit + run: php ./phpunit --testsuite unit --order-by depends,random + + - name: Run end-to-end tests with PHPUnit + run: php ./phpunit --testsuite end-to-end --order-by depends,random diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 00000000000..b3ce97d19a2 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,54 @@ +# https://docs.github.com/en/actions + +on: + push: + tags: + - "**" + +name: Release + +jobs: + release: + name: Release + + runs-on: ubuntu-latest + timeout-minutes: 10 + + permissions: + contents: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install PHP with extensions + uses: shivammathur/setup-php@v2 + with: + php-version: 8.4 + coverage: none + extensions: none + tools: none + + - name: Determine tag + run: echo "RELEASE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV + + - name: Parse ChangeLog + run: build/scripts/extract-release-notes.php ${{ env.RELEASE_TAG }} > release-notes.md + + - name: Create release + uses: ncipollo/release-action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + tag: ${{ env.RELEASE_TAG }} + name: PHPUnit ${{ env.RELEASE_TAG }} + bodyFile: release-notes.md + commit: "13.0" + + - name: Announce release + id: mastodon + uses: cbrgm/mastodon-github-action@v2 + with: + access-token: ${{ secrets.MASTODON_ACCESS_TOKEN }} + url: ${{ secrets.MASTODON_URL }} + language: "en" + message: "#PHPUnit ${{ env.RELEASE_TAG }} has been released: https://github.com/sebastianbergmann/phpunit/releases/tag/${{ env.RELEASE_TAG }}" diff --git a/.gitignore b/.gitignore index a16a6520180..02631f2b73d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ # Composer /vendor -/composer.lock # Apache Ant /.ant_targets @@ -11,15 +10,14 @@ # Build artifacts and temporary files /build/artifacts /build/tmp +/tests/autoload.php # PHP-CS-Fixer -/.php_cs -/.php_cs.cache - -# Psalm -/.psalm/cache +/.php-cs-fixer.php +/.php-cs-fixer.cache # PHPUnit +/.phpunit.cache .phpunit.result.cache # Temporary files generated by PHPT test runner diff --git a/.phive/phars.xml b/.phive/phars.xml index 76d8a3afb1b..ed9f6e45d44 100644 --- a/.phive/phars.xml +++ b/.phive/phars.xml @@ -1,10 +1,7 @@ - - - - - - - + + + + diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 00000000000..d1a1a46a7e1 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,386 @@ + + +For the full copyright and license information, please view the LICENSE +file that was distributed with this source code. +EOF; + +$finder = PhpCsFixer\Finder::create() + ->files() + ->in(__DIR__ . '/src') + ->in(__DIR__ . '/tests/_files') + ->in(__DIR__ . '/tests/end-to-end') + ->in(__DIR__ . '/tests/unit') + // *WithPropertyWith*Hook.php use PHP 8.4 syntax that currently leads to PHP-CS-Fixer errors + ->notName('ExtendableClassWithPropertyWithGetHook.php') + ->notName('ExtendableClassWithPropertyWithSetHook.php') + ->notName('InterfaceWithPropertyWithGetHook.php') + ->notName('InterfaceWithPropertyWithSetHook.php') + // DeprecatedPhpFeatureTest.php must not use declare(strict_types=1); + ->notName('DeprecatedPhpFeatureTest.php') + // UseBaselineTest.php must not use declare(strict_types=1); + ->notName('UseBaselineTest.php') + // Issue5795Test.php contains required whitespace that would be cleaned up + ->notName('Issue5795Test.php') + // Hook methods that should be protected must be public in TestAttributeOnHookMethodsTest.php + ->notName('TestAttributeOnHookMethodsTest.php') + // InvokableConstraintAndPipeOperatorTest.php uses PHP 8.5 syntax + ->notName('InvokableConstraintAndPipeOperatorTest.php') + ->notName('*.phpt'); + +$config = new PhpCsFixer\Config; +$config->setFinder($finder) + ->setUnsupportedPhpVersionAllowed(true) + ->setRiskyAllowed(true) + ->setRules([ + 'align_multiline_comment' => true, + 'array_indentation' => true, + 'array_push' => true, + 'array_syntax' => ['syntax' => 'short'], + 'attribute_empty_parentheses' => [ + 'use_parentheses' => false, + ], + 'backtick_to_shell_exec' => true, + 'binary_operator_spaces' => [ + 'operators' => [ + '*=' => 'align_single_space_minimal', + '+=' => 'align_single_space_minimal', + '-=' => 'align_single_space_minimal', + '/=' => 'align_single_space_minimal', + '=' => 'align_single_space_minimal', + '=>' => 'align_single_space_minimal', + ], + ], + 'blank_line_after_namespace' => true, + 'blank_line_before_statement' => [ + 'statements' => [ + 'break', + 'case', + 'continue', + 'declare', + 'default', + 'do', + 'exit', + 'for', + 'foreach', + 'goto', + 'if', + 'include', + 'include_once', + 'phpdoc', + 'require', + 'require_once', + 'return', + 'switch', + 'throw', + 'try', + 'while', + 'yield', + 'yield_from', + ], + ], + 'blank_lines_before_namespace' => [ + 'max_line_breaks' => 1, + 'min_line_breaks' => 0, + ], + 'braces_position' => [ + 'anonymous_classes_opening_brace' => 'next_line_unless_newline_at_signature_end', + 'anonymous_functions_opening_brace' => 'next_line_unless_newline_at_signature_end', + ], + 'cast_spaces' => true, + 'class_attributes_separation' => [ + 'elements' => [ + 'const' => 'none', + 'method' => 'one', + 'property' => 'only_if_meta' + ] + ], + 'class_definition' => true, + 'clean_namespace' => true, + 'combine_consecutive_issets' => true, + 'combine_consecutive_unsets' => true, + 'combine_nested_dirname' => true, + 'compact_nullable_type_declaration' => true, + 'concat_space' => ['spacing' => 'one'], + 'constant_case' => true, + 'control_structure_braces' => true, + 'control_structure_continuation_position' => true, + 'declare_equal_normalize' => ['space' => 'none'], + 'declare_parentheses' => true, + 'declare_strict_types' => true, + 'dir_constant' => true, + 'echo_tag_syntax' => true, + 'elseif' => true, + 'encoding' => true, + 'ereg_to_preg' => true, + 'explicit_indirect_variable' => true, + 'explicit_string_variable' => true, + 'fopen_flag_order' => true, + 'full_opening_tag' => true, + 'fully_qualified_strict_types' => ['import_symbols' => true], + 'function_declaration' => true, + 'function_to_constant' => true, + 'get_class_to_class_keyword' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'header_comment' => ['header' => $header, 'separate' => 'none'], + 'heredoc_to_nowdoc' => true, + 'implode_call' => true, + 'include' => true, + 'increment_style' => [ + 'style' => 'post', + ], + 'indentation_type' => true, + 'integer_literal_case' => true, + 'is_null' => true, + 'lambda_not_used_import' => true, + 'line_ending' => true, + 'list_syntax' => ['syntax' => 'short'], + 'logical_operators' => true, + 'lowercase_cast' => true, + 'lowercase_keywords' => true, + 'lowercase_static_reference' => true, + 'magic_constant_casing' => true, + 'magic_method_casing' => true, + 'method_argument_space' => [ + 'on_multiline' => 'ensure_fully_multiline', + ], + 'method_chaining_indentation' => true, + 'modernize_strpos' => true, + 'modernize_types_casting' => true, + 'multiline_comment_opening_closing' => true, + 'multiline_whitespace_before_semicolons' => true, + 'native_constant_invocation' => true, + 'native_function_casing' => false, + 'native_function_invocation' => [ + 'include' => [ + '@internal', + ], + ], + 'native_type_declaration_casing' => true, + 'new_expression_parentheses' => true, + 'new_with_parentheses' => [ + 'anonymous_class' => false, + 'named_class' => false, + ], + 'no_alias_functions' => true, + 'no_alias_language_construct_call' => true, + 'no_alternative_syntax' => true, + 'no_binary_string' => true, + 'no_blank_lines_after_class_opening' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_break_comment' => true, + 'no_closing_tag' => true, + 'no_empty_comment' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_extra_blank_lines' => [ + 'tokens' => [ + 'attribute', + 'break', + 'case', + 'continue', + 'curly_brace_block', + 'default', + 'extra', + 'parenthesis_brace_block', + 'return', + 'square_brace_block', + 'switch', + 'throw', + 'use', + ], + ], + 'no_homoglyph_names' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_mixed_echo_print' => ['use' => 'print'], + 'no_multiline_whitespace_around_double_arrow' => true, + 'no_multiple_statements_per_line' => true, + 'no_null_property_initialization' => true, + 'no_php4_constructor' => true, + 'no_short_bool_cast' => true, + 'no_singleline_whitespace_before_semicolons' => true, + 'no_space_around_double_colon' => true, + 'no_spaces_after_function_name' => true, + 'no_spaces_around_offset' => true, + 'no_superfluous_elseif' => true, + 'no_superfluous_phpdoc_tags' => [ + 'allow_mixed' => true, + ], + 'no_trailing_comma_in_singleline' => true, + 'no_trailing_whitespace' => true, + 'no_trailing_whitespace_in_comment' => true, + 'no_trailing_whitespace_in_string' => true, + 'no_unneeded_braces' => true, + 'no_unneeded_control_parentheses' => true, + 'no_unneeded_final_method' => true, + 'no_unneeded_import_alias' => true, + 'no_unreachable_default_argument_value' => true, + 'no_unset_cast' => true, + 'no_unset_on_property' => true, + 'no_unused_imports' => true, + 'no_useless_concat_operator' => true, + 'no_useless_else' => true, + 'no_useless_nullsafe_operator' => true, + 'no_useless_return' => true, + 'no_useless_sprintf' => true, + 'no_whitespace_before_comma_in_array' => true, + 'no_whitespace_in_blank_line' => true, + 'non_printable_character' => true, + 'normalize_index_brace' => true, + 'nullable_type_declaration_for_default_null_value' => true, + 'object_operator_without_whitespace' => true, + 'octal_notation' => true, + 'operator_linebreak' => [ + 'only_booleans' => true, + 'position' => 'end', + ], + 'ordered_class_elements' => [ + 'order' => [ + 'use_trait', + 'constant_public', + 'constant_protected', + 'constant_private', + 'property_public_static', + 'property_protected_static', + 'property_private_static', + 'property_public', + 'property_protected', + 'property_private', + 'method_public_static', + 'construct', + 'destruct', + 'magic', + 'phpunit', + 'method_public', + 'method_protected', + 'method_private', + 'method_protected_static', + 'method_private_static', + ], + ], + 'ordered_imports' => [ + 'imports_order' => [ + 'const', + 'function', + 'class', + ] + ], + 'ordered_interfaces' => [ + 'direction' => 'ascend', + 'order' => 'alpha', + ], + 'ordered_traits' => true, + 'ordered_types' => true, + 'php_unit_set_up_tear_down_visibility' => true, + 'php_unit_test_case_static_method_calls' => [ + 'call_type' => 'this', + ], + 'phpdoc_add_missing_param_annotation' => false, + 'phpdoc_align' => true, + 'phpdoc_annotation_without_dot' => true, + 'phpdoc_indent' => true, + 'phpdoc_inline_tag_normalizer' => true, + 'phpdoc_no_access' => true, + 'phpdoc_no_alias_tag' => true, + 'phpdoc_no_empty_return' => true, + 'phpdoc_no_package' => true, + 'phpdoc_no_useless_inheritdoc' => true, + 'phpdoc_order' => true, + 'phpdoc_order_by_value' => [ + 'annotations' => [ + 'covers', + 'dataProvider', + 'throws', + 'uses', + ], + ], + 'phpdoc_param_order' => true, + 'phpdoc_return_self_reference' => true, + 'phpdoc_scalar' => true, + 'phpdoc_separation' => true, + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_summary' => true, + 'phpdoc_tag_casing' => true, + 'phpdoc_tag_type' => true, + 'phpdoc_to_comment' => false, + 'phpdoc_trim' => true, + 'phpdoc_trim_consecutive_blank_line_separation' => true, + 'phpdoc_types' => ['groups' => ['simple', 'meta']], + 'phpdoc_types_order' => true, + 'phpdoc_var_annotation_correct_order' => true, + 'phpdoc_var_without_name' => true, + 'pow_to_exponentiation' => true, + 'protected_to_private' => true, + 'return_assignment' => true, + 'return_type_declaration' => ['space_before' => 'none'], + 'self_accessor' => true, + 'self_static_accessor' => true, + 'semicolon_after_instruction' => true, + 'set_type_to_cast' => true, + 'short_scalar_cast' => true, + 'simple_to_complex_string_variable' => true, + 'simplified_null_return' => false, + 'single_blank_line_at_eof' => true, + 'single_class_element_per_statement' => true, + 'single_import_per_statement' => true, + 'single_line_after_imports' => true, + 'single_line_comment_spacing' => true, + 'single_quote' => true, + 'single_space_around_construct' => true, + 'single_trait_insert_per_statement' => true, + 'space_after_semicolon' => true, + 'spaces_inside_parentheses' => [ + 'space' => 'none', + ], + 'standardize_increment' => true, + 'standardize_not_equals' => true, + 'statement_indentation' => true, + 'static_lambda' => true, + 'strict_param' => true, + 'string_length_to_empty'=> true, + 'string_line_ending' => true, + 'switch_case_semicolon_to_colon' => true, + 'switch_case_space' => true, + 'switch_continue_to_break' => true, + 'ternary_operator_spaces' => true, + 'ternary_to_elvis_operator' => true, + 'ternary_to_null_coalescing' => true, + 'trailing_comma_in_multiline' => [ + 'elements' => [ + 'arguments', + 'arrays', + 'match', + ] + ], + 'trim_array_spaces' => true, + 'type_declaration_spaces' => [ + 'elements' => [ + 'function', + ], + ], + 'types_spaces' => true, + 'unary_operator_spaces' => true, + 'visibility_required' => [ + 'elements' => [ + 'const', + 'method', + 'property', + ], + ], + 'void_return' => true, + 'whitespace_after_comma_in_array' => true, + ]); + +$config->setCacheFile(__DIR__ . '/.php-cs-fixer.cache/' . json_decode((string) @file_get_contents('composer.json'), true)["extra"]["branch-alias"]["dev-main"] ?? 'unknown'); + +$config->setParallelConfig(\PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect()); + +return $config; diff --git a/.php_cs.dist b/.php_cs.dist deleted file mode 100644 index c393f18e056..00000000000 --- a/.php_cs.dist +++ /dev/null @@ -1,236 +0,0 @@ - - -For the full copyright and license information, please view the LICENSE -file that was distributed with this source code. -EOF; - -$finder = PhpCsFixer\Finder::create() - ->files() - ->in(__DIR__ . '/src') - ->in(__DIR__ . '/tests/basic') - ->in(__DIR__ . '/tests/end-to-end') - ->in(__DIR__ . '/tests/fail') - ->in(__DIR__ . '/tests/unit') - ->in(__DIR__ . '/tests/_files') - ->notName('*.phpt') - ->notName('ClassWithAllPossibleReturnTypes.php') - ->notName('ClassWithUnionReturnTypes.php'); - -return PhpCsFixer\Config::create() - ->setFinder($finder) - ->setRiskyAllowed(true) - ->setRules([ - 'align_multiline_comment' => true, - 'array_indentation' => true, - 'array_syntax' => ['syntax' => 'short'], - 'binary_operator_spaces' => [ - 'operators' => [ - '=' => 'align_single_space_minimal', - '=>' => 'align_single_space_minimal', - ], - ], - 'blank_line_after_namespace' => true, - 'blank_line_before_statement' => [ - 'statements' => [ - 'break', - 'continue', - 'declare', - 'default', - 'die', - 'do', - 'exit', - 'for', - 'foreach', - 'goto', - 'if', - 'include', - 'include_once', - 'require', - 'require_once', - 'return', - 'switch', - 'throw', - 'try', - 'while', - 'yield', - ], - ], - 'braces' => true, - 'cast_spaces' => true, - 'class_attributes_separation' => ['elements' => ['const', 'method', 'property']], - 'combine_consecutive_issets' => true, - 'combine_consecutive_unsets' => true, - 'compact_nullable_typehint' => true, - 'concat_space' => ['spacing' => 'one'], - 'constant_case' => true, - 'declare_equal_normalize' => ['space' => 'none'], - 'declare_strict_types' => true, - 'dir_constant' => true, - 'elseif' => true, - 'encoding' => true, - 'explicit_indirect_variable' => true, - 'explicit_string_variable' => true, - 'full_opening_tag' => true, - 'fully_qualified_strict_types' => true, - 'function_declaration' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'header_comment' => ['header' => $header, 'separate' => 'none'], - 'heredoc_to_nowdoc' => true, - 'increment_style' => [ - 'style' => PhpCsFixer\Fixer\Operator\IncrementStyleFixer::STYLE_POST, - ], - 'indentation_type' => true, - 'is_null' => true, - 'line_ending' => true, - 'list_syntax' => ['syntax' => 'short'], - 'logical_operators' => true, - 'lowercase_keywords' => true, - 'lowercase_static_reference' => true, - 'magic_constant_casing' => true, - 'magic_method_casing' => true, - 'method_argument_space' => ['ensure_fully_multiline' => true], - 'modernize_types_casting' => true, - 'multiline_comment_opening_closing' => true, - 'multiline_whitespace_before_semicolons' => true, - 'native_constant_invocation' => false, - 'native_function_casing' => false, - 'native_function_invocation' => false, - 'native_function_type_declaration_casing' => true, - 'new_with_braces' => false, - 'no_alias_functions' => true, - 'no_alternative_syntax' => true, - 'no_blank_lines_after_class_opening' => true, - 'no_blank_lines_after_phpdoc' => true, - 'no_blank_lines_before_namespace' => true, - 'no_closing_tag' => true, - 'no_empty_comment' => true, - 'no_empty_phpdoc' => true, - 'no_empty_statement' => true, - 'no_extra_blank_lines' => true, - 'no_homoglyph_names' => true, - 'no_leading_import_slash' => true, - 'no_leading_namespace_whitespace' => true, - 'no_mixed_echo_print' => ['use' => 'print'], - 'no_multiline_whitespace_around_double_arrow' => true, - 'no_null_property_initialization' => true, - 'no_php4_constructor' => true, - 'no_short_bool_cast' => true, - 'no_short_echo_tag' => true, - 'no_singleline_whitespace_before_semicolons' => true, - 'no_spaces_after_function_name' => true, - 'no_spaces_around_offset' => true, - 'no_spaces_inside_parenthesis' => true, - 'no_superfluous_elseif' => true, - 'no_superfluous_phpdoc_tags' => [ - 'allow_mixed' => true, - ], - 'no_trailing_comma_in_list_call' => true, - 'no_trailing_comma_in_singleline_array' => true, - 'no_trailing_whitespace' => true, - 'no_trailing_whitespace_in_comment' => true, - 'no_unneeded_control_parentheses' => true, - 'no_unneeded_curly_braces' => true, - 'no_unneeded_final_method' => true, - 'no_unreachable_default_argument_value' => true, - 'no_unset_on_property' => true, - 'no_unused_imports' => true, - 'no_useless_else' => true, - 'no_useless_return' => true, - 'no_whitespace_before_comma_in_array' => true, - 'no_whitespace_in_blank_line' => true, - 'non_printable_character' => true, - 'normalize_index_brace' => true, - 'object_operator_without_whitespace' => true, - 'ordered_class_elements' => [ - 'order' => [ - 'use_trait', - 'constant_public', - 'constant_protected', - 'constant_private', - 'property_public_static', - 'property_protected_static', - 'property_private_static', - 'property_public', - 'property_protected', - 'property_private', - 'method_public_static', - 'construct', - 'destruct', - 'magic', - 'phpunit', - 'method_public', - 'method_protected', - 'method_private', - 'method_protected_static', - 'method_private_static', - ], - ], - 'ordered_imports' => [ - 'imports_order' => [ - PhpCsFixer\Fixer\Import\OrderedImportsFixer::IMPORT_TYPE_CONST, - PhpCsFixer\Fixer\Import\OrderedImportsFixer::IMPORT_TYPE_FUNCTION, - PhpCsFixer\Fixer\Import\OrderedImportsFixer::IMPORT_TYPE_CLASS, - ] - ], - 'ordered_interfaces' => [ - 'direction' => 'ascend', - 'order' => 'alpha', - ], - 'phpdoc_add_missing_param_annotation' => false, - 'phpdoc_align' => true, - 'phpdoc_annotation_without_dot' => true, - 'phpdoc_indent' => true, - 'phpdoc_no_access' => true, - 'phpdoc_no_empty_return' => true, - 'phpdoc_no_package' => true, - 'phpdoc_order' => true, - 'phpdoc_return_self_reference' => true, - 'phpdoc_scalar' => true, - 'phpdoc_separation' => true, - 'phpdoc_single_line_var_spacing' => true, - 'phpdoc_summary' => true, - 'phpdoc_to_comment' => true, - 'phpdoc_trim' => true, - 'phpdoc_trim_consecutive_blank_line_separation' => true, - 'phpdoc_types' => ['groups' => ['simple', 'meta']], - 'phpdoc_types_order' => true, - 'phpdoc_var_without_name' => true, - 'pow_to_exponentiation' => true, - 'protected_to_private' => true, - 'return_assignment' => true, - 'return_type_declaration' => ['space_before' => 'none'], - 'self_accessor' => true, - 'self_static_accessor' => true, - 'semicolon_after_instruction' => true, - 'set_type_to_cast' => true, - 'short_scalar_cast' => true, - 'simple_to_complex_string_variable' => true, - 'simplified_null_return' => false, - 'single_blank_line_at_eof' => true, - 'single_import_per_statement' => true, - 'single_line_after_imports' => true, - 'single_quote' => true, - 'standardize_not_equals' => true, - 'strict_param' => true, - 'ternary_to_null_coalescing' => true, - 'trailing_comma_in_multiline_array' => true, - 'trim_array_spaces' => true, - 'unary_operator_spaces' => true, - 'visibility_required' => [ - 'elements' => [ - 'const', - 'method', - 'property', - ], - ], - 'void_return' => true, - 'whitespace_after_comma_in_array' => true, - ]); diff --git a/.phpstorm.meta.php b/.phpstorm.meta.php deleted file mode 100644 index 5e4c4c2d7dd..00000000000 --- a/.phpstorm.meta.php +++ /dev/null @@ -1,45 +0,0 @@ - - - - - $expectedElement->childNodes->item($i) - $actualElement->childNodes->item($i) - - - $expected - $expected - - - - - Assert::anything(...func_get_args()) - Assert::isTrue(...func_get_args()) - Assert::isFalse(...func_get_args()) - Assert::isJson(...func_get_args()) - Assert::isNull(...func_get_args()) - Assert::isFinite(...func_get_args()) - Assert::isInfinite(...func_get_args()) - Assert::isNan(...func_get_args()) - Assert::isEmpty(...func_get_args()) - Assert::isWritable(...func_get_args()) - Assert::isReadable(...func_get_args()) - Assert::directoryExists(...func_get_args()) - Assert::fileExists(...func_get_args()) - - - - - $other - - - - - $other - - - - - $other - - - - - $this->className - - - - - $className - - - - - $comparisonFailure - - - - - $t->getPrevious() - - - - - $className - $className - $interfaceName - $className - - - - MockObject&RealInstanceType - - - strpos($args[$i], '$') - - - - - $this->type - $this->type - $this->type - - - $object - $object - $object - - - MockObject&MockedType - MockObject&MockedType - MockObject&MockedType - - - $type - - - - - $type instanceof ReflectionUnionType - - - ReflectionUnionType - ReflectionUnionType - - - - - $invocation === null - - - - - null - - - - - $this->expectedException - - - $header - - - - get_class($mock) - $mockObject - - - class-string<MockObject&RealInstanceType> - MockObject&RealInstanceType - - - null - $beStrictAboutChangesToGlobalState - null - - - $categories - - - - - getDiff - - - - - $isAnyCoverageRequired - - - - - $className - $className - - - $afterClassMethod - - - - - $sections['FILEEOF'] - - - $sectionOffset - - - - - $suiteClassName - - - - - $printerClass - - - $class->newInstance($outputStream) - - - null|Printer|string - - - $suite - $suite - $suite - $suite - - - - - $option['desc'] - $option['desc'] - - - - - - $loader - - - - - - - - $i - $i - $i - - - $long_options - - - - - $constants['user'] - - - - - $suite->getName() - - - $this->testSuiteTimes - - - null - - - getName - getName - - - - - $className - - - getName - getName - getName - getName - getName - getName - getName - - - - - $this->getException($warnings[0]) - $this->getException($failures[0]) - - - - $exception - - - Exception - - - strrpos($key, "\0") - - - $childResult->getCodeCoverage() - - - merge - - - setResult - addToAssertionCount - - - - - $prefix['start'] - $prefix['message'] - $prefix['diff'] - $prefix['default'] - $prefix['trace'] - $prefix['last'] - - - - - $this->currentTestClassPrettified - - - - - $value - - - $name - $name - - - - - getName - - - - - $suite->getIterator() - - - - - $item - - - (new DOMDocument)->importNode($element, true) - - - DOMElement - - - - - $suite->getIterator() - - - diff --git a/.psalm/config.xml b/.psalm/config.xml deleted file mode 100644 index f8db545fd8e..00000000000 --- a/.psalm/config.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.psalm/static-analysis.xml b/.psalm/static-analysis.xml deleted file mode 100644 index 8d921fe9f19..00000000000 --- a/.psalm/static-analysis.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ChangeLog-13.0.md b/ChangeLog-13.0.md new file mode 100644 index 00000000000..552565331d3 --- /dev/null +++ b/ChangeLog-13.0.md @@ -0,0 +1,20 @@ +# Changes in PHPUnit 13.0 + +All notable changes of the PHPUnit 13.0 release series are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. + +## [13.0.0] - 2026-02-06 + +### Removed + +* [#6054](https://github.com/sebastianbergmann/phpunit/issues/6054): `Assert::isType()` +* [#6057](https://github.com/sebastianbergmann/phpunit/issues/6057): `assertContainsOnly()` and `assertNotContainsOnly()` +* [#6061](https://github.com/sebastianbergmann/phpunit/issues/6061): `containsOnly()` +* [#6076](https://github.com/sebastianbergmann/phpunit/issues/6076): Support for PHP 8.3 +* [#6141](https://github.com/sebastianbergmann/phpunit/issues/6141): `testClassName()` method on event value objects for hook methods called for test methods +* [#6230](https://github.com/sebastianbergmann/phpunit/issues/6230): `Configuration::includeTestSuite()` and `Configuration::excludeTestSuite()` +* [#6241](https://github.com/sebastianbergmann/phpunit/issues/6241): `--dont-report-useless-tests` CLI option +* [#6247](https://github.com/sebastianbergmann/phpunit/issues/6247): Support for using `#[CoversNothing]` on a test method +* [#6285](https://github.com/sebastianbergmann/phpunit/issues/6285): `#[RunClassInSeparateProcess]` attribute +* [#6356](https://github.com/sebastianbergmann/phpunit/issues/6356): Support for version constraint string argument without explicit version comparison operator + +[13.0.0]: https://github.com/sebastianbergmann/phpunit/compare/12.5...main diff --git a/ChangeLog-8.5.md b/ChangeLog-8.5.md deleted file mode 100644 index 8a8db4960c6..00000000000 --- a/ChangeLog-8.5.md +++ /dev/null @@ -1,88 +0,0 @@ -# Changes in PHPUnit 8.5 - -All notable changes of the PHPUnit 8.5 release series are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. - -## [8.5.8] - 2020-06-22 - -### Fixed - -* [#4312](https://github.com/sebastianbergmann/phpunit/issues/4312): Fix for [#4299](https://github.com/sebastianbergmann/phpunit/issues/4299) breaks backward compatibility - -## [8.5.7] - 2020-06-21 - -### Fixed - -* [#4299](https://github.com/sebastianbergmann/phpunit/issues/4299): "No tests executed" does not always result in exit code `1` -* [#4306](https://github.com/sebastianbergmann/phpunit/issues/4306): Exceptions during code coverage driver initialization are not handled correctly - -## [8.5.6] - 2020-06-15 - -### Fixed - -* [#4211](https://github.com/sebastianbergmann/phpunit/issues/4211): `phpdbg_*()` functions are scoped to `PHPUnit\phpdbg_*()` - -## [8.5.5] - 2020-05-22 - -### Fixed - -* [#4033](https://github.com/sebastianbergmann/phpunit/issues/4033): Unexpected behaviour when `$GLOBALS` is deleted - -## [8.5.4] - 2020-04-23 - -### Changed - -* Changed how `PHPUnit\TextUI\Command` passes warnings to `PHPUnit\TextUI\TestRunner` - -## [8.5.3] - 2020-03-31 - -### Fixed - -* [#4017](https://github.com/sebastianbergmann/phpunit/issues/4017): Do not suggest refactoring to something that is also deprecated -* [#4133](https://github.com/sebastianbergmann/phpunit/issues/4133): `expectExceptionMessageRegExp()` has been removed in PHPUnit 9 without a deprecation warning being given in PHPUnit 8 -* [#4139](https://github.com/sebastianbergmann/phpunit/issues/4139): Cannot double interfaces that declare a constructor with PHP 8 -* [#4144](https://github.com/sebastianbergmann/phpunit/issues/4144): Empty objects are converted to empty arrays in JSON comparison failure diff - -## [8.5.2] - 2020-01-08 - -### Removed - -* `eval-stdin.php` has been removed, it was not used anymore since PHPUnit 7.2.7 - -## [8.5.1] - 2019-12-25 - -### Changed - -* `eval-stdin.php` can now only be executed with `cli` and `phpdbg` - -### Fixed - -* [#3983](https://github.com/sebastianbergmann/phpunit/issues/3983): Deprecation warning given too eagerly - -## [8.5.0] - 2019-12-06 - -### Added - -* [#3911](https://github.com/sebastianbergmann/phpunit/issues/3911): Support combined use of `addMethods()` and `onlyMethods()` -* [#3949](https://github.com/sebastianbergmann/phpunit/issues/3949): Introduce specialized assertions `assertFileEqualsCanonicalizing()`, `assertFileEqualsIgnoringCase()`, `assertStringEqualsFileCanonicalizing()`, `assertStringEqualsFileIgnoringCase()`, `assertFileNotEqualsCanonicalizing()`, `assertFileNotEqualsIgnoringCase()`, `assertStringNotEqualsFileCanonicalizing()`, and `assertStringNotEqualsFileIgnoringCase()` as alternative to using `assertFileEquals()` etc. with optional parameters - -### Changed - -* [#3860](https://github.com/sebastianbergmann/phpunit/pull/3860): Deprecate invoking PHPUnit commandline test runner with just a class name -* [#3950](https://github.com/sebastianbergmann/phpunit/issues/3950): Deprecate optional parameters of `assertFileEquals()` etc. -* [#3955](https://github.com/sebastianbergmann/phpunit/issues/3955): Deprecate support for doubling multiple interfaces - -### Fixed - -* [#3953](https://github.com/sebastianbergmann/phpunit/issues/3953): Code Coverage for test executed in isolation does not work when the PHAR is used -* [#3967](https://github.com/sebastianbergmann/phpunit/issues/3967): Cannot double interface that extends interface that extends `\Throwable` -* [#3968](https://github.com/sebastianbergmann/phpunit/pull/3968): Test class run in a separate PHP process are passing when `exit` called inside - -[8.5.8]: https://github.com/sebastianbergmann/phpunit/compare/8.5.7...8.5.8 -[8.5.7]: https://github.com/sebastianbergmann/phpunit/compare/8.5.6...8.5.7 -[8.5.6]: https://github.com/sebastianbergmann/phpunit/compare/8.5.5...8.5.6 -[8.5.5]: https://github.com/sebastianbergmann/phpunit/compare/8.5.4...8.5.5 -[8.5.4]: https://github.com/sebastianbergmann/phpunit/compare/8.5.3...8.5.4 -[8.5.3]: https://github.com/sebastianbergmann/phpunit/compare/8.5.2...8.5.3 -[8.5.2]: https://github.com/sebastianbergmann/phpunit/compare/8.5.1...8.5.2 -[8.5.1]: https://github.com/sebastianbergmann/phpunit/compare/8.5.0...8.5.1 -[8.5.0]: https://github.com/sebastianbergmann/phpunit/compare/8.4.3...8.5.0 diff --git a/ChangeLog-9.2.md b/ChangeLog-9.2.md deleted file mode 100644 index 03b35a3d13b..00000000000 --- a/ChangeLog-9.2.md +++ /dev/null @@ -1,58 +0,0 @@ -# Changes in PHPUnit 9.2 - -All notable changes of the PHPUnit 9.2 release series are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. - -## [9.2.5] - 2020-06-22 - -### Fixed - -* [#4312](https://github.com/sebastianbergmann/phpunit/issues/4312): Fix for [#4299](https://github.com/sebastianbergmann/phpunit/issues/4299) breaks backward compatibility - -## [9.2.4] - 2020-06-21 - -### Fixed - -* [#4291](https://github.com/sebastianbergmann/phpunit/issues/4291): [#4258](https://github.com/sebastianbergmann/phpunit/pull/4258) breaks backward compatibility -* [#4299](https://github.com/sebastianbergmann/phpunit/issues/4299): "No tests executed" does not always result in exit code `1` -* [#4306](https://github.com/sebastianbergmann/phpunit/issues/4306): Exceptions during code coverage driver initialization are not handled correctly - -## [9.2.3] - 2020-06-15 - -### Fixed - -* [#4211](https://github.com/sebastianbergmann/phpunit/issues/4211): `phpdbg_*()` functions are scoped to `PHPUnit\phpdbg_*()` - -## [9.2.2] - 2020-06-07 - -### Changed - -* Improved message of exception that is raised when multiple matchers can be applied to a test double invocation - -### Fixed - -* Fixed default values for `lowUpperBound` and `highLowerBound` in `phpunit.xsd` - -## [9.2.1] - 2020-06-05 - -### Fixed - -* [#4269](https://github.com/sebastianbergmann/phpunit/issues/4269): Test with `@coversNothing` annotation is wrongly marked as risky with `forceCoversAnnotation="true"` - -## [9.2.0] - 2020-06-05 - -### Added - -* [#4224](https://github.com/sebastianbergmann/phpunit/issues/4224): Support for Union Types for test double code generation - -### Changed - -* [#4246](https://github.com/sebastianbergmann/phpunit/issues/4246): Tests that are supposed to have a `@covers` annotation are now marked as risky even if code coverage is not collected -* [#4258](https://github.com/sebastianbergmann/phpunit/pull/4258): Prevent unpredictable result by raising an exception when multiple matchers can be applied to a test double invocation -* The test runner no longer relies on `$_SERVER['REQUEST_TIME_FLOAT']` for printing the elapsed time - -[9.2.5]: https://github.com/sebastianbergmann/phpunit/compare/9.2.4...9.2.5 -[9.2.4]: https://github.com/sebastianbergmann/phpunit/compare/9.2.3...9.2.4 -[9.2.3]: https://github.com/sebastianbergmann/phpunit/compare/9.2.2...9.2.3 -[9.2.2]: https://github.com/sebastianbergmann/phpunit/compare/9.2.1...9.2.2 -[9.2.1]: https://github.com/sebastianbergmann/phpunit/compare/9.2.0...9.2.1 -[9.2.0]: https://github.com/sebastianbergmann/phpunit/compare/9.1.5...9.2.0 diff --git a/ChangeLog-9.3.md b/ChangeLog-9.3.md deleted file mode 100644 index cd2b0608035..00000000000 --- a/ChangeLog-9.3.md +++ /dev/null @@ -1,42 +0,0 @@ -# Changes in PHPUnit 9.3 - -All notable changes of the PHPUnit 9.3 release series are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. - -## [9.3.0] - 2020-08-07 - -### Added - -* [#3936](https://github.com/sebastianbergmann/phpunit/pull/3936): Support using `@depends` to depend on classes -* [#4260](https://github.com/sebastianbergmann/phpunit/issues/4260): `pathCoverage` attribute on the `phpunit/coverage` element of the XML configuration file for enabling path coverage for code coverage drivers that support it -* [#4314](https://github.com/sebastianbergmann/phpunit/issues/4314): Add option to exit with exit code `1` when no tests are executed - -### Changed - -* [#4226](https://github.com/sebastianbergmann/phpunit/issues/4226): Deprecate `--dump-xdebug-filter` and `--prepend` -* [#4264](https://github.com/sebastianbergmann/phpunit/pull/4264): Refactor logical operator constraints -* `PHPUnit\Framework\TestCase::$backupGlobalsBlacklist` is now deprecated, please use `PHPUnit\Framework\TestCase::$backupGlobalsExcludeList` instead -* `PHPUnit\Framework\TestCase::$backupStaticAttributesBlacklist` is now deprecated, please use `PHPUnit\Framework\TestCase::$backupStaticAttributesExcludeList` instead -* `PHPUnit\Util\Blacklist` is now deprecated, please use `PHPUnit\Util\ExcludeList` instead -* Using `--whitelist ` to include a directory in code coverage reports is now deprecated, please use `--coverage-filter ` instead -* Using `......` instead -* Using `......` instead -* Using `...` to control whether or not uncovered files should be added to code coverage reports is now deprecated, please use `...` instead -* Using `...` to control whether or not uncovered files should be processed for code coverage reporting is now deprecated, please use `...` instead -* Using `...` to configure the token cache (which can reduce the time needed to process multiple code coverage reports) is now deprecated, please use `...` instead -* Using `...` to configure whether `@coverCoverageIgnore` annotations should be ignored is now deprecated, please use `...` instead -* Using `...` to configure whether code units annotated with `@deprecated` should be ignored is now deprecated, please use `...` instead -* Using `` to configure the Clover XML code coverage report is now deprecated, please use `` instead -* Using `` to configure the Crap4J XML code coverage report is now deprecated, please use `` instead -* Using `` to configure the HTML code coverage report is now deprecated, please use `` instead -* Using `` to configure the PHP code coverage report is now deprecated, please use `` instead -* Using `` to configure the Text code coverage report is now deprecated, please use `` instead -* Using `` to configure the XML code coverage report is now deprecated, please use `` instead -* Using `` to configure the JUnit XML logger is now deprecated, please use `` instead -* Using `` to configure the TeamCity logger is now deprecated, please use `` instead -* Using `` to configure the TestDox HTML logger is now deprecated, please use `` instead -* Using `` to configure the TestDox Text logger is now deprecated, please use `` instead -* Using `` to configure the TestDox XML logger is now deprecated, please use `` instead -* Using `` to configure the plain text logger is now deprecated, please use `` instead -* `--generate-configuration` generates a configuration file with `failOnRisky="true"` and `failOnWarning="true"` - -[9.3.0]: https://github.com/sebastianbergmann/phpunit/compare/9.2...master diff --git a/LICENSE b/LICENSE index 3b0d45c9f92..b687f39ac47 100644 --- a/LICENSE +++ b/LICENSE @@ -1,33 +1,29 @@ -PHPUnit +BSD 3-Clause License -Copyright (c) 2001-2020, Sebastian Bergmann . +Copyright (c) 2001-2025, Sebastian Bergmann All rights reserved. Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: +modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. - * Neither the name of Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 07848aa0ed8..189d75f8563 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,109 @@ -# PHPUnit +[![PHPUnit](.github/img/phpunit.svg)](https://phpunit.de/?ref=github) + +[![CI Status](https://github.com/sebastianbergmann/phpunit/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/phpunit/actions) +[![codecov](https://codecov.io/gh/sebastianbergmann/phpunit/branch/main/graph/badge.svg?token=0yzBUK8Wri)](https://codecov.io/gh/sebastianbergmann/phpunit) +[![Latest Stable Version](https://poser.pugx.org/phpunit/phpunit/v)](https://packagist.org/packages/phpunit/phpunit) +[![Total Downloads](https://poser.pugx.org/phpunit/phpunit/downloads)](https://packagist.org/packages/phpunit/phpunit/stats) +[![Monthly Downloads](https://poser.pugx.org/phpunit/phpunit/d/monthly)](https://packagist.org/packages/phpunit/phpunit/stats) +[![Daily Downloads](https://poser.pugx.org/phpunit/phpunit/d/daily)](https://packagist.org/packages/phpunit/phpunit/stats) -PHPUnit is a programmer-oriented testing framework for PHP. It is an instance of the xUnit architecture for unit testing frameworks. +# PHPUnit -[![Latest Stable Version](https://img.shields.io/packagist/v/phpunit/phpunit.svg?style=flat-square)](https://packagist.org/packages/phpunit/phpunit) -[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.3-8892BF.svg?style=flat-square)](https://php.net/) -[![CI Status](https://github.com/sebastianbergmann/phpunit/workflows/CI/badge.svg?branch=master&event=push)](https://phpunit.de/build-status.html) -[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/phpunit/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/phpunit) +PHPUnit is a programmer-oriented testing framework for PHP. +It is an instance of the xUnit architecture for unit testing frameworks. ## Installation -We distribute a [PHP Archive (PHAR)](https://php.net/phar) that has all required (as well as some optional) dependencies of PHPUnit 9.3 bundled in a single file: +We distribute a [PHP Archive (PHAR)](https://php.net/phar) that has all required dependencies of PHPUnit bundled in a single file: ```bash -$ wget https://phar.phpunit.de/phpunit-nightly.phar +$ wget https://phar.phpunit.de/phpunit-X.Y.phar -$ php phpunit-nightly.phar --version +$ php phpunit-X.Y.phar --version ``` -Alternatively, you may use [Composer](https://getcomposer.org/) to download and install PHPUnit as well as its dependencies. Please refer to the "[Getting Started](https://phpunit.de/getting-started-with-phpunit.html)" guide for details on how to install PHPUnit. +Please replace `X.Y` with the version of PHPUnit you are interested in. + +Alternatively, you may use [Composer](https://getcomposer.org/) to download and install PHPUnit as well as its dependencies. +Please refer to the [documentation](https://phpunit.de/documentation.html?ref=github) for details on how to install PHPUnit. ## Contribute -Please refer to [CONTRIBUTING.md](https://github.com/sebastianbergmann/phpunit/blob/master/.github/CONTRIBUTING.md) for information on how to contribute to PHPUnit and its related projects. +Please refer to [CONTRIBUTING.md](https://github.com/sebastianbergmann/phpunit/blob/main/.github/CONTRIBUTING.md) for information on how to contribute to PHPUnit and its related projects. + +A big "Thank you!" to everyone who has contributed to PHPUnit! +You can find a detailed list of contributors on every PHPUnit related package on GitHub. + +Here is a list of all components that are primarily developed and maintained by [Sebastian Bergmann](https://sebastian-bergmann.de/open-source.html?ref=github): + +* [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) +* [phpunit/php-code-coverage](https://github.com/sebastianbergmann/php-code-coverage) +* [phpunit/php-file-iterator](https://github.com/sebastianbergmann/php-file-iterator) +* [phpunit/php-invoker](https://github.com/sebastianbergmann/php-invoker) +* [phpunit/php-text-template](https://github.com/sebastianbergmann/php-text-template) +* [phpunit/php-timer](https://github.com/sebastianbergmann/php-timer) +* [sebastian/cli-parser](https://github.com/sebastianbergmann/cli-parser) +* [sebastian/comparator](https://github.com/sebastianbergmann/comparator) +* [sebastian/complexity](https://github.com/sebastianbergmann/complexity) +* [sebastian/diff](https://github.com/sebastianbergmann/diff) +* [sebastian/environment](https://github.com/sebastianbergmann/environment) +* [sebastian/exporter](https://github.com/sebastianbergmann/exporter) +* [sebastian/global-state](https://github.com/sebastianbergmann/global-state) +* [sebastian/lines-of-code](https://github.com/sebastianbergmann/lines-of-code) +* [sebastian/object-enumerator](https://github.com/sebastianbergmann/object-enumerator) +* [sebastian/object-reflector](https://github.com/sebastianbergmann/object-reflector) +* [sebastian/recursion-context](https://github.com/sebastianbergmann/recursion-context) +* [sebastian/type](https://github.com/sebastianbergmann/type) +* [sebastian/version](https://github.com/sebastianbergmann/version) + +A very special thanks to everyone who has contributed to the [PHPUnit Manual](https://github.com/sebastianbergmann/phpunit-documentation-english). + +In addition to the components listed above, PHPUnit depends on the components listed below: + +* [myclabs/deep-copy](https://github.com/myclabs/DeepCopy) +* [nikic/php-parser](https://github.com/nikic/php-parser) +* [phar-io/manifest](https://github.com/phar-io/manifest) +* [phar-io/version](https://github.com/phar-io/version) +* [staabm/side-effects-detector](https://github.com/staabm/side-effects-detector) +* [theseer/tokenizer](https://github.com/theseer/tokenizer) + +These tools are used to develop PHPUnit: + +* [Composer](https://getcomposer.org/) +* [Phive](https://phar.io/) +* [PHP Autoload Builder](https://github.com/theseer/Autoload/) +* [PHP-CS-Fixer](https://cs.symfony.com/) +* [PHP-Scoper](https://github.com/humbug/php-scoper) +* [PHPStan](https://phpstan.org/) -## List of Contributors +## Sponsors -Thanks to everyone who has contributed to PHPUnit! You can find a detailed list of contributors on every PHPUnit related package on GitHub. This list shows only the major components: +It has taken [Sebastian Bergmann](https://sebastian-bergmann.de/open-source.html?ref=github) thousands of hours to develop, test, and support PHPUnit. +[**You can sponsor his Open Source work through GitHub Sponsors**](https://github.com/sponsors/sebastianbergmann), for example. -* [PHPUnit](https://github.com/sebastianbergmann/phpunit/graphs/contributors) -* [php-code-coverage](https://github.com/sebastianbergmann/php-code-coverage/graphs/contributors) +These businesses support Sebastian Bergmann's work on PHPUnit: -A very special thanks to everyone who has contributed to the documentation and helps maintain the translations: + + + + + + + + + + + + + + + + + +
Bubble Shooterin2it vofLambdaTest
RoaveTestmo GmbHTideways GmbH
TYPO3 GmbHVEMA Versicherungsmakler Genossenschaft eG
-* [English](https://github.com/sebastianbergmann/phpunit-documentation-english/graphs/contributors) -* [Spanish](https://github.com/sebastianbergmann/phpunit-documentation-spanish/graphs/contributors) -* [French](https://github.com/sebastianbergmann/phpunit-documentation-french/graphs/contributors) -* [Japanese](https://github.com/sebastianbergmann/phpunit-documentation-japanese/graphs/contributors) -* [Brazilian Portuguese](https://github.com/sebastianbergmann/phpunit-documentation-brazilian-portuguese/graphs/contributors) -* [Simplified Chinese](https://github.com/sebastianbergmann/phpunit-documentation-chinese/graphs/contributors) +Would you like to see your logo here as well as on the [PHPUnit website](https://phpunit.de/sponsors.html?ref=github)? +Contact Sebastian Bergmann at [sponsoring@phpunit.de](mailto:sponsoring@phpunit.de) to learn more about how you can support his work on PHPUnit. +Whether you are a CEO, CFO, CTO, or a developer: your company surely depends on Open Source software. +[It is time to pay your share](https://opensourcepledge.com/) and support maintainers like [Sebastian Bergmann](https://sebastian-bergmann.de/open-source.html?ref=github). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000000..5f55c41947b --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,33 @@ +# Security Policy + +If you believe you have found a security vulnerability in PHPUnit, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please email `sebastian@phpunit.de`. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + +* The type of issue +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Web Context + +PHPUnit is a framework for writing as well as a command-line tool for running tests. Writing and running tests is a development-time activity. There is no reason why PHPUnit should be installed on a webserver and/or in a production environment. + +**If you upload PHPUnit to a webserver then your deployment process is broken. On a more general note, if your `vendor` directory is publicly accessible on your webserver then your deployment process is also broken.** + +Please note that if you upload PHPUnit to a webserver "bad things" may happen. [You have been warned.](https://thephp.cc/articles/phpunit-a-security-risk?ref=phpunit) + +PHPUnit is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using PHPUnit in an HTTP or web context or with untrusted input data is performed. PHPUnit might also contain functionality that intentionally exposes internal application data for debugging purposes. + +If PHPUnit is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context. + +Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes. diff --git a/build.xml b/build.xml index f2f276dd767..93d043869f0 100644 --- a/build.xml +++ b/build.xml @@ -1,87 +1,100 @@ - - - + + - + + + + - - - - - - - - - + + - - - - - + + + + + + - - - - - - - + - - - - - - - - + + + - - - + + + + + - - - - + + + + - - - - - + + + + + + - + + + + + - - - - + + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + @@ -108,9 +121,9 @@ - + - + @@ -120,8 +133,18 @@ + + + + + - + + + + + + @@ -157,30 +180,23 @@ - - - - - - - - - - + + + - - - + + + - - - + + + @@ -213,6 +229,13 @@ + + + + + + + @@ -234,13 +257,6 @@ - - - - - - - @@ -255,9 +271,9 @@ - - - + + + @@ -269,6 +285,25 @@ + + + + + + + + + + + + + + + + + + + @@ -283,54 +318,12 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -353,27 +346,23 @@ - - - - - - - - + + + + + - - + - + @@ -381,16 +370,20 @@ + + + + + - - + @@ -398,6 +391,10 @@ + + + + @@ -407,56 +404,28 @@ - - - - - - - - - - + + - - - + - - - - - - - - - - - - - - - + + + + + - - - - - - + + + + - - - - - - - + + - diff --git a/build/config/github-ci-fail.xml b/build/config/github-ci-fail.xml deleted file mode 100644 index 94d1cda5331..00000000000 --- a/build/config/github-ci-fail.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - ../../tests/fail - - - - - - - diff --git a/build/config/php-scoper.php b/build/config/php-scoper.php index b2a9a32424a..979bdaccd72 100644 --- a/build/config/php-scoper.php +++ b/build/config/php-scoper.php @@ -7,10 +7,14 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ - return [ - 'whitelist' => [ - 'PHPUnit\*', - 'Prophecy\*', + 'prefix' => 'PHPUnitPHAR', + + 'exclude-namespaces' => [ + 'PHPUnit', + ], + + 'expose-constants' => [ + '/^__PHPUNIT_.+$/' ], ]; diff --git a/build/config/phpdox.xml b/build/config/phpdox.xml deleted file mode 100644 index 40bbacec4af..00000000000 --- a/build/config/phpdox.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/build/scripts/extract-release-notes.php b/build/scripts/extract-release-notes.php new file mode 100755 index 00000000000..050766ede1d --- /dev/null +++ b/build/scripts/extract-release-notes.php @@ -0,0 +1,70 @@ +#!/usr/bin/env php +' . PHP_EOL; + + exit(1); +} + +$version = $argv[1]; +$versionSeries = explode('.', $version)[0] . '.' . explode('.', $version)[1]; + +$file = __DIR__ . '/../../ChangeLog-' . $versionSeries . '.md'; + +if (!is_file($file) || !is_readable($file)) { + print $file . ' cannot be read' . PHP_EOL; + + exit(1); +} + +$buffer = ''; +$append = false; + +foreach (file($file) as $line) { + if (str_starts_with($line, '## [' . $version . ']')) { + $append = true; + + continue; + } + + if ($append && (str_starts_with($line, '## ') || str_starts_with($line, '['))) { + break; + } + + if ($append) { + $buffer .= $line; + } +} + +$buffer = trim($buffer); + +if ($buffer === '') { + print 'Unable to extract release notes' . PHP_EOL; + + exit(1); +} + +print $buffer . PHP_EOL; + +$template = <<<'EOT' + +--- + +Learn how to install or update PHPUnit {{versionSeries}} in the [documentation](https://docs.phpunit.de/en/{{versionSeries}}/installation.html). + +#### Keep up to date with PHPUnit: + +* You can follow [@phpunit@phpc.social](https://phpc.social/@phpunit) to stay up to date with PHPUnit's development. +* You can subscribe to the [PHPUnit Updates](https://phpunit.de/newsletter) newsletter to receive updates about and tips for PHPUnit. + +EOT; + +print str_replace( + [ + '{{versionSeries}}', + ], + [ + $versionSeries, + ], + $template, +); diff --git a/build/scripts/generate-global-assert-wrappers.php b/build/scripts/generate-global-assert-wrappers.php index de7e4f752cb..d53bac1c421 100755 --- a/build/scripts/generate-global-assert-wrappers.php +++ b/build/scripts/generate-global-assert-wrappers.php @@ -20,10 +20,13 @@ */ namespace PHPUnit\Framework; +use ArrayAccess; +use Countable; +use DOMDocument; +use DOMElement; use PHPUnit\Framework\Constraint\Constraint; use PHPUnit\Framework\ExpectationFailedException; use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount as AnyInvokedCountMatcher; -use PHPUnit\Framework\MockObject\Rule\InvokedAtIndex as InvokedAtIndexMatcher; use PHPUnit\Framework\MockObject\Rule\InvokedAtLeastCount as InvokedAtLeastCountMatcher; use PHPUnit\Framework\MockObject\Rule\InvokedAtLeastOnce as InvokedAtLeastOnceMatcher; use PHPUnit\Framework\MockObject\Rule\InvokedAtMostCount as InvokedAtMostCountMatcher; @@ -35,6 +38,7 @@ use PHPUnit\Framework\MockObject\Stub\ReturnSelf as ReturnSelfStub; use PHPUnit\Framework\MockObject\Stub\ReturnStub; use PHPUnit\Framework\MockObject\Stub\ReturnValueMap as ReturnValueMapStub; +use PHPUnit\Util\Xml\XmlException; '; $usedClasses = []; @@ -44,11 +48,15 @@ $constraintMethods = ''; foreach ($class->getMethods() as $method) { - if (!$method->hasReturnType() || $method->getReturnType()->isBuiltin()) { + $returnType = $method->getReturnType(); + + assert($returnType instanceof ReflectionNamedType || $returnType instanceof ReflectionUnionType); + + if ($returnType instanceof ReflectionNamedType && $returnType->isBuiltin()) { continue; } - $returnType = new ReflectionClass($method->getReturnType()->getName()); + $returnType = new ReflectionClass($returnType->getName()); if (!$returnType->isSubclassOf(Constraint::class)) { continue; @@ -56,9 +64,14 @@ $usedClasses[] = $returnType->getName(); + // skip, so we can later on append a signature including precise analysis types + if ($method->getName() === 'callback') { + continue; + } + $constraintMethods .= \sprintf( - "%s\n{\n return Assert::%s(...\\func_get_args());\n}\n\n", - \str_replace('public static ', '', \trim($lines[$method->getStartLine() - 1])), + "if (!function_exists('PHPUnit\Framework\\" . $method->getName() . "')) {\n%s\n{\n return Assert::%s(...\\func_get_args());\n}\n}\n\n", + \str_replace('final public static ', '', \trim($lines[$method->getStartLine() - 1])), $method->getName() ); } @@ -82,131 +95,118 @@ $docComment = \str_replace( ['*/', ' *'], - ["*\n * @see Assert::" . $method->getName() . "\n */", ' *'], - $method->getDocComment() + ["*\n * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit\n * @see Assert::" . $method->getName() . "\n */", ' *'], + (string) $method->getDocComment() ); - $signature = \str_replace('public static ', '', \trim($lines[$method->getStartLine() - 1])); - $body = "{\n Assert::" . $method->getName() . "(...\\func_get_args());\n}"; - $buffer .= "$docComment\n$signature\n$body\n\n"; -} - -$buffer .= $constraintMethods; -$buffer .= '/** - * Returns a matcher that matches when the method is executed - * zero or more times. - */ -function any(): AnyInvokedCountMatcher -{ - return new AnyInvokedCountMatcher; -} - -/** - * Returns a matcher that matches when the method is never executed. - */ -function never(): InvokedCountMatcher -{ - return new InvokedCountMatcher(0); -} - -/** - * Returns a matcher that matches when the method is executed - * at least N times. - */ -function atLeast(int $requiredInvocations): InvokedAtLeastCountMatcher -{ - return new InvokedAtLeastCountMatcher( - $requiredInvocations - ); -} + $signature = \str_replace('final public static ', '', \trim($lines[$method->getStartLine() - 1])); + $body = "{\n Assert::" . $method->getName() . "(...\\func_get_args());\n}"; -/** - * Returns a matcher that matches when the method is executed at least once. - */ -function atLeastOnce(): InvokedAtLeastOnceMatcher -{ - return new InvokedAtLeastOnceMatcher; + $buffer .= "if (!function_exists('PHPUnit\Framework\\" . $method->getName() . "')) {\n"; + $buffer .= "$docComment\n$signature\n$body\n"; + $buffer .= "}\n\n"; } -/** - * Returns a matcher that matches when the method is executed exactly once. - */ -function once(): InvokedCountMatcher -{ - return new InvokedCountMatcher(1); -} +$buffer .= $constraintMethods; -/** - * Returns a matcher that matches when the method is executed - * exactly $count times. - */ -function exactly(int $count): InvokedCountMatcher -{ - return new InvokedCountMatcher($count); +$buffer .= <<<'EOT' +if (!function_exists('PHPUnit\Framework\callback')) { + /** + * @template CallbackInput of mixed + * + * @param callable(CallbackInput $callback): bool $callback + * + * @return Callback + */ + function callback(callable $callback): Callback + { + return Assert::callback($callback); + } } -/** - * Returns a matcher that matches when the method is executed - * at most N times. - */ -function atMost(int $allowedInvocations): InvokedAtMostCountMatcher -{ - return new InvokedAtMostCountMatcher($allowedInvocations); +if (!function_exists('PHPUnit\Framework\any')) { + /** + * Returns a matcher that matches when the method is executed + * zero or more times. + */ + function any(): AnyInvokedCountMatcher + { + return new AnyInvokedCountMatcher; + } } -/** - * Returns a matcher that matches when the method is executed - * at the given index. - */ -function at(int $index): InvokedAtIndexMatcher -{ - return new InvokedAtIndexMatcher($index); +if (!function_exists('PHPUnit\Framework\never')) { + /** + * Returns a matcher that matches when the method is never executed. + */ + function never(): InvokedCountMatcher + { + return new InvokedCountMatcher(0); + } } -function returnValue($value): ReturnStub -{ - return new ReturnStub($value); +if (!function_exists('PHPUnit\Framework\atLeast')) { + /** + * Returns a matcher that matches when the method is executed + * at least N times. + */ + function atLeast(int $requiredInvocations): InvokedAtLeastCountMatcher + { + return new InvokedAtLeastCountMatcher( + $requiredInvocations + ); + } } -function returnValueMap(array $valueMap): ReturnValueMapStub -{ - return new ReturnValueMapStub($valueMap); +if (!function_exists('PHPUnit\Framework\atLeastOnce')) { + /** + * Returns a matcher that matches when the method is executed at least once. + */ + function atLeastOnce(): InvokedAtLeastOnceMatcher + { + return new InvokedAtLeastOnceMatcher; + } } -function returnArgument(int $argumentIndex): ReturnArgumentStub -{ - return new ReturnArgumentStub($argumentIndex); +if (!function_exists('PHPUnit\Framework\once')) { + /** + * Returns a matcher that matches when the method is executed exactly once. + */ + function once(): InvokedCountMatcher + { + return new InvokedCountMatcher(1); + } } -function returnCallback($callback): ReturnCallbackStub -{ - return new ReturnCallbackStub($callback); +if (!function_exists('PHPUnit\Framework\exactly')) { + /** + * Returns a matcher that matches when the method is executed + * exactly $count times. + */ + function exactly(int $count): InvokedCountMatcher + { + return new InvokedCountMatcher($count); + } } -/** - * Returns the current object. - * - * This method is useful when mocking a fluent interface. - */ -function returnSelf(): ReturnSelfStub -{ - return new ReturnSelfStub; +if (!function_exists('PHPUnit\Framework\atMost')) { + /** + * Returns a matcher that matches when the method is executed + * at most N times. + */ + function atMost(int $allowedInvocations): InvokedAtMostCountMatcher + { + return new InvokedAtMostCountMatcher($allowedInvocations); + } } -function throwException(\Throwable $exception): ExceptionStub -{ - return new ExceptionStub($exception); +if (!function_exists('PHPUnit\Framework\throwException')) { + function throwException(\Throwable $exception): ExceptionStub + { + return new ExceptionStub($exception); + } } -/** - * @param mixed $value , ... - */ -function onConsecutiveCalls(): ConsecutiveCallsStub -{ - $args = \func_get_args(); - - return new ConsecutiveCallsStub($args); -} -'; +EOT; \file_put_contents(__DIR__ . '/../../src/Framework/Assert/Functions.php', $buffer); diff --git a/build/scripts/lint-xml-configuration b/build/scripts/lint-xml-configuration deleted file mode 100755 index e179e879896..00000000000 --- a/build/scripts/lint-xml-configuration +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -xmllint --noout --schema phpunit.xsd phpunit.xml -xmllint --noout --schema phpunit.xsd tests/_files/configuration.xml -xmllint --noout --schema phpunit.xsd tests/_files/configuration_empty.xml -xmllint --noout --schema phpunit.xsd tests/_files/configuration_xinclude.xml -xinclude diff --git a/build/scripts/phar-manifest.php b/build/scripts/phar-manifest.php index a794e6ba515..97a0afe9489 100755 --- a/build/scripts/phar-manifest.php +++ b/build/scripts/phar-manifest.php @@ -1,27 +1,173 @@ #!/usr/bin/env php &1'); + exit(1); +} + +$package = package(); +$version = version(); +$dependencies = dependencies(); + +manifest($argv[1], $package, $version, $dependencies); +sbom($argv[2], $package, $version, $dependencies); -if (\strpos($tag, '-') === false && \strpos($tag, 'No names found') === false) { - print $tag; -} else { - $branch = @\exec('git rev-parse --abbrev-ref HEAD'); - $hash = @\exec('git log -1 --format="%H"'); - print $branch . '@' . $hash; +function manifest(string $outputFilename, array $package, string $version, array $dependencies): void +{ + $buffer = sprintf( + '%s/%s: %s' . "\n", + $package['group'], + $package['name'], + $version + ); + + foreach ($dependencies as $dependency) { + $buffer .= sprintf( + '%s: %s' . "\n", + $dependency['name'], + versionWithReference( + $dependency['version'], + $dependency['source']['reference'] + ) + ); + } + + file_put_contents($outputFilename, $buffer); } -print "\n"; +function sbom(string $outputFilename, array $package, string $version, array $dependencies): void +{ + $writer = new XMLWriter; + + $writer->openMemory(); + $writer->setIndent(true); + $writer->startDocument(); + + $writer->startElement('bom'); + $writer->writeAttribute('xmlns', 'http://cyclonedx.org/schema/bom/1.4'); + + $writer->startElement('components'); -$lock = \json_decode(\file_get_contents(__DIR__ . '/../../composer.lock')); + writeComponent( + $writer, + $package['group'], + $package['name'], + $version, + $package['description'], + $package['license'] + ); -foreach ($lock->packages as $package) { - print $package->name . ': ' . $package->version; + foreach ($dependencies as $dependency) { + [$group, $name] = explode('/', $dependency['name']); - if (!\preg_match('/^[v= ]*(([0-9]+)(\\.([0-9]+)(\\.([0-9]+)(-([0-9]+))?(-?([a-zA-Z-+][a-zA-Z0-9\\.\\-:]*)?)?)?)?)$/', $package->version)) { - print '@' . $package->source->reference; + writeComponent( + $writer, + $group, + $name, + versionWithReference( + $dependency['version'], + $dependency['source']['reference'] + ), + $dependency['description'], + $dependency['license'] + ); } - print "\n"; + $writer->endElement(); + $writer->endElement(); + $writer->endDocument(); + + file_put_contents($outputFilename, $writer->outputMemory()); +} + +function package(): array +{ + $data = json_decode( + file_get_contents( + __DIR__ . '/../../composer.json' + ), + true + ); + + [$group, $name] = explode('/', $data['name']); + + return [ + 'group' => $group, + 'name' => $name, + 'description' => $data['description'], + 'license' => [$data['license']], + ]; +} + +function version(): string +{ + $tag = @exec('git describe --tags 2>&1'); + + if (strpos($tag, '-') === false && strpos($tag, 'No names found') === false) { + return $tag; + } + + $branch = @exec('git rev-parse --abbrev-ref HEAD'); + $hash = @exec('git log -1 --format="%H"'); + + return $branch . '@' . $hash; +} + +function dependencies(): array +{ + return json_decode( + file_get_contents( + __DIR__ . '/../../composer.lock' + ), + true + )['packages']; +} + +function versionWithReference(string $version, string $reference): string +{ + if (!preg_match('/^[v= ]*(([0-9]+)(\\.([0-9]+)(\\.([0-9]+)(-([0-9]+))?(-?([a-zA-Z-+][a-zA-Z0-9.\\-:]*)?)?)?)?)$/', $version)) { + $version .= '@' . $reference; + } + + return $version; +} + +function writeComponent(XMLWriter $writer, string $group, string $name, string $version, string $description, array $licenses): void +{ + $writer->startElement('component'); + $writer->writeAttribute('type', 'library'); + + $writer->writeElement('group', $group); + $writer->writeElement('name', $name); + $writer->writeElement('version', $version); + $writer->writeElement('description', $description); + + $writer->startElement('licenses'); + + foreach ($licenses as $license) { + $writer->startElement('license'); + $writer->writeElement('id', $license); + $writer->endElement(); + } + + $writer->endElement(); + + $writer->writeElement( + 'purl', + sprintf( + 'pkg:composer/%s/%s@%s', + $group, + $name, + $version + ) + ); + + $writer->endElement(); } diff --git a/build/scripts/phar-set-timestamps/composer.json b/build/scripts/phar-set-timestamps/composer.json new file mode 100644 index 00000000000..4369496d0e6 --- /dev/null +++ b/build/scripts/phar-set-timestamps/composer.json @@ -0,0 +1,8 @@ +{ + "require": { + "seld/phar-utils": "^1.2" + }, + "config": { + "optimize-autoloader": true + } +} diff --git a/build/scripts/phar-set-timestamps/composer.lock b/build/scripts/phar-set-timestamps/composer.lock new file mode 100644 index 00000000000..e3c10072c6e --- /dev/null +++ b/build/scripts/phar-set-timestamps/composer.lock @@ -0,0 +1,67 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "a2828bc624be51258ae32a2f2dbd5696", + "packages": [ + { + "name": "seld/phar-utils", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/phar-utils.git", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\PharUtils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "PHAR file format utilities, for when PHP phars you up", + "keywords": [ + "phar" + ], + "support": { + "issues": "https://github.com/Seldaek/phar-utils/issues", + "source": "https://github.com/Seldaek/phar-utils/tree/1.2.1" + }, + "time": "2022-08-31T10:31:18+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/build/scripts/phar-set-timestamps/run.php b/build/scripts/phar-set-timestamps/run.php new file mode 100755 index 00000000000..d845e2b9f3b --- /dev/null +++ b/build/scripts/phar-set-timestamps/run.php @@ -0,0 +1,54 @@ +#!/usr/bin/env php +&1'); + + if (is_string($tag) && strpos($tag, 'fatal') === false) { + $tmp = @shell_exec('git log -1 --format=%at ' . trim($tag) . ' 2>&1'); + + if (is_string($tag) && is_numeric(trim($tmp))) { + $epoch = (int) trim($tmp); + + printf( + 'Setting timestamp of files in PHAR to %d (based on when tag %s was created)' . PHP_EOL, + $epoch, + trim($tag) + ); + } + + unset($tmp); + } + + unset($tag); +} + +if (!isset($epoch)) { + $epoch = time(); + + printf( + 'Setting timestamp of files in PHAR to %d (based on current time)' . PHP_EOL, + $epoch + ); +} + +$timestamp = new DateTime; +$timestamp->setTimestamp($epoch); + +$util = new Timestamps($argv[1]); +$util->updateTimestamps($timestamp); +$util->save($argv[1], Phar::SHA512); diff --git a/build/scripts/phar-set-timestamps/vendor/autoload.php b/build/scripts/phar-set-timestamps/vendor/autoload.php new file mode 100644 index 00000000000..bb1037c7c3b --- /dev/null +++ b/build/scripts/phar-set-timestamps/vendor/autoload.php @@ -0,0 +1,25 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ + private $vendorDir; + + // PSR-4 + /** + * @var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var list + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array>> + */ + private $prefixesPsr0 = array(); + /** + * @var list + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var array + */ + private $missingClasses = array(); + + /** @var string|null */ + private $apcuPrefix; + + /** + * @var array + */ + private static $registeredLoaders = array(); + + /** + * @param string|null $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return array> + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return list + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return list + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return array Array of classname => path + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders keyed by their corresponding vendor directories. + * + * @return array + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/build/scripts/phar-set-timestamps/vendor/composer/InstalledVersions.php b/build/scripts/phar-set-timestamps/vendor/composer/InstalledVersions.php new file mode 100644 index 00000000000..51e734a774b --- /dev/null +++ b/build/scripts/phar-set-timestamps/vendor/composer/InstalledVersions.php @@ -0,0 +1,359 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + $installed[] = self::$installedByVendor[$vendorDir] = $required; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array()) { + $installed[] = self::$installed; + } + + return $installed; + } +} diff --git a/build/scripts/phar-set-timestamps/vendor/composer/LICENSE b/build/scripts/phar-set-timestamps/vendor/composer/LICENSE new file mode 100644 index 00000000000..f27399a042d --- /dev/null +++ b/build/scripts/phar-set-timestamps/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +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. + diff --git a/build/scripts/phar-set-timestamps/vendor/composer/autoload_classmap.php b/build/scripts/phar-set-timestamps/vendor/composer/autoload_classmap.php new file mode 100644 index 00000000000..e6a22096ae5 --- /dev/null +++ b/build/scripts/phar-set-timestamps/vendor/composer/autoload_classmap.php @@ -0,0 +1,12 @@ + $vendorDir . '/composer/InstalledVersions.php', + 'Seld\\PharUtils\\Linter' => $vendorDir . '/seld/phar-utils/src/Linter.php', + 'Seld\\PharUtils\\Timestamps' => $vendorDir . '/seld/phar-utils/src/Timestamps.php', +); diff --git a/build/scripts/phar-set-timestamps/vendor/composer/autoload_namespaces.php b/build/scripts/phar-set-timestamps/vendor/composer/autoload_namespaces.php new file mode 100644 index 00000000000..15a2ff3ad6d --- /dev/null +++ b/build/scripts/phar-set-timestamps/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/seld/phar-utils/src'), +); diff --git a/build/scripts/phar-set-timestamps/vendor/composer/autoload_real.php b/build/scripts/phar-set-timestamps/vendor/composer/autoload_real.php new file mode 100644 index 00000000000..69cfbf7e33c --- /dev/null +++ b/build/scripts/phar-set-timestamps/vendor/composer/autoload_real.php @@ -0,0 +1,38 @@ +register(true); + + return $loader; + } +} diff --git a/build/scripts/phar-set-timestamps/vendor/composer/autoload_static.php b/build/scripts/phar-set-timestamps/vendor/composer/autoload_static.php new file mode 100644 index 00000000000..201af17c152 --- /dev/null +++ b/build/scripts/phar-set-timestamps/vendor/composer/autoload_static.php @@ -0,0 +1,38 @@ + + array ( + 'Seld\\PharUtils\\' => 15, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Seld\\PharUtils\\' => + array ( + 0 => __DIR__ . '/..' . '/seld/phar-utils/src', + ), + ); + + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'Seld\\PharUtils\\Linter' => __DIR__ . '/..' . '/seld/phar-utils/src/Linter.php', + 'Seld\\PharUtils\\Timestamps' => __DIR__ . '/..' . '/seld/phar-utils/src/Timestamps.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit386a05f6676643b8b2eb49288e20d079::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit386a05f6676643b8b2eb49288e20d079::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit386a05f6676643b8b2eb49288e20d079::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/build/scripts/phar-set-timestamps/vendor/composer/installed.json b/build/scripts/phar-set-timestamps/vendor/composer/installed.json new file mode 100644 index 00000000000..44d066d18b1 --- /dev/null +++ b/build/scripts/phar-set-timestamps/vendor/composer/installed.json @@ -0,0 +1,57 @@ +{ + "packages": [ + { + "name": "seld/phar-utils", + "version": "1.2.1", + "version_normalized": "1.2.1.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/phar-utils.git", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "time": "2022-08-31T10:31:18+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Seld\\PharUtils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "PHAR file format utilities, for when PHP phars you up", + "keywords": [ + "phar" + ], + "support": { + "issues": "https://github.com/Seldaek/phar-utils/issues", + "source": "https://github.com/Seldaek/phar-utils/tree/1.2.1" + }, + "install-path": "../seld/phar-utils" + } + ], + "dev": true, + "dev-package-names": [] +} diff --git a/build/scripts/phar-set-timestamps/vendor/composer/installed.php b/build/scripts/phar-set-timestamps/vendor/composer/installed.php new file mode 100644 index 00000000000..6f559f8743b --- /dev/null +++ b/build/scripts/phar-set-timestamps/vendor/composer/installed.php @@ -0,0 +1,32 @@ + array( + 'name' => '__root__', + 'pretty_version' => '8.5.x-dev', + 'version' => '8.5.9999999.9999999-dev', + 'reference' => 'aca96fcd8b6799ead1066524b2b91c5184ece78f', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => true, + ), + 'versions' => array( + '__root__' => array( + 'pretty_version' => '8.5.x-dev', + 'version' => '8.5.9999999.9999999-dev', + 'reference' => 'aca96fcd8b6799ead1066524b2b91c5184ece78f', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'seld/phar-utils' => array( + 'pretty_version' => '1.2.1', + 'version' => '1.2.1.0', + 'reference' => 'ea2f4014f163c1be4c601b9b7bd6af81ba8d701c', + 'type' => 'library', + 'install_path' => __DIR__ . '/../seld/phar-utils', + 'aliases' => array(), + 'dev_requirement' => false, + ), + ), +); diff --git a/build/scripts/phar-set-timestamps/vendor/composer/platform_check.php b/build/scripts/phar-set-timestamps/vendor/composer/platform_check.php new file mode 100644 index 00000000000..7621d4ff97f --- /dev/null +++ b/build/scripts/phar-set-timestamps/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 50300)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 5.3.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/build/scripts/phar-set-timestamps/vendor/seld/phar-utils/.gitignore b/build/scripts/phar-set-timestamps/vendor/seld/phar-utils/.gitignore new file mode 100644 index 00000000000..42cd73d9573 --- /dev/null +++ b/build/scripts/phar-set-timestamps/vendor/seld/phar-utils/.gitignore @@ -0,0 +1 @@ +/vendor/ \ No newline at end of file diff --git a/build/scripts/phar-set-timestamps/vendor/seld/phar-utils/LICENSE b/build/scripts/phar-set-timestamps/vendor/seld/phar-utils/LICENSE new file mode 100644 index 00000000000..c1b62a35fa3 --- /dev/null +++ b/build/scripts/phar-set-timestamps/vendor/seld/phar-utils/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015 Jordi Boggiano + +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. diff --git a/build/scripts/phar-set-timestamps/vendor/seld/phar-utils/README.md b/build/scripts/phar-set-timestamps/vendor/seld/phar-utils/README.md new file mode 100644 index 00000000000..27edf743276 --- /dev/null +++ b/build/scripts/phar-set-timestamps/vendor/seld/phar-utils/README.md @@ -0,0 +1,45 @@ +PHAR Utils +========== + +PHAR file format utilities, for when PHP phars you up. + +Installation +------------ + +`composer require seld/phar-utils` + +API +--- + +### `Seld\PharUtils\Timestamps` + +- `__construct($pharFile)` + + > Load a phar file in memory. + +- `updateTimestamps($timestamp = null)` + + > Updates each file's unix timestamps in the PHAR so the PHAR signature + > can be produced in a reproducible manner. + +- `save($path, $signatureAlgo = '')` + + > Saves the updated phar file with an updated signature. + > Algo must be one of `Phar::MD5`, `Phar::SHA1`, `Phar::SHA256` + > or `Phar::SHA512` + +### `Seld\PharUtils\Linter` + +- `Linter::lint($pharFile)` + + > Lints all php files inside a given phar with the current PHP version. + +Requirements +------------ + +PHP 5.3 and above + +License +------- + +PHAR Utils is licensed under the MIT License - see the LICENSE file for details diff --git a/build/scripts/phar-set-timestamps/vendor/seld/phar-utils/composer.json b/build/scripts/phar-set-timestamps/vendor/seld/phar-utils/composer.json new file mode 100644 index 00000000000..8b1f7f2b68a --- /dev/null +++ b/build/scripts/phar-set-timestamps/vendor/seld/phar-utils/composer.json @@ -0,0 +1,26 @@ +{ + "name": "seld/phar-utils", + "description": "PHAR file format utilities, for when PHP phars you up", + "type": "library", + "keywords": ["phar"], + "license": "MIT", + "require": { + "php": ">=5.3" + }, + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "autoload": { + "psr-4": { + "Seld\\PharUtils\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + } +} diff --git a/build/scripts/phar-set-timestamps/vendor/seld/phar-utils/composer.lock b/build/scripts/phar-set-timestamps/vendor/seld/phar-utils/composer.lock new file mode 100644 index 00000000000..21e33c7a217 --- /dev/null +++ b/build/scripts/phar-set-timestamps/vendor/seld/phar-utils/composer.lock @@ -0,0 +1,19 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "e5afe72073d9266712c8e1ddc1648513", + "packages": [], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.3" + }, + "platform-dev": [] +} diff --git a/build/scripts/phar-set-timestamps/vendor/seld/phar-utils/src/Linter.php b/build/scripts/phar-set-timestamps/vendor/seld/phar-utils/src/Linter.php new file mode 100644 index 00000000000..935d04e8411 --- /dev/null +++ b/build/scripts/phar-set-timestamps/vendor/seld/phar-utils/src/Linter.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Seld\PharUtils; + +class Linter +{ + /** + * Lints all php files inside a given phar with the current PHP version + * + * @param string $path Phar file path + * @param list $excludedPaths Paths which should be skipped by the linter + */ + public static function lint($path, array $excludedPaths = array()) + { + $php = defined('PHP_BINARY') ? PHP_BINARY : 'php'; + + if ($isWindows = defined('PHP_WINDOWS_VERSION_BUILD')) { + $tmpFile = @tempnam(sys_get_temp_dir(), ''); + + if (!$tmpFile || !is_writable($tmpFile)) { + throw new \RuntimeException('Unable to create temp file'); + } + + $php = self::escapeWindowsPath($php); + $tmpFile = self::escapeWindowsPath($tmpFile); + + // PHP 8 encloses the command in double-quotes + if (PHP_VERSION_ID >= 80000) { + $format = '%s -l %s'; + } else { + $format = '"%s -l %s"'; + } + + $command = sprintf($format, $php, $tmpFile); + } else { + $command = "'".$php."' -l"; + } + + $descriptorspec = array( + 0 => array('pipe', 'r'), + 1 => array('pipe', 'w'), + 2 => array('pipe', 'w') + ); + + // path to phar + phar:// + trailing slash + $baseLen = strlen(realpath($path)) + 7 + 1; + foreach (new \RecursiveIteratorIterator(new \Phar($path)) as $file) { + if ($file->isDir()) { + continue; + } + if (substr($file, -4) === '.php') { + $filename = (string) $file; + if (in_array(substr($filename, $baseLen), $excludedPaths, true)) { + continue; + } + if ($isWindows) { + file_put_contents($tmpFile, file_get_contents($filename)); + } + + $process = proc_open($command, $descriptorspec, $pipes); + if (is_resource($process)) { + if (!$isWindows) { + fwrite($pipes[0], file_get_contents($filename)); + } + fclose($pipes[0]); + + $stdout = stream_get_contents($pipes[1]); + fclose($pipes[1]); + $stderr = stream_get_contents($pipes[2]); + fclose($pipes[2]); + + $exitCode = proc_close($process); + + if ($exitCode !== 0) { + if ($isWindows) { + $stderr = str_replace($tmpFile, $filename, $stderr); + } + throw new \UnexpectedValueException('Failed linting '.$file.': '.$stderr); + } + } else { + throw new \RuntimeException('Could not start linter process'); + } + } + } + + if ($isWindows) { + @unlink($tmpFile); + } + } + + /** + * Escapes a Windows file path + * + * @param string $path + * @return string The escaped path + */ + private static function escapeWindowsPath($path) + { + // Quote if path contains spaces or brackets + if (strpbrk($path, " ()") !== false) { + $path = '"'.$path.'"'; + } + + return $path; + } +} diff --git a/build/scripts/phar-set-timestamps/vendor/seld/phar-utils/src/Timestamps.php b/build/scripts/phar-set-timestamps/vendor/seld/phar-utils/src/Timestamps.php new file mode 100644 index 00000000000..8077d5b851d --- /dev/null +++ b/build/scripts/phar-set-timestamps/vendor/seld/phar-utils/src/Timestamps.php @@ -0,0 +1,196 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Seld\PharUtils; + +class Timestamps +{ + private $contents; + + /** + * @param string $file path to the phar file to use + */ + public function __construct($file) + { + $this->contents = file_get_contents($file); + } + + /** + * Updates each file's unix timestamps in the PHAR + * + * The PHAR signature can then be produced in a reproducible manner. + * + * @param int|\DateTimeInterface|string $timestamp Date string or DateTime or unix timestamp to use + */ + public function updateTimestamps($timestamp = null) + { + if ($timestamp instanceof \DateTime || $timestamp instanceof \DateTimeInterface) { + $timestamp = $timestamp->getTimestamp(); + } elseif (is_string($timestamp)) { + $timestamp = strtotime($timestamp); + } elseif (!is_int($timestamp)) { + $timestamp = strtotime('1984-12-24T00:00:00Z'); + } + + // detect manifest offset / end of stub + if (!preg_match('{__HALT_COMPILER\(\);(?: +\?>)?\r?\n}', $this->contents, $match, PREG_OFFSET_CAPTURE)) { + throw new \RuntimeException('Could not detect the stub\'s end in the phar'); + } + + // set starting position and skip past manifest length + $pos = $match[0][1] + strlen($match[0][0]); + $stubEnd = $pos + $this->readUint($pos, 4); + $pos += 4; + + $numFiles = $this->readUint($pos, 4); + $pos += 4; + + // skip API version (YOLO) + $pos += 2; + + // skip PHAR flags + $pos += 4; + + $aliasLength = $this->readUint($pos, 4); + $pos += 4 + $aliasLength; + + $metadataLength = $this->readUint($pos, 4); + $pos += 4 + $metadataLength; + + while ($pos < $stubEnd) { + $filenameLength = $this->readUint($pos, 4); + $pos += 4 + $filenameLength; + + // skip filesize + $pos += 4; + + // update timestamp to a fixed value + $timeStampBytes = pack('L', $timestamp); + $this->contents[$pos + 0] = $timeStampBytes[0]; + $this->contents[$pos + 1] = $timeStampBytes[1]; + $this->contents[$pos + 2] = $timeStampBytes[2]; + $this->contents[$pos + 3] = $timeStampBytes[3]; + + // skip timestamp, compressed file size, crc32 checksum and file flags + $pos += 4*4; + + $metadataLength = $this->readUint($pos, 4); + $pos += 4 + $metadataLength; + + $numFiles--; + } + + if ($numFiles !== 0) { + throw new \LogicException('All files were not processed, something must have gone wrong'); + } + } + + /** + * Saves the updated phar file, optionally with an updated signature. + * + * @param string $path + * @param int $signatureAlgo One of Phar::MD5, Phar::SHA1, Phar::SHA256 or Phar::SHA512 + * @return bool + */ + public function save($path, $signatureAlgo) + { + $pos = $this->determineSignatureBegin(); + + $algos = array( + \Phar::MD5 => 'md5', + \Phar::SHA1 => 'sha1', + \Phar::SHA256 => 'sha256', + \Phar::SHA512 => 'sha512', + ); + + if (!isset($algos[$signatureAlgo])) { + throw new \UnexpectedValueException('Invalid hash algorithm given: '.$signatureAlgo.' expected one of Phar::MD5, Phar::SHA1, Phar::SHA256 or Phar::SHA512'); + } + $algo = $algos[$signatureAlgo]; + + // re-sign phar + // signature + $signature = hash($algo, substr($this->contents, 0, $pos), true) + // sig type + . pack('L', $signatureAlgo) + // ohai Greg & Marcus + . 'GBMB'; + + $this->contents = substr($this->contents, 0, $pos) . $signature; + + return file_put_contents($path, $this->contents); + } + + private function readUint($pos, $bytes) + { + $res = unpack('V', substr($this->contents, $pos, $bytes)); + + return $res[1]; + } + + /** + * Determine the beginning of the signature. + * + * @return int + */ + private function determineSignatureBegin() + { + // detect signature position + if (!preg_match('{__HALT_COMPILER\(\);(?: +\?>)?\r?\n}', $this->contents, $match, PREG_OFFSET_CAPTURE)) { + throw new \RuntimeException('Could not detect the stub\'s end in the phar'); + } + + // set starting position and skip past manifest length + $pos = $match[0][1] + strlen($match[0][0]); + $manifestEnd = $pos + 4 + $this->readUint($pos, 4); + + $pos += 4; + $numFiles = $this->readUint($pos, 4); + + $pos += 4; + + // skip API version (YOLO) + $pos += 2; + + // skip PHAR flags + $pos += 4; + + $aliasLength = $this->readUint($pos, 4); + $pos += 4 + $aliasLength; + + $metadataLength = $this->readUint($pos, 4); + $pos += 4 + $metadataLength; + + $compressedSizes = 0; + while (($numFiles > 0) && ($pos < $manifestEnd - 24)) { + $filenameLength = $this->readUint($pos, 4); + $pos += 4 + $filenameLength; + + // skip filesize and timestamp + $pos += 2*4; + + $compressedSizes += $this->readUint($pos, 4); + // skip compressed file size, crc32 checksum and file flags + $pos += 3*4; + + $metadataLength = $this->readUint($pos, 4); + $pos += 4 + $metadataLength; + + $numFiles--; + } + + if ($numFiles !== 0) { + throw new \LogicException('All files were not processed, something must have gone wrong'); + } + + return $manifestEnd + $compressedSizes; + } +} diff --git a/build/scripts/phar-version.php b/build/scripts/phar-version.php index 620a735f265..948c3b21b09 100755 --- a/build/scripts/phar-version.php +++ b/build/scripts/phar-version.php @@ -1,20 +1,21 @@ #!/usr/bin/env php getVersion(); +print $version->asString(); diff --git a/build/templates/binary-phar-autoload.php.in b/build/templates/binary-phar-autoload.php.in index b2d9bc2bb77..207d6d73f57 100644 --- a/build/templates/binary-phar-autoload.php.in +++ b/build/templates/binary-phar-autoload.php.in @@ -1,11 +1,26 @@ #!/usr/bin/env php ')) { +if (!version_compare(PHP_VERSION, PHP_VERSION, '=')) { + fwrite( + STDERR, + sprintf( + '%s declares an invalid value for PHP_VERSION.' . PHP_EOL . + 'This breaks fundamental functionality such as version_compare().' . PHP_EOL . + 'Please use a different PHP interpreter.' . PHP_EOL, + + PHP_BINARY + ) + ); + + die(1); +} + +if (version_compare('8.4.1', PHP_VERSION, '>')) { fwrite( STDERR, sprintf( 'PHPUnit X.Y.Z by Sebastian Bergmann and contributors.' . PHP_EOL . PHP_EOL . - 'This version of PHPUnit is supported on PHP 7.3 and PHP 7.4.' . PHP_EOL . + 'This version of PHPUnit requires PHP >= 8.4.1.' . PHP_EOL . 'You are using PHP %s (%s).' . PHP_EOL, PHP_VERSION, PHP_BINARY @@ -15,20 +30,45 @@ if (version_compare('7.3.0', PHP_VERSION, '>')) { die(1); } +$requiredExtensions = ['ctype', 'dom', 'json', 'libxml', 'mbstring', 'tokenizer', 'xml', 'xmlwriter']; + +$unavailableExtensions = array_filter( + $requiredExtensions, + static function ($extension) { + return !extension_loaded($extension); + } +); + +if ([] !== $unavailableExtensions) { + fwrite( + STDERR, + sprintf( + 'PHPUnit requires the "%s" extensions, but the "%s" %s not available.' . PHP_EOL, + implode('", "', $requiredExtensions), + implode('", "', $unavailableExtensions), + count($unavailableExtensions) === 1 ? 'extension is' : 'extensions are' + ) + ); + + die(1); +} + +unset($requiredExtensions, $unavailableExtensions); + if (__FILE__ === realpath($_SERVER['SCRIPT_NAME'])) { $execute = true; } else { $execute = false; } -$options = getopt('', array('prepend:', 'manifest')); - -if (isset($options['prepend'])) { - require $options['prepend']; -} +$options = getopt('', array('composer-lock', 'manifest', 'sbom')); -if (isset($options['manifest'])) { +if (isset($options['composer-lock'])) { + $printComposerLock = true; +} elseif (isset($options['manifest'])) { $printManifest = true; +} elseif (isset($options['sbom'])) { + $printSbom = true; } unset($options); @@ -38,19 +78,50 @@ define('__PHPUNIT_PHAR_ROOT__', 'phar://___PHAR___'); Phar::mapPhar('___PHAR___'); -___FILELIST___ +spl_autoload_register( + function ($class) { + static $classes = null; + + if ($classes === null) { + $classes = [___CLASSLIST___]; + } + + if (isset($classes[$class])) { + require_once 'phar://___PHAR___' . $classes[$class]; + } + }, + ___EXCEPTION___, + ___PREPEND___ +); + +foreach ([___CLASSLIST___] as $file) { + require_once 'phar://___PHAR___' . $file; +} + require __PHPUNIT_PHAR_ROOT__ . '/phpunit/Framework/Assert/Functions.php'; if ($execute) { + if (isset($printComposerLock)) { + print file_get_contents(__PHPUNIT_PHAR_ROOT__ . '/composer.lock'); + + exit; + } + if (isset($printManifest)) { print file_get_contents(__PHPUNIT_PHAR_ROOT__ . '/manifest.txt'); exit; } + if (isset($printSbom)) { + print file_get_contents(__PHPUNIT_PHAR_ROOT__ . '/sbom.xml'); + + exit; + } + unset($execute); - PHPUnit\TextUI\Command::main(); + exit((new PHPUnit\TextUI\Application)->run($_SERVER['argv'])); } __HALT_COMPILER(); diff --git a/build/templates/library-phar-autoload.php.in b/build/templates/library-phar-autoload.php.in index f09384fb03c..8f3fd2b8acb 100644 --- a/build/templates/library-phar-autoload.php.in +++ b/build/templates/library-phar-autoload.php.in @@ -4,7 +4,26 @@ define('__PHPUNIT_PHAR_ROOT__', 'phar://___PHAR___'); Phar::mapPhar('___PHAR___'); -___FILELIST___ +spl_autoload_register( + function ($class) { + static $classes = null; + + if ($classes === null) { + $classes = [___CLASSLIST___]; + } + + if (isset($classes[$class])) { + require_once 'phar://___PHAR___' . $classes[$class]; + } + }, + ___EXCEPTION___, + ___PREPEND___ +); + +foreach ([___CLASSLIST___] as $file) { + require_once 'phar://___PHAR___' . $file; +} + require __PHPUNIT_PHAR_ROOT__ . '/phpunit/Framework/Assert/Functions.php'; __HALT_COMPILER(); diff --git a/build/test-extension/manifest.xml b/build/test-extension/manifest.xml new file mode 100644 index 00000000000..5ab8593deea --- /dev/null +++ b/build/test-extension/manifest.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/build/test-extension/src/MyExtensionBootstrap.php b/build/test-extension/src/MyExtensionBootstrap.php new file mode 100644 index 00000000000..99a8457a1eb --- /dev/null +++ b/build/test-extension/src/MyExtensionBootstrap.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MyExtension; + +use PHPUnit\Runner\Extension\Extension; +use PHPUnit\Runner\Extension\Facade; +use PHPUnit\Runner\Extension\ParameterCollection; +use PHPUnit\TextUI\Configuration\Configuration; + +final class MyExtensionBootstrap implements Extension +{ + public function bootstrap(Configuration $configuration, Facade $facade, ParameterCollection $parameters): void + { + } +} diff --git a/composer.json b/composer.json index a9686e5a068..3aa2db95ee8 100644 --- a/composer.json +++ b/composer.json @@ -17,54 +17,45 @@ } ], "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues" + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy" }, "prefer-stable": true, - "minimum-stability": "dev", "require": { - "php": "^7.3 || ^8.0", + "php": ">=8.4.1", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "doctrine/instantiator": "^1.3.1", - "myclabs/deep-copy": "^1.9.5", - "phar-io/manifest": "^2.0.1", - "phar-io/version": "^3.0.2", - "phpspec/prophecy": "^1.10.3", - "phpunit/php-code-coverage": "^9.0", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-invoker": "^3.0.2", - "phpunit/php-text-template": "^2.0.2", - "phpunit/php-timer": "^5.0.1", - "sebastian/code-unit": "^1.0.5", - "sebastian/comparator": "^4.0.3", - "sebastian/diff": "^4.0.2", - "sebastian/environment": "^5.1.2", - "sebastian/exporter": "^4.0.2", - "sebastian/global-state": "^5.0", - "sebastian/object-enumerator": "^4.0.2", - "sebastian/resource-operations": "^3.0.2", - "sebastian/type": "^2.2.1", - "sebastian/version": "^3.0.1" - }, - "require-dev": { - "ext-PDO": "*", - "phpspec/prophecy-phpunit": "^2.0" + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "phpunit/php-code-coverage": "^12.4.0", + "phpunit/php-file-iterator": "^6.0.0", + "phpunit/php-invoker": "^6.0.0", + "phpunit/php-text-template": "^5.0.0", + "phpunit/php-timer": "^8.0.0", + "sebastian/cli-parser": "^4.2.0", + "sebastian/comparator": "^7.1.3", + "sebastian/diff": "^7.0.0", + "sebastian/environment": "^8.0.3", + "sebastian/exporter": "^7.0.2", + "sebastian/global-state": "^8.0.2", + "sebastian/object-enumerator": "^7.0.0", + "sebastian/type": "^6.0.3", + "sebastian/version": "^6.0.0", + "staabm/side-effects-detector": "^1.0.5" }, "config": { "platform": { - "php": "7.3.0" + "php": "8.4.1" }, + "classmap-authoritative": true, "optimize-autoloader": true, "sort-packages": true }, - "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" - }, "bin": [ "phpunit" ], @@ -78,17 +69,51 @@ }, "autoload-dev": { "classmap": [ - "tests/" + "tests/_files" ], "files": [ - "tests/_files/CoverageNamespacedFunctionTest.php", + "tests/_files/deprecation-trigger/trigger_deprecation.php", + "tests/unit/Event/AbstractEventTestCase.php", + "tests/unit/TextUI/AbstractSouceFilterTestCase.php", + "tests/unit/Framework/MockObject/TestDoubleTestCase.php", + "tests/unit/Metadata/Parser/AttributeParserTestCase.php", + "tests/unit/Framework/Assert/assertContainsOnlyArrayTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyBoolTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyCallableTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyFloatTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyInstancesOfTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyIntTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyIterableTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyNullTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyNumericTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyObjectTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyResourceTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyClosedResourceTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyScalarTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyStringTest.php", + "tests/unit/Framework/Assert/assertDirectoryExistsTest.php", + "tests/unit/Framework/Assert/assertFileExistsTest.php", + "tests/unit/Framework/Assert/assertIsNumericTest.php", + "tests/unit/Framework/Assert/assertIsObjectTest.php", + "tests/unit/Framework/Assert/assertIsReadableTest.php", + "tests/unit/Framework/Assert/assertIsResourceTest.php", + "tests/unit/Framework/Assert/assertIsScalarTest.php", + "tests/unit/Framework/Assert/assertIsStringTest.php", + "tests/unit/Framework/Assert/assertIsWritableTest.php", + "tests/unit/Framework/Assert/assertMatchesRegularExpressionTest.php", + "tests/unit/Framework/Assert/assertNullTest.php", + "tests/unit/Framework/Assert/assertSameSizeTest.php", + "tests/unit/Framework/Assert/assertSameTest.php", + "tests/unit/TextUI/Output/Default/ResultPrinterTest.php", "tests/_files/CoveredFunction.php", - "tests/_files/NamespaceCoveredFunction.php" + "tests/_files/Generator.php", + "tests/_files/NamespaceCoveredFunction.php", + "tests/end-to-end/_files/listing-tests-and-groups/ExampleAbstractTestCase.php" ] }, "extra": { "branch-alias": { - "dev-master": "9.3-dev" + "dev-main": "13.0-dev" } } } diff --git a/composer.lock b/composer.lock new file mode 100644 index 00000000000..d13babaaad6 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1599 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "69e5dc7fcb02f196f536d57ba935d9a9", + "packages": [ + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.6.2", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "3a454ca033b9e06b63282ce19562e892747449bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb", + "reference": "3a454ca033b9e06b63282ce19562e892747449bb", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2" + }, + "time": "2025-10-21T19:32:17+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "12.4.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "67e8aed88f93d0e6e1cb7effe1a2dfc2fee6022c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/67e8aed88f93d0e6e1cb7effe1a2dfc2fee6022c", + "reference": "67e8aed88f93d0e6e1cb7effe1a2dfc2fee6022c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.6.1", + "php": ">=8.3", + "phpunit/php-file-iterator": "^6.0", + "phpunit/php-text-template": "^5.0", + "sebastian/complexity": "^5.0", + "sebastian/environment": "^8.0.3", + "sebastian/lines-of-code": "^4.0", + "sebastian/version": "^6.0", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.3.7" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "12.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.4.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" + } + ], + "time": "2025-09-24T13:44:41+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "961bc913d42fe24a257bfff826a5068079ac7782" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/961bc913d42fe24a257bfff826a5068079ac7782", + "reference": "961bc913d42fe24a257bfff826a5068079ac7782", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:58:37+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^12.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:58:58+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/e1367a453f0eda562eedb4f659e13aa900d66c53", + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:59:16+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "8.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/8.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:59:38+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "4.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "90f41072d220e5c40df6e8635f5dafba2d9d4d04" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/90f41072d220e5c40df6e8635f5dafba2d9d4d04", + "reference": "90f41072d220e5c40df6e8635f5dafba2d9d4d04", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/4.2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/cli-parser", + "type": "tidelift" + } + ], + "time": "2025-09-14T09:36:45+00:00" + }, + { + "name": "sebastian/comparator", + "version": "7.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/dc904b4bb3ab070865fa4068cd84f3da8b945148", + "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.3", + "sebastian/diff": "^7.0", + "sebastian/exporter": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^12.2" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2025-08-20T11:27:00+00:00" + }, + { + "name": "sebastian/complexity", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/bad4316aba5303d0221f43f8cee37eb58d384bbb", + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:55:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7ab1ea946c012266ca32390913653d844ecd085f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7ab1ea946c012266ca32390913653d844ecd085f", + "reference": "7ab1ea946c012266ca32390913653d844ecd085f", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0", + "symfony/process": "^7.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:55:46+00:00" + }, + { + "name": "sebastian/environment", + "version": "8.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/24a711b5c916efc6d6e62aa65aa2ec98fef77f68", + "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/8.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" + } + ], + "time": "2025-08-12T14:11:56+00:00" + }, + { + "name": "sebastian/exporter", + "version": "7.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "016951ae10980765e4e7aee491eb288c64e505b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/016951ae10980765e4e7aee491eb288c64e505b7", + "reference": "016951ae10980765e4e7aee491eb288c64e505b7", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.3", + "sebastian/recursion-context": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/7.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2025-09-24T06:16:11+00:00" + }, + { + "name": "sebastian/global-state", + "version": "8.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "ef1377171613d09edd25b7816f05be8313f9115d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/ef1377171613d09edd25b7816f05be8313f9115d", + "reference": "ef1377171613d09edd25b7816f05be8313f9115d", + "shasum": "" + }, + "require": { + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" + } + ], + "time": "2025-08-29T11:29:25+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/97ffee3bcfb5805568d6af7f0f893678fc076d2f", + "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:57:28+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1effe8e9b8e068e9ae228e542d5d11b5d16db894", + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894", + "shasum": "" + }, + "require": { + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:57:48+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "4bfa827c969c98be1e527abd576533293c634f6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/4bfa827c969c98be1e527abd576533293c634f6a", + "reference": "4bfa827c969c98be1e527abd576533293c634f6a", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:58:17+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2025-08-13T04:44:59+00:00" + }, + { + "name": "sebastian/type", + "version": "6.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/e549163b9760b8f71f191651d22acf32d56d6d4d", + "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/6.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" + } + ], + "time": "2025-08-09T06:57:12+00:00" + }, + { + "name": "sebastian/version", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/3e6ccf7657d4f0a59200564b08cead899313b53c", + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T05:00:38+00:00" + }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": ">=8.4.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*" + }, + "platform-dev": {}, + "platform-overrides": { + "php": "8.4.1" + }, + "plugin-api-version": "2.9.0" +} diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 00000000000..57bdb4ea6f2 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,67 @@ +parameters: + level: 6 + + paths: + - src + + excludePaths: + # exclude partial traits, which are only used in runtime generated code + - src/Framework/MockObject/Runtime/Api + + resultCachePath: %tmpDir%/phpunit-13.0.php + + checkTooWideReturnTypesInProtectedAndPublicMethods: true + reportAlwaysTrueInLastCondition: true + reportPossiblyNonexistentConstantArrayOffset: true + reportPossiblyNonexistentGeneralArrayOffset: true + treatPhpDocTypesAsCertain: false + + strictRules: + allRules: false + booleansInConditions: true + closureUsesThis: true + disallowedBacktick: true + disallowedEmpty: true + disallowedImplicitArrayCreation: true + disallowedLooseComparison: true + disallowedShortTernary: true + illegalConstructorMethodCall: true + matchingInheritedMethodNames: true + noVariableVariables: true + numericOperandsInArithmeticOperators: true + overwriteVariablesWithLoop: true + requireParentConstructorCall: true + strictArrayFilter: true + strictFunctionCalls: true + switchConditionsMatchingType: true + uselessCast: true + + ergebnis: + allRules: false + final: + enabled: true + classesNotRequiredToBeAbstractOrFinal: + - PHPUnit\Framework\Constraint\Count + - PHPUnit\Framework\AssertionFailedError + - PHPUnit\Framework\Exception + - PHPUnit\Framework\TestSuite + privateInFinalClass: + enabled: true + + type_coverage: + declare: 100 + return: 100 + param: 100 + property: 100 + constant: 100 + + ignoreErrors: + # ignore errors caused by defensive programming + - '#Call to function assert\(\) with true will always evaluate to true.#' + - '#Instanceof between .* and .* will always evaluate to true.#' + - '#Strict comparison using !== between .*non-empty-string.* and .* will always evaluate to true.#' + - '#Strict comparison using !== between .*non-falsy-string.* and .* will always evaluate to true.#' + - identifier: argument.named + +includes: + - phar://phpstan.phar/conf/bleedingEdge.neon diff --git a/phpunit b/phpunit index 9c6ce956c0c..7cace6af4ce 100755 --- a/phpunit +++ b/phpunit @@ -9,11 +9,26 @@ * file that was distributed with this source code. */ -if (version_compare('7.3.0', PHP_VERSION, '>')) { +if (!version_compare(PHP_VERSION, PHP_VERSION, '=')) { fwrite( STDERR, sprintf( - 'This version of PHPUnit is supported on PHP 7.3 and PHP 7.4.' . PHP_EOL . + '%s declares an invalid value for PHP_VERSION.' . PHP_EOL . + 'This breaks fundamental functionality such as version_compare().' . PHP_EOL . + 'Please use a different PHP interpreter.' . PHP_EOL, + + PHP_BINARY + ) + ); + + die(1); +} + +if (version_compare('8.4.1', PHP_VERSION, '>')) { + fwrite( + STDERR, + sprintf( + 'This version of PHPUnit requires PHP >= 8.4.1.' . PHP_EOL . 'You are using PHP %s (%s).' . PHP_EOL, PHP_VERSION, PHP_BINARY @@ -27,15 +42,21 @@ if (!ini_get('date.timezone')) { ini_set('date.timezone', 'UTC'); } -foreach (array(__DIR__ . '/../../autoload.php', __DIR__ . '/../vendor/autoload.php', __DIR__ . '/vendor/autoload.php') as $file) { - if (file_exists($file)) { - define('PHPUNIT_COMPOSER_INSTALL', $file); +if (isset($GLOBALS['_composer_autoload_path'])) { + define('PHPUNIT_COMPOSER_INSTALL', $GLOBALS['_composer_autoload_path']); - break; + unset($GLOBALS['_composer_autoload_path']); +} else { + foreach (array(__DIR__ . '/../../autoload.php', __DIR__ . '/../vendor/autoload.php', __DIR__ . '/vendor/autoload.php') as $file) { + if (file_exists($file)) { + define('PHPUNIT_COMPOSER_INSTALL', $file); + + break; + } } -} -unset($file); + unset($file); +} if (!defined('PHPUNIT_COMPOSER_INSTALL')) { fwrite( @@ -48,14 +69,36 @@ if (!defined('PHPUNIT_COMPOSER_INSTALL')) { die(1); } -$options = getopt('', array('prepend:')); +require PHPUNIT_COMPOSER_INSTALL; + +$requiredExtensions = ['dom', 'json', 'libxml', 'mbstring', 'tokenizer', 'xml', 'xmlwriter']; + +$unavailableExtensions = array_filter( + $requiredExtensions, + static function ($extension) { + return !extension_loaded($extension); + } +); -if (isset($options['prepend'])) { - require $options['prepend']; +// Workaround for https://github.com/sebastianbergmann/phpunit/issues/5662 +if (!function_exists('ctype_alnum')) { + $unavailableExtensions[] = 'ctype'; } -unset($options); +if ([] !== $unavailableExtensions) { + fwrite( + STDERR, + sprintf( + 'PHPUnit requires the "%s" extensions, but the "%s" %s not available.' . PHP_EOL, + implode('", "', $requiredExtensions), + implode('", "', $unavailableExtensions), + count($unavailableExtensions) === 1 ? 'extension is' : 'extensions are' + ) + ); -require PHPUNIT_COMPOSER_INSTALL; + die(1); +} + +unset($requiredExtensions, $unavailableExtensions); -PHPUnit\TextUI\Command::main(); +exit((new PHPUnit\TextUI\Application)->run($_SERVER['argv'])); diff --git a/phpunit.xml b/phpunit.xml index e642c7250e9..088c4197e79 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -2,33 +2,66 @@ + failOnWarning="true" + colors="true"> tests/unit - tests/end-to-end - tests/end-to-end/_files + tests/end-to-end/baseline + tests/end-to-end/check-php-configuration + tests/end-to-end/cli + tests/end-to-end/data-provider + tests/end-to-end/deprecation-trigger + tests/end-to-end/event + tests/end-to-end/execution-order + tests/end-to-end/extension-cli + tests/end-to-end/extension-xml + tests/end-to-end/generic + tests/end-to-end/groups-from-configuration + tests/end-to-end/logging/junit + tests/end-to-end/logging/open-test-reporting + tests/end-to-end/logging/teamcity + tests/end-to-end/logging/testdox + tests/end-to-end/metadata + tests/end-to-end/migration + tests/end-to-end/mock-objects + tests/end-to-end/phpt + tests/end-to-end/regression + tests/end-to-end/self-direct-indirect + tests/end-to-end/testdox + + tests/end-to-end/event/_files + tests/end-to-end/execution-order/_files + tests/end-to-end/groups-from-configuration/_files + tests/end-to-end/logging/_files + tests/end-to-end/migration/_files + tests/end-to-end/self-direct-indirect/_files + tests/end-to-end/testdox/_files - + - src + src src/Framework/Assert/Functions.php - src/Util/PHP/eval-stdin.php - + + + + diff --git a/phpunit.xsd b/phpunit.xsd index 72cd8f09dcc..7f98dabf25e 100644 --- a/phpunit.xsd +++ b/phpunit.xsd @@ -2,7 +2,7 @@ - This Schema file defines the rules by which the XML configuration file of PHPUnit 9.3 may be structured. + This Schema file defines the rules by which the XML configuration file of PHPUnit 13.0 may be structured. @@ -11,18 +11,52 @@ Root Element - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -31,10 +65,8 @@ - - @@ -57,62 +89,19 @@ - - - - - - + - + - - - - - + - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -125,27 +114,11 @@ - - - - - - - - - - - - - - - - - + @@ -164,17 +137,6 @@ - - - - - - - - - - - @@ -207,65 +169,72 @@ - + + - - - - - - - - + + + + + + + + + + + + + + + - + - - - - - + - - - + - - - + - - + - + + + + + + + + + + - + - @@ -279,16 +248,51 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -297,11 +301,10 @@ + - - @@ -310,6 +313,10 @@ + + + + @@ -318,10 +325,24 @@ + + + + + + + + + + + + + + diff --git a/schema/10.0.xsd b/schema/10.0.xsd new file mode 100644 index 00000000000..480d54decab --- /dev/null +++ b/schema/10.0.xsd @@ -0,0 +1,284 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 10.0 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/10.1.xsd b/schema/10.1.xsd new file mode 100644 index 00000000000..1b190c21b90 --- /dev/null +++ b/schema/10.1.xsd @@ -0,0 +1,312 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 10.1 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/10.2.xsd b/schema/10.2.xsd new file mode 100644 index 00000000000..269b7a3aeb4 --- /dev/null +++ b/schema/10.2.xsd @@ -0,0 +1,319 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 10.2 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/10.3.xsd b/schema/10.3.xsd new file mode 100644 index 00000000000..03a54ee0b08 --- /dev/null +++ b/schema/10.3.xsd @@ -0,0 +1,321 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 10.3 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/10.4.xsd b/schema/10.4.xsd new file mode 100644 index 00000000000..bd22b2ca2a7 --- /dev/null +++ b/schema/10.4.xsd @@ -0,0 +1,322 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 10.4 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/10.5.xsd b/schema/10.5.xsd new file mode 100644 index 00000000000..284820b0b9c --- /dev/null +++ b/schema/10.5.xsd @@ -0,0 +1,327 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 10.5 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/11.0.xsd b/schema/11.0.xsd new file mode 100644 index 00000000000..a6e7cb8cd16 --- /dev/null +++ b/schema/11.0.xsd @@ -0,0 +1,323 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 11.0 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/11.1.xsd b/schema/11.1.xsd new file mode 100644 index 00000000000..6172e834933 --- /dev/null +++ b/schema/11.1.xsd @@ -0,0 +1,333 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 11.1 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/11.2.xsd b/schema/11.2.xsd new file mode 100644 index 00000000000..d7c7dcac065 --- /dev/null +++ b/schema/11.2.xsd @@ -0,0 +1,331 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 11.2 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/11.3.xsd b/schema/11.3.xsd new file mode 100644 index 00000000000..3b30de4d6f7 --- /dev/null +++ b/schema/11.3.xsd @@ -0,0 +1,335 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 11.3 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/11.4.xsd b/schema/11.4.xsd new file mode 100644 index 00000000000..52db363026a --- /dev/null +++ b/schema/11.4.xsd @@ -0,0 +1,334 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 11.4 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/11.5.xsd b/schema/11.5.xsd new file mode 100644 index 00000000000..ec0227111c9 --- /dev/null +++ b/schema/11.5.xsd @@ -0,0 +1,339 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 11.5 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/12.0.xsd b/schema/12.0.xsd new file mode 100644 index 00000000000..c993640f9fb --- /dev/null +++ b/schema/12.0.xsd @@ -0,0 +1,335 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 12.0 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/12.1.xsd b/schema/12.1.xsd new file mode 100644 index 00000000000..51f225d45ab --- /dev/null +++ b/schema/12.1.xsd @@ -0,0 +1,339 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 12.1 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/12.2.xsd b/schema/12.2.xsd new file mode 100644 index 00000000000..9303b643344 --- /dev/null +++ b/schema/12.2.xsd @@ -0,0 +1,347 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 12.2 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/12.3.xsd b/schema/12.3.xsd new file mode 100644 index 00000000000..73e7c2144ab --- /dev/null +++ b/schema/12.3.xsd @@ -0,0 +1,348 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 12.3 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/12.4.xsd b/schema/12.4.xsd new file mode 100644 index 00000000000..8a7ce622452 --- /dev/null +++ b/schema/12.4.xsd @@ -0,0 +1,348 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 12.4 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/12.5.xsd b/schema/12.5.xsd new file mode 100644 index 00000000000..53f869f65f5 --- /dev/null +++ b/schema/12.5.xsd @@ -0,0 +1,348 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 12.5 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/8.5.xsd b/schema/8.5.xsd new file mode 100644 index 00000000000..75e22289471 --- /dev/null +++ b/schema/8.5.xsd @@ -0,0 +1,319 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 8.5 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/9.0.xsd b/schema/9.0.xsd new file mode 100644 index 00000000000..6db04c09789 --- /dev/null +++ b/schema/9.0.xsd @@ -0,0 +1,315 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 9.0 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/9.1.xsd b/schema/9.1.xsd new file mode 100644 index 00000000000..b10d30b4619 --- /dev/null +++ b/schema/9.1.xsd @@ -0,0 +1,317 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 9.0 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/9.2.xsd b/schema/9.2.xsd new file mode 100644 index 00000000000..d770e8b03bd --- /dev/null +++ b/schema/9.2.xsd @@ -0,0 +1,317 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 9.2 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/9.3.xsd b/schema/9.3.xsd new file mode 100644 index 00000000000..638f663ac7b --- /dev/null +++ b/schema/9.3.xsd @@ -0,0 +1,327 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 9.3 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/9.4.xsd b/schema/9.4.xsd new file mode 100644 index 00000000000..75a91e832f8 --- /dev/null +++ b/schema/9.4.xsd @@ -0,0 +1,328 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 9.4 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/9.5.xsd b/schema/9.5.xsd new file mode 100644 index 00000000000..eabefac30b0 --- /dev/null +++ b/schema/9.5.xsd @@ -0,0 +1,330 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 9.5 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Event/Dispatcher/CollectingDispatcher.php b/src/Event/Dispatcher/CollectingDispatcher.php new file mode 100644 index 00000000000..e3e9462ee6a --- /dev/null +++ b/src/Event/Dispatcher/CollectingDispatcher.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use PHPUnit\Runner\DeprecationCollector\Facade as DeprecationCollector; +use PHPUnit\Runner\DeprecationCollector\TestTriggeredDeprecationSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CollectingDispatcher implements Dispatcher +{ + private EventCollection $events; + private DirectDispatcher $isolatedDirectDispatcher; + + public function __construct(DirectDispatcher $directDispatcher) + { + $this->isolatedDirectDispatcher = $directDispatcher; + $this->events = new EventCollection; + + $this->isolatedDirectDispatcher->registerSubscriber(new TestTriggeredDeprecationSubscriber(DeprecationCollector::collector())); + } + + public function dispatch(Event $event): void + { + $this->events->add($event); + + try { + $this->isolatedDirectDispatcher->dispatch($event); + } catch (UnknownEventTypeException) { + // Do nothing. + } + } + + public function flush(): EventCollection + { + $events = $this->events; + + $this->events = new EventCollection; + + return $events; + } +} diff --git a/src/Event/Dispatcher/DeferringDispatcher.php b/src/Event/Dispatcher/DeferringDispatcher.php new file mode 100644 index 00000000000..6895facb386 --- /dev/null +++ b/src/Event/Dispatcher/DeferringDispatcher.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class DeferringDispatcher implements SubscribableDispatcher +{ + private readonly SubscribableDispatcher $dispatcher; + private EventCollection $events; + private bool $recording = true; + + public function __construct(SubscribableDispatcher $dispatcher) + { + $this->dispatcher = $dispatcher; + $this->events = new EventCollection; + } + + public function registerTracer(Tracer\Tracer $tracer): void + { + $this->dispatcher->registerTracer($tracer); + } + + public function registerSubscriber(Subscriber $subscriber): void + { + $this->dispatcher->registerSubscriber($subscriber); + } + + public function dispatch(Event $event): void + { + if ($this->recording) { + $this->events->add($event); + + return; + } + + $this->dispatcher->dispatch($event); + } + + public function flush(): void + { + $this->recording = false; + + foreach ($this->events as $event) { + $this->dispatcher->dispatch($event); + } + + $this->events = new EventCollection; + } +} diff --git a/src/Event/Dispatcher/DirectDispatcher.php b/src/Event/Dispatcher/DirectDispatcher.php new file mode 100644 index 00000000000..b5cbc8e31c1 --- /dev/null +++ b/src/Event/Dispatcher/DirectDispatcher.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use const PHP_EOL; +use function array_key_exists; +use function dirname; +use function sprintf; +use function str_starts_with; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class DirectDispatcher implements SubscribableDispatcher +{ + private readonly TypeMap $typeMap; + + /** + * @var array> + */ + private array $subscribers = []; + + /** + * @var list + */ + private array $tracers = []; + + public function __construct(TypeMap $map) + { + $this->typeMap = $map; + } + + public function registerTracer(Tracer\Tracer $tracer): void + { + $this->tracers[] = $tracer; + } + + /** + * @throws MapError + * @throws UnknownSubscriberTypeException + */ + public function registerSubscriber(Subscriber $subscriber): void + { + if (!$this->typeMap->isKnownSubscriberType($subscriber)) { + throw new UnknownSubscriberTypeException( + sprintf( + 'Subscriber "%s" does not implement any known interface - did you forget to register it?', + $subscriber::class, + ), + ); + } + + $eventClassName = $this->typeMap->map($subscriber); + + if (!array_key_exists($eventClassName, $this->subscribers)) { + $this->subscribers[$eventClassName] = []; + } + + $this->subscribers[$eventClassName][] = $subscriber; + } + + /** + * @throws Throwable + * @throws UnknownEventTypeException + */ + public function dispatch(Event $event): void + { + $eventClassName = $event::class; + + if (!$this->typeMap->isKnownEventType($event)) { + throw new UnknownEventTypeException( + sprintf( + 'Unknown event type "%s"', + $eventClassName, + ), + ); + } + + foreach ($this->tracers as $tracer) { + try { + $tracer->trace($event); + // @codeCoverageIgnoreStart + } catch (Throwable $t) { + $this->handleThrowable($t); + } + // @codeCoverageIgnoreEnd + } + + if (!array_key_exists($eventClassName, $this->subscribers)) { + return; + } + + foreach ($this->subscribers[$eventClassName] as $subscriber) { + try { + /** @phpstan-ignore method.notFound */ + $subscriber->notify($event); + } catch (Throwable $t) { + $this->handleThrowable($t); + } + } + } + + /** + * @throws Throwable + */ + public function handleThrowable(Throwable $t): void + { + if ($this->isThrowableFromThirdPartySubscriber($t)) { + Facade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Exception in third-party event subscriber: %s%s%s', + $t->getMessage(), + PHP_EOL, + $t->getTraceAsString(), + ), + ); + + return; + } + + // @codeCoverageIgnoreStart + throw $t; + // @codeCoverageIgnoreEnd + } + + private function isThrowableFromThirdPartySubscriber(Throwable $t): bool + { + return !str_starts_with($t->getFile(), dirname(__DIR__, 2)); + } +} diff --git a/src/Event/Dispatcher/Dispatcher.php b/src/Event/Dispatcher/Dispatcher.php new file mode 100644 index 00000000000..e7086539539 --- /dev/null +++ b/src/Event/Dispatcher/Dispatcher.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface Dispatcher +{ + /** + * @throws UnknownEventTypeException + */ + public function dispatch(Event $event): void; +} diff --git a/src/Event/Dispatcher/SubscribableDispatcher.php b/src/Event/Dispatcher/SubscribableDispatcher.php new file mode 100644 index 00000000000..c4393da1249 --- /dev/null +++ b/src/Event/Dispatcher/SubscribableDispatcher.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface SubscribableDispatcher extends Dispatcher +{ + /** + * @throws UnknownSubscriberTypeException + */ + public function registerSubscriber(Subscriber $subscriber): void; + + public function registerTracer(Tracer\Tracer $tracer): void; +} diff --git a/src/Event/Emitter/DispatchingEmitter.php b/src/Event/Emitter/DispatchingEmitter.php new file mode 100644 index 00000000000..bf18513d84f --- /dev/null +++ b/src/Event/Emitter/DispatchingEmitter.php @@ -0,0 +1,1463 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use function assert; +use function memory_reset_peak_usage; +use function preg_match; +use PHPUnit\Event\Code\ClassMethod; +use PHPUnit\Event\Code\ComparisonFailure; +use PHPUnit\Event\Code\IssueTrigger\IssueTrigger; +use PHPUnit\Event\Code\NoTestCaseObjectOnCallStackException; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\Code\TestMethodBuilder; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Test\DataProviderMethodCalled; +use PHPUnit\Event\Test\DataProviderMethodFinished; +use PHPUnit\Event\TestSuite\Filtered as TestSuiteFiltered; +use PHPUnit\Event\TestSuite\Finished as TestSuiteFinished; +use PHPUnit\Event\TestSuite\Loaded as TestSuiteLoaded; +use PHPUnit\Event\TestSuite\Skipped as TestSuiteSkipped; +use PHPUnit\Event\TestSuite\Sorted as TestSuiteSorted; +use PHPUnit\Event\TestSuite\Started as TestSuiteStarted; +use PHPUnit\Event\TestSuite\TestSuite; +use PHPUnit\Framework\TestCase; +use PHPUnit\Metadata\IgnorePhpunitWarnings; +use PHPUnit\Metadata\Parser\Registry; +use PHPUnit\TextUI\Configuration\Configuration; +use SebastianBergmann\Comparator\Comparator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class DispatchingEmitter implements Emitter +{ + private readonly Dispatcher $dispatcher; + private readonly Telemetry\System $system; + private readonly Telemetry\Snapshot $startSnapshot; + private Telemetry\Snapshot $previousSnapshot; + + public function __construct(Dispatcher $dispatcher, Telemetry\System $system) + { + $this->dispatcher = $dispatcher; + $this->system = $system; + + $this->startSnapshot = $system->snapshot(); + $this->previousSnapshot = $this->startSnapshot; + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function applicationStarted(): void + { + $this->dispatcher->dispatch( + new Application\Started( + $this->telemetryInfo(), + new Runtime\Runtime, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerStarted(): void + { + $this->dispatcher->dispatch( + new TestRunner\Started( + $this->telemetryInfo(), + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerConfigured(Configuration $configuration): void + { + $this->dispatcher->dispatch( + new TestRunner\Configured( + $this->telemetryInfo(), + $configuration, + ), + ); + } + + /** + * @param non-empty-string $filename + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerBootstrapFinished(string $filename): void + { + $this->dispatcher->dispatch( + new TestRunner\BootstrapFinished( + $this->telemetryInfo(), + $filename, + ), + ); + } + + /** + * @param non-empty-string $filename + * @param non-empty-string $name + * @param non-empty-string $version + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerLoadedExtensionFromPhar(string $filename, string $name, string $version): void + { + $this->dispatcher->dispatch( + new TestRunner\ExtensionLoadedFromPhar( + $this->telemetryInfo(), + $filename, + $name, + $version, + ), + ); + } + + /** + * @param class-string $className + * @param array $parameters + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerBootstrappedExtension(string $className, array $parameters): void + { + $this->dispatcher->dispatch( + new TestRunner\ExtensionBootstrapped( + $this->telemetryInfo(), + $className, + $parameters, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function dataProviderMethodCalled(ClassMethod $testMethod, ClassMethod $dataProviderMethod): void + { + $this->dispatcher->dispatch( + new DataProviderMethodCalled( + $this->telemetryInfo(), + $testMethod, + $dataProviderMethod, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function dataProviderMethodFinished(ClassMethod $testMethod, ClassMethod ...$calledMethods): void + { + $this->dispatcher->dispatch( + new DataProviderMethodFinished( + $this->telemetryInfo(), + $testMethod, + ...$calledMethods, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testSuiteLoaded(TestSuite $testSuite): void + { + $this->dispatcher->dispatch( + new TestSuiteLoaded( + $this->telemetryInfo(), + $testSuite, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testSuiteFiltered(TestSuite $testSuite): void + { + $this->dispatcher->dispatch( + new TestSuiteFiltered( + $this->telemetryInfo(), + $testSuite, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testSuiteSorted(int $executionOrder, int $executionOrderDefects, bool $resolveDependencies): void + { + $this->dispatcher->dispatch( + new TestSuiteSorted( + $this->telemetryInfo(), + $executionOrder, + $executionOrderDefects, + $resolveDependencies, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerEventFacadeSealed(): void + { + $this->dispatcher->dispatch( + new TestRunner\EventFacadeSealed( + $this->telemetryInfo(), + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerExecutionStarted(TestSuite $testSuite): void + { + $this->dispatcher->dispatch( + new TestRunner\ExecutionStarted( + $this->telemetryInfo(), + $testSuite, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerDisabledGarbageCollection(): void + { + $this->dispatcher->dispatch( + new TestRunner\GarbageCollectionDisabled($this->telemetryInfo()), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerTriggeredGarbageCollection(): void + { + $this->dispatcher->dispatch( + new TestRunner\GarbageCollectionTriggered($this->telemetryInfo()), + ); + } + + public function childProcessStarted(): void + { + $this->dispatcher->dispatch( + new TestRunner\ChildProcessStarted($this->telemetryInfo()), + ); + } + + public function childProcessErrored(): void + { + $this->dispatcher->dispatch( + new TestRunner\ChildProcessErrored($this->telemetryInfo()), + ); + } + + public function childProcessFinished(string $stdout, string $stderr): void + { + $this->dispatcher->dispatch( + new TestRunner\ChildProcessFinished( + $this->telemetryInfo(), + $stdout, + $stderr, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testSuiteSkipped(TestSuite $testSuite, string $message): void + { + $this->dispatcher->dispatch( + new TestSuiteSkipped( + $this->telemetryInfo(), + $testSuite, + $message, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testSuiteStarted(TestSuite $testSuite): void + { + $this->dispatcher->dispatch( + new TestSuiteStarted( + $this->telemetryInfo(), + $testSuite, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testPreparationStarted(Code\Test $test): void + { + $this->dispatcher->dispatch( + new Test\PreparationStarted( + $this->telemetryInfo(), + $test, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testPreparationErrored(Code\Test $test, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\PreparationErrored( + $this->telemetryInfo(), + $test, + $throwable, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testPreparationFailed(Code\Test $test, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\PreparationFailed( + $this->telemetryInfo(), + $test, + $throwable, + ), + ); + } + + /** + * @param class-string $testClassName + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function beforeFirstTestMethodCalled(string $testClassName, ClassMethod $calledMethod): void + { + $this->dispatcher->dispatch( + new Test\BeforeFirstTestMethodCalled( + $this->telemetryInfo(), + $testClassName, + $calledMethod, + ), + ); + } + + /** + * @param class-string $testClassName + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function beforeFirstTestMethodErrored(string $testClassName, ClassMethod $calledMethod, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\BeforeFirstTestMethodErrored( + $this->telemetryInfo(), + $testClassName, + $calledMethod, + $throwable, + ), + ); + } + + /** + * @param class-string $testClassName + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function beforeFirstTestMethodFailed(string $testClassName, ClassMethod $calledMethod, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\BeforeFirstTestMethodFailed( + $this->telemetryInfo(), + $testClassName, + $calledMethod, + $throwable, + ), + ); + } + + /** + * @param class-string $testClassName + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function beforeFirstTestMethodFinished(string $testClassName, ClassMethod ...$calledMethods): void + { + $this->dispatcher->dispatch( + new Test\BeforeFirstTestMethodFinished( + $this->telemetryInfo(), + $testClassName, + ...$calledMethods, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function beforeTestMethodCalled(TestMethod $test, ClassMethod $calledMethod): void + { + $this->dispatcher->dispatch( + new Test\BeforeTestMethodCalled( + $this->telemetryInfo(), + $test, + $calledMethod, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function beforeTestMethodErrored(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\BeforeTestMethodErrored( + $this->telemetryInfo(), + $test, + $calledMethod, + $throwable, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function beforeTestMethodFailed(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\BeforeTestMethodFailed( + $this->telemetryInfo(), + $test, + $calledMethod, + $throwable, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function beforeTestMethodFinished(TestMethod $test, ClassMethod ...$calledMethods): void + { + $this->dispatcher->dispatch( + new Test\BeforeTestMethodFinished( + $this->telemetryInfo(), + $test, + ...$calledMethods, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function preConditionCalled(TestMethod $test, ClassMethod $calledMethod): void + { + $this->dispatcher->dispatch( + new Test\PreConditionCalled( + $this->telemetryInfo(), + $test, + $calledMethod, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function preConditionErrored(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\PreConditionErrored( + $this->telemetryInfo(), + $test, + $calledMethod, + $throwable, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function preConditionFailed(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\PreConditionFailed( + $this->telemetryInfo(), + $test, + $calledMethod, + $throwable, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function preConditionFinished(TestMethod $test, ClassMethod ...$calledMethods): void + { + $this->dispatcher->dispatch( + new Test\PreConditionFinished( + $this->telemetryInfo(), + $test, + ...$calledMethods, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testPrepared(Code\Test $test): void + { + memory_reset_peak_usage(); + + $this->dispatcher->dispatch( + new Test\Prepared( + $this->telemetryInfo(), + $test, + ), + ); + } + + /** + * @param class-string $className + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRegisteredComparator(string $className): void + { + $this->dispatcher->dispatch( + new Test\ComparatorRegistered( + $this->telemetryInfo(), + $className, + ), + ); + } + + /** + * @param class-string $className + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testCreatedMockObject(string $className): void + { + $this->dispatcher->dispatch( + new Test\MockObjectCreated( + $this->telemetryInfo(), + $className, + ), + ); + } + + /** + * @param list $interfaces + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testCreatedMockObjectForIntersectionOfInterfaces(array $interfaces): void + { + $this->dispatcher->dispatch( + new Test\MockObjectForIntersectionOfInterfacesCreated( + $this->telemetryInfo(), + $interfaces, + ), + ); + } + + /** + * @param class-string $className + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testCreatedPartialMockObject(string $className, string ...$methodNames): void + { + $this->dispatcher->dispatch( + new Test\PartialMockObjectCreated( + $this->telemetryInfo(), + $className, + ...$methodNames, + ), + ); + } + + /** + * @param class-string $className + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testCreatedStub(string $className): void + { + $this->dispatcher->dispatch( + new Test\TestStubCreated( + $this->telemetryInfo(), + $className, + ), + ); + } + + /** + * @param list $interfaces + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testCreatedStubForIntersectionOfInterfaces(array $interfaces): void + { + $this->dispatcher->dispatch( + new Test\TestStubForIntersectionOfInterfacesCreated( + $this->telemetryInfo(), + $interfaces, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testErrored(Code\Test $test, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\Errored( + $this->telemetryInfo(), + $test, + $throwable, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testFailed(Code\Test $test, Throwable $throwable, ?ComparisonFailure $comparisonFailure): void + { + $this->dispatcher->dispatch( + new Test\Failed( + $this->telemetryInfo(), + $test, + $throwable, + $comparisonFailure, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testPassed(Code\Test $test): void + { + $this->dispatcher->dispatch( + new Test\Passed( + $this->telemetryInfo(), + $test, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testConsideredRisky(Code\Test $test, string $message): void + { + $this->dispatcher->dispatch( + new Test\ConsideredRisky( + $this->telemetryInfo(), + $test, + $message, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testMarkedAsIncomplete(Code\Test $test, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\MarkedIncomplete( + $this->telemetryInfo(), + $test, + $throwable, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testSkipped(Code\Test $test, string $message): void + { + $this->dispatcher->dispatch( + new Test\Skipped( + $this->telemetryInfo(), + $test, + $message, + ), + ); + } + + /** + * @param non-empty-string $message + * + * @throws InvalidArgumentException + * @throws NoTestCaseObjectOnCallStackException + * @throws UnknownEventTypeException + */ + public function testTriggeredPhpunitDeprecation(?Code\Test $test, string $message): void + { + if ($test === null) { + $test = TestMethodBuilder::fromCallStack(); + } + + if ($test->isTestMethod()) { + assert($test instanceof TestMethod); + + if ($test->metadata()->isIgnorePhpunitDeprecations()->isNotEmpty()) { + return; + } + } + + $this->dispatcher->dispatch( + new Test\PhpunitDeprecationTriggered( + $this->telemetryInfo(), + $test, + $message, + ), + ); + } + + /** + * @param non-empty-string $message + * + * @throws InvalidArgumentException + * @throws NoTestCaseObjectOnCallStackException + * @throws UnknownEventTypeException + */ + public function testTriggeredPhpunitNotice(?Code\Test $test, string $message): void + { + if ($test === null) { + $test = TestMethodBuilder::fromCallStack(); + } + + $this->dispatcher->dispatch( + new Test\PhpunitNoticeTriggered( + $this->telemetryInfo(), + $test, + $message, + ), + ); + } + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testTriggeredPhpDeprecation(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline, bool $ignoredByTest, IssueTrigger $trigger): void + { + $this->dispatcher->dispatch( + new Test\PhpDeprecationTriggered( + $this->telemetryInfo(), + $test, + $message, + $file, + $line, + $suppressed, + $ignoredByBaseline, + $ignoredByTest, + $trigger, + ), + ); + } + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + * @param non-empty-string $stackTrace + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testTriggeredDeprecation(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline, bool $ignoredByTest, IssueTrigger $trigger, string $stackTrace): void + { + $this->dispatcher->dispatch( + new Test\DeprecationTriggered( + $this->telemetryInfo(), + $test, + $message, + $file, + $line, + $suppressed, + $ignoredByBaseline, + $ignoredByTest, + $trigger, + $stackTrace, + ), + ); + } + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testTriggeredError(Code\Test $test, string $message, string $file, int $line, bool $suppressed): void + { + $this->dispatcher->dispatch( + new Test\ErrorTriggered( + $this->telemetryInfo(), + $test, + $message, + $file, + $line, + $suppressed, + ), + ); + } + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testTriggeredNotice(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void + { + $this->dispatcher->dispatch( + new Test\NoticeTriggered( + $this->telemetryInfo(), + $test, + $message, + $file, + $line, + $suppressed, + $ignoredByBaseline, + ), + ); + } + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testTriggeredPhpNotice(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void + { + $this->dispatcher->dispatch( + new Test\PhpNoticeTriggered( + $this->telemetryInfo(), + $test, + $message, + $file, + $line, + $suppressed, + $ignoredByBaseline, + ), + ); + } + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testTriggeredWarning(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void + { + $this->dispatcher->dispatch( + new Test\WarningTriggered( + $this->telemetryInfo(), + $test, + $message, + $file, + $line, + $suppressed, + $ignoredByBaseline, + ), + ); + } + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testTriggeredPhpWarning(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void + { + $this->dispatcher->dispatch( + new Test\PhpWarningTriggered( + $this->telemetryInfo(), + $test, + $message, + $file, + $line, + $suppressed, + $ignoredByBaseline, + ), + ); + } + + /** + * @param non-empty-string $message + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testTriggeredPhpunitError(Code\Test $test, string $message): void + { + $this->dispatcher->dispatch( + new Test\PhpunitErrorTriggered( + $this->telemetryInfo(), + $test, + $message, + ), + ); + } + + /** + * @param non-empty-string $message + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testTriggeredPhpunitWarning(Code\Test $test, string $message): void + { + $ignoredByTest = false; + + if ($test->isTestMethod()) { + assert($test instanceof TestMethod); + + $metadata = Registry::parser()->forMethod($test->className(), $test->methodName())->isIgnorePhpunitWarnings()->asArray(); + + if (isset($metadata[0])) { + assert($metadata[0] instanceof IgnorePhpunitWarnings); + + $messagePattern = $metadata[0]->messagePattern(); + + if ($messagePattern === null || (bool) preg_match('{' . $messagePattern . '}', $message)) { + $ignoredByTest = true; + } + } + } + + $this->dispatcher->dispatch( + new Test\PhpunitWarningTriggered( + $this->telemetryInfo(), + $test, + $message, + $ignoredByTest, + ), + ); + } + + /** + * @param non-empty-string $output + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testPrintedUnexpectedOutput(string $output): void + { + $this->dispatcher->dispatch( + new Test\PrintedUnexpectedOutput( + $this->telemetryInfo(), + $output, + ), + ); + } + + /** + * @param non-empty-string $additionalInformation + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testProvidedAdditionalInformation(TestMethod $test, string $additionalInformation): void + { + $this->dispatcher->dispatch( + new Test\AdditionalInformationProvided( + $this->telemetryInfo(), + $test, + $additionalInformation, + ), + ); + } + + /** + * @param non-negative-int $numberOfAssertionsPerformed + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testFinished(Code\Test $test, int $numberOfAssertionsPerformed): void + { + $this->dispatcher->dispatch( + new Test\Finished( + $this->telemetryInfo(), + $test, + $numberOfAssertionsPerformed, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function postConditionCalled(TestMethod $test, ClassMethod $calledMethod): void + { + $this->dispatcher->dispatch( + new Test\PostConditionCalled( + $this->telemetryInfo(), + $test, + $calledMethod, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function postConditionErrored(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\PostConditionErrored( + $this->telemetryInfo(), + $test, + $calledMethod, + $throwable, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function postConditionFailed(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\PostConditionFailed( + $this->telemetryInfo(), + $test, + $calledMethod, + $throwable, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function postConditionFinished(TestMethod $test, ClassMethod ...$calledMethods): void + { + $this->dispatcher->dispatch( + new Test\PostConditionFinished( + $this->telemetryInfo(), + $test, + ...$calledMethods, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function afterTestMethodCalled(TestMethod $test, ClassMethod $calledMethod): void + { + $this->dispatcher->dispatch( + new Test\AfterTestMethodCalled( + $this->telemetryInfo(), + $test, + $calledMethod, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function afterTestMethodErrored(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\AfterTestMethodErrored( + $this->telemetryInfo(), + $test, + $calledMethod, + $throwable, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function afterTestMethodFailed(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\AfterTestMethodFailed( + $this->telemetryInfo(), + $test, + $calledMethod, + $throwable, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function afterTestMethodFinished(TestMethod $test, ClassMethod ...$calledMethods): void + { + $this->dispatcher->dispatch( + new Test\AfterTestMethodFinished( + $this->telemetryInfo(), + $test, + ...$calledMethods, + ), + ); + } + + /** + * @param class-string $testClassName + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function afterLastTestMethodCalled(string $testClassName, ClassMethod $calledMethod): void + { + $this->dispatcher->dispatch( + new Test\AfterLastTestMethodCalled( + $this->telemetryInfo(), + $testClassName, + $calledMethod, + ), + ); + } + + /** + * @param class-string $testClassName + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function afterLastTestMethodErrored(string $testClassName, ClassMethod $calledMethod, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\AfterLastTestMethodErrored( + $this->telemetryInfo(), + $testClassName, + $calledMethod, + $throwable, + ), + ); + } + + /** + * @param class-string $testClassName + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function afterLastTestMethodFailed(string $testClassName, ClassMethod $calledMethod, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\AfterLastTestMethodFailed( + $this->telemetryInfo(), + $testClassName, + $calledMethod, + $throwable, + ), + ); + } + + /** + * @param class-string $testClassName + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function afterLastTestMethodFinished(string $testClassName, ClassMethod ...$calledMethods): void + { + $this->dispatcher->dispatch( + new Test\AfterLastTestMethodFinished( + $this->telemetryInfo(), + $testClassName, + ...$calledMethods, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testSuiteFinished(TestSuite $testSuite): void + { + $this->dispatcher->dispatch( + new TestSuiteFinished( + $this->telemetryInfo(), + $testSuite, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerStartedStaticAnalysisForCodeCoverage(): void + { + $this->dispatcher->dispatch( + new TestRunner\StaticAnalysisForCodeCoverageStarted( + $this->telemetryInfo(), + ), + ); + } + + /** + * @param non-negative-int $cacheHits + * @param non-negative-int $cacheMisses + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerFinishedStaticAnalysisForCodeCoverage(int $cacheHits, int $cacheMisses): void + { + $this->dispatcher->dispatch( + new TestRunner\StaticAnalysisForCodeCoverageFinished( + $this->telemetryInfo(), + $cacheHits, + $cacheMisses, + ), + ); + } + + /** + * @param non-empty-string $message + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerTriggeredPhpunitDeprecation(string $message): void + { + try { + if (TestMethodBuilder::fromCallStack()->metadata()->isIgnorePhpunitDeprecations()->isNotEmpty()) { + return; + } + } catch (NoTestCaseObjectOnCallStackException) { + } + + $this->dispatcher->dispatch( + new TestRunner\DeprecationTriggered( + $this->telemetryInfo(), + $message, + ), + ); + } + + /** + * @param non-empty-string $message + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerTriggeredPhpunitNotice(string $message): void + { + $this->dispatcher->dispatch( + new TestRunner\NoticeTriggered( + $this->telemetryInfo(), + $message, + ), + ); + } + + /** + * @param non-empty-string $message + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerTriggeredPhpunitWarning(string $message): void + { + $this->dispatcher->dispatch( + new TestRunner\WarningTriggered( + $this->telemetryInfo(), + $message, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerEnabledGarbageCollection(): void + { + $this->dispatcher->dispatch( + new TestRunner\GarbageCollectionEnabled($this->telemetryInfo()), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerExecutionAborted(): void + { + $this->dispatcher->dispatch( + new TestRunner\ExecutionAborted($this->telemetryInfo()), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerExecutionFinished(): void + { + $this->dispatcher->dispatch( + new TestRunner\ExecutionFinished($this->telemetryInfo()), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerFinished(): void + { + $this->dispatcher->dispatch( + new TestRunner\Finished($this->telemetryInfo()), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function applicationFinished(int $shellExitCode): void + { + $this->dispatcher->dispatch( + new Application\Finished( + $this->telemetryInfo(), + $shellExitCode, + ), + ); + } + + /** + * @throws InvalidArgumentException + */ + private function telemetryInfo(): Telemetry\Info + { + $current = $this->system->snapshot(); + + $info = new Telemetry\Info( + $current, + $current->time()->duration($this->startSnapshot->time()), + $current->memoryUsage()->diff($this->startSnapshot->memoryUsage()), + $current->time()->duration($this->previousSnapshot->time()), + $current->memoryUsage()->diff($this->previousSnapshot->memoryUsage()), + ); + + $this->previousSnapshot = $current; + + return $info; + } +} diff --git a/src/Event/Emitter/Emitter.php b/src/Event/Emitter/Emitter.php new file mode 100644 index 00000000000..eb9d75cca92 --- /dev/null +++ b/src/Event/Emitter/Emitter.php @@ -0,0 +1,331 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use PHPUnit\Event\Code\ClassMethod; +use PHPUnit\Event\Code\ComparisonFailure; +use PHPUnit\Event\Code\IssueTrigger\IssueTrigger; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\TestSuite\TestSuite; +use PHPUnit\Framework\TestCase; +use PHPUnit\TextUI\Configuration\Configuration; +use SebastianBergmann\Comparator\Comparator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Emitter +{ + public function applicationStarted(): void; + + public function testRunnerStarted(): void; + + public function testRunnerConfigured(Configuration $configuration): void; + + /** + * @param non-empty-string $filename + */ + public function testRunnerBootstrapFinished(string $filename): void; + + /** + * @param non-empty-string $filename + * @param non-empty-string $name + * @param non-empty-string $version + */ + public function testRunnerLoadedExtensionFromPhar(string $filename, string $name, string $version): void; + + /** + * @param class-string $className + * @param array $parameters + */ + public function testRunnerBootstrappedExtension(string $className, array $parameters): void; + + public function dataProviderMethodCalled(ClassMethod $testMethod, ClassMethod $dataProviderMethod): void; + + public function dataProviderMethodFinished(ClassMethod $testMethod, ClassMethod ...$calledMethods): void; + + public function testSuiteLoaded(TestSuite $testSuite): void; + + public function testSuiteFiltered(TestSuite $testSuite): void; + + public function testSuiteSorted(int $executionOrder, int $executionOrderDefects, bool $resolveDependencies): void; + + public function testRunnerEventFacadeSealed(): void; + + public function testRunnerExecutionStarted(TestSuite $testSuite): void; + + public function testRunnerDisabledGarbageCollection(): void; + + public function testRunnerTriggeredGarbageCollection(): void; + + /** + * @param non-empty-string $message + */ + public function testSuiteSkipped(TestSuite $testSuite, string $message): void; + + public function testSuiteStarted(TestSuite $testSuite): void; + + public function testPreparationStarted(Code\Test $test): void; + + public function testPreparationErrored(Code\Test $test, Throwable $throwable): void; + + public function testPreparationFailed(Code\Test $test, Throwable $throwable): void; + + /** + * @param class-string $testClassName + */ + public function beforeFirstTestMethodCalled(string $testClassName, ClassMethod $calledMethod): void; + + /** + * @param class-string $testClassName + */ + public function beforeFirstTestMethodErrored(string $testClassName, ClassMethod $calledMethod, Throwable $throwable): void; + + /** + * @param class-string $testClassName + */ + public function beforeFirstTestMethodFailed(string $testClassName, ClassMethod $calledMethod, Throwable $throwable): void; + + /** + * @param class-string $testClassName + */ + public function beforeFirstTestMethodFinished(string $testClassName, ClassMethod ...$calledMethods): void; + + public function beforeTestMethodCalled(TestMethod $test, ClassMethod $calledMethod): void; + + public function beforeTestMethodErrored(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void; + + public function beforeTestMethodFailed(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void; + + public function beforeTestMethodFinished(TestMethod $test, ClassMethod ...$calledMethods): void; + + public function preConditionCalled(TestMethod $test, ClassMethod $calledMethod): void; + + public function preConditionErrored(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void; + + public function preConditionFailed(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void; + + public function preConditionFinished(TestMethod $test, ClassMethod ...$calledMethods): void; + + public function testPrepared(Code\Test $test): void; + + /** + * @param class-string $className + */ + public function testRegisteredComparator(string $className): void; + + /** + * @param class-string $className + */ + public function testCreatedMockObject(string $className): void; + + /** + * @param list $interfaces + */ + public function testCreatedMockObjectForIntersectionOfInterfaces(array $interfaces): void; + + /** + * @param class-string $className + */ + public function testCreatedPartialMockObject(string $className, string ...$methodNames): void; + + /** + * @param class-string $className + */ + public function testCreatedStub(string $className): void; + + /** + * @param list $interfaces + */ + public function testCreatedStubForIntersectionOfInterfaces(array $interfaces): void; + + public function testErrored(Code\Test $test, Throwable $throwable): void; + + public function testFailed(Code\Test $test, Throwable $throwable, ?ComparisonFailure $comparisonFailure): void; + + public function testPassed(Code\Test $test): void; + + /** + * @param non-empty-string $message + */ + public function testConsideredRisky(Code\Test $test, string $message): void; + + public function testMarkedAsIncomplete(Code\Test $test, Throwable $throwable): void; + + /** + * @param non-empty-string $message + */ + public function testSkipped(Code\Test $test, string $message): void; + + /** + * @param non-empty-string $message + */ + public function testTriggeredPhpunitDeprecation(?Code\Test $test, string $message): void; + + /** + * @param non-empty-string $message + */ + public function testTriggeredPhpunitNotice(?Code\Test $test, string $message): void; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + */ + public function testTriggeredPhpDeprecation(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline, bool $ignoredByTest, IssueTrigger $trigger): void; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + * @param non-empty-string $stackTrace + */ + public function testTriggeredDeprecation(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline, bool $ignoredByTest, IssueTrigger $trigger, string $stackTrace): void; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + */ + public function testTriggeredError(Code\Test $test, string $message, string $file, int $line, bool $suppressed): void; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + */ + public function testTriggeredNotice(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + */ + public function testTriggeredPhpNotice(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + */ + public function testTriggeredWarning(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + */ + public function testTriggeredPhpWarning(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void; + + /** + * @param non-empty-string $message + */ + public function testTriggeredPhpunitError(Code\Test $test, string $message): void; + + /** + * @param non-empty-string $message + */ + public function testTriggeredPhpunitWarning(Code\Test $test, string $message): void; + + /** + * @param non-empty-string $output + */ + public function testPrintedUnexpectedOutput(string $output): void; + + /** + * @param non-empty-string $additionalInformation + */ + public function testProvidedAdditionalInformation(TestMethod $test, string $additionalInformation): void; + + /** + * @param non-negative-int $numberOfAssertionsPerformed + */ + public function testFinished(Code\Test $test, int $numberOfAssertionsPerformed): void; + + public function postConditionCalled(TestMethod $test, ClassMethod $calledMethod): void; + + public function postConditionErrored(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void; + + public function postConditionFailed(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void; + + public function postConditionFinished(TestMethod $test, ClassMethod ...$calledMethods): void; + + public function afterTestMethodCalled(TestMethod $test, ClassMethod $calledMethod): void; + + public function afterTestMethodErrored(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void; + + public function afterTestMethodFailed(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void; + + public function afterTestMethodFinished(TestMethod $test, ClassMethod ...$calledMethods): void; + + /** + * @param class-string $testClassName + */ + public function afterLastTestMethodCalled(string $testClassName, ClassMethod $calledMethod): void; + + /** + * @param class-string $testClassName + */ + public function afterLastTestMethodErrored(string $testClassName, ClassMethod $calledMethod, Throwable $throwable): void; + + /** + * @param class-string $testClassName + */ + public function afterLastTestMethodFailed(string $testClassName, ClassMethod $calledMethod, Throwable $throwable): void; + + /** + * @param class-string $testClassName + */ + public function afterLastTestMethodFinished(string $testClassName, ClassMethod ...$calledMethods): void; + + public function testSuiteFinished(TestSuite $testSuite): void; + + public function childProcessStarted(): void; + + public function childProcessErrored(): void; + + public function childProcessFinished(string $stdout, string $stderr): void; + + public function testRunnerStartedStaticAnalysisForCodeCoverage(): void; + + /** + * @param non-negative-int $cacheHits + * @param non-negative-int $cacheMisses + */ + public function testRunnerFinishedStaticAnalysisForCodeCoverage(int $cacheHits, int $cacheMisses): void; + + /** + * @param non-empty-string $message + */ + public function testRunnerTriggeredPhpunitDeprecation(string $message): void; + + /** + * @param non-empty-string $message + */ + public function testRunnerTriggeredPhpunitNotice(string $message): void; + + /** + * @param non-empty-string $message + */ + public function testRunnerTriggeredPhpunitWarning(string $message): void; + + public function testRunnerEnabledGarbageCollection(): void; + + public function testRunnerExecutionAborted(): void; + + public function testRunnerExecutionFinished(): void; + + public function testRunnerFinished(): void; + + public function applicationFinished(int $shellExitCode): void; +} diff --git a/src/Event/Events/Application/Finished.php b/src/Event/Events/Application/Finished.php new file mode 100644 index 00000000000..6e94da2a957 --- /dev/null +++ b/src/Event/Events/Application/Finished.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Application; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Finished implements Event +{ + private Telemetry\Info $telemetryInfo; + private int $shellExitCode; + + public function __construct(Telemetry\Info $telemetryInfo, int $shellExitCode) + { + $this->telemetryInfo = $telemetryInfo; + $this->shellExitCode = $shellExitCode; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function shellExitCode(): int + { + return $this->shellExitCode; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'PHPUnit Finished (Shell Exit Code: %d)', + $this->shellExitCode, + ); + } +} diff --git a/src/Event/Events/Application/FinishedSubscriber.php b/src/Event/Events/Application/FinishedSubscriber.php new file mode 100644 index 00000000000..1e75977617d --- /dev/null +++ b/src/Event/Events/Application/FinishedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Application; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface FinishedSubscriber extends Subscriber +{ + public function notify(Finished $event): void; +} diff --git a/src/Event/Events/Application/Started.php b/src/Event/Events/Application/Started.php new file mode 100644 index 00000000000..a9aa959a72a --- /dev/null +++ b/src/Event/Events/Application/Started.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Application; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Runtime\Runtime; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Started implements Event +{ + private Telemetry\Info $telemetryInfo; + private Runtime $runtime; + + public function __construct(Telemetry\Info $telemetryInfo, Runtime $runtime) + { + $this->telemetryInfo = $telemetryInfo; + $this->runtime = $runtime; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function runtime(): Runtime + { + return $this->runtime; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'PHPUnit Started (%s)', + $this->runtime->asString(), + ); + } +} diff --git a/src/Event/Events/Application/StartedSubscriber.php b/src/Event/Events/Application/StartedSubscriber.php new file mode 100644 index 00000000000..f2ebee2fdbc --- /dev/null +++ b/src/Event/Events/Application/StartedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Application; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface StartedSubscriber extends Subscriber +{ + public function notify(Started $event): void; +} diff --git a/src/Event/Events/Event.php b/src/Event/Events/Event.php new file mode 100644 index 00000000000..443ca7294a5 --- /dev/null +++ b/src/Event/Events/Event.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface Event +{ + public function telemetryInfo(): Telemetry\Info; + + /** + * @return non-empty-string + */ + public function asString(): string; +} diff --git a/src/Event/Events/EventCollection.php b/src/Event/Events/EventCollection.php new file mode 100644 index 00000000000..7c691269294 --- /dev/null +++ b/src/Event/Events/EventCollection.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @template-implements IteratorAggregate + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class EventCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $events = []; + + public function add(Event ...$events): void + { + foreach ($events as $event) { + $this->events[] = $event; + } + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->events; + } + + public function count(): int + { + return count($this->events); + } + + public function isEmpty(): bool + { + return $this->count() === 0; + } + + public function isNotEmpty(): bool + { + return $this->count() > 0; + } + + public function getIterator(): EventCollectionIterator + { + return new EventCollectionIterator($this); + } +} diff --git a/src/Event/Events/EventCollectionIterator.php b/src/Event/Events/EventCollectionIterator.php new file mode 100644 index 00000000000..d121dea6e40 --- /dev/null +++ b/src/Event/Events/EventCollectionIterator.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use function count; +use Iterator; + +/** + * @template-implements Iterator + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class EventCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $events; + private int $position = 0; + + public function __construct(EventCollection $events) + { + $this->events = $events->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->events); + } + + public function key(): int + { + return $this->position; + } + + public function current(): Event + { + return $this->events[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/src/Event/Events/Test/AdditionalInformationProvided.php b/src/Event/Events/Test/AdditionalInformationProvided.php new file mode 100644 index 00000000000..d8f819ee407 --- /dev/null +++ b/src/Event/Events/Test/AdditionalInformationProvided.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AdditionalInformationProvided implements Event +{ + private Telemetry\Info $telemetryInfo; + private TestMethod $test; + + /** + * @var non-empty-string + */ + private string $additionalInformation; + + /** + * @param non-empty-string $additionalInformation + */ + public function __construct(Telemetry\Info $telemetryInfo, TestMethod $test, string $additionalInformation) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->additionalInformation = $additionalInformation; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): TestMethod + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function additionalInformation(): string + { + return $this->additionalInformation; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Provided Additional Information%s%s', + PHP_EOL, + $this->additionalInformation, + ); + } +} diff --git a/src/Event/Events/Test/AdditionalInformationProvidedSubscriber.php b/src/Event/Events/Test/AdditionalInformationProvidedSubscriber.php new file mode 100644 index 00000000000..7d175c5945b --- /dev/null +++ b/src/Event/Events/Test/AdditionalInformationProvidedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface AdditionalInformationProvidedSubscriber extends Subscriber +{ + public function notify(AdditionalInformationProvided $event): void; +} diff --git a/src/Event/Events/Test/ComparatorRegistered.php b/src/Event/Events/Test/ComparatorRegistered.php new file mode 100644 index 00000000000..9db33d5ea1b --- /dev/null +++ b/src/Event/Events/Test/ComparatorRegistered.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; +use SebastianBergmann\Comparator\Comparator; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ComparatorRegistered implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var class-string + */ + private string $className; + + /** + * @param class-string $className + */ + public function __construct(Telemetry\Info $telemetryInfo, string $className) + { + $this->telemetryInfo = $telemetryInfo; + $this->className = $className; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Comparator Registered (%s)', + $this->className, + ); + } +} diff --git a/src/Event/Events/Test/ComparatorRegisteredSubscriber.php b/src/Event/Events/Test/ComparatorRegisteredSubscriber.php new file mode 100644 index 00000000000..10ba78e4faa --- /dev/null +++ b/src/Event/Events/Test/ComparatorRegisteredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ComparatorRegisteredSubscriber extends Subscriber +{ + public function notify(ComparatorRegistered $event): void; +} diff --git a/src/Event/Events/Test/HookMethod/AfterLastTestMethodCalled.php b/src/Event/Events/Test/HookMethod/AfterLastTestMethodCalled.php new file mode 100644 index 00000000000..2c875a8cfec --- /dev/null +++ b/src/Event/Events/Test/HookMethod/AfterLastTestMethodCalled.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; +use PHPUnit\Framework\TestCase; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterLastTestMethodCalled implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var class-string + */ + private string $testClassName; + private Code\ClassMethod $calledMethod; + + /** + * @param class-string $testClassName + */ + public function __construct(Telemetry\Info $telemetryInfo, string $testClassName, Code\ClassMethod $calledMethod) + { + $this->telemetryInfo = $telemetryInfo; + $this->testClassName = $testClassName; + $this->calledMethod = $calledMethod; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function testClassName(): string + { + return $this->testClassName; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'After Last Test Method Called (%s::%s)', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + ); + } +} diff --git a/src/Event/Events/Test/HookMethod/AfterLastTestMethodCalledSubscriber.php b/src/Event/Events/Test/HookMethod/AfterLastTestMethodCalledSubscriber.php new file mode 100644 index 00000000000..08530ab7bf8 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/AfterLastTestMethodCalledSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface AfterLastTestMethodCalledSubscriber extends Subscriber +{ + public function notify(AfterLastTestMethodCalled $event): void; +} diff --git a/src/Event/Events/Test/HookMethod/AfterLastTestMethodErrored.php b/src/Event/Events/Test/HookMethod/AfterLastTestMethodErrored.php new file mode 100644 index 00000000000..ecdb7a15d65 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/AfterLastTestMethodErrored.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; +use PHPUnit\Framework\TestCase; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterLastTestMethodErrored implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var class-string + */ + private string $testClassName; + private Code\ClassMethod $calledMethod; + private Throwable $throwable; + + /** + * @param class-string $testClassName + */ + public function __construct(Telemetry\Info $telemetryInfo, string $testClassName, Code\ClassMethod $calledMethod, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->testClassName = $testClassName; + $this->calledMethod = $calledMethod; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function testClassName(): string + { + return $this->testClassName; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'After Last Test Method Errored (%s::%s)%s', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + $message, + ); + } +} diff --git a/src/Event/Events/Test/HookMethod/AfterLastTestMethodErroredSubscriber.php b/src/Event/Events/Test/HookMethod/AfterLastTestMethodErroredSubscriber.php new file mode 100644 index 00000000000..b994fdeb2f1 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/AfterLastTestMethodErroredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface AfterLastTestMethodErroredSubscriber extends Subscriber +{ + public function notify(AfterLastTestMethodErrored $event): void; +} diff --git a/src/Event/Events/Test/HookMethod/AfterLastTestMethodFailed.php b/src/Event/Events/Test/HookMethod/AfterLastTestMethodFailed.php new file mode 100644 index 00000000000..b22146ea4a2 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/AfterLastTestMethodFailed.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; +use PHPUnit\Framework\TestCase; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterLastTestMethodFailed implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var class-string + */ + private string $testClassName; + private Code\ClassMethod $calledMethod; + private Throwable $throwable; + + /** + * @param class-string $testClassName + */ + public function __construct(Telemetry\Info $telemetryInfo, string $testClassName, Code\ClassMethod $calledMethod, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->testClassName = $testClassName; + $this->calledMethod = $calledMethod; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function testClassName(): string + { + return $this->testClassName; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'After Last Test Method Failed (%s::%s)%s', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + $message, + ); + } +} diff --git a/src/Event/Events/Test/HookMethod/AfterLastTestMethodFailedSubscriber.php b/src/Event/Events/Test/HookMethod/AfterLastTestMethodFailedSubscriber.php new file mode 100644 index 00000000000..3b011bd9935 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/AfterLastTestMethodFailedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface AfterLastTestMethodFailedSubscriber extends Subscriber +{ + public function notify(AfterLastTestMethodFailed $event): void; +} diff --git a/src/Event/Events/Test/HookMethod/AfterLastTestMethodFinished.php b/src/Event/Events/Test/HookMethod/AfterLastTestMethodFinished.php new file mode 100644 index 00000000000..d8a5a11f227 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/AfterLastTestMethodFinished.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; +use PHPUnit\Framework\TestCase; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterLastTestMethodFinished implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var class-string + */ + private string $testClassName; + + /** + * @var list + */ + private array $calledMethods; + + /** + * @param class-string $testClassName + */ + public function __construct(Telemetry\Info $telemetryInfo, string $testClassName, Code\ClassMethod ...$calledMethods) + { + $this->telemetryInfo = $telemetryInfo; + $this->testClassName = $testClassName; + $this->calledMethods = $calledMethods; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function testClassName(): string + { + return $this->testClassName; + } + + /** + * @return list + */ + public function calledMethods(): array + { + return $this->calledMethods; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $buffer = 'After Last Test Method Finished:'; + + foreach ($this->calledMethods as $calledMethod) { + $buffer .= sprintf( + PHP_EOL . '- %s::%s', + $calledMethod->className(), + $calledMethod->methodName(), + ); + } + + return $buffer; + } +} diff --git a/src/Event/Events/Test/HookMethod/AfterLastTestMethodFinishedSubscriber.php b/src/Event/Events/Test/HookMethod/AfterLastTestMethodFinishedSubscriber.php new file mode 100644 index 00000000000..0a366b0d86a --- /dev/null +++ b/src/Event/Events/Test/HookMethod/AfterLastTestMethodFinishedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface AfterLastTestMethodFinishedSubscriber extends Subscriber +{ + public function notify(AfterLastTestMethodFinished $event): void; +} diff --git a/src/Event/Events/Test/HookMethod/AfterTestMethodCalled.php b/src/Event/Events/Test/HookMethod/AfterTestMethodCalled.php new file mode 100644 index 00000000000..ca863a188c2 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/AfterTestMethodCalled.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterTestMethodCalled implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + private Code\ClassMethod $calledMethod; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod $calledMethod) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethod = $calledMethod; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'After Test Method Called (%s::%s)', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + ); + } +} diff --git a/src/Event/Events/Test/HookMethod/AfterTestMethodCalledSubscriber.php b/src/Event/Events/Test/HookMethod/AfterTestMethodCalledSubscriber.php new file mode 100644 index 00000000000..3e72fc91c8b --- /dev/null +++ b/src/Event/Events/Test/HookMethod/AfterTestMethodCalledSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface AfterTestMethodCalledSubscriber extends Subscriber +{ + public function notify(AfterTestMethodCalled $event): void; +} diff --git a/src/Event/Events/Test/HookMethod/AfterTestMethodErrored.php b/src/Event/Events/Test/HookMethod/AfterTestMethodErrored.php new file mode 100644 index 00000000000..ba565fe8b16 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/AfterTestMethodErrored.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterTestMethodErrored implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + private Code\ClassMethod $calledMethod; + private Throwable $throwable; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod $calledMethod, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethod = $calledMethod; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'After Test Method Errored (%s::%s)%s', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + $message, + ); + } +} diff --git a/src/Event/Events/Test/HookMethod/AfterTestMethodErroredSubscriber.php b/src/Event/Events/Test/HookMethod/AfterTestMethodErroredSubscriber.php new file mode 100644 index 00000000000..622f91625d9 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/AfterTestMethodErroredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface AfterTestMethodErroredSubscriber extends Subscriber +{ + public function notify(AfterTestMethodErrored $event): void; +} diff --git a/src/Event/Events/Test/HookMethod/AfterTestMethodFailed.php b/src/Event/Events/Test/HookMethod/AfterTestMethodFailed.php new file mode 100644 index 00000000000..e405066f7ef --- /dev/null +++ b/src/Event/Events/Test/HookMethod/AfterTestMethodFailed.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterTestMethodFailed implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + private Code\ClassMethod $calledMethod; + private Throwable $throwable; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod $calledMethod, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethod = $calledMethod; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'After Test Method Failed (%s::%s)%s', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + $message, + ); + } +} diff --git a/src/Event/Events/Test/HookMethod/AfterTestMethodFailedSubscriber.php b/src/Event/Events/Test/HookMethod/AfterTestMethodFailedSubscriber.php new file mode 100644 index 00000000000..16134322a63 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/AfterTestMethodFailedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface AfterTestMethodFailedSubscriber extends Subscriber +{ + public function notify(AfterTestMethodFailed $event): void; +} diff --git a/src/Event/Events/Test/HookMethod/AfterTestMethodFinished.php b/src/Event/Events/Test/HookMethod/AfterTestMethodFinished.php new file mode 100644 index 00000000000..9f5e0429e87 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/AfterTestMethodFinished.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterTestMethodFinished implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + + /** + * @var list + */ + private array $calledMethods; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod ...$calledMethods) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethods = $calledMethods; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + /** + * @return list + */ + public function calledMethods(): array + { + return $this->calledMethods; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $buffer = 'After Test Method Finished:'; + + foreach ($this->calledMethods as $calledMethod) { + $buffer .= sprintf( + PHP_EOL . '- %s::%s', + $calledMethod->className(), + $calledMethod->methodName(), + ); + } + + return $buffer; + } +} diff --git a/src/Event/Events/Test/HookMethod/AfterTestMethodFinishedSubscriber.php b/src/Event/Events/Test/HookMethod/AfterTestMethodFinishedSubscriber.php new file mode 100644 index 00000000000..5e566889841 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/AfterTestMethodFinishedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface AfterTestMethodFinishedSubscriber extends Subscriber +{ + public function notify(AfterTestMethodFinished $event): void; +} diff --git a/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodCalled.php b/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodCalled.php new file mode 100644 index 00000000000..b08eb8b95fb --- /dev/null +++ b/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodCalled.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; +use PHPUnit\Framework\TestCase; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeFirstTestMethodCalled implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var class-string + */ + private string $testClassName; + private Code\ClassMethod $calledMethod; + + /** + * @param class-string $testClassName + */ + public function __construct(Telemetry\Info $telemetryInfo, string $testClassName, Code\ClassMethod $calledMethod) + { + $this->telemetryInfo = $telemetryInfo; + $this->testClassName = $testClassName; + $this->calledMethod = $calledMethod; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function testClassName(): string + { + return $this->testClassName; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Before First Test Method Called (%s::%s)', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + ); + } +} diff --git a/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodCalledSubscriber.php b/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodCalledSubscriber.php new file mode 100644 index 00000000000..a0d4281ff69 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodCalledSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface BeforeFirstTestMethodCalledSubscriber extends Subscriber +{ + public function notify(BeforeFirstTestMethodCalled $event): void; +} diff --git a/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodErrored.php b/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodErrored.php new file mode 100644 index 00000000000..a2e37b3370f --- /dev/null +++ b/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodErrored.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; +use PHPUnit\Framework\TestCase; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeFirstTestMethodErrored implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var class-string + */ + private string $testClassName; + private Code\ClassMethod $calledMethod; + private Throwable $throwable; + + /** + * @param class-string $testClassName + */ + public function __construct(Telemetry\Info $telemetryInfo, string $testClassName, Code\ClassMethod $calledMethod, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->testClassName = $testClassName; + $this->calledMethod = $calledMethod; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function testClassName(): string + { + return $this->testClassName; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Before First Test Method Errored (%s::%s)%s', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + $message, + ); + } +} diff --git a/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodErroredSubscriber.php b/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodErroredSubscriber.php new file mode 100644 index 00000000000..9a1b8754297 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodErroredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface BeforeFirstTestMethodErroredSubscriber extends Subscriber +{ + public function notify(BeforeFirstTestMethodErrored $event): void; +} diff --git a/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodFailed.php b/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodFailed.php new file mode 100644 index 00000000000..33e669a4e08 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodFailed.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; +use PHPUnit\Framework\TestCase; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeFirstTestMethodFailed implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var class-string + */ + private string $testClassName; + private Code\ClassMethod $calledMethod; + private Throwable $throwable; + + /** + * @param class-string $testClassName + */ + public function __construct(Telemetry\Info $telemetryInfo, string $testClassName, Code\ClassMethod $calledMethod, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->testClassName = $testClassName; + $this->calledMethod = $calledMethod; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function testClassName(): string + { + return $this->testClassName; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Before First Test Method Failed (%s::%s)%s', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + $message, + ); + } +} diff --git a/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodFailedSubscriber.php b/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodFailedSubscriber.php new file mode 100644 index 00000000000..4e0b7eff7b9 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodFailedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface BeforeFirstTestMethodFailedSubscriber extends Subscriber +{ + public function notify(BeforeFirstTestMethodFailed $event): void; +} diff --git a/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodFinished.php b/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodFinished.php new file mode 100644 index 00000000000..87230a676d2 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodFinished.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; +use PHPUnit\Framework\TestCase; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeFirstTestMethodFinished implements Event +{ + private Telemetry\Info$telemetryInfo; + + /** + * @var class-string + */ + private string $testClassName; + + /** + * @var list + */ + private array $calledMethods; + + /** + * @param class-string $testClassName + */ + public function __construct(Telemetry\Info $telemetryInfo, string $testClassName, Code\ClassMethod ...$calledMethods) + { + $this->telemetryInfo = $telemetryInfo; + $this->testClassName = $testClassName; + $this->calledMethods = $calledMethods; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function testClassName(): string + { + return $this->testClassName; + } + + /** + * @return list + */ + public function calledMethods(): array + { + return $this->calledMethods; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $buffer = 'Before First Test Method Finished:'; + + foreach ($this->calledMethods as $calledMethod) { + $buffer .= sprintf( + PHP_EOL . '- %s::%s', + $calledMethod->className(), + $calledMethod->methodName(), + ); + } + + return $buffer; + } +} diff --git a/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodFinishedSubscriber.php b/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodFinishedSubscriber.php new file mode 100644 index 00000000000..c9f1806419a --- /dev/null +++ b/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodFinishedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface BeforeFirstTestMethodFinishedSubscriber extends Subscriber +{ + public function notify(BeforeFirstTestMethodFinished $event): void; +} diff --git a/src/Event/Events/Test/HookMethod/BeforeTestMethodCalled.php b/src/Event/Events/Test/HookMethod/BeforeTestMethodCalled.php new file mode 100644 index 00000000000..d9da9311763 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/BeforeTestMethodCalled.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeTestMethodCalled implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + private Code\ClassMethod $calledMethod; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod $calledMethod) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethod = $calledMethod; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Before Test Method Called (%s::%s)', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + ); + } +} diff --git a/src/Event/Events/Test/HookMethod/BeforeTestMethodCalledSubscriber.php b/src/Event/Events/Test/HookMethod/BeforeTestMethodCalledSubscriber.php new file mode 100644 index 00000000000..5f4e180e67b --- /dev/null +++ b/src/Event/Events/Test/HookMethod/BeforeTestMethodCalledSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface BeforeTestMethodCalledSubscriber extends Subscriber +{ + public function notify(BeforeTestMethodCalled $event): void; +} diff --git a/src/Event/Events/Test/HookMethod/BeforeTestMethodErrored.php b/src/Event/Events/Test/HookMethod/BeforeTestMethodErrored.php new file mode 100644 index 00000000000..e6b7cbb195d --- /dev/null +++ b/src/Event/Events/Test/HookMethod/BeforeTestMethodErrored.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeTestMethodErrored implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + private Code\ClassMethod $calledMethod; + private Throwable $throwable; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod $calledMethod, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethod = $calledMethod; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Before Test Method Errored (%s::%s)%s', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + $message, + ); + } +} diff --git a/src/Event/Events/Test/HookMethod/BeforeTestMethodErroredSubscriber.php b/src/Event/Events/Test/HookMethod/BeforeTestMethodErroredSubscriber.php new file mode 100644 index 00000000000..e53771c4944 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/BeforeTestMethodErroredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface BeforeTestMethodErroredSubscriber extends Subscriber +{ + public function notify(BeforeTestMethodErrored $event): void; +} diff --git a/src/Event/Events/Test/HookMethod/BeforeTestMethodFailed.php b/src/Event/Events/Test/HookMethod/BeforeTestMethodFailed.php new file mode 100644 index 00000000000..95b46e4107e --- /dev/null +++ b/src/Event/Events/Test/HookMethod/BeforeTestMethodFailed.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeTestMethodFailed implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + private Code\ClassMethod $calledMethod; + private Throwable $throwable; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod $calledMethod, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethod = $calledMethod; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Before Test Method Failed (%s::%s)%s', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + $message, + ); + } +} diff --git a/src/Event/Events/Test/HookMethod/BeforeTestMethodFailedSubscriber.php b/src/Event/Events/Test/HookMethod/BeforeTestMethodFailedSubscriber.php new file mode 100644 index 00000000000..0f9f071cea5 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/BeforeTestMethodFailedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface BeforeTestMethodFailedSubscriber extends Subscriber +{ + public function notify(BeforeTestMethodFailed $event): void; +} diff --git a/src/Event/Events/Test/HookMethod/BeforeTestMethodFinished.php b/src/Event/Events/Test/HookMethod/BeforeTestMethodFinished.php new file mode 100644 index 00000000000..66d4b7ba16a --- /dev/null +++ b/src/Event/Events/Test/HookMethod/BeforeTestMethodFinished.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeTestMethodFinished implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + + /** + * @var list + */ + private array $calledMethods; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod ...$calledMethods) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethods = $calledMethods; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + /** + * @return list + */ + public function calledMethods(): array + { + return $this->calledMethods; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $buffer = 'Before Test Method Finished:'; + + foreach ($this->calledMethods as $calledMethod) { + $buffer .= sprintf( + PHP_EOL . '- %s::%s', + $calledMethod->className(), + $calledMethod->methodName(), + ); + } + + return $buffer; + } +} diff --git a/src/Event/Events/Test/HookMethod/BeforeTestMethodFinishedSubscriber.php b/src/Event/Events/Test/HookMethod/BeforeTestMethodFinishedSubscriber.php new file mode 100644 index 00000000000..2a6c758cafd --- /dev/null +++ b/src/Event/Events/Test/HookMethod/BeforeTestMethodFinishedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface BeforeTestMethodFinishedSubscriber extends Subscriber +{ + public function notify(BeforeTestMethodFinished $event): void; +} diff --git a/src/Event/Events/Test/HookMethod/PostConditionCalled.php b/src/Event/Events/Test/HookMethod/PostConditionCalled.php new file mode 100644 index 00000000000..7a9ef804e9b --- /dev/null +++ b/src/Event/Events/Test/HookMethod/PostConditionCalled.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PostConditionCalled implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + private Code\ClassMethod $calledMethod; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod $calledMethod) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethod = $calledMethod; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Post Condition Method Called (%s::%s)', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + ); + } +} diff --git a/src/Event/Events/Test/HookMethod/PostConditionCalledSubscriber.php b/src/Event/Events/Test/HookMethod/PostConditionCalledSubscriber.php new file mode 100644 index 00000000000..2c135f50437 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/PostConditionCalledSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PostConditionCalledSubscriber extends Subscriber +{ + public function notify(PostConditionCalled $event): void; +} diff --git a/src/Event/Events/Test/HookMethod/PostConditionErrored.php b/src/Event/Events/Test/HookMethod/PostConditionErrored.php new file mode 100644 index 00000000000..efb118b0937 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/PostConditionErrored.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PostConditionErrored implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + private Code\ClassMethod $calledMethod; + private Throwable $throwable; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod $calledMethod, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethod = $calledMethod; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Post Condition Method Errored (%s::%s)%s', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + $message, + ); + } +} diff --git a/src/Event/Events/Test/HookMethod/PostConditionErroredSubscriber.php b/src/Event/Events/Test/HookMethod/PostConditionErroredSubscriber.php new file mode 100644 index 00000000000..7bd2c54ce70 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/PostConditionErroredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PostConditionErroredSubscriber extends Subscriber +{ + public function notify(PostConditionErrored $event): void; +} diff --git a/src/Event/Events/Test/HookMethod/PostConditionFailed.php b/src/Event/Events/Test/HookMethod/PostConditionFailed.php new file mode 100644 index 00000000000..cb48689730f --- /dev/null +++ b/src/Event/Events/Test/HookMethod/PostConditionFailed.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PostConditionFailed implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + private Code\ClassMethod $calledMethod; + private Throwable $throwable; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod $calledMethod, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethod = $calledMethod; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Post Condition Method Failed (%s::%s)%s', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + $message, + ); + } +} diff --git a/src/Event/Events/Test/HookMethod/PostConditionFailedSubscriber.php b/src/Event/Events/Test/HookMethod/PostConditionFailedSubscriber.php new file mode 100644 index 00000000000..e6ff7557aa0 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/PostConditionFailedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PostConditionFailedSubscriber extends Subscriber +{ + public function notify(PostConditionFailed $event): void; +} diff --git a/src/Event/Events/Test/HookMethod/PostConditionFinished.php b/src/Event/Events/Test/HookMethod/PostConditionFinished.php new file mode 100644 index 00000000000..b1d0601a03c --- /dev/null +++ b/src/Event/Events/Test/HookMethod/PostConditionFinished.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PostConditionFinished implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + + /** + * @var list + */ + private array $calledMethods; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod ...$calledMethods) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethods = $calledMethods; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + /** + * @return list + */ + public function calledMethods(): array + { + return $this->calledMethods; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $buffer = 'Post Condition Method Finished:'; + + foreach ($this->calledMethods as $calledMethod) { + $buffer .= sprintf( + PHP_EOL . '- %s::%s', + $calledMethod->className(), + $calledMethod->methodName(), + ); + } + + return $buffer; + } +} diff --git a/src/Event/Events/Test/HookMethod/PostConditionFinishedSubscriber.php b/src/Event/Events/Test/HookMethod/PostConditionFinishedSubscriber.php new file mode 100644 index 00000000000..f24d9480054 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/PostConditionFinishedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PostConditionFinishedSubscriber extends Subscriber +{ + public function notify(PostConditionFinished $event): void; +} diff --git a/src/Event/Events/Test/HookMethod/PreConditionCalled.php b/src/Event/Events/Test/HookMethod/PreConditionCalled.php new file mode 100644 index 00000000000..c2fd1cd9e15 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/PreConditionCalled.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PreConditionCalled implements Event +{ + private Telemetry\Info$telemetryInfo; + private Code\TestMethod $test; + private Code\ClassMethod $calledMethod; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod $calledMethod) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethod = $calledMethod; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Pre Condition Method Called (%s::%s)', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + ); + } +} diff --git a/src/Event/Events/Test/HookMethod/PreConditionCalledSubscriber.php b/src/Event/Events/Test/HookMethod/PreConditionCalledSubscriber.php new file mode 100644 index 00000000000..431dfcc405d --- /dev/null +++ b/src/Event/Events/Test/HookMethod/PreConditionCalledSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PreConditionCalledSubscriber extends Subscriber +{ + public function notify(PreConditionCalled $event): void; +} diff --git a/src/Event/Events/Test/HookMethod/PreConditionErrored.php b/src/Event/Events/Test/HookMethod/PreConditionErrored.php new file mode 100644 index 00000000000..9d9001f9510 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/PreConditionErrored.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PreConditionErrored implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + private Code\ClassMethod $calledMethod; + private Throwable $throwable; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod $calledMethod, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethod = $calledMethod; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Pre Condition Method Errored (%s::%s)%s', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + $message, + ); + } +} diff --git a/src/Event/Events/Test/HookMethod/PreConditionErroredSubscriber.php b/src/Event/Events/Test/HookMethod/PreConditionErroredSubscriber.php new file mode 100644 index 00000000000..3465040bbeb --- /dev/null +++ b/src/Event/Events/Test/HookMethod/PreConditionErroredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PreConditionErroredSubscriber extends Subscriber +{ + public function notify(PreConditionErrored $event): void; +} diff --git a/src/Event/Events/Test/HookMethod/PreConditionFailed.php b/src/Event/Events/Test/HookMethod/PreConditionFailed.php new file mode 100644 index 00000000000..bf94de21179 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/PreConditionFailed.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PreConditionFailed implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + private Code\ClassMethod $calledMethod; + private Throwable $throwable; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod $calledMethod, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethod = $calledMethod; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Pre Condition Method Failed (%s::%s)%s', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + $message, + ); + } +} diff --git a/src/Event/Events/Test/HookMethod/PreConditionFailedSubscriber.php b/src/Event/Events/Test/HookMethod/PreConditionFailedSubscriber.php new file mode 100644 index 00000000000..26ce7cdce2c --- /dev/null +++ b/src/Event/Events/Test/HookMethod/PreConditionFailedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PreConditionFailedSubscriber extends Subscriber +{ + public function notify(PreConditionFailed $event): void; +} diff --git a/src/Event/Events/Test/HookMethod/PreConditionFinished.php b/src/Event/Events/Test/HookMethod/PreConditionFinished.php new file mode 100644 index 00000000000..922c9d868c4 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/PreConditionFinished.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PreConditionFinished implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + + /** + * @var list + */ + private array $calledMethods; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod ...$calledMethods) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethods = $calledMethods; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + /** + * @return list + */ + public function calledMethods(): array + { + return $this->calledMethods; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $buffer = 'Pre Condition Method Finished:'; + + foreach ($this->calledMethods as $calledMethod) { + $buffer .= sprintf( + PHP_EOL . '- %s::%s', + $calledMethod->className(), + $calledMethod->methodName(), + ); + } + + return $buffer; + } +} diff --git a/src/Event/Events/Test/HookMethod/PreConditionFinishedSubscriber.php b/src/Event/Events/Test/HookMethod/PreConditionFinishedSubscriber.php new file mode 100644 index 00000000000..9c499407e60 --- /dev/null +++ b/src/Event/Events/Test/HookMethod/PreConditionFinishedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PreConditionFinishedSubscriber extends Subscriber +{ + public function notify(PreConditionFinished $event): void; +} diff --git a/src/Event/Events/Test/Issue/ConsideredRisky.php b/src/Event/Events/Test/Issue/ConsideredRisky.php new file mode 100644 index 00000000000..306c04e7d50 --- /dev/null +++ b/src/Event/Events/Test/Issue/ConsideredRisky.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ConsideredRisky implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\Test $test; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @param non-empty-string $message + */ + public function __construct(Telemetry\Info $telemetryInfo, Code\Test $test, string $message) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Considered Risky (%s)%s%s', + $this->test->id(), + PHP_EOL, + $this->message, + ); + } +} diff --git a/src/Event/Events/Test/Issue/ConsideredRiskySubscriber.php b/src/Event/Events/Test/Issue/ConsideredRiskySubscriber.php new file mode 100644 index 00000000000..a0c714a91ed --- /dev/null +++ b/src/Event/Events/Test/Issue/ConsideredRiskySubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ConsideredRiskySubscriber extends Subscriber +{ + public function notify(ConsideredRisky $event): void; +} diff --git a/src/Event/Events/Test/Issue/DeprecationTriggered.php b/src/Event/Events/Test/Issue/DeprecationTriggered.php new file mode 100644 index 00000000000..09ec56fc8c7 --- /dev/null +++ b/src/Event/Events/Test/Issue/DeprecationTriggered.php @@ -0,0 +1,169 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function implode; +use function sprintf; +use PHPUnit\Event\Code\IssueTrigger\IssueTrigger; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DeprecationTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + private Test $test; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @var non-empty-string + */ + private string $file; + + /** + * @var positive-int + */ + private int $line; + private bool $suppressed; + private bool $ignoredByBaseline; + private bool $ignoredByTest; + private IssueTrigger $trigger; + + /** + * @var non-empty-string + */ + private string $stackTrace; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + * @param non-empty-string $stackTrace + */ + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline, bool $ignoredByTest, IssueTrigger $trigger, string $stackTrace) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->suppressed = $suppressed; + $this->ignoredByBaseline = $ignoredByBaseline; + $this->ignoredByTest = $ignoredByTest; + $this->trigger = $trigger; + $this->stackTrace = $stackTrace; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function file(): string + { + return $this->file; + } + + /** + * @return positive-int + */ + public function line(): int + { + return $this->line; + } + + public function wasSuppressed(): bool + { + return $this->suppressed; + } + + public function ignoredByBaseline(): bool + { + return $this->ignoredByBaseline; + } + + public function ignoredByTest(): bool + { + return $this->ignoredByTest; + } + + public function trigger(): IssueTrigger + { + return $this->trigger; + } + + /** + * @return non-empty-string + */ + public function stackTrace(): string + { + return $this->stackTrace; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->message; + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + $details = [$this->test->id(), $this->trigger->asString()]; + + if ($this->suppressed) { + $details[] = 'suppressed using operator'; + } + + if ($this->ignoredByTest) { + $details[] = 'ignored by test'; + } + + if ($this->ignoredByBaseline) { + $details[] = 'ignored by baseline'; + } + + return sprintf( + 'Test Triggered Deprecation (%s) in %s:%d%s', + implode(', ', $details), + $this->file, + $this->line, + $message, + ); + } +} diff --git a/src/Event/Events/Test/Issue/DeprecationTriggeredSubscriber.php b/src/Event/Events/Test/Issue/DeprecationTriggeredSubscriber.php new file mode 100644 index 00000000000..e166dbed9e2 --- /dev/null +++ b/src/Event/Events/Test/Issue/DeprecationTriggeredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface DeprecationTriggeredSubscriber extends Subscriber +{ + public function notify(DeprecationTriggered $event): void; +} diff --git a/src/Event/Events/Test/Issue/ErrorTriggered.php b/src/Event/Events/Test/Issue/ErrorTriggered.php new file mode 100644 index 00000000000..7faefc634ce --- /dev/null +++ b/src/Event/Events/Test/Issue/ErrorTriggered.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function implode; +use function sprintf; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ErrorTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + private Test $test; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @var non-empty-string + */ + private string $file; + + /** + * @var positive-int + */ + private int $line; + private bool $suppressed; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + */ + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->suppressed = $suppressed; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function file(): string + { + return $this->file; + } + + /** + * @return positive-int + */ + public function line(): int + { + return $this->line; + } + + public function wasSuppressed(): bool + { + return $this->suppressed; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->message; + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + $details = [$this->test->id()]; + + if ($this->suppressed) { + $details[] = 'suppressed using operator'; + } + + return sprintf( + 'Test Triggered Error (%s) in %s:%d%s', + implode(', ', $details), + $this->file, + $this->line, + $message, + ); + } +} diff --git a/src/Event/Events/Test/Issue/ErrorTriggeredSubscriber.php b/src/Event/Events/Test/Issue/ErrorTriggeredSubscriber.php new file mode 100644 index 00000000000..901d88556af --- /dev/null +++ b/src/Event/Events/Test/Issue/ErrorTriggeredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ErrorTriggeredSubscriber extends Subscriber +{ + public function notify(ErrorTriggered $event): void; +} diff --git a/src/Event/Events/Test/Issue/NoticeTriggered.php b/src/Event/Events/Test/Issue/NoticeTriggered.php new file mode 100644 index 00000000000..237e1f18ac8 --- /dev/null +++ b/src/Event/Events/Test/Issue/NoticeTriggered.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function implode; +use function sprintf; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class NoticeTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + private Test $test; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @var non-empty-string + */ + private string $file; + + /** + * @var positive-int + */ + private int $line; + private bool $suppressed; + private bool $ignoredByBaseline; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + */ + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->suppressed = $suppressed; + $this->ignoredByBaseline = $ignoredByBaseline; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function file(): string + { + return $this->file; + } + + /** + * @return positive-int + */ + public function line(): int + { + return $this->line; + } + + public function wasSuppressed(): bool + { + return $this->suppressed; + } + + public function ignoredByBaseline(): bool + { + return $this->ignoredByBaseline; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->message; + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + $details = [$this->test->id()]; + + if ($this->suppressed) { + $details[] = 'suppressed using operator'; + } + + if ($this->ignoredByBaseline) { + $details[] = 'ignored by baseline'; + } + + return sprintf( + 'Test Triggered Notice (%s) in %s:%d%s', + implode(', ', $details), + $this->file, + $this->line, + $message, + ); + } +} diff --git a/src/Event/Events/Test/Issue/NoticeTriggeredSubscriber.php b/src/Event/Events/Test/Issue/NoticeTriggeredSubscriber.php new file mode 100644 index 00000000000..95230d0ff52 --- /dev/null +++ b/src/Event/Events/Test/Issue/NoticeTriggeredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface NoticeTriggeredSubscriber extends Subscriber +{ + public function notify(NoticeTriggered $event): void; +} diff --git a/src/Event/Events/Test/Issue/PhpDeprecationTriggered.php b/src/Event/Events/Test/Issue/PhpDeprecationTriggered.php new file mode 100644 index 00000000000..5b9878ad40f --- /dev/null +++ b/src/Event/Events/Test/Issue/PhpDeprecationTriggered.php @@ -0,0 +1,154 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function implode; +use function sprintf; +use PHPUnit\Event\Code\IssueTrigger\IssueTrigger; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PhpDeprecationTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + private Test $test; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @var non-empty-string + */ + private string $file; + + /** + * @var positive-int + */ + private int $line; + private bool $suppressed; + private bool $ignoredByBaseline; + private bool $ignoredByTest; + private IssueTrigger $trigger; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + */ + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline, bool $ignoredByTest, IssueTrigger $trigger) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->suppressed = $suppressed; + $this->ignoredByBaseline = $ignoredByBaseline; + $this->ignoredByTest = $ignoredByTest; + $this->trigger = $trigger; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function file(): string + { + return $this->file; + } + + /** + * @return positive-int + */ + public function line(): int + { + return $this->line; + } + + public function wasSuppressed(): bool + { + return $this->suppressed; + } + + public function ignoredByBaseline(): bool + { + return $this->ignoredByBaseline; + } + + public function ignoredByTest(): bool + { + return $this->ignoredByTest; + } + + public function trigger(): IssueTrigger + { + return $this->trigger; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->message; + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + $details = [$this->test->id(), $this->trigger->asString()]; + + if ($this->suppressed) { + $details[] = 'suppressed using operator'; + } + + if ($this->ignoredByTest) { + $details[] = 'ignored by test'; + } + + if ($this->ignoredByBaseline) { + $details[] = 'ignored by baseline'; + } + + return sprintf( + 'Test Triggered PHP Deprecation (%s) in %s:%d%s', + implode(', ', $details), + $this->file, + $this->line, + $message, + ); + } +} diff --git a/src/Event/Events/Test/Issue/PhpDeprecationTriggeredSubscriber.php b/src/Event/Events/Test/Issue/PhpDeprecationTriggeredSubscriber.php new file mode 100644 index 00000000000..06159a7ce14 --- /dev/null +++ b/src/Event/Events/Test/Issue/PhpDeprecationTriggeredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PhpDeprecationTriggeredSubscriber extends Subscriber +{ + public function notify(PhpDeprecationTriggered $event): void; +} diff --git a/src/Event/Events/Test/Issue/PhpNoticeTriggered.php b/src/Event/Events/Test/Issue/PhpNoticeTriggered.php new file mode 100644 index 00000000000..ad976cfcff2 --- /dev/null +++ b/src/Event/Events/Test/Issue/PhpNoticeTriggered.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function implode; +use function sprintf; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PhpNoticeTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + private Test $test; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @var non-empty-string + */ + private string $file; + + /** + * @var positive-int + */ + private int $line; + private bool $suppressed; + private bool $ignoredByBaseline; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + */ + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->suppressed = $suppressed; + $this->ignoredByBaseline = $ignoredByBaseline; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function file(): string + { + return $this->file; + } + + /** + * @return positive-int + */ + public function line(): int + { + return $this->line; + } + + public function wasSuppressed(): bool + { + return $this->suppressed; + } + + public function ignoredByBaseline(): bool + { + return $this->ignoredByBaseline; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->message; + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + $details = [$this->test->id()]; + + if ($this->suppressed) { + $details[] = 'suppressed using operator'; + } + + if ($this->ignoredByBaseline) { + $details[] = 'ignored by baseline'; + } + + return sprintf( + 'Test Triggered PHP Notice (%s) in %s:%d%s', + implode(', ', $details), + $this->file, + $this->line, + $message, + ); + } +} diff --git a/src/Event/Events/Test/Issue/PhpNoticeTriggeredSubscriber.php b/src/Event/Events/Test/Issue/PhpNoticeTriggeredSubscriber.php new file mode 100644 index 00000000000..98649bda369 --- /dev/null +++ b/src/Event/Events/Test/Issue/PhpNoticeTriggeredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PhpNoticeTriggeredSubscriber extends Subscriber +{ + public function notify(PhpNoticeTriggered $event): void; +} diff --git a/src/Event/Events/Test/Issue/PhpWarningTriggered.php b/src/Event/Events/Test/Issue/PhpWarningTriggered.php new file mode 100644 index 00000000000..3d65125f753 --- /dev/null +++ b/src/Event/Events/Test/Issue/PhpWarningTriggered.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function implode; +use function sprintf; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PhpWarningTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + private Test $test; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @var non-empty-string + */ + private string $file; + + /** + * @var positive-int + */ + private int $line; + private bool $suppressed; + private bool $ignoredByBaseline; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + */ + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->suppressed = $suppressed; + $this->ignoredByBaseline = $ignoredByBaseline; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function file(): string + { + return $this->file; + } + + /** + * @return positive-int + */ + public function line(): int + { + return $this->line; + } + + public function wasSuppressed(): bool + { + return $this->suppressed; + } + + public function ignoredByBaseline(): bool + { + return $this->ignoredByBaseline; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->message; + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + $details = [$this->test->id()]; + + if ($this->suppressed) { + $details[] = 'suppressed using operator'; + } + + if ($this->ignoredByBaseline) { + $details[] = 'ignored by baseline'; + } + + return sprintf( + 'Test Triggered PHP Warning (%s) in %s:%d%s', + implode(', ', $details), + $this->file, + $this->line, + $message, + ); + } +} diff --git a/src/Event/Events/Test/Issue/PhpWarningTriggeredSubscriber.php b/src/Event/Events/Test/Issue/PhpWarningTriggeredSubscriber.php new file mode 100644 index 00000000000..3638ba1aa64 --- /dev/null +++ b/src/Event/Events/Test/Issue/PhpWarningTriggeredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PhpWarningTriggeredSubscriber extends Subscriber +{ + public function notify(PhpWarningTriggered $event): void; +} diff --git a/src/Event/Events/Test/Issue/PhpunitDeprecationTriggered.php b/src/Event/Events/Test/Issue/PhpunitDeprecationTriggered.php new file mode 100644 index 00000000000..4e1603f355b --- /dev/null +++ b/src/Event/Events/Test/Issue/PhpunitDeprecationTriggered.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PhpunitDeprecationTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + private Test $test; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @param non-empty-string $message + */ + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->message; + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Test Triggered PHPUnit Deprecation (%s)%s', + $this->test->id(), + $message, + ); + } +} diff --git a/src/Event/Events/Test/Issue/PhpunitDeprecationTriggeredSubscriber.php b/src/Event/Events/Test/Issue/PhpunitDeprecationTriggeredSubscriber.php new file mode 100644 index 00000000000..f6b3a239a97 --- /dev/null +++ b/src/Event/Events/Test/Issue/PhpunitDeprecationTriggeredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PhpunitDeprecationTriggeredSubscriber extends Subscriber +{ + public function notify(PhpunitDeprecationTriggered $event): void; +} diff --git a/src/Event/Events/Test/Issue/PhpunitErrorTriggered.php b/src/Event/Events/Test/Issue/PhpunitErrorTriggered.php new file mode 100644 index 00000000000..abd5e8a1f0b --- /dev/null +++ b/src/Event/Events/Test/Issue/PhpunitErrorTriggered.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use function trim; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PhpunitErrorTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + private Test $test; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @param non-empty-string $message + */ + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = trim($this->message); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Test Triggered PHPUnit Error (%s)%s', + $this->test->id(), + $message, + ); + } +} diff --git a/src/Event/Events/Test/Issue/PhpunitErrorTriggeredSubscriber.php b/src/Event/Events/Test/Issue/PhpunitErrorTriggeredSubscriber.php new file mode 100644 index 00000000000..e94d1dde262 --- /dev/null +++ b/src/Event/Events/Test/Issue/PhpunitErrorTriggeredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PhpunitErrorTriggeredSubscriber extends Subscriber +{ + public function notify(PhpunitErrorTriggered $event): void; +} diff --git a/src/Event/Events/Test/Issue/PhpunitNoticeTriggered.php b/src/Event/Events/Test/Issue/PhpunitNoticeTriggered.php new file mode 100644 index 00000000000..33984ba426b --- /dev/null +++ b/src/Event/Events/Test/Issue/PhpunitNoticeTriggered.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use function trim; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PhpunitNoticeTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + private Test $test; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @param non-empty-string $message + */ + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = trim($this->message); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Test Triggered PHPUnit Notice (%s)%s', + $this->test->id(), + $message, + ); + } +} diff --git a/src/Event/Events/Test/Issue/PhpunitNoticeTriggeredSubscriber.php b/src/Event/Events/Test/Issue/PhpunitNoticeTriggeredSubscriber.php new file mode 100644 index 00000000000..0935c6dd350 --- /dev/null +++ b/src/Event/Events/Test/Issue/PhpunitNoticeTriggeredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PhpunitNoticeTriggeredSubscriber extends Subscriber +{ + public function notify(PhpunitNoticeTriggered $event): void; +} diff --git a/src/Event/Events/Test/Issue/PhpunitWarningTriggered.php b/src/Event/Events/Test/Issue/PhpunitWarningTriggered.php new file mode 100644 index 00000000000..75ef4894d47 --- /dev/null +++ b/src/Event/Events/Test/Issue/PhpunitWarningTriggered.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function implode; +use function sprintf; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PhpunitWarningTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + private Test $test; + + /** + * @var non-empty-string + */ + private string $message; + private bool $ignoredByTest; + + /** + * @param non-empty-string $message + */ + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, bool $ignoredByTest) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + $this->ignoredByTest = $ignoredByTest; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + public function ignoredByTest(): bool + { + return $this->ignoredByTest; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->message; + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + $details = [$this->test->id()]; + + if ($this->ignoredByTest) { + $details[] = 'ignored by test'; + } + + return sprintf( + 'Test Triggered PHPUnit Warning (%s)%s', + implode(', ', $details), + $message, + ); + } +} diff --git a/src/Event/Events/Test/Issue/PhpunitWarningTriggeredSubscriber.php b/src/Event/Events/Test/Issue/PhpunitWarningTriggeredSubscriber.php new file mode 100644 index 00000000000..72149b2c803 --- /dev/null +++ b/src/Event/Events/Test/Issue/PhpunitWarningTriggeredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PhpunitWarningTriggeredSubscriber extends Subscriber +{ + public function notify(PhpunitWarningTriggered $event): void; +} diff --git a/src/Event/Events/Test/Issue/WarningTriggered.php b/src/Event/Events/Test/Issue/WarningTriggered.php new file mode 100644 index 00000000000..7b3e313bf26 --- /dev/null +++ b/src/Event/Events/Test/Issue/WarningTriggered.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function implode; +use function sprintf; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class WarningTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + private Test $test; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @var non-empty-string + */ + private string $file; + + /** + * @var positive-int + */ + private int $line; + private bool $suppressed; + private bool $ignoredByBaseline; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + */ + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->suppressed = $suppressed; + $this->ignoredByBaseline = $ignoredByBaseline; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function file(): string + { + return $this->file; + } + + /** + * @return positive-int + */ + public function line(): int + { + return $this->line; + } + + public function wasSuppressed(): bool + { + return $this->suppressed; + } + + public function ignoredByBaseline(): bool + { + return $this->ignoredByBaseline; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->message; + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + $details = [$this->test->id()]; + + if ($this->suppressed) { + $details[] = 'suppressed using operator'; + } + + if ($this->ignoredByBaseline) { + $details[] = 'ignored by baseline'; + } + + return sprintf( + 'Test Triggered Warning (%s) in %s:%d%s', + implode(', ', $details), + $this->file, + $this->line, + $message, + ); + } +} diff --git a/src/Event/Events/Test/Issue/WarningTriggeredSubscriber.php b/src/Event/Events/Test/Issue/WarningTriggeredSubscriber.php new file mode 100644 index 00000000000..8eb66648e48 --- /dev/null +++ b/src/Event/Events/Test/Issue/WarningTriggeredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface WarningTriggeredSubscriber extends Subscriber +{ + public function notify(WarningTriggered $event): void; +} diff --git a/src/Event/Events/Test/Lifecycle/DataProviderMethodCalled.php b/src/Event/Events/Test/Lifecycle/DataProviderMethodCalled.php new file mode 100644 index 00000000000..5631e1cf2b1 --- /dev/null +++ b/src/Event/Events/Test/Lifecycle/DataProviderMethodCalled.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Code\ClassMethod; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry\Info; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DataProviderMethodCalled implements Event +{ + private Info $telemetryInfo; + private ClassMethod $testMethod; + private ClassMethod $dataProviderMethod; + + public function __construct(Info $telemetryInfo, ClassMethod $testMethod, ClassMethod $dataProviderMethod) + { + $this->telemetryInfo = $telemetryInfo; + $this->testMethod = $testMethod; + $this->dataProviderMethod = $dataProviderMethod; + } + + public function telemetryInfo(): Info + { + return $this->telemetryInfo; + } + + public function testMethod(): ClassMethod + { + return $this->testMethod; + } + + public function dataProviderMethod(): ClassMethod + { + return $this->dataProviderMethod; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Data Provider Method Called (%s::%s for test method %s::%s)', + $this->dataProviderMethod->className(), + $this->dataProviderMethod->methodName(), + $this->testMethod->className(), + $this->testMethod->methodName(), + ); + } +} diff --git a/src/Event/Events/Test/Lifecycle/DataProviderMethodCalledSubscriber.php b/src/Event/Events/Test/Lifecycle/DataProviderMethodCalledSubscriber.php new file mode 100644 index 00000000000..5f7d4013bb6 --- /dev/null +++ b/src/Event/Events/Test/Lifecycle/DataProviderMethodCalledSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface DataProviderMethodCalledSubscriber extends Subscriber +{ + public function notify(DataProviderMethodCalled $event): void; +} diff --git a/src/Event/Events/Test/Lifecycle/DataProviderMethodFinished.php b/src/Event/Events/Test/Lifecycle/DataProviderMethodFinished.php new file mode 100644 index 00000000000..ec26779949b --- /dev/null +++ b/src/Event/Events/Test/Lifecycle/DataProviderMethodFinished.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code\ClassMethod; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DataProviderMethodFinished implements Event +{ + private Telemetry\Info $telemetryInfo; + private ClassMethod $testMethod; + + /** + * @var list + */ + private array $calledMethods; + + public function __construct(Telemetry\Info $telemetryInfo, ClassMethod $testMethod, ClassMethod ...$calledMethods) + { + $this->telemetryInfo = $telemetryInfo; + $this->testMethod = $testMethod; + $this->calledMethods = $calledMethods; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function testMethod(): ClassMethod + { + return $this->testMethod; + } + + /** + * @return list + */ + public function calledMethods(): array + { + return $this->calledMethods; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $buffer = sprintf( + 'Data Provider Method Finished for %s::%s:', + $this->testMethod->className(), + $this->testMethod->methodName(), + ); + + foreach ($this->calledMethods as $calledMethod) { + $buffer .= sprintf( + PHP_EOL . '- %s::%s', + $calledMethod->className(), + $calledMethod->methodName(), + ); + } + + return $buffer; + } +} diff --git a/src/Event/Events/Test/Lifecycle/DataProviderMethodFinishedSubscriber.php b/src/Event/Events/Test/Lifecycle/DataProviderMethodFinishedSubscriber.php new file mode 100644 index 00000000000..624f8921d96 --- /dev/null +++ b/src/Event/Events/Test/Lifecycle/DataProviderMethodFinishedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface DataProviderMethodFinishedSubscriber extends Subscriber +{ + public function notify(DataProviderMethodFinished $event): void; +} diff --git a/src/Event/Events/Test/Lifecycle/Finished.php b/src/Event/Events/Test/Lifecycle/Finished.php new file mode 100644 index 00000000000..3cc9a52ffd8 --- /dev/null +++ b/src/Event/Events/Test/Lifecycle/Finished.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Finished implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\Test $test; + + /** + * @var non-negative-int + */ + private int $numberOfAssertionsPerformed; + + /** + * @param non-negative-int $numberOfAssertionsPerformed + */ + public function __construct(Telemetry\Info $telemetryInfo, Code\Test $test, int $numberOfAssertionsPerformed) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->numberOfAssertionsPerformed = $numberOfAssertionsPerformed; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\Test + { + return $this->test; + } + + /** + * @return non-negative-int + */ + public function numberOfAssertionsPerformed(): int + { + return $this->numberOfAssertionsPerformed; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Finished (%s)', + $this->test->id(), + ); + } +} diff --git a/src/Event/Events/Test/Lifecycle/FinishedSubscriber.php b/src/Event/Events/Test/Lifecycle/FinishedSubscriber.php new file mode 100644 index 00000000000..5751e3df7c1 --- /dev/null +++ b/src/Event/Events/Test/Lifecycle/FinishedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface FinishedSubscriber extends Subscriber +{ + public function notify(Finished $event): void; +} diff --git a/src/Event/Events/Test/Lifecycle/PreparationErrored.php b/src/Event/Events/Test/Lifecycle/PreparationErrored.php new file mode 100644 index 00000000000..866bf5f1c4b --- /dev/null +++ b/src/Event/Events/Test/Lifecycle/PreparationErrored.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PreparationErrored implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\Test $test; + private Throwable $throwable; + + public function __construct(Telemetry\Info $telemetryInfo, Code\Test $test, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\Test + { + return $this->test; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Test Preparation Errored (%s)%s', + $this->test->id(), + $message, + ); + } +} diff --git a/src/Event/Events/Test/Lifecycle/PreparationErroredSubscriber.php b/src/Event/Events/Test/Lifecycle/PreparationErroredSubscriber.php new file mode 100644 index 00000000000..2cb43d2e838 --- /dev/null +++ b/src/Event/Events/Test/Lifecycle/PreparationErroredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PreparationErroredSubscriber extends Subscriber +{ + public function notify(PreparationErrored $event): void; +} diff --git a/src/Event/Events/Test/Lifecycle/PreparationFailed.php b/src/Event/Events/Test/Lifecycle/PreparationFailed.php new file mode 100644 index 00000000000..7a8b1d67fcc --- /dev/null +++ b/src/Event/Events/Test/Lifecycle/PreparationFailed.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PreparationFailed implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\Test $test; + private Throwable $throwable; + + public function __construct(Telemetry\Info $telemetryInfo, Code\Test $test, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\Test + { + return $this->test; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Test Preparation Failed (%s)%s', + $this->test->id(), + $message, + ); + } +} diff --git a/src/Event/Events/Test/Lifecycle/PreparationFailedSubscriber.php b/src/Event/Events/Test/Lifecycle/PreparationFailedSubscriber.php new file mode 100644 index 00000000000..da20f11efe7 --- /dev/null +++ b/src/Event/Events/Test/Lifecycle/PreparationFailedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PreparationFailedSubscriber extends Subscriber +{ + public function notify(PreparationFailed $event): void; +} diff --git a/src/Event/Events/Test/Lifecycle/PreparationStarted.php b/src/Event/Events/Test/Lifecycle/PreparationStarted.php new file mode 100644 index 00000000000..7c548b08b0c --- /dev/null +++ b/src/Event/Events/Test/Lifecycle/PreparationStarted.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PreparationStarted implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\Test $test; + + public function __construct(Telemetry\Info $telemetryInfo, Code\Test $test) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Preparation Started (%s)', + $this->test->id(), + ); + } +} diff --git a/src/Event/Events/Test/Lifecycle/PreparationStartedSubscriber.php b/src/Event/Events/Test/Lifecycle/PreparationStartedSubscriber.php new file mode 100644 index 00000000000..f13296b4f4f --- /dev/null +++ b/src/Event/Events/Test/Lifecycle/PreparationStartedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PreparationStartedSubscriber extends Subscriber +{ + public function notify(PreparationStarted $event): void; +} diff --git a/src/Event/Events/Test/Lifecycle/Prepared.php b/src/Event/Events/Test/Lifecycle/Prepared.php new file mode 100644 index 00000000000..d83f1d59570 --- /dev/null +++ b/src/Event/Events/Test/Lifecycle/Prepared.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Prepared implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\Test $test; + + public function __construct(Telemetry\Info $telemetryInfo, Code\Test $test) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Prepared (%s)', + $this->test->id(), + ); + } +} diff --git a/src/Event/Events/Test/Lifecycle/PreparedSubscriber.php b/src/Event/Events/Test/Lifecycle/PreparedSubscriber.php new file mode 100644 index 00000000000..f53e227f46e --- /dev/null +++ b/src/Event/Events/Test/Lifecycle/PreparedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PreparedSubscriber extends Subscriber +{ + public function notify(Prepared $event): void; +} diff --git a/src/Event/Events/Test/Outcome/Errored.php b/src/Event/Events/Test/Outcome/Errored.php new file mode 100644 index 00000000000..ef0684989f9 --- /dev/null +++ b/src/Event/Events/Test/Outcome/Errored.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use function trim; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Errored implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\Test $test; + private Throwable $throwable; + + public function __construct(Telemetry\Info $telemetryInfo, Code\Test $test, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\Test + { + return $this->test; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = trim($this->throwable->message()); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Test Errored (%s)%s', + $this->test->id(), + $message, + ); + } +} diff --git a/src/Event/Events/Test/Outcome/ErroredSubscriber.php b/src/Event/Events/Test/Outcome/ErroredSubscriber.php new file mode 100644 index 00000000000..42dd5b24d58 --- /dev/null +++ b/src/Event/Events/Test/Outcome/ErroredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ErroredSubscriber extends Subscriber +{ + public function notify(Errored $event): void; +} diff --git a/src/Event/Events/Test/Outcome/Failed.php b/src/Event/Events/Test/Outcome/Failed.php new file mode 100644 index 00000000000..bcc5867f04d --- /dev/null +++ b/src/Event/Events/Test/Outcome/Failed.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use function trim; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\ComparisonFailure; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Failed implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\Test $test; + private Throwable $throwable; + private ?ComparisonFailure $comparisonFailure; + + public function __construct(Telemetry\Info $telemetryInfo, Code\Test $test, Throwable $throwable, ?ComparisonFailure $comparisonFailure) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->throwable = $throwable; + $this->comparisonFailure = $comparisonFailure; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\Test + { + return $this->test; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @phpstan-assert-if-true !null $this->comparisonFailure + */ + public function hasComparisonFailure(): bool + { + return $this->comparisonFailure !== null; + } + + /** + * @throws NoComparisonFailureException + */ + public function comparisonFailure(): ComparisonFailure + { + if ($this->comparisonFailure === null) { + throw new NoComparisonFailureException; + } + + return $this->comparisonFailure; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = trim($this->throwable->message()); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Test Failed (%s)%s', + $this->test->id(), + $message, + ); + } +} diff --git a/src/Event/Events/Test/Outcome/FailedSubscriber.php b/src/Event/Events/Test/Outcome/FailedSubscriber.php new file mode 100644 index 00000000000..8da6a85f16d --- /dev/null +++ b/src/Event/Events/Test/Outcome/FailedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface FailedSubscriber extends Subscriber +{ + public function notify(Failed $event): void; +} diff --git a/src/Event/Events/Test/Outcome/MarkedIncomplete.php b/src/Event/Events/Test/Outcome/MarkedIncomplete.php new file mode 100644 index 00000000000..a69b48a427b --- /dev/null +++ b/src/Event/Events/Test/Outcome/MarkedIncomplete.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use function trim; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class MarkedIncomplete implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\Test $test; + private Throwable $throwable; + + public function __construct(Telemetry\Info $telemetryInfo, Code\Test $test, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\Test + { + return $this->test; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = trim($this->throwable->message()); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Test Marked Incomplete (%s)%s', + $this->test->id(), + $message, + ); + } +} diff --git a/src/Event/Events/Test/Outcome/MarkedIncompleteSubscriber.php b/src/Event/Events/Test/Outcome/MarkedIncompleteSubscriber.php new file mode 100644 index 00000000000..ff0acd86366 --- /dev/null +++ b/src/Event/Events/Test/Outcome/MarkedIncompleteSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface MarkedIncompleteSubscriber extends Subscriber +{ + public function notify(MarkedIncomplete $event): void; +} diff --git a/src/Event/Events/Test/Outcome/Passed.php b/src/Event/Events/Test/Outcome/Passed.php new file mode 100644 index 00000000000..38f2d9816ec --- /dev/null +++ b/src/Event/Events/Test/Outcome/Passed.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Passed implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\Test $test; + + public function __construct(Telemetry\Info $telemetryInfo, Code\Test $test) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Passed (%s)', + $this->test->id(), + ); + } +} diff --git a/src/Event/Events/Test/Outcome/PassedSubscriber.php b/src/Event/Events/Test/Outcome/PassedSubscriber.php new file mode 100644 index 00000000000..4a5673816f2 --- /dev/null +++ b/src/Event/Events/Test/Outcome/PassedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PassedSubscriber extends Subscriber +{ + public function notify(Passed $event): void; +} diff --git a/src/Event/Events/Test/Outcome/Skipped.php b/src/Event/Events/Test/Outcome/Skipped.php new file mode 100644 index 00000000000..fe605fff47d --- /dev/null +++ b/src/Event/Events/Test/Outcome/Skipped.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Skipped implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\Test $test; + private string $message; + + public function __construct(Telemetry\Info $telemetryInfo, Code\Test $test, string $message) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\Test + { + return $this->test; + } + + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->message; + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Test Skipped (%s)%s', + $this->test->id(), + $message, + ); + } +} diff --git a/src/Event/Events/Test/Outcome/SkippedSubscriber.php b/src/Event/Events/Test/Outcome/SkippedSubscriber.php new file mode 100644 index 00000000000..5fd48ac6a93 --- /dev/null +++ b/src/Event/Events/Test/Outcome/SkippedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface SkippedSubscriber extends Subscriber +{ + public function notify(Skipped $event): void; +} diff --git a/src/Event/Events/Test/PrintedUnexpectedOutput.php b/src/Event/Events/Test/PrintedUnexpectedOutput.php new file mode 100644 index 00000000000..4a0ceab39ac --- /dev/null +++ b/src/Event/Events/Test/PrintedUnexpectedOutput.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PrintedUnexpectedOutput implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var non-empty-string + */ + private string $output; + + /** + * @param non-empty-string $output + */ + public function __construct(Telemetry\Info $telemetryInfo, string $output) + { + $this->telemetryInfo = $telemetryInfo; + $this->output = $output; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function output(): string + { + return $this->output; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Printed Unexpected Output%s%s', + PHP_EOL, + $this->output, + ); + } +} diff --git a/src/Event/Events/Test/PrintedUnexpectedOutputSubscriber.php b/src/Event/Events/Test/PrintedUnexpectedOutputSubscriber.php new file mode 100644 index 00000000000..ee201572314 --- /dev/null +++ b/src/Event/Events/Test/PrintedUnexpectedOutputSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PrintedUnexpectedOutputSubscriber extends Subscriber +{ + public function notify(PrintedUnexpectedOutput $event): void; +} diff --git a/src/Event/Events/Test/TestDouble/MockObjectCreated.php b/src/Event/Events/Test/TestDouble/MockObjectCreated.php new file mode 100644 index 00000000000..8e91237c4f7 --- /dev/null +++ b/src/Event/Events/Test/TestDouble/MockObjectCreated.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class MockObjectCreated implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var class-string + */ + private string $className; + + /** + * @param class-string $className + */ + public function __construct(Telemetry\Info $telemetryInfo, string $className) + { + $this->telemetryInfo = $telemetryInfo; + $this->className = $className; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Mock Object Created (%s)', + $this->className, + ); + } +} diff --git a/src/Event/Events/Test/TestDouble/MockObjectCreatedSubscriber.php b/src/Event/Events/Test/TestDouble/MockObjectCreatedSubscriber.php new file mode 100644 index 00000000000..8ad2f176b15 --- /dev/null +++ b/src/Event/Events/Test/TestDouble/MockObjectCreatedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface MockObjectCreatedSubscriber extends Subscriber +{ + public function notify(MockObjectCreated $event): void; +} diff --git a/src/Event/Events/Test/TestDouble/MockObjectForIntersectionOfInterfacesCreated.php b/src/Event/Events/Test/TestDouble/MockObjectForIntersectionOfInterfacesCreated.php new file mode 100644 index 00000000000..3548189e8ca --- /dev/null +++ b/src/Event/Events/Test/TestDouble/MockObjectForIntersectionOfInterfacesCreated.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use function implode; +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class MockObjectForIntersectionOfInterfacesCreated implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var list + */ + private array $interfaces; + + /** + * @param list $interfaces + */ + public function __construct(Telemetry\Info $telemetryInfo, array $interfaces) + { + $this->telemetryInfo = $telemetryInfo; + $this->interfaces = $interfaces; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return list + */ + public function interfaces(): array + { + return $this->interfaces; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Mock Object Created (%s)', + implode('&', $this->interfaces), + ); + } +} diff --git a/src/Event/Events/Test/TestDouble/MockObjectForIntersectionOfInterfacesCreatedSubscriber.php b/src/Event/Events/Test/TestDouble/MockObjectForIntersectionOfInterfacesCreatedSubscriber.php new file mode 100644 index 00000000000..5b345b563f4 --- /dev/null +++ b/src/Event/Events/Test/TestDouble/MockObjectForIntersectionOfInterfacesCreatedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface MockObjectForIntersectionOfInterfacesCreatedSubscriber extends Subscriber +{ + public function notify(MockObjectForIntersectionOfInterfacesCreated $event): void; +} diff --git a/src/Event/Events/Test/TestDouble/PartialMockObjectCreated.php b/src/Event/Events/Test/TestDouble/PartialMockObjectCreated.php new file mode 100644 index 00000000000..625747816ce --- /dev/null +++ b/src/Event/Events/Test/TestDouble/PartialMockObjectCreated.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PartialMockObjectCreated implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var class-string + */ + private string $className; + + /** + * @var list + */ + private array $methodNames; + + /** + * @param class-string $className + */ + public function __construct(Telemetry\Info $telemetryInfo, string $className, string ...$methodNames) + { + $this->telemetryInfo = $telemetryInfo; + $this->className = $className; + $this->methodNames = $methodNames; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return list + */ + public function methodNames(): array + { + return $this->methodNames; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Partial Mock Object Created (%s)', + $this->className, + ); + } +} diff --git a/src/Event/Events/Test/TestDouble/PartialMockObjectCreatedSubscriber.php b/src/Event/Events/Test/TestDouble/PartialMockObjectCreatedSubscriber.php new file mode 100644 index 00000000000..e76407418ed --- /dev/null +++ b/src/Event/Events/Test/TestDouble/PartialMockObjectCreatedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PartialMockObjectCreatedSubscriber extends Subscriber +{ + public function notify(PartialMockObjectCreated $event): void; +} diff --git a/src/Event/Events/Test/TestDouble/TestStubCreated.php b/src/Event/Events/Test/TestDouble/TestStubCreated.php new file mode 100644 index 00000000000..667fbad4b68 --- /dev/null +++ b/src/Event/Events/Test/TestDouble/TestStubCreated.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestStubCreated implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var class-string + */ + private string $className; + + /** + * @param class-string $className + */ + public function __construct(Telemetry\Info $telemetryInfo, string $className) + { + $this->telemetryInfo = $telemetryInfo; + $this->className = $className; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Stub Created (%s)', + $this->className, + ); + } +} diff --git a/src/Event/Events/Test/TestDouble/TestStubCreatedSubscriber.php b/src/Event/Events/Test/TestDouble/TestStubCreatedSubscriber.php new file mode 100644 index 00000000000..6b5deaf37c5 --- /dev/null +++ b/src/Event/Events/Test/TestDouble/TestStubCreatedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface TestStubCreatedSubscriber extends Subscriber +{ + public function notify(TestStubCreated $event): void; +} diff --git a/src/Event/Events/Test/TestDouble/TestStubForIntersectionOfInterfacesCreated.php b/src/Event/Events/Test/TestDouble/TestStubForIntersectionOfInterfacesCreated.php new file mode 100644 index 00000000000..bba93d9e02a --- /dev/null +++ b/src/Event/Events/Test/TestDouble/TestStubForIntersectionOfInterfacesCreated.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use function implode; +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestStubForIntersectionOfInterfacesCreated implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var list + */ + private array $interfaces; + + /** + * @param list $interfaces + */ + public function __construct(Telemetry\Info $telemetryInfo, array $interfaces) + { + $this->telemetryInfo = $telemetryInfo; + $this->interfaces = $interfaces; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return list + */ + public function interfaces(): array + { + return $this->interfaces; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Stub Created (%s)', + implode('&', $this->interfaces), + ); + } +} diff --git a/src/Event/Events/Test/TestDouble/TestStubForIntersectionOfInterfacesCreatedSubscriber.php b/src/Event/Events/Test/TestDouble/TestStubForIntersectionOfInterfacesCreatedSubscriber.php new file mode 100644 index 00000000000..aec6f66ce36 --- /dev/null +++ b/src/Event/Events/Test/TestDouble/TestStubForIntersectionOfInterfacesCreatedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface TestStubForIntersectionOfInterfacesCreatedSubscriber extends Subscriber +{ + public function notify(TestStubForIntersectionOfInterfacesCreated $event): void; +} diff --git a/src/Event/Events/TestRunner/BootstrapFinished.php b/src/Event/Events/TestRunner/BootstrapFinished.php new file mode 100644 index 00000000000..8e46a00a836 --- /dev/null +++ b/src/Event/Events/TestRunner/BootstrapFinished.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BootstrapFinished implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var non-empty-string + */ + private string $filename; + + /** + * @param non-empty-string $filename + */ + public function __construct(Telemetry\Info $telemetryInfo, string $filename) + { + $this->telemetryInfo = $telemetryInfo; + $this->filename = $filename; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function filename(): string + { + return $this->filename; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Bootstrap Finished (%s)', + $this->filename, + ); + } +} diff --git a/src/Event/Events/TestRunner/BootstrapFinishedSubscriber.php b/src/Event/Events/TestRunner/BootstrapFinishedSubscriber.php new file mode 100644 index 00000000000..749648ec49e --- /dev/null +++ b/src/Event/Events/TestRunner/BootstrapFinishedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface BootstrapFinishedSubscriber extends Subscriber +{ + public function notify(BootstrapFinished $event): void; +} diff --git a/src/Event/Events/TestRunner/ChildProcessErrored.php b/src/Event/Events/TestRunner/ChildProcessErrored.php new file mode 100644 index 00000000000..2cb96422b88 --- /dev/null +++ b/src/Event/Events/TestRunner/ChildProcessErrored.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ChildProcessErrored implements Event +{ + private Telemetry\Info $telemetryInfo; + + public function __construct(Telemetry\Info $telemetryInfo) + { + $this->telemetryInfo = $telemetryInfo; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Child Process Errored'; + } +} diff --git a/src/Event/Events/TestRunner/ChildProcessErroredSubscriber.php b/src/Event/Events/TestRunner/ChildProcessErroredSubscriber.php new file mode 100644 index 00000000000..6ced5798241 --- /dev/null +++ b/src/Event/Events/TestRunner/ChildProcessErroredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ChildProcessErroredSubscriber extends Subscriber +{ + public function notify(ChildProcessErrored $event): void; +} diff --git a/src/Event/Events/TestRunner/ChildProcessFinished.php b/src/Event/Events/TestRunner/ChildProcessFinished.php new file mode 100644 index 00000000000..705a0c63425 --- /dev/null +++ b/src/Event/Events/TestRunner/ChildProcessFinished.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ChildProcessFinished implements Event +{ + private Telemetry\Info $telemetryInfo; + private string $stdout; + private string $stderr; + + public function __construct(Telemetry\Info $telemetryInfo, string $stdout, string $stderr) + { + $this->telemetryInfo = $telemetryInfo; + $this->stdout = $stdout; + $this->stderr = $stderr; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function stdout(): string + { + return $this->stdout; + } + + public function stderr(): string + { + return $this->stderr; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Child Process Finished'; + } +} diff --git a/src/Event/Events/TestRunner/ChildProcessFinishedSubscriber.php b/src/Event/Events/TestRunner/ChildProcessFinishedSubscriber.php new file mode 100644 index 00000000000..45fefa1827b --- /dev/null +++ b/src/Event/Events/TestRunner/ChildProcessFinishedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ChildProcessFinishedSubscriber extends Subscriber +{ + public function notify(ChildProcessFinished $event): void; +} diff --git a/src/Event/Events/TestRunner/ChildProcessStarted.php b/src/Event/Events/TestRunner/ChildProcessStarted.php new file mode 100644 index 00000000000..2c20471e27f --- /dev/null +++ b/src/Event/Events/TestRunner/ChildProcessStarted.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ChildProcessStarted implements Event +{ + private Telemetry\Info $telemetryInfo; + + public function __construct(Telemetry\Info $telemetryInfo) + { + $this->telemetryInfo = $telemetryInfo; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Child Process Started'; + } +} diff --git a/src/Event/Events/TestRunner/ChildProcessStartedSubscriber.php b/src/Event/Events/TestRunner/ChildProcessStartedSubscriber.php new file mode 100644 index 00000000000..4ba549ce8a0 --- /dev/null +++ b/src/Event/Events/TestRunner/ChildProcessStartedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ChildProcessStartedSubscriber extends Subscriber +{ + public function notify(ChildProcessStarted $event): void; +} diff --git a/src/Event/Events/TestRunner/Configured.php b/src/Event/Events/TestRunner/Configured.php new file mode 100644 index 00000000000..e0d14360a3b --- /dev/null +++ b/src/Event/Events/TestRunner/Configured.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; +use PHPUnit\TextUI\Configuration\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Configured implements Event +{ + private Telemetry\Info $telemetryInfo; + private Configuration $configuration; + + public function __construct(Telemetry\Info $telemetryInfo, Configuration $configuration) + { + $this->telemetryInfo = $telemetryInfo; + $this->configuration = $configuration; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function configuration(): Configuration + { + return $this->configuration; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Test Runner Configured'; + } +} diff --git a/src/Event/Events/TestRunner/ConfiguredSubscriber.php b/src/Event/Events/TestRunner/ConfiguredSubscriber.php new file mode 100644 index 00000000000..0b58f70bf49 --- /dev/null +++ b/src/Event/Events/TestRunner/ConfiguredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ConfiguredSubscriber extends Subscriber +{ + public function notify(Configured $event): void; +} diff --git a/src/Event/Events/TestRunner/DeprecationTriggered.php b/src/Event/Events/TestRunner/DeprecationTriggered.php new file mode 100644 index 00000000000..5cfef8f78cb --- /dev/null +++ b/src/Event/Events/TestRunner/DeprecationTriggered.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DeprecationTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @param non-empty-string $message + */ + public function __construct(Telemetry\Info $telemetryInfo, string $message) + { + $this->telemetryInfo = $telemetryInfo; + $this->message = $message; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Runner Triggered Deprecation (%s)', + $this->message, + ); + } +} diff --git a/src/Event/Events/TestRunner/DeprecationTriggeredSubscriber.php b/src/Event/Events/TestRunner/DeprecationTriggeredSubscriber.php new file mode 100644 index 00000000000..627ffbd0f0c --- /dev/null +++ b/src/Event/Events/TestRunner/DeprecationTriggeredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface DeprecationTriggeredSubscriber extends Subscriber +{ + public function notify(DeprecationTriggered $event): void; +} diff --git a/src/Event/Events/TestRunner/EventFacadeSealed.php b/src/Event/Events/TestRunner/EventFacadeSealed.php new file mode 100644 index 00000000000..bd4f5f60305 --- /dev/null +++ b/src/Event/Events/TestRunner/EventFacadeSealed.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class EventFacadeSealed implements Event +{ + private Telemetry\Info $telemetryInfo; + + public function __construct(Telemetry\Info $telemetryInfo) + { + $this->telemetryInfo = $telemetryInfo; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Event Facade Sealed'; + } +} diff --git a/src/Event/Events/TestRunner/EventFacadeSealedSubscriber.php b/src/Event/Events/TestRunner/EventFacadeSealedSubscriber.php new file mode 100644 index 00000000000..4d0d3d01011 --- /dev/null +++ b/src/Event/Events/TestRunner/EventFacadeSealedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface EventFacadeSealedSubscriber extends Subscriber +{ + public function notify(EventFacadeSealed $event): void; +} diff --git a/src/Event/Events/TestRunner/ExecutionAborted.php b/src/Event/Events/TestRunner/ExecutionAborted.php new file mode 100644 index 00000000000..6107e099b57 --- /dev/null +++ b/src/Event/Events/TestRunner/ExecutionAborted.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ExecutionAborted implements Event +{ + private Telemetry\Info $telemetryInfo; + + public function __construct(Telemetry\Info $telemetryInfo) + { + $this->telemetryInfo = $telemetryInfo; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Test Runner Execution Aborted'; + } +} diff --git a/src/Event/Events/TestRunner/ExecutionAbortedSubscriber.php b/src/Event/Events/TestRunner/ExecutionAbortedSubscriber.php new file mode 100644 index 00000000000..00397cca2e1 --- /dev/null +++ b/src/Event/Events/TestRunner/ExecutionAbortedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ExecutionAbortedSubscriber extends Subscriber +{ + public function notify(ExecutionAborted $event): void; +} diff --git a/src/Event/Events/TestRunner/ExecutionFinished.php b/src/Event/Events/TestRunner/ExecutionFinished.php new file mode 100644 index 00000000000..25789fe7ca1 --- /dev/null +++ b/src/Event/Events/TestRunner/ExecutionFinished.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ExecutionFinished implements Event +{ + private Telemetry\Info $telemetryInfo; + + public function __construct(Telemetry\Info $telemetryInfo) + { + $this->telemetryInfo = $telemetryInfo; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Test Runner Execution Finished'; + } +} diff --git a/src/Event/Events/TestRunner/ExecutionFinishedSubscriber.php b/src/Event/Events/TestRunner/ExecutionFinishedSubscriber.php new file mode 100644 index 00000000000..9945fc77e5e --- /dev/null +++ b/src/Event/Events/TestRunner/ExecutionFinishedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ExecutionFinishedSubscriber extends Subscriber +{ + public function notify(ExecutionFinished $event): void; +} diff --git a/src/Event/Events/TestRunner/ExecutionStarted.php b/src/Event/Events/TestRunner/ExecutionStarted.php new file mode 100644 index 00000000000..e38a2a4d99f --- /dev/null +++ b/src/Event/Events/TestRunner/ExecutionStarted.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; +use PHPUnit\Event\TestSuite\TestSuite; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ExecutionStarted implements Event +{ + private Telemetry\Info $telemetryInfo; + private TestSuite $testSuite; + + public function __construct(Telemetry\Info $telemetryInfo, TestSuite $testSuite) + { + $this->telemetryInfo = $telemetryInfo; + $this->testSuite = $testSuite; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function testSuite(): TestSuite + { + return $this->testSuite; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Runner Execution Started (%d test%s)', + $this->testSuite->count(), + $this->testSuite->count() !== 1 ? 's' : '', + ); + } +} diff --git a/src/Event/Events/TestRunner/ExecutionStartedSubscriber.php b/src/Event/Events/TestRunner/ExecutionStartedSubscriber.php new file mode 100644 index 00000000000..532f4409ae2 --- /dev/null +++ b/src/Event/Events/TestRunner/ExecutionStartedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ExecutionStartedSubscriber extends Subscriber +{ + public function notify(ExecutionStarted $event): void; +} diff --git a/src/Event/Events/TestRunner/ExtensionBootstrapped.php b/src/Event/Events/TestRunner/ExtensionBootstrapped.php new file mode 100644 index 00000000000..4ae1a6d5bd4 --- /dev/null +++ b/src/Event/Events/TestRunner/ExtensionBootstrapped.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ExtensionBootstrapped implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var class-string + */ + private string $className; + + /** + * @var array + */ + private array $parameters; + + /** + * @param class-string $className + * @param array $parameters + */ + public function __construct(Telemetry\Info $telemetryInfo, string $className, array $parameters) + { + $this->telemetryInfo = $telemetryInfo; + $this->className = $className; + $this->parameters = $parameters; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return array + */ + public function parameters(): array + { + return $this->parameters; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Extension Bootstrapped (%s)', + $this->className, + ); + } +} diff --git a/src/Event/Events/TestRunner/ExtensionBootstrappedSubscriber.php b/src/Event/Events/TestRunner/ExtensionBootstrappedSubscriber.php new file mode 100644 index 00000000000..c4c7d55c4b8 --- /dev/null +++ b/src/Event/Events/TestRunner/ExtensionBootstrappedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ExtensionBootstrappedSubscriber extends Subscriber +{ + public function notify(ExtensionBootstrapped $event): void; +} diff --git a/src/Event/Events/TestRunner/ExtensionLoadedFromPhar.php b/src/Event/Events/TestRunner/ExtensionLoadedFromPhar.php new file mode 100644 index 00000000000..2ce358d5c75 --- /dev/null +++ b/src/Event/Events/TestRunner/ExtensionLoadedFromPhar.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ExtensionLoadedFromPhar implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var non-empty-string + */ + private string $filename; + + /** + * @var non-empty-string + */ + private string $name; + + /** + * @var non-empty-string + */ + private string $version; + + /** + * @param non-empty-string $filename + * @param non-empty-string $name + * @param non-empty-string $version + */ + public function __construct(Telemetry\Info $telemetryInfo, string $filename, string $name, string $version) + { + $this->telemetryInfo = $telemetryInfo; + $this->filename = $filename; + $this->name = $name; + $this->version = $version; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function filename(): string + { + return $this->filename; + } + + /** + * @return non-empty-string + */ + public function name(): string + { + return $this->name; + } + + /** + * @return non-empty-string + */ + public function version(): string + { + return $this->version; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Extension Loaded from PHAR (%s %s)', + $this->name, + $this->version, + ); + } +} diff --git a/src/Event/Events/TestRunner/ExtensionLoadedFromPharSubscriber.php b/src/Event/Events/TestRunner/ExtensionLoadedFromPharSubscriber.php new file mode 100644 index 00000000000..fc7c2b0abef --- /dev/null +++ b/src/Event/Events/TestRunner/ExtensionLoadedFromPharSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ExtensionLoadedFromPharSubscriber extends Subscriber +{ + public function notify(ExtensionLoadedFromPhar $event): void; +} diff --git a/src/Event/Events/TestRunner/Finished.php b/src/Event/Events/TestRunner/Finished.php new file mode 100644 index 00000000000..2abc685bb9e --- /dev/null +++ b/src/Event/Events/TestRunner/Finished.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Finished implements Event +{ + private Telemetry\Info $telemetryInfo; + + public function __construct(Telemetry\Info $telemetryInfo) + { + $this->telemetryInfo = $telemetryInfo; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Test Runner Finished'; + } +} diff --git a/src/Event/Events/TestRunner/FinishedSubscriber.php b/src/Event/Events/TestRunner/FinishedSubscriber.php new file mode 100644 index 00000000000..6efc622d0b7 --- /dev/null +++ b/src/Event/Events/TestRunner/FinishedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface FinishedSubscriber extends Subscriber +{ + public function notify(Finished $event): void; +} diff --git a/src/Event/Events/TestRunner/GarbageCollectionDisabled.php b/src/Event/Events/TestRunner/GarbageCollectionDisabled.php new file mode 100644 index 00000000000..4324a5c157c --- /dev/null +++ b/src/Event/Events/TestRunner/GarbageCollectionDisabled.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class GarbageCollectionDisabled implements Event +{ + private Telemetry\Info $telemetryInfo; + + public function __construct(Telemetry\Info $telemetryInfo) + { + $this->telemetryInfo = $telemetryInfo; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Test Runner Disabled Garbage Collection'; + } +} diff --git a/src/Event/Events/TestRunner/GarbageCollectionDisabledSubscriber.php b/src/Event/Events/TestRunner/GarbageCollectionDisabledSubscriber.php new file mode 100644 index 00000000000..bb7e224fc65 --- /dev/null +++ b/src/Event/Events/TestRunner/GarbageCollectionDisabledSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface GarbageCollectionDisabledSubscriber extends Subscriber +{ + public function notify(GarbageCollectionDisabled $event): void; +} diff --git a/src/Event/Events/TestRunner/GarbageCollectionEnabled.php b/src/Event/Events/TestRunner/GarbageCollectionEnabled.php new file mode 100644 index 00000000000..1c4e088874f --- /dev/null +++ b/src/Event/Events/TestRunner/GarbageCollectionEnabled.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class GarbageCollectionEnabled implements Event +{ + private Telemetry\Info $telemetryInfo; + + public function __construct(Telemetry\Info $telemetryInfo) + { + $this->telemetryInfo = $telemetryInfo; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Test Runner Enabled Garbage Collection'; + } +} diff --git a/src/Event/Events/TestRunner/GarbageCollectionEnabledSubscriber.php b/src/Event/Events/TestRunner/GarbageCollectionEnabledSubscriber.php new file mode 100644 index 00000000000..437eddc231e --- /dev/null +++ b/src/Event/Events/TestRunner/GarbageCollectionEnabledSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface GarbageCollectionEnabledSubscriber extends Subscriber +{ + public function notify(GarbageCollectionEnabled $event): void; +} diff --git a/src/Event/Events/TestRunner/GarbageCollectionTriggered.php b/src/Event/Events/TestRunner/GarbageCollectionTriggered.php new file mode 100644 index 00000000000..d6a1ce643a9 --- /dev/null +++ b/src/Event/Events/TestRunner/GarbageCollectionTriggered.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class GarbageCollectionTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + + public function __construct(Telemetry\Info $telemetryInfo) + { + $this->telemetryInfo = $telemetryInfo; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Test Runner Triggered Garbage Collection'; + } +} diff --git a/src/Event/Events/TestRunner/GarbageCollectionTriggeredSubscriber.php b/src/Event/Events/TestRunner/GarbageCollectionTriggeredSubscriber.php new file mode 100644 index 00000000000..8b941c53580 --- /dev/null +++ b/src/Event/Events/TestRunner/GarbageCollectionTriggeredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface GarbageCollectionTriggeredSubscriber extends Subscriber +{ + public function notify(GarbageCollectionTriggered $event): void; +} diff --git a/src/Event/Events/TestRunner/NoticeTriggered.php b/src/Event/Events/TestRunner/NoticeTriggered.php new file mode 100644 index 00000000000..a5bfa04f386 --- /dev/null +++ b/src/Event/Events/TestRunner/NoticeTriggered.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class NoticeTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + private string $message; + + public function __construct(Telemetry\Info $telemetryInfo, string $message) + { + $this->telemetryInfo = $telemetryInfo; + $this->message = $message; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Runner Triggered Notice (%s)', + $this->message, + ); + } +} diff --git a/src/Event/Events/TestRunner/NoticeTriggeredSubscriber.php b/src/Event/Events/TestRunner/NoticeTriggeredSubscriber.php new file mode 100644 index 00000000000..be76b2c639e --- /dev/null +++ b/src/Event/Events/TestRunner/NoticeTriggeredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface NoticeTriggeredSubscriber extends Subscriber +{ + public function notify(NoticeTriggered $event): void; +} diff --git a/src/Event/Events/TestRunner/Started.php b/src/Event/Events/TestRunner/Started.php new file mode 100644 index 00000000000..a5840110850 --- /dev/null +++ b/src/Event/Events/TestRunner/Started.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Started implements Event +{ + private Telemetry\Info $telemetryInfo; + + public function __construct(Telemetry\Info $telemetryInfo) + { + $this->telemetryInfo = $telemetryInfo; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Test Runner Started'; + } +} diff --git a/src/Event/Events/TestRunner/StartedSubscriber.php b/src/Event/Events/TestRunner/StartedSubscriber.php new file mode 100644 index 00000000000..342407031d5 --- /dev/null +++ b/src/Event/Events/TestRunner/StartedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface StartedSubscriber extends Subscriber +{ + public function notify(Started $event): void; +} diff --git a/src/Event/Events/TestRunner/StaticAnalysisForCodeCoverageFinished.php b/src/Event/Events/TestRunner/StaticAnalysisForCodeCoverageFinished.php new file mode 100644 index 00000000000..d484528ec9c --- /dev/null +++ b/src/Event/Events/TestRunner/StaticAnalysisForCodeCoverageFinished.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class StaticAnalysisForCodeCoverageFinished implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var non-negative-int + */ + private int $cacheHits; + + /** + * @var non-negative-int + */ + private int $cacheMisses; + + /** + * @param non-negative-int $cacheHits + * @param non-negative-int $cacheMisses + */ + public function __construct(Telemetry\Info $telemetryInfo, int $cacheHits, int $cacheMisses) + { + $this->telemetryInfo = $telemetryInfo; + $this->cacheHits = $cacheHits; + $this->cacheMisses = $cacheMisses; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-negative-int + */ + public function cacheHits(): int + { + return $this->cacheHits; + } + + /** + * @return non-negative-int + */ + public function cacheMisses(): int + { + return $this->cacheMisses; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Static Analysis for Code Coverage Finished (%d cache hits, %d cache misses)', + $this->cacheHits, + $this->cacheMisses, + ); + } +} diff --git a/src/Event/Events/TestRunner/StaticAnalysisForCodeCoverageFinishedSubscriber.php b/src/Event/Events/TestRunner/StaticAnalysisForCodeCoverageFinishedSubscriber.php new file mode 100644 index 00000000000..eaf4f34856e --- /dev/null +++ b/src/Event/Events/TestRunner/StaticAnalysisForCodeCoverageFinishedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface StaticAnalysisForCodeCoverageFinishedSubscriber extends Subscriber +{ + public function notify(StaticAnalysisForCodeCoverageFinished $event): void; +} diff --git a/src/Event/Events/TestRunner/StaticAnalysisForCodeCoverageStarted.php b/src/Event/Events/TestRunner/StaticAnalysisForCodeCoverageStarted.php new file mode 100644 index 00000000000..d121097272e --- /dev/null +++ b/src/Event/Events/TestRunner/StaticAnalysisForCodeCoverageStarted.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class StaticAnalysisForCodeCoverageStarted implements Event +{ + private Telemetry\Info $telemetryInfo; + + public function __construct(Telemetry\Info $telemetryInfo) + { + $this->telemetryInfo = $telemetryInfo; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Static Analysis for Code Coverage Started'; + } +} diff --git a/src/Event/Events/TestRunner/StaticAnalysisForCodeCoverageStartedSubscriber.php b/src/Event/Events/TestRunner/StaticAnalysisForCodeCoverageStartedSubscriber.php new file mode 100644 index 00000000000..642bf712c80 --- /dev/null +++ b/src/Event/Events/TestRunner/StaticAnalysisForCodeCoverageStartedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface StaticAnalysisForCodeCoverageStartedSubscriber extends Subscriber +{ + public function notify(StaticAnalysisForCodeCoverageStarted $event): void; +} diff --git a/src/Event/Events/TestRunner/WarningTriggered.php b/src/Event/Events/TestRunner/WarningTriggered.php new file mode 100644 index 00000000000..e9df01be971 --- /dev/null +++ b/src/Event/Events/TestRunner/WarningTriggered.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class WarningTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @param non-empty-string $message + */ + public function __construct(Telemetry\Info $telemetryInfo, string $message) + { + $this->telemetryInfo = $telemetryInfo; + $this->message = $message; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Runner Triggered Warning (%s)', + $this->message, + ); + } +} diff --git a/src/Event/Events/TestRunner/WarningTriggeredSubscriber.php b/src/Event/Events/TestRunner/WarningTriggeredSubscriber.php new file mode 100644 index 00000000000..9afdd18f341 --- /dev/null +++ b/src/Event/Events/TestRunner/WarningTriggeredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface WarningTriggeredSubscriber extends Subscriber +{ + public function notify(WarningTriggered $event): void; +} diff --git a/src/Event/Events/TestSuite/Filtered.php b/src/Event/Events/TestSuite/Filtered.php new file mode 100644 index 00000000000..96d626ce4ef --- /dev/null +++ b/src/Event/Events/TestSuite/Filtered.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Filtered implements Event +{ + private Telemetry\Info $telemetryInfo; + private TestSuite $testSuite; + + public function __construct(Telemetry\Info $telemetryInfo, TestSuite $testSuite) + { + $this->telemetryInfo = $telemetryInfo; + $this->testSuite = $testSuite; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function testSuite(): TestSuite + { + return $this->testSuite; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Suite Filtered (%d test%s)', + $this->testSuite->count(), + $this->testSuite->count() !== 1 ? 's' : '', + ); + } +} diff --git a/src/Event/Events/TestSuite/FilteredSubscriber.php b/src/Event/Events/TestSuite/FilteredSubscriber.php new file mode 100644 index 00000000000..6bba3ad4030 --- /dev/null +++ b/src/Event/Events/TestSuite/FilteredSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface FilteredSubscriber extends Subscriber +{ + public function notify(Filtered $event): void; +} diff --git a/src/Event/Events/TestSuite/Finished.php b/src/Event/Events/TestSuite/Finished.php new file mode 100644 index 00000000000..a24ca869296 --- /dev/null +++ b/src/Event/Events/TestSuite/Finished.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Finished implements Event +{ + private Telemetry\Info $telemetryInfo; + private TestSuite $testSuite; + + public function __construct(Telemetry\Info $telemetryInfo, TestSuite $testSuite) + { + $this->telemetryInfo = $telemetryInfo; + $this->testSuite = $testSuite; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function testSuite(): TestSuite + { + return $this->testSuite; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Suite Finished (%s, %d test%s)', + $this->testSuite->name(), + $this->testSuite->count(), + $this->testSuite->count() !== 1 ? 's' : '', + ); + } +} diff --git a/src/Event/Events/TestSuite/FinishedSubscriber.php b/src/Event/Events/TestSuite/FinishedSubscriber.php new file mode 100644 index 00000000000..463c62136ff --- /dev/null +++ b/src/Event/Events/TestSuite/FinishedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface FinishedSubscriber extends Subscriber +{ + public function notify(Finished $event): void; +} diff --git a/src/Event/Events/TestSuite/Loaded.php b/src/Event/Events/TestSuite/Loaded.php new file mode 100644 index 00000000000..d278c0ddc04 --- /dev/null +++ b/src/Event/Events/TestSuite/Loaded.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Loaded implements Event +{ + private Telemetry\Info $telemetryInfo; + private TestSuite $testSuite; + + public function __construct(Telemetry\Info $telemetryInfo, TestSuite $testSuite) + { + $this->telemetryInfo = $telemetryInfo; + $this->testSuite = $testSuite; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function testSuite(): TestSuite + { + return $this->testSuite; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Suite Loaded (%d test%s)', + $this->testSuite->count(), + $this->testSuite->count() !== 1 ? 's' : '', + ); + } +} diff --git a/src/Event/Events/TestSuite/LoadedSubscriber.php b/src/Event/Events/TestSuite/LoadedSubscriber.php new file mode 100644 index 00000000000..e43886c4023 --- /dev/null +++ b/src/Event/Events/TestSuite/LoadedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface LoadedSubscriber extends Subscriber +{ + public function notify(Loaded $event): void; +} diff --git a/src/Event/Events/TestSuite/Skipped.php b/src/Event/Events/TestSuite/Skipped.php new file mode 100644 index 00000000000..efe9c1fff1a --- /dev/null +++ b/src/Event/Events/TestSuite/Skipped.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Skipped implements Event +{ + private Telemetry\Info $telemetryInfo; + private TestSuite $testSuite; + private string $message; + + public function __construct(Telemetry\Info $telemetryInfo, TestSuite $testSuite, string $message) + { + $this->telemetryInfo = $telemetryInfo; + $this->testSuite = $testSuite; + $this->message = $message; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function testSuite(): TestSuite + { + return $this->testSuite; + } + + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Suite Skipped (%s, %s)', + $this->testSuite->name(), + $this->message, + ); + } +} diff --git a/src/Event/Events/TestSuite/SkippedSubscriber.php b/src/Event/Events/TestSuite/SkippedSubscriber.php new file mode 100644 index 00000000000..30f509fc691 --- /dev/null +++ b/src/Event/Events/TestSuite/SkippedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface SkippedSubscriber extends Subscriber +{ + public function notify(Skipped $event): void; +} diff --git a/src/Event/Events/TestSuite/Sorted.php b/src/Event/Events/TestSuite/Sorted.php new file mode 100644 index 00000000000..a73461db8bf --- /dev/null +++ b/src/Event/Events/TestSuite/Sorted.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Sorted implements Event +{ + private Telemetry\Info $telemetryInfo; + private int $executionOrder; + private int $executionOrderDefects; + private bool $resolveDependencies; + + public function __construct(Telemetry\Info $telemetryInfo, int $executionOrder, int $executionOrderDefects, bool $resolveDependencies) + { + $this->telemetryInfo = $telemetryInfo; + $this->executionOrder = $executionOrder; + $this->executionOrderDefects = $executionOrderDefects; + $this->resolveDependencies = $resolveDependencies; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function executionOrder(): int + { + return $this->executionOrder; + } + + public function executionOrderDefects(): int + { + return $this->executionOrderDefects; + } + + public function resolveDependencies(): bool + { + return $this->resolveDependencies; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Test Suite Sorted'; + } +} diff --git a/src/Event/Events/TestSuite/SortedSubscriber.php b/src/Event/Events/TestSuite/SortedSubscriber.php new file mode 100644 index 00000000000..481eabb04dc --- /dev/null +++ b/src/Event/Events/TestSuite/SortedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface SortedSubscriber extends Subscriber +{ + public function notify(Sorted $event): void; +} diff --git a/src/Event/Events/TestSuite/Started.php b/src/Event/Events/TestSuite/Started.php new file mode 100644 index 00000000000..36fee1f0065 --- /dev/null +++ b/src/Event/Events/TestSuite/Started.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Started implements Event +{ + private Telemetry\Info $telemetryInfo; + private TestSuite $testSuite; + + public function __construct(Telemetry\Info $telemetryInfo, TestSuite $testSuite) + { + $this->telemetryInfo = $telemetryInfo; + $this->testSuite = $testSuite; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function testSuite(): TestSuite + { + return $this->testSuite; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Suite Started (%s, %d test%s)', + $this->testSuite->name(), + $this->testSuite->count(), + $this->testSuite->count() !== 1 ? 's' : '', + ); + } +} diff --git a/src/Event/Events/TestSuite/StartedSubscriber.php b/src/Event/Events/TestSuite/StartedSubscriber.php new file mode 100644 index 00000000000..66c4e1b2dbc --- /dev/null +++ b/src/Event/Events/TestSuite/StartedSubscriber.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface StartedSubscriber extends Subscriber +{ + public function notify(Started $event): void; +} diff --git a/src/Event/Exception/EventAlreadyAssignedException.php b/src/Event/Exception/EventAlreadyAssignedException.php new file mode 100644 index 00000000000..a7dba264c45 --- /dev/null +++ b/src/Event/Exception/EventAlreadyAssignedException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class EventAlreadyAssignedException extends RuntimeException implements Exception +{ +} diff --git a/src/Event/Exception/EventFacadeIsSealedException.php b/src/Event/Exception/EventFacadeIsSealedException.php new file mode 100644 index 00000000000..96bf949d92a --- /dev/null +++ b/src/Event/Exception/EventFacadeIsSealedException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class EventFacadeIsSealedException extends RuntimeException implements Exception +{ +} diff --git a/src/Event/Exception/Exception.php b/src/Event/Exception/Exception.php new file mode 100644 index 00000000000..25bf06c6b39 --- /dev/null +++ b/src/Event/Exception/Exception.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface Exception extends \PHPUnit\Exception +{ +} diff --git a/src/Event/Exception/InvalidArgumentException.php b/src/Event/Exception/InvalidArgumentException.php new file mode 100644 index 00000000000..3fb060cf75d --- /dev/null +++ b/src/Event/Exception/InvalidArgumentException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidArgumentException extends \InvalidArgumentException implements Exception +{ +} diff --git a/src/Event/Exception/InvalidEventException.php b/src/Event/Exception/InvalidEventException.php new file mode 100644 index 00000000000..05290372f5a --- /dev/null +++ b/src/Event/Exception/InvalidEventException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidEventException extends RuntimeException implements Exception +{ +} diff --git a/src/Event/Exception/InvalidSubscriberException.php b/src/Event/Exception/InvalidSubscriberException.php new file mode 100644 index 00000000000..d12deb7f1ae --- /dev/null +++ b/src/Event/Exception/InvalidSubscriberException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidSubscriberException extends RuntimeException implements Exception +{ +} diff --git a/src/Event/Exception/MapError.php b/src/Event/Exception/MapError.php new file mode 100644 index 00000000000..b97a18e6bcc --- /dev/null +++ b/src/Event/Exception/MapError.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class MapError extends RuntimeException implements Exception +{ +} diff --git a/src/Event/Exception/NoComparisonFailureException.php b/src/Event/Exception/NoComparisonFailureException.php new file mode 100644 index 00000000000..f9926772c65 --- /dev/null +++ b/src/Event/Exception/NoComparisonFailureException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\Exception; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class NoComparisonFailureException extends RuntimeException implements Exception +{ +} diff --git a/src/Event/Exception/NoDataSetFromDataProviderException.php b/src/Event/Exception/NoDataSetFromDataProviderException.php new file mode 100644 index 00000000000..b17a4d154f0 --- /dev/null +++ b/src/Event/Exception/NoDataSetFromDataProviderException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestData; + +use PHPUnit\Event\Exception; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class NoDataSetFromDataProviderException extends RuntimeException implements Exception +{ +} diff --git a/src/Event/Exception/NoPreviousThrowableException.php b/src/Event/Exception/NoPreviousThrowableException.php new file mode 100644 index 00000000000..e339323cd41 --- /dev/null +++ b/src/Event/Exception/NoPreviousThrowableException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class NoPreviousThrowableException extends RuntimeException implements Exception +{ +} diff --git a/src/Event/Exception/NoTestCaseObjectOnCallStackException.php b/src/Event/Exception/NoTestCaseObjectOnCallStackException.php new file mode 100644 index 00000000000..35b4c25af75 --- /dev/null +++ b/src/Event/Exception/NoTestCaseObjectOnCallStackException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +use PHPUnit\Event\Exception; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NoTestCaseObjectOnCallStackException extends RuntimeException implements Exception +{ + public function __construct() + { + parent::__construct('Cannot find TestCase object on call stack'); + } +} diff --git a/src/Event/Exception/RuntimeException.php b/src/Event/Exception/RuntimeException.php new file mode 100644 index 00000000000..2a444db2fa6 --- /dev/null +++ b/src/Event/Exception/RuntimeException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class RuntimeException extends \RuntimeException implements Exception +{ +} diff --git a/src/Event/Exception/SubscriberTypeAlreadyRegisteredException.php b/src/Event/Exception/SubscriberTypeAlreadyRegisteredException.php new file mode 100644 index 00000000000..ebbbd3fa0aa --- /dev/null +++ b/src/Event/Exception/SubscriberTypeAlreadyRegisteredException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class SubscriberTypeAlreadyRegisteredException extends RuntimeException implements Exception +{ +} diff --git a/src/Event/Exception/UnknownEventException.php b/src/Event/Exception/UnknownEventException.php new file mode 100644 index 00000000000..0c1211473f0 --- /dev/null +++ b/src/Event/Exception/UnknownEventException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class UnknownEventException extends RuntimeException implements Exception +{ +} diff --git a/src/Event/Exception/UnknownEventTypeException.php b/src/Event/Exception/UnknownEventTypeException.php new file mode 100644 index 00000000000..ab9432decc5 --- /dev/null +++ b/src/Event/Exception/UnknownEventTypeException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class UnknownEventTypeException extends RuntimeException implements Exception +{ +} diff --git a/src/Event/Exception/UnknownSubscriberException.php b/src/Event/Exception/UnknownSubscriberException.php new file mode 100644 index 00000000000..b9aaedb1db4 --- /dev/null +++ b/src/Event/Exception/UnknownSubscriberException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class UnknownSubscriberException extends RuntimeException implements Exception +{ +} diff --git a/src/Event/Exception/UnknownSubscriberTypeException.php b/src/Event/Exception/UnknownSubscriberTypeException.php new file mode 100644 index 00000000000..d44ff0e9c8a --- /dev/null +++ b/src/Event/Exception/UnknownSubscriberTypeException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class UnknownSubscriberTypeException extends RuntimeException implements Exception +{ +} diff --git a/src/Event/Facade.php b/src/Event/Facade.php new file mode 100644 index 00000000000..6348fc434c5 --- /dev/null +++ b/src/Event/Facade.php @@ -0,0 +1,274 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use function assert; +use function interface_exists; +use PHPUnit\Event\Telemetry\HRTime; +use PHPUnit\Event\Telemetry\SystemGarbageCollectorStatusProvider; +use PHPUnit\Runner\DeprecationCollector\Facade as DeprecationCollector; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Facade +{ + private static ?self $instance = null; + private Emitter $emitter; + private ?TypeMap $typeMap = null; + private ?DeferringDispatcher $deferringDispatcher = null; + private bool $sealed = false; + + public static function instance(): self + { + if (self::$instance === null) { + self::$instance = new self; + } + + return self::$instance; + } + + public static function emitter(): Emitter + { + return self::instance()->emitter; + } + + public function __construct() + { + $this->emitter = $this->createDispatchingEmitter(); + } + + /** + * @throws EventFacadeIsSealedException + * @throws UnknownSubscriberTypeException + */ + public function registerSubscribers(Subscriber ...$subscribers): void + { + foreach ($subscribers as $subscriber) { + $this->registerSubscriber($subscriber); + } + } + + /** + * @throws EventFacadeIsSealedException + * @throws UnknownSubscriberTypeException + */ + public function registerSubscriber(Subscriber $subscriber): void + { + if ($this->sealed) { + throw new EventFacadeIsSealedException; + } + + $this->deferredDispatcher()->registerSubscriber($subscriber); + } + + /** + * @throws EventFacadeIsSealedException + */ + public function registerTracer(Tracer\Tracer $tracer): void + { + if ($this->sealed) { + throw new EventFacadeIsSealedException; + } + + $this->deferredDispatcher()->registerTracer($tracer); + } + + /** + * @codeCoverageIgnore + * + * @noinspection PhpUnused + */ + public function initForIsolation(HRTime $offset): CollectingDispatcher + { + DeprecationCollector::initForIsolation(); + + $dispatcher = new CollectingDispatcher( + new DirectDispatcher($this->typeMap()), + ); + + $this->emitter = new DispatchingEmitter( + $dispatcher, + new Telemetry\System( + new Telemetry\SystemStopWatchWithOffset($offset), + new Telemetry\SystemMemoryMeter, + new SystemGarbageCollectorStatusProvider, + ), + ); + + $this->sealed = true; + + return $dispatcher; + } + + public function forward(EventCollection $events): void + { + $dispatcher = $this->deferredDispatcher(); + + foreach ($events as $event) { + $dispatcher->dispatch($event); + } + } + + public function seal(): void + { + $this->deferredDispatcher()->flush(); + + $this->sealed = true; + + $this->emitter->testRunnerEventFacadeSealed(); + } + + private function createDispatchingEmitter(): DispatchingEmitter + { + return new DispatchingEmitter( + $this->deferredDispatcher(), + $this->createTelemetrySystem(), + ); + } + + private function createTelemetrySystem(): Telemetry\System + { + return new Telemetry\System( + new Telemetry\SystemStopWatch, + new Telemetry\SystemMemoryMeter, + new SystemGarbageCollectorStatusProvider, + ); + } + + private function deferredDispatcher(): DeferringDispatcher + { + if ($this->deferringDispatcher === null) { + $this->deferringDispatcher = new DeferringDispatcher( + new DirectDispatcher($this->typeMap()), + ); + } + + return $this->deferringDispatcher; + } + + private function typeMap(): TypeMap + { + if ($this->typeMap === null) { + $typeMap = new TypeMap; + + $this->registerDefaultTypes($typeMap); + + $this->typeMap = $typeMap; + } + + return $this->typeMap; + } + + private function registerDefaultTypes(TypeMap $typeMap): void + { + $defaultEvents = [ + Application\Started::class, + Application\Finished::class, + + Test\DataProviderMethodCalled::class, + Test\DataProviderMethodFinished::class, + Test\MarkedIncomplete::class, + Test\AfterLastTestMethodCalled::class, + Test\AfterLastTestMethodErrored::class, + Test\AfterLastTestMethodFailed::class, + Test\AfterLastTestMethodFinished::class, + Test\AfterTestMethodCalled::class, + Test\AfterTestMethodErrored::class, + Test\AfterTestMethodFailed::class, + Test\AfterTestMethodFinished::class, + Test\BeforeFirstTestMethodCalled::class, + Test\BeforeFirstTestMethodErrored::class, + Test\BeforeFirstTestMethodFailed::class, + Test\BeforeFirstTestMethodFinished::class, + Test\BeforeTestMethodCalled::class, + Test\BeforeTestMethodErrored::class, + Test\BeforeTestMethodFailed::class, + Test\BeforeTestMethodFinished::class, + Test\AdditionalInformationProvided::class, + Test\ComparatorRegistered::class, + Test\ConsideredRisky::class, + Test\DeprecationTriggered::class, + Test\Errored::class, + Test\ErrorTriggered::class, + Test\Failed::class, + Test\Finished::class, + Test\NoticeTriggered::class, + Test\Passed::class, + Test\PhpDeprecationTriggered::class, + Test\PhpNoticeTriggered::class, + Test\PhpunitDeprecationTriggered::class, + Test\PhpunitNoticeTriggered::class, + Test\PhpunitErrorTriggered::class, + Test\PhpunitWarningTriggered::class, + Test\PhpWarningTriggered::class, + Test\PostConditionCalled::class, + Test\PostConditionErrored::class, + Test\PostConditionFailed::class, + Test\PostConditionFinished::class, + Test\PreConditionCalled::class, + Test\PreConditionErrored::class, + Test\PreConditionFailed::class, + Test\PreConditionFinished::class, + Test\PreparationStarted::class, + Test\Prepared::class, + Test\PreparationErrored::class, + Test\PreparationFailed::class, + Test\PrintedUnexpectedOutput::class, + Test\Skipped::class, + Test\WarningTriggered::class, + + Test\MockObjectCreated::class, + Test\MockObjectForIntersectionOfInterfacesCreated::class, + Test\PartialMockObjectCreated::class, + Test\TestStubCreated::class, + Test\TestStubForIntersectionOfInterfacesCreated::class, + + TestRunner\BootstrapFinished::class, + TestRunner\Configured::class, + TestRunner\EventFacadeSealed::class, + TestRunner\ExecutionAborted::class, + TestRunner\ExecutionFinished::class, + TestRunner\ExecutionStarted::class, + TestRunner\ExtensionLoadedFromPhar::class, + TestRunner\ExtensionBootstrapped::class, + TestRunner\Finished::class, + TestRunner\Started::class, + TestRunner\DeprecationTriggered::class, + TestRunner\NoticeTriggered::class, + TestRunner\WarningTriggered::class, + TestRunner\GarbageCollectionDisabled::class, + TestRunner\GarbageCollectionTriggered::class, + TestRunner\GarbageCollectionEnabled::class, + TestRunner\ChildProcessStarted::class, + TestRunner\ChildProcessErrored::class, + TestRunner\ChildProcessFinished::class, + TestRunner\StaticAnalysisForCodeCoverageFinished::class, + TestRunner\StaticAnalysisForCodeCoverageStarted::class, + + TestSuite\Filtered::class, + TestSuite\Finished::class, + TestSuite\Loaded::class, + TestSuite\Skipped::class, + TestSuite\Sorted::class, + TestSuite\Started::class, + ]; + + foreach ($defaultEvents as $eventClass) { + $subscriberInterface = $eventClass . 'Subscriber'; + + assert(interface_exists($subscriberInterface)); + + $typeMap->addMapping($subscriberInterface, $eventClass); + } + } +} diff --git a/src/Event/Subscriber.php b/src/Event/Subscriber.php new file mode 100644 index 00000000000..e0455c025ca --- /dev/null +++ b/src/Event/Subscriber.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface Subscriber +{ +} diff --git a/src/Event/Tracer.php b/src/Event/Tracer.php new file mode 100644 index 00000000000..3b029fdf217 --- /dev/null +++ b/src/Event/Tracer.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Tracer; + +use PHPUnit\Event\Event; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface Tracer +{ + public function trace(Event $event): void; +} diff --git a/src/Event/TypeMap.php b/src/Event/TypeMap.php new file mode 100644 index 00000000000..08f42a42094 --- /dev/null +++ b/src/Event/TypeMap.php @@ -0,0 +1,190 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use function array_any; +use function array_key_exists; +use function class_exists; +use function class_implements; +use function in_array; +use function interface_exists; +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TypeMap +{ + /** + * @var array + */ + private array $mapping = []; + + /** + * @param class-string $subscriberInterface + * @param class-string $eventClass + * + * @throws EventAlreadyAssignedException + * @throws InvalidEventException + * @throws InvalidSubscriberException + * @throws SubscriberTypeAlreadyRegisteredException + * @throws UnknownEventException + * @throws UnknownSubscriberException + */ + public function addMapping(string $subscriberInterface, string $eventClass): void + { + $this->ensureSubscriberInterfaceExists($subscriberInterface); + $this->ensureSubscriberInterfaceExtendsInterface($subscriberInterface); + $this->ensureEventClassExists($eventClass); + $this->ensureEventClassImplementsEventInterface($eventClass); + $this->ensureSubscriberWasNotAlreadyRegistered($subscriberInterface); + $this->ensureEventWasNotAlreadyAssigned($eventClass); + + $this->mapping[$subscriberInterface] = $eventClass; + } + + public function isKnownSubscriberType(Subscriber $subscriber): bool + { + return array_any( + class_implements($subscriber), + fn (string $interface) => array_key_exists($interface, $this->mapping), + ); + } + + public function isKnownEventType(Event $event): bool + { + return in_array($event::class, $this->mapping, true); + } + + /** + * @throws MapError + * + * @return class-string + */ + public function map(Subscriber $subscriber): string + { + foreach (class_implements($subscriber) as $interface) { + if (array_key_exists($interface, $this->mapping)) { + return $this->mapping[$interface]; + } + } + + throw new MapError( + sprintf( + 'Subscriber "%s" does not implement a known interface', + $subscriber::class, + ), + ); + } + + /** + * @param class-string $subscriberInterface + * + * @throws UnknownSubscriberException + */ + private function ensureSubscriberInterfaceExists(string $subscriberInterface): void + { + if (!interface_exists($subscriberInterface)) { + throw new UnknownSubscriberException( + sprintf( + 'Subscriber "%s" does not exist or is not an interface', + $subscriberInterface, + ), + ); + } + } + + /** + * @param class-string $eventClass + * + * @throws UnknownEventException + */ + private function ensureEventClassExists(string $eventClass): void + { + if (!class_exists($eventClass)) { + throw new UnknownEventException( + sprintf( + 'Event class "%s" does not exist', + $eventClass, + ), + ); + } + } + + /** + * @param class-string $subscriberInterface + * + * @throws InvalidSubscriberException + */ + private function ensureSubscriberInterfaceExtendsInterface(string $subscriberInterface): void + { + if (!in_array(Subscriber::class, class_implements($subscriberInterface), true)) { + throw new InvalidSubscriberException( + sprintf( + 'Subscriber "%s" does not extend Subscriber interface', + $subscriberInterface, + ), + ); + } + } + + /** + * @param class-string $eventClass + * + * @throws InvalidEventException + */ + private function ensureEventClassImplementsEventInterface(string $eventClass): void + { + if (!in_array(Event::class, class_implements($eventClass), true)) { + throw new InvalidEventException( + sprintf( + 'Event "%s" does not implement Event interface', + $eventClass, + ), + ); + } + } + + /** + * @param class-string $subscriberInterface + * + * @throws SubscriberTypeAlreadyRegisteredException + */ + private function ensureSubscriberWasNotAlreadyRegistered(string $subscriberInterface): void + { + if (array_key_exists($subscriberInterface, $this->mapping)) { + throw new SubscriberTypeAlreadyRegisteredException( + sprintf( + 'Subscriber type "%s" already registered', + $subscriberInterface, + ), + ); + } + } + + /** + * @param class-string $eventClass + * + * @throws EventAlreadyAssignedException + */ + private function ensureEventWasNotAlreadyAssigned(string $eventClass): void + { + if (in_array($eventClass, $this->mapping, true)) { + throw new EventAlreadyAssignedException( + sprintf( + 'Event "%s" already assigned', + $eventClass, + ), + ); + } + } +} diff --git a/src/Event/Value/ClassMethod.php b/src/Event/Value/ClassMethod.php new file mode 100644 index 00000000000..2a94033a5db --- /dev/null +++ b/src/Event/Value/ClassMethod.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ClassMethod +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function __construct(string $className, string $methodName) + { + $this->className = $className; + $this->methodName = $methodName; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/src/Event/Value/ComparisonFailure.php b/src/Event/Value/ComparisonFailure.php new file mode 100644 index 00000000000..c31f93e65cd --- /dev/null +++ b/src/Event/Value/ComparisonFailure.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ComparisonFailure +{ + private string $expected; + private string $actual; + private string $diff; + + public function __construct(string $expected, string $actual, string $diff) + { + $this->expected = $expected; + $this->actual = $actual; + $this->diff = $diff; + } + + public function expected(): string + { + return $this->expected; + } + + public function actual(): string + { + return $this->actual; + } + + public function diff(): string + { + return $this->diff; + } +} diff --git a/src/Event/Value/ComparisonFailureBuilder.php b/src/Event/Value/ComparisonFailureBuilder.php new file mode 100644 index 00000000000..53fe0d09aec --- /dev/null +++ b/src/Event/Value/ComparisonFailureBuilder.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +use function is_bool; +use function is_scalar; +use function print_r; +use PHPUnit\Framework\ExpectationFailedException; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ComparisonFailureBuilder +{ + public static function from(Throwable $t): ?ComparisonFailure + { + if (!$t instanceof ExpectationFailedException) { + return null; + } + + if ($t->getComparisonFailure() === null) { + return null; + } + + $expectedAsString = $t->getComparisonFailure()->getExpectedAsString(); + + if ($expectedAsString === '') { + $expectedAsString = self::mapScalarValueToString($t->getComparisonFailure()->getExpected()); + } + + $actualAsString = $t->getComparisonFailure()->getActualAsString(); + + if ($actualAsString === '') { + $actualAsString = self::mapScalarValueToString($t->getComparisonFailure()->getActual()); + } + + return new ComparisonFailure( + $expectedAsString, + $actualAsString, + $t->getComparisonFailure()->getDiff(), + ); + } + + private static function mapScalarValueToString(mixed $value): string + { + if ($value === null) { + return 'null'; + } + + if (is_bool($value)) { + return $value ? 'true' : 'false'; + } + + if (is_scalar($value)) { + return print_r($value, true); + } + + return ''; + } +} diff --git a/src/Event/Value/Runtime/OperatingSystem.php b/src/Event/Value/Runtime/OperatingSystem.php new file mode 100644 index 00000000000..508d809f103 --- /dev/null +++ b/src/Event/Value/Runtime/OperatingSystem.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Runtime; + +use const PHP_OS; +use const PHP_OS_FAMILY; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class OperatingSystem +{ + private string $operatingSystem; + private string $operatingSystemFamily; + + public function __construct() + { + $this->operatingSystem = PHP_OS; + $this->operatingSystemFamily = PHP_OS_FAMILY; + } + + public function operatingSystem(): string + { + return $this->operatingSystem; + } + + public function operatingSystemFamily(): string + { + return $this->operatingSystemFamily; + } +} diff --git a/src/Event/Value/Runtime/PHP.php b/src/Event/Value/Runtime/PHP.php new file mode 100644 index 00000000000..46004f98c65 --- /dev/null +++ b/src/Event/Value/Runtime/PHP.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Runtime; + +use const PHP_EXTRA_VERSION; +use const PHP_MAJOR_VERSION; +use const PHP_MINOR_VERSION; +use const PHP_RELEASE_VERSION; +use const PHP_SAPI; +use const PHP_VERSION; +use const PHP_VERSION_ID; +use function array_merge; +use function get_loaded_extensions; +use function sort; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PHP +{ + private string $version; + private int $versionId; + private int $majorVersion; + private int $minorVersion; + private int $releaseVersion; + private string $extraVersion; + private string $sapi; + + /** + * @var list + */ + private array $extensions; + + public function __construct() + { + $this->version = PHP_VERSION; + $this->versionId = PHP_VERSION_ID; + $this->majorVersion = PHP_MAJOR_VERSION; + $this->minorVersion = PHP_MINOR_VERSION; + $this->releaseVersion = PHP_RELEASE_VERSION; + $this->extraVersion = PHP_EXTRA_VERSION; + $this->sapi = PHP_SAPI; + + $extensions = array_merge( + get_loaded_extensions(true), + get_loaded_extensions(), + ); + + sort($extensions); + + $this->extensions = $extensions; + } + + public function version(): string + { + return $this->version; + } + + public function sapi(): string + { + return $this->sapi; + } + + public function majorVersion(): int + { + return $this->majorVersion; + } + + public function minorVersion(): int + { + return $this->minorVersion; + } + + public function releaseVersion(): int + { + return $this->releaseVersion; + } + + public function extraVersion(): string + { + return $this->extraVersion; + } + + public function versionId(): int + { + return $this->versionId; + } + + /** + * @return list + */ + public function extensions(): array + { + return $this->extensions; + } +} diff --git a/src/Event/Value/Runtime/PHPUnit.php b/src/Event/Value/Runtime/PHPUnit.php new file mode 100644 index 00000000000..7f85133d2cc --- /dev/null +++ b/src/Event/Value/Runtime/PHPUnit.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Runtime; + +use PHPUnit\Runner\Version; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PHPUnit +{ + private string $versionId; + private string $releaseSeries; + + public function __construct() + { + $this->versionId = Version::id(); + $this->releaseSeries = Version::series(); + } + + public function versionId(): string + { + return $this->versionId; + } + + public function releaseSeries(): string + { + return $this->releaseSeries; + } +} diff --git a/src/Event/Value/Runtime/Runtime.php b/src/Event/Value/Runtime/Runtime.php new file mode 100644 index 00000000000..552ec98878f --- /dev/null +++ b/src/Event/Value/Runtime/Runtime.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Runtime; + +use function sprintf; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Runtime +{ + private OperatingSystem $operatingSystem; + private PHP $php; + private PHPUnit $phpunit; + + public function __construct() + { + $this->operatingSystem = new OperatingSystem; + $this->php = new PHP; + $this->phpunit = new PHPUnit; + } + + public function asString(): string + { + $php = $this->php(); + + return sprintf( + 'PHPUnit %s using PHP %s (%s) on %s', + $this->phpunit()->versionId(), + $php->version(), + $php->sapi(), + $this->operatingSystem()->operatingSystem(), + ); + } + + public function operatingSystem(): OperatingSystem + { + return $this->operatingSystem; + } + + public function php(): PHP + { + return $this->php; + } + + public function phpunit(): PHPUnit + { + return $this->phpunit; + } +} diff --git a/src/Event/Value/Telemetry/Duration.php b/src/Event/Value/Telemetry/Duration.php new file mode 100644 index 00000000000..230150a159b --- /dev/null +++ b/src/Event/Value/Telemetry/Duration.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Telemetry; + +use function floor; +use function sprintf; +use PHPUnit\Event\InvalidArgumentException; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Duration +{ + private int $seconds; + private int $nanoseconds; + + /** + * @throws InvalidArgumentException + */ + public static function fromSecondsAndNanoseconds(int $seconds, int $nanoseconds): self + { + return new self( + $seconds, + $nanoseconds, + ); + } + + /** + * @throws InvalidArgumentException + */ + private function __construct(int $seconds, int $nanoseconds) + { + $this->ensureNotNegative($seconds, 'seconds'); + $this->ensureNotNegative($nanoseconds, 'nanoseconds'); + $this->ensureNanoSecondsInRange($nanoseconds); + + $this->seconds = $seconds; + $this->nanoseconds = $nanoseconds; + } + + public function seconds(): int + { + return $this->seconds; + } + + public function nanoseconds(): int + { + return $this->nanoseconds; + } + + public function asFloat(): float + { + return $this->seconds() + ($this->nanoseconds() / 1000000000); + } + + public function asString(): string + { + $seconds = $this->seconds(); + $minutes = 0; + $hours = 0; + + if ($seconds > 60 * 60) { + $hours = floor($seconds / 60 / 60); + $seconds -= ($hours * 60 * 60); + } + + if ($seconds > 60) { + $minutes = floor($seconds / 60); + $seconds -= ($minutes * 60); + } + + return sprintf( + '%02d:%02d:%02d.%09d', + $hours, + $minutes, + $seconds, + $this->nanoseconds(), + ); + } + + public function equals(self $other): bool + { + return $this->seconds === $other->seconds && + $this->nanoseconds === $other->nanoseconds; + } + + public function isLessThan(self $other): bool + { + if ($this->seconds < $other->seconds) { + return true; + } + + if ($this->seconds > $other->seconds) { + return false; + } + + return $this->nanoseconds < $other->nanoseconds; + } + + public function isGreaterThan(self $other): bool + { + if ($this->seconds > $other->seconds) { + return true; + } + + if ($this->seconds < $other->seconds) { + return false; + } + + return $this->nanoseconds > $other->nanoseconds; + } + + /** + * @throws InvalidArgumentException + */ + private function ensureNotNegative(int $value, string $type): void + { + if ($value < 0) { + throw new InvalidArgumentException( + sprintf( + 'Value for %s must not be negative.', + $type, + ), + ); + } + } + + /** + * @throws InvalidArgumentException + */ + private function ensureNanoSecondsInRange(int $nanoseconds): void + { + if ($nanoseconds > 999999999) { + throw new InvalidArgumentException( + 'Value for nanoseconds must not be greater than 999999999.', + ); + } + } +} diff --git a/src/Event/Value/Telemetry/GarbageCollectorStatus.php b/src/Event/Value/Telemetry/GarbageCollectorStatus.php new file mode 100644 index 00000000000..f8bb77a74ec --- /dev/null +++ b/src/Event/Value/Telemetry/GarbageCollectorStatus.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class GarbageCollectorStatus +{ + private int $runs; + private int $collected; + private int $threshold; + private int $roots; + private float $applicationTime; + private float $collectorTime; + private float $destructorTime; + private float $freeTime; + private bool $running; + private bool $protected; + private bool $full; + private int $bufferSize; + + public function __construct(int $runs, int $collected, int $threshold, int $roots, float $applicationTime, float $collectorTime, float $destructorTime, float $freeTime, bool $running, bool $protected, bool $full, int $bufferSize) + { + $this->runs = $runs; + $this->collected = $collected; + $this->threshold = $threshold; + $this->roots = $roots; + $this->applicationTime = $applicationTime; + $this->collectorTime = $collectorTime; + $this->destructorTime = $destructorTime; + $this->freeTime = $freeTime; + $this->running = $running; + $this->protected = $protected; + $this->full = $full; + $this->bufferSize = $bufferSize; + } + + public function runs(): int + { + return $this->runs; + } + + public function collected(): int + { + return $this->collected; + } + + public function threshold(): int + { + return $this->threshold; + } + + public function roots(): int + { + return $this->roots; + } + + public function applicationTime(): float + { + return $this->applicationTime; + } + + public function collectorTime(): float + { + return $this->collectorTime; + } + + public function destructorTime(): float + { + return $this->destructorTime; + } + + public function freeTime(): float + { + return $this->freeTime; + } + + public function isRunning(): bool + { + return $this->running; + } + + public function isProtected(): bool + { + return $this->protected; + } + + public function isFull(): bool + { + return $this->full; + } + + public function bufferSize(): int + { + return $this->bufferSize; + } +} diff --git a/src/Event/Value/Telemetry/GarbageCollectorStatusProvider.php b/src/Event/Value/Telemetry/GarbageCollectorStatusProvider.php new file mode 100644 index 00000000000..09bede2e539 --- /dev/null +++ b/src/Event/Value/Telemetry/GarbageCollectorStatusProvider.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Telemetry; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface GarbageCollectorStatusProvider +{ + public function status(): GarbageCollectorStatus; +} diff --git a/src/Event/Value/Telemetry/HRTime.php b/src/Event/Value/Telemetry/HRTime.php new file mode 100644 index 00000000000..8a7b97ebd04 --- /dev/null +++ b/src/Event/Value/Telemetry/HRTime.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Telemetry; + +use function sprintf; +use PHPUnit\Event\InvalidArgumentException; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class HRTime +{ + private int $seconds; + private int $nanoseconds; + + /** + * @throws InvalidArgumentException + */ + public static function fromSecondsAndNanoseconds(int $seconds, int $nanoseconds): self + { + return new self( + $seconds, + $nanoseconds, + ); + } + + /** + * @throws InvalidArgumentException + */ + private function __construct(int $seconds, int $nanoseconds) + { + $this->ensureNotNegative($seconds, 'seconds'); + $this->ensureNotNegative($nanoseconds, 'nanoseconds'); + $this->ensureNanoSecondsInRange($nanoseconds); + + $this->seconds = $seconds; + $this->nanoseconds = $nanoseconds; + } + + public function seconds(): int + { + return $this->seconds; + } + + public function nanoseconds(): int + { + return $this->nanoseconds; + } + + public function duration(self $start): Duration + { + $seconds = $this->seconds - $start->seconds(); + $nanoseconds = $this->nanoseconds - $start->nanoseconds(); + + if ($nanoseconds < 0) { + $seconds--; + + $nanoseconds += 1000000000; + } + + if ($seconds < 0) { + return Duration::fromSecondsAndNanoseconds(0, 0); + } + + return Duration::fromSecondsAndNanoseconds( + $seconds, + $nanoseconds, + ); + } + + /** + * @throws InvalidArgumentException + */ + private function ensureNotNegative(int $value, string $type): void + { + if ($value < 0) { + throw new InvalidArgumentException( + sprintf( + 'Value for %s must not be negative.', + $type, + ), + ); + } + } + + /** + * @throws InvalidArgumentException + */ + private function ensureNanoSecondsInRange(int $nanoseconds): void + { + if ($nanoseconds > 999999999) { + throw new InvalidArgumentException( + 'Value for nanoseconds must not be greater than 999999999.', + ); + } + } +} diff --git a/src/Event/Value/Telemetry/Info.php b/src/Event/Value/Telemetry/Info.php new file mode 100644 index 00000000000..a0d0a99f169 --- /dev/null +++ b/src/Event/Value/Telemetry/Info.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Telemetry; + +use function sprintf; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Info +{ + private Snapshot $current; + private Duration $durationSinceStart; + private MemoryUsage $memorySinceStart; + private Duration $durationSincePrevious; + private MemoryUsage $memorySincePrevious; + + public function __construct(Snapshot $current, Duration $durationSinceStart, MemoryUsage $memorySinceStart, Duration $durationSincePrevious, MemoryUsage $memorySincePrevious) + { + $this->current = $current; + $this->durationSinceStart = $durationSinceStart; + $this->memorySinceStart = $memorySinceStart; + $this->durationSincePrevious = $durationSincePrevious; + $this->memorySincePrevious = $memorySincePrevious; + } + + public function time(): HRTime + { + return $this->current->time(); + } + + public function memoryUsage(): MemoryUsage + { + return $this->current->memoryUsage(); + } + + public function peakMemoryUsage(): MemoryUsage + { + return $this->current->peakMemoryUsage(); + } + + public function durationSinceStart(): Duration + { + return $this->durationSinceStart; + } + + public function memoryUsageSinceStart(): MemoryUsage + { + return $this->memorySinceStart; + } + + public function durationSincePrevious(): Duration + { + return $this->durationSincePrevious; + } + + public function memoryUsageSincePrevious(): MemoryUsage + { + return $this->memorySincePrevious; + } + + public function garbageCollectorStatus(): GarbageCollectorStatus + { + return $this->current->garbageCollectorStatus(); + } + + public function asString(): string + { + return sprintf( + '[%s / %s] [%d bytes]', + $this->durationSinceStart()->asString(), + $this->durationSincePrevious()->asString(), + $this->peakMemoryUsage()->bytes(), + ); + } +} diff --git a/src/Event/Value/Telemetry/MemoryMeter.php b/src/Event/Value/Telemetry/MemoryMeter.php new file mode 100644 index 00000000000..4d116ff3ef3 --- /dev/null +++ b/src/Event/Value/Telemetry/MemoryMeter.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Telemetry; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface MemoryMeter +{ + public function memoryUsage(): MemoryUsage; + + public function peakMemoryUsage(): MemoryUsage; +} diff --git a/src/Event/Value/Telemetry/MemoryUsage.php b/src/Event/Value/Telemetry/MemoryUsage.php new file mode 100644 index 00000000000..8ace32ea0bf --- /dev/null +++ b/src/Event/Value/Telemetry/MemoryUsage.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class MemoryUsage +{ + private int $bytes; + + public static function fromBytes(int $bytes): self + { + return new self($bytes); + } + + private function __construct(int $bytes) + { + $this->bytes = $bytes; + } + + public function bytes(): int + { + return $this->bytes; + } + + public function diff(self $other): self + { + return self::fromBytes($this->bytes - $other->bytes); + } +} diff --git a/src/Event/Value/Telemetry/Snapshot.php b/src/Event/Value/Telemetry/Snapshot.php new file mode 100644 index 00000000000..0f00f5d8054 --- /dev/null +++ b/src/Event/Value/Telemetry/Snapshot.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Snapshot +{ + private HRTime $time; + private MemoryUsage $memoryUsage; + private MemoryUsage $peakMemoryUsage; + private GarbageCollectorStatus $garbageCollectorStatus; + + public function __construct(HRTime $time, MemoryUsage $memoryUsage, MemoryUsage $peakMemoryUsage, GarbageCollectorStatus $garbageCollectorStatus) + { + $this->time = $time; + $this->memoryUsage = $memoryUsage; + $this->peakMemoryUsage = $peakMemoryUsage; + $this->garbageCollectorStatus = $garbageCollectorStatus; + } + + public function time(): HRTime + { + return $this->time; + } + + public function memoryUsage(): MemoryUsage + { + return $this->memoryUsage; + } + + public function peakMemoryUsage(): MemoryUsage + { + return $this->peakMemoryUsage; + } + + public function garbageCollectorStatus(): GarbageCollectorStatus + { + return $this->garbageCollectorStatus; + } +} diff --git a/src/Event/Value/Telemetry/StopWatch.php b/src/Event/Value/Telemetry/StopWatch.php new file mode 100644 index 00000000000..07ce5227faa --- /dev/null +++ b/src/Event/Value/Telemetry/StopWatch.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Telemetry; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface StopWatch +{ + public function current(): HRTime; +} diff --git a/src/Event/Value/Telemetry/System.php b/src/Event/Value/Telemetry/System.php new file mode 100644 index 00000000000..0a178363e55 --- /dev/null +++ b/src/Event/Value/Telemetry/System.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Telemetry; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class System +{ + private StopWatch $stopWatch; + private MemoryMeter $memoryMeter; + private GarbageCollectorStatusProvider $garbageCollectorStatusProvider; + + public function __construct(StopWatch $stopWatch, MemoryMeter $memoryMeter, GarbageCollectorStatusProvider $garbageCollectorStatusProvider) + { + $this->stopWatch = $stopWatch; + $this->memoryMeter = $memoryMeter; + $this->garbageCollectorStatusProvider = $garbageCollectorStatusProvider; + } + + public function snapshot(): Snapshot + { + return new Snapshot( + $this->stopWatch->current(), + $this->memoryMeter->memoryUsage(), + $this->memoryMeter->peakMemoryUsage(), + $this->garbageCollectorStatusProvider->status(), + ); + } +} diff --git a/src/Event/Value/Telemetry/SystemGarbageCollectorStatusProvider.php b/src/Event/Value/Telemetry/SystemGarbageCollectorStatusProvider.php new file mode 100644 index 00000000000..3f33d690d0e --- /dev/null +++ b/src/Event/Value/Telemetry/SystemGarbageCollectorStatusProvider.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Telemetry; + +use function gc_status; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class SystemGarbageCollectorStatusProvider implements GarbageCollectorStatusProvider +{ + public function status(): GarbageCollectorStatus + { + $status = gc_status(); + + return new GarbageCollectorStatus( + $status['runs'], + $status['collected'], + $status['threshold'], + $status['roots'], + $status['application_time'], + $status['collector_time'], + $status['destructor_time'], + $status['free_time'], + $status['running'], + $status['protected'], + $status['full'], + $status['buffer_size'], + ); + } +} diff --git a/src/Event/Value/Telemetry/SystemMemoryMeter.php b/src/Event/Value/Telemetry/SystemMemoryMeter.php new file mode 100644 index 00000000000..16d895a949b --- /dev/null +++ b/src/Event/Value/Telemetry/SystemMemoryMeter.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Telemetry; + +use function memory_get_peak_usage; +use function memory_get_usage; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class SystemMemoryMeter implements MemoryMeter +{ + public function memoryUsage(): MemoryUsage + { + return MemoryUsage::fromBytes(memory_get_usage()); + } + + public function peakMemoryUsage(): MemoryUsage + { + return MemoryUsage::fromBytes(memory_get_peak_usage()); + } +} diff --git a/src/Event/Value/Telemetry/SystemStopWatch.php b/src/Event/Value/Telemetry/SystemStopWatch.php new file mode 100644 index 00000000000..a57c1032407 --- /dev/null +++ b/src/Event/Value/Telemetry/SystemStopWatch.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Telemetry; + +use function hrtime; +use PHPUnit\Event\InvalidArgumentException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class SystemStopWatch implements StopWatch +{ + /** + * @throws InvalidArgumentException + */ + public function current(): HRTime + { + return HRTime::fromSecondsAndNanoseconds(...hrtime()); + } +} diff --git a/src/Event/Value/Telemetry/SystemStopWatchWithOffset.php b/src/Event/Value/Telemetry/SystemStopWatchWithOffset.php new file mode 100644 index 00000000000..d27fd98c14b --- /dev/null +++ b/src/Event/Value/Telemetry/SystemStopWatchWithOffset.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Telemetry; + +use function hrtime; +use PHPUnit\Event\InvalidArgumentException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @codeCoverageIgnore + */ +final class SystemStopWatchWithOffset implements StopWatch +{ + private ?HRTime $offset; + + public function __construct(HRTime $offset) + { + $this->offset = $offset; + } + + /** + * @throws InvalidArgumentException + */ + public function current(): HRTime + { + if ($this->offset !== null) { + $offset = $this->offset; + + $this->offset = null; + + return $offset; + } + + return HRTime::fromSecondsAndNanoseconds(...hrtime()); + } +} diff --git a/src/Event/Value/Test/Issue/DirectTrigger.php b/src/Event/Value/Test/Issue/DirectTrigger.php new file mode 100644 index 00000000000..bbb3c66a890 --- /dev/null +++ b/src/Event/Value/Test/Issue/DirectTrigger.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code\IssueTrigger; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class DirectTrigger extends IssueTrigger +{ + /** + * Your own code triggers an issue in third-party code. + */ + public function isDirect(): true + { + return true; + } + + public function asString(): string + { + return 'issue triggered by first-party code calling into third-party code'; + } +} diff --git a/src/Event/Value/Test/Issue/IndirectTrigger.php b/src/Event/Value/Test/Issue/IndirectTrigger.php new file mode 100644 index 00000000000..81f76b45e9a --- /dev/null +++ b/src/Event/Value/Test/Issue/IndirectTrigger.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code\IssueTrigger; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IndirectTrigger extends IssueTrigger +{ + /** + * Third-party code triggers an issue either in your own code or in third-party code. + */ + public function isIndirect(): true + { + return true; + } + + public function asString(): string + { + return 'issue triggered by third-party code'; + } +} diff --git a/src/Event/Value/Test/Issue/IssueTrigger.php b/src/Event/Value/Test/Issue/IssueTrigger.php new file mode 100644 index 00000000000..93e42c72950 --- /dev/null +++ b/src/Event/Value/Test/Issue/IssueTrigger.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code\IssueTrigger; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +abstract class IssueTrigger +{ + public static function test(): TestTrigger + { + return new TestTrigger; + } + + public static function self(): SelfTrigger + { + return new SelfTrigger; + } + + public static function direct(): DirectTrigger + { + return new DirectTrigger; + } + + public static function indirect(): IndirectTrigger + { + return new IndirectTrigger; + } + + public static function unknown(): UnknownTrigger + { + return new UnknownTrigger; + } + + final private function __construct() + { + } + + /** + * Your test code triggers an issue. + * + * @phpstan-assert-if-true TestTrigger $this + */ + public function isTest(): bool + { + return false; + } + + /** + * Your own code triggers an issue in your own code. + * + * @phpstan-assert-if-true SelfTrigger $this + */ + public function isSelf(): bool + { + return false; + } + + /** + * Your own code triggers an issue in third-party code. + * + * @phpstan-assert-if-true DirectTrigger $this + */ + public function isDirect(): bool + { + return false; + } + + /** + * Third-party code triggers an issue either in your own code or in third-party code. + * + * @phpstan-assert-if-true IndirectTrigger $this + */ + public function isIndirect(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true UnknownTrigger $this + */ + public function isUnknown(): bool + { + return false; + } + + abstract public function asString(): string; +} diff --git a/src/Event/Value/Test/Issue/SelfTrigger.php b/src/Event/Value/Test/Issue/SelfTrigger.php new file mode 100644 index 00000000000..e569e72f578 --- /dev/null +++ b/src/Event/Value/Test/Issue/SelfTrigger.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code\IssueTrigger; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class SelfTrigger extends IssueTrigger +{ + /** + * Your own code triggers an issue in your own code. + */ + public function isSelf(): true + { + return true; + } + + public function asString(): string + { + return 'issue triggered by first-party code calling into first-party code'; + } +} diff --git a/src/Event/Value/Test/Issue/TestTrigger.php b/src/Event/Value/Test/Issue/TestTrigger.php new file mode 100644 index 00000000000..4768baca6bd --- /dev/null +++ b/src/Event/Value/Test/Issue/TestTrigger.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code\IssueTrigger; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class TestTrigger extends IssueTrigger +{ + /** + * Your test code triggers an issue. + */ + public function isTest(): true + { + return true; + } + + public function asString(): string + { + return 'issue triggered by test code'; + } +} diff --git a/src/Event/Value/Test/Issue/UnknownTrigger.php b/src/Event/Value/Test/Issue/UnknownTrigger.php new file mode 100644 index 00000000000..61f81148929 --- /dev/null +++ b/src/Event/Value/Test/Issue/UnknownTrigger.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code\IssueTrigger; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class UnknownTrigger extends IssueTrigger +{ + public function isUnknown(): true + { + return true; + } + + public function asString(): string + { + return 'unknown if issue was triggered in first-party code or third-party code'; + } +} diff --git a/src/Event/Value/Test/Phpt.php b/src/Event/Value/Test/Phpt.php new file mode 100644 index 00000000000..65a3aec8268 --- /dev/null +++ b/src/Event/Value/Test/Phpt.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Phpt extends Test +{ + public function isPhpt(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function id(): string + { + return $this->file(); + } + + /** + * @return non-empty-string + */ + public function name(): string + { + return $this->file(); + } +} diff --git a/src/Event/Value/Test/Test.php b/src/Event/Value/Test/Test.php new file mode 100644 index 00000000000..43ed73eb7b6 --- /dev/null +++ b/src/Event/Value/Test/Test.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Test +{ + /** + * @var non-empty-string + */ + private string $file; + + /** + * @param non-empty-string $file + */ + public function __construct(string $file) + { + $this->file = $file; + } + + /** + * @return non-empty-string + */ + public function file(): string + { + return $this->file; + } + + /** + * @phpstan-assert-if-true TestMethod $this + */ + public function isTestMethod(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Phpt $this + */ + public function isPhpt(): bool + { + return false; + } + + /** + * @return non-empty-string + */ + abstract public function id(): string; + + /** + * @return non-empty-string + */ + abstract public function name(): string; +} diff --git a/src/Event/Value/Test/TestCollection.php b/src/Event/Value/Test/TestCollection.php new file mode 100644 index 00000000000..1924b64b614 --- /dev/null +++ b/src/Event/Value/Test/TestCollection.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @template-implements IteratorAggregate + * + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $tests; + + /** + * @param list $tests + */ + public static function fromArray(array $tests): self + { + return new self(...$tests); + } + + private function __construct(Test ...$tests) + { + $this->tests = $tests; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->tests; + } + + public function count(): int + { + return count($this->tests); + } + + public function getIterator(): TestCollectionIterator + { + return new TestCollectionIterator($this); + } +} diff --git a/src/Event/Value/Test/TestCollectionIterator.php b/src/Event/Value/Test/TestCollectionIterator.php new file mode 100644 index 00000000000..7c96f3409e4 --- /dev/null +++ b/src/Event/Value/Test/TestCollectionIterator.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +use function count; +use Iterator; + +/** + * @template-implements Iterator + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class TestCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $tests; + private int $position = 0; + + public function __construct(TestCollection $tests) + { + $this->tests = $tests->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->tests); + } + + public function key(): int + { + return $this->position; + } + + public function current(): Test + { + return $this->tests[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/src/Event/Value/Test/TestData/DataFromDataProvider.php b/src/Event/Value/Test/TestData/DataFromDataProvider.php new file mode 100644 index 00000000000..981fd9e4f65 --- /dev/null +++ b/src/Event/Value/Test/TestData/DataFromDataProvider.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestData; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DataFromDataProvider extends TestData +{ + private int|string $dataSetName; + private string $dataAsStringForResultOutput; + + public static function from(int|string $dataSetName, string $data, string $dataAsStringForResultOutput): self + { + return new self($dataSetName, $data, $dataAsStringForResultOutput); + } + + protected function __construct(int|string $dataSetName, string $data, string $dataAsStringForResultOutput) + { + $this->dataSetName = $dataSetName; + $this->dataAsStringForResultOutput = $dataAsStringForResultOutput; + + parent::__construct($data); + } + + public function dataSetName(): int|string + { + return $this->dataSetName; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function dataAsStringForResultOutput(): string + { + return $this->dataAsStringForResultOutput; + } + + public function isFromDataProvider(): true + { + return true; + } +} diff --git a/src/Event/Value/Test/TestData/DataFromTestDependency.php b/src/Event/Value/Test/TestData/DataFromTestDependency.php new file mode 100644 index 00000000000..9fbf17ebf43 --- /dev/null +++ b/src/Event/Value/Test/TestData/DataFromTestDependency.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestData; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DataFromTestDependency extends TestData +{ + public static function from(string $data): self + { + return new self($data); + } + + public function isFromTestDependency(): true + { + return true; + } +} diff --git a/src/Event/Value/Test/TestData/TestData.php b/src/Event/Value/Test/TestData/TestData.php new file mode 100644 index 00000000000..893444806c2 --- /dev/null +++ b/src/Event/Value/Test/TestData/TestData.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestData; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class TestData +{ + private string $data; + + protected function __construct(string $data) + { + $this->data = $data; + } + + public function data(): string + { + return $this->data; + } + + /** + * @phpstan-assert-if-true DataFromDataProvider $this + */ + public function isFromDataProvider(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true DataFromTestDependency $this + */ + public function isFromTestDependency(): bool + { + return false; + } +} diff --git a/src/Event/Value/Test/TestData/TestDataCollection.php b/src/Event/Value/Test/TestData/TestDataCollection.php new file mode 100644 index 00000000000..460f0c0c59b --- /dev/null +++ b/src/Event/Value/Test/TestData/TestDataCollection.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestData; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @template-implements IteratorAggregate + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestDataCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $data; + private ?DataFromDataProvider $fromDataProvider; + + /** + * @param list $data + */ + public static function fromArray(array $data): self + { + return new self(...$data); + } + + private function __construct(TestData ...$data) + { + $fromDataProvider = null; + + foreach ($data as $_data) { + if ($_data->isFromDataProvider()) { + $fromDataProvider = $_data; + } + } + + $this->data = $data; + $this->fromDataProvider = $fromDataProvider; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->data; + } + + public function count(): int + { + return count($this->data); + } + + /** + * @phpstan-assert-if-true !null $this->fromDataProvider + */ + public function hasDataFromDataProvider(): bool + { + return $this->fromDataProvider !== null; + } + + /** + * @throws NoDataSetFromDataProviderException + */ + public function dataFromDataProvider(): DataFromDataProvider + { + if (!$this->hasDataFromDataProvider()) { + throw new NoDataSetFromDataProviderException; + } + + return $this->fromDataProvider; + } + + public function getIterator(): TestDataCollectionIterator + { + return new TestDataCollectionIterator($this); + } +} diff --git a/src/Event/Value/Test/TestData/TestDataCollectionIterator.php b/src/Event/Value/Test/TestData/TestDataCollectionIterator.php new file mode 100644 index 00000000000..aebed66ca17 --- /dev/null +++ b/src/Event/Value/Test/TestData/TestDataCollectionIterator.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestData; + +use function count; +use Iterator; + +/** + * @template-implements Iterator + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class TestDataCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $data; + private int $position = 0; + + public function __construct(TestDataCollection $data) + { + $this->data = $data->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->data); + } + + public function key(): int + { + return $this->position; + } + + public function current(): TestData + { + return $this->data[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/src/Event/Value/Test/TestDox.php b/src/Event/Value/Test/TestDox.php new file mode 100644 index 00000000000..53604dbfc65 --- /dev/null +++ b/src/Event/Value/Test/TestDox.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestDox +{ + private string $prettifiedClassName; + private string $prettifiedMethodName; + private string $prettifiedAndColorizedMethodName; + + public function __construct(string $prettifiedClassName, string $prettifiedMethodName, string $prettifiedAndColorizedMethodName) + { + $this->prettifiedClassName = $prettifiedClassName; + $this->prettifiedMethodName = $prettifiedMethodName; + $this->prettifiedAndColorizedMethodName = $prettifiedAndColorizedMethodName; + } + + public function prettifiedClassName(): string + { + return $this->prettifiedClassName; + } + + public function prettifiedMethodName(bool $colorize = false): string + { + if ($colorize) { + return $this->prettifiedAndColorizedMethodName; + } + + return $this->prettifiedMethodName; + } +} diff --git a/src/Event/Value/Test/TestDoxBuilder.php b/src/Event/Value/Test/TestDoxBuilder.php new file mode 100644 index 00000000000..532dde00378 --- /dev/null +++ b/src/Event/Value/Test/TestDoxBuilder.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +use PHPUnit\Framework\TestCase; +use PHPUnit\Logging\TestDox\NamePrettifier; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestDoxBuilder +{ + private static ?NamePrettifier $namePrettifier = null; + + public static function fromTestCase(TestCase $testCase): TestDox + { + $prettifier = self::namePrettifier(); + + return new TestDox( + $prettifier->prettifyTestClassName($testCase::class), + $prettifier->prettifyTestCase($testCase, false), + $prettifier->prettifyTestCase($testCase, true), + ); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public static function fromClassNameAndMethodName(string $className, string $methodName): TestDox + { + $prettifier = self::namePrettifier(); + + $prettifiedMethodName = $prettifier->prettifyTestMethodName($methodName); + + return new TestDox( + $prettifier->prettifyTestClassName($className), + $prettifiedMethodName, + $prettifiedMethodName, + ); + } + + private static function namePrettifier(): NamePrettifier + { + if (self::$namePrettifier === null) { + self::$namePrettifier = new NamePrettifier; + } + + return self::$namePrettifier; + } +} diff --git a/src/Event/Value/Test/TestMethod.php b/src/Event/Value/Test/TestMethod.php new file mode 100644 index 00000000000..4c972645436 --- /dev/null +++ b/src/Event/Value/Test/TestMethod.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +use function is_int; +use function sprintf; +use PHPUnit\Event\TestData\TestDataCollection; +use PHPUnit\Metadata\MetadataCollection; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestMethod extends Test +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @var non-negative-int + */ + private int $line; + private TestDox $testDox; + private MetadataCollection $metadata; + private TestDataCollection $testData; + + /** + * @param class-string $className + * @param non-empty-string $methodName + * @param non-empty-string $file + * @param non-negative-int $line + */ + public function __construct(string $className, string $methodName, string $file, int $line, TestDox $testDox, MetadataCollection $metadata, TestDataCollection $testData) + { + parent::__construct($file); + + $this->className = $className; + $this->methodName = $methodName; + $this->line = $line; + $this->testDox = $testDox; + $this->metadata = $metadata; + $this->testData = $testData; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } + + /** + * @return non-negative-int + */ + public function line(): int + { + return $this->line; + } + + public function testDox(): TestDox + { + return $this->testDox; + } + + public function metadata(): MetadataCollection + { + return $this->metadata; + } + + public function testData(): TestDataCollection + { + return $this->testData; + } + + public function isTestMethod(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function id(): string + { + $buffer = $this->className . '::' . $this->methodName; + + if ($this->testData()->hasDataFromDataProvider()) { + $buffer .= '#' . $this->testData->dataFromDataProvider()->dataSetName(); + } + + return $buffer; + } + + /** + * @return non-empty-string + */ + public function nameWithClass(): string + { + return $this->className . '::' . $this->name(); + } + + /** + * @return non-empty-string + */ + public function name(): string + { + if (!$this->testData->hasDataFromDataProvider()) { + return $this->methodName; + } + + $dataSetName = $this->testData->dataFromDataProvider()->dataSetName(); + + if (is_int($dataSetName)) { + $dataSetName = sprintf( + ' with data set #%d', + $dataSetName, + ); + } else { + $dataSetName = sprintf( + ' with data set "%s"', + $dataSetName, + ); + } + + return $this->methodName . $dataSetName; + } +} diff --git a/src/Event/Value/Test/TestMethodBuilder.php b/src/Event/Value/Test/TestMethodBuilder.php new file mode 100644 index 00000000000..dc1a32ef8ab --- /dev/null +++ b/src/Event/Value/Test/TestMethodBuilder.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +use function is_numeric; +use PHPUnit\Event\TestData\DataFromDataProvider; +use PHPUnit\Event\TestData\DataFromTestDependency; +use PHPUnit\Event\TestData\TestDataCollection; +use PHPUnit\Framework\TestCase; +use PHPUnit\Metadata\Parser\Registry as MetadataRegistry; +use PHPUnit\Util\Exporter; +use PHPUnit\Util\Reflection; +use PHPUnit\Util\Test as TestUtil; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestMethodBuilder +{ + public static function fromTestCase(TestCase $testCase, bool $useTestCaseForTestDox = true): TestMethod + { + $methodName = $testCase->name(); + $location = Reflection::sourceLocationFor($testCase::class, $methodName); + + if ($useTestCaseForTestDox) { + $testDox = TestDoxBuilder::fromTestCase($testCase); + } else { + $testDox = TestDoxBuilder::fromClassNameAndMethodName($testCase::class, $testCase->name()); + } + + return new TestMethod( + $testCase::class, + $methodName, + $location['file'], + $location['line'], + $testDox, + MetadataRegistry::parser()->forClassAndMethod($testCase::class, $methodName), + self::dataFor($testCase), + ); + } + + /** + * @throws NoTestCaseObjectOnCallStackException + */ + public static function fromCallStack(): TestMethod + { + return TestUtil::currentTestCase()->valueObjectForEvents(); + } + + private static function dataFor(TestCase $testCase): TestDataCollection + { + $testData = []; + + if ($testCase->usesDataProvider()) { + $dataSetName = $testCase->dataName(); + + if (is_numeric($dataSetName)) { + $dataSetName = (int) $dataSetName; + } + + $testData[] = DataFromDataProvider::from( + $dataSetName, + Exporter::shortenedRecursiveExport($testCase->providedData()), + $testCase->dataSetAsStringWithData(), + ); + } + + if ($testCase->hasDependencyInput()) { + $testData[] = DataFromTestDependency::from( + Exporter::shortenedRecursiveExport($testCase->dependencyInput()), + ); + } + + return TestDataCollection::fromArray($testData); + } +} diff --git a/src/Event/Value/TestSuite/TestSuite.php b/src/Event/Value/TestSuite/TestSuite.php new file mode 100644 index 00000000000..783de57bd24 --- /dev/null +++ b/src/Event/Value/TestSuite/TestSuite.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use PHPUnit\Event\Code\TestCollection; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class TestSuite +{ + /** + * @var non-empty-string + */ + private string $name; + private int $count; + private TestCollection $tests; + + /** + * @param non-empty-string $name + */ + public function __construct(string $name, int $size, TestCollection $tests) + { + $this->name = $name; + $this->count = $size; + $this->tests = $tests; + } + + /** + * @return non-empty-string + */ + public function name(): string + { + return $this->name; + } + + public function count(): int + { + return $this->count; + } + + public function tests(): TestCollection + { + return $this->tests; + } + + /** + * @phpstan-assert-if-true TestSuiteWithName $this + */ + public function isWithName(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true TestSuiteForTestClass $this + */ + public function isForTestClass(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true TestSuiteForTestMethodWithDataProvider $this + */ + public function isForTestMethodWithDataProvider(): bool + { + return false; + } +} diff --git a/src/Event/Value/TestSuite/TestSuiteBuilder.php b/src/Event/Value/TestSuite/TestSuiteBuilder.php new file mode 100644 index 00000000000..3192636baa9 --- /dev/null +++ b/src/Event/Value/TestSuite/TestSuiteBuilder.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use function assert; +use function class_exists; +use function count; +use function explode; +use function method_exists; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Code\TestCollection; +use PHPUnit\Event\RuntimeException; +use PHPUnit\Framework\DataProviderTestSuite; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestSuite as FrameworkTestSuite; +use PHPUnit\Runner\Phpt\TestCase as PhptTestCase; +use ReflectionClass; +use ReflectionMethod; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteBuilder +{ + /** + * @throws RuntimeException + */ + public static function from(FrameworkTestSuite $testSuite): TestSuite + { + $tests = []; + + self::process($testSuite, $tests); + + if ($testSuite instanceof DataProviderTestSuite) { + assert(count(explode('::', $testSuite->name())) === 2); + [$className, $methodName] = explode('::', $testSuite->name()); + + assert(class_exists($className)); + assert($methodName !== '' && method_exists($className, $methodName)); + + $reflector = new ReflectionMethod($className, $methodName); + + $file = $reflector->getFileName(); + $line = $reflector->getStartLine(); + + assert($file !== false); + assert($line !== false); + + return new TestSuiteForTestMethodWithDataProvider( + $testSuite->name(), + $testSuite->count(), + TestCollection::fromArray($tests), + $className, + $methodName, + $file, + $line, + ); + } + + if ($testSuite->isForTestClass()) { + $testClassName = $testSuite->name(); + + assert(class_exists($testClassName)); + + $reflector = new ReflectionClass($testClassName); + + $file = $reflector->getFileName(); + $line = $reflector->getStartLine(); + + assert($file !== false); + assert($line !== false); + + return new TestSuiteForTestClass( + $testClassName, + $testSuite->count(), + TestCollection::fromArray($tests), + $file, + $line, + ); + } + + return new TestSuiteWithName( + $testSuite->name(), + $testSuite->count(), + TestCollection::fromArray($tests), + ); + } + + /** + * @param list $tests + */ + private static function process(FrameworkTestSuite $testSuite, array &$tests): void + { + foreach ($testSuite->getIterator() as $test) { + if ($test instanceof FrameworkTestSuite) { + self::process($test, $tests); + + continue; + } + + if ($test instanceof TestCase || $test instanceof PhptTestCase) { + $tests[] = $test->valueObjectForEvents(); + } + } + } +} diff --git a/src/Event/Value/TestSuite/TestSuiteForTestClass.php b/src/Event/Value/TestSuite/TestSuiteForTestClass.php new file mode 100644 index 00000000000..34ad9d5eac3 --- /dev/null +++ b/src/Event/Value/TestSuite/TestSuiteForTestClass.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use PHPUnit\Event\Code\TestCollection; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteForTestClass extends TestSuite +{ + /** + * @var class-string + */ + private string $className; + private string $file; + private int $line; + + /** + * @param class-string $name + */ + public function __construct(string $name, int $size, TestCollection $tests, string $file, int $line) + { + parent::__construct($name, $size, $tests); + + $this->className = $name; + $this->file = $file; + $this->line = $line; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + public function file(): string + { + return $this->file; + } + + public function line(): int + { + return $this->line; + } + + public function isForTestClass(): true + { + return true; + } +} diff --git a/src/Event/Value/TestSuite/TestSuiteForTestMethodWithDataProvider.php b/src/Event/Value/TestSuite/TestSuiteForTestMethodWithDataProvider.php new file mode 100644 index 00000000000..67a94391dd5 --- /dev/null +++ b/src/Event/Value/TestSuite/TestSuiteForTestMethodWithDataProvider.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use PHPUnit\Event\Code\TestCollection; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteForTestMethodWithDataProvider extends TestSuite +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + private string $file; + private int $line; + + /** + * @param non-empty-string $name + * @param class-string $className + * @param non-empty-string $methodName + */ + public function __construct(string $name, int $size, TestCollection $tests, string $className, string $methodName, string $file, int $line) + { + parent::__construct($name, $size, $tests); + + $this->className = $className; + $this->methodName = $methodName; + $this->file = $file; + $this->line = $line; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } + + public function file(): string + { + return $this->file; + } + + public function line(): int + { + return $this->line; + } + + public function isForTestMethodWithDataProvider(): true + { + return true; + } +} diff --git a/src/Event/Value/TestSuite/TestSuiteWithName.php b/src/Event/Value/TestSuite/TestSuiteWithName.php new file mode 100644 index 00000000000..4823fb26333 --- /dev/null +++ b/src/Event/Value/TestSuite/TestSuiteWithName.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteWithName extends TestSuite +{ + public function isWithName(): true + { + return true; + } +} diff --git a/src/Event/Value/Throwable.php b/src/Event/Value/Throwable.php new file mode 100644 index 00000000000..e48cf1a3ac6 --- /dev/null +++ b/src/Event/Value/Throwable.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +use const PHP_EOL; +use PHPUnit\Event\NoPreviousThrowableException; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Throwable +{ + /** + * @var class-string + */ + private string $className; + private string $message; + private string $description; + private string $stackTrace; + private ?Throwable $previous; + + /** + * @param class-string $className + */ + public function __construct(string $className, string $message, string $description, string $stackTrace, ?self $previous) + { + $this->className = $className; + $this->message = $message; + $this->description = $description; + $this->stackTrace = $stackTrace; + $this->previous = $previous; + } + + /** + * @throws NoPreviousThrowableException + */ + public function asString(): string + { + $buffer = $this->description(); + + if ($this->stackTrace() !== '') { + $buffer .= PHP_EOL . $this->stackTrace(); + } + + if ($this->hasPrevious()) { + $buffer .= PHP_EOL . 'Caused by' . PHP_EOL . $this->previous()->asString(); + } + + return $buffer; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + public function message(): string + { + return $this->message; + } + + public function description(): string + { + return $this->description; + } + + public function stackTrace(): string + { + return $this->stackTrace; + } + + /** + * @phpstan-assert-if-true !null $this->previous + */ + public function hasPrevious(): bool + { + return $this->previous !== null; + } + + /** + * @throws NoPreviousThrowableException + */ + public function previous(): self + { + if ($this->previous === null) { + throw new NoPreviousThrowableException; + } + + return $this->previous; + } +} diff --git a/src/Event/Value/ThrowableBuilder.php b/src/Event/Value/ThrowableBuilder.php new file mode 100644 index 00000000000..7db4beea721 --- /dev/null +++ b/src/Event/Value/ThrowableBuilder.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +use PHPUnit\Event\NoPreviousThrowableException; +use PHPUnit\Framework\Exception; +use PHPUnit\Util\Filter; +use PHPUnit\Util\ThrowableToStringMapper; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ThrowableBuilder +{ + /** + * @throws Exception + * @throws NoPreviousThrowableException + */ + public static function from(\Throwable $t): Throwable + { + $previous = $t->getPrevious(); + + if ($previous !== null) { + $previous = self::from($previous); + } + + return new Throwable( + $t::class, + $t->getMessage(), + ThrowableToStringMapper::map($t), + Filter::stackTraceFromThrowableAsString($t, false), + $previous, + ); + } +} diff --git a/src/Exception.php b/src/Exception.php index 4e7c33353b8..7a8302e20d0 100644 --- a/src/Exception.php +++ b/src/Exception.php @@ -12,7 +12,7 @@ use Throwable; /** - * @internal This class is not covered by the backward compatibility promise for PHPUnit + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ interface Exception extends Throwable { diff --git a/src/Framework/Assert.php b/src/Framework/Assert.php index c5634a753b3..5692d8fd722 100644 --- a/src/Framework/Assert.php +++ b/src/Framework/Assert.php @@ -9,38 +9,18 @@ */ namespace PHPUnit\Framework; -use const DEBUG_BACKTRACE_IGNORE_ARGS; -use const PHP_EOL; -use function array_shift; -use function array_unshift; -use function assert; +use function array_combine; +use function array_intersect_key; use function class_exists; use function count; -use function debug_backtrace; -use function explode; use function file_get_contents; -use function func_get_args; -use function implode; use function interface_exists; -use function is_array; use function is_bool; -use function is_int; -use function is_iterable; -use function is_object; -use function is_string; -use function preg_match; -use function preg_split; -use function sprintf; -use function strpos; use ArrayAccess; use Countable; -use DOMAttr; -use DOMDocument; -use DOMElement; +use Generator; use PHPUnit\Framework\Constraint\ArrayHasKey; use PHPUnit\Framework\Constraint\Callback; -use PHPUnit\Framework\Constraint\ClassHasAttribute; -use PHPUnit\Framework\Constraint\ClassHasStaticAttribute; use PHPUnit\Framework\Constraint\Constraint; use PHPUnit\Framework\Constraint\Count; use PHPUnit\Framework\Constraint\DirectoryExists; @@ -58,6 +38,7 @@ use PHPUnit\Framework\Constraint\IsInfinite; use PHPUnit\Framework\Constraint\IsInstanceOf; use PHPUnit\Framework\Constraint\IsJson; +use PHPUnit\Framework\Constraint\IsList; use PHPUnit\Framework\Constraint\IsNan; use PHPUnit\Framework\Constraint\IsNull; use PHPUnit\Framework\Constraint\IsReadable; @@ -70,412 +51,1011 @@ use PHPUnit\Framework\Constraint\LogicalNot; use PHPUnit\Framework\Constraint\LogicalOr; use PHPUnit\Framework\Constraint\LogicalXor; -use PHPUnit\Framework\Constraint\ObjectHasAttribute; +use PHPUnit\Framework\Constraint\ObjectEquals; +use PHPUnit\Framework\Constraint\ObjectHasProperty; use PHPUnit\Framework\Constraint\RegularExpression; use PHPUnit\Framework\Constraint\SameSize; use PHPUnit\Framework\Constraint\StringContains; use PHPUnit\Framework\Constraint\StringEndsWith; +use PHPUnit\Framework\Constraint\StringEqualsStringIgnoringLineEndings; use PHPUnit\Framework\Constraint\StringMatchesFormatDescription; use PHPUnit\Framework\Constraint\StringStartsWith; use PHPUnit\Framework\Constraint\TraversableContainsEqual; use PHPUnit\Framework\Constraint\TraversableContainsIdentical; use PHPUnit\Framework\Constraint\TraversableContainsOnly; -use PHPUnit\Util\Type; -use PHPUnit\Util\Xml; +use PHPUnit\Util\Xml\Loader as XmlLoader; +use PHPUnit\Util\Xml\XmlException; /** - * A set of assertion methods. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ abstract class Assert { + private static int $count = 0; + /** - * @var int + * Asserts that two arrays are equal while only considering a list of keys. + * + * @param array $expected + * @param array $actual + * @param non-empty-list $keysToBeConsidered + * + * @throws Exception + * @throws ExpectationFailedException */ - private static $count = 0; + final public static function assertArrayIsEqualToArrayOnlyConsideringListOfKeys(array $expected, array $actual, array $keysToBeConsidered, string $message = ''): void + { + $filteredExpected = []; + + foreach ($keysToBeConsidered as $key) { + if (isset($expected[$key])) { + $filteredExpected[$key] = $expected[$key]; + } + } + + $filteredActual = []; + + foreach ($keysToBeConsidered as $key) { + if (isset($actual[$key])) { + $filteredActual[$key] = $actual[$key]; + } + } + + self::assertEquals($filteredExpected, $filteredActual, $message); + } /** - * Asserts that an array has a specified key. + * Asserts that two arrays are equal while ignoring a list of keys. * - * @param int|string $key - * @param array|ArrayAccess $array + * @param array $expected + * @param array $actual + * @param non-empty-list $keysToBeIgnored * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws Exception + * @throws ExpectationFailedException */ - public static function assertArrayHasKey($key, $array, string $message = ''): void + final public static function assertArrayIsEqualToArrayIgnoringListOfKeys(array $expected, array $actual, array $keysToBeIgnored, string $message = ''): void { - if (!(is_int($key) || is_string($key))) { - throw InvalidArgumentException::create( - 1, - 'integer or string' - ); + foreach ($keysToBeIgnored as $key) { + unset($expected[$key], $actual[$key]); } - if (!(is_array($array) || $array instanceof ArrayAccess)) { - throw InvalidArgumentException::create( - 2, - 'array or ArrayAccess' - ); + self::assertEquals($expected, $actual, $message); + } + + /** + * Asserts that two arrays are identical while only considering a list of keys. + * + * @param array $expected + * @param array $actual + * @param non-empty-list $keysToBeConsidered + * + * @throws Exception + * @throws ExpectationFailedException + */ + final public static function assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys(array $expected, array $actual, array $keysToBeConsidered, string $message = ''): void + { + $keysToBeConsidered = array_combine($keysToBeConsidered, $keysToBeConsidered); + $expected = array_intersect_key($expected, $keysToBeConsidered); + $actual = array_intersect_key($actual, $keysToBeConsidered); + + self::assertSame($expected, $actual, $message); + } + + /** + * Asserts that two arrays are equal while ignoring a list of keys. + * + * @param array $expected + * @param array $actual + * @param non-empty-list $keysToBeIgnored + * + * @throws Exception + * @throws ExpectationFailedException + */ + final public static function assertArrayIsIdenticalToArrayIgnoringListOfKeys(array $expected, array $actual, array $keysToBeIgnored, string $message = ''): void + { + foreach ($keysToBeIgnored as $key) { + unset($expected[$key], $actual[$key]); } + self::assertSame($expected, $actual, $message); + } + + /** + * Asserts that an array has a specified key. + * + * @param array|ArrayAccess $array + * + * @throws Exception + * @throws ExpectationFailedException + */ + final public static function assertArrayHasKey(mixed $key, array|ArrayAccess $array, string $message = ''): void + { $constraint = new ArrayHasKey($key); - static::assertThat($array, $constraint, $message); + self::assertThat($array, $constraint, $message); } /** * Asserts that an array does not have a specified key. * - * @param int|string $key - * @param array|ArrayAccess $array + * @param array|ArrayAccess $array + * + * @throws Exception + * @throws ExpectationFailedException + */ + final public static function assertArrayNotHasKey(mixed $key, array|ArrayAccess $array, string $message = ''): void + { + $constraint = new LogicalNot( + new ArrayHasKey($key), + ); + + self::assertThat($array, $constraint, $message); + } + + /** + * @phpstan-assert list $array * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + */ + final public static function assertIsList(mixed $array, string $message = ''): void + { + self::assertThat( + $array, + new IsList, + $message, + ); + } + + /** + * Asserts that a haystack contains a needle. + * + * @param iterable $haystack + * * @throws Exception + * @throws ExpectationFailedException */ - public static function assertArrayNotHasKey($key, $array, string $message = ''): void + final public static function assertContains(mixed $needle, iterable $haystack, string $message = ''): void { - if (!(is_int($key) || is_string($key))) { - throw InvalidArgumentException::create( - 1, - 'integer or string' - ); - } + $constraint = new TraversableContainsIdentical($needle); - if (!(is_array($array) || $array instanceof ArrayAccess)) { - throw InvalidArgumentException::create( - 2, - 'array or ArrayAccess' - ); - } + self::assertThat($haystack, $constraint, $message); + } + + /** + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsEquals(mixed $needle, iterable $haystack, string $message = ''): void + { + $constraint = new TraversableContainsEqual($needle); + + self::assertThat($haystack, $constraint, $message); + } + /** + * Asserts that a haystack does not contain a needle. + * + * @param iterable $haystack + * + * @throws Exception + * @throws ExpectationFailedException + */ + final public static function assertNotContains(mixed $needle, iterable $haystack, string $message = ''): void + { $constraint = new LogicalNot( - new ArrayHasKey($key) + new TraversableContainsIdentical($needle), + ); + + self::assertThat($haystack, $constraint, $message); + } + + /** + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertNotContainsEquals(mixed $needle, iterable $haystack, string $message = ''): void + { + $constraint = new LogicalNot(new TraversableContainsEqual($needle)); + + self::assertThat($haystack, $constraint, $message); + } + + /** + * Asserts that a haystack contains only values of type array. + * + * @phpstan-assert iterable> $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyArray(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forNativeType( + NativeType::Array, + ), + $message, + ); + } + + /** + * Asserts that a haystack contains only values of type bool. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyBool(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forNativeType( + NativeType::Bool, + ), + $message, + ); + } + + /** + * Asserts that a haystack contains only values of type callable. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyCallable(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forNativeType( + NativeType::Callable, + ), + $message, + ); + } + + /** + * Asserts that a haystack contains only values of type float. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyFloat(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forNativeType( + NativeType::Float, + ), + $message, + ); + } + + /** + * Asserts that a haystack contains only values of type int. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyInt(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forNativeType( + NativeType::Int, + ), + $message, ); + } - static::assertThat($array, $constraint, $message); + /** + * Asserts that a haystack contains only values of type iterable. + * + * @phpstan-assert iterable> $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyIterable(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forNativeType( + NativeType::Iterable, + ), + $message, + ); + } + + /** + * Asserts that a haystack contains only values of type null. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyNull(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forNativeType( + NativeType::Null, + ), + $message, + ); + } + + /** + * Asserts that a haystack contains only values of type numeric. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyNumeric(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forNativeType( + NativeType::Numeric, + ), + $message, + ); + } + + /** + * Asserts that a haystack contains only values of type object. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyObject(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forNativeType( + NativeType::Object, + ), + $message, + ); + } + + /** + * Asserts that a haystack contains only values of type resource. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyResource(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forNativeType( + NativeType::Resource, + ), + $message, + ); + } + + /** + * Asserts that a haystack contains only values of type closed resource. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyClosedResource(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forNativeType( + NativeType::ClosedResource, + ), + $message, + ); + } + + /** + * Asserts that a haystack contains only values of type scalar. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyScalar(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forNativeType( + NativeType::Scalar, + ), + $message, + ); + } + + /** + * Asserts that a haystack contains only values of type string. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyString(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forNativeType( + NativeType::String, + ), + $message, + ); + } + + /** + * Asserts that a haystack contains only instances of a specified interface or class name. + * + * @template T + * + * @phpstan-assert iterable $haystack + * + * @param class-string $className + * @param iterable $haystack + * + * @throws Exception + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyInstancesOf(string $className, iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forClassOrInterface($className), + $message, + ); + } + + /** + * Asserts that a haystack does not contain only values of type array. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsNotOnlyArray(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + new LogicalNot( + TraversableContainsOnly::forNativeType( + NativeType::Array, + ), + ), + $message, + ); + } + + /** + * Asserts that a haystack does not contain only values of type bool. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsNotOnlyBool(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + new LogicalNot( + TraversableContainsOnly::forNativeType( + NativeType::Bool, + ), + ), + $message, + ); + } + + /** + * Asserts that a haystack does not contain only values of type callable. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsNotOnlyCallable(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + new LogicalNot( + TraversableContainsOnly::forNativeType( + NativeType::Callable, + ), + ), + $message, + ); + } + + /** + * Asserts that a haystack does not contain only values of type float. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsNotOnlyFloat(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + new LogicalNot( + TraversableContainsOnly::forNativeType( + NativeType::Float, + ), + ), + $message, + ); + } + + /** + * Asserts that a haystack does not contain only values of type int. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsNotOnlyInt(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + new LogicalNot( + TraversableContainsOnly::forNativeType( + NativeType::Int, + ), + ), + $message, + ); + } + + /** + * Asserts that a haystack does not contain only values of type iterable. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsNotOnlyIterable(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + new LogicalNot( + TraversableContainsOnly::forNativeType( + NativeType::Iterable, + ), + ), + $message, + ); + } + + /** + * Asserts that a haystack does not contain only values of type null. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsNotOnlyNull(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + new LogicalNot( + TraversableContainsOnly::forNativeType( + NativeType::Null, + ), + ), + $message, + ); + } + + /** + * Asserts that a haystack does not contain only values of type numeric. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsNotOnlyNumeric(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + new LogicalNot( + TraversableContainsOnly::forNativeType( + NativeType::Numeric, + ), + ), + $message, + ); + } + + /** + * Asserts that a haystack does not contain only values of type object. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsNotOnlyObject(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + new LogicalNot( + TraversableContainsOnly::forNativeType( + NativeType::Object, + ), + ), + $message, + ); } /** - * Asserts that a haystack contains a needle. + * Asserts that a haystack does not contain only values of type resource. + * + * @param iterable $haystack * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception */ - public static function assertContains($needle, iterable $haystack, string $message = ''): void - { - $constraint = new TraversableContainsIdentical($needle); - - static::assertThat($haystack, $constraint, $message); - } - - public static function assertContainsEquals($needle, iterable $haystack, string $message = ''): void + final public static function assertContainsNotOnlyResource(iterable $haystack, string $message = ''): void { - $constraint = new TraversableContainsEqual($needle); - - static::assertThat($haystack, $constraint, $message); + self::assertThat( + $haystack, + new LogicalNot( + TraversableContainsOnly::forNativeType( + NativeType::Resource, + ), + ), + $message, + ); } /** - * Asserts that a haystack does not contain a needle. + * Asserts that a haystack does not contain only values of type closed resource. + * + * @param iterable $haystack * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception */ - public static function assertNotContains($needle, iterable $haystack, string $message = ''): void + final public static function assertContainsNotOnlyClosedResource(iterable $haystack, string $message = ''): void { - $constraint = new LogicalNot( - new TraversableContainsIdentical($needle) + self::assertThat( + $haystack, + new LogicalNot( + TraversableContainsOnly::forNativeType( + NativeType::ClosedResource, + ), + ), + $message, ); - - static::assertThat($haystack, $constraint, $message); - } - - public static function assertNotContainsEquals($needle, iterable $haystack, string $message = ''): void - { - $constraint = new LogicalNot(new TraversableContainsEqual($needle)); - - static::assertThat($haystack, $constraint, $message); } /** - * Asserts that a haystack contains only values of a given type. + * Asserts that a haystack does not contain only values of type scalar. + * + * @param iterable $haystack * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertContainsOnly(string $type, iterable $haystack, ?bool $isNativeType = null, string $message = ''): void + final public static function assertContainsNotOnlyScalar(iterable $haystack, string $message = ''): void { - if ($isNativeType === null) { - $isNativeType = Type::isType($type); - } - - static::assertThat( + self::assertThat( $haystack, - new TraversableContainsOnly( - $type, - $isNativeType + new LogicalNot( + TraversableContainsOnly::forNativeType( + NativeType::Scalar, + ), ), - $message + $message, ); } /** - * Asserts that a haystack contains only instances of a given class name. + * Asserts that a haystack does not contain only values of type string. + * + * @param iterable $haystack * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertContainsOnlyInstancesOf(string $className, iterable $haystack, string $message = ''): void + final public static function assertContainsNotOnlyString(iterable $haystack, string $message = ''): void { - static::assertThat( + self::assertThat( $haystack, - new TraversableContainsOnly( - $className, - false + new LogicalNot( + TraversableContainsOnly::forNativeType( + NativeType::String, + ), ), - $message + $message, ); } /** - * Asserts that a haystack does not contain only values of a given type. + * Asserts that a haystack does not contain only instances of a specified interface or class name. + * + * @param class-string $className + * @param iterable $haystack * + * @throws Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertNotContainsOnly(string $type, iterable $haystack, ?bool $isNativeType = null, string $message = ''): void + final public static function assertContainsNotOnlyInstancesOf(string $className, iterable $haystack, string $message = ''): void { - if ($isNativeType === null) { - $isNativeType = Type::isType($type); - } - - static::assertThat( + self::assertThat( $haystack, new LogicalNot( - new TraversableContainsOnly( - $type, - $isNativeType - ) + TraversableContainsOnly::forClassOrInterface($className), ), - $message + $message, ); } /** * Asserts the number of elements of an array, Countable or Traversable. * - * @param Countable|iterable $haystack + * @param Countable|iterable $haystack * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws Exception + * @throws ExpectationFailedException + * @throws GeneratorNotSupportedException */ - public static function assertCount(int $expectedCount, $haystack, string $message = ''): void + final public static function assertCount(int $expectedCount, Countable|iterable $haystack, string $message = ''): void { - if (!$haystack instanceof Countable && !is_iterable($haystack)) { - throw InvalidArgumentException::create(2, 'countable or iterable'); + if ($haystack instanceof Generator) { + throw GeneratorNotSupportedException::fromParameterName('$haystack'); } - static::assertThat( + self::assertThat( $haystack, new Count($expectedCount), - $message + $message, ); } /** * Asserts the number of elements of an array, Countable or Traversable. * - * @param Countable|iterable $haystack + * @param Countable|iterable $haystack * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws Exception + * @throws ExpectationFailedException + * @throws GeneratorNotSupportedException */ - public static function assertNotCount(int $expectedCount, $haystack, string $message = ''): void + final public static function assertNotCount(int $expectedCount, Countable|iterable $haystack, string $message = ''): void { - if (!$haystack instanceof Countable && !is_iterable($haystack)) { - throw InvalidArgumentException::create(2, 'countable or iterable'); + if ($haystack instanceof Generator) { + throw GeneratorNotSupportedException::fromParameterName('$haystack'); } $constraint = new LogicalNot( - new Count($expectedCount) + new Count($expectedCount), ); - static::assertThat($haystack, $constraint, $message); + self::assertThat($haystack, $constraint, $message); } /** * Asserts that two variables are equal. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertEquals($expected, $actual, string $message = ''): void + final public static function assertEquals(mixed $expected, mixed $actual, string $message = ''): void { $constraint = new IsEqual($expected); - static::assertThat($actual, $constraint, $message); + self::assertThat($actual, $constraint, $message); } /** * Asserts that two variables are equal (canonicalizing). * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertEqualsCanonicalizing($expected, $actual, string $message = ''): void + final public static function assertEqualsCanonicalizing(mixed $expected, mixed $actual, string $message = ''): void { $constraint = new IsEqualCanonicalizing($expected); - static::assertThat($actual, $constraint, $message); + self::assertThat($actual, $constraint, $message); } /** * Asserts that two variables are equal (ignoring case). * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertEqualsIgnoringCase($expected, $actual, string $message = ''): void + final public static function assertEqualsIgnoringCase(mixed $expected, mixed $actual, string $message = ''): void { $constraint = new IsEqualIgnoringCase($expected); - static::assertThat($actual, $constraint, $message); + self::assertThat($actual, $constraint, $message); } /** * Asserts that two variables are equal (with delta). * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertEqualsWithDelta($expected, $actual, float $delta, string $message = ''): void + final public static function assertEqualsWithDelta(mixed $expected, mixed $actual, float $delta, string $message = ''): void { $constraint = new IsEqualWithDelta( $expected, - $delta + $delta, ); - static::assertThat($actual, $constraint, $message); + self::assertThat($actual, $constraint, $message); } /** * Asserts that two variables are not equal. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertNotEquals($expected, $actual, string $message = ''): void + final public static function assertNotEquals(mixed $expected, mixed $actual, string $message = ''): void { $constraint = new LogicalNot( - new IsEqual($expected) + new IsEqual($expected), ); - static::assertThat($actual, $constraint, $message); + self::assertThat($actual, $constraint, $message); } /** * Asserts that two variables are not equal (canonicalizing). * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertNotEqualsCanonicalizing($expected, $actual, string $message = ''): void + final public static function assertNotEqualsCanonicalizing(mixed $expected, mixed $actual, string $message = ''): void { $constraint = new LogicalNot( - new IsEqualCanonicalizing($expected) + new IsEqualCanonicalizing($expected), ); - static::assertThat($actual, $constraint, $message); + self::assertThat($actual, $constraint, $message); } /** * Asserts that two variables are not equal (ignoring case). * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertNotEqualsIgnoringCase($expected, $actual, string $message = ''): void + final public static function assertNotEqualsIgnoringCase(mixed $expected, mixed $actual, string $message = ''): void { $constraint = new LogicalNot( - new IsEqualIgnoringCase($expected) + new IsEqualIgnoringCase($expected), ); - static::assertThat($actual, $constraint, $message); + self::assertThat($actual, $constraint, $message); } /** * Asserts that two variables are not equal (with delta). * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertNotEqualsWithDelta($expected, $actual, float $delta, string $message = ''): void + final public static function assertNotEqualsWithDelta(mixed $expected, mixed $actual, float $delta, string $message = ''): void { $constraint = new LogicalNot( new IsEqualWithDelta( $expected, - $delta - ) + $delta, + ), + ); + + self::assertThat($actual, $constraint, $message); + } + + /** + * @throws ExpectationFailedException + */ + final public static function assertObjectEquals(object $expected, object $actual, string $method = 'equals', string $message = ''): void + { + self::assertThat( + $actual, + self::objectEquals($expected, $method), + $message, ); + } - static::assertThat($actual, $constraint, $message); + /** + * @throws ExpectationFailedException + */ + final public static function assertObjectNotEquals(object $expected, object $actual, string $method = 'equals', string $message = ''): void + { + self::assertThat( + $actual, + self::logicalNot( + self::objectEquals($expected, $method), + ), + $message, + ); } /** * Asserts that a variable is empty. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert empty $actual + * @throws GeneratorNotSupportedException */ - public static function assertEmpty($actual, string $message = ''): void + final public static function assertEmpty(mixed $actual, string $message = ''): void { - static::assertThat($actual, static::isEmpty(), $message); + if ($actual instanceof Generator) { + throw GeneratorNotSupportedException::fromParameterName('$actual'); + } + + self::assertThat($actual, self::isEmpty(), $message); } /** * Asserts that a variable is not empty. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !empty $actual + * @throws GeneratorNotSupportedException */ - public static function assertNotEmpty($actual, string $message = ''): void + final public static function assertNotEmpty(mixed $actual, string $message = ''): void { - static::assertThat($actual, static::logicalNot(static::isEmpty()), $message); + if ($actual instanceof Generator) { + throw GeneratorNotSupportedException::fromParameterName('$actual'); + } + + self::assertThat($actual, self::logicalNot(self::isEmpty()), $message); } /** * Asserts that a value is greater than another value. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertGreaterThan($expected, $actual, string $message = ''): void + final public static function assertGreaterThan(mixed $minimum, mixed $actual, string $message = ''): void { - static::assertThat($actual, static::greaterThan($expected), $message); + self::assertThat($actual, self::greaterThan($minimum), $message); } /** * Asserts that a value is greater than or equal to another value. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertGreaterThanOrEqual($expected, $actual, string $message = ''): void + final public static function assertGreaterThanOrEqual(mixed $minimum, mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - static::greaterThanOrEqual($expected), - $message + self::greaterThanOrEqual($minimum), + $message, ); } @@ -483,22 +1063,20 @@ public static function assertGreaterThanOrEqual($expected, $actual, string $mess * Asserts that a value is smaller than another value. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertLessThan($expected, $actual, string $message = ''): void + final public static function assertLessThan(mixed $maximum, mixed $actual, string $message = ''): void { - static::assertThat($actual, static::lessThan($expected), $message); + self::assertThat($actual, self::lessThan($maximum), $message); } /** * Asserts that a value is smaller than or equal to another value. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertLessThanOrEqual($expected, $actual, string $message = ''): void + final public static function assertLessThanOrEqual(mixed $maximum, mixed $actual, string $message = ''): void { - static::assertThat($actual, static::lessThanOrEqual($expected), $message); + self::assertThat($actual, self::lessThanOrEqual($maximum), $message); } /** @@ -506,16 +1084,15 @@ public static function assertLessThanOrEqual($expected, $actual, string $message * file. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertFileEquals(string $expected, string $actual, string $message = ''): void + final public static function assertFileEquals(string $expected, string $actual, string $message = ''): void { - static::assertFileExists($expected, $message); - static::assertFileExists($actual, $message); + self::assertFileExists($expected, $message); + self::assertFileExists($actual, $message); $constraint = new IsEqual(file_get_contents($expected)); - static::assertThat(file_get_contents($actual), $constraint, $message); + self::assertThat(file_get_contents($actual), $constraint, $message); } /** @@ -523,18 +1100,17 @@ public static function assertFileEquals(string $expected, string $actual, string * file (canonicalizing). * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertFileEqualsCanonicalizing(string $expected, string $actual, string $message = ''): void + final public static function assertFileEqualsCanonicalizing(string $expected, string $actual, string $message = ''): void { - static::assertFileExists($expected, $message); - static::assertFileExists($actual, $message); + self::assertFileExists($expected, $message); + self::assertFileExists($actual, $message); $constraint = new IsEqualCanonicalizing( - file_get_contents($expected) + file_get_contents($expected), ); - static::assertThat(file_get_contents($actual), $constraint, $message); + self::assertThat(file_get_contents($actual), $constraint, $message); } /** @@ -542,16 +1118,15 @@ public static function assertFileEqualsCanonicalizing(string $expected, string $ * file (ignoring case). * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertFileEqualsIgnoringCase(string $expected, string $actual, string $message = ''): void + final public static function assertFileEqualsIgnoringCase(string $expected, string $actual, string $message = ''): void { - static::assertFileExists($expected, $message); - static::assertFileExists($actual, $message); + self::assertFileExists($expected, $message); + self::assertFileExists($actual, $message); $constraint = new IsEqualIgnoringCase(file_get_contents($expected)); - static::assertThat(file_get_contents($actual), $constraint, $message); + self::assertThat(file_get_contents($actual), $constraint, $message); } /** @@ -559,18 +1134,17 @@ public static function assertFileEqualsIgnoringCase(string $expected, string $ac * another file. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertFileNotEquals(string $expected, string $actual, string $message = ''): void + final public static function assertFileNotEquals(string $expected, string $actual, string $message = ''): void { - static::assertFileExists($expected, $message); - static::assertFileExists($actual, $message); + self::assertFileExists($expected, $message); + self::assertFileExists($actual, $message); $constraint = new LogicalNot( - new IsEqual(file_get_contents($expected)) + new IsEqual(file_get_contents($expected)), ); - static::assertThat(file_get_contents($actual), $constraint, $message); + self::assertThat(file_get_contents($actual), $constraint, $message); } /** @@ -578,18 +1152,17 @@ public static function assertFileNotEquals(string $expected, string $actual, str * file (canonicalizing). * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertFileNotEqualsCanonicalizing(string $expected, string $actual, string $message = ''): void + final public static function assertFileNotEqualsCanonicalizing(string $expected, string $actual, string $message = ''): void { - static::assertFileExists($expected, $message); - static::assertFileExists($actual, $message); + self::assertFileExists($expected, $message); + self::assertFileExists($actual, $message); $constraint = new LogicalNot( - new IsEqualCanonicalizing(file_get_contents($expected)) + new IsEqualCanonicalizing(file_get_contents($expected)), ); - static::assertThat(file_get_contents($actual), $constraint, $message); + self::assertThat(file_get_contents($actual), $constraint, $message); } /** @@ -597,18 +1170,17 @@ public static function assertFileNotEqualsCanonicalizing(string $expected, strin * file (ignoring case). * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertFileNotEqualsIgnoringCase(string $expected, string $actual, string $message = ''): void + final public static function assertFileNotEqualsIgnoringCase(string $expected, string $actual, string $message = ''): void { - static::assertFileExists($expected, $message); - static::assertFileExists($actual, $message); + self::assertFileExists($expected, $message); + self::assertFileExists($actual, $message); $constraint = new LogicalNot( - new IsEqualIgnoringCase(file_get_contents($expected)) + new IsEqualIgnoringCase(file_get_contents($expected)), ); - static::assertThat(file_get_contents($actual), $constraint, $message); + self::assertThat(file_get_contents($actual), $constraint, $message); } /** @@ -616,15 +1188,14 @@ public static function assertFileNotEqualsIgnoringCase(string $expected, string * to the contents of a file. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertStringEqualsFile(string $expectedFile, string $actualString, string $message = ''): void + final public static function assertStringEqualsFile(string $expectedFile, string $actualString, string $message = ''): void { - static::assertFileExists($expectedFile, $message); + self::assertFileExists($expectedFile, $message); $constraint = new IsEqual(file_get_contents($expectedFile)); - static::assertThat($actualString, $constraint, $message); + self::assertThat($actualString, $constraint, $message); } /** @@ -632,15 +1203,14 @@ public static function assertStringEqualsFile(string $expectedFile, string $actu * to the contents of a file (canonicalizing). * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertStringEqualsFileCanonicalizing(string $expectedFile, string $actualString, string $message = ''): void + final public static function assertStringEqualsFileCanonicalizing(string $expectedFile, string $actualString, string $message = ''): void { - static::assertFileExists($expectedFile, $message); + self::assertFileExists($expectedFile, $message); $constraint = new IsEqualCanonicalizing(file_get_contents($expectedFile)); - static::assertThat($actualString, $constraint, $message); + self::assertThat($actualString, $constraint, $message); } /** @@ -648,15 +1218,14 @@ public static function assertStringEqualsFileCanonicalizing(string $expectedFile * to the contents of a file (ignoring case). * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertStringEqualsFileIgnoringCase(string $expectedFile, string $actualString, string $message = ''): void + final public static function assertStringEqualsFileIgnoringCase(string $expectedFile, string $actualString, string $message = ''): void { - static::assertFileExists($expectedFile, $message); + self::assertFileExists($expectedFile, $message); $constraint = new IsEqualIgnoringCase(file_get_contents($expectedFile)); - static::assertThat($actualString, $constraint, $message); + self::assertThat($actualString, $constraint, $message); } /** @@ -664,17 +1233,16 @@ public static function assertStringEqualsFileIgnoringCase(string $expectedFile, * to the contents of a file. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertStringNotEqualsFile(string $expectedFile, string $actualString, string $message = ''): void + final public static function assertStringNotEqualsFile(string $expectedFile, string $actualString, string $message = ''): void { - static::assertFileExists($expectedFile, $message); + self::assertFileExists($expectedFile, $message); $constraint = new LogicalNot( - new IsEqual(file_get_contents($expectedFile)) + new IsEqual(file_get_contents($expectedFile)), ); - static::assertThat($actualString, $constraint, $message); + self::assertThat($actualString, $constraint, $message); } /** @@ -682,17 +1250,16 @@ public static function assertStringNotEqualsFile(string $expectedFile, string $a * to the contents of a file (canonicalizing). * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertStringNotEqualsFileCanonicalizing(string $expectedFile, string $actualString, string $message = ''): void + final public static function assertStringNotEqualsFileCanonicalizing(string $expectedFile, string $actualString, string $message = ''): void { - static::assertFileExists($expectedFile, $message); + self::assertFileExists($expectedFile, $message); $constraint = new LogicalNot( - new IsEqualCanonicalizing(file_get_contents($expectedFile)) + new IsEqualCanonicalizing(file_get_contents($expectedFile)), ); - static::assertThat($actualString, $constraint, $message); + self::assertThat($actualString, $constraint, $message); } /** @@ -700,143 +1267,84 @@ public static function assertStringNotEqualsFileCanonicalizing(string $expectedF * to the contents of a file (ignoring case). * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertStringNotEqualsFileIgnoringCase(string $expectedFile, string $actualString, string $message = ''): void + final public static function assertStringNotEqualsFileIgnoringCase(string $expectedFile, string $actualString, string $message = ''): void { - static::assertFileExists($expectedFile, $message); + self::assertFileExists($expectedFile, $message); $constraint = new LogicalNot( - new IsEqualIgnoringCase(file_get_contents($expectedFile)) + new IsEqualIgnoringCase(file_get_contents($expectedFile)), ); - static::assertThat($actualString, $constraint, $message); + self::assertThat($actualString, $constraint, $message); } /** * Asserts that a file/dir is readable. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public static function assertIsReadable(string $filename, string $message = ''): void - { - static::assertThat($filename, new IsReadable, $message); - } - - /** - * Asserts that a file/dir exists and is not readable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertIsNotReadable(string $filename, string $message = ''): void + final public static function assertIsReadable(string $filename, string $message = ''): void { - static::assertThat($filename, new LogicalNot(new IsReadable), $message); + self::assertThat($filename, new IsReadable, $message); } /** * Asserts that a file/dir exists and is not readable. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4062 */ - public static function assertNotIsReadable(string $filename, string $message = ''): void + final public static function assertIsNotReadable(string $filename, string $message = ''): void { - self::createWarning('assertNotIsReadable() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertIsNotReadable() instead.'); - - static::assertThat($filename, new LogicalNot(new IsReadable), $message); + self::assertThat($filename, new LogicalNot(new IsReadable), $message); } /** * Asserts that a file/dir exists and is writable. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public static function assertIsWritable(string $filename, string $message = ''): void - { - static::assertThat($filename, new IsWritable, $message); - } - - /** - * Asserts that a file/dir exists and is not writable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertIsNotWritable(string $filename, string $message = ''): void + final public static function assertIsWritable(string $filename, string $message = ''): void { - static::assertThat($filename, new LogicalNot(new IsWritable), $message); + self::assertThat($filename, new IsWritable, $message); } /** * Asserts that a file/dir exists and is not writable. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4065 */ - public static function assertNotIsWritable(string $filename, string $message = ''): void + final public static function assertIsNotWritable(string $filename, string $message = ''): void { - self::createWarning('assertNotIsWritable() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertIsNotWritable() instead.'); - - static::assertThat($filename, new LogicalNot(new IsWritable), $message); + self::assertThat($filename, new LogicalNot(new IsWritable), $message); } /** * Asserts that a directory exists. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public static function assertDirectoryExists(string $directory, string $message = ''): void - { - static::assertThat($directory, new DirectoryExists, $message); - } - - /** - * Asserts that a directory does not exist. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertDirectoryDoesNotExist(string $directory, string $message = ''): void + final public static function assertDirectoryExists(string $directory, string $message = ''): void { - static::assertThat($directory, new LogicalNot(new DirectoryExists), $message); + self::assertThat($directory, new DirectoryExists, $message); } /** * Asserts that a directory does not exist. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4068 */ - public static function assertDirectoryNotExists(string $directory, string $message = ''): void + final public static function assertDirectoryDoesNotExist(string $directory, string $message = ''): void { - self::createWarning('assertDirectoryNotExists() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertDirectoryDoesNotExist() instead.'); - - static::assertThat($directory, new LogicalNot(new DirectoryExists), $message); + self::assertThat($directory, new LogicalNot(new DirectoryExists), $message); } /** * Asserts that a directory exists and is readable. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertDirectoryIsReadable(string $directory, string $message = ''): void + final public static function assertDirectoryIsReadable(string $directory, string $message = ''): void { self::assertDirectoryExists($directory, $message); self::assertIsReadable($directory, $message); @@ -846,28 +1354,9 @@ public static function assertDirectoryIsReadable(string $directory, string $mess * Asserts that a directory exists and is not readable. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public static function assertDirectoryIsNotReadable(string $directory, string $message = ''): void - { - self::assertDirectoryExists($directory, $message); - self::assertIsNotReadable($directory, $message); - } - - /** - * Asserts that a directory exists and is not readable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4071 */ - public static function assertDirectoryNotIsReadable(string $directory, string $message = ''): void + final public static function assertDirectoryIsNotReadable(string $directory, string $message = ''): void { - self::createWarning('assertDirectoryNotIsReadable() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertDirectoryIsNotReadable() instead.'); - self::assertDirectoryExists($directory, $message); self::assertIsNotReadable($directory, $message); } @@ -876,9 +1365,8 @@ public static function assertDirectoryNotIsReadable(string $directory, string $m * Asserts that a directory exists and is writable. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertDirectoryIsWritable(string $directory, string $message = ''): void + final public static function assertDirectoryIsWritable(string $directory, string $message = ''): void { self::assertDirectoryExists($directory, $message); self::assertIsWritable($directory, $message); @@ -888,78 +1376,39 @@ public static function assertDirectoryIsWritable(string $directory, string $mess * Asserts that a directory exists and is not writable. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertDirectoryIsNotWritable(string $directory, string $message = ''): void + final public static function assertDirectoryIsNotWritable(string $directory, string $message = ''): void { self::assertDirectoryExists($directory, $message); self::assertIsNotWritable($directory, $message); } - /** - * Asserts that a directory exists and is not writable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4074 - */ - public static function assertDirectoryNotIsWritable(string $directory, string $message = ''): void - { - self::createWarning('assertDirectoryNotIsWritable() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertDirectoryIsNotWritable() instead.'); - - self::assertDirectoryExists($directory, $message); - self::assertIsNotWritable($directory, $message); - } - /** * Asserts that a file exists. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public static function assertFileExists(string $filename, string $message = ''): void - { - static::assertThat($filename, new FileExists, $message); - } - - /** - * Asserts that a file does not exist. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertFileDoesNotExist(string $filename, string $message = ''): void + final public static function assertFileExists(string $filename, string $message = ''): void { - static::assertThat($filename, new LogicalNot(new FileExists), $message); + self::assertThat($filename, new FileExists, $message); } /** * Asserts that a file does not exist. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4077 */ - public static function assertFileNotExists(string $filename, string $message = ''): void + final public static function assertFileDoesNotExist(string $filename, string $message = ''): void { - self::createWarning('assertFileNotExists() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertFileDoesNotExist() instead.'); - - static::assertThat($filename, new LogicalNot(new FileExists), $message); + self::assertThat($filename, new LogicalNot(new FileExists), $message); } /** * Asserts that a file exists and is readable. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertFileIsReadable(string $file, string $message = ''): void + final public static function assertFileIsReadable(string $file, string $message = ''): void { self::assertFileExists($file, $message); self::assertIsReadable($file, $message); @@ -969,28 +1418,9 @@ public static function assertFileIsReadable(string $file, string $message = ''): * Asserts that a file exists and is not readable. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public static function assertFileIsNotReadable(string $file, string $message = ''): void - { - self::assertFileExists($file, $message); - self::assertIsNotReadable($file, $message); - } - - /** - * Asserts that a file exists and is not readable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4080 */ - public static function assertFileNotIsReadable(string $file, string $message = ''): void + final public static function assertFileIsNotReadable(string $file, string $message = ''): void { - self::createWarning('assertFileNotIsReadable() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertFileIsNotReadable() instead.'); - self::assertFileExists($file, $message); self::assertIsNotReadable($file, $message); } @@ -999,9 +1429,8 @@ public static function assertFileNotIsReadable(string $file, string $message = ' * Asserts that a file exists and is writable. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertFileIsWritable(string $file, string $message = ''): void + final public static function assertFileIsWritable(string $file, string $message = ''): void { self::assertFileExists($file, $message); self::assertIsWritable($file, $message); @@ -1011,28 +1440,9 @@ public static function assertFileIsWritable(string $file, string $message = ''): * Asserts that a file exists and is not writable. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public static function assertFileIsNotWritable(string $file, string $message = ''): void - { - self::assertFileExists($file, $message); - self::assertIsNotWritable($file, $message); - } - - /** - * Asserts that a file exists and is not writable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4083 */ - public static function assertFileNotIsWritable(string $file, string $message = ''): void + final public static function assertFileIsNotWritable(string $file, string $message = ''): void { - self::createWarning('assertFileNotIsWritable() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertFileIsNotWritable() instead.'); - self::assertFileExists($file, $message); self::assertIsNotWritable($file, $message); } @@ -1041,260 +1451,131 @@ public static function assertFileNotIsWritable(string $file, string $message = ' * Asserts that a condition is true. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert true $condition + * @phpstan-assert true $condition */ - public static function assertTrue($condition, string $message = ''): void + final public static function assertTrue(mixed $condition, string $message = ''): void { - static::assertThat($condition, static::isTrue(), $message); + self::assertThat($condition, self::isTrue(), $message); } /** * Asserts that a condition is not true. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert !true $condition + * @phpstan-assert !true $condition */ - public static function assertNotTrue($condition, string $message = ''): void + final public static function assertNotTrue(mixed $condition, string $message = ''): void { - static::assertThat($condition, static::logicalNot(static::isTrue()), $message); + self::assertThat($condition, self::logicalNot(self::isTrue()), $message); } /** * Asserts that a condition is false. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert false $condition + * @phpstan-assert false $condition */ - public static function assertFalse($condition, string $message = ''): void + final public static function assertFalse(mixed $condition, string $message = ''): void { - static::assertThat($condition, static::isFalse(), $message); + self::assertThat($condition, self::isFalse(), $message); } /** * Asserts that a condition is not false. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert !false $condition + * @phpstan-assert !false $condition */ - public static function assertNotFalse($condition, string $message = ''): void + final public static function assertNotFalse(mixed $condition, string $message = ''): void { - static::assertThat($condition, static::logicalNot(static::isFalse()), $message); + self::assertThat($condition, self::logicalNot(self::isFalse()), $message); } /** * Asserts that a variable is null. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert null $actual + * @phpstan-assert null $actual */ - public static function assertNull($actual, string $message = ''): void + final public static function assertNull(mixed $actual, string $message = ''): void { - static::assertThat($actual, static::isNull(), $message); + self::assertThat($actual, self::isNull(), $message); } /** * Asserts that a variable is not null. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert !null $actual + * @phpstan-assert !null $actual */ - public static function assertNotNull($actual, string $message = ''): void + final public static function assertNotNull(mixed $actual, string $message = ''): void { - static::assertThat($actual, static::logicalNot(static::isNull()), $message); + self::assertThat($actual, self::logicalNot(self::isNull()), $message); } /** * Asserts that a variable is finite. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertFinite($actual, string $message = ''): void + final public static function assertFinite(mixed $actual, string $message = ''): void { - static::assertThat($actual, static::isFinite(), $message); + self::assertThat($actual, self::isFinite(), $message); } /** * Asserts that a variable is infinite. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertInfinite($actual, string $message = ''): void + final public static function assertInfinite(mixed $actual, string $message = ''): void { - static::assertThat($actual, static::isInfinite(), $message); + self::assertThat($actual, self::isInfinite(), $message); } /** * Asserts that a variable is nan. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public static function assertNan($actual, string $message = ''): void - { - static::assertThat($actual, static::isNan(), $message); - } - - /** - * Asserts that a class has a specified attribute. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - */ - public static function assertClassHasAttribute(string $attributeName, string $className, string $message = ''): void - { - if (!self::isValidClassAttributeName($attributeName)) { - throw InvalidArgumentException::create(1, 'valid attribute name'); - } - - if (!class_exists($className)) { - throw InvalidArgumentException::create(2, 'class name'); - } - - static::assertThat($className, new ClassHasAttribute($attributeName), $message); - } - - /** - * Asserts that a class does not have a specified attribute. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - */ - public static function assertClassNotHasAttribute(string $attributeName, string $className, string $message = ''): void - { - if (!self::isValidClassAttributeName($attributeName)) { - throw InvalidArgumentException::create(1, 'valid attribute name'); - } - - if (!class_exists($className)) { - throw InvalidArgumentException::create(2, 'class name'); - } - - static::assertThat( - $className, - new LogicalNot( - new ClassHasAttribute($attributeName) - ), - $message - ); - } - - /** - * Asserts that a class has a specified static attribute. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - */ - public static function assertClassHasStaticAttribute(string $attributeName, string $className, string $message = ''): void - { - if (!self::isValidClassAttributeName($attributeName)) { - throw InvalidArgumentException::create(1, 'valid attribute name'); - } - - if (!class_exists($className)) { - throw InvalidArgumentException::create(2, 'class name'); - } - - static::assertThat( - $className, - new ClassHasStaticAttribute($attributeName), - $message - ); - } - - /** - * Asserts that a class does not have a specified static attribute. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception */ - public static function assertClassNotHasStaticAttribute(string $attributeName, string $className, string $message = ''): void + final public static function assertNan(mixed $actual, string $message = ''): void { - if (!self::isValidClassAttributeName($attributeName)) { - throw InvalidArgumentException::create(1, 'valid attribute name'); - } - - if (!class_exists($className)) { - throw InvalidArgumentException::create(2, 'class name'); - } - - static::assertThat( - $className, - new LogicalNot( - new ClassHasStaticAttribute($attributeName) - ), - $message - ); + self::assertThat($actual, self::isNan(), $message); } /** - * Asserts that an object has a specified attribute. - * - * @param object $object + * Asserts that an object has a specified property. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception */ - public static function assertObjectHasAttribute(string $attributeName, $object, string $message = ''): void + final public static function assertObjectHasProperty(string $propertyName, object $object, string $message = ''): void { - if (!self::isValidObjectAttributeName($attributeName)) { - throw InvalidArgumentException::create(1, 'valid attribute name'); - } - - if (!is_object($object)) { - throw InvalidArgumentException::create(2, 'object'); - } - - static::assertThat( + self::assertThat( $object, - new ObjectHasAttribute($attributeName), - $message + new ObjectHasProperty($propertyName), + $message, ); } /** - * Asserts that an object does not have a specified attribute. - * - * @param object $object + * Asserts that an object does not have a specified property. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception */ - public static function assertObjectNotHasAttribute(string $attributeName, $object, string $message = ''): void + final public static function assertObjectNotHasProperty(string $propertyName, object $object, string $message = ''): void { - if (!self::isValidObjectAttributeName($attributeName)) { - throw InvalidArgumentException::create(1, 'valid attribute name'); - } - - if (!is_object($object)) { - throw InvalidArgumentException::create(2, 'object'); - } - - static::assertThat( + self::assertThat( $object, new LogicalNot( - new ObjectHasAttribute($attributeName) + new ObjectHasProperty($propertyName), ), - $message + $message, ); } @@ -1303,19 +1584,20 @@ public static function assertObjectNotHasAttribute(string $attributeName, $objec * Used on objects, it asserts that two variables reference * the same object. * + * @template ExpectedType + * + * @param ExpectedType $expected + * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-template ExpectedType - * @psalm-param ExpectedType $expected - * @psalm-assert =ExpectedType $actual + * @phpstan-assert =ExpectedType $actual */ - public static function assertSame($expected, $actual, string $message = ''): void + final public static function assertSame(mixed $expected, mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, new IsIdentical($expected), - $message + $message, ); } @@ -1325,512 +1607,506 @@ public static function assertSame($expected, $actual, string $message = ''): voi * the same object. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertNotSame($expected, $actual, string $message = ''): void + final public static function assertNotSame(mixed $expected, mixed $actual, string $message = ''): void { if (is_bool($expected) && is_bool($actual)) { - static::assertNotEquals($expected, $actual, $message); + self::assertNotEquals($expected, $actual, $message); } - static::assertThat( + self::assertThat( $actual, new LogicalNot( - new IsIdentical($expected) + new IsIdentical($expected), ), - $message + $message, ); } /** * Asserts that a variable is of a given type. * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @template ExpectedType of object + * + * @param class-string $expected + * * @throws Exception + * @throws ExpectationFailedException + * @throws UnknownClassOrInterfaceException * - * @psalm-template ExpectedType of object - * @psalm-param class-string $expected - * @psalm-assert ExpectedType $actual + * @phpstan-assert =ExpectedType $actual */ - public static function assertInstanceOf(string $expected, $actual, string $message = ''): void + final public static function assertInstanceOf(string $expected, mixed $actual, string $message = ''): void { if (!class_exists($expected) && !interface_exists($expected)) { - throw InvalidArgumentException::create(1, 'class or interface name'); + throw new UnknownClassOrInterfaceException($expected); } - static::assertThat( + self::assertThat( $actual, new IsInstanceOf($expected), - $message + $message, ); } /** * Asserts that a variable is not of a given type. * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @template ExpectedType of object + * + * @param class-string $expected + * * @throws Exception + * @throws ExpectationFailedException * - * @psalm-template ExpectedType of object - * @psalm-param class-string $expected - * @psalm-assert !ExpectedType $actual + * @phpstan-assert !ExpectedType $actual */ - public static function assertNotInstanceOf(string $expected, $actual, string $message = ''): void + final public static function assertNotInstanceOf(string $expected, mixed $actual, string $message = ''): void { if (!class_exists($expected) && !interface_exists($expected)) { - throw InvalidArgumentException::create(1, 'class or interface name'); + throw new UnknownClassOrInterfaceException($expected); } - static::assertThat( + self::assertThat( $actual, new LogicalNot( - new IsInstanceOf($expected) + new IsInstanceOf($expected), ), - $message + $message, ); } /** * Asserts that a variable is of type array. * + * @throws Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert array $actual + * @phpstan-assert array $actual */ - public static function assertIsArray($actual, string $message = ''): void + final public static function assertIsArray(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new IsType(IsType::TYPE_ARRAY), - $message + new IsType(NativeType::Array), + $message, ); } /** * Asserts that a variable is of type bool. * + * @throws Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert bool $actual + * @phpstan-assert bool $actual */ - public static function assertIsBool($actual, string $message = ''): void + final public static function assertIsBool(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new IsType(IsType::TYPE_BOOL), - $message + new IsType(NativeType::Bool), + $message, ); } /** * Asserts that a variable is of type float. * + * @throws Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert float $actual + * @phpstan-assert float $actual */ - public static function assertIsFloat($actual, string $message = ''): void + final public static function assertIsFloat(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new IsType(IsType::TYPE_FLOAT), - $message + new IsType(NativeType::Float), + $message, ); } /** * Asserts that a variable is of type int. * + * @throws Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert int $actual + * @phpstan-assert int $actual */ - public static function assertIsInt($actual, string $message = ''): void + final public static function assertIsInt(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new IsType(IsType::TYPE_INT), - $message + new IsType(NativeType::Int), + $message, ); } /** * Asserts that a variable is of type numeric. * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert numeric $actual + */ + final public static function assertIsNumeric(mixed $actual, string $message = ''): void + { + self::assertThat( + $actual, + new IsType(NativeType::Numeric), + $message, + ); + } + + /** + * Asserts that a variable is of type object. + * + * @throws Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert numeric $actual + * @phpstan-assert object $actual */ - public static function assertIsNumeric($actual, string $message = ''): void + final public static function assertIsObject(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new IsType(IsType::TYPE_NUMERIC), - $message + new IsType(NativeType::Object), + $message, ); } /** - * Asserts that a variable is of type object. + * Asserts that a variable is of type resource. * + * @throws Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert object $actual + * @phpstan-assert resource $actual */ - public static function assertIsObject($actual, string $message = ''): void + final public static function assertIsResource(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new IsType(IsType::TYPE_OBJECT), - $message + new IsType(NativeType::Resource), + $message, ); } /** - * Asserts that a variable is of type resource. + * Asserts that a variable is of type resource and is closed. * + * @throws Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert resource $actual + * @phpstan-assert resource $actual */ - public static function assertIsResource($actual, string $message = ''): void + final public static function assertIsClosedResource(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new IsType(IsType::TYPE_RESOURCE), - $message + new IsType(NativeType::ClosedResource), + $message, ); } /** * Asserts that a variable is of type string. * + * @throws Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert string $actual + * @phpstan-assert string $actual */ - public static function assertIsString($actual, string $message = ''): void + final public static function assertIsString(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new IsType(IsType::TYPE_STRING), - $message + new IsType(NativeType::String), + $message, ); } /** * Asserts that a variable is of type scalar. * + * @throws Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert scalar $actual + * @phpstan-assert scalar $actual */ - public static function assertIsScalar($actual, string $message = ''): void + final public static function assertIsScalar(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new IsType(IsType::TYPE_SCALAR), - $message + new IsType(NativeType::Scalar), + $message, ); } /** * Asserts that a variable is of type callable. * + * @throws Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert callable $actual + * @phpstan-assert callable $actual */ - public static function assertIsCallable($actual, string $message = ''): void + final public static function assertIsCallable(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new IsType(IsType::TYPE_CALLABLE), - $message + new IsType(NativeType::Callable), + $message, ); } /** * Asserts that a variable is of type iterable. * + * @throws Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert iterable $actual + * @phpstan-assert iterable $actual */ - public static function assertIsIterable($actual, string $message = ''): void + final public static function assertIsIterable(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new IsType(IsType::TYPE_ITERABLE), - $message + new IsType(NativeType::Iterable), + $message, ); } /** * Asserts that a variable is not of type array. * + * @throws Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert !array $actual + * @phpstan-assert !array $actual */ - public static function assertIsNotArray($actual, string $message = ''): void + final public static function assertIsNotArray(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new LogicalNot(new IsType(IsType::TYPE_ARRAY)), - $message + new LogicalNot(new IsType(NativeType::Array)), + $message, ); } /** * Asserts that a variable is not of type bool. * + * @throws Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert !bool $actual + * @phpstan-assert !bool $actual */ - public static function assertIsNotBool($actual, string $message = ''): void + final public static function assertIsNotBool(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new LogicalNot(new IsType(IsType::TYPE_BOOL)), - $message + new LogicalNot(new IsType(NativeType::Bool)), + $message, ); } /** * Asserts that a variable is not of type float. * + * @throws Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert !float $actual + * @phpstan-assert !float $actual */ - public static function assertIsNotFloat($actual, string $message = ''): void + final public static function assertIsNotFloat(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new LogicalNot(new IsType(IsType::TYPE_FLOAT)), - $message + new LogicalNot(new IsType(NativeType::Float)), + $message, ); } /** * Asserts that a variable is not of type int. * + * @throws Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert !int $actual + * @phpstan-assert !int $actual */ - public static function assertIsNotInt($actual, string $message = ''): void + final public static function assertIsNotInt(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new LogicalNot(new IsType(IsType::TYPE_INT)), - $message + new LogicalNot(new IsType(NativeType::Int)), + $message, ); } /** * Asserts that a variable is not of type numeric. * + * @throws Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert !numeric $actual + * @phpstan-assert !numeric $actual */ - public static function assertIsNotNumeric($actual, string $message = ''): void + final public static function assertIsNotNumeric(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new LogicalNot(new IsType(IsType::TYPE_NUMERIC)), - $message + new LogicalNot(new IsType(NativeType::Numeric)), + $message, ); } /** * Asserts that a variable is not of type object. * + * @throws Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert !object $actual + * @phpstan-assert !object $actual */ - public static function assertIsNotObject($actual, string $message = ''): void + final public static function assertIsNotObject(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new LogicalNot(new IsType(IsType::TYPE_OBJECT)), - $message + new LogicalNot(new IsType(NativeType::Object)), + $message, ); } /** * Asserts that a variable is not of type resource. * + * @throws Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert !resource $actual + * @phpstan-assert !resource $actual */ - public static function assertIsNotResource($actual, string $message = ''): void + final public static function assertIsNotResource(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new LogicalNot(new IsType(IsType::TYPE_RESOURCE)), - $message + new LogicalNot(new IsType(NativeType::Resource)), + $message, ); } /** - * Asserts that a variable is not of type string. + * Asserts that a variable is not of type resource. * + * @throws Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert !string $actual + * @phpstan-assert !resource $actual */ - public static function assertIsNotString($actual, string $message = ''): void + final public static function assertIsNotClosedResource(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new LogicalNot(new IsType(IsType::TYPE_STRING)), - $message + new LogicalNot(new IsType(NativeType::ClosedResource)), + $message, ); } /** - * Asserts that a variable is not of type scalar. + * Asserts that a variable is not of type string. * + * @throws Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert !scalar $actual + * @phpstan-assert !string $actual */ - public static function assertIsNotScalar($actual, string $message = ''): void + final public static function assertIsNotString(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new LogicalNot(new IsType(IsType::TYPE_SCALAR)), - $message + new LogicalNot(new IsType(NativeType::String)), + $message, ); } /** - * Asserts that a variable is not of type callable. + * Asserts that a variable is not of type scalar. * + * @throws Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert !callable $actual + * @phpstan-assert !scalar $actual */ - public static function assertIsNotCallable($actual, string $message = ''): void + final public static function assertIsNotScalar(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new LogicalNot(new IsType(IsType::TYPE_CALLABLE)), - $message + new LogicalNot(new IsType(NativeType::Scalar)), + $message, ); } /** - * Asserts that a variable is not of type iterable. + * Asserts that a variable is not of type callable. * + * @throws Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * - * @psalm-assert !iterable $actual + * @phpstan-assert !callable $actual */ - public static function assertIsNotIterable($actual, string $message = ''): void + final public static function assertIsNotCallable(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new LogicalNot(new IsType(IsType::TYPE_ITERABLE)), - $message + new LogicalNot(new IsType(NativeType::Callable)), + $message, ); } /** - * Asserts that a string matches a given regular expression. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public static function assertMatchesRegularExpression(string $pattern, string $string, string $message = ''): void - { - static::assertThat($string, new RegularExpression($pattern), $message); - } - - /** - * Asserts that a string matches a given regular expression. + * Asserts that a variable is not of type iterable. * + * @throws Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4086 + * @phpstan-assert !iterable $actual */ - public static function assertRegExp(string $pattern, string $string, string $message = ''): void + final public static function assertIsNotIterable(mixed $actual, string $message = ''): void { - self::createWarning('assertRegExp() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertMatchesRegularExpression() instead.'); - - static::assertThat($string, new RegularExpression($pattern), $message); + self::assertThat( + $actual, + new LogicalNot(new IsType(NativeType::Iterable)), + $message, + ); } /** - * Asserts that a string does not match a given regular expression. + * Asserts that a string matches a given regular expression. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertDoesNotMatchRegularExpression(string $pattern, string $string, string $message = ''): void + final public static function assertMatchesRegularExpression(string $pattern, string $string, string $message = ''): void { - static::assertThat( - $string, - new LogicalNot( - new RegularExpression($pattern) - ), - $message - ); + self::assertThat($string, new RegularExpression($pattern), $message); } /** * Asserts that a string does not match a given regular expression. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4089 */ - public static function assertNotRegExp(string $pattern, string $string, string $message = ''): void + final public static function assertDoesNotMatchRegularExpression(string $pattern, string $string, string $message = ''): void { - self::createWarning('assertNotRegExp() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertDoesNotMatchRegularExpression() instead.'); - - static::assertThat( + self::assertThat( $string, new LogicalNot( - new RegularExpression($pattern) + new RegularExpression($pattern), ), - $message + $message, ); } @@ -1838,27 +2114,27 @@ public static function assertNotRegExp(string $pattern, string $string, string $ * Assert that the size of two arrays (or `Countable` or `Traversable` objects) * is the same. * - * @param Countable|iterable $expected - * @param Countable|iterable $actual + * @param Countable|iterable $expected + * @param Countable|iterable $actual * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws Exception + * @throws ExpectationFailedException + * @throws GeneratorNotSupportedException */ - public static function assertSameSize($expected, $actual, string $message = ''): void + final public static function assertSameSize(Countable|iterable $expected, Countable|iterable $actual, string $message = ''): void { - if (!$expected instanceof Countable && !is_iterable($expected)) { - throw InvalidArgumentException::create(1, 'countable or iterable'); + if ($expected instanceof Generator) { + throw GeneratorNotSupportedException::fromParameterName('$expected'); } - if (!$actual instanceof Countable && !is_iterable($actual)) { - throw InvalidArgumentException::create(2, 'countable or iterable'); + if ($actual instanceof Generator) { + throw GeneratorNotSupportedException::fromParameterName('$actual'); } - static::assertThat( + self::assertThat( $actual, new SameSize($expected), - $message + $message, ); } @@ -1866,390 +2142,314 @@ public static function assertSameSize($expected, $actual, string $message = ''): * Assert that the size of two arrays (or `Countable` or `Traversable` objects) * is not the same. * - * @param Countable|iterable $expected - * @param Countable|iterable $actual + * @param Countable|iterable $expected + * @param Countable|iterable $actual * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws Exception + * @throws ExpectationFailedException + * @throws GeneratorNotSupportedException */ - public static function assertNotSameSize($expected, $actual, string $message = ''): void + final public static function assertNotSameSize(Countable|iterable $expected, Countable|iterable $actual, string $message = ''): void { - if (!$expected instanceof Countable && !is_iterable($expected)) { - throw InvalidArgumentException::create(1, 'countable or iterable'); + if ($expected instanceof Generator) { + throw GeneratorNotSupportedException::fromParameterName('$expected'); } - if (!$actual instanceof Countable && !is_iterable($actual)) { - throw InvalidArgumentException::create(2, 'countable or iterable'); + if ($actual instanceof Generator) { + throw GeneratorNotSupportedException::fromParameterName('$actual'); } - static::assertThat( + self::assertThat( $actual, new LogicalNot( - new SameSize($expected) + new SameSize($expected), ), - $message + $message, ); } /** - * Asserts that a string matches a given format string. + * @throws ExpectationFailedException + */ + final public static function assertStringContainsStringIgnoringLineEndings(string $needle, string $haystack, string $message = ''): void + { + self::assertThat($haystack, new StringContains($needle, false, true), $message); + } + + /** + * Asserts that two strings are equal except for line endings. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertStringMatchesFormat(string $format, string $string, string $message = ''): void + final public static function assertStringEqualsStringIgnoringLineEndings(string $expected, string $actual, string $message = ''): void { - static::assertThat($string, new StringMatchesFormatDescription($format), $message); + self::assertThat($actual, new StringEqualsStringIgnoringLineEndings($expected), $message); } /** - * Asserts that a string does not match a given format string. + * Asserts that a string matches a given format string. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertStringNotMatchesFormat(string $format, string $string, string $message = ''): void + final public static function assertFileMatchesFormat(string $format, string $actualFile, string $message = ''): void { - static::assertThat( - $string, - new LogicalNot( - new StringMatchesFormatDescription($format) - ), - $message + self::assertFileExists($actualFile, $message); + + self::assertThat( + file_get_contents($actualFile), + new StringMatchesFormatDescription($format), + $message, ); } /** - * Asserts that a string matches a given format file. + * Asserts that a string matches a given format string. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertStringMatchesFormatFile(string $formatFile, string $string, string $message = ''): void + final public static function assertFileMatchesFormatFile(string $formatFile, string $actualFile, string $message = ''): void { - static::assertFileExists($formatFile, $message); + self::assertFileExists($formatFile, $message); + self::assertFileExists($actualFile, $message); - static::assertThat( - $string, - new StringMatchesFormatDescription( - file_get_contents($formatFile) - ), - $message + $formatDescription = file_get_contents($formatFile); + + self::assertIsString($formatDescription); + + self::assertThat( + file_get_contents($actualFile), + new StringMatchesFormatDescription($formatDescription), + $message, ); } /** - * Asserts that a string does not match a given format string. + * Asserts that a string matches a given format string. + * + * @throws ExpectationFailedException + */ + final public static function assertStringMatchesFormat(string $format, string $string, string $message = ''): void + { + self::assertThat($string, new StringMatchesFormatDescription($format), $message); + } + + /** + * Asserts that a string matches a given format file. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertStringNotMatchesFormatFile(string $formatFile, string $string, string $message = ''): void + final public static function assertStringMatchesFormatFile(string $formatFile, string $string, string $message = ''): void { - static::assertFileExists($formatFile, $message); + self::assertFileExists($formatFile, $message); - static::assertThat( + $formatDescription = file_get_contents($formatFile); + + self::assertIsString($formatDescription); + + self::assertThat( $string, - new LogicalNot( - new StringMatchesFormatDescription( - file_get_contents($formatFile) - ) + new StringMatchesFormatDescription( + $formatDescription, ), - $message + $message, ); } /** * Asserts that a string starts with a given prefix. * + * @param non-empty-string $prefix + * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws InvalidArgumentException */ - public static function assertStringStartsWith(string $prefix, string $string, string $message = ''): void + final public static function assertStringStartsWith(string $prefix, string $string, string $message = ''): void { - static::assertThat($string, new StringStartsWith($prefix), $message); + self::assertThat($string, new StringStartsWith($prefix), $message); } /** * Asserts that a string starts not with a given prefix. * - * @param string $prefix - * @param string $string + * @param non-empty-string $prefix * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws InvalidArgumentException */ - public static function assertStringStartsNotWith($prefix, $string, string $message = ''): void + final public static function assertStringStartsNotWith(string $prefix, string $string, string $message = ''): void { - static::assertThat( + self::assertThat( $string, new LogicalNot( - new StringStartsWith($prefix) + new StringStartsWith($prefix), ), - $message + $message, ); } /** * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertStringContainsString(string $needle, string $haystack, string $message = ''): void + final public static function assertStringContainsString(string $needle, string $haystack, string $message = ''): void { - $constraint = new StringContains($needle, false); + $constraint = new StringContains($needle); - static::assertThat($haystack, $constraint, $message); + self::assertThat($haystack, $constraint, $message); } /** * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertStringContainsStringIgnoringCase(string $needle, string $haystack, string $message = ''): void + final public static function assertStringContainsStringIgnoringCase(string $needle, string $haystack, string $message = ''): void { $constraint = new StringContains($needle, true); - static::assertThat($haystack, $constraint, $message); + self::assertThat($haystack, $constraint, $message); } /** * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertStringNotContainsString(string $needle, string $haystack, string $message = ''): void + final public static function assertStringNotContainsString(string $needle, string $haystack, string $message = ''): void { $constraint = new LogicalNot(new StringContains($needle)); - static::assertThat($haystack, $constraint, $message); + self::assertThat($haystack, $constraint, $message); } /** * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertStringNotContainsStringIgnoringCase(string $needle, string $haystack, string $message = ''): void + final public static function assertStringNotContainsStringIgnoringCase(string $needle, string $haystack, string $message = ''): void { $constraint = new LogicalNot(new StringContains($needle, true)); - static::assertThat($haystack, $constraint, $message); + self::assertThat($haystack, $constraint, $message); } /** * Asserts that a string ends with a given suffix. * + * @param non-empty-string $suffix + * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws InvalidArgumentException */ - public static function assertStringEndsWith(string $suffix, string $string, string $message = ''): void + final public static function assertStringEndsWith(string $suffix, string $string, string $message = ''): void { - static::assertThat($string, new StringEndsWith($suffix), $message); + self::assertThat($string, new StringEndsWith($suffix), $message); } /** * Asserts that a string ends not with a given suffix. * + * @param non-empty-string $suffix + * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws InvalidArgumentException */ - public static function assertStringEndsNotWith(string $suffix, string $string, string $message = ''): void + final public static function assertStringEndsNotWith(string $suffix, string $string, string $message = ''): void { - static::assertThat( + self::assertThat( $string, new LogicalNot( - new StringEndsWith($suffix) + new StringEndsWith($suffix), ), - $message + $message, ); } /** * Asserts that two XML files are equal. * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws Exception + * @throws ExpectationFailedException + * @throws XmlException */ - public static function assertXmlFileEqualsXmlFile(string $expectedFile, string $actualFile, string $message = ''): void + final public static function assertXmlFileEqualsXmlFile(string $expectedFile, string $actualFile, string $message = ''): void { - $expected = Xml::loadFile($expectedFile); - $actual = Xml::loadFile($actualFile); + $expected = (new XmlLoader)->loadFile($expectedFile); + $actual = (new XmlLoader)->loadFile($actualFile); - static::assertEquals($expected, $actual, $message); + self::assertEquals($expected, $actual, $message); } /** * Asserts that two XML files are not equal. * + * @throws \PHPUnit\Util\Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception */ - public static function assertXmlFileNotEqualsXmlFile(string $expectedFile, string $actualFile, string $message = ''): void + final public static function assertXmlFileNotEqualsXmlFile(string $expectedFile, string $actualFile, string $message = ''): void { - $expected = Xml::loadFile($expectedFile); - $actual = Xml::loadFile($actualFile); + $expected = (new XmlLoader)->loadFile($expectedFile); + $actual = (new XmlLoader)->loadFile($actualFile); - static::assertNotEquals($expected, $actual, $message); + self::assertNotEquals($expected, $actual, $message); } /** * Asserts that two XML documents are equal. * - * @param DOMDocument|string $actualXml - * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception + * @throws XmlException */ - public static function assertXmlStringEqualsXmlFile(string $expectedFile, $actualXml, string $message = ''): void + final public static function assertXmlStringEqualsXmlFile(string $expectedFile, string $actualXml, string $message = ''): void { - $expected = Xml::loadFile($expectedFile); - $actual = Xml::load($actualXml); + $expected = (new XmlLoader)->loadFile($expectedFile); + $actual = (new XmlLoader)->load($actualXml); - static::assertEquals($expected, $actual, $message); + self::assertEquals($expected, $actual, $message); } /** * Asserts that two XML documents are not equal. * - * @param DOMDocument|string $actualXml - * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception + * @throws XmlException */ - public static function assertXmlStringNotEqualsXmlFile(string $expectedFile, $actualXml, string $message = ''): void + final public static function assertXmlStringNotEqualsXmlFile(string $expectedFile, string $actualXml, string $message = ''): void { - $expected = Xml::loadFile($expectedFile); - $actual = Xml::load($actualXml); + $expected = (new XmlLoader)->loadFile($expectedFile); + $actual = (new XmlLoader)->load($actualXml); - static::assertNotEquals($expected, $actual, $message); + self::assertNotEquals($expected, $actual, $message); } /** * Asserts that two XML documents are equal. * - * @param DOMDocument|string $expectedXml - * @param DOMDocument|string $actualXml - * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception + * @throws XmlException */ - public static function assertXmlStringEqualsXmlString($expectedXml, $actualXml, string $message = ''): void + final public static function assertXmlStringEqualsXmlString(string $expectedXml, string $actualXml, string $message = ''): void { - $expected = Xml::load($expectedXml); - $actual = Xml::load($actualXml); + $expected = (new XmlLoader)->load($expectedXml); + $actual = (new XmlLoader)->load($actualXml); - static::assertEquals($expected, $actual, $message); + self::assertEquals($expected, $actual, $message); } /** * Asserts that two XML documents are not equal. * - * @param DOMDocument|string $expectedXml - * @param DOMDocument|string $actualXml - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - */ - public static function assertXmlStringNotEqualsXmlString($expectedXml, $actualXml, string $message = ''): void - { - $expected = Xml::load($expectedXml); - $actual = Xml::load($actualXml); - - static::assertNotEquals($expected, $actual, $message); - } - - /** - * Asserts that a hierarchy of DOMElements matches. - * - * @throws AssertionFailedError * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4091 + * @throws XmlException */ - public static function assertEqualXMLStructure(DOMElement $expectedElement, DOMElement $actualElement, bool $checkAttributes = false, string $message = ''): void + final public static function assertXmlStringNotEqualsXmlString(string $expectedXml, string $actualXml, string $message = ''): void { - self::createWarning('assertEqualXMLStructure() is deprecated and will be removed in PHPUnit 10.'); - - $expectedElement = Xml::import($expectedElement); - $actualElement = Xml::import($actualElement); - - static::assertSame( - $expectedElement->tagName, - $actualElement->tagName, - $message - ); - - if ($checkAttributes) { - static::assertSame( - $expectedElement->attributes->length, - $actualElement->attributes->length, - sprintf( - '%s%sNumber of attributes on node "%s" does not match', - $message, - !empty($message) ? "\n" : '', - $expectedElement->tagName - ) - ); - - for ($i = 0; $i < $expectedElement->attributes->length; $i++) { - $expectedAttribute = $expectedElement->attributes->item($i); - $actualAttribute = $actualElement->attributes->getNamedItem($expectedAttribute->name); - - assert($expectedAttribute instanceof DOMAttr); - - if (!$actualAttribute) { - static::fail( - sprintf( - '%s%sCould not find attribute "%s" on node "%s"', - $message, - !empty($message) ? "\n" : '', - $expectedAttribute->name, - $expectedElement->tagName - ) - ); - } - } - } + $expected = (new XmlLoader)->load($expectedXml); + $actual = (new XmlLoader)->load($actualXml); - Xml::removeCharacterDataNodes($expectedElement); - Xml::removeCharacterDataNodes($actualElement); - - static::assertSame( - $expectedElement->childNodes->length, - $actualElement->childNodes->length, - sprintf( - '%s%sNumber of child nodes of "%s" differs', - $message, - !empty($message) ? "\n" : '', - $expectedElement->tagName - ) - ); - - for ($i = 0; $i < $expectedElement->childNodes->length; $i++) { - static::assertEqualXMLStructure( - $expectedElement->childNodes->item($i), - $actualElement->childNodes->item($i), - $checkAttributes, - $message - ); - } + self::assertNotEquals($expected, $actual, $message); } /** * Evaluates a PHPUnit\Framework\Constraint matcher object. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertThat($value, Constraint $constraint, string $message = ''): void + final public static function assertThat(mixed $value, Constraint $constraint, string $message = ''): void { self::$count += count($constraint); @@ -2260,47 +2460,41 @@ public static function assertThat($value, Constraint $constraint, string $messag * Asserts that a string is a valid JSON string. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertJson(string $actualJson, string $message = ''): void + final public static function assertJson(string $actual, string $message = ''): void { - static::assertThat($actualJson, static::isJson(), $message); + self::assertThat($actual, self::isJson(), $message); } /** * Asserts that two given JSON encoded objects or arrays are equal. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertJsonStringEqualsJsonString(string $expectedJson, string $actualJson, string $message = ''): void + final public static function assertJsonStringEqualsJsonString(string $expectedJson, string $actualJson, string $message = ''): void { - static::assertJson($expectedJson, $message); - static::assertJson($actualJson, $message); + self::assertJson($expectedJson, $message); + self::assertJson($actualJson, $message); - static::assertThat($actualJson, new JsonMatches($expectedJson), $message); + self::assertThat($actualJson, new JsonMatches($expectedJson), $message); } /** * Asserts that two given JSON encoded objects or arrays are not equal. * - * @param string $expectedJson - * @param string $actualJson - * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertJsonStringNotEqualsJsonString($expectedJson, $actualJson, string $message = ''): void + final public static function assertJsonStringNotEqualsJsonString(string $expectedJson, string $actualJson, string $message = ''): void { - static::assertJson($expectedJson, $message); - static::assertJson($actualJson, $message); + self::assertJson($expectedJson, $message); + self::assertJson($actualJson, $message); - static::assertThat( + self::assertThat( $actualJson, new LogicalNot( - new JsonMatches($expectedJson) + new JsonMatches($expectedJson), ), - $message + $message, ); } @@ -2308,39 +2502,41 @@ public static function assertJsonStringNotEqualsJsonString($expectedJson, $actua * Asserts that the generated JSON encoded object and the content of the given file are equal. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertJsonStringEqualsJsonFile(string $expectedFile, string $actualJson, string $message = ''): void + final public static function assertJsonStringEqualsJsonFile(string $expectedFile, string $actualJson, string $message = ''): void { - static::assertFileExists($expectedFile, $message); + self::assertFileExists($expectedFile, $message); + $expectedJson = file_get_contents($expectedFile); - static::assertJson($expectedJson, $message); - static::assertJson($actualJson, $message); + self::assertIsString($expectedJson); + self::assertJson($expectedJson, $message); + self::assertJson($actualJson, $message); - static::assertThat($actualJson, new JsonMatches($expectedJson), $message); + self::assertThat($actualJson, new JsonMatches($expectedJson), $message); } /** * Asserts that the generated JSON encoded object and the content of the given file are not equal. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertJsonStringNotEqualsJsonFile(string $expectedFile, string $actualJson, string $message = ''): void + final public static function assertJsonStringNotEqualsJsonFile(string $expectedFile, string $actualJson, string $message = ''): void { - static::assertFileExists($expectedFile, $message); + self::assertFileExists($expectedFile, $message); + $expectedJson = file_get_contents($expectedFile); - static::assertJson($expectedJson, $message); - static::assertJson($actualJson, $message); + self::assertIsString($expectedJson); + self::assertJson($expectedJson, $message); + self::assertJson($actualJson, $message); - static::assertThat( + self::assertThat( $actualJson, new LogicalNot( - new JsonMatches($expectedJson) + new JsonMatches($expectedJson), ), - $message + $message, ); } @@ -2348,306 +2544,420 @@ public static function assertJsonStringNotEqualsJsonFile(string $expectedFile, s * Asserts that two JSON files are equal. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertJsonFileEqualsJsonFile(string $expectedFile, string $actualFile, string $message = ''): void + final public static function assertJsonFileEqualsJsonFile(string $expectedFile, string $actualFile, string $message = ''): void { - static::assertFileExists($expectedFile, $message); - static::assertFileExists($actualFile, $message); + self::assertFileExists($expectedFile, $message); - $actualJson = file_get_contents($actualFile); $expectedJson = file_get_contents($expectedFile); - static::assertJson($expectedJson, $message); - static::assertJson($actualJson, $message); + self::assertIsString($expectedJson); + self::assertJson($expectedJson, $message); - $constraintExpected = new JsonMatches( - $expectedJson - ); + self::assertFileExists($actualFile, $message); + + $actualJson = file_get_contents($actualFile); - $constraintActual = new JsonMatches($actualJson); + self::assertIsString($actualJson); + self::assertJson($actualJson, $message); - static::assertThat($expectedJson, $constraintActual, $message); - static::assertThat($actualJson, $constraintExpected, $message); + self::assertThat($actualJson, new JsonMatches($expectedJson), $message); } /** * Asserts that two JSON files are not equal. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public static function assertJsonFileNotEqualsJsonFile(string $expectedFile, string $actualFile, string $message = ''): void + final public static function assertJsonFileNotEqualsJsonFile(string $expectedFile, string $actualFile, string $message = ''): void { - static::assertFileExists($expectedFile, $message); - static::assertFileExists($actualFile, $message); + self::assertFileExists($expectedFile, $message); - $actualJson = file_get_contents($actualFile); $expectedJson = file_get_contents($expectedFile); - static::assertJson($expectedJson, $message); - static::assertJson($actualJson, $message); + self::assertIsString($expectedJson); + self::assertJson($expectedJson, $message); - $constraintExpected = new JsonMatches( - $expectedJson - ); + self::assertFileExists($actualFile, $message); + + $actualJson = file_get_contents($actualFile); - $constraintActual = new JsonMatches($actualJson); + self::assertIsString($actualJson); + self::assertJson($actualJson, $message); - static::assertThat($expectedJson, new LogicalNot($constraintActual), $message); - static::assertThat($actualJson, new LogicalNot($constraintExpected), $message); + self::assertThat($actualJson, self::logicalNot(new JsonMatches($expectedJson)), $message); } /** * @throws Exception */ - public static function logicalAnd(): LogicalAnd + final public static function logicalAnd(mixed ...$constraints): LogicalAnd { - $constraints = func_get_args(); - - $constraint = new LogicalAnd; - $constraint->setConstraints($constraints); - - return $constraint; + return LogicalAnd::fromConstraints(...$constraints); } - public static function logicalOr(): LogicalOr + final public static function logicalOr(mixed ...$constraints): LogicalOr { - $constraints = func_get_args(); - - $constraint = new LogicalOr; - $constraint->setConstraints($constraints); - - return $constraint; + return LogicalOr::fromConstraints(...$constraints); } - public static function logicalNot(Constraint $constraint): LogicalNot + final public static function logicalNot(Constraint $constraint): LogicalNot { return new LogicalNot($constraint); } - public static function logicalXor(): LogicalXor + final public static function logicalXor(mixed ...$constraints): LogicalXor { - $constraints = func_get_args(); - - $constraint = new LogicalXor; - $constraint->setConstraints($constraints); - - return $constraint; + return LogicalXor::fromConstraints(...$constraints); } - public static function anything(): IsAnything + final public static function anything(): IsAnything { return new IsAnything; } - public static function isTrue(): IsTrue + final public static function isTrue(): IsTrue { return new IsTrue; } - public static function callback(callable $callback): Callback + /** + * @template CallbackInput of mixed + * + * @param callable(CallbackInput $callback): bool $callback + * + * @return Callback + */ + final public static function callback(callable $callback): Callback { return new Callback($callback); } - public static function isFalse(): IsFalse + final public static function isFalse(): IsFalse { return new IsFalse; } - public static function isJson(): IsJson + final public static function isJson(): IsJson { return new IsJson; } - public static function isNull(): IsNull + final public static function isNull(): IsNull { return new IsNull; } - public static function isFinite(): IsFinite + final public static function isFinite(): IsFinite { return new IsFinite; } - public static function isInfinite(): IsInfinite + final public static function isInfinite(): IsInfinite { return new IsInfinite; } - public static function isNan(): IsNan + final public static function isNan(): IsNan { return new IsNan; } - public static function containsEqual($value): TraversableContainsEqual + final public static function containsEqual(mixed $value): TraversableContainsEqual { return new TraversableContainsEqual($value); } - public static function containsIdentical($value): TraversableContainsIdentical + final public static function containsIdentical(mixed $value): TraversableContainsIdentical { return new TraversableContainsIdentical($value); } - public static function containsOnly(string $type): TraversableContainsOnly + final public static function containsOnlyArray(): TraversableContainsOnly + { + return TraversableContainsOnly::forNativeType(NativeType::Array); + } + + final public static function containsOnlyBool(): TraversableContainsOnly + { + return TraversableContainsOnly::forNativeType(NativeType::Bool); + } + + final public static function containsOnlyCallable(): TraversableContainsOnly + { + return TraversableContainsOnly::forNativeType(NativeType::Callable); + } + + final public static function containsOnlyFloat(): TraversableContainsOnly + { + return TraversableContainsOnly::forNativeType(NativeType::Float); + } + + final public static function containsOnlyInt(): TraversableContainsOnly + { + return TraversableContainsOnly::forNativeType(NativeType::Int); + } + + final public static function containsOnlyIterable(): TraversableContainsOnly + { + return TraversableContainsOnly::forNativeType(NativeType::Iterable); + } + + final public static function containsOnlyNull(): TraversableContainsOnly + { + return TraversableContainsOnly::forNativeType(NativeType::Null); + } + + final public static function containsOnlyNumeric(): TraversableContainsOnly + { + return TraversableContainsOnly::forNativeType(NativeType::Numeric); + } + + final public static function containsOnlyObject(): TraversableContainsOnly + { + return TraversableContainsOnly::forNativeType(NativeType::Object); + } + + final public static function containsOnlyResource(): TraversableContainsOnly + { + return TraversableContainsOnly::forNativeType(NativeType::Resource); + } + + final public static function containsOnlyClosedResource(): TraversableContainsOnly + { + return TraversableContainsOnly::forNativeType(NativeType::ClosedResource); + } + + final public static function containsOnlyScalar(): TraversableContainsOnly { - return new TraversableContainsOnly($type); + return TraversableContainsOnly::forNativeType(NativeType::Scalar); } - public static function containsOnlyInstancesOf(string $className): TraversableContainsOnly + final public static function containsOnlyString(): TraversableContainsOnly { - return new TraversableContainsOnly($className, false); + return TraversableContainsOnly::forNativeType(NativeType::String); } /** - * @param int|string $key + * @param class-string $className + * + * @throws Exception */ - public static function arrayHasKey($key): ArrayHasKey + final public static function containsOnlyInstancesOf(string $className): TraversableContainsOnly + { + return TraversableContainsOnly::forClassOrInterface($className); + } + + final public static function arrayHasKey(mixed $key): ArrayHasKey { return new ArrayHasKey($key); } - public static function equalTo($value): IsEqual + final public static function isList(): IsList + { + return new IsList; + } + + final public static function equalTo(mixed $value): IsEqual { - return new IsEqual($value, 0.0, false, false); + return new IsEqual($value); } - public static function equalToCanonicalizing($value): IsEqualCanonicalizing + final public static function equalToCanonicalizing(mixed $value): IsEqualCanonicalizing { return new IsEqualCanonicalizing($value); } - public static function equalToIgnoringCase($value): IsEqualIgnoringCase + final public static function equalToIgnoringCase(mixed $value): IsEqualIgnoringCase { return new IsEqualIgnoringCase($value); } - public static function equalToWithDelta($value, float $delta): IsEqualWithDelta + final public static function equalToWithDelta(mixed $value, float $delta): IsEqualWithDelta { return new IsEqualWithDelta($value, $delta); } - public static function isEmpty(): IsEmpty + final public static function isEmpty(): IsEmpty { return new IsEmpty; } - public static function isWritable(): IsWritable + final public static function isWritable(): IsWritable { return new IsWritable; } - public static function isReadable(): IsReadable + final public static function isReadable(): IsReadable { return new IsReadable; } - public static function directoryExists(): DirectoryExists + final public static function directoryExists(): DirectoryExists { return new DirectoryExists; } - public static function fileExists(): FileExists + final public static function fileExists(): FileExists { return new FileExists; } - public static function greaterThan($value): GreaterThan + final public static function greaterThan(mixed $value): GreaterThan { return new GreaterThan($value); } - public static function greaterThanOrEqual($value): LogicalOr + final public static function greaterThanOrEqual(mixed $value): LogicalOr { - return static::logicalOr( + return self::logicalOr( new IsEqual($value), - new GreaterThan($value) + new GreaterThan($value), ); } - public static function classHasAttribute(string $attributeName): ClassHasAttribute + final public static function identicalTo(mixed $value): IsIdentical + { + return new IsIdentical($value); + } + + /** + * @throws UnknownClassOrInterfaceException + */ + final public static function isInstanceOf(string $className): IsInstanceOf { - return new ClassHasAttribute($attributeName); + return new IsInstanceOf($className); } - public static function classHasStaticAttribute(string $attributeName): ClassHasStaticAttribute + final public static function isArray(): IsType { - return new ClassHasStaticAttribute($attributeName); + return new IsType(NativeType::Array); } - public static function objectHasAttribute($attributeName): ObjectHasAttribute + final public static function isBool(): IsType { - return new ObjectHasAttribute($attributeName); + return new IsType(NativeType::Bool); } - public static function identicalTo($value): IsIdentical + final public static function isCallable(): IsType { - return new IsIdentical($value); + return new IsType(NativeType::Callable); } - public static function isInstanceOf(string $className): IsInstanceOf + final public static function isFloat(): IsType { - return new IsInstanceOf($className); + return new IsType(NativeType::Float); + } + + final public static function isInt(): IsType + { + return new IsType(NativeType::Int); + } + + final public static function isIterable(): IsType + { + return new IsType(NativeType::Iterable); + } + + final public static function isNumeric(): IsType + { + return new IsType(NativeType::Numeric); + } + + final public static function isObject(): IsType + { + return new IsType(NativeType::Object); + } + + final public static function isResource(): IsType + { + return new IsType(NativeType::Resource); + } + + final public static function isClosedResource(): IsType + { + return new IsType(NativeType::ClosedResource); } - public static function isType(string $type): IsType + final public static function isScalar(): IsType { - return new IsType($type); + return new IsType(NativeType::Scalar); } - public static function lessThan($value): LessThan + final public static function isString(): IsType + { + return new IsType(NativeType::String); + } + + final public static function lessThan(mixed $value): LessThan { return new LessThan($value); } - public static function lessThanOrEqual($value): LogicalOr + final public static function lessThanOrEqual(mixed $value): LogicalOr { - return static::logicalOr( + return self::logicalOr( new IsEqual($value), - new LessThan($value) + new LessThan($value), ); } - public static function matchesRegularExpression(string $pattern): RegularExpression + final public static function matchesRegularExpression(string $pattern): RegularExpression { return new RegularExpression($pattern); } - public static function matches(string $string): StringMatchesFormatDescription + final public static function matches(string $string): StringMatchesFormatDescription { return new StringMatchesFormatDescription($string); } - public static function stringStartsWith($prefix): StringStartsWith + /** + * @param non-empty-string $prefix + * + * @throws InvalidArgumentException + */ + final public static function stringStartsWith(string $prefix): StringStartsWith { return new StringStartsWith($prefix); } - public static function stringContains(string $string, bool $case = true): StringContains + final public static function stringContains(string $string, bool $case = true): StringContains { return new StringContains($string, $case); } - public static function stringEndsWith(string $suffix): StringEndsWith + /** + * @param non-empty-string $suffix + * + * @throws InvalidArgumentException + */ + final public static function stringEndsWith(string $suffix): StringEndsWith { return new StringEndsWith($suffix); } - public static function countOf(int $count): Count + final public static function stringEqualsStringIgnoringLineEndings(string $string): StringEqualsStringIgnoringLineEndings + { + return new StringEqualsStringIgnoringLineEndings($string); + } + + final public static function countOf(int $count): Count { return new Count($count); } + final public static function objectEquals(object $object, string $method = 'equals'): ObjectEquals + { + return new ObjectEquals($object, $method); + } + /** * Fails a test with the given message. * * @throws AssertionFailedError - * - * @psalm-return never-return */ - public static function fail(string $message = ''): void + final public static function fail(string $message = ''): never { self::$count++; @@ -2658,10 +2968,8 @@ public static function fail(string $message = ''): void * Mark the test as incomplete. * * @throws IncompleteTestError - * - * @psalm-return never-return */ - public static function markTestIncomplete(string $message = ''): void + final public static function markTestIncomplete(string $message = ''): never { throw new IncompleteTestError($message); } @@ -2669,27 +2977,17 @@ public static function markTestIncomplete(string $message = ''): void /** * Mark the test as skipped. * - * @throws SkippedTestError - * @throws SyntheticSkippedError - * - * @psalm-return never-return + * @throws SkippedWithMessageException */ - public static function markTestSkipped(string $message = ''): void + final public static function markTestSkipped(string $message = ''): never { - if ($hint = self::detectLocationHint($message)) { - $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); - array_unshift($trace, $hint); - - throw new SyntheticSkippedError($hint['message'], 0, $hint['file'], (int) $hint['line'], $trace); - } - - throw new SkippedTestError($message); + throw new SkippedWithMessageException($message); } /** * Return the current assertion count. */ - public static function getCount(): int + final public static function getCount(): int { return self::$count; } @@ -2697,58 +2995,8 @@ public static function getCount(): int /** * Reset the assertion counter. */ - public static function resetCount(): void + final public static function resetCount(): void { self::$count = 0; } - - private static function detectLocationHint(string $message): ?array - { - $hint = null; - $lines = preg_split('/\r\n|\r|\n/', $message); - - while (strpos($lines[0], '__OFFSET') !== false) { - $offset = explode('=', array_shift($lines)); - - if ($offset[0] === '__OFFSET_FILE') { - $hint['file'] = $offset[1]; - } - - if ($offset[0] === '__OFFSET_LINE') { - $hint['line'] = $offset[1]; - } - } - - if ($hint) { - $hint['message'] = implode(PHP_EOL, $lines); - } - - return $hint; - } - - private static function isValidObjectAttributeName(string $attributeName): bool - { - return (bool) preg_match('/[^\x00-\x1f\x7f-\x9f]+/', $attributeName); - } - - private static function isValidClassAttributeName(string $attributeName): bool - { - return (bool) preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName); - } - - /** - * @codeCoverageIgnore - */ - private static function createWarning(string $warning): void - { - foreach (debug_backtrace() as $step) { - if (isset($step['object']) && $step['object'] instanceof TestCase) { - assert($step['object'] instanceof TestCase); - - $step['object']->addWarning($warning); - - break; - } - } - } } diff --git a/src/Framework/Assert/Functions.php b/src/Framework/Assert/Functions.php index c50568e45ce..cf0d3e3b85d 100644 --- a/src/Framework/Assert/Functions.php +++ b/src/Framework/Assert/Functions.php @@ -10,14 +10,11 @@ namespace PHPUnit\Framework; use function func_get_args; +use function function_exists; use ArrayAccess; use Countable; -use DOMDocument; -use DOMElement; use PHPUnit\Framework\Constraint\ArrayHasKey; use PHPUnit\Framework\Constraint\Callback; -use PHPUnit\Framework\Constraint\ClassHasAttribute; -use PHPUnit\Framework\Constraint\ClassHasStaticAttribute; use PHPUnit\Framework\Constraint\Constraint; use PHPUnit\Framework\Constraint\Count; use PHPUnit\Framework\Constraint\DirectoryExists; @@ -35,6 +32,7 @@ use PHPUnit\Framework\Constraint\IsInfinite; use PHPUnit\Framework\Constraint\IsInstanceOf; use PHPUnit\Framework\Constraint\IsJson; +use PHPUnit\Framework\Constraint\IsList; use PHPUnit\Framework\Constraint\IsNan; use PHPUnit\Framework\Constraint\IsNull; use PHPUnit\Framework\Constraint\IsReadable; @@ -46,2267 +44,3339 @@ use PHPUnit\Framework\Constraint\LogicalNot; use PHPUnit\Framework\Constraint\LogicalOr; use PHPUnit\Framework\Constraint\LogicalXor; -use PHPUnit\Framework\Constraint\ObjectHasAttribute; +use PHPUnit\Framework\Constraint\ObjectEquals; use PHPUnit\Framework\Constraint\RegularExpression; use PHPUnit\Framework\Constraint\StringContains; use PHPUnit\Framework\Constraint\StringEndsWith; +use PHPUnit\Framework\Constraint\StringEqualsStringIgnoringLineEndings; use PHPUnit\Framework\Constraint\StringMatchesFormatDescription; use PHPUnit\Framework\Constraint\StringStartsWith; use PHPUnit\Framework\Constraint\TraversableContainsEqual; use PHPUnit\Framework\Constraint\TraversableContainsIdentical; use PHPUnit\Framework\Constraint\TraversableContainsOnly; use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount as AnyInvokedCountMatcher; -use PHPUnit\Framework\MockObject\Rule\InvokedAtIndex as InvokedAtIndexMatcher; use PHPUnit\Framework\MockObject\Rule\InvokedAtLeastCount as InvokedAtLeastCountMatcher; use PHPUnit\Framework\MockObject\Rule\InvokedAtLeastOnce as InvokedAtLeastOnceMatcher; use PHPUnit\Framework\MockObject\Rule\InvokedAtMostCount as InvokedAtMostCountMatcher; use PHPUnit\Framework\MockObject\Rule\InvokedCount as InvokedCountMatcher; -use PHPUnit\Framework\MockObject\Stub\ConsecutiveCalls as ConsecutiveCallsStub; use PHPUnit\Framework\MockObject\Stub\Exception as ExceptionStub; -use PHPUnit\Framework\MockObject\Stub\ReturnArgument as ReturnArgumentStub; -use PHPUnit\Framework\MockObject\Stub\ReturnCallback as ReturnCallbackStub; -use PHPUnit\Framework\MockObject\Stub\ReturnSelf as ReturnSelfStub; -use PHPUnit\Framework\MockObject\Stub\ReturnStub; -use PHPUnit\Framework\MockObject\Stub\ReturnValueMap as ReturnValueMapStub; +use PHPUnit\Util\Xml\XmlException; use Throwable; -/** - * Asserts that an array has a specified key. - * - * @param int|string $key - * @param array|ArrayAccess $array - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @see Assert::assertArrayHasKey - */ -function assertArrayHasKey($key, $array, string $message = ''): void -{ - Assert::assertArrayHasKey(...func_get_args()); -} - -/** - * Asserts that an array does not have a specified key. - * - * @param int|string $key - * @param array|ArrayAccess $array - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @see Assert::assertArrayNotHasKey - */ -function assertArrayNotHasKey($key, $array, string $message = ''): void -{ - Assert::assertArrayNotHasKey(...func_get_args()); -} - -/** - * Asserts that a haystack contains a needle. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @see Assert::assertContains - */ -function assertContains($needle, iterable $haystack, string $message = ''): void -{ - Assert::assertContains(...func_get_args()); -} - -function assertContainsEquals($needle, iterable $haystack, string $message = ''): void -{ - Assert::assertContainsEquals(...func_get_args()); -} - -/** - * Asserts that a haystack does not contain a needle. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @see Assert::assertNotContains - */ -function assertNotContains($needle, iterable $haystack, string $message = ''): void -{ - Assert::assertNotContains(...func_get_args()); -} - -function assertNotContainsEquals($needle, iterable $haystack, string $message = ''): void -{ - Assert::assertNotContainsEquals(...func_get_args()); -} - -/** - * Asserts that a haystack contains only values of a given type. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertContainsOnly - */ -function assertContainsOnly(string $type, iterable $haystack, ?bool $isNativeType = null, string $message = ''): void -{ - Assert::assertContainsOnly(...func_get_args()); -} - -/** - * Asserts that a haystack contains only instances of a given class name. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertContainsOnlyInstancesOf - */ -function assertContainsOnlyInstancesOf(string $className, iterable $haystack, string $message = ''): void -{ - Assert::assertContainsOnlyInstancesOf(...func_get_args()); -} - -/** - * Asserts that a haystack does not contain only values of a given type. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertNotContainsOnly - */ -function assertNotContainsOnly(string $type, iterable $haystack, ?bool $isNativeType = null, string $message = ''): void -{ - Assert::assertNotContainsOnly(...func_get_args()); -} - -/** - * Asserts the number of elements of an array, Countable or Traversable. - * - * @param Countable|iterable $haystack - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @see Assert::assertCount - */ -function assertCount(int $expectedCount, $haystack, string $message = ''): void -{ - Assert::assertCount(...func_get_args()); -} - -/** - * Asserts the number of elements of an array, Countable or Traversable. - * - * @param Countable|iterable $haystack - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @see Assert::assertNotCount - */ -function assertNotCount(int $expectedCount, $haystack, string $message = ''): void -{ - Assert::assertNotCount(...func_get_args()); -} - -/** - * Asserts that two variables are equal. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertEquals - */ -function assertEquals($expected, $actual, string $message = ''): void -{ - Assert::assertEquals(...func_get_args()); -} - -/** - * Asserts that two variables are equal (canonicalizing). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertEqualsCanonicalizing - */ -function assertEqualsCanonicalizing($expected, $actual, string $message = ''): void -{ - Assert::assertEqualsCanonicalizing(...func_get_args()); -} - -/** - * Asserts that two variables are equal (ignoring case). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertEqualsIgnoringCase - */ -function assertEqualsIgnoringCase($expected, $actual, string $message = ''): void -{ - Assert::assertEqualsIgnoringCase(...func_get_args()); -} - -/** - * Asserts that two variables are equal (with delta). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertEqualsWithDelta - */ -function assertEqualsWithDelta($expected, $actual, float $delta, string $message = ''): void -{ - Assert::assertEqualsWithDelta(...func_get_args()); -} - -/** - * Asserts that two variables are not equal. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertNotEquals - */ -function assertNotEquals($expected, $actual, string $message = ''): void -{ - Assert::assertNotEquals(...func_get_args()); -} - -/** - * Asserts that two variables are not equal (canonicalizing). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertNotEqualsCanonicalizing - */ -function assertNotEqualsCanonicalizing($expected, $actual, string $message = ''): void -{ - Assert::assertNotEqualsCanonicalizing(...func_get_args()); -} - -/** - * Asserts that two variables are not equal (ignoring case). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertNotEqualsIgnoringCase - */ -function assertNotEqualsIgnoringCase($expected, $actual, string $message = ''): void -{ - Assert::assertNotEqualsIgnoringCase(...func_get_args()); -} - -/** - * Asserts that two variables are not equal (with delta). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertNotEqualsWithDelta - */ -function assertNotEqualsWithDelta($expected, $actual, float $delta, string $message = ''): void -{ - Assert::assertNotEqualsWithDelta(...func_get_args()); -} - -/** - * Asserts that a variable is empty. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert empty $actual - * - * @see Assert::assertEmpty - */ -function assertEmpty($actual, string $message = ''): void -{ - Assert::assertEmpty(...func_get_args()); -} - -/** - * Asserts that a variable is not empty. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !empty $actual - * - * @see Assert::assertNotEmpty - */ -function assertNotEmpty($actual, string $message = ''): void -{ - Assert::assertNotEmpty(...func_get_args()); -} - -/** - * Asserts that a value is greater than another value. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertGreaterThan - */ -function assertGreaterThan($expected, $actual, string $message = ''): void -{ - Assert::assertGreaterThan(...func_get_args()); -} - -/** - * Asserts that a value is greater than or equal to another value. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertGreaterThanOrEqual - */ -function assertGreaterThanOrEqual($expected, $actual, string $message = ''): void -{ - Assert::assertGreaterThanOrEqual(...func_get_args()); -} - -/** - * Asserts that a value is smaller than another value. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertLessThan - */ -function assertLessThan($expected, $actual, string $message = ''): void -{ - Assert::assertLessThan(...func_get_args()); -} - -/** - * Asserts that a value is smaller than or equal to another value. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertLessThanOrEqual - */ -function assertLessThanOrEqual($expected, $actual, string $message = ''): void -{ - Assert::assertLessThanOrEqual(...func_get_args()); -} - -/** - * Asserts that the contents of one file is equal to the contents of another - * file. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertFileEquals - */ -function assertFileEquals(string $expected, string $actual, string $message = ''): void -{ - Assert::assertFileEquals(...func_get_args()); -} - -/** - * Asserts that the contents of one file is equal to the contents of another - * file (canonicalizing). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertFileEqualsCanonicalizing - */ -function assertFileEqualsCanonicalizing(string $expected, string $actual, string $message = ''): void -{ - Assert::assertFileEqualsCanonicalizing(...func_get_args()); -} - -/** - * Asserts that the contents of one file is equal to the contents of another - * file (ignoring case). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertFileEqualsIgnoringCase - */ -function assertFileEqualsIgnoringCase(string $expected, string $actual, string $message = ''): void -{ - Assert::assertFileEqualsIgnoringCase(...func_get_args()); -} - -/** - * Asserts that the contents of one file is not equal to the contents of - * another file. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertFileNotEquals - */ -function assertFileNotEquals(string $expected, string $actual, string $message = ''): void -{ - Assert::assertFileNotEquals(...func_get_args()); -} - -/** - * Asserts that the contents of one file is not equal to the contents of another - * file (canonicalizing). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertFileNotEqualsCanonicalizing - */ -function assertFileNotEqualsCanonicalizing(string $expected, string $actual, string $message = ''): void -{ - Assert::assertFileNotEqualsCanonicalizing(...func_get_args()); -} - -/** - * Asserts that the contents of one file is not equal to the contents of another - * file (ignoring case). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertFileNotEqualsIgnoringCase - */ -function assertFileNotEqualsIgnoringCase(string $expected, string $actual, string $message = ''): void -{ - Assert::assertFileNotEqualsIgnoringCase(...func_get_args()); -} - -/** - * Asserts that the contents of a string is equal - * to the contents of a file. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertStringEqualsFile - */ -function assertStringEqualsFile(string $expectedFile, string $actualString, string $message = ''): void -{ - Assert::assertStringEqualsFile(...func_get_args()); -} - -/** - * Asserts that the contents of a string is equal - * to the contents of a file (canonicalizing). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertStringEqualsFileCanonicalizing - */ -function assertStringEqualsFileCanonicalizing(string $expectedFile, string $actualString, string $message = ''): void -{ - Assert::assertStringEqualsFileCanonicalizing(...func_get_args()); -} - -/** - * Asserts that the contents of a string is equal - * to the contents of a file (ignoring case). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertStringEqualsFileIgnoringCase - */ -function assertStringEqualsFileIgnoringCase(string $expectedFile, string $actualString, string $message = ''): void -{ - Assert::assertStringEqualsFileIgnoringCase(...func_get_args()); -} - -/** - * Asserts that the contents of a string is not equal - * to the contents of a file. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertStringNotEqualsFile - */ -function assertStringNotEqualsFile(string $expectedFile, string $actualString, string $message = ''): void -{ - Assert::assertStringNotEqualsFile(...func_get_args()); -} - -/** - * Asserts that the contents of a string is not equal - * to the contents of a file (canonicalizing). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertStringNotEqualsFileCanonicalizing - */ -function assertStringNotEqualsFileCanonicalizing(string $expectedFile, string $actualString, string $message = ''): void -{ - Assert::assertStringNotEqualsFileCanonicalizing(...func_get_args()); -} - -/** - * Asserts that the contents of a string is not equal - * to the contents of a file (ignoring case). - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertStringNotEqualsFileIgnoringCase - */ -function assertStringNotEqualsFileIgnoringCase(string $expectedFile, string $actualString, string $message = ''): void -{ - Assert::assertStringNotEqualsFileIgnoringCase(...func_get_args()); -} - -/** - * Asserts that a file/dir is readable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertIsReadable - */ -function assertIsReadable(string $filename, string $message = ''): void -{ - Assert::assertIsReadable(...func_get_args()); -} - -/** - * Asserts that a file/dir exists and is not readable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertIsNotReadable - */ -function assertIsNotReadable(string $filename, string $message = ''): void -{ - Assert::assertIsNotReadable(...func_get_args()); -} - -/** - * Asserts that a file/dir exists and is not readable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4062 - * @see Assert::assertNotIsReadable - */ -function assertNotIsReadable(string $filename, string $message = ''): void -{ - Assert::assertNotIsReadable(...func_get_args()); -} - -/** - * Asserts that a file/dir exists and is writable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertIsWritable - */ -function assertIsWritable(string $filename, string $message = ''): void -{ - Assert::assertIsWritable(...func_get_args()); -} - -/** - * Asserts that a file/dir exists and is not writable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertIsNotWritable - */ -function assertIsNotWritable(string $filename, string $message = ''): void -{ - Assert::assertIsNotWritable(...func_get_args()); -} - -/** - * Asserts that a file/dir exists and is not writable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4065 - * @see Assert::assertNotIsWritable - */ -function assertNotIsWritable(string $filename, string $message = ''): void -{ - Assert::assertNotIsWritable(...func_get_args()); -} - -/** - * Asserts that a directory exists. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertDirectoryExists - */ -function assertDirectoryExists(string $directory, string $message = ''): void -{ - Assert::assertDirectoryExists(...func_get_args()); -} - -/** - * Asserts that a directory does not exist. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertDirectoryDoesNotExist - */ -function assertDirectoryDoesNotExist(string $directory, string $message = ''): void -{ - Assert::assertDirectoryDoesNotExist(...func_get_args()); -} - -/** - * Asserts that a directory does not exist. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4068 - * @see Assert::assertDirectoryNotExists - */ -function assertDirectoryNotExists(string $directory, string $message = ''): void -{ - Assert::assertDirectoryNotExists(...func_get_args()); -} - -/** - * Asserts that a directory exists and is readable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertDirectoryIsReadable - */ -function assertDirectoryIsReadable(string $directory, string $message = ''): void -{ - Assert::assertDirectoryIsReadable(...func_get_args()); -} - -/** - * Asserts that a directory exists and is not readable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertDirectoryIsNotReadable - */ -function assertDirectoryIsNotReadable(string $directory, string $message = ''): void -{ - Assert::assertDirectoryIsNotReadable(...func_get_args()); -} - -/** - * Asserts that a directory exists and is not readable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4071 - * @see Assert::assertDirectoryNotIsReadable - */ -function assertDirectoryNotIsReadable(string $directory, string $message = ''): void -{ - Assert::assertDirectoryNotIsReadable(...func_get_args()); -} - -/** - * Asserts that a directory exists and is writable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertDirectoryIsWritable - */ -function assertDirectoryIsWritable(string $directory, string $message = ''): void -{ - Assert::assertDirectoryIsWritable(...func_get_args()); -} - -/** - * Asserts that a directory exists and is not writable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertDirectoryIsNotWritable - */ -function assertDirectoryIsNotWritable(string $directory, string $message = ''): void -{ - Assert::assertDirectoryIsNotWritable(...func_get_args()); -} - -/** - * Asserts that a directory exists and is not writable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4074 - * @see Assert::assertDirectoryNotIsWritable - */ -function assertDirectoryNotIsWritable(string $directory, string $message = ''): void -{ - Assert::assertDirectoryNotIsWritable(...func_get_args()); -} - -/** - * Asserts that a file exists. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertFileExists - */ -function assertFileExists(string $filename, string $message = ''): void -{ - Assert::assertFileExists(...func_get_args()); -} - -/** - * Asserts that a file does not exist. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertFileDoesNotExist - */ -function assertFileDoesNotExist(string $filename, string $message = ''): void -{ - Assert::assertFileDoesNotExist(...func_get_args()); -} - -/** - * Asserts that a file does not exist. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4077 - * @see Assert::assertFileNotExists - */ -function assertFileNotExists(string $filename, string $message = ''): void -{ - Assert::assertFileNotExists(...func_get_args()); -} - -/** - * Asserts that a file exists and is readable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertFileIsReadable - */ -function assertFileIsReadable(string $file, string $message = ''): void -{ - Assert::assertFileIsReadable(...func_get_args()); -} - -/** - * Asserts that a file exists and is not readable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertFileIsNotReadable - */ -function assertFileIsNotReadable(string $file, string $message = ''): void -{ - Assert::assertFileIsNotReadable(...func_get_args()); -} - -/** - * Asserts that a file exists and is not readable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4080 - * @see Assert::assertFileNotIsReadable - */ -function assertFileNotIsReadable(string $file, string $message = ''): void -{ - Assert::assertFileNotIsReadable(...func_get_args()); -} - -/** - * Asserts that a file exists and is writable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertFileIsWritable - */ -function assertFileIsWritable(string $file, string $message = ''): void -{ - Assert::assertFileIsWritable(...func_get_args()); -} - -/** - * Asserts that a file exists and is not writable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertFileIsNotWritable - */ -function assertFileIsNotWritable(string $file, string $message = ''): void -{ - Assert::assertFileIsNotWritable(...func_get_args()); -} - -/** - * Asserts that a file exists and is not writable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4083 - * @see Assert::assertFileNotIsWritable - */ -function assertFileNotIsWritable(string $file, string $message = ''): void -{ - Assert::assertFileNotIsWritable(...func_get_args()); -} - -/** - * Asserts that a condition is true. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert true $condition - * - * @see Assert::assertTrue - */ -function assertTrue($condition, string $message = ''): void -{ - Assert::assertTrue(...func_get_args()); -} - -/** - * Asserts that a condition is not true. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !true $condition - * - * @see Assert::assertNotTrue - */ -function assertNotTrue($condition, string $message = ''): void -{ - Assert::assertNotTrue(...func_get_args()); -} - -/** - * Asserts that a condition is false. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert false $condition - * - * @see Assert::assertFalse - */ -function assertFalse($condition, string $message = ''): void -{ - Assert::assertFalse(...func_get_args()); -} - -/** - * Asserts that a condition is not false. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !false $condition - * - * @see Assert::assertNotFalse - */ -function assertNotFalse($condition, string $message = ''): void -{ - Assert::assertNotFalse(...func_get_args()); -} - -/** - * Asserts that a variable is null. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert null $actual - * - * @see Assert::assertNull - */ -function assertNull($actual, string $message = ''): void -{ - Assert::assertNull(...func_get_args()); -} - -/** - * Asserts that a variable is not null. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !null $actual - * - * @see Assert::assertNotNull - */ -function assertNotNull($actual, string $message = ''): void -{ - Assert::assertNotNull(...func_get_args()); -} - -/** - * Asserts that a variable is finite. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertFinite - */ -function assertFinite($actual, string $message = ''): void -{ - Assert::assertFinite(...func_get_args()); -} - -/** - * Asserts that a variable is infinite. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertInfinite - */ -function assertInfinite($actual, string $message = ''): void -{ - Assert::assertInfinite(...func_get_args()); -} - -/** - * Asserts that a variable is nan. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertNan - */ -function assertNan($actual, string $message = ''): void -{ - Assert::assertNan(...func_get_args()); -} - -/** - * Asserts that a class has a specified attribute. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @see Assert::assertClassHasAttribute - */ -function assertClassHasAttribute(string $attributeName, string $className, string $message = ''): void -{ - Assert::assertClassHasAttribute(...func_get_args()); -} - -/** - * Asserts that a class does not have a specified attribute. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @see Assert::assertClassNotHasAttribute - */ -function assertClassNotHasAttribute(string $attributeName, string $className, string $message = ''): void -{ - Assert::assertClassNotHasAttribute(...func_get_args()); -} - -/** - * Asserts that a class has a specified static attribute. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @see Assert::assertClassHasStaticAttribute - */ -function assertClassHasStaticAttribute(string $attributeName, string $className, string $message = ''): void -{ - Assert::assertClassHasStaticAttribute(...func_get_args()); -} - -/** - * Asserts that a class does not have a specified static attribute. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @see Assert::assertClassNotHasStaticAttribute - */ -function assertClassNotHasStaticAttribute(string $attributeName, string $className, string $message = ''): void -{ - Assert::assertClassNotHasStaticAttribute(...func_get_args()); -} - -/** - * Asserts that an object has a specified attribute. - * - * @param object $object - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @see Assert::assertObjectHasAttribute - */ -function assertObjectHasAttribute(string $attributeName, $object, string $message = ''): void -{ - Assert::assertObjectHasAttribute(...func_get_args()); -} - -/** - * Asserts that an object does not have a specified attribute. - * - * @param object $object - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @see Assert::assertObjectNotHasAttribute - */ -function assertObjectNotHasAttribute(string $attributeName, $object, string $message = ''): void -{ - Assert::assertObjectNotHasAttribute(...func_get_args()); -} - -/** - * Asserts that two variables have the same type and value. - * Used on objects, it asserts that two variables reference - * the same object. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-template ExpectedType - * @psalm-param ExpectedType $expected - * @psalm-assert =ExpectedType $actual - * - * @see Assert::assertSame - */ -function assertSame($expected, $actual, string $message = ''): void -{ - Assert::assertSame(...func_get_args()); -} - -/** - * Asserts that two variables do not have the same type and value. - * Used on objects, it asserts that two variables do not reference - * the same object. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertNotSame - */ -function assertNotSame($expected, $actual, string $message = ''): void -{ - Assert::assertNotSame(...func_get_args()); -} - -/** - * Asserts that a variable is of a given type. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @psalm-template ExpectedType of object - * @psalm-param class-string $expected - * @psalm-assert ExpectedType $actual - * - * @see Assert::assertInstanceOf - */ -function assertInstanceOf(string $expected, $actual, string $message = ''): void -{ - Assert::assertInstanceOf(...func_get_args()); -} - -/** - * Asserts that a variable is not of a given type. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @psalm-template ExpectedType of object - * @psalm-param class-string $expected - * @psalm-assert !ExpectedType $actual - * - * @see Assert::assertNotInstanceOf - */ -function assertNotInstanceOf(string $expected, $actual, string $message = ''): void -{ - Assert::assertNotInstanceOf(...func_get_args()); -} - -/** - * Asserts that a variable is of type array. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert array $actual - * - * @see Assert::assertIsArray - */ -function assertIsArray($actual, string $message = ''): void -{ - Assert::assertIsArray(...func_get_args()); -} - -/** - * Asserts that a variable is of type bool. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert bool $actual - * - * @see Assert::assertIsBool - */ -function assertIsBool($actual, string $message = ''): void -{ - Assert::assertIsBool(...func_get_args()); -} - -/** - * Asserts that a variable is of type float. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert float $actual - * - * @see Assert::assertIsFloat - */ -function assertIsFloat($actual, string $message = ''): void -{ - Assert::assertIsFloat(...func_get_args()); -} - -/** - * Asserts that a variable is of type int. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert int $actual - * - * @see Assert::assertIsInt - */ -function assertIsInt($actual, string $message = ''): void -{ - Assert::assertIsInt(...func_get_args()); -} - -/** - * Asserts that a variable is of type numeric. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert numeric $actual - * - * @see Assert::assertIsNumeric - */ -function assertIsNumeric($actual, string $message = ''): void -{ - Assert::assertIsNumeric(...func_get_args()); -} - -/** - * Asserts that a variable is of type object. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert object $actual - * - * @see Assert::assertIsObject - */ -function assertIsObject($actual, string $message = ''): void -{ - Assert::assertIsObject(...func_get_args()); -} - -/** - * Asserts that a variable is of type resource. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert resource $actual - * - * @see Assert::assertIsResource - */ -function assertIsResource($actual, string $message = ''): void -{ - Assert::assertIsResource(...func_get_args()); -} - -/** - * Asserts that a variable is of type string. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert string $actual - * - * @see Assert::assertIsString - */ -function assertIsString($actual, string $message = ''): void -{ - Assert::assertIsString(...func_get_args()); -} - -/** - * Asserts that a variable is of type scalar. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert scalar $actual - * - * @see Assert::assertIsScalar - */ -function assertIsScalar($actual, string $message = ''): void -{ - Assert::assertIsScalar(...func_get_args()); -} - -/** - * Asserts that a variable is of type callable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert callable $actual - * - * @see Assert::assertIsCallable - */ -function assertIsCallable($actual, string $message = ''): void -{ - Assert::assertIsCallable(...func_get_args()); -} - -/** - * Asserts that a variable is of type iterable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert iterable $actual - * - * @see Assert::assertIsIterable - */ -function assertIsIterable($actual, string $message = ''): void -{ - Assert::assertIsIterable(...func_get_args()); -} - -/** - * Asserts that a variable is not of type array. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !array $actual - * - * @see Assert::assertIsNotArray - */ -function assertIsNotArray($actual, string $message = ''): void -{ - Assert::assertIsNotArray(...func_get_args()); -} - -/** - * Asserts that a variable is not of type bool. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !bool $actual - * - * @see Assert::assertIsNotBool - */ -function assertIsNotBool($actual, string $message = ''): void -{ - Assert::assertIsNotBool(...func_get_args()); -} - -/** - * Asserts that a variable is not of type float. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !float $actual - * - * @see Assert::assertIsNotFloat - */ -function assertIsNotFloat($actual, string $message = ''): void -{ - Assert::assertIsNotFloat(...func_get_args()); -} - -/** - * Asserts that a variable is not of type int. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !int $actual - * - * @see Assert::assertIsNotInt - */ -function assertIsNotInt($actual, string $message = ''): void -{ - Assert::assertIsNotInt(...func_get_args()); -} - -/** - * Asserts that a variable is not of type numeric. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !numeric $actual - * - * @see Assert::assertIsNotNumeric - */ -function assertIsNotNumeric($actual, string $message = ''): void -{ - Assert::assertIsNotNumeric(...func_get_args()); -} - -/** - * Asserts that a variable is not of type object. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !object $actual - * - * @see Assert::assertIsNotObject - */ -function assertIsNotObject($actual, string $message = ''): void -{ - Assert::assertIsNotObject(...func_get_args()); -} - -/** - * Asserts that a variable is not of type resource. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !resource $actual - * - * @see Assert::assertIsNotResource - */ -function assertIsNotResource($actual, string $message = ''): void -{ - Assert::assertIsNotResource(...func_get_args()); -} - -/** - * Asserts that a variable is not of type string. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !string $actual - * - * @see Assert::assertIsNotString - */ -function assertIsNotString($actual, string $message = ''): void -{ - Assert::assertIsNotString(...func_get_args()); -} - -/** - * Asserts that a variable is not of type scalar. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !scalar $actual - * - * @see Assert::assertIsNotScalar - */ -function assertIsNotScalar($actual, string $message = ''): void -{ - Assert::assertIsNotScalar(...func_get_args()); -} - -/** - * Asserts that a variable is not of type callable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !callable $actual - * - * @see Assert::assertIsNotCallable - */ -function assertIsNotCallable($actual, string $message = ''): void -{ - Assert::assertIsNotCallable(...func_get_args()); -} - -/** - * Asserts that a variable is not of type iterable. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-assert !iterable $actual - * - * @see Assert::assertIsNotIterable - */ -function assertIsNotIterable($actual, string $message = ''): void -{ - Assert::assertIsNotIterable(...func_get_args()); -} - -/** - * Asserts that a string matches a given regular expression. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertMatchesRegularExpression - */ -function assertMatchesRegularExpression(string $pattern, string $string, string $message = ''): void -{ - Assert::assertMatchesRegularExpression(...func_get_args()); -} - -/** - * Asserts that a string matches a given regular expression. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4086 - * @see Assert::assertRegExp - */ -function assertRegExp(string $pattern, string $string, string $message = ''): void -{ - Assert::assertRegExp(...func_get_args()); -} - -/** - * Asserts that a string does not match a given regular expression. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertDoesNotMatchRegularExpression - */ -function assertDoesNotMatchRegularExpression(string $pattern, string $string, string $message = ''): void -{ - Assert::assertDoesNotMatchRegularExpression(...func_get_args()); -} - -/** - * Asserts that a string does not match a given regular expression. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4089 - * @see Assert::assertNotRegExp - */ -function assertNotRegExp(string $pattern, string $string, string $message = ''): void -{ - Assert::assertNotRegExp(...func_get_args()); -} - -/** - * Assert that the size of two arrays (or `Countable` or `Traversable` objects) - * is the same. - * - * @param Countable|iterable $expected - * @param Countable|iterable $actual - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @see Assert::assertSameSize - */ -function assertSameSize($expected, $actual, string $message = ''): void -{ - Assert::assertSameSize(...func_get_args()); -} - -/** - * Assert that the size of two arrays (or `Countable` or `Traversable` objects) - * is not the same. - * - * @param Countable|iterable $expected - * @param Countable|iterable $actual - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @see Assert::assertNotSameSize - */ -function assertNotSameSize($expected, $actual, string $message = ''): void -{ - Assert::assertNotSameSize(...func_get_args()); -} - -/** - * Asserts that a string matches a given format string. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertStringMatchesFormat - */ -function assertStringMatchesFormat(string $format, string $string, string $message = ''): void -{ - Assert::assertStringMatchesFormat(...func_get_args()); -} - -/** - * Asserts that a string does not match a given format string. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertStringNotMatchesFormat - */ -function assertStringNotMatchesFormat(string $format, string $string, string $message = ''): void -{ - Assert::assertStringNotMatchesFormat(...func_get_args()); -} - -/** - * Asserts that a string matches a given format file. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertStringMatchesFormatFile - */ -function assertStringMatchesFormatFile(string $formatFile, string $string, string $message = ''): void -{ - Assert::assertStringMatchesFormatFile(...func_get_args()); -} - -/** - * Asserts that a string does not match a given format string. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertStringNotMatchesFormatFile - */ -function assertStringNotMatchesFormatFile(string $formatFile, string $string, string $message = ''): void -{ - Assert::assertStringNotMatchesFormatFile(...func_get_args()); -} - -/** - * Asserts that a string starts with a given prefix. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertStringStartsWith - */ -function assertStringStartsWith(string $prefix, string $string, string $message = ''): void -{ - Assert::assertStringStartsWith(...func_get_args()); -} - -/** - * Asserts that a string starts not with a given prefix. - * - * @param string $prefix - * @param string $string - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertStringStartsNotWith - */ -function assertStringStartsNotWith($prefix, $string, string $message = ''): void -{ - Assert::assertStringStartsNotWith(...func_get_args()); -} - -/** - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertStringContainsString - */ -function assertStringContainsString(string $needle, string $haystack, string $message = ''): void -{ - Assert::assertStringContainsString(...func_get_args()); -} - -/** - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertStringContainsStringIgnoringCase - */ -function assertStringContainsStringIgnoringCase(string $needle, string $haystack, string $message = ''): void -{ - Assert::assertStringContainsStringIgnoringCase(...func_get_args()); -} - -/** - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertStringNotContainsString - */ -function assertStringNotContainsString(string $needle, string $haystack, string $message = ''): void -{ - Assert::assertStringNotContainsString(...func_get_args()); -} - -/** - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertStringNotContainsStringIgnoringCase - */ -function assertStringNotContainsStringIgnoringCase(string $needle, string $haystack, string $message = ''): void -{ - Assert::assertStringNotContainsStringIgnoringCase(...func_get_args()); -} - -/** - * Asserts that a string ends with a given suffix. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertStringEndsWith - */ -function assertStringEndsWith(string $suffix, string $string, string $message = ''): void -{ - Assert::assertStringEndsWith(...func_get_args()); -} - -/** - * Asserts that a string ends not with a given suffix. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertStringEndsNotWith - */ -function assertStringEndsNotWith(string $suffix, string $string, string $message = ''): void -{ - Assert::assertStringEndsNotWith(...func_get_args()); -} - -/** - * Asserts that two XML files are equal. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @see Assert::assertXmlFileEqualsXmlFile - */ -function assertXmlFileEqualsXmlFile(string $expectedFile, string $actualFile, string $message = ''): void -{ - Assert::assertXmlFileEqualsXmlFile(...func_get_args()); -} - -/** - * Asserts that two XML files are not equal. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @see Assert::assertXmlFileNotEqualsXmlFile - */ -function assertXmlFileNotEqualsXmlFile(string $expectedFile, string $actualFile, string $message = ''): void -{ - Assert::assertXmlFileNotEqualsXmlFile(...func_get_args()); -} - -/** - * Asserts that two XML documents are equal. - * - * @param DOMDocument|string $actualXml - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @see Assert::assertXmlStringEqualsXmlFile - */ -function assertXmlStringEqualsXmlFile(string $expectedFile, $actualXml, string $message = ''): void -{ - Assert::assertXmlStringEqualsXmlFile(...func_get_args()); -} - -/** - * Asserts that two XML documents are not equal. - * - * @param DOMDocument|string $actualXml - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @see Assert::assertXmlStringNotEqualsXmlFile - */ -function assertXmlStringNotEqualsXmlFile(string $expectedFile, $actualXml, string $message = ''): void -{ - Assert::assertXmlStringNotEqualsXmlFile(...func_get_args()); -} - -/** - * Asserts that two XML documents are equal. - * - * @param DOMDocument|string $expectedXml - * @param DOMDocument|string $actualXml - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @see Assert::assertXmlStringEqualsXmlString - */ -function assertXmlStringEqualsXmlString($expectedXml, $actualXml, string $message = ''): void -{ - Assert::assertXmlStringEqualsXmlString(...func_get_args()); -} - -/** - * Asserts that two XML documents are not equal. - * - * @param DOMDocument|string $expectedXml - * @param DOMDocument|string $actualXml - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * - * @see Assert::assertXmlStringNotEqualsXmlString - */ -function assertXmlStringNotEqualsXmlString($expectedXml, $actualXml, string $message = ''): void -{ - Assert::assertXmlStringNotEqualsXmlString(...func_get_args()); -} - -/** - * Asserts that a hierarchy of DOMElements matches. - * - * @throws AssertionFailedError - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4091 - * @see Assert::assertEqualXMLStructure - */ -function assertEqualXMLStructure(DOMElement $expectedElement, DOMElement $actualElement, bool $checkAttributes = false, string $message = ''): void -{ - Assert::assertEqualXMLStructure(...func_get_args()); -} - -/** - * Evaluates a PHPUnit\Framework\Constraint matcher object. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertThat - */ -function assertThat($value, Constraint $constraint, string $message = ''): void -{ - Assert::assertThat(...func_get_args()); -} - -/** - * Asserts that a string is a valid JSON string. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertJson - */ -function assertJson(string $actualJson, string $message = ''): void -{ - Assert::assertJson(...func_get_args()); -} - -/** - * Asserts that two given JSON encoded objects or arrays are equal. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertJsonStringEqualsJsonString - */ -function assertJsonStringEqualsJsonString(string $expectedJson, string $actualJson, string $message = ''): void -{ - Assert::assertJsonStringEqualsJsonString(...func_get_args()); -} - -/** - * Asserts that two given JSON encoded objects or arrays are not equal. - * - * @param string $expectedJson - * @param string $actualJson - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertJsonStringNotEqualsJsonString - */ -function assertJsonStringNotEqualsJsonString($expectedJson, $actualJson, string $message = ''): void -{ - Assert::assertJsonStringNotEqualsJsonString(...func_get_args()); -} - -/** - * Asserts that the generated JSON encoded object and the content of the given file are equal. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertJsonStringEqualsJsonFile - */ -function assertJsonStringEqualsJsonFile(string $expectedFile, string $actualJson, string $message = ''): void -{ - Assert::assertJsonStringEqualsJsonFile(...func_get_args()); -} - -/** - * Asserts that the generated JSON encoded object and the content of the given file are not equal. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertJsonStringNotEqualsJsonFile - */ -function assertJsonStringNotEqualsJsonFile(string $expectedFile, string $actualJson, string $message = ''): void -{ - Assert::assertJsonStringNotEqualsJsonFile(...func_get_args()); -} - -/** - * Asserts that two JSON files are equal. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertJsonFileEqualsJsonFile - */ -function assertJsonFileEqualsJsonFile(string $expectedFile, string $actualFile, string $message = ''): void -{ - Assert::assertJsonFileEqualsJsonFile(...func_get_args()); -} - -/** - * Asserts that two JSON files are not equal. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @see Assert::assertJsonFileNotEqualsJsonFile - */ -function assertJsonFileNotEqualsJsonFile(string $expectedFile, string $actualFile, string $message = ''): void -{ - Assert::assertJsonFileNotEqualsJsonFile(...func_get_args()); +if (!function_exists('PHPUnit\Framework\assertArrayIsEqualToArrayOnlyConsideringListOfKeys')) { + /** + * Asserts that two arrays are equal while only considering a list of keys. + * + * @param array $expected + * @param array $actual + * @param non-empty-list $keysToBeConsidered + * + * @throws Exception + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertArrayIsEqualToArrayOnlyConsideringListOfKeys + */ + function assertArrayIsEqualToArrayOnlyConsideringListOfKeys(array $expected, array $actual, array $keysToBeConsidered, string $message = ''): void + { + Assert::assertArrayIsEqualToArrayOnlyConsideringListOfKeys(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertArrayIsEqualToArrayIgnoringListOfKeys')) { + /** + * Asserts that two arrays are equal while ignoring a list of keys. + * + * @param array $expected + * @param array $actual + * @param non-empty-list $keysToBeIgnored + * + * @throws Exception + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertArrayIsEqualToArrayIgnoringListOfKeys + */ + function assertArrayIsEqualToArrayIgnoringListOfKeys(array $expected, array $actual, array $keysToBeIgnored, string $message = ''): void + { + Assert::assertArrayIsEqualToArrayIgnoringListOfKeys(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys')) { + /** + * Asserts that two arrays are identical while only considering a list of keys. + * + * @param array $expected + * @param array $actual + * @param non-empty-list $keysToBeConsidered + * + * @throws Exception + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys + */ + function assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys(array $expected, array $actual, array $keysToBeConsidered, string $message = ''): void + { + Assert::assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertArrayIsIdenticalToArrayIgnoringListOfKeys')) { + /** + * Asserts that two arrays are equal while ignoring a list of keys. + * + * @param array $expected + * @param array $actual + * @param non-empty-list $keysToBeIgnored + * + * @throws Exception + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertArrayIsIdenticalToArrayIgnoringListOfKeys + */ + function assertArrayIsIdenticalToArrayIgnoringListOfKeys(array $expected, array $actual, array $keysToBeIgnored, string $message = ''): void + { + Assert::assertArrayIsIdenticalToArrayIgnoringListOfKeys(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertArrayHasKey')) { + /** + * Asserts that an array has a specified key. + * + * @param array|ArrayAccess $array + * + * @throws Exception + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertArrayHasKey + */ + function assertArrayHasKey(mixed $key, array|ArrayAccess $array, string $message = ''): void + { + Assert::assertArrayHasKey(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertArrayNotHasKey')) { + /** + * Asserts that an array does not have a specified key. + * + * @param array|ArrayAccess $array + * + * @throws Exception + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertArrayNotHasKey + */ + function assertArrayNotHasKey(mixed $key, array|ArrayAccess $array, string $message = ''): void + { + Assert::assertArrayNotHasKey(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsList')) { + /** + * @phpstan-assert list $array + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsList + */ + function assertIsList(mixed $array, string $message = ''): void + { + Assert::assertIsList(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContains')) { + /** + * Asserts that a haystack contains a needle. + * + * @param iterable $haystack + * + * @throws Exception + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContains + */ + function assertContains(mixed $needle, iterable $haystack, string $message = ''): void + { + Assert::assertContains(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsEquals')) { + /** + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsEquals + */ + function assertContainsEquals(mixed $needle, iterable $haystack, string $message = ''): void + { + Assert::assertContainsEquals(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotContains')) { + /** + * Asserts that a haystack does not contain a needle. + * + * @param iterable $haystack + * + * @throws Exception + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotContains + */ + function assertNotContains(mixed $needle, iterable $haystack, string $message = ''): void + { + Assert::assertNotContains(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotContainsEquals')) { + /** + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotContainsEquals + */ + function assertNotContainsEquals(mixed $needle, iterable $haystack, string $message = ''): void + { + Assert::assertNotContainsEquals(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyArray')) { + /** + * Asserts that a haystack contains only values of type array. + * + * @phpstan-assert iterable> $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyArray + */ + function assertContainsOnlyArray(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyArray(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyBool')) { + /** + * Asserts that a haystack contains only values of type bool. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyBool + */ + function assertContainsOnlyBool(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyBool(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyCallable')) { + /** + * Asserts that a haystack contains only values of type callable. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyCallable + */ + function assertContainsOnlyCallable(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyCallable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyFloat')) { + /** + * Asserts that a haystack contains only values of type float. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyFloat + */ + function assertContainsOnlyFloat(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyFloat(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyInt')) { + /** + * Asserts that a haystack contains only values of type int. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyInt + */ + function assertContainsOnlyInt(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyInt(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyIterable')) { + /** + * Asserts that a haystack contains only values of type iterable. + * + * @phpstan-assert iterable> $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyIterable + */ + function assertContainsOnlyIterable(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyIterable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyNull')) { + /** + * Asserts that a haystack contains only values of type null. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyNull + */ + function assertContainsOnlyNull(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyNull(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyNumeric')) { + /** + * Asserts that a haystack contains only values of type numeric. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyNumeric + */ + function assertContainsOnlyNumeric(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyNumeric(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyObject')) { + /** + * Asserts that a haystack contains only values of type object. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyObject + */ + function assertContainsOnlyObject(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyObject(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyResource')) { + /** + * Asserts that a haystack contains only values of type resource. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyResource + */ + function assertContainsOnlyResource(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyResource(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyClosedResource')) { + /** + * Asserts that a haystack contains only values of type closed resource. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyClosedResource + */ + function assertContainsOnlyClosedResource(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyClosedResource(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyScalar')) { + /** + * Asserts that a haystack contains only values of type scalar. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyScalar + */ + function assertContainsOnlyScalar(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyScalar(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyString')) { + /** + * Asserts that a haystack contains only values of type string. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyString + */ + function assertContainsOnlyString(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyString(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyInstancesOf')) { + /** + * Asserts that a haystack contains only instances of a specified interface or class name. + * + * @template T + * + * @phpstan-assert iterable $haystack + * + * @param class-string $className + * @param iterable $haystack + * + * @throws Exception + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyInstancesOf + */ + function assertContainsOnlyInstancesOf(string $className, iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyInstancesOf(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyArray')) { + /** + * Asserts that a haystack does not contain only values of type array. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsNotOnlyArray + */ + function assertContainsNotOnlyArray(iterable $haystack, string $message = ''): void + { + Assert::assertContainsNotOnlyArray(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyBool')) { + /** + * Asserts that a haystack does not contain only values of type bool. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsNotOnlyBool + */ + function assertContainsNotOnlyBool(iterable $haystack, string $message = ''): void + { + Assert::assertContainsNotOnlyBool(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyCallable')) { + /** + * Asserts that a haystack does not contain only values of type callable. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsNotOnlyCallable + */ + function assertContainsNotOnlyCallable(iterable $haystack, string $message = ''): void + { + Assert::assertContainsNotOnlyCallable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyFloat')) { + /** + * Asserts that a haystack does not contain only values of type float. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsNotOnlyFloat + */ + function assertContainsNotOnlyFloat(iterable $haystack, string $message = ''): void + { + Assert::assertContainsNotOnlyFloat(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyInt')) { + /** + * Asserts that a haystack does not contain only values of type int. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsNotOnlyInt + */ + function assertContainsNotOnlyInt(iterable $haystack, string $message = ''): void + { + Assert::assertContainsNotOnlyInt(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyIterable')) { + /** + * Asserts that a haystack does not contain only values of type iterable. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsNotOnlyIterable + */ + function assertContainsNotOnlyIterable(iterable $haystack, string $message = ''): void + { + Assert::assertContainsNotOnlyIterable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyNull')) { + /** + * Asserts that a haystack does not contain only values of type null. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsNotOnlyNull + */ + function assertContainsNotOnlyNull(iterable $haystack, string $message = ''): void + { + Assert::assertContainsNotOnlyNull(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyNumeric')) { + /** + * Asserts that a haystack does not contain only values of type numeric. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsNotOnlyNumeric + */ + function assertContainsNotOnlyNumeric(iterable $haystack, string $message = ''): void + { + Assert::assertContainsNotOnlyNumeric(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyObject')) { + /** + * Asserts that a haystack does not contain only values of type object. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsNotOnlyObject + */ + function assertContainsNotOnlyObject(iterable $haystack, string $message = ''): void + { + Assert::assertContainsNotOnlyObject(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyResource')) { + /** + * Asserts that a haystack does not contain only values of type resource. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsNotOnlyResource + */ + function assertContainsNotOnlyResource(iterable $haystack, string $message = ''): void + { + Assert::assertContainsNotOnlyResource(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyClosedResource')) { + /** + * Asserts that a haystack does not contain only values of type closed resource. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsNotOnlyClosedResource + */ + function assertContainsNotOnlyClosedResource(iterable $haystack, string $message = ''): void + { + Assert::assertContainsNotOnlyClosedResource(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyScalar')) { + /** + * Asserts that a haystack does not contain only values of type scalar. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsNotOnlyScalar + */ + function assertContainsNotOnlyScalar(iterable $haystack, string $message = ''): void + { + Assert::assertContainsNotOnlyScalar(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyString')) { + /** + * Asserts that a haystack does not contain only values of type string. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsNotOnlyString + */ + function assertContainsNotOnlyString(iterable $haystack, string $message = ''): void + { + Assert::assertContainsNotOnlyString(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyInstancesOf')) { + /** + * Asserts that a haystack does not contain only instances of a specified interface or class name. + * + * @param class-string $className + * @param iterable $haystack + * + * @throws Exception + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsNotOnlyInstancesOf + */ + function assertContainsNotOnlyInstancesOf(string $className, iterable $haystack, string $message = ''): void + { + Assert::assertContainsNotOnlyInstancesOf(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertCount')) { + /** + * Asserts the number of elements of an array, Countable or Traversable. + * + * @param Countable|iterable $haystack + * + * @throws Exception + * @throws ExpectationFailedException + * @throws GeneratorNotSupportedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertCount + */ + function assertCount(int $expectedCount, Countable|iterable $haystack, string $message = ''): void + { + Assert::assertCount(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotCount')) { + /** + * Asserts the number of elements of an array, Countable or Traversable. + * + * @param Countable|iterable $haystack + * + * @throws Exception + * @throws ExpectationFailedException + * @throws GeneratorNotSupportedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotCount + */ + function assertNotCount(int $expectedCount, Countable|iterable $haystack, string $message = ''): void + { + Assert::assertNotCount(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertEquals')) { + /** + * Asserts that two variables are equal. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertEquals + */ + function assertEquals(mixed $expected, mixed $actual, string $message = ''): void + { + Assert::assertEquals(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertEqualsCanonicalizing')) { + /** + * Asserts that two variables are equal (canonicalizing). + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertEqualsCanonicalizing + */ + function assertEqualsCanonicalizing(mixed $expected, mixed $actual, string $message = ''): void + { + Assert::assertEqualsCanonicalizing(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertEqualsIgnoringCase')) { + /** + * Asserts that two variables are equal (ignoring case). + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertEqualsIgnoringCase + */ + function assertEqualsIgnoringCase(mixed $expected, mixed $actual, string $message = ''): void + { + Assert::assertEqualsIgnoringCase(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertEqualsWithDelta')) { + /** + * Asserts that two variables are equal (with delta). + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertEqualsWithDelta + */ + function assertEqualsWithDelta(mixed $expected, mixed $actual, float $delta, string $message = ''): void + { + Assert::assertEqualsWithDelta(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotEquals')) { + /** + * Asserts that two variables are not equal. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotEquals + */ + function assertNotEquals(mixed $expected, mixed $actual, string $message = ''): void + { + Assert::assertNotEquals(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotEqualsCanonicalizing')) { + /** + * Asserts that two variables are not equal (canonicalizing). + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotEqualsCanonicalizing + */ + function assertNotEqualsCanonicalizing(mixed $expected, mixed $actual, string $message = ''): void + { + Assert::assertNotEqualsCanonicalizing(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotEqualsIgnoringCase')) { + /** + * Asserts that two variables are not equal (ignoring case). + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotEqualsIgnoringCase + */ + function assertNotEqualsIgnoringCase(mixed $expected, mixed $actual, string $message = ''): void + { + Assert::assertNotEqualsIgnoringCase(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotEqualsWithDelta')) { + /** + * Asserts that two variables are not equal (with delta). + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotEqualsWithDelta + */ + function assertNotEqualsWithDelta(mixed $expected, mixed $actual, float $delta, string $message = ''): void + { + Assert::assertNotEqualsWithDelta(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertObjectEquals')) { + /** + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertObjectEquals + */ + function assertObjectEquals(object $expected, object $actual, string $method = 'equals', string $message = ''): void + { + Assert::assertObjectEquals(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertObjectNotEquals')) { + /** + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertObjectNotEquals + */ + function assertObjectNotEquals(object $expected, object $actual, string $method = 'equals', string $message = ''): void + { + Assert::assertObjectNotEquals(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertEmpty')) { + /** + * Asserts that a variable is empty. + * + * @throws ExpectationFailedException + * @throws GeneratorNotSupportedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertEmpty + */ + function assertEmpty(mixed $actual, string $message = ''): void + { + Assert::assertEmpty(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotEmpty')) { + /** + * Asserts that a variable is not empty. + * + * @throws ExpectationFailedException + * @throws GeneratorNotSupportedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotEmpty + */ + function assertNotEmpty(mixed $actual, string $message = ''): void + { + Assert::assertNotEmpty(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertGreaterThan')) { + /** + * Asserts that a value is greater than another value. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertGreaterThan + */ + function assertGreaterThan(mixed $minimum, mixed $actual, string $message = ''): void + { + Assert::assertGreaterThan(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertGreaterThanOrEqual')) { + /** + * Asserts that a value is greater than or equal to another value. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertGreaterThanOrEqual + */ + function assertGreaterThanOrEqual(mixed $minimum, mixed $actual, string $message = ''): void + { + Assert::assertGreaterThanOrEqual(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertLessThan')) { + /** + * Asserts that a value is smaller than another value. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertLessThan + */ + function assertLessThan(mixed $maximum, mixed $actual, string $message = ''): void + { + Assert::assertLessThan(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertLessThanOrEqual')) { + /** + * Asserts that a value is smaller than or equal to another value. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertLessThanOrEqual + */ + function assertLessThanOrEqual(mixed $maximum, mixed $actual, string $message = ''): void + { + Assert::assertLessThanOrEqual(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileEquals')) { + /** + * Asserts that the contents of one file is equal to the contents of another + * file. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileEquals + */ + function assertFileEquals(string $expected, string $actual, string $message = ''): void + { + Assert::assertFileEquals(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileEqualsCanonicalizing')) { + /** + * Asserts that the contents of one file is equal to the contents of another + * file (canonicalizing). + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileEqualsCanonicalizing + */ + function assertFileEqualsCanonicalizing(string $expected, string $actual, string $message = ''): void + { + Assert::assertFileEqualsCanonicalizing(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileEqualsIgnoringCase')) { + /** + * Asserts that the contents of one file is equal to the contents of another + * file (ignoring case). + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileEqualsIgnoringCase + */ + function assertFileEqualsIgnoringCase(string $expected, string $actual, string $message = ''): void + { + Assert::assertFileEqualsIgnoringCase(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileNotEquals')) { + /** + * Asserts that the contents of one file is not equal to the contents of + * another file. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileNotEquals + */ + function assertFileNotEquals(string $expected, string $actual, string $message = ''): void + { + Assert::assertFileNotEquals(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileNotEqualsCanonicalizing')) { + /** + * Asserts that the contents of one file is not equal to the contents of another + * file (canonicalizing). + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileNotEqualsCanonicalizing + */ + function assertFileNotEqualsCanonicalizing(string $expected, string $actual, string $message = ''): void + { + Assert::assertFileNotEqualsCanonicalizing(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileNotEqualsIgnoringCase')) { + /** + * Asserts that the contents of one file is not equal to the contents of another + * file (ignoring case). + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileNotEqualsIgnoringCase + */ + function assertFileNotEqualsIgnoringCase(string $expected, string $actual, string $message = ''): void + { + Assert::assertFileNotEqualsIgnoringCase(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringEqualsFile')) { + /** + * Asserts that the contents of a string is equal + * to the contents of a file. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringEqualsFile + */ + function assertStringEqualsFile(string $expectedFile, string $actualString, string $message = ''): void + { + Assert::assertStringEqualsFile(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringEqualsFileCanonicalizing')) { + /** + * Asserts that the contents of a string is equal + * to the contents of a file (canonicalizing). + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringEqualsFileCanonicalizing + */ + function assertStringEqualsFileCanonicalizing(string $expectedFile, string $actualString, string $message = ''): void + { + Assert::assertStringEqualsFileCanonicalizing(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringEqualsFileIgnoringCase')) { + /** + * Asserts that the contents of a string is equal + * to the contents of a file (ignoring case). + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringEqualsFileIgnoringCase + */ + function assertStringEqualsFileIgnoringCase(string $expectedFile, string $actualString, string $message = ''): void + { + Assert::assertStringEqualsFileIgnoringCase(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringNotEqualsFile')) { + /** + * Asserts that the contents of a string is not equal + * to the contents of a file. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringNotEqualsFile + */ + function assertStringNotEqualsFile(string $expectedFile, string $actualString, string $message = ''): void + { + Assert::assertStringNotEqualsFile(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringNotEqualsFileCanonicalizing')) { + /** + * Asserts that the contents of a string is not equal + * to the contents of a file (canonicalizing). + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringNotEqualsFileCanonicalizing + */ + function assertStringNotEqualsFileCanonicalizing(string $expectedFile, string $actualString, string $message = ''): void + { + Assert::assertStringNotEqualsFileCanonicalizing(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringNotEqualsFileIgnoringCase')) { + /** + * Asserts that the contents of a string is not equal + * to the contents of a file (ignoring case). + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringNotEqualsFileIgnoringCase + */ + function assertStringNotEqualsFileIgnoringCase(string $expectedFile, string $actualString, string $message = ''): void + { + Assert::assertStringNotEqualsFileIgnoringCase(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsReadable')) { + /** + * Asserts that a file/dir is readable. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsReadable + */ + function assertIsReadable(string $filename, string $message = ''): void + { + Assert::assertIsReadable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotReadable')) { + /** + * Asserts that a file/dir exists and is not readable. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotReadable + */ + function assertIsNotReadable(string $filename, string $message = ''): void + { + Assert::assertIsNotReadable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsWritable')) { + /** + * Asserts that a file/dir exists and is writable. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsWritable + */ + function assertIsWritable(string $filename, string $message = ''): void + { + Assert::assertIsWritable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotWritable')) { + /** + * Asserts that a file/dir exists and is not writable. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotWritable + */ + function assertIsNotWritable(string $filename, string $message = ''): void + { + Assert::assertIsNotWritable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertDirectoryExists')) { + /** + * Asserts that a directory exists. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDirectoryExists + */ + function assertDirectoryExists(string $directory, string $message = ''): void + { + Assert::assertDirectoryExists(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertDirectoryDoesNotExist')) { + /** + * Asserts that a directory does not exist. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDirectoryDoesNotExist + */ + function assertDirectoryDoesNotExist(string $directory, string $message = ''): void + { + Assert::assertDirectoryDoesNotExist(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertDirectoryIsReadable')) { + /** + * Asserts that a directory exists and is readable. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDirectoryIsReadable + */ + function assertDirectoryIsReadable(string $directory, string $message = ''): void + { + Assert::assertDirectoryIsReadable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertDirectoryIsNotReadable')) { + /** + * Asserts that a directory exists and is not readable. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDirectoryIsNotReadable + */ + function assertDirectoryIsNotReadable(string $directory, string $message = ''): void + { + Assert::assertDirectoryIsNotReadable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertDirectoryIsWritable')) { + /** + * Asserts that a directory exists and is writable. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDirectoryIsWritable + */ + function assertDirectoryIsWritable(string $directory, string $message = ''): void + { + Assert::assertDirectoryIsWritable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertDirectoryIsNotWritable')) { + /** + * Asserts that a directory exists and is not writable. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDirectoryIsNotWritable + */ + function assertDirectoryIsNotWritable(string $directory, string $message = ''): void + { + Assert::assertDirectoryIsNotWritable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileExists')) { + /** + * Asserts that a file exists. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileExists + */ + function assertFileExists(string $filename, string $message = ''): void + { + Assert::assertFileExists(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileDoesNotExist')) { + /** + * Asserts that a file does not exist. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileDoesNotExist + */ + function assertFileDoesNotExist(string $filename, string $message = ''): void + { + Assert::assertFileDoesNotExist(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileIsReadable')) { + /** + * Asserts that a file exists and is readable. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileIsReadable + */ + function assertFileIsReadable(string $file, string $message = ''): void + { + Assert::assertFileIsReadable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileIsNotReadable')) { + /** + * Asserts that a file exists and is not readable. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileIsNotReadable + */ + function assertFileIsNotReadable(string $file, string $message = ''): void + { + Assert::assertFileIsNotReadable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileIsWritable')) { + /** + * Asserts that a file exists and is writable. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileIsWritable + */ + function assertFileIsWritable(string $file, string $message = ''): void + { + Assert::assertFileIsWritable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileIsNotWritable')) { + /** + * Asserts that a file exists and is not writable. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileIsNotWritable + */ + function assertFileIsNotWritable(string $file, string $message = ''): void + { + Assert::assertFileIsNotWritable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertTrue')) { + /** + * Asserts that a condition is true. + * + * @throws ExpectationFailedException + * + * @phpstan-assert true $condition + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertTrue + */ + function assertTrue(mixed $condition, string $message = ''): void + { + Assert::assertTrue(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotTrue')) { + /** + * Asserts that a condition is not true. + * + * @throws ExpectationFailedException + * + * @phpstan-assert !true $condition + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotTrue + */ + function assertNotTrue(mixed $condition, string $message = ''): void + { + Assert::assertNotTrue(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFalse')) { + /** + * Asserts that a condition is false. + * + * @throws ExpectationFailedException + * + * @phpstan-assert false $condition + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFalse + */ + function assertFalse(mixed $condition, string $message = ''): void + { + Assert::assertFalse(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotFalse')) { + /** + * Asserts that a condition is not false. + * + * @throws ExpectationFailedException + * + * @phpstan-assert !false $condition + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotFalse + */ + function assertNotFalse(mixed $condition, string $message = ''): void + { + Assert::assertNotFalse(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNull')) { + /** + * Asserts that a variable is null. + * + * @throws ExpectationFailedException + * + * @phpstan-assert null $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNull + */ + function assertNull(mixed $actual, string $message = ''): void + { + Assert::assertNull(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotNull')) { + /** + * Asserts that a variable is not null. + * + * @throws ExpectationFailedException + * + * @phpstan-assert !null $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotNull + */ + function assertNotNull(mixed $actual, string $message = ''): void + { + Assert::assertNotNull(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFinite')) { + /** + * Asserts that a variable is finite. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFinite + */ + function assertFinite(mixed $actual, string $message = ''): void + { + Assert::assertFinite(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertInfinite')) { + /** + * Asserts that a variable is infinite. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertInfinite + */ + function assertInfinite(mixed $actual, string $message = ''): void + { + Assert::assertInfinite(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNan')) { + /** + * Asserts that a variable is nan. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNan + */ + function assertNan(mixed $actual, string $message = ''): void + { + Assert::assertNan(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertObjectHasProperty')) { + /** + * Asserts that an object has a specified property. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertObjectHasProperty + */ + function assertObjectHasProperty(string $propertyName, object $object, string $message = ''): void + { + Assert::assertObjectHasProperty(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertObjectNotHasProperty')) { + /** + * Asserts that an object does not have a specified property. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertObjectNotHasProperty + */ + function assertObjectNotHasProperty(string $propertyName, object $object, string $message = ''): void + { + Assert::assertObjectNotHasProperty(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertSame')) { + /** + * Asserts that two variables have the same type and value. + * Used on objects, it asserts that two variables reference + * the same object. + * + * @template ExpectedType + * + * @param ExpectedType $expected + * + * @throws ExpectationFailedException + * + * @phpstan-assert =ExpectedType $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertSame + */ + function assertSame(mixed $expected, mixed $actual, string $message = ''): void + { + Assert::assertSame(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotSame')) { + /** + * Asserts that two variables do not have the same type and value. + * Used on objects, it asserts that two variables do not reference + * the same object. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotSame + */ + function assertNotSame(mixed $expected, mixed $actual, string $message = ''): void + { + Assert::assertNotSame(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertInstanceOf')) { + /** + * Asserts that a variable is of a given type. + * + * @template ExpectedType of object + * + * @param class-string $expected + * + * @throws Exception + * @throws ExpectationFailedException + * @throws UnknownClassOrInterfaceException + * + * @phpstan-assert =ExpectedType $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertInstanceOf + */ + function assertInstanceOf(string $expected, mixed $actual, string $message = ''): void + { + Assert::assertInstanceOf(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotInstanceOf')) { + /** + * Asserts that a variable is not of a given type. + * + * @template ExpectedType of object + * + * @param class-string $expected + * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert !ExpectedType $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotInstanceOf + */ + function assertNotInstanceOf(string $expected, mixed $actual, string $message = ''): void + { + Assert::assertNotInstanceOf(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsArray')) { + /** + * Asserts that a variable is of type array. + * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert array $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsArray + */ + function assertIsArray(mixed $actual, string $message = ''): void + { + Assert::assertIsArray(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsBool')) { + /** + * Asserts that a variable is of type bool. + * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert bool $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsBool + */ + function assertIsBool(mixed $actual, string $message = ''): void + { + Assert::assertIsBool(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsFloat')) { + /** + * Asserts that a variable is of type float. + * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert float $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsFloat + */ + function assertIsFloat(mixed $actual, string $message = ''): void + { + Assert::assertIsFloat(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsInt')) { + /** + * Asserts that a variable is of type int. + * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert int $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsInt + */ + function assertIsInt(mixed $actual, string $message = ''): void + { + Assert::assertIsInt(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNumeric')) { + /** + * Asserts that a variable is of type numeric. + * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert numeric $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNumeric + */ + function assertIsNumeric(mixed $actual, string $message = ''): void + { + Assert::assertIsNumeric(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsObject')) { + /** + * Asserts that a variable is of type object. + * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert object $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsObject + */ + function assertIsObject(mixed $actual, string $message = ''): void + { + Assert::assertIsObject(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsResource')) { + /** + * Asserts that a variable is of type resource. + * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert resource $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsResource + */ + function assertIsResource(mixed $actual, string $message = ''): void + { + Assert::assertIsResource(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsClosedResource')) { + /** + * Asserts that a variable is of type resource and is closed. + * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert resource $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsClosedResource + */ + function assertIsClosedResource(mixed $actual, string $message = ''): void + { + Assert::assertIsClosedResource(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsString')) { + /** + * Asserts that a variable is of type string. + * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert string $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsString + */ + function assertIsString(mixed $actual, string $message = ''): void + { + Assert::assertIsString(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsScalar')) { + /** + * Asserts that a variable is of type scalar. + * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert scalar $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsScalar + */ + function assertIsScalar(mixed $actual, string $message = ''): void + { + Assert::assertIsScalar(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsCallable')) { + /** + * Asserts that a variable is of type callable. + * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert callable $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsCallable + */ + function assertIsCallable(mixed $actual, string $message = ''): void + { + Assert::assertIsCallable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsIterable')) { + /** + * Asserts that a variable is of type iterable. + * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert iterable $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsIterable + */ + function assertIsIterable(mixed $actual, string $message = ''): void + { + Assert::assertIsIterable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotArray')) { + /** + * Asserts that a variable is not of type array. + * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert !array $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotArray + */ + function assertIsNotArray(mixed $actual, string $message = ''): void + { + Assert::assertIsNotArray(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotBool')) { + /** + * Asserts that a variable is not of type bool. + * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert !bool $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotBool + */ + function assertIsNotBool(mixed $actual, string $message = ''): void + { + Assert::assertIsNotBool(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotFloat')) { + /** + * Asserts that a variable is not of type float. + * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert !float $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotFloat + */ + function assertIsNotFloat(mixed $actual, string $message = ''): void + { + Assert::assertIsNotFloat(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotInt')) { + /** + * Asserts that a variable is not of type int. + * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert !int $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotInt + */ + function assertIsNotInt(mixed $actual, string $message = ''): void + { + Assert::assertIsNotInt(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotNumeric')) { + /** + * Asserts that a variable is not of type numeric. + * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert !numeric $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotNumeric + */ + function assertIsNotNumeric(mixed $actual, string $message = ''): void + { + Assert::assertIsNotNumeric(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotObject')) { + /** + * Asserts that a variable is not of type object. + * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert !object $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotObject + */ + function assertIsNotObject(mixed $actual, string $message = ''): void + { + Assert::assertIsNotObject(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotResource')) { + /** + * Asserts that a variable is not of type resource. + * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert !resource $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotResource + */ + function assertIsNotResource(mixed $actual, string $message = ''): void + { + Assert::assertIsNotResource(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotClosedResource')) { + /** + * Asserts that a variable is not of type resource. + * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert !resource $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotClosedResource + */ + function assertIsNotClosedResource(mixed $actual, string $message = ''): void + { + Assert::assertIsNotClosedResource(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotString')) { + /** + * Asserts that a variable is not of type string. + * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert !string $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotString + */ + function assertIsNotString(mixed $actual, string $message = ''): void + { + Assert::assertIsNotString(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotScalar')) { + /** + * Asserts that a variable is not of type scalar. + * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert !scalar $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotScalar + */ + function assertIsNotScalar(mixed $actual, string $message = ''): void + { + Assert::assertIsNotScalar(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotCallable')) { + /** + * Asserts that a variable is not of type callable. + * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert !callable $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotCallable + */ + function assertIsNotCallable(mixed $actual, string $message = ''): void + { + Assert::assertIsNotCallable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsNotIterable')) { + /** + * Asserts that a variable is not of type iterable. + * + * @throws Exception + * @throws ExpectationFailedException + * + * @phpstan-assert !iterable $actual + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsNotIterable + */ + function assertIsNotIterable(mixed $actual, string $message = ''): void + { + Assert::assertIsNotIterable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertMatchesRegularExpression')) { + /** + * Asserts that a string matches a given regular expression. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertMatchesRegularExpression + */ + function assertMatchesRegularExpression(string $pattern, string $string, string $message = ''): void + { + Assert::assertMatchesRegularExpression(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertDoesNotMatchRegularExpression')) { + /** + * Asserts that a string does not match a given regular expression. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertDoesNotMatchRegularExpression + */ + function assertDoesNotMatchRegularExpression(string $pattern, string $string, string $message = ''): void + { + Assert::assertDoesNotMatchRegularExpression(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertSameSize')) { + /** + * Assert that the size of two arrays (or `Countable` or `Traversable` objects) + * is the same. + * + * @param Countable|iterable $expected + * @param Countable|iterable $actual + * + * @throws Exception + * @throws ExpectationFailedException + * @throws GeneratorNotSupportedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertSameSize + */ + function assertSameSize(Countable|iterable $expected, Countable|iterable $actual, string $message = ''): void + { + Assert::assertSameSize(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotSameSize')) { + /** + * Assert that the size of two arrays (or `Countable` or `Traversable` objects) + * is not the same. + * + * @param Countable|iterable $expected + * @param Countable|iterable $actual + * + * @throws Exception + * @throws ExpectationFailedException + * @throws GeneratorNotSupportedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotSameSize + */ + function assertNotSameSize(Countable|iterable $expected, Countable|iterable $actual, string $message = ''): void + { + Assert::assertNotSameSize(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringContainsStringIgnoringLineEndings')) { + /** + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringContainsStringIgnoringLineEndings + */ + function assertStringContainsStringIgnoringLineEndings(string $needle, string $haystack, string $message = ''): void + { + Assert::assertStringContainsStringIgnoringLineEndings(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringEqualsStringIgnoringLineEndings')) { + /** + * Asserts that two strings are equal except for line endings. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringEqualsStringIgnoringLineEndings + */ + function assertStringEqualsStringIgnoringLineEndings(string $expected, string $actual, string $message = ''): void + { + Assert::assertStringEqualsStringIgnoringLineEndings(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileMatchesFormat')) { + /** + * Asserts that a string matches a given format string. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileMatchesFormat + */ + function assertFileMatchesFormat(string $format, string $actualFile, string $message = ''): void + { + Assert::assertFileMatchesFormat(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileMatchesFormatFile')) { + /** + * Asserts that a string matches a given format string. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertFileMatchesFormatFile + */ + function assertFileMatchesFormatFile(string $formatFile, string $actualFile, string $message = ''): void + { + Assert::assertFileMatchesFormatFile(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringMatchesFormat')) { + /** + * Asserts that a string matches a given format string. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringMatchesFormat + */ + function assertStringMatchesFormat(string $format, string $string, string $message = ''): void + { + Assert::assertStringMatchesFormat(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringMatchesFormatFile')) { + /** + * Asserts that a string matches a given format file. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringMatchesFormatFile + */ + function assertStringMatchesFormatFile(string $formatFile, string $string, string $message = ''): void + { + Assert::assertStringMatchesFormatFile(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringStartsWith')) { + /** + * Asserts that a string starts with a given prefix. + * + * @param non-empty-string $prefix + * + * @throws ExpectationFailedException + * @throws InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringStartsWith + */ + function assertStringStartsWith(string $prefix, string $string, string $message = ''): void + { + Assert::assertStringStartsWith(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringStartsNotWith')) { + /** + * Asserts that a string starts not with a given prefix. + * + * @param non-empty-string $prefix + * + * @throws ExpectationFailedException + * @throws InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringStartsNotWith + */ + function assertStringStartsNotWith(string $prefix, string $string, string $message = ''): void + { + Assert::assertStringStartsNotWith(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringContainsString')) { + /** + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringContainsString + */ + function assertStringContainsString(string $needle, string $haystack, string $message = ''): void + { + Assert::assertStringContainsString(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringContainsStringIgnoringCase')) { + /** + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringContainsStringIgnoringCase + */ + function assertStringContainsStringIgnoringCase(string $needle, string $haystack, string $message = ''): void + { + Assert::assertStringContainsStringIgnoringCase(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringNotContainsString')) { + /** + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringNotContainsString + */ + function assertStringNotContainsString(string $needle, string $haystack, string $message = ''): void + { + Assert::assertStringNotContainsString(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringNotContainsStringIgnoringCase')) { + /** + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringNotContainsStringIgnoringCase + */ + function assertStringNotContainsStringIgnoringCase(string $needle, string $haystack, string $message = ''): void + { + Assert::assertStringNotContainsStringIgnoringCase(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringEndsWith')) { + /** + * Asserts that a string ends with a given suffix. + * + * @param non-empty-string $suffix + * + * @throws ExpectationFailedException + * @throws InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringEndsWith + */ + function assertStringEndsWith(string $suffix, string $string, string $message = ''): void + { + Assert::assertStringEndsWith(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringEndsNotWith')) { + /** + * Asserts that a string ends not with a given suffix. + * + * @param non-empty-string $suffix + * + * @throws ExpectationFailedException + * @throws InvalidArgumentException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringEndsNotWith + */ + function assertStringEndsNotWith(string $suffix, string $string, string $message = ''): void + { + Assert::assertStringEndsNotWith(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertXmlFileEqualsXmlFile')) { + /** + * Asserts that two XML files are equal. + * + * @throws Exception + * @throws ExpectationFailedException + * @throws XmlException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertXmlFileEqualsXmlFile + */ + function assertXmlFileEqualsXmlFile(string $expectedFile, string $actualFile, string $message = ''): void + { + Assert::assertXmlFileEqualsXmlFile(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertXmlFileNotEqualsXmlFile')) { + /** + * Asserts that two XML files are not equal. + * + * @throws \PHPUnit\Util\Exception + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertXmlFileNotEqualsXmlFile + */ + function assertXmlFileNotEqualsXmlFile(string $expectedFile, string $actualFile, string $message = ''): void + { + Assert::assertXmlFileNotEqualsXmlFile(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertXmlStringEqualsXmlFile')) { + /** + * Asserts that two XML documents are equal. + * + * @throws ExpectationFailedException + * @throws XmlException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertXmlStringEqualsXmlFile + */ + function assertXmlStringEqualsXmlFile(string $expectedFile, string $actualXml, string $message = ''): void + { + Assert::assertXmlStringEqualsXmlFile(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertXmlStringNotEqualsXmlFile')) { + /** + * Asserts that two XML documents are not equal. + * + * @throws ExpectationFailedException + * @throws XmlException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertXmlStringNotEqualsXmlFile + */ + function assertXmlStringNotEqualsXmlFile(string $expectedFile, string $actualXml, string $message = ''): void + { + Assert::assertXmlStringNotEqualsXmlFile(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertXmlStringEqualsXmlString')) { + /** + * Asserts that two XML documents are equal. + * + * @throws ExpectationFailedException + * @throws XmlException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertXmlStringEqualsXmlString + */ + function assertXmlStringEqualsXmlString(string $expectedXml, string $actualXml, string $message = ''): void + { + Assert::assertXmlStringEqualsXmlString(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertXmlStringNotEqualsXmlString')) { + /** + * Asserts that two XML documents are not equal. + * + * @throws ExpectationFailedException + * @throws XmlException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertXmlStringNotEqualsXmlString + */ + function assertXmlStringNotEqualsXmlString(string $expectedXml, string $actualXml, string $message = ''): void + { + Assert::assertXmlStringNotEqualsXmlString(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertThat')) { + /** + * Evaluates a PHPUnit\Framework\Constraint matcher object. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertThat + */ + function assertThat(mixed $value, Constraint $constraint, string $message = ''): void + { + Assert::assertThat(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertJson')) { + /** + * Asserts that a string is a valid JSON string. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertJson + */ + function assertJson(string $actual, string $message = ''): void + { + Assert::assertJson(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertJsonStringEqualsJsonString')) { + /** + * Asserts that two given JSON encoded objects or arrays are equal. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertJsonStringEqualsJsonString + */ + function assertJsonStringEqualsJsonString(string $expectedJson, string $actualJson, string $message = ''): void + { + Assert::assertJsonStringEqualsJsonString(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertJsonStringNotEqualsJsonString')) { + /** + * Asserts that two given JSON encoded objects or arrays are not equal. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertJsonStringNotEqualsJsonString + */ + function assertJsonStringNotEqualsJsonString(string $expectedJson, string $actualJson, string $message = ''): void + { + Assert::assertJsonStringNotEqualsJsonString(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertJsonStringEqualsJsonFile')) { + /** + * Asserts that the generated JSON encoded object and the content of the given file are equal. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertJsonStringEqualsJsonFile + */ + function assertJsonStringEqualsJsonFile(string $expectedFile, string $actualJson, string $message = ''): void + { + Assert::assertJsonStringEqualsJsonFile(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertJsonStringNotEqualsJsonFile')) { + /** + * Asserts that the generated JSON encoded object and the content of the given file are not equal. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertJsonStringNotEqualsJsonFile + */ + function assertJsonStringNotEqualsJsonFile(string $expectedFile, string $actualJson, string $message = ''): void + { + Assert::assertJsonStringNotEqualsJsonFile(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertJsonFileEqualsJsonFile')) { + /** + * Asserts that two JSON files are equal. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertJsonFileEqualsJsonFile + */ + function assertJsonFileEqualsJsonFile(string $expectedFile, string $actualFile, string $message = ''): void + { + Assert::assertJsonFileEqualsJsonFile(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertJsonFileNotEqualsJsonFile')) { + /** + * Asserts that two JSON files are not equal. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertJsonFileNotEqualsJsonFile + */ + function assertJsonFileNotEqualsJsonFile(string $expectedFile, string $actualFile, string $message = ''): void + { + Assert::assertJsonFileNotEqualsJsonFile(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\logicalAnd')) { + function logicalAnd(mixed ...$constraints): LogicalAnd + { + return Assert::logicalAnd(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\logicalOr')) { + function logicalOr(mixed ...$constraints): LogicalOr + { + return Assert::logicalOr(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\logicalNot')) { + function logicalNot(Constraint $constraint): LogicalNot + { + return Assert::logicalNot(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\logicalXor')) { + function logicalXor(mixed ...$constraints): LogicalXor + { + return Assert::logicalXor(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\anything')) { + function anything(): IsAnything + { + return Assert::anything(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\isTrue')) { + function isTrue(): IsTrue + { + return Assert::isTrue(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\isFalse')) { + function isFalse(): IsFalse + { + return Assert::isFalse(...func_get_args()); + } } -function logicalAnd(): LogicalAnd -{ - return Assert::logicalAnd(...func_get_args()); +if (!function_exists('PHPUnit\Framework\isJson')) { + function isJson(): IsJson + { + return Assert::isJson(...func_get_args()); + } } -function logicalOr(): LogicalOr -{ - return Assert::logicalOr(...func_get_args()); +if (!function_exists('PHPUnit\Framework\isNull')) { + function isNull(): IsNull + { + return Assert::isNull(...func_get_args()); + } } -function logicalNot(Constraint $constraint): LogicalNot -{ - return Assert::logicalNot(...func_get_args()); +if (!function_exists('PHPUnit\Framework\isFinite')) { + function isFinite(): IsFinite + { + return Assert::isFinite(...func_get_args()); + } } -function logicalXor(): LogicalXor -{ - return Assert::logicalXor(...func_get_args()); +if (!function_exists('PHPUnit\Framework\isInfinite')) { + function isInfinite(): IsInfinite + { + return Assert::isInfinite(...func_get_args()); + } } -function anything(): IsAnything -{ - return Assert::anything(...func_get_args()); +if (!function_exists('PHPUnit\Framework\isNan')) { + function isNan(): IsNan + { + return Assert::isNan(...func_get_args()); + } } -function isTrue(): IsTrue -{ - return Assert::isTrue(...func_get_args()); +if (!function_exists('PHPUnit\Framework\containsEqual')) { + function containsEqual(mixed $value): TraversableContainsEqual + { + return Assert::containsEqual(...func_get_args()); + } } -function callback(callable $callback): Callback -{ - return Assert::callback(...func_get_args()); +if (!function_exists('PHPUnit\Framework\containsIdentical')) { + function containsIdentical(mixed $value): TraversableContainsIdentical + { + return Assert::containsIdentical(...func_get_args()); + } } -function isFalse(): IsFalse -{ - return Assert::isFalse(...func_get_args()); +if (!function_exists('PHPUnit\Framework\containsOnlyArray')) { + function containsOnlyArray(): TraversableContainsOnly + { + return Assert::containsOnlyArray(...func_get_args()); + } } -function isJson(): IsJson -{ - return Assert::isJson(...func_get_args()); +if (!function_exists('PHPUnit\Framework\containsOnlyBool')) { + function containsOnlyBool(): TraversableContainsOnly + { + return Assert::containsOnlyBool(...func_get_args()); + } } -function isNull(): IsNull -{ - return Assert::isNull(...func_get_args()); +if (!function_exists('PHPUnit\Framework\containsOnlyCallable')) { + function containsOnlyCallable(): TraversableContainsOnly + { + return Assert::containsOnlyCallable(...func_get_args()); + } } -function isFinite(): IsFinite -{ - return Assert::isFinite(...func_get_args()); +if (!function_exists('PHPUnit\Framework\containsOnlyFloat')) { + function containsOnlyFloat(): TraversableContainsOnly + { + return Assert::containsOnlyFloat(...func_get_args()); + } } -function isInfinite(): IsInfinite -{ - return Assert::isInfinite(...func_get_args()); +if (!function_exists('PHPUnit\Framework\containsOnlyInt')) { + function containsOnlyInt(): TraversableContainsOnly + { + return Assert::containsOnlyInt(...func_get_args()); + } } -function isNan(): IsNan -{ - return Assert::isNan(...func_get_args()); +if (!function_exists('PHPUnit\Framework\containsOnlyIterable')) { + function containsOnlyIterable(): TraversableContainsOnly + { + return Assert::containsOnlyIterable(...func_get_args()); + } } -function containsEqual($value): TraversableContainsEqual -{ - return Assert::containsEqual(...func_get_args()); +if (!function_exists('PHPUnit\Framework\containsOnlyNull')) { + function containsOnlyNull(): TraversableContainsOnly + { + return Assert::containsOnlyNull(...func_get_args()); + } } -function containsIdentical($value): TraversableContainsIdentical -{ - return Assert::containsIdentical(...func_get_args()); +if (!function_exists('PHPUnit\Framework\containsOnlyNumeric')) { + function containsOnlyNumeric(): TraversableContainsOnly + { + return Assert::containsOnlyNumeric(...func_get_args()); + } } -function containsOnly(string $type): TraversableContainsOnly -{ - return Assert::containsOnly(...func_get_args()); +if (!function_exists('PHPUnit\Framework\containsOnlyObject')) { + function containsOnlyObject(): TraversableContainsOnly + { + return Assert::containsOnlyObject(...func_get_args()); + } } -function containsOnlyInstancesOf(string $className): TraversableContainsOnly -{ - return Assert::containsOnlyInstancesOf(...func_get_args()); +if (!function_exists('PHPUnit\Framework\containsOnlyResource')) { + function containsOnlyResource(): TraversableContainsOnly + { + return Assert::containsOnlyResource(...func_get_args()); + } } -function arrayHasKey($key): ArrayHasKey -{ - return Assert::arrayHasKey(...func_get_args()); +if (!function_exists('PHPUnit\Framework\containsOnlyClosedResource')) { + function containsOnlyClosedResource(): TraversableContainsOnly + { + return Assert::containsOnlyClosedResource(...func_get_args()); + } } -function equalTo($value): IsEqual -{ - return Assert::equalTo(...func_get_args()); +if (!function_exists('PHPUnit\Framework\containsOnlyScalar')) { + function containsOnlyScalar(): TraversableContainsOnly + { + return Assert::containsOnlyScalar(...func_get_args()); + } } -function equalToCanonicalizing($value): IsEqualCanonicalizing -{ - return Assert::equalToCanonicalizing(...func_get_args()); +if (!function_exists('PHPUnit\Framework\containsOnlyString')) { + function containsOnlyString(): TraversableContainsOnly + { + return Assert::containsOnlyString(...func_get_args()); + } } -function equalToIgnoringCase($value): IsEqualIgnoringCase -{ - return Assert::equalToIgnoringCase(...func_get_args()); +if (!function_exists('PHPUnit\Framework\containsOnlyInstancesOf')) { + function containsOnlyInstancesOf(string $className): TraversableContainsOnly + { + return Assert::containsOnlyInstancesOf(...func_get_args()); + } } -function equalToWithDelta($value, float $delta): IsEqualWithDelta -{ - return Assert::equalToWithDelta(...func_get_args()); +if (!function_exists('PHPUnit\Framework\arrayHasKey')) { + function arrayHasKey(mixed $key): ArrayHasKey + { + return Assert::arrayHasKey(...func_get_args()); + } } -function isEmpty(): IsEmpty -{ - return Assert::isEmpty(...func_get_args()); +if (!function_exists('PHPUnit\Framework\isList')) { + function isList(): IsList + { + return Assert::isList(...func_get_args()); + } } -function isWritable(): IsWritable -{ - return Assert::isWritable(...func_get_args()); +if (!function_exists('PHPUnit\Framework\equalTo')) { + function equalTo(mixed $value): IsEqual + { + return Assert::equalTo(...func_get_args()); + } } -function isReadable(): IsReadable -{ - return Assert::isReadable(...func_get_args()); +if (!function_exists('PHPUnit\Framework\equalToCanonicalizing')) { + function equalToCanonicalizing(mixed $value): IsEqualCanonicalizing + { + return Assert::equalToCanonicalizing(...func_get_args()); + } } -function directoryExists(): DirectoryExists -{ - return Assert::directoryExists(...func_get_args()); +if (!function_exists('PHPUnit\Framework\equalToIgnoringCase')) { + function equalToIgnoringCase(mixed $value): IsEqualIgnoringCase + { + return Assert::equalToIgnoringCase(...func_get_args()); + } } -function fileExists(): FileExists -{ - return Assert::fileExists(...func_get_args()); +if (!function_exists('PHPUnit\Framework\equalToWithDelta')) { + function equalToWithDelta(mixed $value, float $delta): IsEqualWithDelta + { + return Assert::equalToWithDelta(...func_get_args()); + } } -function greaterThan($value): GreaterThan -{ - return Assert::greaterThan(...func_get_args()); +if (!function_exists('PHPUnit\Framework\isEmpty')) { + function isEmpty(): IsEmpty + { + return Assert::isEmpty(...func_get_args()); + } } -function greaterThanOrEqual($value): LogicalOr -{ - return Assert::greaterThanOrEqual(...func_get_args()); +if (!function_exists('PHPUnit\Framework\isWritable')) { + function isWritable(): IsWritable + { + return Assert::isWritable(...func_get_args()); + } } -function classHasAttribute(string $attributeName): ClassHasAttribute -{ - return Assert::classHasAttribute(...func_get_args()); +if (!function_exists('PHPUnit\Framework\isReadable')) { + function isReadable(): IsReadable + { + return Assert::isReadable(...func_get_args()); + } } -function classHasStaticAttribute(string $attributeName): ClassHasStaticAttribute -{ - return Assert::classHasStaticAttribute(...func_get_args()); +if (!function_exists('PHPUnit\Framework\directoryExists')) { + function directoryExists(): DirectoryExists + { + return Assert::directoryExists(...func_get_args()); + } } -function objectHasAttribute($attributeName): ObjectHasAttribute -{ - return Assert::objectHasAttribute(...func_get_args()); +if (!function_exists('PHPUnit\Framework\fileExists')) { + function fileExists(): FileExists + { + return Assert::fileExists(...func_get_args()); + } } -function identicalTo($value): IsIdentical -{ - return Assert::identicalTo(...func_get_args()); +if (!function_exists('PHPUnit\Framework\greaterThan')) { + function greaterThan(mixed $value): GreaterThan + { + return Assert::greaterThan(...func_get_args()); + } } -function isInstanceOf(string $className): IsInstanceOf -{ - return Assert::isInstanceOf(...func_get_args()); +if (!function_exists('PHPUnit\Framework\greaterThanOrEqual')) { + function greaterThanOrEqual(mixed $value): LogicalOr + { + return Assert::greaterThanOrEqual(...func_get_args()); + } } -function isType(string $type): IsType -{ - return Assert::isType(...func_get_args()); +if (!function_exists('PHPUnit\Framework\identicalTo')) { + function identicalTo(mixed $value): IsIdentical + { + return Assert::identicalTo(...func_get_args()); + } } -function lessThan($value): LessThan -{ - return Assert::lessThan(...func_get_args()); +if (!function_exists('PHPUnit\Framework\isInstanceOf')) { + function isInstanceOf(string $className): IsInstanceOf + { + return Assert::isInstanceOf(...func_get_args()); + } } -function lessThanOrEqual($value): LogicalOr -{ - return Assert::lessThanOrEqual(...func_get_args()); +if (!function_exists('PHPUnit\Framework\isArray')) { + function isArray(): IsType + { + return Assert::isArray(...func_get_args()); + } } -function matchesRegularExpression(string $pattern): RegularExpression -{ - return Assert::matchesRegularExpression(...func_get_args()); +if (!function_exists('PHPUnit\Framework\isBool')) { + function isBool(): IsType + { + return Assert::isBool(...func_get_args()); + } } -function matches(string $string): StringMatchesFormatDescription -{ - return Assert::matches(...func_get_args()); +if (!function_exists('PHPUnit\Framework\isCallable')) { + function isCallable(): IsType + { + return Assert::isCallable(...func_get_args()); + } } -function stringStartsWith($prefix): StringStartsWith -{ - return Assert::stringStartsWith(...func_get_args()); +if (!function_exists('PHPUnit\Framework\isFloat')) { + function isFloat(): IsType + { + return Assert::isFloat(...func_get_args()); + } } -function stringContains(string $string, bool $case = true): StringContains -{ - return Assert::stringContains(...func_get_args()); +if (!function_exists('PHPUnit\Framework\isInt')) { + function isInt(): IsType + { + return Assert::isInt(...func_get_args()); + } } -function stringEndsWith(string $suffix): StringEndsWith -{ - return Assert::stringEndsWith(...func_get_args()); +if (!function_exists('PHPUnit\Framework\isIterable')) { + function isIterable(): IsType + { + return Assert::isIterable(...func_get_args()); + } } -function countOf(int $count): Count -{ - return Assert::countOf(...func_get_args()); +if (!function_exists('PHPUnit\Framework\isNumeric')) { + function isNumeric(): IsType + { + return Assert::isNumeric(...func_get_args()); + } } -/** - * Returns a matcher that matches when the method is executed - * zero or more times. - */ -function any(): AnyInvokedCountMatcher -{ - return new AnyInvokedCountMatcher; -} - -/** - * Returns a matcher that matches when the method is never executed. - */ -function never(): InvokedCountMatcher -{ - return new InvokedCountMatcher(0); -} - -/** - * Returns a matcher that matches when the method is executed - * at least N times. - */ -function atLeast(int $requiredInvocations): InvokedAtLeastCountMatcher -{ - return new InvokedAtLeastCountMatcher( - $requiredInvocations - ); -} - -/** - * Returns a matcher that matches when the method is executed at least once. - */ -function atLeastOnce(): InvokedAtLeastOnceMatcher -{ - return new InvokedAtLeastOnceMatcher; +if (!function_exists('PHPUnit\Framework\isObject')) { + function isObject(): IsType + { + return Assert::isObject(...func_get_args()); + } } -/** - * Returns a matcher that matches when the method is executed exactly once. - */ -function once(): InvokedCountMatcher -{ - return new InvokedCountMatcher(1); +if (!function_exists('PHPUnit\Framework\isResource')) { + function isResource(): IsType + { + return Assert::isResource(...func_get_args()); + } } -/** - * Returns a matcher that matches when the method is executed - * exactly $count times. - */ -function exactly(int $count): InvokedCountMatcher -{ - return new InvokedCountMatcher($count); +if (!function_exists('PHPUnit\Framework\isClosedResource')) { + function isClosedResource(): IsType + { + return Assert::isClosedResource(...func_get_args()); + } } -/** - * Returns a matcher that matches when the method is executed - * at most N times. - */ -function atMost(int $allowedInvocations): InvokedAtMostCountMatcher -{ - return new InvokedAtMostCountMatcher($allowedInvocations); +if (!function_exists('PHPUnit\Framework\isScalar')) { + function isScalar(): IsType + { + return Assert::isScalar(...func_get_args()); + } } -/** - * Returns a matcher that matches when the method is executed - * at the given index. - */ -function at(int $index): InvokedAtIndexMatcher -{ - return new InvokedAtIndexMatcher($index); +if (!function_exists('PHPUnit\Framework\isString')) { + function isString(): IsType + { + return Assert::isString(...func_get_args()); + } } -function returnValue($value): ReturnStub -{ - return new ReturnStub($value); +if (!function_exists('PHPUnit\Framework\lessThan')) { + function lessThan(mixed $value): LessThan + { + return Assert::lessThan(...func_get_args()); + } } -function returnValueMap(array $valueMap): ReturnValueMapStub -{ - return new ReturnValueMapStub($valueMap); +if (!function_exists('PHPUnit\Framework\lessThanOrEqual')) { + function lessThanOrEqual(mixed $value): LogicalOr + { + return Assert::lessThanOrEqual(...func_get_args()); + } } -function returnArgument(int $argumentIndex): ReturnArgumentStub -{ - return new ReturnArgumentStub($argumentIndex); +if (!function_exists('PHPUnit\Framework\matchesRegularExpression')) { + function matchesRegularExpression(string $pattern): RegularExpression + { + return Assert::matchesRegularExpression(...func_get_args()); + } } -function returnCallback($callback): ReturnCallbackStub -{ - return new ReturnCallbackStub($callback); +if (!function_exists('PHPUnit\Framework\matches')) { + function matches(string $string): StringMatchesFormatDescription + { + return Assert::matches(...func_get_args()); + } } -/** - * Returns the current object. - * - * This method is useful when mocking a fluent interface. - */ -function returnSelf(): ReturnSelfStub -{ - return new ReturnSelfStub; +if (!function_exists('PHPUnit\Framework\stringStartsWith')) { + function stringStartsWith(string $prefix): StringStartsWith + { + return Assert::stringStartsWith(...func_get_args()); + } } -function throwException(Throwable $exception): ExceptionStub -{ - return new ExceptionStub($exception); +if (!function_exists('PHPUnit\Framework\stringContains')) { + function stringContains(string $string, bool $case = true): StringContains + { + return Assert::stringContains(...func_get_args()); + } } -function onConsecutiveCalls(): ConsecutiveCallsStub -{ - $args = func_get_args(); +if (!function_exists('PHPUnit\Framework\stringEndsWith')) { + function stringEndsWith(string $suffix): StringEndsWith + { + return Assert::stringEndsWith(...func_get_args()); + } +} - return new ConsecutiveCallsStub($args); +if (!function_exists('PHPUnit\Framework\stringEqualsStringIgnoringLineEndings')) { + function stringEqualsStringIgnoringLineEndings(string $string): StringEqualsStringIgnoringLineEndings + { + return Assert::stringEqualsStringIgnoringLineEndings(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\countOf')) { + function countOf(int $count): Count + { + return Assert::countOf(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\objectEquals')) { + function objectEquals(object $object, string $method = 'equals'): ObjectEquals + { + return Assert::objectEquals(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\callback')) { + /** + * @template CallbackInput of mixed + * + * @param callable(CallbackInput $callback): bool $callback + * + * @return Callback + */ + function callback(callable $callback): Callback + { + return Assert::callback($callback); + } +} + +if (!function_exists('PHPUnit\Framework\any')) { + /** + * Returns a matcher that matches when the method is executed + * zero or more times. + */ + function any(): AnyInvokedCountMatcher + { + return new AnyInvokedCountMatcher; + } +} + +if (!function_exists('PHPUnit\Framework\never')) { + /** + * Returns a matcher that matches when the method is never executed. + */ + function never(): InvokedCountMatcher + { + return new InvokedCountMatcher(0); + } +} + +if (!function_exists('PHPUnit\Framework\atLeast')) { + /** + * Returns a matcher that matches when the method is executed + * at least N times. + */ + function atLeast(int $requiredInvocations): InvokedAtLeastCountMatcher + { + return new InvokedAtLeastCountMatcher( + $requiredInvocations, + ); + } +} + +if (!function_exists('PHPUnit\Framework\atLeastOnce')) { + /** + * Returns a matcher that matches when the method is executed at least once. + */ + function atLeastOnce(): InvokedAtLeastOnceMatcher + { + return new InvokedAtLeastOnceMatcher; + } +} + +if (!function_exists('PHPUnit\Framework\once')) { + /** + * Returns a matcher that matches when the method is executed exactly once. + */ + function once(): InvokedCountMatcher + { + return new InvokedCountMatcher(1); + } +} + +if (!function_exists('PHPUnit\Framework\exactly')) { + /** + * Returns a matcher that matches when the method is executed + * exactly $count times. + */ + function exactly(int $count): InvokedCountMatcher + { + return new InvokedCountMatcher($count); + } +} + +if (!function_exists('PHPUnit\Framework\atMost')) { + /** + * Returns a matcher that matches when the method is executed + * at most N times. + */ + function atMost(int $allowedInvocations): InvokedAtMostCountMatcher + { + return new InvokedAtMostCountMatcher($allowedInvocations); + } +} + +if (!function_exists('PHPUnit\Framework\throwException')) { + function throwException(Throwable $exception): ExceptionStub + { + return new ExceptionStub($exception); + } } diff --git a/src/Framework/Attributes/After.php b/src/Framework/Attributes/After.php new file mode 100644 index 00000000000..6d36d29138f --- /dev/null +++ b/src/Framework/Attributes/After.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD)] +final readonly class After +{ + private int $priority; + + public function __construct(int $priority = 0) + { + $this->priority = $priority; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/src/Framework/Attributes/AfterClass.php b/src/Framework/Attributes/AfterClass.php new file mode 100644 index 00000000000..d4a9d6f4c5e --- /dev/null +++ b/src/Framework/Attributes/AfterClass.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD)] +final readonly class AfterClass +{ + private int $priority; + + public function __construct(int $priority = 0) + { + $this->priority = $priority; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/src/Framework/Attributes/BackupGlobals.php b/src/Framework/Attributes/BackupGlobals.php new file mode 100644 index 00000000000..f9526ca281f --- /dev/null +++ b/src/Framework/Attributes/BackupGlobals.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +final readonly class BackupGlobals +{ + private bool $enabled; + + public function __construct(bool $enabled) + { + $this->enabled = $enabled; + } + + public function enabled(): bool + { + return $this->enabled; + } +} diff --git a/src/Framework/Attributes/BackupStaticProperties.php b/src/Framework/Attributes/BackupStaticProperties.php new file mode 100644 index 00000000000..e633cf88a95 --- /dev/null +++ b/src/Framework/Attributes/BackupStaticProperties.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +final readonly class BackupStaticProperties +{ + private bool $enabled; + + public function __construct(bool $enabled) + { + $this->enabled = $enabled; + } + + public function enabled(): bool + { + return $this->enabled; + } +} diff --git a/src/Framework/Attributes/Before.php b/src/Framework/Attributes/Before.php new file mode 100644 index 00000000000..c2af00b7fa2 --- /dev/null +++ b/src/Framework/Attributes/Before.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD)] +final readonly class Before +{ + private int $priority; + + public function __construct(int $priority = 0) + { + $this->priority = $priority; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/src/Framework/Attributes/BeforeClass.php b/src/Framework/Attributes/BeforeClass.php new file mode 100644 index 00000000000..2bb5a07b5f3 --- /dev/null +++ b/src/Framework/Attributes/BeforeClass.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD)] +final readonly class BeforeClass +{ + private int $priority; + + public function __construct(int $priority = 0) + { + $this->priority = $priority; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/src/Framework/Attributes/CoversClass.php b/src/Framework/Attributes/CoversClass.php new file mode 100644 index 00000000000..2cf0b2dd267 --- /dev/null +++ b/src/Framework/Attributes/CoversClass.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class CoversClass +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param class-string $className + */ + public function __construct(string $className) + { + $this->className = $className; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } +} diff --git a/src/Framework/Attributes/CoversClassesThatExtendClass.php b/src/Framework/Attributes/CoversClassesThatExtendClass.php new file mode 100644 index 00000000000..486fc5b0a7a --- /dev/null +++ b/src/Framework/Attributes/CoversClassesThatExtendClass.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class CoversClassesThatExtendClass +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param class-string $className + */ + public function __construct(string $className) + { + $this->className = $className; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } +} diff --git a/src/Framework/Attributes/CoversClassesThatImplementInterface.php b/src/Framework/Attributes/CoversClassesThatImplementInterface.php new file mode 100644 index 00000000000..69bcd84603b --- /dev/null +++ b/src/Framework/Attributes/CoversClassesThatImplementInterface.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class CoversClassesThatImplementInterface +{ + /** + * @var class-string + */ + private string $interfaceName; + + /** + * @param class-string $interfaceName + */ + public function __construct(string $interfaceName) + { + $this->interfaceName = $interfaceName; + } + + /** + * @return class-string + */ + public function interfaceName(): string + { + return $this->interfaceName; + } +} diff --git a/src/Framework/Attributes/CoversFunction.php b/src/Framework/Attributes/CoversFunction.php new file mode 100644 index 00000000000..3b58b1914d0 --- /dev/null +++ b/src/Framework/Attributes/CoversFunction.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class CoversFunction +{ + /** + * @var non-empty-string + */ + private string $functionName; + + /** + * @param non-empty-string $functionName + */ + public function __construct(string $functionName) + { + $this->functionName = $functionName; + } + + /** + * @return non-empty-string + */ + public function functionName(): string + { + return $this->functionName; + } +} diff --git a/src/Framework/Attributes/CoversMethod.php b/src/Framework/Attributes/CoversMethod.php new file mode 100644 index 00000000000..4181239b9d8 --- /dev/null +++ b/src/Framework/Attributes/CoversMethod.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class CoversMethod +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function __construct(string $className, string $methodName) + { + $this->className = $className; + $this->methodName = $methodName; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/src/Framework/Attributes/CoversNamespace.php b/src/Framework/Attributes/CoversNamespace.php new file mode 100644 index 00000000000..50d82b99473 --- /dev/null +++ b/src/Framework/Attributes/CoversNamespace.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class CoversNamespace +{ + /** + * @var non-empty-string + */ + private string $namespace; + + /** + * @param non-empty-string $namespace + */ + public function __construct(string $namespace) + { + $this->namespace = $namespace; + } + + /** + * @return non-empty-string + */ + public function namespace(): string + { + return $this->namespace; + } +} diff --git a/src/Framework/Attributes/CoversNothing.php b/src/Framework/Attributes/CoversNothing.php new file mode 100644 index 00000000000..33ce31f30f1 --- /dev/null +++ b/src/Framework/Attributes/CoversNothing.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +final readonly class CoversNothing +{ +} diff --git a/src/Framework/Attributes/CoversTrait.php b/src/Framework/Attributes/CoversTrait.php new file mode 100644 index 00000000000..89927855053 --- /dev/null +++ b/src/Framework/Attributes/CoversTrait.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class CoversTrait +{ + /** + * @var trait-string + */ + private string $traitName; + + /** + * @param trait-string $traitName + */ + public function __construct(string $traitName) + { + $this->traitName = $traitName; + } + + /** + * @return trait-string + */ + public function traitName(): string + { + return $this->traitName; + } +} diff --git a/src/Framework/Attributes/DataProvider.php b/src/Framework/Attributes/DataProvider.php new file mode 100644 index 00000000000..fc4a822f64f --- /dev/null +++ b/src/Framework/Attributes/DataProvider.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class DataProvider +{ + /** + * @var non-empty-string + */ + private string $methodName; + private bool $validateArgumentCount; + + /** + * @param non-empty-string $methodName + */ + public function __construct(string $methodName, bool $validateArgumentCount = true) + { + $this->methodName = $methodName; + $this->validateArgumentCount = $validateArgumentCount; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } + + public function validateArgumentCount(): bool + { + return $this->validateArgumentCount; + } +} diff --git a/src/Framework/Attributes/DataProviderExternal.php b/src/Framework/Attributes/DataProviderExternal.php new file mode 100644 index 00000000000..e1f11ab131d --- /dev/null +++ b/src/Framework/Attributes/DataProviderExternal.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class DataProviderExternal +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + private bool $validateArgumentCount; + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function __construct(string $className, string $methodName, bool $validateArgumentCount = true) + { + $this->className = $className; + $this->methodName = $methodName; + $this->validateArgumentCount = $validateArgumentCount; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } + + public function validateArgumentCount(): bool + { + return $this->validateArgumentCount; + } +} diff --git a/src/Framework/Attributes/Depends.php b/src/Framework/Attributes/Depends.php new file mode 100644 index 00000000000..1375ae5bdaa --- /dev/null +++ b/src/Framework/Attributes/Depends.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class Depends +{ + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param non-empty-string $methodName + */ + public function __construct(string $methodName) + { + $this->methodName = $methodName; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/src/Framework/Attributes/DependsExternal.php b/src/Framework/Attributes/DependsExternal.php new file mode 100644 index 00000000000..c2f9e39f0b4 --- /dev/null +++ b/src/Framework/Attributes/DependsExternal.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class DependsExternal +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function __construct(string $className, string $methodName) + { + $this->className = $className; + $this->methodName = $methodName; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/src/Framework/Attributes/DependsExternalUsingDeepClone.php b/src/Framework/Attributes/DependsExternalUsingDeepClone.php new file mode 100644 index 00000000000..d71452b8837 --- /dev/null +++ b/src/Framework/Attributes/DependsExternalUsingDeepClone.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class DependsExternalUsingDeepClone +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function __construct(string $className, string $methodName) + { + $this->className = $className; + $this->methodName = $methodName; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/src/Framework/Attributes/DependsExternalUsingShallowClone.php b/src/Framework/Attributes/DependsExternalUsingShallowClone.php new file mode 100644 index 00000000000..9fb8fbf7d8b --- /dev/null +++ b/src/Framework/Attributes/DependsExternalUsingShallowClone.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class DependsExternalUsingShallowClone +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function __construct(string $className, string $methodName) + { + $this->className = $className; + $this->methodName = $methodName; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/src/Framework/Attributes/DependsOnClass.php b/src/Framework/Attributes/DependsOnClass.php new file mode 100644 index 00000000000..14465c77e68 --- /dev/null +++ b/src/Framework/Attributes/DependsOnClass.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class DependsOnClass +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param class-string $className + */ + public function __construct(string $className) + { + $this->className = $className; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } +} diff --git a/src/Framework/Attributes/DependsOnClassUsingDeepClone.php b/src/Framework/Attributes/DependsOnClassUsingDeepClone.php new file mode 100644 index 00000000000..dc46c39df58 --- /dev/null +++ b/src/Framework/Attributes/DependsOnClassUsingDeepClone.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class DependsOnClassUsingDeepClone +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param class-string $className + */ + public function __construct(string $className) + { + $this->className = $className; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } +} diff --git a/src/Framework/Attributes/DependsOnClassUsingShallowClone.php b/src/Framework/Attributes/DependsOnClassUsingShallowClone.php new file mode 100644 index 00000000000..5201f045d85 --- /dev/null +++ b/src/Framework/Attributes/DependsOnClassUsingShallowClone.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class DependsOnClassUsingShallowClone +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param class-string $className + */ + public function __construct(string $className) + { + $this->className = $className; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } +} diff --git a/src/Framework/Attributes/DependsUsingDeepClone.php b/src/Framework/Attributes/DependsUsingDeepClone.php new file mode 100644 index 00000000000..173188ec6e0 --- /dev/null +++ b/src/Framework/Attributes/DependsUsingDeepClone.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class DependsUsingDeepClone +{ + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param non-empty-string $methodName + */ + public function __construct(string $methodName) + { + $this->methodName = $methodName; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/src/Framework/Attributes/DependsUsingShallowClone.php b/src/Framework/Attributes/DependsUsingShallowClone.php new file mode 100644 index 00000000000..8aff52a3a2c --- /dev/null +++ b/src/Framework/Attributes/DependsUsingShallowClone.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class DependsUsingShallowClone +{ + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param non-empty-string $methodName + */ + public function __construct(string $methodName) + { + $this->methodName = $methodName; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/src/Framework/Attributes/DisableReturnValueGenerationForTestDoubles.php b/src/Framework/Attributes/DisableReturnValueGenerationForTestDoubles.php new file mode 100644 index 00000000000..2709f569e75 --- /dev/null +++ b/src/Framework/Attributes/DisableReturnValueGenerationForTestDoubles.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS)] +final readonly class DisableReturnValueGenerationForTestDoubles +{ +} diff --git a/src/Framework/Attributes/DoesNotPerformAssertions.php b/src/Framework/Attributes/DoesNotPerformAssertions.php new file mode 100644 index 00000000000..f193e5af359 --- /dev/null +++ b/src/Framework/Attributes/DoesNotPerformAssertions.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +final readonly class DoesNotPerformAssertions +{ +} diff --git a/src/Framework/Attributes/ExcludeGlobalVariableFromBackup.php b/src/Framework/Attributes/ExcludeGlobalVariableFromBackup.php new file mode 100644 index 00000000000..5d1ac72c83e --- /dev/null +++ b/src/Framework/Attributes/ExcludeGlobalVariableFromBackup.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class ExcludeGlobalVariableFromBackup +{ + /** + * @var non-empty-string + */ + private string $globalVariableName; + + /** + * @param non-empty-string $globalVariableName + */ + public function __construct(string $globalVariableName) + { + $this->globalVariableName = $globalVariableName; + } + + /** + * @return non-empty-string + */ + public function globalVariableName(): string + { + return $this->globalVariableName; + } +} diff --git a/src/Framework/Attributes/ExcludeStaticPropertyFromBackup.php b/src/Framework/Attributes/ExcludeStaticPropertyFromBackup.php new file mode 100644 index 00000000000..ea572549185 --- /dev/null +++ b/src/Framework/Attributes/ExcludeStaticPropertyFromBackup.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class ExcludeStaticPropertyFromBackup +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $propertyName; + + /** + * @param class-string $className + * @param non-empty-string $propertyName + */ + public function __construct(string $className, string $propertyName) + { + $this->className = $className; + $this->propertyName = $propertyName; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function propertyName(): string + { + return $this->propertyName; + } +} diff --git a/src/Framework/Attributes/Group.php b/src/Framework/Attributes/Group.php new file mode 100644 index 00000000000..5a6942bb802 --- /dev/null +++ b/src/Framework/Attributes/Group.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class Group +{ + /** + * @var non-empty-string + */ + private string $name; + + /** + * @param non-empty-string $name + */ + public function __construct(string $name) + { + $this->name = $name; + } + + /** + * @return non-empty-string + */ + public function name(): string + { + return $this->name; + } +} diff --git a/src/Framework/Attributes/IgnoreDeprecations.php b/src/Framework/Attributes/IgnoreDeprecations.php new file mode 100644 index 00000000000..d137edd78e6 --- /dev/null +++ b/src/Framework/Attributes/IgnoreDeprecations.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +final readonly class IgnoreDeprecations +{ + /** @var null|non-empty-string */ + private ?string $messagePattern; + + /** + * @param null|non-empty-string $messagePattern + */ + public function __construct(null|string $messagePattern = null) + { + $this->messagePattern = $messagePattern; + } + + /** + * @return null|non-empty-string + */ + public function messagePattern(): ?string + { + return $this->messagePattern; + } +} diff --git a/src/Framework/Attributes/IgnorePhpunitDeprecations.php b/src/Framework/Attributes/IgnorePhpunitDeprecations.php new file mode 100644 index 00000000000..1cebec04dce --- /dev/null +++ b/src/Framework/Attributes/IgnorePhpunitDeprecations.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +final readonly class IgnorePhpunitDeprecations +{ +} diff --git a/src/Framework/Attributes/IgnorePhpunitWarnings.php b/src/Framework/Attributes/IgnorePhpunitWarnings.php new file mode 100644 index 00000000000..af1d22bda92 --- /dev/null +++ b/src/Framework/Attributes/IgnorePhpunitWarnings.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD)] +final readonly class IgnorePhpunitWarnings +{ + /** @var null|non-empty-string */ + private ?string $messagePattern; + + /** + * @param null|non-empty-string $messagePattern + */ + public function __construct(null|string $messagePattern = null) + { + $this->messagePattern = $messagePattern; + } + + /** + * @return null|non-empty-string + */ + public function messagePattern(): ?string + { + return $this->messagePattern; + } +} diff --git a/src/Framework/Attributes/Large.php b/src/Framework/Attributes/Large.php new file mode 100644 index 00000000000..a751a934361 --- /dev/null +++ b/src/Framework/Attributes/Large.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS)] +final readonly class Large +{ +} diff --git a/src/Framework/Attributes/Medium.php b/src/Framework/Attributes/Medium.php new file mode 100644 index 00000000000..debc4b0d319 --- /dev/null +++ b/src/Framework/Attributes/Medium.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS)] +final readonly class Medium +{ +} diff --git a/src/Framework/Attributes/PostCondition.php b/src/Framework/Attributes/PostCondition.php new file mode 100644 index 00000000000..8eb40fe03d6 --- /dev/null +++ b/src/Framework/Attributes/PostCondition.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD)] +final readonly class PostCondition +{ + private int $priority; + + public function __construct(int $priority = 0) + { + $this->priority = $priority; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/src/Framework/Attributes/PreCondition.php b/src/Framework/Attributes/PreCondition.php new file mode 100644 index 00000000000..5f47fc599a0 --- /dev/null +++ b/src/Framework/Attributes/PreCondition.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD)] +final readonly class PreCondition +{ + private int $priority; + + public function __construct(int $priority = 0) + { + $this->priority = $priority; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/src/Framework/Attributes/PreserveGlobalState.php b/src/Framework/Attributes/PreserveGlobalState.php new file mode 100644 index 00000000000..fcd9c637246 --- /dev/null +++ b/src/Framework/Attributes/PreserveGlobalState.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +final readonly class PreserveGlobalState +{ + private bool $enabled; + + public function __construct(bool $enabled) + { + $this->enabled = $enabled; + } + + public function enabled(): bool + { + return $this->enabled; + } +} diff --git a/src/Framework/Attributes/RequiresEnvironmentVariable.php b/src/Framework/Attributes/RequiresEnvironmentVariable.php new file mode 100644 index 00000000000..7e460b99afb --- /dev/null +++ b/src/Framework/Attributes/RequiresEnvironmentVariable.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class RequiresEnvironmentVariable +{ + private string $environmentVariableName; + private null|string $value; + + public function __construct(string $environmentVariableName, null|string $value = null) + { + $this->environmentVariableName = $environmentVariableName; + $this->value = $value; + } + + public function environmentVariableName(): string + { + return $this->environmentVariableName; + } + + public function value(): null|string + { + return $this->value; + } +} diff --git a/src/Framework/Attributes/RequiresFunction.php b/src/Framework/Attributes/RequiresFunction.php new file mode 100644 index 00000000000..e358bdf116b --- /dev/null +++ b/src/Framework/Attributes/RequiresFunction.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class RequiresFunction +{ + /** + * @var non-empty-string + */ + private string $functionName; + + /** + * @param non-empty-string $functionName + */ + public function __construct(string $functionName) + { + $this->functionName = $functionName; + } + + /** + * @return non-empty-string + */ + public function functionName(): string + { + return $this->functionName; + } +} diff --git a/src/Framework/Attributes/RequiresMethod.php b/src/Framework/Attributes/RequiresMethod.php new file mode 100644 index 00000000000..713c1ee91a2 --- /dev/null +++ b/src/Framework/Attributes/RequiresMethod.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class RequiresMethod +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function __construct(string $className, string $methodName) + { + $this->className = $className; + $this->methodName = $methodName; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/src/Framework/Attributes/RequiresOperatingSystem.php b/src/Framework/Attributes/RequiresOperatingSystem.php new file mode 100644 index 00000000000..066d7d3c7c6 --- /dev/null +++ b/src/Framework/Attributes/RequiresOperatingSystem.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +final readonly class RequiresOperatingSystem +{ + /** + * @var non-empty-string + */ + private string $regularExpression; + + /** + * @param non-empty-string $regularExpression + */ + public function __construct(string $regularExpression) + { + $this->regularExpression = $regularExpression; + } + + /** + * @return non-empty-string + */ + public function regularExpression(): string + { + return $this->regularExpression; + } +} diff --git a/src/Framework/Attributes/RequiresOperatingSystemFamily.php b/src/Framework/Attributes/RequiresOperatingSystemFamily.php new file mode 100644 index 00000000000..088ba85cea6 --- /dev/null +++ b/src/Framework/Attributes/RequiresOperatingSystemFamily.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +final readonly class RequiresOperatingSystemFamily +{ + /** + * @var non-empty-string + */ + private string $operatingSystemFamily; + + /** + * @param non-empty-string $operatingSystemFamily + */ + public function __construct(string $operatingSystemFamily) + { + $this->operatingSystemFamily = $operatingSystemFamily; + } + + /** + * @return non-empty-string + */ + public function operatingSystemFamily(): string + { + return $this->operatingSystemFamily; + } +} diff --git a/src/Framework/Attributes/RequiresPhp.php b/src/Framework/Attributes/RequiresPhp.php new file mode 100644 index 00000000000..94fbe51b88f --- /dev/null +++ b/src/Framework/Attributes/RequiresPhp.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +final readonly class RequiresPhp +{ + /** + * @var non-empty-string + */ + private string $versionRequirement; + + /** + * @param non-empty-string $versionRequirement + */ + public function __construct(string $versionRequirement) + { + $this->versionRequirement = $versionRequirement; + } + + /** + * @return non-empty-string + */ + public function versionRequirement(): string + { + return $this->versionRequirement; + } +} diff --git a/src/Framework/Attributes/RequiresPhpExtension.php b/src/Framework/Attributes/RequiresPhpExtension.php new file mode 100644 index 00000000000..61a81ec4781 --- /dev/null +++ b/src/Framework/Attributes/RequiresPhpExtension.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class RequiresPhpExtension +{ + /** + * @var non-empty-string + */ + private string $extension; + + /** + * @var null|non-empty-string + */ + private ?string $versionRequirement; + + /** + * @param non-empty-string $extension + * @param null|non-empty-string $versionRequirement + */ + public function __construct(string $extension, ?string $versionRequirement = null) + { + $this->extension = $extension; + $this->versionRequirement = $versionRequirement; + } + + /** + * @return non-empty-string + */ + public function extension(): string + { + return $this->extension; + } + + /** + * @return null|non-empty-string + */ + public function versionRequirement(): ?string + { + return $this->versionRequirement; + } +} diff --git a/src/Framework/Attributes/RequiresPhpunit.php b/src/Framework/Attributes/RequiresPhpunit.php new file mode 100644 index 00000000000..8b26405b759 --- /dev/null +++ b/src/Framework/Attributes/RequiresPhpunit.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +final readonly class RequiresPhpunit +{ + /** + * @var non-empty-string + */ + private string $versionRequirement; + + /** + * @param non-empty-string $versionRequirement + */ + public function __construct(string $versionRequirement) + { + $this->versionRequirement = $versionRequirement; + } + + /** + * @return non-empty-string + */ + public function versionRequirement(): string + { + return $this->versionRequirement; + } +} diff --git a/src/Framework/Attributes/RequiresPhpunitExtension.php b/src/Framework/Attributes/RequiresPhpunitExtension.php new file mode 100644 index 00000000000..9b6a164f248 --- /dev/null +++ b/src/Framework/Attributes/RequiresPhpunitExtension.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; +use PHPUnit\Runner\Extension\Extension; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class RequiresPhpunitExtension +{ + /** + * @var class-string + */ + private string $extensionClass; + + /** + * @param class-string $extensionClass + */ + public function __construct(string $extensionClass) + { + $this->extensionClass = $extensionClass; + } + + /** + * @return class-string + */ + public function extensionClass(): string + { + return $this->extensionClass; + } +} diff --git a/src/Framework/Attributes/RequiresSetting.php b/src/Framework/Attributes/RequiresSetting.php new file mode 100644 index 00000000000..86828dd0f01 --- /dev/null +++ b/src/Framework/Attributes/RequiresSetting.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class RequiresSetting +{ + /** + * @var non-empty-string + */ + private string $setting; + + /** + * @var non-empty-string + */ + private string $value; + + /** + * @param non-empty-string $setting + * @param non-empty-string $value + */ + public function __construct(string $setting, string $value) + { + $this->setting = $setting; + $this->value = $value; + } + + /** + * @return non-empty-string + */ + public function setting(): string + { + return $this->setting; + } + + /** + * @return non-empty-string + */ + public function value(): string + { + return $this->value; + } +} diff --git a/src/Framework/Attributes/RunInSeparateProcess.php b/src/Framework/Attributes/RunInSeparateProcess.php new file mode 100644 index 00000000000..740d6f38746 --- /dev/null +++ b/src/Framework/Attributes/RunInSeparateProcess.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD)] +final readonly class RunInSeparateProcess +{ +} diff --git a/src/Framework/Attributes/RunTestsInSeparateProcesses.php b/src/Framework/Attributes/RunTestsInSeparateProcesses.php new file mode 100644 index 00000000000..0f8d4320012 --- /dev/null +++ b/src/Framework/Attributes/RunTestsInSeparateProcesses.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS)] +final readonly class RunTestsInSeparateProcesses +{ +} diff --git a/src/Framework/Attributes/Small.php b/src/Framework/Attributes/Small.php new file mode 100644 index 00000000000..5ef284ab1ff --- /dev/null +++ b/src/Framework/Attributes/Small.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS)] +final readonly class Small +{ +} diff --git a/src/Framework/Attributes/Test.php b/src/Framework/Attributes/Test.php new file mode 100644 index 00000000000..a15f7f55770 --- /dev/null +++ b/src/Framework/Attributes/Test.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD)] +final readonly class Test +{ +} diff --git a/src/Framework/Attributes/TestDox.php b/src/Framework/Attributes/TestDox.php new file mode 100644 index 00000000000..b0478506805 --- /dev/null +++ b/src/Framework/Attributes/TestDox.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +final readonly class TestDox +{ + /** + * @var non-empty-string + */ + private string $text; + + /** + * @param non-empty-string $text + */ + public function __construct(string $text) + { + $this->text = $text; + } + + /** + * @return non-empty-string + */ + public function text(): string + { + return $this->text; + } +} diff --git a/src/Framework/Attributes/TestDoxFormatter.php b/src/Framework/Attributes/TestDoxFormatter.php new file mode 100644 index 00000000000..0456d843f66 --- /dev/null +++ b/src/Framework/Attributes/TestDoxFormatter.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD)] +final readonly class TestDoxFormatter +{ + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param non-empty-string $methodName + */ + public function __construct(string $methodName) + { + $this->methodName = $methodName; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/src/Framework/Attributes/TestDoxFormatterExternal.php b/src/Framework/Attributes/TestDoxFormatterExternal.php new file mode 100644 index 00000000000..de5cf701e84 --- /dev/null +++ b/src/Framework/Attributes/TestDoxFormatterExternal.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD)] +final readonly class TestDoxFormatterExternal +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function __construct(string $className, string $methodName) + { + $this->className = $className; + $this->methodName = $methodName; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/src/Framework/Attributes/TestWith.php b/src/Framework/Attributes/TestWith.php new file mode 100644 index 00000000000..b5c33a43e62 --- /dev/null +++ b/src/Framework/Attributes/TestWith.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class TestWith +{ + /** + * @var array + */ + private array $data; + + /** + * @var ?non-empty-string + */ + private ?string $name; + + /** + * @param array $data + * @param ?non-empty-string $name + */ + public function __construct(array $data, ?string $name = null) + { + $this->data = $data; + $this->name = $name; + } + + /** + * @return array + */ + public function data(): array + { + return $this->data; + } + + /** + * @return ?non-empty-string + */ + public function name(): ?string + { + return $this->name; + } +} diff --git a/src/Framework/Attributes/TestWithJson.php b/src/Framework/Attributes/TestWithJson.php new file mode 100644 index 00000000000..bfe9c09226b --- /dev/null +++ b/src/Framework/Attributes/TestWithJson.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class TestWithJson +{ + /** + * @var non-empty-string + */ + private string $json; + + /** + * @var ?non-empty-string + */ + private ?string $name; + + /** + * @param non-empty-string $json + * @param ?non-empty-string $name + */ + public function __construct(string $json, ?string $name = null) + { + $this->json = $json; + $this->name = $name; + } + + /** + * @return non-empty-string + */ + public function json(): string + { + return $this->json; + } + + /** + * @return ?non-empty-string + */ + public function name(): ?string + { + return $this->name; + } +} diff --git a/src/Framework/Attributes/Ticket.php b/src/Framework/Attributes/Ticket.php new file mode 100644 index 00000000000..e463247e2ef --- /dev/null +++ b/src/Framework/Attributes/Ticket.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class Ticket +{ + /** + * @var non-empty-string + */ + private string $text; + + /** + * @param non-empty-string $text + */ + public function __construct(string $text) + { + $this->text = $text; + } + + /** + * @return non-empty-string + */ + public function text(): string + { + return $this->text; + } +} diff --git a/src/Framework/Attributes/UsesClass.php b/src/Framework/Attributes/UsesClass.php new file mode 100644 index 00000000000..f7078bce671 --- /dev/null +++ b/src/Framework/Attributes/UsesClass.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class UsesClass +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param class-string $className + */ + public function __construct(string $className) + { + $this->className = $className; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } +} diff --git a/src/Framework/Attributes/UsesClassesThatExtendClass.php b/src/Framework/Attributes/UsesClassesThatExtendClass.php new file mode 100644 index 00000000000..d1aa73faa88 --- /dev/null +++ b/src/Framework/Attributes/UsesClassesThatExtendClass.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class UsesClassesThatExtendClass +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param class-string $className + */ + public function __construct(string $className) + { + $this->className = $className; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } +} diff --git a/src/Framework/Attributes/UsesClassesThatImplementInterface.php b/src/Framework/Attributes/UsesClassesThatImplementInterface.php new file mode 100644 index 00000000000..0f2241c8675 --- /dev/null +++ b/src/Framework/Attributes/UsesClassesThatImplementInterface.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class UsesClassesThatImplementInterface +{ + /** + * @var class-string + */ + private string $interfaceName; + + /** + * @param class-string $interfaceName + */ + public function __construct(string $interfaceName) + { + $this->interfaceName = $interfaceName; + } + + /** + * @return class-string + */ + public function interfaceName(): string + { + return $this->interfaceName; + } +} diff --git a/src/Framework/Attributes/UsesFunction.php b/src/Framework/Attributes/UsesFunction.php new file mode 100644 index 00000000000..12cc408b982 --- /dev/null +++ b/src/Framework/Attributes/UsesFunction.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class UsesFunction +{ + /** + * @var non-empty-string + */ + private string $functionName; + + /** + * @param non-empty-string $functionName + */ + public function __construct(string $functionName) + { + $this->functionName = $functionName; + } + + /** + * @return non-empty-string + */ + public function functionName(): string + { + return $this->functionName; + } +} diff --git a/src/Framework/Attributes/UsesMethod.php b/src/Framework/Attributes/UsesMethod.php new file mode 100644 index 00000000000..b1ee19b54d9 --- /dev/null +++ b/src/Framework/Attributes/UsesMethod.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class UsesMethod +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function __construct(string $className, string $methodName) + { + $this->className = $className; + $this->methodName = $methodName; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/src/Framework/Attributes/UsesNamespace.php b/src/Framework/Attributes/UsesNamespace.php new file mode 100644 index 00000000000..ad929cdb128 --- /dev/null +++ b/src/Framework/Attributes/UsesNamespace.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class UsesNamespace +{ + /** + * @var non-empty-string + */ + private string $namespace; + + /** + * @param non-empty-string $namespace + */ + public function __construct(string $namespace) + { + $this->namespace = $namespace; + } + + /** + * @return non-empty-string + */ + public function namespace(): string + { + return $this->namespace; + } +} diff --git a/src/Framework/Attributes/UsesTrait.php b/src/Framework/Attributes/UsesTrait.php new file mode 100644 index 00000000000..469e79c6866 --- /dev/null +++ b/src/Framework/Attributes/UsesTrait.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class UsesTrait +{ + /** + * @var trait-string + */ + private string $traitName; + + /** + * @param trait-string $traitName + */ + public function __construct(string $traitName) + { + $this->traitName = $traitName; + } + + /** + * @return trait-string + */ + public function traitName(): string + { + return $this->traitName; + } +} diff --git a/src/Framework/Attributes/WithEnvironmentVariable.php b/src/Framework/Attributes/WithEnvironmentVariable.php new file mode 100644 index 00000000000..6d8e2d305ad --- /dev/null +++ b/src/Framework/Attributes/WithEnvironmentVariable.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class WithEnvironmentVariable +{ + /** + * @var non-empty-string + */ + private string $environmentVariableName; + private null|string $value; + + /** + * @param non-empty-string $environmentVariableName + */ + public function __construct(string $environmentVariableName, null|string $value = null) + { + $this->environmentVariableName = $environmentVariableName; + $this->value = $value; + } + + /** + * @return non-empty-string + */ + public function environmentVariableName(): string + { + return $this->environmentVariableName; + } + + public function value(): null|string + { + return $this->value; + } +} diff --git a/src/Framework/Attributes/WithoutErrorHandler.php b/src/Framework/Attributes/WithoutErrorHandler.php new file mode 100644 index 00000000000..a10f0fc2f16 --- /dev/null +++ b/src/Framework/Attributes/WithoutErrorHandler.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD)] +final readonly class WithoutErrorHandler +{ +} diff --git a/src/Framework/Constraint/Boolean/IsFalse.php b/src/Framework/Constraint/Boolean/IsFalse.php index 8b11e0ac57d..5846cf19cb0 100644 --- a/src/Framework/Constraint/Boolean/IsFalse.php +++ b/src/Framework/Constraint/Boolean/IsFalse.php @@ -10,7 +10,7 @@ namespace PHPUnit\Framework\Constraint; /** - * Constraint that accepts false. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class IsFalse extends Constraint { @@ -25,10 +25,8 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return $other === false; } diff --git a/src/Framework/Constraint/Boolean/IsTrue.php b/src/Framework/Constraint/Boolean/IsTrue.php index 7948c8fa0d0..be589523ba4 100644 --- a/src/Framework/Constraint/Boolean/IsTrue.php +++ b/src/Framework/Constraint/Boolean/IsTrue.php @@ -10,7 +10,7 @@ namespace PHPUnit\Framework\Constraint; /** - * Constraint that accepts true. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class IsTrue extends Constraint { @@ -25,10 +25,8 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return $other === true; } diff --git a/src/Framework/Constraint/Callback.php b/src/Framework/Constraint/Callback.php index 97ec763dfdf..2b09ff1e082 100644 --- a/src/Framework/Constraint/Callback.php +++ b/src/Framework/Constraint/Callback.php @@ -9,18 +9,24 @@ */ namespace PHPUnit\Framework\Constraint; -use function call_user_func; +use Closure; +use ReflectionFunction; /** - * Constraint that evaluates against a specified closure. + * @template CallbackInput of mixed + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class Callback extends Constraint { /** - * @var callable + * @var callable(CallbackInput): bool */ - private $callback; + private readonly mixed $callback; + /** + * @param callable(CallbackInput $input): bool $callback + */ public function __construct(callable $callback) { $this->callback = $callback; @@ -34,14 +40,23 @@ public function toString(): string return 'is accepted by specified callback'; } + public function isVariadic(): bool + { + return new ReflectionFunction(Closure::fromCallable($this->callback))->isVariadic(); + } + /** * Evaluates the constraint for parameter $value. Returns true if the * constraint is met, false otherwise. * - * @param mixed $other value or object to evaluate + * @param CallbackInput $other */ - protected function matches($other): bool + protected function matches(mixed $other): bool { - return call_user_func($this->callback, $other); + if ($this->isVariadic()) { + return ($this->callback)(...$other); + } + + return ($this->callback)($other); } } diff --git a/src/Framework/Constraint/Cardinality/Count.php b/src/Framework/Constraint/Cardinality/Count.php index 60d922406e9..2ec012a0e5a 100644 --- a/src/Framework/Constraint/Cardinality/Count.php +++ b/src/Framework/Constraint/Cardinality/Count.php @@ -10,23 +10,23 @@ namespace PHPUnit\Framework\Constraint; use function count; -use function is_array; +use function is_countable; use function iterator_count; use function sprintf; -use Countable; use EmptyIterator; use Generator; use Iterator; use IteratorAggregate; use PHPUnit\Framework\Exception; +use PHPUnit\Framework\GeneratorNotSupportedException; use Traversable; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ class Count extends Constraint { - /** - * @var int - */ - private $expectedCount; + private readonly int $expectedCount; public function __construct(int $expected) { @@ -37,7 +37,7 @@ public function toString(): string { return sprintf( 'count matches %d', - $this->expectedCount + $this->expectedCount, ); } @@ -47,7 +47,7 @@ public function toString(): string * * @throws Exception */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return $this->expectedCount === $this->getCountOf($other); } @@ -55,9 +55,9 @@ protected function matches($other): bool /** * @throws Exception */ - protected function getCountOf($other): ?int + protected function getCountOf(mixed $other): ?int { - if ($other instanceof Countable || is_array($other)) { + if (is_countable($other)) { return count($other); } @@ -73,7 +73,7 @@ protected function getCountOf($other): ?int throw new Exception( $e->getMessage(), $e->getCode(), - $e + $e, ); } } @@ -81,7 +81,7 @@ protected function getCountOf($other): ?int $iterator = $other; if ($iterator instanceof Generator) { - return $this->getCountOfGenerator($iterator); + throw new GeneratorNotSupportedException; } if (!$iterator instanceof Iterator) { @@ -107,33 +107,20 @@ protected function getCountOf($other): ?int return null; } - /** - * Returns the total number of iterations from a generator. - * This will fully exhaust the generator. - */ - protected function getCountOfGenerator(Generator $generator): int - { - for ($count = 0; $generator->valid(); $generator->next()) { - $count++; - } - - return $count; - } - /** * Returns the description of the failure. * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. * - * @param mixed $other evaluated value or object + * @throws Exception */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return sprintf( 'actual size %d matches expected size %d', (int) $this->getCountOf($other), - $this->expectedCount + $this->expectedCount, ); } } diff --git a/src/Framework/Constraint/Cardinality/GreaterThan.php b/src/Framework/Constraint/Cardinality/GreaterThan.php index b007615ca39..7b252dd9c86 100644 --- a/src/Framework/Constraint/Cardinality/GreaterThan.php +++ b/src/Framework/Constraint/Cardinality/GreaterThan.php @@ -9,42 +9,33 @@ */ namespace PHPUnit\Framework\Constraint; +use PHPUnit\Util\Exporter; + /** - * Constraint that asserts that the value it is evaluated for is greater - * than a given value. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class GreaterThan extends Constraint { - /** - * @var float|int - */ - private $value; + private readonly mixed $value; - /** - * @param float|int $value - */ - public function __construct($value) + public function __construct(mixed $value) { $this->value = $value; } /** * Returns a string representation of the constraint. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ public function toString(): string { - return 'is greater than ' . $this->exporter()->export($this->value); + return 'is greater than ' . Exporter::export($this->value); } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return $this->value < $other; } diff --git a/src/Framework/Constraint/Cardinality/IsEmpty.php b/src/Framework/Constraint/Cardinality/IsEmpty.php index 555afa7633c..31942d9f000 100644 --- a/src/Framework/Constraint/Cardinality/IsEmpty.php +++ b/src/Framework/Constraint/Cardinality/IsEmpty.php @@ -12,12 +12,12 @@ use function count; use function gettype; use function sprintf; -use function strpos; +use function str_starts_with; use Countable; use EmptyIterator; /** - * Constraint that checks whether a variable is empty(). + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class IsEmpty extends Constraint { @@ -32,10 +32,8 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { if ($other instanceof EmptyIterator) { return true; @@ -45,6 +43,7 @@ protected function matches($other): bool return count($other) === 0; } + /** @phpstan-ignore empty.notAllowed */ return empty($other); } @@ -53,18 +52,16 @@ protected function matches($other): bool * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { $type = gettype($other); return sprintf( '%s %s %s', - strpos($type, 'a') === 0 || strpos($type, 'o') === 0 ? 'an' : 'a', + str_starts_with($type, 'a') || str_starts_with($type, 'o') ? 'an' : 'a', $type, - $this->toString() + $this->toString(), ); } } diff --git a/src/Framework/Constraint/Cardinality/LessThan.php b/src/Framework/Constraint/Cardinality/LessThan.php index 781c81744a3..122dd7347eb 100644 --- a/src/Framework/Constraint/Cardinality/LessThan.php +++ b/src/Framework/Constraint/Cardinality/LessThan.php @@ -9,42 +9,33 @@ */ namespace PHPUnit\Framework\Constraint; +use PHPUnit\Util\Exporter; + /** - * Constraint that asserts that the value it is evaluated for is less than - * a given value. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class LessThan extends Constraint { - /** - * @var float|int - */ - private $value; + private readonly mixed $value; - /** - * @param float|int $value - */ - public function __construct($value) + public function __construct(mixed $value) { $this->value = $value; } /** * Returns a string representation of the constraint. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ public function toString(): string { - return 'is less than ' . $this->exporter()->export($this->value); + return 'is less than ' . Exporter::export($this->value); } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return $this->value > $other; } diff --git a/src/Framework/Constraint/Cardinality/SameSize.php b/src/Framework/Constraint/Cardinality/SameSize.php index a321680fa29..a2417a28d78 100644 --- a/src/Framework/Constraint/Cardinality/SameSize.php +++ b/src/Framework/Constraint/Cardinality/SameSize.php @@ -9,9 +9,20 @@ */ namespace PHPUnit\Framework\Constraint; +use Countable; +use PHPUnit\Framework\Exception; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ final class SameSize extends Count { - public function __construct(iterable $expected) + /** + * @param Countable|iterable $expected + * + * @throws Exception + */ + public function __construct(Countable|iterable $expected) { parent::__construct((int) $this->getCountOf($expected)); } diff --git a/src/Framework/Constraint/Constraint.php b/src/Framework/Constraint/Constraint.php index 71db9418df1..2f7028d75bb 100644 --- a/src/Framework/Constraint/Constraint.php +++ b/src/Framework/Constraint/Constraint.php @@ -9,22 +9,41 @@ */ namespace PHPUnit\Framework\Constraint; +use function assert; +use function gettype; +use function is_int; +use function is_object; use function sprintf; +use function str_replace; +use function strpos; +use function strtolower; +use function substr; use Countable; +use PHPUnit\Framework\Assert; use PHPUnit\Framework\ExpectationFailedException; use PHPUnit\Framework\SelfDescribing; +use PHPUnit\Util\Exporter; +use ReflectionObject; use SebastianBergmann\Comparator\ComparisonFailure; -use SebastianBergmann\Exporter\Exporter; /** - * Abstract base class for constraints which can be applied to any value. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ abstract class Constraint implements Countable, SelfDescribing { /** - * @var ?Exporter + * @template A + * + * @param A $actual + * + * @return A */ - private $exporter; + final public function __invoke(mixed $actual): mixed + { + Assert::assertThat($actual, $this); + + return $actual; + } /** * Evaluates the constraint for parameter $other. @@ -37,9 +56,8 @@ abstract class Constraint implements Countable, SelfDescribing * failure. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + public function evaluate(mixed $other, string $description = '', bool $returnResult = false): ?bool { $success = false; @@ -66,25 +84,13 @@ public function count(): int return 1; } - protected function exporter(): Exporter - { - if ($this->exporter === null) { - $this->exporter = new Exporter; - } - - return $this->exporter; - } - /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * This method can be overridden to implement the evaluation algorithm. - * - * @param mixed $other value or object to evaluate - * @codeCoverageIgnore */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return false; } @@ -92,35 +98,28 @@ protected function matches($other): bool /** * Throws an exception for the given compared value and test description. * - * @param mixed $other evaluated value or object - * @param string $description Additional information about the test - * @param ComparisonFailure $comparisonFailure - * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-return never-return */ - protected function fail($other, $description, ComparisonFailure $comparisonFailure = null): void + protected function fail(mixed $other, string $description, ?ComparisonFailure $comparisonFailure = null): never { $failureDescription = sprintf( 'Failed asserting that %s.', - $this->failureDescription($other) + $this->failureDescription($other), ); $additionalFailureDescription = $this->additionalFailureDescription($other); - if ($additionalFailureDescription) { + if ($additionalFailureDescription !== '') { $failureDescription .= "\n" . $additionalFailureDescription; } - if (!empty($description)) { + if ($description !== '') { $failureDescription = $description . "\n" . $failureDescription; } throw new ExpectationFailedException( $failureDescription, - $comparisonFailure + $comparisonFailure, ); } @@ -129,10 +128,8 @@ protected function fail($other, $description, ComparisonFailure $comparisonFailu * * The function can be overridden to provide additional failure * information like a diff - * - * @param mixed $other evaluated value or object */ - protected function additionalFailureDescription($other): string + protected function additionalFailureDescription(mixed $other): string { return ''; } @@ -145,14 +142,10 @@ protected function additionalFailureDescription($other): string * * To provide additional failure information additionalFailureDescription * can be used. - * - * @param mixed $other evaluated value or object - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { - return $this->exporter()->export($other) . ' ' . $this->toString(); + return Exporter::export($other) . ' ' . $this->toString(); } /** @@ -166,11 +159,8 @@ protected function failureDescription($other): string * * The method shall return empty string, when it does not handle * customization by itself. - * - * @param Operator $operator the $operator of the expression - * @param mixed $role role of $this constraint in the $operator expression */ - protected function toStringInContext(Operator $operator, $role): string + protected function toStringInContext(Operator $operator, mixed $role): string { return ''; } @@ -179,19 +169,15 @@ protected function toStringInContext(Operator $operator, $role): string * Returns the description of the failure when this constraint appears in * context of an $operator expression. * - * The purpose of this method is to provide meaningful failue description + * The purpose of this method is to provide meaningful failure description * in context of operators such as LogicalNot. Native PHPUnit constraints * are supported out of the box by LogicalNot, but externally developed * ones had no way to provide correct messages in this context. * * The method shall return empty string, when it does not handle * customization by itself. - * - * @param Operator $operator the $operator of the expression - * @param mixed $role role of $this constraint in the $operator expression - * @param mixed $other evaluated value or object */ - protected function failureDescriptionInContext(Operator $operator, $role, $other): string + protected function failureDescriptionInContext(Operator $operator, mixed $role, mixed $other): string { $string = $this->toStringInContext($operator, $role); @@ -199,7 +185,7 @@ protected function failureDescriptionInContext(Operator $operator, $role, $other return ''; } - return $this->exporter()->export($other) . ' ' . $string; + return Exporter::export($other) . ' ' . $string; } /** @@ -266,4 +252,45 @@ protected function reduce(): self { return $this; } + + /** + * @return non-empty-string + */ + protected function valueToTypeStringFragment(mixed $value): string + { + if (is_object($value)) { + $reflector = new ReflectionObject($value); + + if ($reflector->isAnonymous()) { + $name = str_replace('class@anonymous', '', $reflector->getName()); + + $length = strpos($name, '$'); + + assert(is_int($length)); + + $name = substr($name, 0, $length); + + return 'an instance of anonymous class created at ' . $name . ' '; + } + + return 'an instance of class ' . $reflector->getName() . ' '; + } + + $type = strtolower(gettype($value)); + + if ($type === 'double') { + $type = 'float'; + } + + if ($type === 'resource (closed)') { + $type = 'closed resource'; + } + + return match ($type) { + 'array', 'integer' => 'an ' . $type . ' ', + 'boolean', 'closed resource', 'float', 'resource', 'string' => 'a ' . $type . ' ', + 'null' => 'null ', + default => 'a value of ' . $type . ' ', + }; + } } diff --git a/src/Framework/Constraint/Equality/IsEqual.php b/src/Framework/Constraint/Equality/IsEqual.php index 93151f75869..2051bfce3e7 100644 --- a/src/Framework/Constraint/Equality/IsEqual.php +++ b/src/Framework/Constraint/Equality/IsEqual.php @@ -11,49 +11,23 @@ use function is_string; use function sprintf; -use function strpos; +use function str_contains; use function trim; use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Util\Exporter; use SebastianBergmann\Comparator\ComparisonFailure; use SebastianBergmann\Comparator\Factory as ComparatorFactory; /** - * Constraint that checks if one value is equal to another. - * - * Equality is checked with PHP's == operator, the operator is explained in - * detail at {@url https://php.net/manual/en/types.comparisons.php}. - * Two values are equal if they have the same value disregarding type. - * - * The expected value is passed in the constructor. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class IsEqual extends Constraint { - /** - * @var mixed - */ - private $value; - - /** - * @var float - */ - private $delta; - - /** - * @var bool - */ - private $canonicalize; + private readonly mixed $value; - /** - * @var bool - */ - private $ignoreCase; - - public function __construct($value, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false) + public function __construct(mixed $value) { - $this->value = $value; - $this->delta = $delta; - $this->canonicalize = $canonicalize; - $this->ignoreCase = $ignoreCase; + $this->value = $value; } /** @@ -67,10 +41,8 @@ public function __construct($value, float $delta = 0.0, bool $canonicalize = fal * failure. * * @throws ExpectationFailedException - * - * @return bool */ - public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + public function evaluate(mixed $other, string $description = '', bool $returnResult = false): bool { // If $this->value and $other are identical, they are also equal. // This is the most common path and will allow us to skip @@ -84,15 +56,12 @@ public function evaluate($other, string $description = '', bool $returnResult = try { $comparator = $comparatorFactory->getComparatorFor( $this->value, - $other + $other, ); $comparator->assertEquals( $this->value, $other, - $this->delta, - $this->canonicalize, - $this->ignoreCase ); } catch (ComparisonFailure $f) { if ($returnResult) { @@ -101,7 +70,7 @@ public function evaluate($other, string $description = '', bool $returnResult = throw new ExpectationFailedException( trim($description . "\n" . $f->getMessage()), - $f + $f, ); } @@ -110,35 +79,26 @@ public function evaluate($other, string $description = '', bool $returnResult = /** * Returns a string representation of the constraint. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ public function toString(): string { $delta = ''; if (is_string($this->value)) { - if (strpos($this->value, "\n") !== false) { + if (str_contains($this->value, "\n")) { return 'is equal to '; } return sprintf( "is equal to '%s'", - $this->value - ); - } - - if ($this->delta != 0) { - $delta = sprintf( - ' with delta <%F>', - $this->delta + $this->value, ); } return sprintf( 'is equal to %s%s', - $this->exporter()->export($this->value), - $delta + Exporter::export($this->value), + $delta, ); } } diff --git a/src/Framework/Constraint/Equality/IsEqualCanonicalizing.php b/src/Framework/Constraint/Equality/IsEqualCanonicalizing.php index 964352ba323..b826464db5c 100644 --- a/src/Framework/Constraint/Equality/IsEqualCanonicalizing.php +++ b/src/Framework/Constraint/Equality/IsEqualCanonicalizing.php @@ -11,20 +11,21 @@ use function is_string; use function sprintf; -use function strpos; +use function str_contains; use function trim; use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Util\Exporter; use SebastianBergmann\Comparator\ComparisonFailure; use SebastianBergmann\Comparator\Factory as ComparatorFactory; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ final class IsEqualCanonicalizing extends Constraint { - /** - * @var mixed - */ - private $value; + private readonly mixed $value; - public function __construct($value) + public function __construct(mixed $value) { $this->value = $value; } @@ -41,7 +42,7 @@ public function __construct($value) * * @throws ExpectationFailedException */ - public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + public function evaluate(mixed $other, string $description = '', bool $returnResult = false): bool { // If $this->value and $other are identical, they are also equal. // This is the most common path and will allow us to skip @@ -55,7 +56,7 @@ public function evaluate($other, string $description = '', bool $returnResult = try { $comparator = $comparatorFactory->getComparatorFor( $this->value, - $other + $other, ); $comparator->assertEquals( @@ -63,7 +64,6 @@ public function evaluate($other, string $description = '', bool $returnResult = $other, 0.0, true, - false ); } catch (ComparisonFailure $f) { if ($returnResult) { @@ -72,7 +72,7 @@ public function evaluate($other, string $description = '', bool $returnResult = throw new ExpectationFailedException( trim($description . "\n" . $f->getMessage()), - $f + $f, ); } @@ -81,25 +81,23 @@ public function evaluate($other, string $description = '', bool $returnResult = /** * Returns a string representation of the constraint. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ public function toString(): string { if (is_string($this->value)) { - if (strpos($this->value, "\n") !== false) { + if (str_contains($this->value, "\n")) { return 'is equal to '; } return sprintf( "is equal to '%s'", - $this->value + $this->value, ); } return sprintf( 'is equal to %s', - $this->exporter()->export($this->value) + Exporter::export($this->value), ); } } diff --git a/src/Framework/Constraint/Equality/IsEqualIgnoringCase.php b/src/Framework/Constraint/Equality/IsEqualIgnoringCase.php index 72d451720f3..c7b2c31da54 100644 --- a/src/Framework/Constraint/Equality/IsEqualIgnoringCase.php +++ b/src/Framework/Constraint/Equality/IsEqualIgnoringCase.php @@ -11,20 +11,21 @@ use function is_string; use function sprintf; -use function strpos; +use function str_contains; use function trim; use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Util\Exporter; use SebastianBergmann\Comparator\ComparisonFailure; use SebastianBergmann\Comparator\Factory as ComparatorFactory; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ final class IsEqualIgnoringCase extends Constraint { - /** - * @var mixed - */ - private $value; + private readonly mixed $value; - public function __construct($value) + public function __construct(mixed $value) { $this->value = $value; } @@ -41,7 +42,7 @@ public function __construct($value) * * @throws ExpectationFailedException */ - public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + public function evaluate(mixed $other, string $description = '', bool $returnResult = false): bool { // If $this->value and $other are identical, they are also equal. // This is the most common path and will allow us to skip @@ -55,7 +56,7 @@ public function evaluate($other, string $description = '', bool $returnResult = try { $comparator = $comparatorFactory->getComparatorFor( $this->value, - $other + $other, ); $comparator->assertEquals( @@ -63,7 +64,7 @@ public function evaluate($other, string $description = '', bool $returnResult = $other, 0.0, false, - true + true, ); } catch (ComparisonFailure $f) { if ($returnResult) { @@ -72,7 +73,7 @@ public function evaluate($other, string $description = '', bool $returnResult = throw new ExpectationFailedException( trim($description . "\n" . $f->getMessage()), - $f + $f, ); } @@ -81,25 +82,23 @@ public function evaluate($other, string $description = '', bool $returnResult = /** * Returns a string representation of the constraint. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ public function toString(): string { if (is_string($this->value)) { - if (strpos($this->value, "\n") !== false) { + if (str_contains($this->value, "\n")) { return 'is equal to '; } return sprintf( "is equal to '%s'", - $this->value + $this->value, ); } return sprintf( 'is equal to %s', - $this->exporter()->export($this->value) + Exporter::export($this->value), ); } } diff --git a/src/Framework/Constraint/Equality/IsEqualWithDelta.php b/src/Framework/Constraint/Equality/IsEqualWithDelta.php index 3ec72998897..7f18b5bbee0 100644 --- a/src/Framework/Constraint/Equality/IsEqualWithDelta.php +++ b/src/Framework/Constraint/Equality/IsEqualWithDelta.php @@ -12,22 +12,19 @@ use function sprintf; use function trim; use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Util\Exporter; use SebastianBergmann\Comparator\ComparisonFailure; use SebastianBergmann\Comparator\Factory as ComparatorFactory; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ final class IsEqualWithDelta extends Constraint { - /** - * @var mixed - */ - private $value; - - /** - * @var float - */ - private $delta; + private readonly mixed $value; + private readonly float $delta; - public function __construct($value, float $delta) + public function __construct(mixed $value, float $delta) { $this->value = $value; $this->delta = $delta; @@ -45,7 +42,7 @@ public function __construct($value, float $delta) * * @throws ExpectationFailedException */ - public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + public function evaluate(mixed $other, string $description = '', bool $returnResult = false): bool { // If $this->value and $other are identical, they are also equal. // This is the most common path and will allow us to skip @@ -59,13 +56,13 @@ public function evaluate($other, string $description = '', bool $returnResult = try { $comparator = $comparatorFactory->getComparatorFor( $this->value, - $other + $other, ); $comparator->assertEquals( $this->value, $other, - $this->delta + $this->delta, ); } catch (ComparisonFailure $f) { if ($returnResult) { @@ -74,7 +71,7 @@ public function evaluate($other, string $description = '', bool $returnResult = throw new ExpectationFailedException( trim($description . "\n" . $f->getMessage()), - $f + $f, ); } @@ -83,15 +80,13 @@ public function evaluate($other, string $description = '', bool $returnResult = /** * Returns a string representation of the constraint. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ public function toString(): string { return sprintf( - 'is equal to %s with delta <%F>>', - $this->exporter()->export($this->value), - $this->delta + 'is equal to %s with delta <%F>', + Exporter::export($this->value), + $this->delta, ); } } diff --git a/src/Framework/Constraint/Exception/Exception.php b/src/Framework/Constraint/Exception/Exception.php index 5119071e04f..a84f6eb988f 100644 --- a/src/Framework/Constraint/Exception/Exception.php +++ b/src/Framework/Constraint/Exception/Exception.php @@ -9,17 +9,18 @@ */ namespace PHPUnit\Framework\Constraint; -use function get_class; use function sprintf; use PHPUnit\Util\Filter; use Throwable; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ final class Exception extends Constraint { - /** - * @var string - */ - private $className; + private readonly string $className; public function __construct(string $className) { @@ -33,17 +34,15 @@ public function toString(): string { return sprintf( 'exception of type "%s"', - $this->className + $this->className, ); } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return $other instanceof $this->className; } @@ -54,29 +53,29 @@ protected function matches($other): bool * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. * - * @param mixed $other evaluated value or object + * @throws \PHPUnit\Framework\Exception */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { - if ($other !== null) { - $message = ''; - - if ($other instanceof Throwable) { - $message = '. Message was: "' . $other->getMessage() . '" at' - . "\n" . Filter::getFilteredStacktrace($other); - } - + if ($other === null) { return sprintf( - 'exception of type "%s" matches expected exception "%s"%s', - get_class($other), + 'exception of type "%s" is thrown', $this->className, - $message ); } + $message = ''; + + if ($other instanceof Throwable) { + $message = '. Message was: "' . $other->getMessage() . '" at' + . "\n" . Filter::stackTraceFromThrowableAsString($other); + } + return sprintf( - 'exception of type "%s" is thrown', - $this->className + 'exception of type "%s" matches expected exception "%s"%s', + $other::class, + $this->className, + $message, ); } } diff --git a/src/Framework/Constraint/Exception/ExceptionCode.php b/src/Framework/Constraint/Exception/ExceptionCode.php index 682f48cb367..666f733728f 100644 --- a/src/Framework/Constraint/Exception/ExceptionCode.php +++ b/src/Framework/Constraint/Exception/ExceptionCode.php @@ -10,37 +10,34 @@ namespace PHPUnit\Framework\Constraint; use function sprintf; -use Throwable; +use PHPUnit\Util\Exporter; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ final class ExceptionCode extends Constraint { - /** - * @var int|string - */ - private $expectedCode; + private readonly int|string $expectedCode; - /** - * @param int|string $expected - */ - public function __construct($expected) + public function __construct(int|string $expected) { $this->expectedCode = $expected; } public function toString(): string { - return 'exception code is '; + return 'exception code is ' . $this->expectedCode; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param Throwable $other */ - protected function matches($other): bool + protected function matches(mixed $other): bool { - return (string) $other->getCode() === (string) $this->expectedCode; + return (string) $other === (string) $this->expectedCode; } /** @@ -48,17 +45,13 @@ protected function matches($other): bool * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return sprintf( '%s is equal to expected exception code %s', - $this->exporter()->export($other->getCode()), - $this->exporter()->export($this->expectedCode) + Exporter::export($other), + Exporter::export($this->expectedCode), ); } } diff --git a/src/Framework/Constraint/Exception/ExceptionMessage.php b/src/Framework/Constraint/Exception/ExceptionMessage.php deleted file mode 100644 index 4836b150571..00000000000 --- a/src/Framework/Constraint/Exception/ExceptionMessage.php +++ /dev/null @@ -1,75 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use function sprintf; -use function strpos; -use Throwable; - -final class ExceptionMessage extends Constraint -{ - /** - * @var string - */ - private $expectedMessage; - - public function __construct(string $expected) - { - $this->expectedMessage = $expected; - } - - public function toString(): string - { - if ($this->expectedMessage === '') { - return 'exception message is empty'; - } - - return 'exception message contains '; - } - - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param Throwable $other - */ - protected function matches($other): bool - { - if ($this->expectedMessage === '') { - return $other->getMessage() === ''; - } - - return strpos((string) $other->getMessage(), $this->expectedMessage) !== false; - } - - /** - * Returns the description of the failure. - * - * The beginning of failure messages is "Failed asserting that" in most - * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - */ - protected function failureDescription($other): string - { - if ($this->expectedMessage === '') { - return sprintf( - "exception message is empty but is '%s'", - $other->getMessage() - ); - } - - return sprintf( - "exception message '%s' contains '%s'", - $other->getMessage(), - $this->expectedMessage - ); - } -} diff --git a/src/Framework/Constraint/Exception/ExceptionMessageIsOrContains.php b/src/Framework/Constraint/Exception/ExceptionMessageIsOrContains.php new file mode 100644 index 00000000000..ae92090396c --- /dev/null +++ b/src/Framework/Constraint/Exception/ExceptionMessageIsOrContains.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function sprintf; +use function str_contains; +use PHPUnit\Util\Exporter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ExceptionMessageIsOrContains extends Constraint +{ + private readonly string $expectedMessage; + + public function __construct(string $expectedMessage) + { + $this->expectedMessage = $expectedMessage; + } + + public function toString(): string + { + if ($this->expectedMessage === '') { + return 'exception message is empty'; + } + + return 'exception message contains ' . Exporter::export($this->expectedMessage); + } + + protected function matches(mixed $other): bool + { + if ($this->expectedMessage === '') { + return $other === ''; + } + + return str_contains((string) $other, $this->expectedMessage); + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + */ + protected function failureDescription(mixed $other): string + { + if ($this->expectedMessage === '') { + return sprintf( + "exception message is empty but is '%s'", + $other, + ); + } + + return sprintf( + "exception message '%s' contains '%s'", + $other, + $this->expectedMessage, + ); + } +} diff --git a/src/Framework/Constraint/Exception/ExceptionMessageMatchesRegularExpression.php b/src/Framework/Constraint/Exception/ExceptionMessageMatchesRegularExpression.php new file mode 100644 index 00000000000..611af03746f --- /dev/null +++ b/src/Framework/Constraint/Exception/ExceptionMessageMatchesRegularExpression.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function preg_match; +use function sprintf; +use Exception; +use PHPUnit\Util\Exporter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ExceptionMessageMatchesRegularExpression extends Constraint +{ + private readonly string $regularExpression; + + public function __construct(string $regularExpression) + { + $this->regularExpression = $regularExpression; + } + + public function toString(): string + { + return 'exception message matches ' . Exporter::export($this->regularExpression); + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @throws \PHPUnit\Framework\Exception + * @throws Exception + */ + protected function matches(mixed $other): bool + { + $match = @preg_match($this->regularExpression, (string) $other); + + if ($match === false) { + throw new \PHPUnit\Framework\Exception( + sprintf( + 'Invalid expected exception message regular expression given: %s', + $this->regularExpression, + ), + ); + } + + return $match === 1; + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + */ + protected function failureDescription(mixed $other): string + { + return sprintf( + "exception message '%s' matches '%s'", + $other, + $this->regularExpression, + ); + } +} diff --git a/src/Framework/Constraint/Exception/ExceptionMessageRegularExpression.php b/src/Framework/Constraint/Exception/ExceptionMessageRegularExpression.php deleted file mode 100644 index 393f2f8802a..00000000000 --- a/src/Framework/Constraint/Exception/ExceptionMessageRegularExpression.php +++ /dev/null @@ -1,71 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use function sprintf; -use Exception; -use PHPUnit\Util\RegularExpression as RegularExpressionUtil; - -final class ExceptionMessageRegularExpression extends Constraint -{ - /** - * @var string - */ - private $expectedMessageRegExp; - - public function __construct(string $expected) - { - $this->expectedMessageRegExp = $expected; - } - - public function toString(): string - { - return 'exception message matches '; - } - - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param \PHPUnit\Framework\Exception $other - * - * @throws Exception - * @throws \PHPUnit\Framework\Exception - */ - protected function matches($other): bool - { - $match = RegularExpressionUtil::safeMatch($this->expectedMessageRegExp, $other->getMessage()); - - if ($match === false) { - throw new \PHPUnit\Framework\Exception( - "Invalid expected exception message regex given: '{$this->expectedMessageRegExp}'" - ); - } - - return $match === 1; - } - - /** - * Returns the description of the failure. - * - * The beginning of failure messages is "Failed asserting that" in most - * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - */ - protected function failureDescription($other): string - { - return sprintf( - "exception message '%s' matches '%s'", - $other->getMessage(), - $this->expectedMessageRegExp - ); - } -} diff --git a/src/Framework/Constraint/Filesystem/DirectoryExists.php b/src/Framework/Constraint/Filesystem/DirectoryExists.php index ecdad816ff1..83b991e1aee 100644 --- a/src/Framework/Constraint/Filesystem/DirectoryExists.php +++ b/src/Framework/Constraint/Filesystem/DirectoryExists.php @@ -13,9 +13,7 @@ use function sprintf; /** - * Constraint that checks if the directory(name) that it is evaluated for exists. - * - * The file path to check is passed as $other in evaluate(). + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class DirectoryExists extends Constraint { @@ -30,10 +28,8 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return is_dir($other); } @@ -43,14 +39,12 @@ protected function matches($other): bool * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return sprintf( 'directory "%s" exists', - $other + $other, ); } } diff --git a/src/Framework/Constraint/Filesystem/FileExists.php b/src/Framework/Constraint/Filesystem/FileExists.php index 8637359a527..cfc3b1b6f22 100644 --- a/src/Framework/Constraint/Filesystem/FileExists.php +++ b/src/Framework/Constraint/Filesystem/FileExists.php @@ -13,9 +13,7 @@ use function sprintf; /** - * Constraint that checks if the file(name) that it is evaluated for exists. - * - * The file path to check is passed as $other in evaluate(). + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class FileExists extends Constraint { @@ -30,10 +28,8 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return file_exists($other); } @@ -43,14 +39,12 @@ protected function matches($other): bool * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return sprintf( 'file "%s" exists', - $other + $other, ); } } diff --git a/src/Framework/Constraint/Filesystem/IsReadable.php b/src/Framework/Constraint/Filesystem/IsReadable.php index bcf8274e845..1a32546ce7a 100644 --- a/src/Framework/Constraint/Filesystem/IsReadable.php +++ b/src/Framework/Constraint/Filesystem/IsReadable.php @@ -13,9 +13,7 @@ use function sprintf; /** - * Constraint that checks if the file/dir(name) that it is evaluated for is readable. - * - * The file path to check is passed as $other in evaluate(). + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class IsReadable extends Constraint { @@ -30,10 +28,8 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return is_readable($other); } @@ -43,14 +39,12 @@ protected function matches($other): bool * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return sprintf( '"%s" is readable', - $other + $other, ); } } diff --git a/src/Framework/Constraint/Filesystem/IsWritable.php b/src/Framework/Constraint/Filesystem/IsWritable.php index 8dd86b562ba..24e94f821df 100644 --- a/src/Framework/Constraint/Filesystem/IsWritable.php +++ b/src/Framework/Constraint/Filesystem/IsWritable.php @@ -13,9 +13,7 @@ use function sprintf; /** - * Constraint that checks if the file/dir(name) that it is evaluated for is writable. - * - * The file path to check is passed as $other in evaluate(). + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class IsWritable extends Constraint { @@ -30,10 +28,8 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return is_writable($other); } @@ -43,14 +39,12 @@ protected function matches($other): bool * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return sprintf( '"%s" is writable', - $other + $other, ); } } diff --git a/src/Framework/Constraint/IsAnything.php b/src/Framework/Constraint/IsAnything.php index fb9e20a397a..d65844fc586 100644 --- a/src/Framework/Constraint/IsAnything.php +++ b/src/Framework/Constraint/IsAnything.php @@ -9,10 +9,8 @@ */ namespace PHPUnit\Framework\Constraint; -use PHPUnit\Framework\ExpectationFailedException; - /** - * Constraint that accepts any input value. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class IsAnything extends Constraint { @@ -26,9 +24,9 @@ final class IsAnything extends Constraint * a boolean value instead: true in case of success, false in case of a * failure. * - * @throws ExpectationFailedException + * @throws void */ - public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + public function evaluate(mixed $other, string $description = '', bool $returnResult = false): ?bool { return $returnResult ? true : null; } diff --git a/src/Framework/Constraint/IsIdentical.php b/src/Framework/Constraint/IsIdentical.php index 4da34837bf9..03ba16f7777 100644 --- a/src/Framework/Constraint/IsIdentical.php +++ b/src/Framework/Constraint/IsIdentical.php @@ -9,42 +9,25 @@ */ namespace PHPUnit\Framework\Constraint; -use function abs; -use function get_class; +use function explode; +use function gettype; use function is_array; -use function is_float; -use function is_infinite; -use function is_nan; use function is_object; use function is_string; use function sprintf; use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Util\Exporter; use SebastianBergmann\Comparator\ComparisonFailure; +use UnitEnum; /** - * Constraint that asserts that one value is identical to another. - * - * Identical check is performed with PHP's === operator, the operator is - * explained in detail at - * {@url https://php.net/manual/en/types.comparisons.php}. - * Two values are identical if they have the same value and are of the same - * type. - * - * The expected value is passed in the constructor. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class IsIdentical extends Constraint { - /** - * @var float - */ - private const EPSILON = 0.0000000001; - - /** - * @var mixed - */ - private $value; + private readonly mixed $value; - public function __construct($value) + public function __construct(mixed $value) { $this->value = $value; } @@ -60,17 +43,10 @@ public function __construct($value) * failure. * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + public function evaluate(mixed $other, string $description = '', bool $returnResult = false): ?bool { - if (is_float($this->value) && is_float($other) && - !is_infinite($this->value) && !is_infinite($other) && - !is_nan($this->value) && !is_nan($other)) { - $success = abs($this->value - $other) < self::EPSILON; - } else { - $success = $this->value === $other; - } + $success = $this->value === $other; if ($returnResult) { return $success; @@ -85,17 +61,17 @@ public function evaluate($other, string $description = '', bool $returnResult = $this->value, $other, sprintf("'%s'", $this->value), - sprintf("'%s'", $other) + sprintf("'%s'", $other), ); } - // if both values are array, make sure a diff is generated - if (is_array($this->value) && is_array($other)) { + // if both values are array or enums, make sure a diff is generated + if ((is_array($this->value) && is_array($other)) || ($this->value instanceof UnitEnum && $other instanceof UnitEnum)) { $f = new ComparisonFailure( $this->value, $other, - $this->exporter()->export($this->value), - $this->exporter()->export($other) + Exporter::export($this->value), + Exporter::export($other), ); } @@ -107,17 +83,15 @@ public function evaluate($other, string $description = '', bool $returnResult = /** * Returns a string representation of the constraint. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ public function toString(): string { if (is_object($this->value)) { return 'is identical to an object of class "' . - get_class($this->value) . '"'; + $this->value::class . '"'; } - return 'is identical to ' . $this->exporter()->export($this->value); + return 'is identical to ' . Exporter::export($this->value); } /** @@ -125,17 +99,17 @@ public function toString(): string * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { if (is_object($this->value) && is_object($other)) { return 'two variables reference the same object'; } + if (explode(' ', gettype($this->value), 2)[0] === 'resource' && explode(' ', gettype($other), 2)[0] === 'resource') { + return 'two variables reference the same resource'; + } + if (is_string($this->value) && is_string($other)) { return 'two strings are identical'; } diff --git a/src/Framework/Constraint/JsonMatches.php b/src/Framework/Constraint/JsonMatches.php index 1e6168159dc..3fd6736a43c 100644 --- a/src/Framework/Constraint/JsonMatches.php +++ b/src/Framework/Constraint/JsonMatches.php @@ -12,18 +12,16 @@ use function json_decode; use function sprintf; use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Util\InvalidJsonException; use PHPUnit\Util\Json; use SebastianBergmann\Comparator\ComparisonFailure; /** - * Asserts whether or not two JSON objects are equal. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class JsonMatches extends Constraint { - /** - * @var string - */ - private $value; + private readonly string $value; public function __construct(string $value) { @@ -37,7 +35,7 @@ public function toString(): string { return sprintf( 'matches JSON string "%s"', - $this->value + $this->value, ); } @@ -46,10 +44,8 @@ public function toString(): string * constraint is met, false otherwise. * * This method can be overridden to implement the evaluation algorithm. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { [$error, $recodedOther] = Json::canonicalize($other); @@ -63,23 +59,16 @@ protected function matches($other): bool return false; } - return $recodedOther == $recodedValue; + return $recodedOther === $recodedValue; } /** * Throws an exception for the given compared value and test description. * - * @param mixed $other evaluated value or object - * @param string $description Additional information about the test - * @param ComparisonFailure $comparisonFailure - * * @throws ExpectationFailedException - * @throws \PHPUnit\Framework\Exception - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * - * @psalm-return never-return + * @throws InvalidJsonException */ - protected function fail($other, $description, ComparisonFailure $comparisonFailure = null): void + protected function fail(mixed $other, string $description, ?ComparisonFailure $comparisonFailure = null): never { if ($comparisonFailure === null) { [$error, $recodedOther] = Json::canonicalize($other); @@ -99,8 +88,7 @@ protected function fail($other, $description, ComparisonFailure $comparisonFailu json_decode($other), Json::prettify($recodedValue), Json::prettify($recodedOther), - false, - 'Failed asserting that two json values are equal.' + 'Failed asserting that two json values are equal.', ); } diff --git a/src/Framework/Constraint/JsonMatchesErrorMessageProvider.php b/src/Framework/Constraint/JsonMatchesErrorMessageProvider.php deleted file mode 100644 index 2d28c27618e..00000000000 --- a/src/Framework/Constraint/JsonMatchesErrorMessageProvider.php +++ /dev/null @@ -1,72 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use const JSON_ERROR_CTRL_CHAR; -use const JSON_ERROR_DEPTH; -use const JSON_ERROR_NONE; -use const JSON_ERROR_STATE_MISMATCH; -use const JSON_ERROR_SYNTAX; -use const JSON_ERROR_UTF8; -use function strtolower; - -/** - * Provides human readable messages for each JSON error. - */ -final class JsonMatchesErrorMessageProvider -{ - /** - * Translates JSON error to a human readable string. - */ - public static function determineJsonError(string $error, string $prefix = ''): ?string - { - switch ($error) { - case JSON_ERROR_NONE: - return null; - case JSON_ERROR_DEPTH: - return $prefix . 'Maximum stack depth exceeded'; - case JSON_ERROR_STATE_MISMATCH: - return $prefix . 'Underflow or the modes mismatch'; - case JSON_ERROR_CTRL_CHAR: - return $prefix . 'Unexpected control character found'; - case JSON_ERROR_SYNTAX: - return $prefix . 'Syntax error, malformed JSON'; - case JSON_ERROR_UTF8: - return $prefix . 'Malformed UTF-8 characters, possibly incorrectly encoded'; - - default: - return $prefix . 'Unknown error'; - } - } - - /** - * Translates a given type to a human readable message prefix. - */ - public static function translateTypeToPrefix(string $type): string - { - switch (strtolower($type)) { - case 'expected': - $prefix = 'Expected value JSON decode error - '; - - break; - case 'actual': - $prefix = 'Actual value JSON decode error - '; - - break; - - default: - $prefix = ''; - - break; - } - - return $prefix; - } -} diff --git a/src/Framework/Constraint/Math/IsFinite.php b/src/Framework/Constraint/Math/IsFinite.php index ed727d59787..b70de503d31 100644 --- a/src/Framework/Constraint/Math/IsFinite.php +++ b/src/Framework/Constraint/Math/IsFinite.php @@ -12,7 +12,7 @@ use function is_finite; /** - * Constraint that accepts finite. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class IsFinite extends Constraint { @@ -27,10 +27,8 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return is_finite($other); } diff --git a/src/Framework/Constraint/Math/IsInfinite.php b/src/Framework/Constraint/Math/IsInfinite.php index 0ada7f1bdfb..dbf4803bc57 100644 --- a/src/Framework/Constraint/Math/IsInfinite.php +++ b/src/Framework/Constraint/Math/IsInfinite.php @@ -12,7 +12,7 @@ use function is_infinite; /** - * Constraint that accepts infinite. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class IsInfinite extends Constraint { @@ -27,10 +27,8 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return is_infinite($other); } diff --git a/src/Framework/Constraint/Math/IsNan.php b/src/Framework/Constraint/Math/IsNan.php index 9dde4c6f402..f9c47219e60 100644 --- a/src/Framework/Constraint/Math/IsNan.php +++ b/src/Framework/Constraint/Math/IsNan.php @@ -12,7 +12,7 @@ use function is_nan; /** - * Constraint that accepts nan. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class IsNan extends Constraint { @@ -27,10 +27,8 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return is_nan($other); } diff --git a/src/Framework/Constraint/Object/ClassHasAttribute.php b/src/Framework/Constraint/Object/ClassHasAttribute.php deleted file mode 100644 index c42964cf56a..00000000000 --- a/src/Framework/Constraint/Object/ClassHasAttribute.php +++ /dev/null @@ -1,91 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use function get_class; -use function is_object; -use function sprintf; -use PHPUnit\Framework\Exception; -use ReflectionClass; -use ReflectionException; - -/** - * Constraint that asserts that the class it is evaluated for has a given - * attribute. - * - * The attribute name is passed in the constructor. - */ -class ClassHasAttribute extends Constraint -{ - /** - * @var string - */ - private $attributeName; - - public function __construct(string $attributeName) - { - $this->attributeName = $attributeName; - } - - /** - * Returns a string representation of the constraint. - */ - public function toString(): string - { - return sprintf( - 'has attribute "%s"', - $this->attributeName - ); - } - - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate - */ - protected function matches($other): bool - { - try { - return (new ReflectionClass($other))->hasProperty($this->attributeName); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - } - - /** - * Returns the description of the failure. - * - * The beginning of failure messages is "Failed asserting that" in most - * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - */ - protected function failureDescription($other): string - { - return sprintf( - '%sclass "%s" %s', - is_object($other) ? 'object of ' : '', - is_object($other) ? get_class($other) : $other, - $this->toString() - ); - } - - protected function attributeName(): string - { - return $this->attributeName; - } -} diff --git a/src/Framework/Constraint/Object/ClassHasStaticAttribute.php b/src/Framework/Constraint/Object/ClassHasStaticAttribute.php deleted file mode 100644 index 16e2917f6e6..00000000000 --- a/src/Framework/Constraint/Object/ClassHasStaticAttribute.php +++ /dev/null @@ -1,62 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use function sprintf; -use PHPUnit\Framework\Exception; -use ReflectionClass; -use ReflectionException; - -/** - * Constraint that asserts that the class it is evaluated for has a given - * static attribute. - * - * The attribute name is passed in the constructor. - */ -final class ClassHasStaticAttribute extends ClassHasAttribute -{ - /** - * Returns a string representation of the constraint. - */ - public function toString(): string - { - return sprintf( - 'has static attribute "%s"', - $this->attributeName() - ); - } - - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate - */ - protected function matches($other): bool - { - try { - $class = new ReflectionClass($other); - - if ($class->hasProperty($this->attributeName())) { - return $class->getProperty($this->attributeName())->isStatic(); - } - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - return false; - } -} diff --git a/src/Framework/Constraint/Object/ObjectEquals.php b/src/Framework/Constraint/Object/ObjectEquals.php new file mode 100644 index 00000000000..2c78ea42759 --- /dev/null +++ b/src/Framework/Constraint/Object/ObjectEquals.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function assert; +use function count; +use function is_object; +use PHPUnit\Framework\ActualValueIsNotAnObjectException; +use PHPUnit\Framework\ComparisonMethodDoesNotAcceptParameterTypeException; +use PHPUnit\Framework\ComparisonMethodDoesNotDeclareBoolReturnTypeException; +use PHPUnit\Framework\ComparisonMethodDoesNotDeclareExactlyOneParameterException; +use PHPUnit\Framework\ComparisonMethodDoesNotDeclareParameterTypeException; +use PHPUnit\Framework\ComparisonMethodDoesNotExistException; +use ReflectionNamedType; +use ReflectionObject; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class ObjectEquals extends Constraint +{ + private readonly object $expected; + private readonly string $method; + + public function __construct(object $object, string $method = 'equals') + { + $this->expected = $object; + $this->method = $method; + } + + public function toString(): string + { + return 'two objects are equal'; + } + + /** + * @throws ActualValueIsNotAnObjectException + * @throws ComparisonMethodDoesNotAcceptParameterTypeException + * @throws ComparisonMethodDoesNotDeclareBoolReturnTypeException + * @throws ComparisonMethodDoesNotDeclareExactlyOneParameterException + * @throws ComparisonMethodDoesNotDeclareParameterTypeException + * @throws ComparisonMethodDoesNotExistException + */ + protected function matches(mixed $other): bool + { + if (!is_object($other)) { + throw new ActualValueIsNotAnObjectException; + } + + $object = new ReflectionObject($other); + + if (!$object->hasMethod($this->method)) { + throw new ComparisonMethodDoesNotExistException( + $other::class, + $this->method, + ); + } + + $method = $object->getMethod($this->method); + + if (!$method->hasReturnType()) { + throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException( + $other::class, + $this->method, + ); + } + + $returnType = $method->getReturnType(); + + if (!$returnType instanceof ReflectionNamedType) { + throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException( + $other::class, + $this->method, + ); + } + + if ($returnType->allowsNull()) { + throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException( + $other::class, + $this->method, + ); + } + + if ($returnType->getName() !== 'bool') { + throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException( + $other::class, + $this->method, + ); + } + + if ($method->getNumberOfParameters() !== 1 || $method->getNumberOfRequiredParameters() !== 1) { + throw new ComparisonMethodDoesNotDeclareExactlyOneParameterException( + $other::class, + $this->method, + ); + } + + assert(count($method->getParameters()) > 0); + $parameter = $method->getParameters()[0]; + + if (!$parameter->hasType()) { + throw new ComparisonMethodDoesNotDeclareParameterTypeException( + $other::class, + $this->method, + ); + } + + $type = $parameter->getType(); + + if (!$type instanceof ReflectionNamedType) { + throw new ComparisonMethodDoesNotDeclareParameterTypeException( + $other::class, + $this->method, + ); + } + + $typeName = $type->getName(); + + if ($typeName === 'self') { + $typeName = $other::class; + } + + if (!$this->expected instanceof $typeName) { + throw new ComparisonMethodDoesNotAcceptParameterTypeException( + $other::class, + $this->method, + $this->expected::class, + ); + } + + /** @phpstan-ignore method.dynamicName */ + return $other->{$this->method}($this->expected); + } + + protected function failureDescription(mixed $other): string + { + return $this->toString(); + } +} diff --git a/src/Framework/Constraint/Object/ObjectHasAttribute.php b/src/Framework/Constraint/Object/ObjectHasAttribute.php deleted file mode 100644 index 8543c220ff6..00000000000 --- a/src/Framework/Constraint/Object/ObjectHasAttribute.php +++ /dev/null @@ -1,32 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use ReflectionObject; - -/** - * Constraint that asserts that the object it is evaluated for has a given - * attribute. - * - * The attribute name is passed in the constructor. - */ -final class ObjectHasAttribute extends ClassHasAttribute -{ - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate - */ - protected function matches($other): bool - { - return (new ReflectionObject($other))->hasProperty($this->attributeName()); - } -} diff --git a/src/Framework/Constraint/Object/ObjectHasProperty.php b/src/Framework/Constraint/Object/ObjectHasProperty.php new file mode 100644 index 00000000000..39e4680d9f3 --- /dev/null +++ b/src/Framework/Constraint/Object/ObjectHasProperty.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function gettype; +use function is_object; +use function sprintf; +use ReflectionObject; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class ObjectHasProperty extends Constraint +{ + private readonly string $propertyName; + + public function __construct(string $propertyName) + { + $this->propertyName = $propertyName; + } + + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return sprintf( + 'has property "%s"', + $this->propertyName, + ); + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches(mixed $other): bool + { + if (!is_object($other)) { + return false; + } + + return new ReflectionObject($other)->hasProperty($this->propertyName); + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + */ + protected function failureDescription(mixed $other): string + { + if (is_object($other)) { + return sprintf( + 'object of class "%s" %s', + $other::class, + $this->toString(), + ); + } + + return sprintf( + '"%s" (%s) %s', + $other, + gettype($other), + $this->toString(), + ); + } +} diff --git a/src/Framework/Constraint/Operator/BinaryOperator.php b/src/Framework/Constraint/Operator/BinaryOperator.php index 0e2d8139a3b..feb6d040899 100644 --- a/src/Framework/Constraint/Operator/BinaryOperator.php +++ b/src/Framework/Constraint/Operator/BinaryOperator.php @@ -10,49 +10,24 @@ namespace PHPUnit\Framework\Constraint; use function array_map; -use function array_values; use function count; /** - * Abstract base class for binary operators. - * - * Binary operator, as formally defined, accepts two operands. A BinaryOperator - * object, however, accepts arbitrary number of arguments for backward - * compatibility. The object can actually be thought to be an expression - * with zero or more repetitions of a given binary operator. The expected - * behavior for typical implementation of a BinaryOperator is the following: - * - * - when created with no arguments, it shall evaluate to false unconditionally, - * - when created with one argument, it is a degenerate operator, which just - * returns the result of its single operand constraint, - * - with two arguments, it shall follow its classical definition, - * - with more than two arguments, it shall resemble repeated application of - * the same operator, for example $1 or $2 or $3. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ abstract class BinaryOperator extends Operator { /** - * @var Constraint[] + * @var list */ - private $constraints = []; - - public static function fromConstraints(Constraint ...$constraints): self - { - $constraint = new static; - - $constraint->constraints = $constraints; + private readonly array $constraints; - return $constraint; - } - - /** - * @param mixed[] $constraints - */ - public function setConstraints(array $constraints): void + protected function __construct(mixed ...$constraints) { - $this->constraints = array_map(function ($constraint): Constraint { - return $this->checkConstraint($constraint); - }, array_values($constraints)); + $this->constraints = array_map( + fn (mixed $constraint): Constraint => $this->checkConstraint($constraint), + $constraints, + ); } /** @@ -100,7 +75,7 @@ public function count(): int } /** - * Returns the nested constraints. + * @return list */ final protected function constraints(): array { @@ -124,7 +99,7 @@ final protected function constraintNeedsParentheses(Constraint $constraint): boo */ protected function reduce(): Constraint { - if ($this->arity() === 1 && $this->constraints[0] instanceof Operator) { + if (count($this->constraints) === 1 && $this->constraints[0] instanceof Operator) { return $this->constraints[0]->reduce(); } @@ -133,9 +108,6 @@ protected function reduce(): Constraint /** * Returns string representation of given operand in context of this operator. - * - * @param Constraint $constraint operand constraint - * @param int $position position of $constraint in this expression */ private function constraintToString(Constraint $constraint, int $position): string { diff --git a/src/Framework/Constraint/Operator/LogicalAnd.php b/src/Framework/Constraint/Operator/LogicalAnd.php index ebfe2fabb27..e0a00a0ea4c 100644 --- a/src/Framework/Constraint/Operator/LogicalAnd.php +++ b/src/Framework/Constraint/Operator/LogicalAnd.php @@ -9,8 +9,16 @@ */ namespace PHPUnit\Framework\Constraint; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ final class LogicalAnd extends BinaryOperator { + public static function fromConstraints(mixed ...$constraints): self + { + return new self(...$constraints); + } + /** * Returns the name of this operator. */ @@ -32,10 +40,8 @@ public function precedence(): int /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { foreach ($this->constraints() as $constraint) { if (!$constraint->evaluate($other, '', true)) { diff --git a/src/Framework/Constraint/Operator/LogicalNot.php b/src/Framework/Constraint/Operator/LogicalNot.php index 8b834a52460..224260909f2 100644 --- a/src/Framework/Constraint/Operator/LogicalNot.php +++ b/src/Framework/Constraint/Operator/LogicalNot.php @@ -14,7 +14,11 @@ use function preg_match; use function preg_quote; use function preg_replace; +use PHPUnit\Framework\ExpectationFailedException; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ final class LogicalNot extends UnaryOperator { public static function negate(string $string): string @@ -47,11 +51,16 @@ public static function negate(string $string): string preg_match('/(\'[\w\W]*\')([\w\W]*)("[\w\W]*")/i', $string, $matches); - $positives = array_map(function (string $s) { - return '/\\b' . preg_quote($s, '/') . '/'; - }, $positives); + if (count($matches) === 0) { + preg_match('/(\'[\w\W]*\')([\w\W]*)(\'[\w\W]*\')/i', $string, $matches); + } + + $positives = array_map( + static fn (string $s) => '/\\b' . preg_quote($s, '/') . '/', + $positives, + ); - if (count($matches) > 0) { + if (count($matches) >= 3) { $nonInput = $matches[2]; $negatedString = preg_replace( @@ -59,15 +68,15 @@ public static function negate(string $string): string preg_replace( $positives, $negatives, - $nonInput + $nonInput, ), - $string + $string, ); } else { $negatedString = preg_replace( $positives, $negatives, - $string + $string, ); } @@ -96,9 +105,9 @@ public function precedence(): int * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * - * @param mixed $other value or object to evaluate + * @throws ExpectationFailedException */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return !$this->constraint()->evaluate($other, '', true); } diff --git a/src/Framework/Constraint/Operator/LogicalOr.php b/src/Framework/Constraint/Operator/LogicalOr.php index 4cfd41732eb..370aa2cab78 100644 --- a/src/Framework/Constraint/Operator/LogicalOr.php +++ b/src/Framework/Constraint/Operator/LogicalOr.php @@ -9,8 +9,18 @@ */ namespace PHPUnit\Framework\Constraint; +use function array_any; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ final class LogicalOr extends BinaryOperator { + public static function fromConstraints(mixed ...$constraints): self + { + return new self(...$constraints); + } + /** * Returns the name of this operator. */ @@ -32,17 +42,12 @@ public function precedence(): int /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - public function matches($other): bool + public function matches(mixed $other): bool { - foreach ($this->constraints() as $constraint) { - if ($constraint->evaluate($other, '', true)) { - return true; - } - } - - return false; + return array_any( + $this->constraints(), + static fn (Constraint $constraint) => $constraint->evaluate($other, '', true), + ); } } diff --git a/src/Framework/Constraint/Operator/LogicalXor.php b/src/Framework/Constraint/Operator/LogicalXor.php index aba0d7f21de..3b40a126190 100644 --- a/src/Framework/Constraint/Operator/LogicalXor.php +++ b/src/Framework/Constraint/Operator/LogicalXor.php @@ -11,9 +11,18 @@ use function array_reduce; use function array_shift; +use PHPUnit\Framework\ExpectationFailedException; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ final class LogicalXor extends BinaryOperator { + public static function fromConstraints(mixed ...$constraints): self + { + return new self(...$constraints); + } + /** * Returns the name of this operator. */ @@ -36,9 +45,9 @@ public function precedence(): int * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * - * @param mixed $other value or object to evaluate + * @throws ExpectationFailedException */ - public function matches($other): bool + public function matches(mixed $other): bool { $constraints = $this->constraints(); @@ -50,10 +59,8 @@ public function matches($other): bool return array_reduce( $constraints, - static function (bool $matches, Constraint $constraint) use ($other): bool { - return $matches xor $constraint->evaluate($other, '', true); - }, - $initial->evaluate($other, '', true) + static fn (?bool $matches, Constraint $constraint): bool => $matches xor $constraint->evaluate($other, '', true), + $initial->evaluate($other, '', true), ); } } diff --git a/src/Framework/Constraint/Operator/Operator.php b/src/Framework/Constraint/Operator/Operator.php index 4555f1c09d0..1195156e036 100644 --- a/src/Framework/Constraint/Operator/Operator.php +++ b/src/Framework/Constraint/Operator/Operator.php @@ -9,6 +9,9 @@ */ namespace PHPUnit\Framework\Constraint; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ abstract class Operator extends Constraint { /** @@ -31,7 +34,7 @@ abstract public function arity(): int; /** * Validates $constraint argument. */ - protected function checkConstraint($constraint): Constraint + protected function checkConstraint(mixed $constraint): Constraint { if (!$constraint instanceof Constraint) { return new IsEqual($constraint); diff --git a/src/Framework/Constraint/Operator/UnaryOperator.php b/src/Framework/Constraint/Operator/UnaryOperator.php index 56a1abcff9b..d6ac6e3f6c8 100644 --- a/src/Framework/Constraint/Operator/UnaryOperator.php +++ b/src/Framework/Constraint/Operator/UnaryOperator.php @@ -11,17 +11,14 @@ use function count; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ abstract class UnaryOperator extends Operator { - /** - * @var Constraint - */ - private $constraint; + private readonly Constraint $constraint; - /** - * @param Constraint|mixed $constraint - */ - public function __construct($constraint) + public function __construct(mixed $constraint) { $this->constraint = $this->checkConstraint($constraint); } @@ -73,12 +70,8 @@ public function count(): int * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { $reduced = $this->reduce(); @@ -102,15 +95,13 @@ protected function failureDescription($other): string } /** - * Transforms string returned by the memeber constraint's toString() or + * Transforms string returned by the member constraint's toString() or * failureDescription() such that it reflects constraint's participation in * this expression. * * The method may be overwritten in a subclass to apply default * transformation in case the operand constraint does not provide its own * custom strings via toStringInContext() or failureDescriptionInContext(). - * - * @param string $string the string to be transformed */ protected function transformString(string $string): string { diff --git a/src/Framework/Constraint/String/IsJson.php b/src/Framework/Constraint/String/IsJson.php index b2d0474e14e..582b58e8bdc 100644 --- a/src/Framework/Constraint/String/IsJson.php +++ b/src/Framework/Constraint/String/IsJson.php @@ -9,12 +9,19 @@ */ namespace PHPUnit\Framework\Constraint; +use const JSON_ERROR_CTRL_CHAR; +use const JSON_ERROR_DEPTH; +use const JSON_ERROR_NONE; +use const JSON_ERROR_STATE_MISMATCH; +use const JSON_ERROR_SYNTAX; +use const JSON_ERROR_UTF8; +use function is_string; use function json_decode; use function json_last_error; use function sprintf; /** - * Constraint that asserts that a string is valid JSON. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class IsJson extends Constraint { @@ -29,18 +36,16 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { - if ($other === '') { + if (!is_string($other) || $other === '') { return false; } json_decode($other); - if (json_last_error()) { + if (json_last_error() !== JSON_ERROR_NONE) { return false; } @@ -52,26 +57,35 @@ protected function matches($other): bool * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { + if (!is_string($other)) { + return $this->valueToTypeStringFragment($other) . 'is valid JSON'; + } + if ($other === '') { return 'an empty string is valid JSON'; } - json_decode($other); - $error = (string) JsonMatchesErrorMessageProvider::determineJsonError( - (string) json_last_error() - ); - return sprintf( - '%s is valid JSON (%s)', - $this->exporter()->shortenedExport($other), - $error + 'a string is valid JSON (%s)', + $this->determineJsonError($other), ); } + + private function determineJsonError(string $json): string + { + json_decode($json); + + return match (json_last_error()) { + JSON_ERROR_NONE => '', + JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', + JSON_ERROR_STATE_MISMATCH => 'Underflow or the modes mismatch', + JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', + JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON', + JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded', + default => 'Unknown error', + }; + } } diff --git a/src/Framework/Constraint/String/RegularExpression.php b/src/Framework/Constraint/String/RegularExpression.php index 963bfd05d17..03b0e4ea1a3 100644 --- a/src/Framework/Constraint/String/RegularExpression.php +++ b/src/Framework/Constraint/String/RegularExpression.php @@ -13,20 +13,11 @@ use function sprintf; /** - * Constraint that asserts that the string it is evaluated for matches - * a regular expression. - * - * Checks a given value using the Perl Compatible Regular Expression extension - * in PHP. The pattern is matched by executing preg_match(). - * - * The pattern string passed in the constructor. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ -class RegularExpression extends Constraint +final class RegularExpression extends Constraint { - /** - * @var string - */ - private $pattern; + private readonly string $pattern; public function __construct(string $pattern) { @@ -40,17 +31,15 @@ public function toString(): string { return sprintf( 'matches PCRE pattern "%s"', - $this->pattern + $this->pattern, ); } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return preg_match($this->pattern, $other) > 0; } diff --git a/src/Framework/Constraint/String/StringContains.php b/src/Framework/Constraint/String/StringContains.php index 6f36cb914cf..0f801a605fd 100644 --- a/src/Framework/Constraint/String/StringContains.php +++ b/src/Framework/Constraint/String/StringContains.php @@ -9,36 +9,34 @@ */ namespace PHPUnit\Framework\Constraint; +use function is_string; +use function mb_detect_encoding; use function mb_stripos; use function mb_strtolower; use function sprintf; -use function strpos; +use function str_contains; +use function strlen; +use function strtr; +use PHPUnit\Util\Exporter; /** - * Constraint that asserts that the string it is evaluated for contains - * a given string. - * - * Uses mb_strpos() to find the position of the string in the input, if not - * found the evaluation fails. - * - * The sub-string is passed in the constructor. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class StringContains extends Constraint { - /** - * @var string - */ - private $string; + private readonly string $needle; + private readonly bool $ignoreCase; + private readonly bool $ignoreLineEndings; - /** - * @var bool - */ - private $ignoreCase; - - public function __construct(string $string, bool $ignoreCase = false) + public function __construct(string $needle, bool $ignoreCase = false, bool $ignoreLineEndings = false) { - $this->string = $string; - $this->ignoreCase = $ignoreCase; + if ($ignoreLineEndings) { + $needle = $this->normalizeLineEndings($needle); + } + + $this->needle = $needle; + $this->ignoreCase = $ignoreCase; + $this->ignoreLineEndings = $ignoreLineEndings; } /** @@ -46,46 +44,117 @@ public function __construct(string $string, bool $ignoreCase = false) */ public function toString(): string { + $needle = $this->needle; + if ($this->ignoreCase) { - $string = mb_strtolower($this->string, 'UTF-8'); - } else { - $string = $this->string; + $needle = mb_strtolower($this->needle, 'UTF-8'); } return sprintf( - 'contains "%s"', - $string + 'contains "%s" [%s](length: %s)', + $needle, + $this->detectedEncoding($needle), + strlen($needle), + ); + } + + public function failureDescription(mixed $other): string + { + $stringifiedHaystack = Exporter::export($other); + $haystackEncoding = $this->detectedEncoding($other); + $haystackLength = $this->haystackLength($other); + + $haystackInformation = sprintf( + '%s [%s](length: %s) ', + $stringifiedHaystack, + $haystackEncoding, + $haystackLength, ); + + $needleInformation = $this->toString(); + + return $haystackInformation . $needleInformation; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { - if ('' === $this->string) { + $haystack = $other; + + if ('' === $this->needle) { return true; } + if (!is_string($haystack)) { + return false; + } + + if ($this->ignoreLineEndings) { + $haystack = $this->normalizeLineEndings($haystack); + } + if ($this->ignoreCase) { /* - * We must use the multi byte safe version so we can accurately compare non latin upper characters with + * We must use the multibyte-safe version, so we can accurately compare non-latin uppercase characters with * their lowercase equivalents. */ - return mb_stripos($other, $this->string, 0, 'UTF-8') !== false; + return mb_stripos($haystack, $this->needle, 0, 'UTF-8') !== false; } /* - * Use the non multi byte safe functions to see if the string is contained in $other. + * Use the non-multibyte safe functions to see if the string is contained in $other. * - * This function is very fast and we don't care about the character position in the string. + * This function is very fast, and we don't care about the character position in the string. * - * Additionally, we want this method to be binary safe so we can check if some binary data is in other binary + * Additionally, we want this method to be binary safe, so we can check if some binary data is in other binary * data. */ - return strpos($other, $this->string) !== false; + return str_contains($haystack, $this->needle); + } + + private function detectedEncoding(mixed $other): string + { + if ($this->ignoreCase) { + return 'Encoding ignored'; + } + + if (!is_string($other)) { + return 'Encoding detection failed'; + } + + $detectedEncoding = mb_detect_encoding($other, null, true); + + if ($detectedEncoding === false) { + return 'Encoding detection failed'; + } + + return $detectedEncoding; + } + + private function haystackLength(mixed $haystack): int + { + if (!is_string($haystack)) { + return 0; + } + + if ($this->ignoreLineEndings) { + $haystack = $this->normalizeLineEndings($haystack); + } + + return strlen($haystack); + } + + private function normalizeLineEndings(string $string): string + { + return strtr( + $string, + [ + "\r\n" => "\n", + "\r" => "\n", + ], + ); } } diff --git a/src/Framework/Constraint/String/StringEndsWith.php b/src/Framework/Constraint/String/StringEndsWith.php index ed11b01d17d..1dd43b84b74 100644 --- a/src/Framework/Constraint/String/StringEndsWith.php +++ b/src/Framework/Constraint/String/StringEndsWith.php @@ -9,22 +9,25 @@ */ namespace PHPUnit\Framework\Constraint; -use function strlen; -use function substr; +use function str_ends_with; +use PHPUnit\Framework\EmptyStringException; /** - * Constraint that asserts that the string it is evaluated for ends with a given - * suffix. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class StringEndsWith extends Constraint { + private readonly string $suffix; + /** - * @var string + * @throws EmptyStringException */ - private $suffix; - public function __construct(string $suffix) { + if ($suffix === '') { + throw new EmptyStringException; + } + $this->suffix = $suffix; } @@ -39,11 +42,9 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { - return substr($other, 0 - strlen($this->suffix)) === $this->suffix; + return str_ends_with((string) $other, $this->suffix); } } diff --git a/src/Framework/Constraint/String/StringEqualsStringIgnoringLineEndings.php b/src/Framework/Constraint/String/StringEqualsStringIgnoringLineEndings.php new file mode 100644 index 00000000000..56c59943ef2 --- /dev/null +++ b/src/Framework/Constraint/String/StringEqualsStringIgnoringLineEndings.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function sprintf; +use function strtr; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class StringEqualsStringIgnoringLineEndings extends Constraint +{ + private readonly string $string; + + public function __construct(string $string) + { + $this->string = $this->normalizeLineEndings($string); + } + + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return sprintf( + 'is equal to "%s" ignoring line endings', + $this->string, + ); + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + */ + protected function matches(mixed $other): bool + { + return $this->string === $this->normalizeLineEndings((string) $other); + } + + private function normalizeLineEndings(string $string): string + { + return strtr( + $string, + [ + "\r\n" => "\n", + "\r" => "\n", + ], + ); + } +} diff --git a/src/Framework/Constraint/String/StringMatchesFormatDescription.php b/src/Framework/Constraint/String/StringMatchesFormatDescription.php index bc5e835070f..4df81715f82 100644 --- a/src/Framework/Constraint/String/StringMatchesFormatDescription.php +++ b/src/Framework/Constraint/String/StringMatchesFormatDescription.php @@ -10,6 +10,7 @@ namespace PHPUnit\Framework\Constraint; use const DIRECTORY_SEPARATOR; +use const PHP_EOL; use function explode; use function implode; use function preg_match; @@ -19,81 +20,104 @@ use SebastianBergmann\Diff\Differ; use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder; -final class StringMatchesFormatDescription extends RegularExpression +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class StringMatchesFormatDescription extends Constraint { - /** - * @var string - */ - private $string; + private readonly string $formatDescription; - public function __construct(string $string) + public function __construct(string $formatDescription) { - parent::__construct( - $this->createPatternFromFormat( - $this->convertNewlines($string) - ) - ); + $this->formatDescription = $formatDescription; + } - $this->string = $string; + public function toString(): string + { + return 'matches format description:' . PHP_EOL . $this->formatDescription; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { - return parent::matches( - $this->convertNewlines($other) + $other = $this->convertNewlines($other); + + $matches = preg_match( + $this->regularExpressionForFormatDescription( + $this->convertNewlines($this->formatDescription), + ), + $other, ); + + return $matches > 0; } - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return 'string matches format description'; } - protected function additionalFailureDescription($other): string + /** + * Returns a cleaned up diff. + * + * The expected string can contain placeholders like %s and %d. + * By using 'diff' such placeholders compared to the real output will + * always be different, although we don't want to show them as different. + * This method removes the expected differences by figuring out if a difference + * is allowed by the use of a placeholder. + * + * The problem here are %A and %a multiline placeholders since we look at the + * expected and actual output line by line. If differences allowed by those placeholders + * stretch over multiple lines they will still end up in the final diff. + * And since they mess up the line sync between the expected and actual output + * all following allowed changes will not be detected/removed anymore. + */ + protected function additionalFailureDescription(mixed $other): string { - $from = explode("\n", $this->string); + $from = explode("\n", $this->formatDescription); $to = explode("\n", $this->convertNewlines($other)); foreach ($from as $index => $line) { + // is the expected output line different from the actual output line if (isset($to[$index]) && $line !== $to[$index]) { - $line = $this->createPatternFromFormat($line); + $line = $this->regularExpressionForFormatDescription($line); + // if the difference is allowed by a placeholder + // overwrite the expected line with the actual line to prevent it from showing up in the diff if (preg_match($line, $to[$index]) > 0) { $from[$index] = $to[$index]; } } } - $this->string = implode("\n", $from); - $other = implode("\n", $to); + $from = implode("\n", $from); + $to = implode("\n", $to); - return (new Differ(new UnifiedDiffOutputBuilder("--- Expected\n+++ Actual\n")))->diff($this->string, $other); + return $this->differ()->diff($from, $to); } - private function createPatternFromFormat(string $string): string + private function regularExpressionForFormatDescription(string $string): string { $string = strtr( preg_quote($string, '/'), [ '%%' => '%', - '%e' => '\\' . DIRECTORY_SEPARATOR, + '%e' => preg_quote(DIRECTORY_SEPARATOR, '/'), '%s' => '[^\r\n]+', '%S' => '[^\r\n]*', - '%a' => '.+', - '%A' => '.*', + '%a' => '.+?', + '%A' => '.*?', '%w' => '\s*', '%i' => '[+-]?\d+', '%d' => '\d+', '%x' => '[0-9a-fA-F]+', - '%f' => '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?', + '%f' => '[+-]?(?:\d+|(?=\.\d))(?:\.\d+)?(?:[Ee][+-]?\d+)?', '%c' => '.', - ] + '%0' => '\x00', + ], ); return '/^' . $string . '$/s'; @@ -103,4 +127,9 @@ private function convertNewlines(string $text): string { return preg_replace('/\r\n/', "\n", $text); } + + private function differ(): Differ + { + return new Differ(new UnifiedDiffOutputBuilder("--- Expected\n+++ Actual\n")); + } } diff --git a/src/Framework/Constraint/String/StringStartsWith.php b/src/Framework/Constraint/String/StringStartsWith.php index a80a3f6b004..eee545c7811 100644 --- a/src/Framework/Constraint/String/StringStartsWith.php +++ b/src/Framework/Constraint/String/StringStartsWith.php @@ -9,25 +9,23 @@ */ namespace PHPUnit\Framework\Constraint; -use function strlen; -use function strpos; -use PHPUnit\Framework\InvalidArgumentException; +use function str_starts_with; +use PHPUnit\Framework\EmptyStringException; /** - * Constraint that asserts that the string it is evaluated for begins with a - * given prefix. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class StringStartsWith extends Constraint { + private readonly string $prefix; + /** - * @var string + * @throws EmptyStringException */ - private $prefix; - public function __construct(string $prefix) { - if (strlen($prefix) === 0) { - throw InvalidArgumentException::create(1, 'non-empty string'); + if ($prefix === '') { + throw new EmptyStringException; } $this->prefix = $prefix; @@ -44,11 +42,9 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { - return strpos((string) $other, $this->prefix) === 0; + return str_starts_with((string) $other, $this->prefix); } } diff --git a/src/Framework/Constraint/Traversable/ArrayHasKey.php b/src/Framework/Constraint/Traversable/ArrayHasKey.php index 0ba5be92a40..fa8d2744111 100644 --- a/src/Framework/Constraint/Traversable/ArrayHasKey.php +++ b/src/Framework/Constraint/Traversable/ArrayHasKey.php @@ -12,47 +12,33 @@ use function array_key_exists; use function is_array; use ArrayAccess; +use PHPUnit\Util\Exporter; /** - * Constraint that asserts that the array it is evaluated for has a given key. - * - * Uses array_key_exists() to check if the key is found in the input array, if - * not found the evaluation fails. - * - * The array key is passed in the constructor. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class ArrayHasKey extends Constraint { - /** - * @var int|string - */ - private $key; + private readonly mixed $key; - /** - * @param int|string $key - */ - public function __construct($key) + public function __construct(mixed $key) { $this->key = $key; } /** * Returns a string representation of the constraint. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ public function toString(): string { - return 'has the key ' . $this->exporter()->export($this->key); + return 'has the key ' . Exporter::export($this->key); } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { if (is_array($other)) { return array_key_exists($this->key, $other); @@ -70,12 +56,8 @@ protected function matches($other): bool * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return 'an array ' . $this->toString(); } diff --git a/src/Framework/Constraint/Traversable/IsList.php b/src/Framework/Constraint/Traversable/IsList.php new file mode 100644 index 00000000000..f6ac30618e0 --- /dev/null +++ b/src/Framework/Constraint/Traversable/IsList.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function array_is_list; +use function is_array; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsList extends Constraint +{ + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return 'is a list'; + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + */ + protected function matches(mixed $other): bool + { + if (!is_array($other)) { + return false; + } + + return array_is_list($other); + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + */ + protected function failureDescription(mixed $other): string + { + return $this->valueToTypeStringFragment($other) . $this->toString(); + } +} diff --git a/src/Framework/Constraint/Traversable/TraversableContains.php b/src/Framework/Constraint/Traversable/TraversableContains.php index f3904ef80b3..6f1a298bf1a 100644 --- a/src/Framework/Constraint/Traversable/TraversableContains.php +++ b/src/Framework/Constraint/Traversable/TraversableContains.php @@ -11,31 +11,26 @@ use function is_array; use function sprintf; +use PHPUnit\Util\Exporter; /** - * Constraint that asserts that the Traversable it is applied to contains - * a given value. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ abstract class TraversableContains extends Constraint { - /** - * @var mixed - */ - private $value; + private readonly mixed $value; - public function __construct($value) + public function __construct(mixed $value) { $this->value = $value; } /** * Returns a string representation of the constraint. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ public function toString(): string { - return 'contains ' . $this->exporter()->export($this->value); + return 'contains ' . Exporter::export($this->value); } /** @@ -43,21 +38,17 @@ public function toString(): string * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return sprintf( '%s %s', is_array($other) ? 'an array' : 'a traversable', - $this->toString() + $this->toString(), ); } - protected function value() + protected function value(): mixed { return $this->value; } diff --git a/src/Framework/Constraint/Traversable/TraversableContainsEqual.php b/src/Framework/Constraint/Traversable/TraversableContainsEqual.php index 285571303b0..f89835163c5 100644 --- a/src/Framework/Constraint/Traversable/TraversableContainsEqual.php +++ b/src/Framework/Constraint/Traversable/TraversableContainsEqual.php @@ -12,25 +12,22 @@ use SplObjectStorage; /** - * Constraint that asserts that the Traversable it is applied to contains - * a given value (using non-strict comparison). + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class TraversableContainsEqual extends TraversableContains { /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { if ($other instanceof SplObjectStorage) { - return $other->contains($this->value()); + return $other->offsetExists($this->value()); } foreach ($other as $element) { - /* @noinspection TypeUnsafeComparisonInspection */ + /** @phpstan-ignore equal.notAllowed */ if ($this->value() == $element) { return true; } diff --git a/src/Framework/Constraint/Traversable/TraversableContainsIdentical.php b/src/Framework/Constraint/Traversable/TraversableContainsIdentical.php index 523599dc162..b4e295376bf 100644 --- a/src/Framework/Constraint/Traversable/TraversableContainsIdentical.php +++ b/src/Framework/Constraint/Traversable/TraversableContainsIdentical.php @@ -12,21 +12,18 @@ use SplObjectStorage; /** - * Constraint that asserts that the Traversable it is applied to contains - * a given value (using strict comparison). + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class TraversableContainsIdentical extends TraversableContains { /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { if ($other instanceof SplObjectStorage) { - return $other->contains($this->value()); + return $other->offsetExists($this->value()); } foreach ($other as $element) { diff --git a/src/Framework/Constraint/Traversable/TraversableContainsOnly.php b/src/Framework/Constraint/Traversable/TraversableContainsOnly.php index d987d20cf7f..50da87f298d 100644 --- a/src/Framework/Constraint/Traversable/TraversableContainsOnly.php +++ b/src/Framework/Constraint/Traversable/TraversableContainsOnly.php @@ -10,38 +10,33 @@ namespace PHPUnit\Framework\Constraint; use PHPUnit\Framework\ExpectationFailedException; -use Traversable; +use PHPUnit\Framework\NativeType; /** - * Constraint that asserts that the Traversable it is applied to contains - * only values of a given type. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class TraversableContainsOnly extends Constraint { - /** - * @var Constraint - */ - private $constraint; + private readonly Constraint $constraint; + private readonly string $type; - /** - * @var string - */ - private $type; + public static function forNativeType(NativeType $type): self + { + return new self(new IsType($type), $type->value); + } /** - * @throws \PHPUnit\Framework\Exception + * @param class-string $type */ - public function __construct(string $type, bool $isNativeType = true) + public static function forClassOrInterface(string $type): self { - if ($isNativeType) { - $this->constraint = new IsType($type); - } else { - $this->constraint = new IsInstanceOf( - $type - ); - } + return new self(new IsInstanceOf($type), $type); + } - $this->type = $type; + private function __construct(IsInstanceOf|IsType $constraint, string $type) + { + $this->constraint = $constraint; + $this->type = $type; } /** @@ -54,12 +49,9 @@ public function __construct(string $type, bool $isNativeType = true) * a boolean value instead: true in case of success, false in case of a * failure. * - * @param mixed|Traversable $other - * * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + public function evaluate(mixed $other, string $description = '', bool $returnResult = false): bool { $success = true; @@ -71,15 +63,11 @@ public function evaluate($other, string $description = '', bool $returnResult = } } - if ($returnResult) { - return $success; - } - - if (!$success) { + if (!$success && !$returnResult) { $this->fail($other, $description); } - return null; + return $success; } /** diff --git a/src/Framework/Constraint/Type/IsInstanceOf.php b/src/Framework/Constraint/Type/IsInstanceOf.php index 02631f8f469..df7ebf1e2c3 100644 --- a/src/Framework/Constraint/Type/IsInstanceOf.php +++ b/src/Framework/Constraint/Type/IsInstanceOf.php @@ -9,26 +9,40 @@ */ namespace PHPUnit\Framework\Constraint; +use function class_exists; +use function interface_exists; use function sprintf; -use ReflectionClass; -use ReflectionException; +use PHPUnit\Framework\UnknownClassOrInterfaceException; /** - * Constraint that asserts that the object it is evaluated for is an instance - * of a given class. - * - * The expected class name is passed in the constructor. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class IsInstanceOf extends Constraint { /** - * @var string + * @var class-string */ - private $className; + private readonly string $name; - public function __construct(string $className) + /** + * @var 'class'|'interface' + */ + private readonly string $type; + + /** + * @throws UnknownClassOrInterfaceException + */ + public function __construct(string $name) { - $this->className = $className; + if (class_exists($name)) { + $this->type = 'class'; + } elseif (interface_exists($name)) { + $this->type = 'interface'; + } else { + throw new UnknownClassOrInterfaceException($name); + } + + $this->name = $name; } /** @@ -37,21 +51,19 @@ public function __construct(string $className) public function toString(): string { return sprintf( - 'is instance of %s "%s"', - $this->getType(), - $this->className + 'is an instance of %s %s', + $this->type, + $this->name, ); } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { - return $other instanceof $this->className; + return $other instanceof $this->name; } /** @@ -59,32 +71,9 @@ protected function matches($other): bool * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { - return sprintf( - '%s is an instance of %s "%s"', - $this->exporter()->shortenedExport($other), - $this->getType(), - $this->className - ); - } - - private function getType(): string - { - try { - $reflection = new ReflectionClass($this->className); - - if ($reflection->isInterface()) { - return 'interface'; - } - } catch (ReflectionException $e) { - } - - return 'class'; + return $this->valueToTypeStringFragment($other) . $this->toString(); } } diff --git a/src/Framework/Constraint/Type/IsNull.php b/src/Framework/Constraint/Type/IsNull.php index 1538138b7ff..37c89f7a043 100644 --- a/src/Framework/Constraint/Type/IsNull.php +++ b/src/Framework/Constraint/Type/IsNull.php @@ -10,7 +10,7 @@ namespace PHPUnit\Framework\Constraint; /** - * Constraint that accepts null. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class IsNull extends Constraint { @@ -25,10 +25,8 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return $other === null; } diff --git a/src/Framework/Constraint/Type/IsType.php b/src/Framework/Constraint/Type/IsType.php index 59c390c0b52..c2bff8eda98 100644 --- a/src/Framework/Constraint/Type/IsType.php +++ b/src/Framework/Constraint/Type/IsType.php @@ -9,7 +9,7 @@ */ namespace PHPUnit\Framework\Constraint; -use function get_resource_type; +use function gettype; use function is_array; use function is_bool; use function is_callable; @@ -18,122 +18,20 @@ use function is_iterable; use function is_numeric; use function is_object; -use function is_resource; use function is_scalar; use function is_string; use function sprintf; -use TypeError; +use PHPUnit\Framework\NativeType; /** - * Constraint that asserts that the value it is evaluated for is of a - * specified type. - * - * The expected value is passed in the constructor. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class IsType extends Constraint { - /** - * @var string - */ - public const TYPE_ARRAY = 'array'; - - /** - * @var string - */ - public const TYPE_BOOL = 'bool'; - - /** - * @var string - */ - public const TYPE_FLOAT = 'float'; - - /** - * @var string - */ - public const TYPE_INT = 'int'; - - /** - * @var string - */ - public const TYPE_NULL = 'null'; - - /** - * @var string - */ - public const TYPE_NUMERIC = 'numeric'; - - /** - * @var string - */ - public const TYPE_OBJECT = 'object'; - - /** - * @var string - */ - public const TYPE_RESOURCE = 'resource'; + private readonly NativeType $type; - /** - * @var string - */ - public const TYPE_STRING = 'string'; - - /** - * @var string - */ - public const TYPE_SCALAR = 'scalar'; - - /** - * @var string - */ - public const TYPE_CALLABLE = 'callable'; - - /** - * @var string - */ - public const TYPE_ITERABLE = 'iterable'; - - /** - * @var array - */ - private const KNOWN_TYPES = [ - 'array' => true, - 'boolean' => true, - 'bool' => true, - 'double' => true, - 'float' => true, - 'integer' => true, - 'int' => true, - 'null' => true, - 'numeric' => true, - 'object' => true, - 'real' => true, - 'resource' => true, - 'string' => true, - 'scalar' => true, - 'callable' => true, - 'iterable' => true, - ]; - - /** - * @var string - */ - private $type; - - /** - * @throws \PHPUnit\Framework\Exception - */ - public function __construct(string $type) + public function __construct(NativeType $type) { - if (!isset(self::KNOWN_TYPES[$type])) { - throw new \PHPUnit\Framework\Exception( - sprintf( - 'Type specified for PHPUnit\Framework\Constraint\IsType <%s> ' . - 'is not a valid type.', - $type - ) - ); - } - $this->type = $type; } @@ -143,71 +41,57 @@ public function __construct(string $type) public function toString(): string { return sprintf( - 'is of type "%s"', - $this->type + 'is of type %s', + $this->type->value, ); } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { switch ($this->type) { - case 'numeric': + case NativeType::Numeric: return is_numeric($other); - case 'integer': - case 'int': + case NativeType::Int: return is_int($other); - case 'double': - case 'float': - case 'real': + case NativeType::Float: return is_float($other); - case 'string': + case NativeType::String: return is_string($other); - case 'boolean': - case 'bool': + case NativeType::Bool: return is_bool($other); - case 'null': + case NativeType::Null: return null === $other; - case 'array': + case NativeType::Array: return is_array($other); - case 'object': + case NativeType::Object: return is_object($other); - case 'resource': - if (is_resource($other)) { - return true; - } - - try { - $resource = @get_resource_type($other); + case NativeType::Resource: + $type = gettype($other); - if (is_string($resource)) { - return true; - } - } catch (TypeError $e) { - } + return $type === 'resource' || $type === 'resource (closed)'; - return false; + case NativeType::ClosedResource: + return gettype($other) === 'resource (closed)'; - case 'scalar': + case NativeType::Scalar: return is_scalar($other); - case 'callable': + case NativeType::Callable: return is_callable($other); - case 'iterable': + case NativeType::Iterable: return is_iterable($other); default: diff --git a/src/Framework/DataProviderTestSuite.php b/src/Framework/DataProviderTestSuite.php index 18b5499965e..262530b7a58 100644 --- a/src/Framework/DataProviderTestSuite.php +++ b/src/Framework/DataProviderTestSuite.php @@ -9,10 +9,16 @@ */ namespace PHPUnit\Framework; +use function assert; +use function class_exists; +use function count; use function explode; -use PHPUnit\Util\Test as TestUtil; +use PHPUnit\Framework\TestSize\TestSize; +use PHPUnit\Metadata\Api\Groups; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class DataProviderTestSuite extends TestSuite @@ -20,7 +26,12 @@ final class DataProviderTestSuite extends TestSuite /** * @var list */ - private $dependencies = []; + private array $dependencies = []; + + /** + * @var ?non-empty-list + */ + private ?array $providedTests = null; /** * @param list $dependencies @@ -29,23 +40,22 @@ public function setDependencies(array $dependencies): void { $this->dependencies = $dependencies; - foreach ($this->tests as $test) { + foreach ($this->tests() as $test) { if (!$test instanceof TestCase) { - // @codeCoverageIgnoreStart continue; - // @codeCoverageIgnoreStart } + $test->setDependencies($dependencies); } } /** - * @return list + * @return non-empty-list */ public function provides(): array { if ($this->providedTests === null) { - $this->providedTests = [new ExecutionOrderDependency($this->getName())]; + $this->providedTests = [new ExecutionOrderDependency($this->name())]; } return $this->providedTests; @@ -62,14 +72,16 @@ public function requires(): array } /** - * Returns the size of the each test created using the data provider(s). - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * Returns the size of each test created using the data provider(s). */ - public function getSize(): int + public function size(): TestSize { - [$className, $methodName] = explode('::', $this->getName()); + assert(count(explode('::', $this->name())) === 2); + [$className, $methodName] = explode('::', $this->name()); + + assert(class_exists($className)); + assert($methodName !== ''); - return TestUtil::getSize($className, $methodName); + return (new Groups)->size($className, $methodName); } } diff --git a/src/Framework/Error/Deprecated.php b/src/Framework/Error/Deprecated.php deleted file mode 100644 index db62195f8da..00000000000 --- a/src/Framework/Error/Deprecated.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Error; - -/** - * @internal - */ -final class Deprecated extends Error -{ -} diff --git a/src/Framework/Error/Error.php b/src/Framework/Error/Error.php deleted file mode 100644 index 2990b360e30..00000000000 --- a/src/Framework/Error/Error.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Error; - -use PHPUnit\Framework\Exception; - -/** - * @internal - */ -class Error extends Exception -{ - public function __construct(string $message, int $code, string $file, int $line, \Exception $previous = null) - { - parent::__construct($message, $code, $previous); - - $this->file = $file; - $this->line = $line; - } -} diff --git a/src/Framework/Error/Notice.php b/src/Framework/Error/Notice.php deleted file mode 100644 index 54e5e31ea8d..00000000000 --- a/src/Framework/Error/Notice.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Error; - -/** - * @internal - */ -final class Notice extends Error -{ -} diff --git a/src/Framework/Error/Warning.php b/src/Framework/Error/Warning.php deleted file mode 100644 index 0c0c0064f7d..00000000000 --- a/src/Framework/Error/Warning.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Error; - -/** - * @internal - */ -final class Warning extends Error -{ -} diff --git a/src/Framework/Exception/AssertionFailedError.php b/src/Framework/Exception/AssertionFailedError.php index 0ba25286f6c..6bd59c4aea1 100644 --- a/src/Framework/Exception/AssertionFailedError.php +++ b/src/Framework/Exception/AssertionFailedError.php @@ -10,6 +10,8 @@ namespace PHPUnit\Framework; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ class AssertionFailedError extends Exception implements SelfDescribing diff --git a/src/Framework/Exception/CodeCoverageException.php b/src/Framework/Exception/CodeCoverageException.php deleted file mode 100644 index 36b07231301..00000000000 --- a/src/Framework/Exception/CodeCoverageException.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -class CodeCoverageException extends Exception -{ -} diff --git a/src/Framework/Exception/CoveredCodeNotExecutedException.php b/src/Framework/Exception/CoveredCodeNotExecutedException.php deleted file mode 100644 index 78f89bc39ee..00000000000 --- a/src/Framework/Exception/CoveredCodeNotExecutedException.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class CoveredCodeNotExecutedException extends RiskyTestError -{ -} diff --git a/src/Framework/Exception/EmptyStringException.php b/src/Framework/Exception/EmptyStringException.php new file mode 100644 index 00000000000..f5980cd71ab --- /dev/null +++ b/src/Framework/Exception/EmptyStringException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class EmptyStringException extends InvalidArgumentException +{ +} diff --git a/src/Framework/Exception/ErrorLogNotWritableException.php b/src/Framework/Exception/ErrorLogNotWritableException.php new file mode 100644 index 00000000000..602a3e49d63 --- /dev/null +++ b/src/Framework/Exception/ErrorLogNotWritableException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ErrorLogNotWritableException extends Exception +{ + public function __construct() + { + parent::__construct('Could not create writable file for error_log()'); + } +} diff --git a/src/Framework/Exception/Exception.php b/src/Framework/Exception/Exception.php index 0b21e6de300..fb45f47d1de 100644 --- a/src/Framework/Exception/Exception.php +++ b/src/Framework/Exception/Exception.php @@ -11,7 +11,8 @@ use function array_keys; use function get_object_vars; -use PHPUnit\Util\Filter; +use function is_int; +use function sprintf; use RuntimeException; use Throwable; @@ -35,17 +36,31 @@ * * @see http://fabien.potencier.org/article/9/php-serialization-stack-traces-and-exceptions * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ class Exception extends RuntimeException implements \PHPUnit\Exception { /** - * @var array + * @var list */ - protected $serializableTrace; + protected array $serializableTrace; - public function __construct($message = '', $code = 0, Throwable $previous = null) + public function __construct(string $message = '', int|string $code = 0, ?Throwable $previous = null) { + /** + * @see https://github.com/sebastianbergmann/phpunit/issues/5965 + */ + if (!is_int($code)) { + $message .= sprintf( + ' (exception code: %s)', + $code, + ); + + $code = 0; + } + parent::__construct($message, $code, $previous); $this->serializableTrace = $this->getTrace(); @@ -55,24 +70,15 @@ public function __construct($message = '', $code = 0, Throwable $previous = null } } - public function __toString(): string - { - $string = TestFailure::exceptionToString($this); - - if ($trace = Filter::getFilteredStacktrace($this)) { - $string .= "\n" . $trace; - } - - return $string; - } - - public function __sleep(): array + public function __serialize(): array { - return array_keys(get_object_vars($this)); + return get_object_vars($this); } /** * Returns the serializable trace (without 'args'). + * + * @return list */ public function getSerializableTrace(): array { diff --git a/src/Framework/Exception/ExpectationFailedException.php b/src/Framework/Exception/ExpectationFailedException.php index b9a595a88c4..c46a27bebeb 100644 --- a/src/Framework/Exception/ExpectationFailedException.php +++ b/src/Framework/Exception/ExpectationFailedException.php @@ -19,16 +19,13 @@ * SebastianBergmann\Comparator\ComparisonFailure which is used to * generate diff output of the failed expectations. * - * @internal This class is not covered by the backward compatibility promise for PHPUnit + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class ExpectationFailedException extends AssertionFailedError { - /** - * @var ComparisonFailure - */ - protected $comparisonFailure; + protected ?ComparisonFailure $comparisonFailure = null; - public function __construct(string $message, ComparisonFailure $comparisonFailure = null, Exception $previous = null) + public function __construct(string $message, ?ComparisonFailure $comparisonFailure = null, ?Exception $previous = null) { $this->comparisonFailure = $comparisonFailure; diff --git a/src/Framework/Exception/GeneratorNotSupportedException.php b/src/Framework/Exception/GeneratorNotSupportedException.php new file mode 100644 index 00000000000..b3b179531a6 --- /dev/null +++ b/src/Framework/Exception/GeneratorNotSupportedException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class GeneratorNotSupportedException extends InvalidArgumentException +{ + public static function fromParameterName(string $parameterName): self + { + return new self( + sprintf( + 'Passing an argument of type Generator for the %s parameter is not supported', + $parameterName, + ), + ); + } +} diff --git a/src/Framework/Exception/Incomplete/IncompleteTest.php b/src/Framework/Exception/Incomplete/IncompleteTest.php new file mode 100644 index 00000000000..4492ef226f6 --- /dev/null +++ b/src/Framework/Exception/Incomplete/IncompleteTest.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface IncompleteTest extends Throwable +{ +} diff --git a/src/Framework/Exception/IncompleteTestError.php b/src/Framework/Exception/Incomplete/IncompleteTestError.php similarity index 81% rename from src/Framework/Exception/IncompleteTestError.php rename to src/Framework/Exception/Incomplete/IncompleteTestError.php index 65f9c8bc343..a45564da8fb 100644 --- a/src/Framework/Exception/IncompleteTestError.php +++ b/src/Framework/Exception/Incomplete/IncompleteTestError.php @@ -10,6 +10,8 @@ namespace PHPUnit\Framework; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class IncompleteTestError extends AssertionFailedError implements IncompleteTest diff --git a/src/Framework/Exception/InvalidArgumentException.php b/src/Framework/Exception/InvalidArgumentException.php index aec29f432a4..700abf03889 100644 --- a/src/Framework/Exception/InvalidArgumentException.php +++ b/src/Framework/Exception/InvalidArgumentException.php @@ -9,34 +9,11 @@ */ namespace PHPUnit\Framework; -use function debug_backtrace; -use function in_array; -use function lcfirst; -use function sprintf; - /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class InvalidArgumentException extends Exception +abstract class InvalidArgumentException extends Exception { - public static function create(int $argument, string $type): self - { - $stack = debug_backtrace(); - - return new self( - sprintf( - 'Argument #%d of %s::%s() must be %s %s', - $argument, - $stack[1]['class'], - $stack[1]['function'], - in_array(lcfirst($type)[0], ['a', 'e', 'i', 'o', 'u'], true) ? 'an' : 'a', - $type - ) - ); - } - - private function __construct(string $message = '', int $code = 0, \Exception $previous = null) - { - parent::__construct($message, $code, $previous); - } } diff --git a/src/Framework/Exception/InvalidCoversTargetException.php b/src/Framework/Exception/InvalidCoversTargetException.php deleted file mode 100644 index ebf2994a94e..00000000000 --- a/src/Framework/Exception/InvalidCoversTargetException.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class InvalidCoversTargetException extends CodeCoverageException -{ -} diff --git a/src/Framework/Exception/InvalidDataProviderException.php b/src/Framework/Exception/InvalidDataProviderException.php index 7e2ef24c637..b7aa4ae88db 100644 --- a/src/Framework/Exception/InvalidDataProviderException.php +++ b/src/Framework/Exception/InvalidDataProviderException.php @@ -9,9 +9,31 @@ */ namespace PHPUnit\Framework; +use Throwable; + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class InvalidDataProviderException extends Exception { + private ?string $providerLabel = null; + + public static function forException(Throwable $e, string $providerLabel): self + { + $exception = new self( + $e->getMessage(), + $e->getCode(), + $e, + ); + $exception->providerLabel = $providerLabel; + + return $exception; + } + + public function getProviderLabel(): ?string + { + return $this->providerLabel; + } } diff --git a/src/Framework/Exception/InvalidDependencyException.php b/src/Framework/Exception/InvalidDependencyException.php new file mode 100644 index 00000000000..8a636fd4851 --- /dev/null +++ b/src/Framework/Exception/InvalidDependencyException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidDependencyException extends AssertionFailedError implements SkippedTest +{ +} diff --git a/src/Framework/Exception/MissingCoversAnnotationException.php b/src/Framework/Exception/MissingCoversAnnotationException.php deleted file mode 100644 index 567a6c4c581..00000000000 --- a/src/Framework/Exception/MissingCoversAnnotationException.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class MissingCoversAnnotationException extends RiskyTestError -{ -} diff --git a/src/Framework/Exception/NoChildTestSuiteException.php b/src/Framework/Exception/NoChildTestSuiteException.php index 7ef4153b0f0..e59df81df12 100644 --- a/src/Framework/Exception/NoChildTestSuiteException.php +++ b/src/Framework/Exception/NoChildTestSuiteException.php @@ -10,6 +10,8 @@ namespace PHPUnit\Framework; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class NoChildTestSuiteException extends Exception diff --git a/src/Framework/Exception/ObjectEquals/ActualValueIsNotAnObjectException.php b/src/Framework/Exception/ObjectEquals/ActualValueIsNotAnObjectException.php new file mode 100644 index 00000000000..258b940ae21 --- /dev/null +++ b/src/Framework/Exception/ObjectEquals/ActualValueIsNotAnObjectException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ActualValueIsNotAnObjectException extends Exception +{ + public function __construct() + { + parent::__construct( + 'Actual value is not an object', + ); + } +} diff --git a/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotAcceptParameterTypeException.php b/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotAcceptParameterTypeException.php new file mode 100644 index 00000000000..74d00a17d76 --- /dev/null +++ b/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotAcceptParameterTypeException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ComparisonMethodDoesNotAcceptParameterTypeException extends Exception +{ + public function __construct(string $className, string $methodName, string $type) + { + parent::__construct( + sprintf( + '%s is not an accepted argument type for comparison method %s::%s().', + $type, + $className, + $methodName, + ), + ); + } +} diff --git a/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotDeclareBoolReturnTypeException.php b/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotDeclareBoolReturnTypeException.php new file mode 100644 index 00000000000..62dc7e8cbbb --- /dev/null +++ b/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotDeclareBoolReturnTypeException.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ComparisonMethodDoesNotDeclareBoolReturnTypeException extends Exception +{ + public function __construct(string $className, string $methodName) + { + parent::__construct( + sprintf( + 'Comparison method %s::%s() does not declare bool return type.', + $className, + $methodName, + ), + ); + } +} diff --git a/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotDeclareExactlyOneParameterException.php b/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotDeclareExactlyOneParameterException.php new file mode 100644 index 00000000000..d57607447ae --- /dev/null +++ b/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotDeclareExactlyOneParameterException.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ComparisonMethodDoesNotDeclareExactlyOneParameterException extends Exception +{ + public function __construct(string $className, string $methodName) + { + parent::__construct( + sprintf( + 'Comparison method %s::%s() does not declare exactly one parameter.', + $className, + $methodName, + ), + ); + } +} diff --git a/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotDeclareParameterTypeException.php b/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotDeclareParameterTypeException.php new file mode 100644 index 00000000000..65718682913 --- /dev/null +++ b/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotDeclareParameterTypeException.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ComparisonMethodDoesNotDeclareParameterTypeException extends Exception +{ + public function __construct(string $className, string $methodName) + { + parent::__construct( + sprintf( + 'Parameter of comparison method %s::%s() does not have a declared type.', + $className, + $methodName, + ), + ); + } +} diff --git a/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotExistException.php b/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotExistException.php new file mode 100644 index 00000000000..94590b51062 --- /dev/null +++ b/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotExistException.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ComparisonMethodDoesNotExistException extends Exception +{ + public function __construct(string $className, string $methodName) + { + parent::__construct( + sprintf( + 'Comparison method %s::%s() does not exist.', + $className, + $methodName, + ), + ); + } +} diff --git a/src/Framework/Exception/OutputError.php b/src/Framework/Exception/OutputError.php deleted file mode 100644 index 1c8b37e5617..00000000000 --- a/src/Framework/Exception/OutputError.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class OutputError extends AssertionFailedError -{ -} diff --git a/src/Framework/Exception/PHPTAssertionFailedError.php b/src/Framework/Exception/PHPTAssertionFailedError.php deleted file mode 100644 index 17126139f90..00000000000 --- a/src/Framework/Exception/PHPTAssertionFailedError.php +++ /dev/null @@ -1,32 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class PHPTAssertionFailedError extends SyntheticError -{ - /** - * @var string - */ - private $diff; - - public function __construct(string $message, int $code, string $file, int $line, array $trace, string $diff) - { - parent::__construct($message, $code, $file, $line, $trace); - $this->diff = $diff; - } - - public function getDiff(): string - { - return $this->diff; - } -} diff --git a/src/Framework/Exception/PhptAssertionFailedError.php b/src/Framework/Exception/PhptAssertionFailedError.php new file mode 100644 index 00000000000..3742abbbc6c --- /dev/null +++ b/src/Framework/Exception/PhptAssertionFailedError.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class PhptAssertionFailedError extends AssertionFailedError +{ + private readonly string $syntheticFile; + private readonly int $syntheticLine; + + /** + * @var list + */ + private readonly array $syntheticTrace; + private readonly string $diff; + + /** + * @param list $trace + */ + public function __construct(string $message, int $code, string $file, int $line, array $trace, string $diff) + { + parent::__construct($message, $code); + + $this->syntheticFile = $file; + $this->syntheticLine = $line; + $this->syntheticTrace = $trace; + $this->diff = $diff; + } + + public function syntheticFile(): string + { + return $this->syntheticFile; + } + + public function syntheticLine(): int + { + return $this->syntheticLine; + } + + /** + * @return list + */ + public function syntheticTrace(): array + { + return $this->syntheticTrace; + } + + public function diff(): string + { + return $this->diff; + } +} diff --git a/src/Framework/Exception/ProcessIsolationException.php b/src/Framework/Exception/ProcessIsolationException.php new file mode 100644 index 00000000000..e59c9c60358 --- /dev/null +++ b/src/Framework/Exception/ProcessIsolationException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ProcessIsolationException extends Exception +{ +} diff --git a/src/Framework/Exception/RiskyTestError.php b/src/Framework/Exception/RiskyTestError.php deleted file mode 100644 index a66552c0d68..00000000000 --- a/src/Framework/Exception/RiskyTestError.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -class RiskyTestError extends AssertionFailedError -{ -} diff --git a/src/Framework/Exception/Skipped/SkippedTest.php b/src/Framework/Exception/Skipped/SkippedTest.php new file mode 100644 index 00000000000..ab2f6749635 --- /dev/null +++ b/src/Framework/Exception/Skipped/SkippedTest.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface SkippedTest extends Throwable +{ +} diff --git a/src/Framework/Exception/SkippedTestSuiteError.php b/src/Framework/Exception/Skipped/SkippedTestSuiteError.php similarity index 81% rename from src/Framework/Exception/SkippedTestSuiteError.php rename to src/Framework/Exception/Skipped/SkippedTestSuiteError.php index 5448508a1ea..d3a4788b0cf 100644 --- a/src/Framework/Exception/SkippedTestSuiteError.php +++ b/src/Framework/Exception/Skipped/SkippedTestSuiteError.php @@ -10,6 +10,8 @@ namespace PHPUnit\Framework; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class SkippedTestSuiteError extends AssertionFailedError implements SkippedTest diff --git a/src/Framework/Exception/Skipped/SkippedWithMessageException.php b/src/Framework/Exception/Skipped/SkippedWithMessageException.php new file mode 100644 index 00000000000..d09a760a37c --- /dev/null +++ b/src/Framework/Exception/Skipped/SkippedWithMessageException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SkippedWithMessageException extends AssertionFailedError implements SkippedTest +{ +} diff --git a/src/Framework/Exception/SkippedTestError.php b/src/Framework/Exception/SkippedTestError.php deleted file mode 100644 index 7d553dcf3f1..00000000000 --- a/src/Framework/Exception/SkippedTestError.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class SkippedTestError extends AssertionFailedError implements SkippedTest -{ -} diff --git a/src/Framework/Exception/SyntheticError.php b/src/Framework/Exception/SyntheticError.php deleted file mode 100644 index c3124ba0c74..00000000000 --- a/src/Framework/Exception/SyntheticError.php +++ /dev/null @@ -1,61 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -class SyntheticError extends AssertionFailedError -{ - /** - * The synthetic file. - * - * @var string - */ - protected $syntheticFile = ''; - - /** - * The synthetic line number. - * - * @var int - */ - protected $syntheticLine = 0; - - /** - * The synthetic trace. - * - * @var array - */ - protected $syntheticTrace = []; - - public function __construct(string $message, int $code, string $file, int $line, array $trace) - { - parent::__construct($message, $code); - - $this->syntheticFile = $file; - $this->syntheticLine = $line; - $this->syntheticTrace = $trace; - } - - public function getSyntheticFile(): string - { - return $this->syntheticFile; - } - - public function getSyntheticLine(): int - { - return $this->syntheticLine; - } - - public function getSyntheticTrace(): array - { - return $this->syntheticTrace; - } -} diff --git a/src/Framework/Exception/SyntheticSkippedError.php b/src/Framework/Exception/SyntheticSkippedError.php deleted file mode 100644 index f6e155d7b9e..00000000000 --- a/src/Framework/Exception/SyntheticSkippedError.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class SyntheticSkippedError extends SyntheticError implements SkippedTest -{ -} diff --git a/src/Framework/Exception/UnintentionallyCoveredCodeError.php b/src/Framework/Exception/UnintentionallyCoveredCodeError.php deleted file mode 100644 index fcd1d824964..00000000000 --- a/src/Framework/Exception/UnintentionallyCoveredCodeError.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class UnintentionallyCoveredCodeError extends RiskyTestError -{ -} diff --git a/src/Framework/Exception/UnknownClassOrInterfaceException.php b/src/Framework/Exception/UnknownClassOrInterfaceException.php new file mode 100644 index 00000000000..6a10f97fb1a --- /dev/null +++ b/src/Framework/Exception/UnknownClassOrInterfaceException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class UnknownClassOrInterfaceException extends InvalidArgumentException +{ + public function __construct(string $name) + { + parent::__construct( + sprintf( + 'Class or interface "%s" does not exist', + $name, + ), + ); + } +} diff --git a/src/Framework/Exception/UnknownNativeTypeException.php b/src/Framework/Exception/UnknownNativeTypeException.php new file mode 100644 index 00000000000..09da4609328 --- /dev/null +++ b/src/Framework/Exception/UnknownNativeTypeException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class UnknownNativeTypeException extends InvalidArgumentException +{ + public function __construct(string $type) + { + parent::__construct( + sprintf( + 'Native type "%s" is not known', + $type, + ), + ); + } +} diff --git a/src/Framework/Exception/Warning.php b/src/Framework/Exception/Warning.php deleted file mode 100644 index 35e94493cf0..00000000000 --- a/src/Framework/Exception/Warning.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Warning extends Exception implements SelfDescribing -{ - /** - * Wrapper for getMessage() which is declared as final. - */ - public function toString(): string - { - return $this->getMessage(); - } -} diff --git a/src/Framework/ExceptionWrapper.php b/src/Framework/ExceptionWrapper.php deleted file mode 100644 index 265ffba0b45..00000000000 --- a/src/Framework/ExceptionWrapper.php +++ /dev/null @@ -1,121 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use function array_keys; -use function get_class; -use function spl_object_hash; -use PHPUnit\Util\Filter; -use Throwable; - -/** - * Wraps Exceptions thrown by code under test. - * - * Re-instantiates Exceptions thrown by user-space code to retain their original - * class names, properties, and stack traces (but without arguments). - * - * Unlike PHPUnit\Framework_\Exception, the complete stack of previous Exceptions - * is processed. - * - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ExceptionWrapper extends Exception -{ - /** - * @var string - */ - protected $className; - - /** - * @var null|ExceptionWrapper - */ - protected $previous; - - public function __construct(Throwable $t) - { - // PDOException::getCode() is a string. - // @see https://php.net/manual/en/class.pdoexception.php#95812 - parent::__construct($t->getMessage(), (int) $t->getCode()); - $this->setOriginalException($t); - } - - public function __toString(): string - { - $string = TestFailure::exceptionToString($this); - - if ($trace = Filter::getFilteredStacktrace($this)) { - $string .= "\n" . $trace; - } - - if ($this->previous) { - $string .= "\nCaused by\n" . $this->previous; - } - - return $string; - } - - public function getClassName(): string - { - return $this->className; - } - - public function getPreviousWrapped(): ?self - { - return $this->previous; - } - - public function setClassName(string $className): void - { - $this->className = $className; - } - - public function setOriginalException(Throwable $t): void - { - $this->originalException($t); - - $this->className = get_class($t); - $this->file = $t->getFile(); - $this->line = $t->getLine(); - - $this->serializableTrace = $t->getTrace(); - - foreach (array_keys($this->serializableTrace) as $key) { - unset($this->serializableTrace[$key]['args']); - } - - if ($t->getPrevious()) { - $this->previous = new self($t->getPrevious()); - } - } - - public function getOriginalException(): ?Throwable - { - return $this->originalException(); - } - - /** - * Method to contain static originalException to exclude it from stacktrace to prevent the stacktrace contents, - * which can be quite big, from being garbage-collected, thus blocking memory until shutdown. - * - * Approach works both for var_dump() and var_export() and print_r(). - */ - private function originalException(Throwable $exceptionToStore = null): ?Throwable - { - static $originalExceptions; - - $instanceId = spl_object_hash($this); - - if ($exceptionToStore) { - $originalExceptions[$instanceId] = $exceptionToStore; - } - - return $originalExceptions[$instanceId] ?? null; - } -} diff --git a/src/Framework/ExecutionOrderDependency.php b/src/Framework/ExecutionOrderDependency.php index 81b386a0d1f..896d2560a4a 100644 --- a/src/Framework/ExecutionOrderDependency.php +++ b/src/Framework/ExecutionOrderDependency.php @@ -11,48 +11,56 @@ use function array_filter; use function array_map; -use function array_search; +use function array_values; +use function assert; use function count; use function explode; -use function strpos; -use function trim; +use function in_array; +use function str_contains; +use PHPUnit\Metadata\DependsOnClass; +use PHPUnit\Metadata\DependsOnMethod; +use Stringable; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class ExecutionOrderDependency +final class ExecutionOrderDependency implements Stringable { - /* @var string */ - private $className = ''; - - /* @var string */ - private $methodName = ''; - - /* @var bool */ - private $useShallowClone = false; + private string $className = ''; + private string $methodName = ''; + private readonly bool $shallowClone; + private readonly bool $deepClone; - /* @var bool */ - private $useDeepClone = false; - - public static function createFromDependsAnnotation(string $className, string $annotation): self + public static function invalid(): self { - // Split clone option and target - $parts = explode(' ', trim($annotation), 2); - - if (count($parts) === 1) { - $cloneOption = ''; - $target = $parts[0]; - } else { - $cloneOption = $parts[0]; - $target = $parts[1]; - } + return new self( + '', + '', + false, + false, + ); + } - // Prefix provided class for targets assumed to be in scope - if ($target !== '' && strpos($target, '::') === false) { - $target = $className . '::' . $target; - } + public static function forClass(DependsOnClass $metadata): self + { + return new self( + $metadata->className(), + 'class', + $metadata->deepClone(), + $metadata->shallowClone(), + ); + } - return new self($target, null, $cloneOption); + public static function forMethod(DependsOnMethod $metadata): self + { + return new self( + $metadata->className(), + $metadata->methodName(), + $metadata->deepClone(), + $metadata->shallowClone(), + ); } /** @@ -62,9 +70,12 @@ public static function createFromDependsAnnotation(string $className, string $an */ public static function filterInvalid(array $dependencies): array { - return array_values(array_filter($dependencies, function (self $d) { - return $d->isValid(); - })); + return array_values( + array_filter( + $dependencies, + static fn (self $d) => $d->isValid(), + ), + ); } /** @@ -75,16 +86,19 @@ public static function filterInvalid(array $dependencies): array */ public static function mergeUnique(array $existing, array $additional): array { - $existingTargets = array_map(static function ($dependency) { - return $dependency->getTarget(); - }, $existing); + $existingTargets = array_map( + static fn (ExecutionOrderDependency $dependency) => $dependency->getTarget(), + $existing, + ); foreach ($additional as $dependency) { - if (array_search($dependency->getTarget(), $existingTargets, true) !== false) { + $additionalTarget = $dependency->getTarget(); + + if (in_array($additionalTarget, $existingTargets, true)) { continue; } - $existingTargets[] = $dependency->getTarget(); + $existingTargets[] = $additionalTarget; $existing[] = $dependency; } @@ -108,12 +122,13 @@ public static function diff(array $left, array $right): array } $diff = []; - $rightTargets = array_map(static function ($dependency) { - return $dependency->getTarget(); - }, $right); + $rightTargets = array_map( + static fn (ExecutionOrderDependency $dependency) => $dependency->getTarget(), + $right, + ); foreach ($left as $dependency) { - if (array_search($dependency->getTarget(), $rightTargets, true) !== false) { + if (in_array($dependency->getTarget(), $rightTargets, true)) { continue; } @@ -123,26 +138,22 @@ public static function diff(array $left, array $right): array return $diff; } - public function __construct(string $classOrCallableName, ?string $methodName = null, ?string $option = null) + public function __construct(string $classOrCallableName, ?string $methodName = null, bool $deepClone = false, bool $shallowClone = false) { + $this->deepClone = $deepClone; + $this->shallowClone = $shallowClone; + if ($classOrCallableName === '') { - return $this; + return; } - if (strpos($classOrCallableName, '::') !== false) { + if (str_contains($classOrCallableName, '::')) { + assert(count(explode('::', $classOrCallableName)) === 2); [$this->className, $this->methodName] = explode('::', $classOrCallableName); } else { $this->className = $classOrCallableName; - $this->methodName = !empty($methodName) ? $methodName : 'class'; + $this->methodName = $methodName !== null && $methodName !== '' ? $methodName : 'class'; } - - if ($option === 'clone') { - $this->useDeepClone = true; - } elseif ($option === 'shallowClone') { - $this->useShallowClone = true; - } - - return $this; } public function __toString(): string @@ -150,20 +161,23 @@ public function __toString(): string return $this->getTarget(); } + /** + * @phpstan-assert-if-true non-empty-string $this->getTarget() + */ public function isValid(): bool { // Invalid dependencies can be declared and are skipped by the runner return $this->className !== '' && $this->methodName !== ''; } - public function useShallowClone(): bool + public function shallowClone(): bool { - return $this->useShallowClone; + return $this->shallowClone; } - public function useDeepClone(): bool + public function deepClone(): bool { - return $this->useDeepClone; + return $this->deepClone; } public function targetIsClass(): bool diff --git a/src/Framework/IncompleteTest.php b/src/Framework/IncompleteTest.php deleted file mode 100644 index b77b1afff2c..00000000000 --- a/src/Framework/IncompleteTest.php +++ /dev/null @@ -1,19 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -interface IncompleteTest extends Throwable -{ -} diff --git a/src/Framework/IncompleteTestCase.php b/src/Framework/IncompleteTestCase.php deleted file mode 100644 index e6562485747..00000000000 --- a/src/Framework/IncompleteTestCase.php +++ /dev/null @@ -1,71 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class IncompleteTestCase extends TestCase -{ - /** - * @var bool - */ - protected $backupGlobals = false; - - /** - * @var bool - */ - protected $backupStaticAttributes = false; - - /** - * @var bool - */ - protected $runTestInSeparateProcess = false; - - /** - * @var bool - */ - protected $useErrorHandler = false; - - /** - * @var string - */ - private $message; - - public function __construct(string $className, string $methodName, string $message = '') - { - parent::__construct($className . '::' . $methodName); - - $this->message = $message; - } - - public function getMessage(): string - { - return $this->message; - } - - /** - * Returns a string representation of the test case. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function toString(): string - { - return $this->getName(); - } - - /** - * @throws Exception - */ - protected function runTest(): void - { - $this->markTestIncomplete($this->message); - } -} diff --git a/src/Framework/InvalidParameterGroupException.php b/src/Framework/InvalidParameterGroupException.php deleted file mode 100644 index feb9cc989f1..00000000000 --- a/src/Framework/InvalidParameterGroupException.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class InvalidParameterGroupException extends Exception -{ -} diff --git a/src/Framework/MockObject/Api/Api.php b/src/Framework/MockObject/Api/Api.php deleted file mode 100644 index e2f0a2802d6..00000000000 --- a/src/Framework/MockObject/Api/Api.php +++ /dev/null @@ -1,97 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use PHPUnit\Framework\MockObject\Builder\InvocationMocker as InvocationMockerBuilder; -use PHPUnit\Framework\MockObject\Rule\InvocationOrder; - -/** - * @internal This trait is not covered by the backward compatibility promise for PHPUnit - */ -trait Api -{ - /** - * @var ConfigurableMethod[] - */ - private static $__phpunit_configurableMethods; - - /** - * @var object - */ - private $__phpunit_originalObject; - - /** - * @var bool - */ - private $__phpunit_returnValueGeneration = true; - - /** - * @var InvocationHandler - */ - private $__phpunit_invocationMocker; - - /** @noinspection MagicMethodsValidityInspection */ - public static function __phpunit_initConfigurableMethods(ConfigurableMethod ...$configurableMethods): void - { - if (isset(static::$__phpunit_configurableMethods)) { - throw new ConfigurableMethodsAlreadyInitializedException( - 'Configurable methods is already initialized and can not be reinitialized' - ); - } - - static::$__phpunit_configurableMethods = $configurableMethods; - } - - /** @noinspection MagicMethodsValidityInspection */ - public function __phpunit_setOriginalObject($originalObject): void - { - $this->__phpunit_originalObject = $originalObject; - } - - /** @noinspection MagicMethodsValidityInspection */ - public function __phpunit_setReturnValueGeneration(bool $returnValueGeneration): void - { - $this->__phpunit_returnValueGeneration = $returnValueGeneration; - } - - /** @noinspection MagicMethodsValidityInspection */ - public function __phpunit_getInvocationHandler(): InvocationHandler - { - if ($this->__phpunit_invocationMocker === null) { - $this->__phpunit_invocationMocker = new InvocationHandler( - static::$__phpunit_configurableMethods, - $this->__phpunit_returnValueGeneration - ); - } - - return $this->__phpunit_invocationMocker; - } - - /** @noinspection MagicMethodsValidityInspection */ - public function __phpunit_hasMatchers(): bool - { - return $this->__phpunit_getInvocationHandler()->hasMatchers(); - } - - /** @noinspection MagicMethodsValidityInspection */ - public function __phpunit_verify(bool $unsetInvocationMocker = true): void - { - $this->__phpunit_getInvocationHandler()->verify(); - - if ($unsetInvocationMocker) { - $this->__phpunit_invocationMocker = null; - } - } - - public function expects(InvocationOrder $matcher): InvocationMockerBuilder - { - return $this->__phpunit_getInvocationHandler()->expects($matcher); - } -} diff --git a/src/Framework/MockObject/Api/Method.php b/src/Framework/MockObject/Api/Method.php deleted file mode 100644 index f6df7533c1f..00000000000 --- a/src/Framework/MockObject/Api/Method.php +++ /dev/null @@ -1,30 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function call_user_func_array; -use function func_get_args; -use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount; - -/** - * @internal This trait is not covered by the backward compatibility promise for PHPUnit - */ -trait Method -{ - public function method() - { - $expects = $this->expects(new AnyInvokedCount); - - return call_user_func_array( - [$expects, 'method'], - func_get_args() - ); - } -} diff --git a/src/Framework/MockObject/Api/MockedCloneMethod.php b/src/Framework/MockObject/Api/MockedCloneMethod.php deleted file mode 100644 index 91e35f937f1..00000000000 --- a/src/Framework/MockObject/Api/MockedCloneMethod.php +++ /dev/null @@ -1,21 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -/** - * @internal This trait is not covered by the backward compatibility promise for PHPUnit - */ -trait MockedCloneMethod -{ - public function __clone() - { - $this->__phpunit_invocationMocker = clone $this->__phpunit_getInvocationHandler(); - } -} diff --git a/src/Framework/MockObject/Api/UnmockedCloneMethod.php b/src/Framework/MockObject/Api/UnmockedCloneMethod.php deleted file mode 100644 index 3f493d203d1..00000000000 --- a/src/Framework/MockObject/Api/UnmockedCloneMethod.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -/** - * @internal This trait is not covered by the backward compatibility promise for PHPUnit - */ -trait UnmockedCloneMethod -{ - public function __clone() - { - $this->__phpunit_invocationMocker = clone $this->__phpunit_getInvocationHandler(); - - parent::__clone(); - } -} diff --git a/src/Framework/MockObject/Builder/Identity.php b/src/Framework/MockObject/Builder/Identity.php deleted file mode 100644 index a68bfadf9dc..00000000000 --- a/src/Framework/MockObject/Builder/Identity.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Builder; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -interface Identity -{ - /** - * Sets the identification of the expectation to $id. - * - * @note The identifier is unique per mock object. - * - * @param string $id unique identification of expectation - */ - public function id($id); -} diff --git a/src/Framework/MockObject/Builder/InvocationMocker.php b/src/Framework/MockObject/Builder/InvocationMocker.php deleted file mode 100644 index ee4dd20a667..00000000000 --- a/src/Framework/MockObject/Builder/InvocationMocker.php +++ /dev/null @@ -1,299 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Builder; - -use function array_map; -use function array_merge; -use function count; -use function get_class; -use function gettype; -use function in_array; -use function is_object; -use function is_string; -use function sprintf; -use function strtolower; -use PHPUnit\Framework\Constraint\Constraint; -use PHPUnit\Framework\MockObject\ConfigurableMethod; -use PHPUnit\Framework\MockObject\IncompatibleReturnValueException; -use PHPUnit\Framework\MockObject\InvocationHandler; -use PHPUnit\Framework\MockObject\Matcher; -use PHPUnit\Framework\MockObject\Rule; -use PHPUnit\Framework\MockObject\RuntimeException; -use PHPUnit\Framework\MockObject\Stub\ConsecutiveCalls; -use PHPUnit\Framework\MockObject\Stub\Exception; -use PHPUnit\Framework\MockObject\Stub\ReturnArgument; -use PHPUnit\Framework\MockObject\Stub\ReturnCallback; -use PHPUnit\Framework\MockObject\Stub\ReturnReference; -use PHPUnit\Framework\MockObject\Stub\ReturnSelf; -use PHPUnit\Framework\MockObject\Stub\ReturnStub; -use PHPUnit\Framework\MockObject\Stub\ReturnValueMap; -use PHPUnit\Framework\MockObject\Stub\Stub; -use Throwable; - -final class InvocationMocker implements InvocationStubber, MethodNameMatch -{ - /** - * @var InvocationHandler - */ - private $invocationHandler; - - /** - * @var Matcher - */ - private $matcher; - - /** - * @var ConfigurableMethod[] - */ - private $configurableMethods; - - public function __construct(InvocationHandler $handler, Matcher $matcher, ConfigurableMethod ...$configurableMethods) - { - $this->invocationHandler = $handler; - $this->matcher = $matcher; - $this->configurableMethods = $configurableMethods; - } - - /** - * @return $this - */ - public function id($id): self - { - $this->invocationHandler->registerMatcher($id, $this->matcher); - - return $this; - } - - /** - * @return $this - */ - public function will(Stub $stub): Identity - { - $this->matcher->setStub($stub); - - return $this; - } - - public function willReturn($value, ...$nextValues): self - { - if (count($nextValues) === 0) { - $this->ensureTypeOfReturnValues([$value]); - - $stub = $value instanceof Stub ? $value : new ReturnStub($value); - } else { - $values = array_merge([$value], $nextValues); - - $this->ensureTypeOfReturnValues($values); - - $stub = new ConsecutiveCalls($values); - } - - return $this->will($stub); - } - - public function willReturnReference(&$reference): self - { - $stub = new ReturnReference($reference); - - return $this->will($stub); - } - - public function willReturnMap(array $valueMap): self - { - $stub = new ReturnValueMap($valueMap); - - return $this->will($stub); - } - - public function willReturnArgument($argumentIndex): self - { - $stub = new ReturnArgument($argumentIndex); - - return $this->will($stub); - } - - public function willReturnCallback($callback): self - { - $stub = new ReturnCallback($callback); - - return $this->will($stub); - } - - public function willReturnSelf(): self - { - $stub = new ReturnSelf; - - return $this->will($stub); - } - - public function willReturnOnConsecutiveCalls(...$values): self - { - $stub = new ConsecutiveCalls($values); - - return $this->will($stub); - } - - public function willThrowException(Throwable $exception): self - { - $stub = new Exception($exception); - - return $this->will($stub); - } - - /** - * @return $this - */ - public function after($id): self - { - $this->matcher->setAfterMatchBuilderId($id); - - return $this; - } - - /** - * @throws RuntimeException - * - * @return $this - */ - public function with(...$arguments): self - { - $this->canDefineParameters(); - - $this->matcher->setParametersRule(new Rule\Parameters($arguments)); - - return $this; - } - - /** - * @param array ...$arguments - * - * @throws RuntimeException - * - * @return $this - */ - public function withConsecutive(...$arguments): self - { - $this->canDefineParameters(); - - $this->matcher->setParametersRule(new Rule\ConsecutiveParameters($arguments)); - - return $this; - } - - /** - * @throws RuntimeException - * - * @return $this - */ - public function withAnyParameters(): self - { - $this->canDefineParameters(); - - $this->matcher->setParametersRule(new Rule\AnyParameters); - - return $this; - } - - /** - * @param Constraint|string $constraint - * - * @throws RuntimeException - * - * @return $this - */ - public function method($constraint): self - { - if ($this->matcher->hasMethodNameRule()) { - throw new RuntimeException( - 'Rule for method name is already defined, cannot redefine' - ); - } - - $configurableMethodNames = array_map( - static function (ConfigurableMethod $configurable) { - return strtolower($configurable->getName()); - }, - $this->configurableMethods - ); - - if (is_string($constraint) && !in_array(strtolower($constraint), $configurableMethodNames, true)) { - throw new RuntimeException( - sprintf( - 'Trying to configure method "%s" which cannot be configured because it does not exist, has not been specified, is final, or is static', - $constraint - ) - ); - } - - $this->matcher->setMethodNameRule(new Rule\MethodName($constraint)); - - return $this; - } - - /** - * Validate that a parameters rule can be defined, throw exceptions otherwise. - * - * @throws RuntimeException - */ - private function canDefineParameters(): void - { - if (!$this->matcher->hasMethodNameRule()) { - throw new RuntimeException( - 'Rule for method name is not defined, cannot define rule for parameters ' . - 'without one' - ); - } - - if ($this->matcher->hasParametersRule()) { - throw new RuntimeException( - 'Rule for parameters is already defined, cannot redefine' - ); - } - } - - private function getConfiguredMethod(): ?ConfigurableMethod - { - $configuredMethod = null; - - foreach ($this->configurableMethods as $configurableMethod) { - if ($this->matcher->getMethodNameRule()->matchesName($configurableMethod->getName())) { - if ($configuredMethod !== null) { - return null; - } - - $configuredMethod = $configurableMethod; - } - } - - return $configuredMethod; - } - - private function ensureTypeOfReturnValues(array $values): void - { - $configuredMethod = $this->getConfiguredMethod(); - - if ($configuredMethod === null) { - return; - } - - foreach ($values as $value) { - if (!$configuredMethod->mayReturn($value)) { - throw new IncompatibleReturnValueException( - sprintf( - 'Method %s may not return value of type %s, its return declaration is "%s"', - $configuredMethod->getName(), - is_object($value) ? get_class($value) : gettype($value), - $configuredMethod->getReturnTypeDeclaration() - ) - ); - } - } - } -} diff --git a/src/Framework/MockObject/Builder/InvocationStubber.php b/src/Framework/MockObject/Builder/InvocationStubber.php deleted file mode 100644 index c0e51b00e75..00000000000 --- a/src/Framework/MockObject/Builder/InvocationStubber.php +++ /dev/null @@ -1,62 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Builder; - -use PHPUnit\Framework\MockObject\Stub\Stub; -use Throwable; - -interface InvocationStubber -{ - public function will(Stub $stub): Identity; - - /** @return self */ - public function willReturn($value, ...$nextValues)/*: self */; - - /** - * @param mixed $reference - * - * @return self - */ - public function willReturnReference(&$reference)/*: self */; - - /** - * @param array> $valueMap - * - * @return self - */ - public function willReturnMap(array $valueMap)/*: self */; - - /** - * @param int $argumentIndex - * - * @return self - */ - public function willReturnArgument($argumentIndex)/*: self */; - - /** - * @param callable $callback - * - * @return self - */ - public function willReturnCallback($callback)/*: self */; - - /** @return self */ - public function willReturnSelf()/*: self */; - - /** - * @param mixed $values - * - * @return self - */ - public function willReturnOnConsecutiveCalls(...$values)/*: self */; - - /** @return self */ - public function willThrowException(Throwable $exception)/*: self */; -} diff --git a/src/Framework/MockObject/Builder/Match.php b/src/Framework/MockObject/Builder/Match.php deleted file mode 100644 index d343eacfb80..00000000000 --- a/src/Framework/MockObject/Builder/Match.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Builder; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -interface Match extends Stub -{ - /** - * Defines the expectation which must occur before the current is valid. - * - * @param string $id the identification of the expectation that should - * occur before this one - * - * @return Stub - */ - public function after($id); -} diff --git a/src/Framework/MockObject/Builder/MethodNameMatch.php b/src/Framework/MockObject/Builder/MethodNameMatch.php deleted file mode 100644 index f4b1150b5ad..00000000000 --- a/src/Framework/MockObject/Builder/MethodNameMatch.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Builder; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -interface MethodNameMatch extends ParametersMatch -{ - /** - * Adds a new method name match and returns the parameter match object for - * further matching possibilities. - * - * @param \PHPUnit\Framework\Constraint\Constraint $name Constraint for matching method, if a string is passed it will use the PHPUnit_Framework_Constraint_IsEqual - * - * @return ParametersMatch - */ - public function method($name); -} diff --git a/src/Framework/MockObject/Builder/ParametersMatch.php b/src/Framework/MockObject/Builder/ParametersMatch.php deleted file mode 100644 index ae16d79882a..00000000000 --- a/src/Framework/MockObject/Builder/ParametersMatch.php +++ /dev/null @@ -1,48 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Builder; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -interface ParametersMatch extends Match -{ - /** - * Sets the parameters to match for, each parameter to this function will - * be part of match. To perform specific matches or constraints create a - * new PHPUnit\Framework\Constraint\Constraint and use it for the parameter. - * If the parameter value is not a constraint it will use the - * PHPUnit\Framework\Constraint\IsEqual for the value. - * - * Some examples: - * - * // match first parameter with value 2 - * $b->with(2); - * // match first parameter with value 'smock' and second identical to 42 - * $b->with('smock', new PHPUnit\Framework\Constraint\IsEqual(42)); - * - * - * @return ParametersMatch - */ - public function with(...$arguments); - - /** - * Sets a rule which allows any kind of parameters. - * - * Some examples: - * - * // match any number of parameters - * $b->withAnyParameters(); - * - * - * @return ParametersMatch - */ - public function withAnyParameters(); -} diff --git a/src/Framework/MockObject/Builder/Stub.php b/src/Framework/MockObject/Builder/Stub.php deleted file mode 100644 index d7cb78fc4e3..00000000000 --- a/src/Framework/MockObject/Builder/Stub.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Builder; - -use PHPUnit\Framework\MockObject\Stub\Stub as BaseStub; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -interface Stub extends Identity -{ - /** - * Stubs the matching method with the stub object $stub. Any invocations of - * the matched method will now be handled by the stub instead. - */ - public function will(BaseStub $stub): Identity; -} diff --git a/src/Framework/MockObject/ConfigurableMethod.php b/src/Framework/MockObject/ConfigurableMethod.php index 4757dc63743..7cce22c3814 100644 --- a/src/Framework/MockObject/ConfigurableMethod.php +++ b/src/Framework/MockObject/ConfigurableMethod.php @@ -12,41 +12,71 @@ use SebastianBergmann\Type\Type; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class ConfigurableMethod +final readonly class ConfigurableMethod { /** - * @var string + * @var non-empty-string */ - private $name; + private string $name; /** - * @var Type + * @var array */ - private $returnType; + private array $defaultParameterValues; - public function __construct(string $name, Type $returnType) + /** + * @var non-negative-int + */ + private int $numberOfParameters; + private Type $returnType; + + /** + * @param non-empty-string $name + * @param array $defaultParameterValues + * @param non-negative-int $numberOfParameters + */ + public function __construct(string $name, array $defaultParameterValues, int $numberOfParameters, Type $returnType) { - $this->name = $name; - $this->returnType = $returnType; + $this->name = $name; + $this->defaultParameterValues = $defaultParameterValues; + $this->numberOfParameters = $numberOfParameters; + $this->returnType = $returnType; } - public function getName(): string + /** + * @return non-empty-string + */ + public function name(): string { return $this->name; } - public function mayReturn($value): bool + /** + * @return array + */ + public function defaultParameterValues(): array + { + return $this->defaultParameterValues; + } + + /** + * @return non-negative-int + */ + public function numberOfParameters(): int { - if ($value === null && $this->returnType->allowsNull()) { - return true; - } + return $this->numberOfParameters; + } + public function mayReturn(mixed $value): bool + { return $this->returnType->isAssignable(Type::fromValue($value, false)); } - public function getReturnTypeDeclaration(): string + public function returnTypeDeclaration(): string { return $this->returnType->asString(); } diff --git a/src/Framework/MockObject/Exception/BadMethodCallException.php b/src/Framework/MockObject/Exception/BadMethodCallException.php index 7e655e235a9..e8ddadda5e1 100644 --- a/src/Framework/MockObject/Exception/BadMethodCallException.php +++ b/src/Framework/MockObject/Exception/BadMethodCallException.php @@ -10,6 +10,8 @@ namespace PHPUnit\Framework\MockObject; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class BadMethodCallException extends \BadMethodCallException implements Exception diff --git a/src/Framework/MockObject/Exception/CannotUseOnlyMethodsException.php b/src/Framework/MockObject/Exception/CannotUseOnlyMethodsException.php new file mode 100644 index 00000000000..6cb399e53a6 --- /dev/null +++ b/src/Framework/MockObject/Exception/CannotUseOnlyMethodsException.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CannotUseOnlyMethodsException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(string $type, string $methodName) + { + parent::__construct( + sprintf( + 'Trying to configure method "%s" with onlyMethods(), but it does not exist in class "%s"', + $methodName, + $type, + ), + ); + } +} diff --git a/src/Framework/MockObject/Exception/ConfigurableMethodsAlreadyInitializedException.php b/src/Framework/MockObject/Exception/ConfigurableMethodsAlreadyInitializedException.php deleted file mode 100644 index d12ac997382..00000000000 --- a/src/Framework/MockObject/Exception/ConfigurableMethodsAlreadyInitializedException.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ConfigurableMethodsAlreadyInitializedException extends \PHPUnit\Framework\Exception implements Exception -{ -} diff --git a/src/Framework/MockObject/Exception/Exception.php b/src/Framework/MockObject/Exception/Exception.php index 5880bc033ec..f7994f20f77 100644 --- a/src/Framework/MockObject/Exception/Exception.php +++ b/src/Framework/MockObject/Exception/Exception.php @@ -12,6 +12,8 @@ use Throwable; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ interface Exception extends Throwable diff --git a/src/Framework/MockObject/Exception/IncompatibleReturnValueException.php b/src/Framework/MockObject/Exception/IncompatibleReturnValueException.php index f1ceb1debfa..faf8a498d95 100644 --- a/src/Framework/MockObject/Exception/IncompatibleReturnValueException.php +++ b/src/Framework/MockObject/Exception/IncompatibleReturnValueException.php @@ -9,9 +9,25 @@ */ namespace PHPUnit\Framework\MockObject; +use function get_debug_type; +use function sprintf; + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class IncompatibleReturnValueException extends \PHPUnit\Framework\Exception implements Exception { + public function __construct(ConfigurableMethod $method, mixed $value) + { + parent::__construct( + sprintf( + 'Method %s may not return value of type %s, its declared return type is "%s"', + $method->name(), + get_debug_type($value), + $method->returnTypeDeclaration(), + ), + ); + } } diff --git a/src/Framework/MockObject/Exception/MatchBuilderNotFoundException.php b/src/Framework/MockObject/Exception/MatchBuilderNotFoundException.php new file mode 100644 index 00000000000..8bf8967b066 --- /dev/null +++ b/src/Framework/MockObject/Exception/MatchBuilderNotFoundException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MatchBuilderNotFoundException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(string $id) + { + parent::__construct( + sprintf( + 'No builder found for match builder identification <%s>', + $id, + ), + ); + } +} diff --git a/src/Framework/MockObject/Exception/MatcherAlreadyRegisteredException.php b/src/Framework/MockObject/Exception/MatcherAlreadyRegisteredException.php new file mode 100644 index 00000000000..de62b867922 --- /dev/null +++ b/src/Framework/MockObject/Exception/MatcherAlreadyRegisteredException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MatcherAlreadyRegisteredException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(string $id) + { + parent::__construct( + sprintf( + 'Matcher with id <%s> is already registered', + $id, + ), + ); + } +} diff --git a/src/Framework/MockObject/Exception/MethodCannotBeConfiguredException.php b/src/Framework/MockObject/Exception/MethodCannotBeConfiguredException.php new file mode 100644 index 00000000000..4d39b5d9250 --- /dev/null +++ b/src/Framework/MockObject/Exception/MethodCannotBeConfiguredException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MethodCannotBeConfiguredException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(string $method) + { + parent::__construct( + sprintf( + 'Trying to configure method "%s" which cannot be configured because it does not exist, has not been specified, is final, or is static', + $method, + ), + ); + } +} diff --git a/src/Framework/MockObject/Exception/MethodNameAlreadyConfiguredException.php b/src/Framework/MockObject/Exception/MethodNameAlreadyConfiguredException.php new file mode 100644 index 00000000000..e4a375927bc --- /dev/null +++ b/src/Framework/MockObject/Exception/MethodNameAlreadyConfiguredException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MethodNameAlreadyConfiguredException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct() + { + parent::__construct('Method name is already configured'); + } +} diff --git a/src/Framework/MockObject/Exception/MethodNameNotConfiguredException.php b/src/Framework/MockObject/Exception/MethodNameNotConfiguredException.php new file mode 100644 index 00000000000..25c11341660 --- /dev/null +++ b/src/Framework/MockObject/Exception/MethodNameNotConfiguredException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MethodNameNotConfiguredException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct() + { + parent::__construct('Method name is not configured'); + } +} diff --git a/src/Framework/MockObject/Exception/MethodParametersAlreadyConfiguredException.php b/src/Framework/MockObject/Exception/MethodParametersAlreadyConfiguredException.php new file mode 100644 index 00000000000..fba96cf45c9 --- /dev/null +++ b/src/Framework/MockObject/Exception/MethodParametersAlreadyConfiguredException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MethodParametersAlreadyConfiguredException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct() + { + parent::__construct('Method parameters already configured'); + } +} diff --git a/src/Framework/MockObject/Exception/NeverReturningMethodException.php b/src/Framework/MockObject/Exception/NeverReturningMethodException.php new file mode 100644 index 00000000000..3e565cea836 --- /dev/null +++ b/src/Framework/MockObject/Exception/NeverReturningMethodException.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class NeverReturningMethodException extends RuntimeException implements Exception +{ + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function __construct(string $className, string $methodName) + { + parent::__construct( + sprintf( + 'Method %s::%s() is declared to never return', + $className, + $methodName, + ), + ); + } +} diff --git a/src/Framework/MockObject/Exception/NoMoreReturnValuesConfiguredException.php b/src/Framework/MockObject/Exception/NoMoreReturnValuesConfiguredException.php new file mode 100644 index 00000000000..c4b181653f9 --- /dev/null +++ b/src/Framework/MockObject/Exception/NoMoreReturnValuesConfiguredException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NoMoreReturnValuesConfiguredException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(Invocation $invocation, int $numberOfConfiguredReturnValues) + { + parent::__construct( + sprintf( + 'Only %d return values have been configured for %s::%s()', + $numberOfConfiguredReturnValues, + $invocation->className(), + $invocation->methodName(), + ), + ); + } +} diff --git a/src/Framework/MockObject/Exception/ReturnValueNotConfiguredException.php b/src/Framework/MockObject/Exception/ReturnValueNotConfiguredException.php new file mode 100644 index 00000000000..cf193f10c14 --- /dev/null +++ b/src/Framework/MockObject/Exception/ReturnValueNotConfiguredException.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ReturnValueNotConfiguredException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(Invocation $invocation) + { + parent::__construct( + sprintf( + 'No return value is configured for %s::%s() and return value generation is disabled', + $invocation->className(), + $invocation->methodName(), + ), + ); + } +} diff --git a/src/Framework/MockObject/Exception/RuntimeException.php b/src/Framework/MockObject/Exception/RuntimeException.php index 33b6a5be32f..b99a903e5d1 100644 --- a/src/Framework/MockObject/Exception/RuntimeException.php +++ b/src/Framework/MockObject/Exception/RuntimeException.php @@ -10,6 +10,8 @@ namespace PHPUnit\Framework\MockObject; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class RuntimeException extends \RuntimeException implements Exception diff --git a/src/Framework/MockObject/Generator.php b/src/Framework/MockObject/Generator.php deleted file mode 100644 index 01b5610135d..00000000000 --- a/src/Framework/MockObject/Generator.php +++ /dev/null @@ -1,983 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use const DIRECTORY_SEPARATOR; -use const PHP_EOL; -use const PHP_MAJOR_VERSION; -use const PREG_OFFSET_CAPTURE; -use const WSDL_CACHE_NONE; -use function array_diff_assoc; -use function array_merge; -use function array_pop; -use function array_unique; -use function class_exists; -use function count; -use function explode; -use function extension_loaded; -use function implode; -use function in_array; -use function interface_exists; -use function is_array; -use function is_object; -use function md5; -use function mt_rand; -use function preg_match; -use function preg_match_all; -use function range; -use function serialize; -use function sort; -use function sprintf; -use function str_replace; -use function strlen; -use function strpos; -use function strtolower; -use function substr; -use function trait_exists; -use Doctrine\Instantiator\Exception\ExceptionInterface as InstantiatorException; -use Doctrine\Instantiator\Instantiator; -use Exception; -use Iterator; -use IteratorAggregate; -use PHPUnit\Framework\InvalidArgumentException; -use ReflectionClass; -use ReflectionException; -use ReflectionMethod; -use SebastianBergmann\Template\Template; -use SoapClient; -use SoapFault; -use Throwable; -use Traversable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Generator -{ - /** - * @var array - */ - private const EXCLUDED_METHOD_NAMES = [ - '__CLASS__' => true, - '__DIR__' => true, - '__FILE__' => true, - '__FUNCTION__' => true, - '__LINE__' => true, - '__METHOD__' => true, - '__NAMESPACE__' => true, - '__TRAIT__' => true, - '__clone' => true, - '__halt_compiler' => true, - ]; - - /** - * @var array - */ - private static $cache = []; - - /** - * @var Template[] - */ - private static $templates = []; - - /** - * Returns a mock object for the specified class. - * - * @param null|array $methods - * - * @throws RuntimeException - */ - public function getMock(string $type, $methods = [], array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $callAutoload = true, bool $cloneArguments = true, bool $callOriginalMethods = false, object $proxyTarget = null, bool $allowMockingUnknownTypes = true, bool $returnValueGeneration = true): MockObject - { - if (!is_array($methods) && null !== $methods) { - throw InvalidArgumentException::create(2, 'array'); - } - - if ($type === 'Traversable' || $type === '\\Traversable') { - $type = 'Iterator'; - } - - if (!$allowMockingUnknownTypes && !class_exists($type, $callAutoload) && !interface_exists($type, $callAutoload)) { - throw new RuntimeException( - sprintf( - 'Cannot stub or mock class or interface "%s" which does not exist', - $type - ) - ); - } - - if (null !== $methods) { - foreach ($methods as $method) { - if (!preg_match('~[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*~', (string) $method)) { - throw new RuntimeException( - sprintf( - 'Cannot stub or mock method with invalid name "%s"', - $method - ) - ); - } - } - - if ($methods !== array_unique($methods)) { - throw new RuntimeException( - sprintf( - 'Cannot stub or mock using a method list that contains duplicates: "%s" (duplicate: "%s")', - implode(', ', $methods), - implode(', ', array_unique(array_diff_assoc($methods, array_unique($methods)))) - ) - ); - } - } - - if ($mockClassName !== '' && class_exists($mockClassName, false)) { - try { - $reflector = new ReflectionClass($mockClassName); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new RuntimeException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - if (!$reflector->implementsInterface(MockObject::class)) { - throw new RuntimeException( - sprintf( - 'Class "%s" already exists.', - $mockClassName - ) - ); - } - } - - if (!$callOriginalConstructor && $callOriginalMethods) { - throw new RuntimeException( - 'Proxying to original methods requires invoking the original constructor' - ); - } - - $mock = $this->generate( - $type, - $methods, - $mockClassName, - $callOriginalClone, - $callAutoload, - $cloneArguments, - $callOriginalMethods - ); - - return $this->getObject( - $mock, - $type, - $callOriginalConstructor, - $callAutoload, - $arguments, - $callOriginalMethods, - $proxyTarget, - $returnValueGeneration - ); - } - - /** - * Returns a mock object for the specified abstract class with all abstract - * methods of the class mocked. - * - * Concrete methods to mock can be specified with the $mockedMethods parameter. - * - * @psalm-template RealInstanceType of object - * @psalm-param class-string $originalClassName - * @psalm-return MockObject&RealInstanceType - * - * @throws RuntimeException - */ - public function getMockForAbstractClass(string $originalClassName, array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $callAutoload = true, array $mockedMethods = null, bool $cloneArguments = true): MockObject - { - if (class_exists($originalClassName, $callAutoload) || - interface_exists($originalClassName, $callAutoload)) { - try { - $reflector = new ReflectionClass($originalClassName); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new RuntimeException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - $methods = $mockedMethods; - - foreach ($reflector->getMethods() as $method) { - if ($method->isAbstract() && !in_array($method->getName(), $methods ?? [], true)) { - $methods[] = $method->getName(); - } - } - - if (empty($methods)) { - $methods = null; - } - - return $this->getMock( - $originalClassName, - $methods, - $arguments, - $mockClassName, - $callOriginalConstructor, - $callOriginalClone, - $callAutoload, - $cloneArguments - ); - } - - throw new RuntimeException( - sprintf('Class "%s" does not exist.', $originalClassName) - ); - } - - /** - * Returns a mock object for the specified trait with all abstract methods - * of the trait mocked. Concrete methods to mock can be specified with the - * `$mockedMethods` parameter. - * - * @throws RuntimeException - */ - public function getMockForTrait(string $traitName, array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $callAutoload = true, array $mockedMethods = null, bool $cloneArguments = true): MockObject - { - if (!trait_exists($traitName, $callAutoload)) { - throw new RuntimeException( - sprintf( - 'Trait "%s" does not exist.', - $traitName - ) - ); - } - - $className = $this->generateClassName( - $traitName, - '', - 'Trait_' - ); - - $classTemplate = $this->getTemplate('trait_class.tpl'); - - $classTemplate->setVar( - [ - 'prologue' => 'abstract ', - 'class_name' => $className['className'], - 'trait_name' => $traitName, - ] - ); - - $mockTrait = new MockTrait($classTemplate->render(), $className['className']); - $mockTrait->generate(); - - return $this->getMockForAbstractClass($className['className'], $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments); - } - - /** - * Returns an object for the specified trait. - * - * @throws RuntimeException - */ - public function getObjectForTrait(string $traitName, string $traitClassName = '', bool $callAutoload = true, bool $callOriginalConstructor = false, array $arguments = []): object - { - if (!trait_exists($traitName, $callAutoload)) { - throw new RuntimeException( - sprintf( - 'Trait "%s" does not exist.', - $traitName - ) - ); - } - - $className = $this->generateClassName( - $traitName, - $traitClassName, - 'Trait_' - ); - - $classTemplate = $this->getTemplate('trait_class.tpl'); - - $classTemplate->setVar( - [ - 'prologue' => '', - 'class_name' => $className['className'], - 'trait_name' => $traitName, - ] - ); - - return $this->getObject( - new MockTrait( - $classTemplate->render(), - $className['className'] - ), - '', - $callOriginalConstructor, - $callAutoload, - $arguments - ); - } - - public function generate(string $type, array $methods = null, string $mockClassName = '', bool $callOriginalClone = true, bool $callAutoload = true, bool $cloneArguments = true, bool $callOriginalMethods = false): MockClass - { - if ($mockClassName !== '') { - return $this->generateMock( - $type, - $methods, - $mockClassName, - $callOriginalClone, - $callAutoload, - $cloneArguments, - $callOriginalMethods - ); - } - - $key = md5( - $type . - serialize($methods) . - serialize($callOriginalClone) . - serialize($cloneArguments) . - serialize($callOriginalMethods) - ); - - if (!isset(self::$cache[$key])) { - self::$cache[$key] = $this->generateMock( - $type, - $methods, - $mockClassName, - $callOriginalClone, - $callAutoload, - $cloneArguments, - $callOriginalMethods - ); - } - - return self::$cache[$key]; - } - - /** - * @throws RuntimeException - */ - public function generateClassFromWsdl(string $wsdlFile, string $className, array $methods = [], array $options = []): string - { - if (!extension_loaded('soap')) { - throw new RuntimeException( - 'The SOAP extension is required to generate a mock object from WSDL.' - ); - } - - $options = array_merge($options, ['cache_wsdl' => WSDL_CACHE_NONE]); - - try { - $client = new SoapClient($wsdlFile, $options); - $_methods = array_unique($client->__getFunctions()); - unset($client); - } catch (SoapFault $e) { - throw new RuntimeException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - - sort($_methods); - - $methodTemplate = $this->getTemplate('wsdl_method.tpl'); - $methodsBuffer = ''; - - foreach ($_methods as $method) { - preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\(/', $method, $matches, PREG_OFFSET_CAPTURE); - $lastFunction = array_pop($matches[0]); - $nameStart = $lastFunction[1]; - $nameEnd = $nameStart + strlen($lastFunction[0]) - 1; - $name = str_replace('(', '', $lastFunction[0]); - - if (empty($methods) || in_array($name, $methods, true)) { - $args = explode( - ',', - str_replace(')', '', substr($method, $nameEnd + 1)) - ); - - foreach (range(0, count($args) - 1) as $i) { - $args[$i] = substr($args[$i], strpos($args[$i], '$')); - } - - $methodTemplate->setVar( - [ - 'method_name' => $name, - 'arguments' => implode(', ', $args), - ] - ); - - $methodsBuffer .= $methodTemplate->render(); - } - } - - $optionsBuffer = '['; - - foreach ($options as $key => $value) { - $optionsBuffer .= $key . ' => ' . $value; - } - - $optionsBuffer .= ']'; - - $classTemplate = $this->getTemplate('wsdl_class.tpl'); - $namespace = ''; - - if (strpos($className, '\\') !== false) { - $parts = explode('\\', $className); - $className = array_pop($parts); - $namespace = 'namespace ' . implode('\\', $parts) . ';' . "\n\n"; - } - - $classTemplate->setVar( - [ - 'namespace' => $namespace, - 'class_name' => $className, - 'wsdl' => $wsdlFile, - 'options' => $optionsBuffer, - 'methods' => $methodsBuffer, - ] - ); - - return $classTemplate->render(); - } - - /** - * @throws RuntimeException - * - * @return string[] - */ - public function getClassMethods(string $className): array - { - try { - $class = new ReflectionClass($className); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new RuntimeException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - $methods = []; - - foreach ($class->getMethods() as $method) { - if ($method->isPublic() || $method->isAbstract()) { - $methods[] = $method->getName(); - } - } - - return $methods; - } - - /** - * @throws RuntimeException - * - * @return MockMethod[] - */ - public function mockClassMethods(string $className, bool $callOriginalMethods, bool $cloneArguments): array - { - try { - $class = new ReflectionClass($className); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new RuntimeException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - $methods = []; - - foreach ($class->getMethods() as $method) { - if (($method->isPublic() || $method->isAbstract()) && $this->canMockMethod($method)) { - $methods[] = MockMethod::fromReflection($method, $callOriginalMethods, $cloneArguments); - } - } - - return $methods; - } - - /** - * @throws RuntimeException - * - * @return MockMethod[] - */ - public function mockInterfaceMethods(string $interfaceName, bool $cloneArguments): array - { - try { - $class = new ReflectionClass($interfaceName); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new RuntimeException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - $methods = []; - - foreach ($class->getMethods() as $method) { - $methods[] = MockMethod::fromReflection($method, false, $cloneArguments); - } - - return $methods; - } - - /** - * @psalm-param class-string $interfaceName - * - * @return ReflectionMethod[] - */ - private function userDefinedInterfaceMethods(string $interfaceName): array - { - try { - // @codeCoverageIgnoreStart - $interface = new ReflectionClass($interfaceName); - } catch (ReflectionException $e) { - throw new RuntimeException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - $methods = []; - - foreach ($interface->getMethods() as $method) { - if (!$method->isUserDefined()) { - continue; - } - - $methods[] = $method; - } - - return $methods; - } - - private function getObject(MockType $mockClass, $type = '', bool $callOriginalConstructor = false, bool $callAutoload = false, array $arguments = [], bool $callOriginalMethods = false, object $proxyTarget = null, bool $returnValueGeneration = true) - { - $className = $mockClass->generate(); - - if ($callOriginalConstructor) { - if (count($arguments) === 0) { - $object = new $className; - } else { - try { - $class = new ReflectionClass($className); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new RuntimeException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - $object = $class->newInstanceArgs($arguments); - } - } else { - try { - $object = (new Instantiator)->instantiate($className); - } catch (InstantiatorException $exception) { - throw new RuntimeException($exception->getMessage()); - } - } - - if ($callOriginalMethods) { - if (!is_object($proxyTarget)) { - if (count($arguments) === 0) { - $proxyTarget = new $type; - } else { - try { - $class = new ReflectionClass($type); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new RuntimeException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - $proxyTarget = $class->newInstanceArgs($arguments); - } - } - - $object->__phpunit_setOriginalObject($proxyTarget); - } - - if ($object instanceof MockObject) { - $object->__phpunit_setReturnValueGeneration($returnValueGeneration); - } - - return $object; - } - - /** - * @throws RuntimeException - */ - private function generateMock(string $type, ?array $explicitMethods, string $mockClassName, bool $callOriginalClone, bool $callAutoload, bool $cloneArguments, bool $callOriginalMethods): MockClass - { - $classTemplate = $this->getTemplate('mocked_class.tpl'); - $additionalInterfaces = []; - $mockedCloneMethod = false; - $unmockedCloneMethod = false; - $isClass = false; - $isInterface = false; - $class = null; - $mockMethods = new MockMethodSet; - - $_mockClassName = $this->generateClassName( - $type, - $mockClassName, - 'Mock_' - ); - - if (class_exists($_mockClassName['fullClassName'], $callAutoload)) { - $isClass = true; - } elseif (interface_exists($_mockClassName['fullClassName'], $callAutoload)) { - $isInterface = true; - } - - if (!$isClass && !$isInterface) { - $prologue = 'class ' . $_mockClassName['originalClassName'] . "\n{\n}\n\n"; - - if (!empty($_mockClassName['namespaceName'])) { - $prologue = 'namespace ' . $_mockClassName['namespaceName'] . - " {\n\n" . $prologue . "}\n\n" . - "namespace {\n\n"; - - $epilogue = "\n\n}"; - } - - $mockedCloneMethod = true; - } else { - try { - $class = new ReflectionClass($_mockClassName['fullClassName']); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new RuntimeException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - if ($class->isFinal()) { - throw new RuntimeException( - sprintf( - 'Class "%s" is declared "final" and cannot be mocked.', - $_mockClassName['fullClassName'] - ) - ); - } - - // @see https://github.com/sebastianbergmann/phpunit/issues/2995 - if ($isInterface && $class->implementsInterface(Throwable::class)) { - $actualClassName = Exception::class; - $additionalInterfaces[] = $class->getName(); - $isInterface = false; - - try { - $class = new ReflectionClass($actualClassName); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new RuntimeException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - foreach ($this->userDefinedInterfaceMethods($_mockClassName['fullClassName']) as $method) { - $methodName = $method->getName(); - - if ($class->hasMethod($methodName)) { - try { - $classMethod = $class->getMethod($methodName); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new RuntimeException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - if (!$this->canMockMethod($classMethod)) { - continue; - } - } - - $mockMethods->addMethods( - MockMethod::fromReflection($method, $callOriginalMethods, $cloneArguments) - ); - } - - $_mockClassName = $this->generateClassName( - $actualClassName, - $_mockClassName['className'], - 'Mock_' - ); - } - - // @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/103 - if ($isInterface && $class->implementsInterface(Traversable::class) && - !$class->implementsInterface(Iterator::class) && - !$class->implementsInterface(IteratorAggregate::class)) { - $additionalInterfaces[] = Iterator::class; - - $mockMethods->addMethods( - ...$this->mockClassMethods(Iterator::class, $callOriginalMethods, $cloneArguments) - ); - } - - if ($class->hasMethod('__clone')) { - try { - $cloneMethod = $class->getMethod('__clone'); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new RuntimeException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - if (!$cloneMethod->isFinal()) { - if ($callOriginalClone && !$isInterface) { - $unmockedCloneMethod = true; - } else { - $mockedCloneMethod = true; - } - } - } else { - $mockedCloneMethod = true; - } - } - - if ($isClass && $explicitMethods === []) { - $mockMethods->addMethods( - ...$this->mockClassMethods($_mockClassName['fullClassName'], $callOriginalMethods, $cloneArguments) - ); - } - - if ($isInterface && ($explicitMethods === [] || $explicitMethods === null)) { - $mockMethods->addMethods( - ...$this->mockInterfaceMethods($_mockClassName['fullClassName'], $cloneArguments) - ); - } - - if (is_array($explicitMethods)) { - foreach ($explicitMethods as $methodName) { - if ($class !== null && $class->hasMethod($methodName)) { - try { - $method = $class->getMethod($methodName); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new RuntimeException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - if ($this->canMockMethod($method)) { - $mockMethods->addMethods( - MockMethod::fromReflection($method, $callOriginalMethods, $cloneArguments) - ); - } - } else { - $mockMethods->addMethods( - MockMethod::fromName( - $_mockClassName['fullClassName'], - $methodName, - $cloneArguments - ) - ); - } - } - } - - $mockedMethods = ''; - $configurable = []; - - foreach ($mockMethods->asArray() as $mockMethod) { - $mockedMethods .= $mockMethod->generateCode(); - $configurable[] = new ConfigurableMethod($mockMethod->getName(), $mockMethod->getReturnType()); - } - - $method = ''; - - if (!$mockMethods->hasMethod('method') && (!isset($class) || !$class->hasMethod('method'))) { - $method = PHP_EOL . ' use \PHPUnit\Framework\MockObject\Method;'; - } - - $cloneTrait = ''; - - if ($mockedCloneMethod) { - $cloneTrait = PHP_EOL . ' use \PHPUnit\Framework\MockObject\MockedCloneMethod;'; - } - - if ($unmockedCloneMethod) { - $cloneTrait = PHP_EOL . ' use \PHPUnit\Framework\MockObject\UnmockedCloneMethod;'; - } - - $classTemplate->setVar( - [ - 'prologue' => $prologue ?? '', - 'epilogue' => $epilogue ?? '', - 'class_declaration' => $this->generateMockClassDeclaration( - $_mockClassName, - $isInterface, - $additionalInterfaces - ), - 'clone' => $cloneTrait, - 'mock_class_name' => $_mockClassName['className'], - 'mocked_methods' => $mockedMethods, - 'method' => $method, - ] - ); - - return new MockClass( - $classTemplate->render(), - $_mockClassName['className'], - $configurable - ); - } - - private function generateClassName(string $type, string $className, string $prefix): array - { - if ($type[0] === '\\') { - $type = substr($type, 1); - } - - $classNameParts = explode('\\', $type); - - if (count($classNameParts) > 1) { - $type = array_pop($classNameParts); - $namespaceName = implode('\\', $classNameParts); - $fullClassName = $namespaceName . '\\' . $type; - } else { - $namespaceName = ''; - $fullClassName = $type; - } - - if ($className === '') { - do { - $className = $prefix . $type . '_' . - substr(md5((string) mt_rand()), 0, 8); - } while (class_exists($className, false)); - } - - return [ - 'className' => $className, - 'originalClassName' => $type, - 'fullClassName' => $fullClassName, - 'namespaceName' => $namespaceName, - ]; - } - - private function generateMockClassDeclaration(array $mockClassName, bool $isInterface, array $additionalInterfaces = []): string - { - $buffer = 'class '; - - $additionalInterfaces[] = MockObject::class; - $interfaces = implode(', ', $additionalInterfaces); - - if ($isInterface) { - $buffer .= sprintf( - '%s implements %s', - $mockClassName['className'], - $interfaces - ); - - if (!in_array($mockClassName['originalClassName'], $additionalInterfaces, true)) { - $buffer .= ', '; - - if (!empty($mockClassName['namespaceName'])) { - $buffer .= $mockClassName['namespaceName'] . '\\'; - } - - $buffer .= $mockClassName['originalClassName']; - } - } else { - $buffer .= sprintf( - '%s extends %s%s implements %s', - $mockClassName['className'], - !empty($mockClassName['namespaceName']) ? $mockClassName['namespaceName'] . '\\' : '', - $mockClassName['originalClassName'], - $interfaces - ); - } - - return $buffer; - } - - private function canMockMethod(ReflectionMethod $method): bool - { - return !($this->isConstructor($method) || $method->isFinal() || $method->isPrivate() || $this->isMethodNameExcluded($method->getName())); - } - - private function isMethodNameExcluded(string $name): bool - { - return isset(self::EXCLUDED_METHOD_NAMES[$name]); - } - - private function getTemplate(string $template): Template - { - $filename = __DIR__ . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR . $template; - - if (!isset(self::$templates[$filename])) { - self::$templates[$filename] = new Template($filename); - } - - return self::$templates[$filename]; - } - - /** - * @see https://github.com/sebastianbergmann/phpunit/issues/4139#issuecomment-605409765 - */ - private function isConstructor(ReflectionMethod $method): bool - { - $methodName = strtolower($method->getName()); - - if ($methodName === '__construct') { - return true; - } - - if (PHP_MAJOR_VERSION >= 8) { - return false; - } - - $className = strtolower($method->getDeclaringClass()->getName()); - - return $methodName === $className; - } -} diff --git a/src/Framework/MockObject/Generator/DoubledClass.php b/src/Framework/MockObject/Generator/DoubledClass.php new file mode 100644 index 00000000000..3d88c72e331 --- /dev/null +++ b/src/Framework/MockObject/Generator/DoubledClass.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +use function class_exists; +use PHPUnit\Framework\MockObject\ConfigurableMethod; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DoubledClass +{ + private string $classCode; + + /** + * @var class-string + */ + private string $mockName; + + /** + * @var list + */ + private array $configurableMethods; + + /** + * @param class-string $mockName + * @param list $configurableMethods + */ + public function __construct(string $classCode, string $mockName, array $configurableMethods) + { + $this->classCode = $classCode; + $this->mockName = $mockName; + $this->configurableMethods = $configurableMethods; + } + + /** + * @return class-string + */ + public function generate(): string + { + if (!class_exists($this->mockName, false)) { + eval($this->classCode); + } + + return $this->mockName; + } + + public function classCode(): string + { + return $this->classCode; + } + + /** + * @return list + */ + public function configurableMethods(): array + { + return $this->configurableMethods; + } +} diff --git a/src/Framework/MockObject/Generator/DoubledMethod.php b/src/Framework/MockObject/Generator/DoubledMethod.php new file mode 100644 index 00000000000..09006b257c2 --- /dev/null +++ b/src/Framework/MockObject/Generator/DoubledMethod.php @@ -0,0 +1,395 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +use function array_key_exists; +use function assert; +use function count; +use function explode; +use function implode; +use function is_object; +use function is_string; +use function preg_match; +use function preg_replace; +use function str_contains; +use function strlen; +use function strpos; +use function substr; +use function substr_count; +use function trim; +use function var_export; +use ReflectionMethod; +use ReflectionParameter; +use SebastianBergmann\Type\ReflectionMapper; +use SebastianBergmann\Type\Type; +use SebastianBergmann\Type\UnknownType; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class DoubledMethod +{ + use TemplateLoader; + + /** + * @var class-string + */ + private readonly string $className; + + /** + * @var non-empty-string + */ + private readonly string $methodName; + private readonly string $modifier; + private readonly string $argumentsForDeclaration; + private readonly string $argumentsForCall; + private readonly Type $returnType; + private readonly string $reference; + private readonly bool $static; + private readonly ?string $deprecation; + + /** + * @var array + */ + private readonly array $defaultParameterValues; + + /** + * @var non-negative-int + */ + private readonly int $numberOfParameters; + + /** + * @throws ReflectionException + * @throws RuntimeException + */ + public static function fromReflection(ReflectionMethod $method): self + { + if ($method->isPrivate()) { + $modifier = 'private'; + } elseif ($method->isProtected()) { + $modifier = 'protected'; + } else { + $modifier = 'public'; + } + + if ($method->isStatic()) { + $modifier .= ' static'; + } + + if ($method->returnsReference()) { + $reference = '&'; + } else { + $reference = ''; + } + + $docComment = $method->getDocComment(); + + if (is_string($docComment) && + preg_match('#\*[ \t]*+@deprecated[ \t]*+(.*?)\r?+\n[ \t]*+\*(?:[ \t]*+@|/$)#s', $docComment, $deprecation) > 0 + ) { + $deprecation = trim(preg_replace('#[ \t]*\r?\n[ \t]*+\*[ \t]*+#', ' ', $deprecation[1])); + } else { + $deprecation = null; + } + + return new self( + $method->getDeclaringClass()->getName(), + $method->getName(), + $modifier, + self::methodParametersForDeclaration($method), + self::methodParametersForCall($method), + self::methodParametersDefaultValues($method), + count($method->getParameters()), + (new ReflectionMapper)->fromReturnType($method), + $reference, + $method->isStatic(), + $deprecation, + ); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public static function fromName(string $className, string $methodName): self + { + return new self( + $className, + $methodName, + 'public', + '', + '', + [], + 0, + new UnknownType, + '', + false, + null, + ); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + * @param array $defaultParameterValues + * @param non-negative-int $numberOfParameters + */ + private function __construct(string $className, string $methodName, string $modifier, string $argumentsForDeclaration, string $argumentsForCall, array $defaultParameterValues, int $numberOfParameters, Type $returnType, string $reference, bool $static, ?string $deprecation) + { + $this->className = $className; + $this->methodName = $methodName; + $this->modifier = $modifier; + $this->argumentsForDeclaration = $argumentsForDeclaration; + $this->argumentsForCall = $argumentsForCall; + $this->defaultParameterValues = $defaultParameterValues; + $this->numberOfParameters = $numberOfParameters; + $this->returnType = $returnType; + $this->reference = $reference; + $this->static = $static; + $this->deprecation = $deprecation; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } + + /** + * @throws RuntimeException + */ + public function generateCode(): string + { + if ($this->static) { + $templateFile = 'doubled_static_method.tpl'; + } else { + $templateFile = 'doubled_method.tpl'; + } + + $deprecation = $this->deprecation; + $returnResult = ''; + + if (!$this->returnType->isNever() && !$this->returnType->isVoid()) { + $returnResult = <<<'EOT' + + + return $__phpunit_result; +EOT; + } + + if (null !== $this->deprecation) { + $deprecation = "The {$this->className}::{$this->methodName} method is deprecated ({$this->deprecation})."; + $deprecationTemplate = $this->loadTemplate('deprecation.tpl'); + + $deprecationTemplate->setVar( + [ + 'deprecation' => var_export($deprecation, true), + ], + ); + + $deprecation = $deprecationTemplate->render(); + } + + $template = $this->loadTemplate($templateFile); + + $argumentsCount = 0; + + if (str_contains($this->argumentsForCall, '...')) { + $argumentsCount = null; + } elseif ($this->argumentsForCall !== '') { + $argumentsCount = substr_count($this->argumentsForCall, ',') + 1; + } + + $returnDeclaration = ''; + $returnTypeAsString = $this->returnType->asString(); + + if ($returnTypeAsString !== '') { + $returnDeclaration = ': ' . $returnTypeAsString; + } + + $template->setVar( + [ + 'arguments_decl' => $this->argumentsForDeclaration, + 'arguments_call' => $this->argumentsForCall, + 'return_declaration' => $returnDeclaration, + 'return_type' => $this->returnType->asString(), + 'arguments_count' => (string) $argumentsCount, + 'class_name' => $this->className, + 'method_name' => $this->methodName, + 'modifier' => $this->modifier, + 'reference' => $this->reference, + 'deprecation' => $deprecation, + 'return_result' => $returnResult, + ], + ); + + return $template->render(); + } + + public function returnType(): Type + { + return $this->returnType; + } + + /** + * @return array + */ + public function defaultParameterValues(): array + { + return $this->defaultParameterValues; + } + + /** + * @return non-negative-int + */ + public function numberOfParameters(): int + { + return $this->numberOfParameters; + } + + /** + * Returns the parameters of a function or method. + * + * @throws RuntimeException + */ + private static function methodParametersForDeclaration(ReflectionMethod $method): string + { + $parameters = []; + $types = (new ReflectionMapper)->fromParameterTypes($method); + + foreach ($method->getParameters() as $i => $parameter) { + $name = '$' . $parameter->getName(); + + /* Note: PHP extensions may use empty names for reference arguments + * or "..." for methods taking a variable number of arguments. + */ + if ($name === '$' || $name === '$...') { + $name = '$arg' . $i; + } + + $default = ''; + $reference = ''; + $typeDeclaration = ''; + + assert(array_key_exists($i, $types)); + + if (!$types[$i]->type()->isUnknown()) { + $typeDeclaration = $types[$i]->type()->asString() . ' '; + } + + if ($parameter->isPassedByReference()) { + $reference = '&'; + } + + if ($parameter->isVariadic()) { + $name = '...' . $name; + } elseif ($parameter->isDefaultValueAvailable()) { + $default = ' = ' . self::exportDefaultValue($parameter); + } elseif ($parameter->isOptional()) { + $default = ' = null'; + } + + $parameters[] = $typeDeclaration . $reference . $name . $default; + } + + return implode(', ', $parameters); + } + + /** + * Returns the parameters of a function or method. + * + * @throws ReflectionException + */ + private static function methodParametersForCall(ReflectionMethod $method): string + { + $parameters = []; + + foreach ($method->getParameters() as $i => $parameter) { + $name = '$' . $parameter->getName(); + + /* Note: PHP extensions may use empty names for reference arguments + * or "..." for methods taking a variable number of arguments. + */ + if ($name === '$' || $name === '$...') { + $name = '$arg' . $i; + } + + if ($parameter->isVariadic()) { + continue; + } + + if ($parameter->isPassedByReference()) { + $parameters[] = '&' . $name; + } else { + $parameters[] = $name; + } + } + + return implode(', ', $parameters); + } + + /** + * @throws ReflectionException + */ + private static function exportDefaultValue(ReflectionParameter $parameter): string + { + try { + $defaultValue = $parameter->getDefaultValue(); + + if (!is_object($defaultValue)) { + return var_export($defaultValue, true); + } + + $parameterAsString = $parameter->__toString(); + + return explode( + ' = ', + substr( + substr( + $parameterAsString, + strpos($parameterAsString, ' ') + strlen(' '), + ), + 0, + -2, + ), + )[1]; + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + } + + /** + * @return array + */ + private static function methodParametersDefaultValues(ReflectionMethod $method): array + { + $result = []; + + foreach ($method->getParameters() as $i => $parameter) { + if (!$parameter->isDefaultValueAvailable()) { + continue; + } + + $result[$i] = $parameter->getDefaultValue(); + } + + return $result; + } +} diff --git a/src/Framework/MockObject/Generator/DoubledMethodSet.php b/src/Framework/MockObject/Generator/DoubledMethodSet.php new file mode 100644 index 00000000000..6a2d29f4f7d --- /dev/null +++ b/src/Framework/MockObject/Generator/DoubledMethodSet.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +use function array_key_exists; +use function array_values; +use function strtolower; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class DoubledMethodSet +{ + /** + * @var array + */ + private array $methods = []; + + public function addMethods(DoubledMethod ...$methods): void + { + foreach ($methods as $method) { + $this->methods[strtolower($method->methodName())] = $method; + } + } + + /** + * @return list + */ + public function asArray(): array + { + return array_values($this->methods); + } + + public function hasMethod(string $methodName): bool + { + return array_key_exists(strtolower($methodName), $this->methods); + } +} diff --git a/src/Framework/MockObject/Generator/Exception/ClassIsEnumerationException.php b/src/Framework/MockObject/Generator/Exception/ClassIsEnumerationException.php new file mode 100644 index 00000000000..e2cde18b68b --- /dev/null +++ b/src/Framework/MockObject/Generator/Exception/ClassIsEnumerationException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ClassIsEnumerationException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(string $className) + { + parent::__construct( + sprintf( + 'Class "%s" is an enumeration and cannot be doubled', + $className, + ), + ); + } +} diff --git a/src/Framework/MockObject/Generator/Exception/ClassIsFinalException.php b/src/Framework/MockObject/Generator/Exception/ClassIsFinalException.php new file mode 100644 index 00000000000..f10100b90de --- /dev/null +++ b/src/Framework/MockObject/Generator/Exception/ClassIsFinalException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ClassIsFinalException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(string $className) + { + parent::__construct( + sprintf( + 'Class "%s" is declared "final" and cannot be doubled', + $className, + ), + ); + } +} diff --git a/src/Framework/MockObject/Generator/Exception/DuplicateMethodException.php b/src/Framework/MockObject/Generator/Exception/DuplicateMethodException.php new file mode 100644 index 00000000000..b18ed4f561e --- /dev/null +++ b/src/Framework/MockObject/Generator/Exception/DuplicateMethodException.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +use function array_diff_assoc; +use function array_unique; +use function implode; +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class DuplicateMethodException extends \PHPUnit\Framework\Exception implements Exception +{ + /** + * @param list $methods + */ + public function __construct(array $methods) + { + parent::__construct( + sprintf( + 'Cannot double using a method list that contains duplicates: "%s" (duplicate: "%s")', + implode(', ', $methods), + implode(', ', array_unique(array_diff_assoc($methods, array_unique($methods)))), + ), + ); + } +} diff --git a/src/Framework/MockObject/Generator/Exception/Exception.php b/src/Framework/MockObject/Generator/Exception/Exception.php new file mode 100644 index 00000000000..8d62606fca2 --- /dev/null +++ b/src/Framework/MockObject/Generator/Exception/Exception.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +use PHPUnit\Framework\MockObject\Exception as BaseException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface Exception extends BaseException +{ +} diff --git a/src/Framework/MockObject/Generator/Exception/InvalidMethodNameException.php b/src/Framework/MockObject/Generator/Exception/InvalidMethodNameException.php new file mode 100644 index 00000000000..32296ce3940 --- /dev/null +++ b/src/Framework/MockObject/Generator/Exception/InvalidMethodNameException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidMethodNameException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(string $method) + { + parent::__construct( + sprintf( + 'Cannot double method with invalid name "%s"', + $method, + ), + ); + } +} diff --git a/src/Framework/MockObject/Generator/Exception/MethodNamedMethodException.php b/src/Framework/MockObject/Generator/Exception/MethodNamedMethodException.php new file mode 100644 index 00000000000..78586fe1067 --- /dev/null +++ b/src/Framework/MockObject/Generator/Exception/MethodNamedMethodException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MethodNamedMethodException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct() + { + parent::__construct('Doubling interfaces (or classes) that have a method named "method" is not supported.'); + } +} diff --git a/src/Framework/MockObject/Generator/Exception/NameAlreadyInUseException.php b/src/Framework/MockObject/Generator/Exception/NameAlreadyInUseException.php new file mode 100644 index 00000000000..eec81c065d5 --- /dev/null +++ b/src/Framework/MockObject/Generator/Exception/NameAlreadyInUseException.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NameAlreadyInUseException extends \PHPUnit\Framework\Exception implements Exception +{ + /** + * @param class-string|trait-string $name + */ + public function __construct(string $name) + { + parent::__construct( + sprintf( + 'The name "%s" is already in use', + $name, + ), + ); + } +} diff --git a/src/Framework/MockObject/Generator/Exception/ReflectionException.php b/src/Framework/MockObject/Generator/Exception/ReflectionException.php new file mode 100644 index 00000000000..f4a84f18e6e --- /dev/null +++ b/src/Framework/MockObject/Generator/Exception/ReflectionException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ReflectionException extends \PHPUnit\Framework\Exception implements Exception +{ +} diff --git a/src/Framework/MockObject/Generator/Exception/RuntimeException.php b/src/Framework/MockObject/Generator/Exception/RuntimeException.php new file mode 100644 index 00000000000..eed41c37a33 --- /dev/null +++ b/src/Framework/MockObject/Generator/Exception/RuntimeException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class RuntimeException extends \PHPUnit\Framework\Exception implements Exception +{ +} diff --git a/src/Framework/MockObject/Generator/Exception/UnknownInterfaceException.php b/src/Framework/MockObject/Generator/Exception/UnknownInterfaceException.php new file mode 100644 index 00000000000..95f03e73291 --- /dev/null +++ b/src/Framework/MockObject/Generator/Exception/UnknownInterfaceException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class UnknownInterfaceException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(string $interfaceName) + { + parent::__construct( + sprintf( + 'Interface "%s" does not exist', + $interfaceName, + ), + ); + } +} diff --git a/src/Framework/MockObject/Generator/Exception/UnknownTypeException.php b/src/Framework/MockObject/Generator/Exception/UnknownTypeException.php new file mode 100644 index 00000000000..cd1e1e072b1 --- /dev/null +++ b/src/Framework/MockObject/Generator/Exception/UnknownTypeException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class UnknownTypeException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(string $type) + { + parent::__construct( + sprintf( + 'Class or interface "%s" does not exist', + $type, + ), + ); + } +} diff --git a/src/Framework/MockObject/Generator/Generator.php b/src/Framework/MockObject/Generator/Generator.php new file mode 100644 index 00000000000..1794d327ea3 --- /dev/null +++ b/src/Framework/MockObject/Generator/Generator.php @@ -0,0 +1,887 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +use const PHP_EOL; +use const PHP_VERSION; +use function array_merge; +use function array_pop; +use function array_unique; +use function assert; +use function class_exists; +use function count; +use function explode; +use function implode; +use function in_array; +use function interface_exists; +use function is_array; +use function md5; +use function mt_rand; +use function preg_match; +use function serialize; +use function sort; +use function sprintf; +use function substr; +use function trait_exists; +use function version_compare; +use Exception; +use Iterator; +use IteratorAggregate; +use PHPUnit\Framework\MockObject\ConfigurableMethod; +use PHPUnit\Framework\MockObject\DoubledCloneMethod; +use PHPUnit\Framework\MockObject\Method; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\MockObject\MockObjectApi; +use PHPUnit\Framework\MockObject\MockObjectInternal; +use PHPUnit\Framework\MockObject\ProxiedCloneMethod; +use PHPUnit\Framework\MockObject\Stub; +use PHPUnit\Framework\MockObject\StubApi; +use PHPUnit\Framework\MockObject\StubInternal; +use PHPUnit\Framework\MockObject\TestDoubleState; +use PropertyHookType; +use ReflectionClass; +use ReflectionMethod; +use ReflectionObject; +use SebastianBergmann\Type\ReflectionMapper; +use SebastianBergmann\Type\Type; +use Throwable; +use Traversable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Generator +{ + use TemplateLoader; + + /** + * @var null|non-empty-array + */ + private static ?array $excludedMethodNames = null; + + /** + * @var array + */ + private static array $cache = []; + + /** + * Returns a test double for the specified class. + * + * @param class-string $type + * @param ?list $methods + * @param array $arguments + * + * @throws ClassIsEnumerationException + * @throws ClassIsFinalException + * @throws DuplicateMethodException + * @throws InvalidMethodNameException + * @throws NameAlreadyInUseException + * @throws ReflectionException + * @throws RuntimeException + * @throws UnknownTypeException + */ + public function testDouble(string $type, bool $mockObject, ?array $methods = [], array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $returnValueGeneration = true): MockObject|Stub + { + if ($type === Traversable::class) { + $type = Iterator::class; + } + + $this->ensureKnownType($type); + $this->ensureValidMethods($methods); + $this->ensureNameForTestDoubleClassIsAvailable($mockClassName); + + $mock = $this->generate( + $type, + $mockObject, + $methods, + $mockClassName, + $callOriginalClone, + ); + + $object = $this->instantiate( + $mock, + $callOriginalConstructor, + $arguments, + $returnValueGeneration, + ); + + assert($object instanceof $type); + + if ($mockObject) { + assert($object instanceof MockObject); + } else { + assert($object instanceof Stub); + } + + return $object; + } + + /** + * @param list $interfaces + * + * @throws RuntimeException + * @throws UnknownInterfaceException + */ + public function testDoubleForInterfaceIntersection(array $interfaces, bool $mockObject, bool $returnValueGeneration = true): MockObject|Stub + { + if (count($interfaces) < 2) { + throw new RuntimeException('At least two interfaces must be specified'); + } + + foreach ($interfaces as $interface) { + if (!interface_exists($interface)) { + throw new UnknownInterfaceException($interface); + } + } + + sort($interfaces); + + $methods = []; + + foreach ($interfaces as $interface) { + $methods = array_merge($methods, $this->namesOfMethodsIn($interface)); + } + + if (count(array_unique($methods)) < count($methods)) { + throw new RuntimeException('Interfaces must not declare the same method'); + } + + $unqualifiedNames = []; + + foreach ($interfaces as $interface) { + $parts = explode('\\', $interface); + $unqualifiedNames[] = array_pop($parts); + } + + sort($unqualifiedNames); + + do { + $intersectionName = sprintf( + 'Intersection_%s_%s', + implode('_', $unqualifiedNames), + substr(md5((string) mt_rand()), 0, 8), + ); + } while (interface_exists($intersectionName, false)); + + $template = $this->loadTemplate('intersection.tpl'); + + $template->setVar( + [ + 'intersection' => $intersectionName, + 'interfaces' => implode(', ', $interfaces), + ], + ); + + eval($template->render()); + + assert(interface_exists($intersectionName)); + + return $this->testDouble( + $intersectionName, + $mockObject, + returnValueGeneration: $returnValueGeneration, + ); + } + + /** + * @param class-string $type + * @param ?list $methods + * + * @throws ClassIsEnumerationException + * @throws ClassIsFinalException + * @throws ReflectionException + * @throws RuntimeException + * + * @todo This method is only public because it is used to test generated code in PHPT tests + * + * @see https://github.com/sebastianbergmann/phpunit/issues/5476 + */ + public function generate(string $type, bool $mockObject, ?array $methods = null, string $mockClassName = '', bool $callOriginalClone = true): DoubledClass + { + if ($mockClassName !== '') { + return $this->generateCodeForTestDoubleClass( + $type, + $mockObject, + $methods, + $mockClassName, + $callOriginalClone, + ); + } + + $key = md5( + $type . + ($mockObject ? 'MockObject' : 'TestStub') . + serialize($methods) . + serialize($callOriginalClone), + ); + + if (!isset(self::$cache[$key])) { + self::$cache[$key] = $this->generateCodeForTestDoubleClass( + $type, + $mockObject, + $methods, + $mockClassName, + $callOriginalClone, + ); + } + + return self::$cache[$key]; + } + + /** + * @param class-string $className + * + * @throws ReflectionException + * + * @return list + */ + private function mockClassMethods(string $className): array + { + $class = $this->reflectClass($className); + $methods = []; + + foreach ($class->getMethods() as $method) { + if (($method->isPublic() || $method->isAbstract()) && $this->canMethodBeDoubled($method)) { + $methods[] = DoubledMethod::fromReflection($method); + } + } + + return $methods; + } + + /** + * @param class-string $interfaceName + * + * @throws ReflectionException + * + * @return list + */ + private function userDefinedInterfaceMethods(string $interfaceName): array + { + $interface = $this->reflectClass($interfaceName); + $methods = []; + + foreach ($interface->getMethods() as $method) { + if (!$method->isUserDefined()) { + continue; + } + + $methods[] = $method; + } + + return $methods; + } + + /** + * @param array $arguments + * + * @throws ReflectionException + * @throws RuntimeException + */ + private function instantiate(DoubledClass $mockClass, bool $callOriginalConstructor = false, array $arguments = [], bool $returnValueGeneration = true): object + { + $className = $mockClass->generate(); + + try { + $object = new ReflectionClass($className)->newInstanceWithoutConstructor(); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + // @codeCoverageIgnoreEnd + } + + $reflector = new ReflectionObject($object); + + /** + * @noinspection PhpUnhandledExceptionInspection + */ + $reflector->getProperty('__phpunit_state')->setValue( + $object, + new TestDoubleState($mockClass->configurableMethods(), $returnValueGeneration), + ); + + if ($callOriginalConstructor && $reflector->getConstructor() !== null) { + try { + $reflector->getConstructor()->invokeArgs($object, $arguments); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + // @codeCoverageIgnoreEnd + } + } + + return $object; + } + + /** + * @param class-string $type + * @param ?list $explicitMethods + * + * @throws ClassIsEnumerationException + * @throws ClassIsFinalException + * @throws MethodNamedMethodException + * @throws ReflectionException + * @throws RuntimeException + */ + private function generateCodeForTestDoubleClass(string $type, bool $mockObject, ?array $explicitMethods, string $mockClassName, bool $callOriginalClone): DoubledClass + { + $classTemplate = $this->loadTemplate('test_double_class.tpl'); + $additionalInterfaces = []; + $doubledCloneMethod = false; + $proxiedCloneMethod = false; + $isClass = false; + $isReadonly = false; + $isInterface = false; + $mockMethods = new DoubledMethodSet; + $testDoubleClassPrefix = $mockObject ? 'MockObject_' : 'TestStub_'; + + $_mockClassName = $this->generateClassName( + $type, + $mockClassName, + $testDoubleClassPrefix, + ); + + if (class_exists($_mockClassName['fullClassName'])) { + $isClass = true; + } elseif (interface_exists($_mockClassName['fullClassName'])) { + $isInterface = true; + } + + $class = $this->reflectClass($_mockClassName['fullClassName']); + + if ($class->isEnum()) { + throw new ClassIsEnumerationException($_mockClassName['fullClassName']); + } + + if ($class->isFinal()) { + throw new ClassIsFinalException($_mockClassName['fullClassName']); + } + + if ($class->isReadOnly()) { + $isReadonly = true; + } + + // @see https://github.com/sebastianbergmann/phpunit/issues/2995 + if ($isInterface && $class->implementsInterface(Throwable::class)) { + $actualClassName = Exception::class; + $additionalInterfaces[] = $class->getName(); + $isInterface = false; + $class = $this->reflectClass($actualClassName); + + foreach ($this->userDefinedInterfaceMethods($_mockClassName['fullClassName']) as $method) { + $methodName = $method->getName(); + + if ($class->hasMethod($methodName)) { + $classMethod = $class->getMethod($methodName); + + if (!$this->canMethodBeDoubled($classMethod)) { + continue; + } + } + + $mockMethods->addMethods( + DoubledMethod::fromReflection($method), + ); + } + + $_mockClassName = $this->generateClassName( + $actualClassName, + $_mockClassName['className'], + $testDoubleClassPrefix, + ); + } + + // @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/103 + if ($isInterface && $class->implementsInterface(Traversable::class) && + !$class->implementsInterface(Iterator::class) && + !$class->implementsInterface(IteratorAggregate::class)) { + $additionalInterfaces[] = Iterator::class; + + $mockMethods->addMethods( + ...$this->mockClassMethods(Iterator::class), + ); + } + + if ($class->hasMethod('__clone')) { + $cloneMethod = $class->getMethod('__clone'); + + if (!$cloneMethod->isFinal()) { + if ($callOriginalClone && !$isInterface) { + $proxiedCloneMethod = true; + } else { + $doubledCloneMethod = true; + } + } + } else { + $doubledCloneMethod = true; + } + + if ($isClass && $explicitMethods === []) { + $mockMethods->addMethods( + ...$this->mockClassMethods($_mockClassName['fullClassName']), + ); + } + + if ($isInterface && ($explicitMethods === [] || $explicitMethods === null)) { + $mockMethods->addMethods( + ...$this->interfaceMethods($_mockClassName['fullClassName']), + ); + } + + if (is_array($explicitMethods)) { + foreach ($explicitMethods as $methodName) { + if ($class->hasMethod($methodName)) { + $method = $class->getMethod($methodName); + + if ($this->canMethodBeDoubled($method)) { + $mockMethods->addMethods( + DoubledMethod::fromReflection($method), + ); + } + } else { + $mockMethods->addMethods( + DoubledMethod::fromName( + $_mockClassName['fullClassName'], + $methodName, + ), + ); + } + } + } + + $propertiesWithHooks = $this->properties($class); + $configurableMethods = $this->configurableMethods($mockMethods, $propertiesWithHooks); + + $mockedMethods = ''; + + foreach ($mockMethods->asArray() as $mockMethod) { + $mockedMethods .= $mockMethod->generateCode(); + } + + /** @var trait-string[] $traits */ + $traits = [StubApi::class]; + + if ($mockObject) { + $traits[] = MockObjectApi::class; + } + + if ($mockMethods->hasMethod('method') || $class->hasMethod('method')) { + throw new MethodNamedMethodException; + } + + $traits[] = Method::class; + + if ($doubledCloneMethod) { + $traits[] = DoubledCloneMethod::class; + } elseif ($proxiedCloneMethod) { + $traits[] = ProxiedCloneMethod::class; + } + + $useStatements = ''; + + foreach ($traits as $trait) { + $useStatements .= sprintf( + ' use %s;' . PHP_EOL, + $trait, + ); + } + + unset($traits); + + $classTemplate->setVar( + [ + 'class_declaration' => $this->generateTestDoubleClassDeclaration( + $mockObject, + $_mockClassName, + $isInterface, + $additionalInterfaces, + $isReadonly, + ), + 'use_statements' => $useStatements, + 'mock_class_name' => $_mockClassName['className'], + 'methods' => $mockedMethods, + 'property_hooks' => (new HookedPropertyGenerator)->generate( + $_mockClassName['className'], + $propertiesWithHooks, + ), + ], + ); + + return new DoubledClass( + $classTemplate->render(), + $_mockClassName['className'], + $configurableMethods, + ); + } + + /** + * @param class-string $type + * + * @return array{className: class-string, originalClassName: class-string, fullClassName: class-string, namespaceName: string} + */ + private function generateClassName(string $type, string $className, string $prefix): array + { + if ($type[0] === '\\') { + $type = substr($type, 1); + } + + $classNameParts = explode('\\', $type); + + if (count($classNameParts) > 1) { + $type = array_pop($classNameParts); + $namespaceName = implode('\\', $classNameParts); + $fullClassName = $namespaceName . '\\' . $type; + } else { + $namespaceName = ''; + $fullClassName = $type; + } + + if ($className === '') { + do { + $className = $prefix . $type . '_' . + substr(md5((string) mt_rand()), 0, 8); + } while (class_exists($className, false)); + } + + return [ + 'className' => $className, + 'originalClassName' => $type, + 'fullClassName' => $fullClassName, + 'namespaceName' => $namespaceName, + ]; + } + + /** + * @param array{className: non-empty-string, originalClassName: non-empty-string, fullClassName: non-empty-string, namespaceName: string} $mockClassName + * @param list $additionalInterfaces + */ + private function generateTestDoubleClassDeclaration(bool $mockObject, array $mockClassName, bool $isInterface, array $additionalInterfaces, bool $isReadonly): string + { + if ($mockObject) { + $additionalInterfaces[] = MockObjectInternal::class; + } else { + $additionalInterfaces[] = StubInternal::class; + } + + if ($isReadonly) { + $buffer = 'readonly class '; + } else { + $buffer = 'class '; + } + + $interfaces = implode(', ', $additionalInterfaces); + + if ($isInterface) { + $buffer .= sprintf( + '%s implements %s', + $mockClassName['className'], + $interfaces, + ); + + if (!in_array($mockClassName['originalClassName'], $additionalInterfaces, true)) { + $buffer .= ', '; + + if ($mockClassName['namespaceName'] !== '') { + $buffer .= $mockClassName['namespaceName'] . '\\'; + } + + $buffer .= $mockClassName['originalClassName']; + } + } else { + $buffer .= sprintf( + '%s extends %s%s implements %s', + $mockClassName['className'], + $mockClassName['namespaceName'] !== '' ? $mockClassName['namespaceName'] . '\\' : '', + $mockClassName['originalClassName'], + $interfaces, + ); + } + + return $buffer; + } + + private function canMethodBeDoubled(ReflectionMethod $method): bool + { + if ($method->isConstructor()) { + return false; + } + + if ($method->isDestructor()) { + return false; + } + + if ($method->isFinal()) { + return false; + } + + if ($method->isPrivate()) { + return false; + } + + return !$this->isMethodNameExcluded($method->getName()); + } + + private function isMethodNameExcluded(string $name): bool + { + if (self::$excludedMethodNames === null) { + self::$excludedMethodNames = [ + '__CLASS__' => true, + '__DIR__' => true, + '__FILE__' => true, + '__FUNCTION__' => true, + '__LINE__' => true, + '__METHOD__' => true, + '__NAMESPACE__' => true, + '__TRAIT__' => true, + '__clone' => true, + '__halt_compiler' => true, + ]; + + if (version_compare(PHP_VERSION, '8.5', '>=')) { + self::$excludedMethodNames['__sleep'] = true; + self::$excludedMethodNames['__wakeup'] = true; + } + } + + return isset(self::$excludedMethodNames[$name]); + } + + /** + * @throws UnknownTypeException + */ + private function ensureKnownType(string $type): void + { + if (!class_exists($type) && !interface_exists($type)) { + throw new UnknownTypeException($type); + } + } + + /** + * @param ?list $methods + * + * @throws DuplicateMethodException + * @throws InvalidMethodNameException + */ + private function ensureValidMethods(?array $methods): void + { + if ($methods === null) { + return; + } + + foreach ($methods as $method) { + if (!preg_match('~[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*~', (string) $method)) { + throw new InvalidMethodNameException((string) $method); + } + } + + if ($methods !== array_unique($methods)) { + throw new DuplicateMethodException($methods); + } + } + + /** + * @throws NameAlreadyInUseException + * @throws ReflectionException + */ + private function ensureNameForTestDoubleClassIsAvailable(string $className): void + { + if ($className === '') { + return; + } + + if (class_exists($className, false) || + interface_exists($className, false) || + trait_exists($className, false)) { + throw new NameAlreadyInUseException($className); + } + } + + /** + * @template T of object + * + * @param class-string $className + * + * @throws ReflectionException + * + * @return ReflectionClass + * + * @phpstan-ignore throws.unusedType + */ + private function reflectClass(string $className): ReflectionClass + { + try { + $class = new ReflectionClass($className); + + // @codeCoverageIgnoreStart + /** @phpstan-ignore catch.neverThrown */ + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + return $class; + } + + /** + * @param class-string $classOrInterfaceName + * + * @throws ReflectionException + * + * @return list + */ + private function namesOfMethodsIn(string $classOrInterfaceName): array + { + $class = $this->reflectClass($classOrInterfaceName); + $methods = []; + + foreach ($class->getMethods() as $method) { + if ($method->isPublic() || $method->isAbstract()) { + $methods[] = $method->getName(); + } + } + + return $methods; + } + + /** + * @param class-string $interfaceName + * + * @throws ReflectionException + * + * @return list + */ + private function interfaceMethods(string $interfaceName): array + { + $class = $this->reflectClass($interfaceName); + $methods = []; + + foreach ($class->getMethods() as $method) { + $methods[] = DoubledMethod::fromReflection($method); + } + + return $methods; + } + + /** + * @param list $propertiesWithHooks + * + * @return list + */ + private function configurableMethods(DoubledMethodSet $methods, array $propertiesWithHooks): array + { + $configurable = []; + + foreach ($methods->asArray() as $method) { + $configurable[] = new ConfigurableMethod( + $method->methodName(), + $method->defaultParameterValues(), + $method->numberOfParameters(), + $method->returnType(), + ); + } + + foreach ($propertiesWithHooks as $property) { + if ($property->hasGetHook()) { + $configurable[] = new ConfigurableMethod( + sprintf( + '$%s::get', + $property->name(), + ), + [], + 0, + $property->type(), + ); + } + + if ($property->hasSetHook()) { + $configurable[] = new ConfigurableMethod( + sprintf( + '$%s::set', + $property->name(), + ), + [], + 1, + Type::fromName('void', false), + ); + } + } + + return $configurable; + } + + /** + * @param ?ReflectionClass $class + * + * @return list + */ + private function properties(?ReflectionClass $class): array + { + if ($class === null) { + return []; + } + + $mapper = new ReflectionMapper; + $properties = []; + + foreach ($class->getProperties() as $property) { + if (!$property->isPublic()) { + continue; + } + + if ($property->isFinal()) { + continue; + } + + if (!$property->hasHooks()) { + continue; + } + + $hasGetHook = false; + $hasSetHook = false; + + if ($property->hasHook(PropertyHookType::Get) && + !$property->getHook(PropertyHookType::Get)->isFinal()) { + $hasGetHook = true; + } + + if ($property->hasHook(PropertyHookType::Set) && + !$property->getHook(PropertyHookType::Set)->isFinal()) { + $hasSetHook = true; + } + + if (!$hasGetHook && !$hasSetHook) { + continue; + } + + $properties[] = new HookedProperty( + $property->getName(), + $mapper->fromPropertyType($property), + $hasGetHook, + $hasSetHook, + ); + } + + return $properties; + } +} diff --git a/src/Framework/MockObject/Generator/HookedProperty.php b/src/Framework/MockObject/Generator/HookedProperty.php new file mode 100644 index 00000000000..e43d589d7e7 --- /dev/null +++ b/src/Framework/MockObject/Generator/HookedProperty.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +use SebastianBergmann\Type\Type; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class HookedProperty +{ + /** + * @var non-empty-string + */ + private string $name; + private Type $type; + private bool $getHook; + private bool $setHook; + + /** + * @param non-empty-string $name + */ + public function __construct(string $name, Type $type, bool $getHook, bool $setHook) + { + $this->name = $name; + $this->type = $type; + $this->getHook = $getHook; + $this->setHook = $setHook; + } + + public function name(): string + { + return $this->name; + } + + public function type(): Type + { + return $this->type; + } + + public function hasGetHook(): bool + { + return $this->getHook; + } + + public function hasSetHook(): bool + { + return $this->setHook; + } +} diff --git a/src/Framework/MockObject/Generator/HookedPropertyGenerator.php b/src/Framework/MockObject/Generator/HookedPropertyGenerator.php new file mode 100644 index 00000000000..4fcff6c824d --- /dev/null +++ b/src/Framework/MockObject/Generator/HookedPropertyGenerator.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class HookedPropertyGenerator +{ + /** + * @param class-string $className + * @param list $properties + */ + public function generate(string $className, array $properties): string + { + $code = ''; + + foreach ($properties as $property) { + $code .= sprintf( + <<<'EOT' + + public %s $%s { +EOT, + $property->type()->asString(), + $property->name(), + ); + + if ($property->hasGetHook()) { + $code .= sprintf( + <<<'EOT' + + get { + return $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + '%s', '$%s::get', [], '%s', $this + ) + ); + } + +EOT, + $className, + $property->name(), + $property->type()->asString(), + ); + } + + if ($property->hasSetHook()) { + $code .= sprintf( + <<<'EOT' + + set (%s $value) { + $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + '%s', '$%s::set', [$value], 'void', $this + ) + ); + } + +EOT, + $property->type()->asString(), + $className, + $property->name(), + ); + } + + $code .= <<<'EOT' + } + +EOT; + } + + return $code; + } +} diff --git a/src/Framework/MockObject/Generator/TemplateLoader.php b/src/Framework/MockObject/Generator/TemplateLoader.php new file mode 100644 index 00000000000..8106ce59cf4 --- /dev/null +++ b/src/Framework/MockObject/Generator/TemplateLoader.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +use SebastianBergmann\Template\Template; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This trait is not covered by the backward compatibility promise for PHPUnit + */ +trait TemplateLoader +{ + /** + * @var array + */ + private static array $templates = []; + + private function loadTemplate(string $template): Template + { + $filename = __DIR__ . '/templates/' . $template; + + if (!isset(self::$templates[$filename])) { + self::$templates[$filename] = new Template($filename); + } + + return self::$templates[$filename]; + } +} diff --git a/src/Framework/MockObject/Generator/mocked_class.tpl b/src/Framework/MockObject/Generator/mocked_class.tpl deleted file mode 100644 index 593119fb26f..00000000000 --- a/src/Framework/MockObject/Generator/mocked_class.tpl +++ /dev/null @@ -1,6 +0,0 @@ -declare(strict_types=1); - -{prologue}{class_declaration} -{ - use \PHPUnit\Framework\MockObject\Api;{method}{clone} -{mocked_methods}}{epilogue} diff --git a/src/Framework/MockObject/Generator/mocked_method.tpl b/src/Framework/MockObject/Generator/mocked_method.tpl deleted file mode 100644 index 114ff8d0d92..00000000000 --- a/src/Framework/MockObject/Generator/mocked_method.tpl +++ /dev/null @@ -1,22 +0,0 @@ - - {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} - {{deprecation} - $__phpunit_arguments = [{arguments_call}]; - $__phpunit_count = func_num_args(); - - if ($__phpunit_count > {arguments_count}) { - $__phpunit_arguments_tmp = func_get_args(); - - for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { - $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; - } - } - - $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( - new \PHPUnit\Framework\MockObject\Invocation( - '{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this, {clone_arguments} - ) - ); - - return $__phpunit_result; - } diff --git a/src/Framework/MockObject/Generator/mocked_method_void.tpl b/src/Framework/MockObject/Generator/mocked_method_void.tpl deleted file mode 100644 index 390202201a2..00000000000 --- a/src/Framework/MockObject/Generator/mocked_method_void.tpl +++ /dev/null @@ -1,20 +0,0 @@ - - {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} - {{deprecation} - $__phpunit_arguments = [{arguments_call}]; - $__phpunit_count = func_num_args(); - - if ($__phpunit_count > {arguments_count}) { - $__phpunit_arguments_tmp = func_get_args(); - - for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { - $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; - } - } - - $this->__phpunit_getInvocationHandler()->invoke( - new \PHPUnit\Framework\MockObject\Invocation( - '{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this, {clone_arguments} - ) - ); - } diff --git a/src/Framework/MockObject/Generator/proxied_method.tpl b/src/Framework/MockObject/Generator/proxied_method.tpl deleted file mode 100644 index 91bef463d9f..00000000000 --- a/src/Framework/MockObject/Generator/proxied_method.tpl +++ /dev/null @@ -1,22 +0,0 @@ - - {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} - { - $__phpunit_arguments = [{arguments_call}]; - $__phpunit_count = func_num_args(); - - if ($__phpunit_count > {arguments_count}) { - $__phpunit_arguments_tmp = func_get_args(); - - for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { - $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; - } - } - - $this->__phpunit_getInvocationHandler()->invoke( - new \PHPUnit\Framework\MockObject\Invocation( - '{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this, {clone_arguments}, true - ) - ); - - return call_user_func_array(array($this->__phpunit_originalObject, "{method_name}"), $__phpunit_arguments); - } diff --git a/src/Framework/MockObject/Generator/proxied_method_void.tpl b/src/Framework/MockObject/Generator/proxied_method_void.tpl deleted file mode 100644 index cce19882649..00000000000 --- a/src/Framework/MockObject/Generator/proxied_method_void.tpl +++ /dev/null @@ -1,22 +0,0 @@ - - {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} - { - $__phpunit_arguments = [{arguments_call}]; - $__phpunit_count = func_num_args(); - - if ($__phpunit_count > {arguments_count}) { - $__phpunit_arguments_tmp = func_get_args(); - - for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { - $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; - } - } - - $this->__phpunit_getInvocationHandler()->invoke( - new \PHPUnit\Framework\MockObject\Invocation( - '{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this, {clone_arguments}, true - ) - ); - - call_user_func_array(array($this->__phpunit_originalObject, "{method_name}"), $__phpunit_arguments); - } diff --git a/src/Framework/MockObject/Generator/deprecation.tpl b/src/Framework/MockObject/Generator/templates/deprecation.tpl similarity index 100% rename from src/Framework/MockObject/Generator/deprecation.tpl rename to src/Framework/MockObject/Generator/templates/deprecation.tpl diff --git a/src/Framework/MockObject/Generator/templates/doubled_method.tpl b/src/Framework/MockObject/Generator/templates/doubled_method.tpl new file mode 100644 index 00000000000..bb6fb761d1a --- /dev/null +++ b/src/Framework/MockObject/Generator/templates/doubled_method.tpl @@ -0,0 +1,35 @@ + + {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} + {{deprecation} + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + + $__phpunit_arguments = [{arguments_call}]; + $__phpunit_count = func_num_args(); + + if ({arguments_count} !== null && $__phpunit_count > {arguments_count}) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + '{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this + ) + );{return_result} + } diff --git a/src/Framework/MockObject/Generator/mocked_static_method.tpl b/src/Framework/MockObject/Generator/templates/doubled_static_method.tpl similarity index 100% rename from src/Framework/MockObject/Generator/mocked_static_method.tpl rename to src/Framework/MockObject/Generator/templates/doubled_static_method.tpl diff --git a/src/Framework/MockObject/Generator/templates/intersection.tpl b/src/Framework/MockObject/Generator/templates/intersection.tpl new file mode 100644 index 00000000000..75cd27a6c02 --- /dev/null +++ b/src/Framework/MockObject/Generator/templates/intersection.tpl @@ -0,0 +1,5 @@ +declare(strict_types=1); + +interface {intersection} extends {interfaces} +{ +} diff --git a/src/Framework/MockObject/Generator/templates/test_double_class.tpl b/src/Framework/MockObject/Generator/templates/test_double_class.tpl new file mode 100644 index 00000000000..5d015e3f961 --- /dev/null +++ b/src/Framework/MockObject/Generator/templates/test_double_class.tpl @@ -0,0 +1,5 @@ +declare(strict_types=1); + +{class_declaration} +{ +{use_statements}{property_hooks}{methods}} diff --git a/src/Framework/MockObject/Generator/trait_class.tpl b/src/Framework/MockObject/Generator/trait_class.tpl deleted file mode 100644 index a8fe470fdc4..00000000000 --- a/src/Framework/MockObject/Generator/trait_class.tpl +++ /dev/null @@ -1,6 +0,0 @@ -declare(strict_types=1); - -{prologue}class {class_name} -{ - use {trait_name}; -} diff --git a/src/Framework/MockObject/Generator/wsdl_class.tpl b/src/Framework/MockObject/Generator/wsdl_class.tpl deleted file mode 100644 index b3100b41417..00000000000 --- a/src/Framework/MockObject/Generator/wsdl_class.tpl +++ /dev/null @@ -1,9 +0,0 @@ -declare(strict_types=1); - -{namespace}class {class_name} extends \SoapClient -{ - public function __construct($wsdl, array $options) - { - parent::__construct('{wsdl}', $options); - } -{methods}} diff --git a/src/Framework/MockObject/Generator/wsdl_method.tpl b/src/Framework/MockObject/Generator/wsdl_method.tpl deleted file mode 100644 index bb16e763eba..00000000000 --- a/src/Framework/MockObject/Generator/wsdl_method.tpl +++ /dev/null @@ -1,4 +0,0 @@ - - public function {method_name}({arguments}) - { - } diff --git a/src/Framework/MockObject/Invocation.php b/src/Framework/MockObject/Invocation.php deleted file mode 100644 index ba307024f6f..00000000000 --- a/src/Framework/MockObject/Invocation.php +++ /dev/null @@ -1,213 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function array_map; -use function explode; -use function implode; -use function is_object; -use function sprintf; -use function strpos; -use function strtolower; -use function substr; -use PHPUnit\Framework\SelfDescribing; -use PHPUnit\Util\Type; -use SebastianBergmann\Exporter\Exporter; -use stdClass; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Invocation implements SelfDescribing -{ - /** - * @var string - */ - private $className; - - /** - * @var string - */ - private $methodName; - - /** - * @var array - */ - private $parameters; - - /** - * @var string - */ - private $returnType; - - /** - * @var bool - */ - private $isReturnTypeNullable = false; - - /** - * @var bool - */ - private $proxiedCall; - - /** - * @var object - */ - private $object; - - public function __construct(string $className, string $methodName, array $parameters, string $returnType, object $object, bool $cloneObjects = false, bool $proxiedCall = false) - { - $this->className = $className; - $this->methodName = $methodName; - $this->parameters = $parameters; - $this->object = $object; - $this->proxiedCall = $proxiedCall; - - if (strtolower($methodName) === '__tostring') { - $returnType = 'string'; - } - - if (strpos($returnType, '?') === 0) { - $returnType = substr($returnType, 1); - $this->isReturnTypeNullable = true; - } - - $this->returnType = $returnType; - - if (!$cloneObjects) { - return; - } - - foreach ($this->parameters as $key => $value) { - if (is_object($value)) { - $this->parameters[$key] = $this->cloneObject($value); - } - } - } - - public function getClassName(): string - { - return $this->className; - } - - public function getMethodName(): string - { - return $this->methodName; - } - - public function getParameters(): array - { - return $this->parameters; - } - - /** - * @throws RuntimeException - * - * @return mixed Mocked return value - */ - public function generateReturnValue() - { - if ($this->isReturnTypeNullable || $this->proxiedCall) { - return; - } - - $returnType = $this->returnType; - - if (strpos($returnType, '|') !== false) { - $types = explode('|', $returnType); - $returnType = $types[0]; - - foreach ($types as $type) { - if ($type === 'null') { - return; - } - } - } - - switch (strtolower($returnType)) { - case '': - case 'void': - return; - - case 'string': - return ''; - - case 'float': - return 0.0; - - case 'int': - return 0; - - case 'bool': - return false; - - case 'array': - return []; - - case 'object': - return new stdClass; - - case 'callable': - case 'closure': - return static function (): void { - }; - - case 'traversable': - case 'generator': - case 'iterable': - $generator = static function () { - yield; - }; - - return $generator(); - - case 'mixed': - return null; - - default: - $generator = new Generator; - - return $generator->getMock($this->returnType, [], [], '', false); - } - } - - public function toString(): string - { - $exporter = new Exporter; - - return sprintf( - '%s::%s(%s)%s', - $this->className, - $this->methodName, - implode( - ', ', - array_map( - [$exporter, 'shortenedExport'], - $this->parameters - ) - ), - $this->returnType ? sprintf(': %s', $this->returnType) : '' - ); - } - - public function getObject(): object - { - return $this->object; - } - - private function cloneObject(object $original): object - { - if (Type::isCloneable($original)) { - return clone $original; - } - - return $original; - } -} diff --git a/src/Framework/MockObject/InvocationHandler.php b/src/Framework/MockObject/InvocationHandler.php deleted file mode 100644 index c2b399a883e..00000000000 --- a/src/Framework/MockObject/InvocationHandler.php +++ /dev/null @@ -1,195 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function sprintf; -use function strtolower; -use Exception; -use PHPUnit\Framework\MockObject\Builder\InvocationMocker; -use PHPUnit\Framework\MockObject\Rule\InvocationOrder; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class InvocationHandler -{ - /** - * @var Matcher[] - */ - private $matchers = []; - - /** - * @var Matcher[] - */ - private $matcherMap = []; - - /** - * @var ConfigurableMethod[] - */ - private $configurableMethods; - - /** - * @var bool - */ - private $returnValueGeneration; - - /** - * @var Throwable - */ - private $deferredError; - - public function __construct(array $configurableMethods, bool $returnValueGeneration) - { - $this->configurableMethods = $configurableMethods; - $this->returnValueGeneration = $returnValueGeneration; - } - - public function hasMatchers(): bool - { - foreach ($this->matchers as $matcher) { - if ($matcher->hasMatchers()) { - return true; - } - } - - return false; - } - - /** - * Looks up the match builder with identification $id and returns it. - * - * @param string $id The identification of the match builder - */ - public function lookupMatcher(string $id): ?Matcher - { - if (isset($this->matcherMap[$id])) { - return $this->matcherMap[$id]; - } - - return null; - } - - /** - * Registers a matcher with the identification $id. The matcher can later be - * looked up using lookupMatcher() to figure out if it has been invoked. - * - * @param string $id The identification of the matcher - * @param Matcher $matcher The builder which is being registered - * - * @throws RuntimeException - */ - public function registerMatcher(string $id, Matcher $matcher): void - { - if (isset($this->matcherMap[$id])) { - throw new RuntimeException( - 'Matcher with id <' . $id . '> is already registered.' - ); - } - - $this->matcherMap[$id] = $matcher; - } - - public function expects(InvocationOrder $rule): InvocationMocker - { - $matcher = new Matcher($rule); - $this->addMatcher($matcher); - - return new InvocationMocker( - $this, - $matcher, - ...$this->configurableMethods - ); - } - - /** - * @throws RuntimeException - * @throws Exception - */ - public function invoke(Invocation $invocation) - { - $exception = null; - $hasReturnValue = false; - $returnValue = null; - - foreach ($this->matchers as $match) { - try { - if ($match->matches($invocation)) { - $value = $match->invoked($invocation); - - if (!$hasReturnValue) { - $returnValue = $value; - $hasReturnValue = true; - } - } - } catch (Exception $e) { - $exception = $e; - } - } - - if ($exception !== null) { - throw $exception; - } - - if ($hasReturnValue) { - return $returnValue; - } - - if (!$this->returnValueGeneration) { - $exception = new RuntimeException( - sprintf( - 'Return value inference disabled and no expectation set up for %s::%s()', - $invocation->getClassName(), - $invocation->getMethodName() - ) - ); - - if (strtolower($invocation->getMethodName()) === '__tostring') { - $this->deferredError = $exception; - - return ''; - } - - throw $exception; - } - - return $invocation->generateReturnValue(); - } - - public function matches(Invocation $invocation): bool - { - foreach ($this->matchers as $matcher) { - if (!$matcher->matches($invocation)) { - return false; - } - } - - return true; - } - - /** - * @throws Throwable - */ - public function verify(): void - { - foreach ($this->matchers as $matcher) { - $matcher->verify(); - } - - if ($this->deferredError) { - throw $this->deferredError; - } - } - - private function addMatcher(Matcher $matcher): void - { - $this->matchers[] = $matcher; - } -} diff --git a/src/Framework/MockObject/Matcher.php b/src/Framework/MockObject/Matcher.php deleted file mode 100644 index db6d8776769..00000000000 --- a/src/Framework/MockObject/Matcher.php +++ /dev/null @@ -1,278 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function assert; -use function implode; -use function sprintf; -use Exception; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount; -use PHPUnit\Framework\MockObject\Rule\AnyParameters; -use PHPUnit\Framework\MockObject\Rule\InvocationOrder; -use PHPUnit\Framework\MockObject\Rule\InvokedCount; -use PHPUnit\Framework\MockObject\Rule\MethodName; -use PHPUnit\Framework\MockObject\Rule\ParametersRule; -use PHPUnit\Framework\MockObject\Stub\Stub; -use PHPUnit\Framework\TestFailure; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Matcher -{ - /** - * @var InvocationOrder - */ - private $invocationRule; - - /** - * @var mixed - */ - private $afterMatchBuilderId; - - /** - * @var bool - */ - private $afterMatchBuilderIsInvoked = false; - - /** - * @var MethodName - */ - private $methodNameRule; - - /** - * @var ParametersRule - */ - private $parametersRule; - - /** - * @var Stub - */ - private $stub; - - public function __construct(InvocationOrder $rule) - { - $this->invocationRule = $rule; - } - - public function hasMatchers(): bool - { - return !$this->invocationRule instanceof AnyInvokedCount; - } - - public function hasMethodNameRule(): bool - { - return $this->methodNameRule !== null; - } - - public function getMethodNameRule(): MethodName - { - return $this->methodNameRule; - } - - public function setMethodNameRule(MethodName $rule): void - { - $this->methodNameRule = $rule; - } - - public function hasParametersRule(): bool - { - return $this->parametersRule !== null; - } - - public function setParametersRule(ParametersRule $rule): void - { - $this->parametersRule = $rule; - } - - public function setStub(Stub $stub): void - { - $this->stub = $stub; - } - - public function setAfterMatchBuilderId(string $id): void - { - $this->afterMatchBuilderId = $id; - } - - /** - * @throws Exception - * @throws RuntimeException - * @throws ExpectationFailedException - */ - public function invoked(Invocation $invocation) - { - if ($this->methodNameRule === null) { - throw new RuntimeException('No method rule is set'); - } - - if ($this->afterMatchBuilderId !== null) { - $matcher = $invocation->getObject() - ->__phpunit_getInvocationHandler() - ->lookupMatcher($this->afterMatchBuilderId); - - if (!$matcher) { - throw new RuntimeException( - sprintf( - 'No builder found for match builder identification <%s>', - $this->afterMatchBuilderId - ) - ); - } - assert($matcher instanceof self); - - if ($matcher->invocationRule->hasBeenInvoked()) { - $this->afterMatchBuilderIsInvoked = true; - } - } - - $this->invocationRule->invoked($invocation); - - try { - if ($this->parametersRule !== null) { - $this->parametersRule->apply($invocation); - } - } catch (ExpectationFailedException $e) { - throw new ExpectationFailedException( - sprintf( - "Expectation failed for %s when %s\n%s", - $this->methodNameRule->toString(), - $this->invocationRule->toString(), - $e->getMessage() - ), - $e->getComparisonFailure() - ); - } - - if ($this->stub) { - return $this->stub->invoke($invocation); - } - - return $invocation->generateReturnValue(); - } - - /** - * @throws RuntimeException - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function matches(Invocation $invocation): bool - { - if ($this->afterMatchBuilderId !== null) { - $matcher = $invocation->getObject() - ->__phpunit_getInvocationHandler() - ->lookupMatcher($this->afterMatchBuilderId); - - if (!$matcher) { - throw new RuntimeException( - sprintf( - 'No builder found for match builder identification <%s>', - $this->afterMatchBuilderId - ) - ); - } - assert($matcher instanceof self); - - if (!$matcher->invocationRule->hasBeenInvoked()) { - return false; - } - } - - if ($this->methodNameRule === null) { - throw new RuntimeException('No method rule is set'); - } - - if (!$this->invocationRule->matches($invocation)) { - return false; - } - - try { - if (!$this->methodNameRule->matches($invocation)) { - return false; - } - } catch (ExpectationFailedException $e) { - throw new ExpectationFailedException( - sprintf( - "Expectation failed for %s when %s\n%s", - $this->methodNameRule->toString(), - $this->invocationRule->toString(), - $e->getMessage() - ), - $e->getComparisonFailure() - ); - } - - return true; - } - - /** - * @throws RuntimeException - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function verify(): void - { - if ($this->methodNameRule === null) { - throw new RuntimeException('No method rule is set'); - } - - try { - $this->invocationRule->verify(); - - if ($this->parametersRule === null) { - $this->parametersRule = new AnyParameters; - } - - $invocationIsAny = $this->invocationRule instanceof AnyInvokedCount; - $invocationIsNever = $this->invocationRule instanceof InvokedCount && $this->invocationRule->isNever(); - - if (!$invocationIsAny && !$invocationIsNever) { - $this->parametersRule->verify(); - } - } catch (ExpectationFailedException $e) { - throw new ExpectationFailedException( - sprintf( - "Expectation failed for %s when %s.\n%s", - $this->methodNameRule->toString(), - $this->invocationRule->toString(), - TestFailure::exceptionToString($e) - ) - ); - } - } - - public function toString(): string - { - $list = []; - - if ($this->invocationRule !== null) { - $list[] = $this->invocationRule->toString(); - } - - if ($this->methodNameRule !== null) { - $list[] = 'where ' . $this->methodNameRule->toString(); - } - - if ($this->parametersRule !== null) { - $list[] = 'and ' . $this->parametersRule->toString(); - } - - if ($this->afterMatchBuilderId !== null) { - $list[] = 'after ' . $this->afterMatchBuilderId; - } - - if ($this->stub !== null) { - $list[] = 'will ' . $this->stub->toString(); - } - - return implode(' ', $list); - } -} diff --git a/src/Framework/MockObject/MethodNameConstraint.php b/src/Framework/MockObject/MethodNameConstraint.php deleted file mode 100644 index 3082ab384c9..00000000000 --- a/src/Framework/MockObject/MethodNameConstraint.php +++ /dev/null @@ -1,48 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function is_string; -use function sprintf; -use function strtolower; -use PHPUnit\Framework\Constraint\Constraint; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class MethodNameConstraint extends Constraint -{ - /** - * @var string - */ - private $methodName; - - public function __construct(string $methodName) - { - $this->methodName = $methodName; - } - - public function toString(): string - { - return sprintf( - 'is "%s"', - $this->methodName - ); - } - - protected function matches($other): bool - { - if (!is_string($other)) { - return false; - } - - return strtolower($this->methodName) === strtolower($other); - } -} diff --git a/src/Framework/MockObject/MockBuilder.php b/src/Framework/MockObject/MockBuilder.php index 6ff2b264ed2..9bba136a7ca 100644 --- a/src/Framework/MockObject/MockBuilder.php +++ b/src/Framework/MockObject/MockBuilder.php @@ -9,99 +9,59 @@ */ namespace PHPUnit\Framework\MockObject; -use function array_diff; use function array_merge; -use function sprintf; +use function assert; +use PHPUnit\Framework\InvalidArgumentException; +use PHPUnit\Framework\MockObject\Generator\ClassIsEnumerationException; +use PHPUnit\Framework\MockObject\Generator\ClassIsFinalException; +use PHPUnit\Framework\MockObject\Generator\DuplicateMethodException; +use PHPUnit\Framework\MockObject\Generator\Generator; +use PHPUnit\Framework\MockObject\Generator\InvalidMethodNameException; +use PHPUnit\Framework\MockObject\Generator\NameAlreadyInUseException; +use PHPUnit\Framework\MockObject\Generator\ReflectionException; +use PHPUnit\Framework\MockObject\Generator\RuntimeException; +use PHPUnit\Framework\MockObject\Generator\UnknownTypeException; use PHPUnit\Framework\TestCase; use ReflectionClass; -use ReflectionException; /** - * @psalm-template MockedType + * @template MockedType + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class MockBuilder { - /** - * @var TestCase - */ - private $testCase; + private readonly TestCase $testCase; /** - * @var string + * @var class-string|trait-string */ - private $type; + private readonly string $type; /** - * @var null|string[] + * @var list */ - private $methods = []; + private array $methods = []; + private bool $emptyMethodsArray = false; /** - * @var bool + * @var ?class-string */ - private $emptyMethodsArray = false; + private ?string $mockClassName = null; /** - * @var string + * @var array */ - private $mockClassName = ''; + private array $constructorArgs = []; + private bool $originalConstructor = true; + private bool $originalClone = true; + private bool $returnValueGeneration = true; + private readonly Generator $generator; /** - * @var array + * @param class-string|trait-string $type */ - private $constructorArgs = []; - - /** - * @var bool - */ - private $originalConstructor = true; - - /** - * @var bool - */ - private $originalClone = true; - - /** - * @var bool - */ - private $autoload = true; - - /** - * @var bool - */ - private $cloneArguments = false; - - /** - * @var bool - */ - private $callOriginalMethods = false; - - /** - * @var ?object - */ - private $proxyTarget; - - /** - * @var bool - */ - private $allowMockingUnknownTypes = true; - - /** - * @var bool - */ - private $returnValueGeneration = true; - - /** - * @var Generator - */ - private $generator; - - /** - * @param string|string[] $type - * - * @psalm-param class-string|string|string[] $type - */ - public function __construct(TestCase $testCase, $type) + public function __construct(TestCase $testCase, string $type) { $this->testCase = $testCase; $this->type = $type; @@ -111,114 +71,52 @@ public function __construct(TestCase $testCase, $type) /** * Creates a mock object using a fluent interface. * + * @throws ClassIsEnumerationException + * @throws ClassIsFinalException + * @throws DuplicateMethodException + * @throws InvalidArgumentException + * @throws InvalidMethodNameException + * @throws NameAlreadyInUseException + * @throws ReflectionException * @throws RuntimeException + * @throws UnknownTypeException * - * @psalm-return MockObject&MockedType + * @return MockedType&MockObject */ public function getMock(): MockObject { - $object = $this->generator->getMock( + $object = $this->generator->testDouble( $this->type, + true, !$this->emptyMethodsArray ? $this->methods : null, $this->constructorArgs, - $this->mockClassName, + $this->mockClassName ?? '', $this->originalConstructor, $this->originalClone, - $this->autoload, - $this->cloneArguments, - $this->callOriginalMethods, - $this->proxyTarget, - $this->allowMockingUnknownTypes, - $this->returnValueGeneration + $this->returnValueGeneration, ); - $this->testCase->registerMockObject($object); - - return $object; - } - - /** - * Creates a mock object for an abstract class using a fluent interface. - * - * @throws \PHPUnit\Framework\Exception - * @throws RuntimeException - * - * @psalm-return MockObject&MockedType - */ - public function getMockForAbstractClass(): MockObject - { - $object = $this->generator->getMockForAbstractClass( - $this->type, - $this->constructorArgs, - $this->mockClassName, - $this->originalConstructor, - $this->originalClone, - $this->autoload, - $this->methods, - $this->cloneArguments - ); + assert($object instanceof $this->type); + assert($object instanceof MockObject); - $this->testCase->registerMockObject($object); + $this->testCase->registerMockObject($this->type, $object); return $object; } - /** - * Creates a mock object for a trait using a fluent interface. - * - * @throws \PHPUnit\Framework\Exception - * @throws RuntimeException - * - * @psalm-return MockObject&MockedType - */ - public function getMockForTrait(): MockObject - { - $object = $this->generator->getMockForTrait( - $this->type, - $this->constructorArgs, - $this->mockClassName, - $this->originalConstructor, - $this->originalClone, - $this->autoload, - $this->methods, - $this->cloneArguments - ); - - $this->testCase->registerMockObject($object); - - return $object; - } - - /** - * Specifies the subset of methods to mock. Default is to mock none of them. - * - * @deprecated https://github.com/sebastianbergmann/phpunit/pull/3687 - * - * @return $this - */ - public function setMethods(?array $methods = null): self - { - if ($methods === null) { - $this->methods = $methods; - } else { - $this->methods = array_merge($this->methods ?? [], $methods); - } - - return $this; - } - /** * Specifies the subset of methods to mock, requiring each to exist in the class. * - * @param string[] $methods + * @param list $methods * - * @throws RuntimeException + * @throws CannotUseOnlyMethodsException + * @throws ReflectionException * * @return $this */ public function onlyMethods(array $methods): self { - if (empty($methods)) { + if ($methods === []) { $this->emptyMethodsArray = true; return $this; @@ -226,100 +124,39 @@ public function onlyMethods(array $methods): self try { $reflector = new ReflectionClass($this->type); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new RuntimeException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - foreach ($methods as $method) { - if (!$reflector->hasMethod($method)) { - throw new RuntimeException( - sprintf( - 'Trying to set mock method "%s" with onlyMethods, but it does not exist in class "%s". Use addMethods() for methods that don\'t exist in the class.', - $method, - $this->type - ) - ); - } - } - - $this->methods = array_merge($this->methods ?? [], $methods); - - return $this; - } - /** - * Specifies methods that don't exist in the class which you want to mock. - * - * @param string[] $methods - * - * @throws RuntimeException - * - * @return $this - */ - public function addMethods(array $methods): self - { - if (empty($methods)) { - $this->emptyMethodsArray = true; - - return $this; - } - - try { - $reflector = new ReflectionClass($this->type); // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new RuntimeException( + /** @phpstan-ignore catch.neverThrown */ + } catch (\ReflectionException $e) { + throw new ReflectionException( $e->getMessage(), - (int) $e->getCode(), - $e + $e->getCode(), + $e, ); + // @codeCoverageIgnoreEnd } - // @codeCoverageIgnoreEnd foreach ($methods as $method) { - if ($reflector->hasMethod($method)) { - throw new RuntimeException( - sprintf( - 'Trying to set mock method "%s" with addMethods(), but it exists in class "%s". Use onlyMethods() for methods that exist in the class.', - $method, - $this->type - ) - ); + if (!$reflector->hasMethod($method)) { + throw new CannotUseOnlyMethodsException($this->type, $method); } } - $this->methods = array_merge($this->methods ?? [], $methods); + $this->methods = array_merge($this->methods, $methods); return $this; } - /** - * Specifies the subset of methods to not mock. Default is to mock all of them. - */ - public function setMethodsExcept(array $methods = []): self - { - return $this->setMethods( - array_diff( - $this->generator->getClassMethods($this->type), - $methods - ) - ); - } - /** * Specifies the arguments for the constructor. * + * @param array $arguments + * * @return $this */ - public function setConstructorArgs(array $args): self + public function setConstructorArgs(array $arguments): self { - $this->constructorArgs = $args; + $this->constructorArgs = $arguments; return $this; } @@ -327,6 +164,8 @@ public function setConstructorArgs(array $args): self /** * Specifies the name for the mock class. * + * @param class-string $name + * * @return $this */ public function setMockClassName(string $name): self @@ -384,111 +223,6 @@ public function enableOriginalClone(): self return $this; } - /** - * Disables the use of class autoloading while creating the mock object. - * - * @return $this - */ - public function disableAutoload(): self - { - $this->autoload = false; - - return $this; - } - - /** - * Enables the use of class autoloading while creating the mock object. - * - * @return $this - */ - public function enableAutoload(): self - { - $this->autoload = true; - - return $this; - } - - /** - * Disables the cloning of arguments passed to mocked methods. - * - * @return $this - */ - public function disableArgumentCloning(): self - { - $this->cloneArguments = false; - - return $this; - } - - /** - * Enables the cloning of arguments passed to mocked methods. - * - * @return $this - */ - public function enableArgumentCloning(): self - { - $this->cloneArguments = true; - - return $this; - } - - /** - * Enables the invocation of the original methods. - * - * @return $this - */ - public function enableProxyingToOriginalMethods(): self - { - $this->callOriginalMethods = true; - - return $this; - } - - /** - * Disables the invocation of the original methods. - * - * @return $this - */ - public function disableProxyingToOriginalMethods(): self - { - $this->callOriginalMethods = false; - $this->proxyTarget = null; - - return $this; - } - - /** - * Sets the proxy target. - * - * @return $this - */ - public function setProxyTarget(object $object): self - { - $this->proxyTarget = $object; - - return $this; - } - - /** - * @return $this - */ - public function allowMockingUnknownTypes(): self - { - $this->allowMockingUnknownTypes = true; - - return $this; - } - - /** - * @return $this - */ - public function disallowMockingUnknownTypes(): self - { - $this->allowMockingUnknownTypes = false; - - return $this; - } - /** * @return $this */ diff --git a/src/Framework/MockObject/MockClass.php b/src/Framework/MockObject/MockClass.php deleted file mode 100644 index 4aaac1b4315..00000000000 --- a/src/Framework/MockObject/MockClass.php +++ /dev/null @@ -1,63 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function call_user_func; -use function class_exists; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class MockClass implements MockType -{ - /** - * @var string - */ - private $classCode; - - /** - * @var string - */ - private $mockName; - - /** - * @var ConfigurableMethod[] - */ - private $configurableMethods; - - public function __construct(string $classCode, string $mockName, array $configurableMethods) - { - $this->classCode = $classCode; - $this->mockName = $mockName; - $this->configurableMethods = $configurableMethods; - } - - public function generate(): string - { - if (!class_exists($this->mockName, false)) { - eval($this->classCode); - - call_user_func( - [ - $this->mockName, - '__phpunit_initConfigurableMethods', - ], - ...$this->configurableMethods - ); - } - - return $this->mockName; - } - - public function getClassCode(): string - { - return $this->classCode; - } -} diff --git a/src/Framework/MockObject/MockMethod.php b/src/Framework/MockObject/MockMethod.php deleted file mode 100644 index 00c3c039a8b..00000000000 --- a/src/Framework/MockObject/MockMethod.php +++ /dev/null @@ -1,386 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use const DIRECTORY_SEPARATOR; -use function implode; -use function is_string; -use function preg_match; -use function preg_replace; -use function sprintf; -use function substr_count; -use function trim; -use function var_export; -use ReflectionException; -use ReflectionMethod; -use ReflectionNamedType; -use ReflectionParameter; -use ReflectionUnionType; -use SebastianBergmann\Template\Template; -use SebastianBergmann\Type\ReflectionMapper; -use SebastianBergmann\Type\Type; -use SebastianBergmann\Type\UnknownType; -use SebastianBergmann\Type\VoidType; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class MockMethod -{ - /** - * @var Template[] - */ - private static $templates = []; - - /** - * @var string - */ - private $className; - - /** - * @var string - */ - private $methodName; - - /** - * @var bool - */ - private $cloneArguments; - - /** - * @var string string - */ - private $modifier; - - /** - * @var string - */ - private $argumentsForDeclaration; - - /** - * @var string - */ - private $argumentsForCall; - - /** - * @var Type - */ - private $returnType; - - /** - * @var string - */ - private $reference; - - /** - * @var bool - */ - private $callOriginalMethod; - - /** - * @var bool - */ - private $static; - - /** - * @var ?string - */ - private $deprecation; - - /** - * @throws RuntimeException - */ - public static function fromReflection(ReflectionMethod $method, bool $callOriginalMethod, bool $cloneArguments): self - { - if ($method->isPrivate()) { - $modifier = 'private'; - } elseif ($method->isProtected()) { - $modifier = 'protected'; - } else { - $modifier = 'public'; - } - - if ($method->isStatic()) { - $modifier .= ' static'; - } - - if ($method->returnsReference()) { - $reference = '&'; - } else { - $reference = ''; - } - - $docComment = $method->getDocComment(); - - if (is_string($docComment) && - preg_match('#\*[ \t]*+@deprecated[ \t]*+(.*?)\r?+\n[ \t]*+\*(?:[ \t]*+@|/$)#s', $docComment, $deprecation)) { - $deprecation = trim(preg_replace('#[ \t]*\r?\n[ \t]*+\*[ \t]*+#', ' ', $deprecation[1])); - } else { - $deprecation = null; - } - - return new self( - $method->getDeclaringClass()->getName(), - $method->getName(), - $cloneArguments, - $modifier, - self::getMethodParametersForDeclaration($method), - self::getMethodParametersForCall($method), - (new ReflectionMapper)->fromMethodReturnType($method), - $reference, - $callOriginalMethod, - $method->isStatic(), - $deprecation - ); - } - - public static function fromName(string $fullClassName, string $methodName, bool $cloneArguments): self - { - return new self( - $fullClassName, - $methodName, - $cloneArguments, - 'public', - '', - '', - new UnknownType, - '', - false, - false, - null - ); - } - - public function __construct(string $className, string $methodName, bool $cloneArguments, string $modifier, string $argumentsForDeclaration, string $argumentsForCall, Type $returnType, string $reference, bool $callOriginalMethod, bool $static, ?string $deprecation) - { - $this->className = $className; - $this->methodName = $methodName; - $this->cloneArguments = $cloneArguments; - $this->modifier = $modifier; - $this->argumentsForDeclaration = $argumentsForDeclaration; - $this->argumentsForCall = $argumentsForCall; - $this->returnType = $returnType; - $this->reference = $reference; - $this->callOriginalMethod = $callOriginalMethod; - $this->static = $static; - $this->deprecation = $deprecation; - } - - public function getName(): string - { - return $this->methodName; - } - - /** - * @throws RuntimeException - */ - public function generateCode(): string - { - if ($this->static) { - $templateFile = 'mocked_static_method.tpl'; - } elseif ($this->returnType instanceof VoidType) { - $templateFile = sprintf( - '%s_method_void.tpl', - $this->callOriginalMethod ? 'proxied' : 'mocked' - ); - } else { - $templateFile = sprintf( - '%s_method.tpl', - $this->callOriginalMethod ? 'proxied' : 'mocked' - ); - } - - $deprecation = $this->deprecation; - - if (null !== $this->deprecation) { - $deprecation = "The {$this->className}::{$this->methodName} method is deprecated ({$this->deprecation})."; - $deprecationTemplate = $this->getTemplate('deprecation.tpl'); - - $deprecationTemplate->setVar( - [ - 'deprecation' => var_export($deprecation, true), - ] - ); - - $deprecation = $deprecationTemplate->render(); - } - - $template = $this->getTemplate($templateFile); - - $template->setVar( - [ - 'arguments_decl' => $this->argumentsForDeclaration, - 'arguments_call' => $this->argumentsForCall, - 'return_declaration' => !empty($this->returnType->asString()) ? (': ' . $this->returnType->asString()) : '', - 'return_type' => $this->returnType->asString(), - 'arguments_count' => !empty($this->argumentsForCall) ? substr_count($this->argumentsForCall, ',') + 1 : 0, - 'class_name' => $this->className, - 'method_name' => $this->methodName, - 'modifier' => $this->modifier, - 'reference' => $this->reference, - 'clone_arguments' => $this->cloneArguments ? 'true' : 'false', - 'deprecation' => $deprecation, - ] - ); - - return $template->render(); - } - - public function getReturnType(): Type - { - return $this->returnType; - } - - private function getTemplate(string $template): Template - { - $filename = __DIR__ . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR . $template; - - if (!isset(self::$templates[$filename])) { - self::$templates[$filename] = new Template($filename); - } - - return self::$templates[$filename]; - } - - /** - * Returns the parameters of a function or method. - * - * @throws RuntimeException - */ - private static function getMethodParametersForDeclaration(ReflectionMethod $method): string - { - $parameters = []; - - foreach ($method->getParameters() as $i => $parameter) { - $name = '$' . $parameter->getName(); - - /* Note: PHP extensions may use empty names for reference arguments - * or "..." for methods taking a variable number of arguments. - */ - if ($name === '$' || $name === '$...') { - $name = '$arg' . $i; - } - - $nullable = ''; - $default = ''; - $reference = ''; - $typeDeclaration = ''; - $type = null; - $typeName = null; - - if ($parameter->hasType()) { - $type = $parameter->getType(); - - if ($type instanceof ReflectionNamedType) { - $typeName = $type->getName(); - } - } - - if ($parameter->isVariadic()) { - $name = '...' . $name; - } elseif ($parameter->isDefaultValueAvailable()) { - $default = ' = ' . self::exportDefaultValue($parameter); - } elseif ($parameter->isOptional()) { - $default = ' = null'; - } - - if ($type !== null) { - if ($typeName !== 'mixed' && $parameter->allowsNull()) { - $nullable = '?'; - } - - if ($typeName === 'self') { - $typeDeclaration = $method->getDeclaringClass()->getName() . ' '; - } elseif ($typeName !== null) { - $typeDeclaration = $typeName . ' '; - } elseif ($type instanceof ReflectionUnionType) { - $typeDeclaration = self::unionTypeAsString( - $type, - $method->getDeclaringClass()->getName() - ); - } - } - - if ($parameter->isPassedByReference()) { - $reference = '&'; - } - - $parameters[] = $nullable . $typeDeclaration . $reference . $name . $default; - } - - return implode(', ', $parameters); - } - - /** - * Returns the parameters of a function or method. - * - * @throws RuntimeException - */ - private static function getMethodParametersForCall(ReflectionMethod $method): string - { - $parameters = []; - - foreach ($method->getParameters() as $i => $parameter) { - $name = '$' . $parameter->getName(); - - /* Note: PHP extensions may use empty names for reference arguments - * or "..." for methods taking a variable number of arguments. - */ - if ($name === '$' || $name === '$...') { - $name = '$arg' . $i; - } - - if ($parameter->isVariadic()) { - continue; - } - - if ($parameter->isPassedByReference()) { - $parameters[] = '&' . $name; - } else { - $parameters[] = $name; - } - } - - return implode(', ', $parameters); - } - - /** - * @throws RuntimeException - */ - private static function exportDefaultValue(ReflectionParameter $parameter): string - { - try { - return (string) var_export($parameter->getDefaultValue(), true); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new RuntimeException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - } - - private static function unionTypeAsString(ReflectionUnionType $union, string $self): string - { - $types = []; - - foreach ($union->getTypes() as $type) { - if ($type === 'self') { - $types[] = $self; - } else { - $types[] = $type; - } - } - - return implode('|', $types) . ' '; - } -} diff --git a/src/Framework/MockObject/MockMethodSet.php b/src/Framework/MockObject/MockMethodSet.php deleted file mode 100644 index 1c78963c08b..00000000000 --- a/src/Framework/MockObject/MockMethodSet.php +++ /dev/null @@ -1,45 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function array_key_exists; -use function array_values; -use function strtolower; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class MockMethodSet -{ - /** - * @var MockMethod[] - */ - private $methods = []; - - public function addMethods(MockMethod ...$methods): void - { - foreach ($methods as $method) { - $this->methods[strtolower($method->getName())] = $method; - } - } - - /** - * @return MockMethod[] - */ - public function asArray(): array - { - return array_values($this->methods); - } - - public function hasMethod(string $methodName): bool - { - return array_key_exists(strtolower($methodName), $this->methods); - } -} diff --git a/src/Framework/MockObject/MockObject.php b/src/Framework/MockObject/MockObject.php deleted file mode 100644 index 4db11e1524c..00000000000 --- a/src/Framework/MockObject/MockObject.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use PHPUnit\Framework\MockObject\Builder\InvocationMocker as BuilderInvocationMocker; -use PHPUnit\Framework\MockObject\Rule\InvocationOrder; - -/** - * @method BuilderInvocationMocker method($constraint) - */ -interface MockObject extends Stub -{ - public function __phpunit_setOriginalObject($originalObject): void; - - public function __phpunit_verify(bool $unsetInvocationMocker = true): void; - - public function expects(InvocationOrder $invocationRule): BuilderInvocationMocker; -} diff --git a/src/Framework/MockObject/MockTrait.php b/src/Framework/MockObject/MockTrait.php deleted file mode 100644 index 7b9f45003cb..00000000000 --- a/src/Framework/MockObject/MockTrait.php +++ /dev/null @@ -1,48 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function class_exists; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class MockTrait implements MockType -{ - /** - * @var string - */ - private $classCode; - - /** - * @var string - */ - private $mockName; - - public function __construct(string $classCode, string $mockName) - { - $this->classCode = $classCode; - $this->mockName = $mockName; - } - - public function generate(): string - { - if (!class_exists($this->mockName, false)) { - eval($this->classCode); - } - - return $this->mockName; - } - - public function getClassCode(): string - { - return $this->classCode; - } -} diff --git a/src/Framework/MockObject/MockType.php b/src/Framework/MockObject/MockType.php deleted file mode 100644 index b35ac306d20..00000000000 --- a/src/Framework/MockObject/MockType.php +++ /dev/null @@ -1,18 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -interface MockType -{ - public function generate(): string; -} diff --git a/src/Framework/MockObject/Rule/ConsecutiveParameters.php b/src/Framework/MockObject/Rule/ConsecutiveParameters.php deleted file mode 100644 index ac368051dfe..00000000000 --- a/src/Framework/MockObject/Rule/ConsecutiveParameters.php +++ /dev/null @@ -1,136 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Rule; - -use function count; -use function gettype; -use function is_iterable; -use function sprintf; -use PHPUnit\Framework\Constraint\Constraint; -use PHPUnit\Framework\Constraint\IsEqual; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\InvalidParameterGroupException; -use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ConsecutiveParameters implements ParametersRule -{ - /** - * @var array - */ - private $parameterGroups = []; - - /** - * @var array - */ - private $invocations = []; - - /** - * @throws \PHPUnit\Framework\Exception - */ - public function __construct(array $parameterGroups) - { - foreach ($parameterGroups as $index => $parameters) { - if (!is_iterable($parameters)) { - throw new InvalidParameterGroupException( - sprintf( - 'Parameter group #%d must be an array or Traversable, got %s', - $index, - gettype($parameters) - ) - ); - } - - foreach ($parameters as $parameter) { - if (!$parameter instanceof Constraint) { - $parameter = new IsEqual($parameter); - } - - $this->parameterGroups[$index][] = $parameter; - } - } - } - - public function toString(): string - { - return 'with consecutive parameters'; - } - - /** - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function apply(BaseInvocation $invocation): void - { - $this->invocations[] = $invocation; - $callIndex = count($this->invocations) - 1; - - $this->verifyInvocation($invocation, $callIndex); - } - - /** - * @throws \PHPUnit\Framework\ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function verify(): void - { - foreach ($this->invocations as $callIndex => $invocation) { - $this->verifyInvocation($invocation, $callIndex); - } - } - - /** - * Verify a single invocation. - * - * @param int $callIndex - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - private function verifyInvocation(BaseInvocation $invocation, $callIndex): void - { - if (!isset($this->parameterGroups[$callIndex])) { - // no parameter assertion for this call index - return; - } - - if ($invocation === null) { - throw new ExpectationFailedException( - 'Mocked method does not exist.' - ); - } - - $parameters = $this->parameterGroups[$callIndex]; - - if (count($invocation->getParameters()) < count($parameters)) { - throw new ExpectationFailedException( - sprintf( - 'Parameter count for invocation %s is too low.', - $invocation->toString() - ) - ); - } - - foreach ($parameters as $i => $parameter) { - $parameter->evaluate( - $invocation->getParameters()[$i], - sprintf( - 'Parameter %s for invocation #%d %s does not match expected ' . - 'value.', - $i, - $callIndex, - $invocation->toString() - ) - ); - } - } -} diff --git a/src/Framework/MockObject/Rule/InvocationOrder.php b/src/Framework/MockObject/Rule/InvocationOrder.php deleted file mode 100644 index 90aa49350de..00000000000 --- a/src/Framework/MockObject/Rule/InvocationOrder.php +++ /dev/null @@ -1,47 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Rule; - -use function count; -use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; -use PHPUnit\Framework\MockObject\Verifiable; -use PHPUnit\Framework\SelfDescribing; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -abstract class InvocationOrder implements SelfDescribing, Verifiable -{ - /** - * @var BaseInvocation[] - */ - private $invocations = []; - - public function getInvocationCount(): int - { - return count($this->invocations); - } - - public function hasBeenInvoked(): bool - { - return count($this->invocations) > 0; - } - - final public function invoked(BaseInvocation $invocation) - { - $this->invocations[] = $invocation; - - return $this->invokedDo($invocation); - } - - abstract public function matches(BaseInvocation $invocation): bool; - - abstract protected function invokedDo(BaseInvocation $invocation); -} diff --git a/src/Framework/MockObject/Rule/InvokedAtIndex.php b/src/Framework/MockObject/Rule/InvokedAtIndex.php deleted file mode 100644 index 0454ca700e9..00000000000 --- a/src/Framework/MockObject/Rule/InvokedAtIndex.php +++ /dev/null @@ -1,72 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Rule; - -use function sprintf; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class InvokedAtIndex extends InvocationOrder -{ - /** - * @var int - */ - private $sequenceIndex; - - /** - * @var int - */ - private $currentIndex = -1; - - /** - * @param int $sequenceIndex - */ - public function __construct($sequenceIndex) - { - $this->sequenceIndex = $sequenceIndex; - } - - public function toString(): string - { - return 'invoked at sequence index ' . $this->sequenceIndex; - } - - public function matches(BaseInvocation $invocation): bool - { - $this->currentIndex++; - - return $this->currentIndex == $this->sequenceIndex; - } - - /** - * Verifies that the current expectation is valid. If everything is OK the - * code should just return, if not it must throw an exception. - * - * @throws ExpectationFailedException - */ - public function verify(): void - { - if ($this->currentIndex < $this->sequenceIndex) { - throw new ExpectationFailedException( - sprintf( - 'The expected invocation at index %s was never reached.', - $this->sequenceIndex - ) - ); - } - } - - protected function invokedDo(BaseInvocation $invocation): void - { - } -} diff --git a/src/Framework/MockObject/Rule/InvokedAtLeastCount.php b/src/Framework/MockObject/Rule/InvokedAtLeastCount.php deleted file mode 100644 index a84aa655904..00000000000 --- a/src/Framework/MockObject/Rule/InvokedAtLeastCount.php +++ /dev/null @@ -1,64 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Rule; - -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class InvokedAtLeastCount extends InvocationOrder -{ - /** - * @var int - */ - private $requiredInvocations; - - /** - * @param int $requiredInvocations - */ - public function __construct($requiredInvocations) - { - $this->requiredInvocations = $requiredInvocations; - } - - public function toString(): string - { - return 'invoked at least ' . $this->requiredInvocations . ' times'; - } - - /** - * Verifies that the current expectation is valid. If everything is OK the - * code should just return, if not it must throw an exception. - * - * @throws ExpectationFailedException - */ - public function verify(): void - { - $count = $this->getInvocationCount(); - - if ($count < $this->requiredInvocations) { - throw new ExpectationFailedException( - 'Expected invocation at least ' . $this->requiredInvocations . - ' times but it occurred ' . $count . ' time(s).' - ); - } - } - - public function matches(BaseInvocation $invocation): bool - { - return true; - } - - protected function invokedDo(BaseInvocation $invocation): void - { - } -} diff --git a/src/Framework/MockObject/Rule/InvokedAtMostCount.php b/src/Framework/MockObject/Rule/InvokedAtMostCount.php deleted file mode 100644 index c3b815aa45e..00000000000 --- a/src/Framework/MockObject/Rule/InvokedAtMostCount.php +++ /dev/null @@ -1,64 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Rule; - -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class InvokedAtMostCount extends InvocationOrder -{ - /** - * @var int - */ - private $allowedInvocations; - - /** - * @param int $allowedInvocations - */ - public function __construct($allowedInvocations) - { - $this->allowedInvocations = $allowedInvocations; - } - - public function toString(): string - { - return 'invoked at most ' . $this->allowedInvocations . ' times'; - } - - /** - * Verifies that the current expectation is valid. If everything is OK the - * code should just return, if not it must throw an exception. - * - * @throws ExpectationFailedException - */ - public function verify(): void - { - $count = $this->getInvocationCount(); - - if ($count > $this->allowedInvocations) { - throw new ExpectationFailedException( - 'Expected invocation at most ' . $this->allowedInvocations . - ' times but it occurred ' . $count . ' time(s).' - ); - } - } - - public function matches(BaseInvocation $invocation): bool - { - return true; - } - - protected function invokedDo(BaseInvocation $invocation): void - { - } -} diff --git a/src/Framework/MockObject/Rule/InvokedCount.php b/src/Framework/MockObject/Rule/InvokedCount.php deleted file mode 100644 index 188326c91f5..00000000000 --- a/src/Framework/MockObject/Rule/InvokedCount.php +++ /dev/null @@ -1,102 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Rule; - -use function sprintf; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class InvokedCount extends InvocationOrder -{ - /** - * @var int - */ - private $expectedCount; - - /** - * @param int $expectedCount - */ - public function __construct($expectedCount) - { - $this->expectedCount = $expectedCount; - } - - public function isNever(): bool - { - return $this->expectedCount === 0; - } - - public function toString(): string - { - return 'invoked ' . $this->expectedCount . ' time(s)'; - } - - public function matches(BaseInvocation $invocation): bool - { - return true; - } - - /** - * Verifies that the current expectation is valid. If everything is OK the - * code should just return, if not it must throw an exception. - * - * @throws ExpectationFailedException - */ - public function verify(): void - { - $count = $this->getInvocationCount(); - - if ($count !== $this->expectedCount) { - throw new ExpectationFailedException( - sprintf( - 'Method was expected to be called %d times, ' . - 'actually called %d times.', - $this->expectedCount, - $count - ) - ); - } - } - - /** - * @throws ExpectationFailedException - */ - protected function invokedDo(BaseInvocation $invocation): void - { - $count = $this->getInvocationCount(); - - if ($count > $this->expectedCount) { - $message = $invocation->toString() . ' '; - - switch ($this->expectedCount) { - case 0: - $message .= 'was not expected to be called.'; - - break; - - case 1: - $message .= 'was not expected to be called more than once.'; - - break; - - default: - $message .= sprintf( - 'was not expected to be called more than %d times.', - $this->expectedCount - ); - } - - throw new ExpectationFailedException($message); - } - } -} diff --git a/src/Framework/MockObject/Rule/MethodName.php b/src/Framework/MockObject/Rule/MethodName.php deleted file mode 100644 index eff01d4ebd1..00000000000 --- a/src/Framework/MockObject/Rule/MethodName.php +++ /dev/null @@ -1,64 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Rule; - -use function is_string; -use PHPUnit\Framework\Constraint\Constraint; -use PHPUnit\Framework\InvalidArgumentException; -use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; -use PHPUnit\Framework\MockObject\MethodNameConstraint; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class MethodName -{ - /** - * @var Constraint - */ - private $constraint; - - /** - * @param Constraint|string $constraint - * - * @throws InvalidArgumentException - */ - public function __construct($constraint) - { - if (is_string($constraint)) { - $constraint = new MethodNameConstraint($constraint); - } - - if (!$constraint instanceof Constraint) { - throw InvalidArgumentException::create(1, 'PHPUnit\Framework\Constraint\Constraint object or string'); - } - - $this->constraint = $constraint; - } - - public function toString(): string - { - return 'method name ' . $this->constraint->toString(); - } - - /** - * @throws \PHPUnit\Framework\ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function matches(BaseInvocation $invocation): bool - { - return $this->matchesName($invocation->getMethodName()); - } - - public function matchesName(string $methodName): bool - { - return (bool) $this->constraint->evaluate($methodName, '', true); - } -} diff --git a/src/Framework/MockObject/Rule/Parameters.php b/src/Framework/MockObject/Rule/Parameters.php deleted file mode 100644 index 62230b00e5a..00000000000 --- a/src/Framework/MockObject/Rule/Parameters.php +++ /dev/null @@ -1,160 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Rule; - -use function count; -use function get_class; -use function sprintf; -use Exception; -use PHPUnit\Framework\Constraint\Constraint; -use PHPUnit\Framework\Constraint\IsAnything; -use PHPUnit\Framework\Constraint\IsEqual; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Parameters implements ParametersRule -{ - /** - * @var Constraint[] - */ - private $parameters = []; - - /** - * @var BaseInvocation - */ - private $invocation; - - /** - * @var bool|ExpectationFailedException - */ - private $parameterVerificationResult; - - /** - * @throws \PHPUnit\Framework\Exception - */ - public function __construct(array $parameters) - { - foreach ($parameters as $parameter) { - if (!($parameter instanceof Constraint)) { - $parameter = new IsEqual( - $parameter - ); - } - - $this->parameters[] = $parameter; - } - } - - public function toString(): string - { - $text = 'with parameter'; - - foreach ($this->parameters as $index => $parameter) { - if ($index > 0) { - $text .= ' and'; - } - - $text .= ' ' . $index . ' ' . $parameter->toString(); - } - - return $text; - } - - /** - * @throws Exception - */ - public function apply(BaseInvocation $invocation): void - { - $this->invocation = $invocation; - $this->parameterVerificationResult = null; - - try { - $this->parameterVerificationResult = $this->doVerify(); - } catch (ExpectationFailedException $e) { - $this->parameterVerificationResult = $e; - - throw $this->parameterVerificationResult; - } - } - - /** - * Checks if the invocation $invocation matches the current rules. If it - * does the rule will get the invoked() method called which should check - * if an expectation is met. - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function verify(): void - { - $this->doVerify(); - } - - /** - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - private function doVerify(): bool - { - if (isset($this->parameterVerificationResult)) { - return $this->guardAgainstDuplicateEvaluationOfParameterConstraints(); - } - - if ($this->invocation === null) { - throw new ExpectationFailedException('Mocked method does not exist.'); - } - - if (count($this->invocation->getParameters()) < count($this->parameters)) { - $message = 'Parameter count for invocation %s is too low.'; - - // The user called `->with($this->anything())`, but may have meant - // `->withAnyParameters()`. - // - // @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/199 - if (count($this->parameters) === 1 && - get_class($this->parameters[0]) === IsAnything::class) { - $message .= "\nTo allow 0 or more parameters with any value, omit ->with() or use ->withAnyParameters() instead."; - } - - throw new ExpectationFailedException( - sprintf($message, $this->invocation->toString()) - ); - } - - foreach ($this->parameters as $i => $parameter) { - $parameter->evaluate( - $this->invocation->getParameters()[$i], - sprintf( - 'Parameter %s for invocation %s does not match expected ' . - 'value.', - $i, - $this->invocation->toString() - ) - ); - } - - return true; - } - - /** - * @throws ExpectationFailedException - */ - private function guardAgainstDuplicateEvaluationOfParameterConstraints(): bool - { - if ($this->parameterVerificationResult instanceof ExpectationFailedException) { - throw $this->parameterVerificationResult; - } - - return (bool) $this->parameterVerificationResult; - } -} diff --git a/src/Framework/MockObject/Runtime/Api/DoubledCloneMethod.php b/src/Framework/MockObject/Runtime/Api/DoubledCloneMethod.php new file mode 100644 index 00000000000..4da35c0c176 --- /dev/null +++ b/src/Framework/MockObject/Runtime/Api/DoubledCloneMethod.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This trait is not covered by the backward compatibility promise for PHPUnit + */ +trait DoubledCloneMethod +{ + public function __clone(): void + { + $this->__phpunit_state = clone $this->__phpunit_state; + + $this->__phpunit_state()->cloneInvocationHandler(); + } + + abstract public function __phpunit_state(): TestDoubleState; +} diff --git a/src/Framework/MockObject/Runtime/Api/Method.php b/src/Framework/MockObject/Runtime/Api/Method.php new file mode 100644 index 00000000000..c9b4e42e55a --- /dev/null +++ b/src/Framework/MockObject/Runtime/Api/Method.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\Constraint\Constraint; +use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount; +use PHPUnit\Framework\MockObject\Runtime\PropertyHook; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This trait is not covered by the backward compatibility promise for PHPUnit + */ +trait Method +{ + abstract public function __phpunit_getInvocationHandler(): InvocationHandler; + + public function method(Constraint|PropertyHook|string $constraint): InvocationStubber + { + return $this + ->__phpunit_getInvocationHandler() + ->expects(new AnyInvokedCount) + ->method($constraint); + } +} diff --git a/src/Framework/MockObject/Runtime/Api/MockObjectApi.php b/src/Framework/MockObject/Runtime/Api/MockObjectApi.php new file mode 100644 index 00000000000..a7369567686 --- /dev/null +++ b/src/Framework/MockObject/Runtime/Api/MockObjectApi.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\MockObject\Rule\InvocationOrder; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This trait is not covered by the backward compatibility promise for PHPUnit + */ +trait MockObjectApi +{ + /** @noinspection MagicMethodsValidityInspection */ + public function __phpunit_hasMatchers(): bool + { + return $this->__phpunit_getInvocationHandler()->hasMatchers(); + } + + /** @noinspection MagicMethodsValidityInspection */ + public function __phpunit_verify(bool $unsetInvocationMocker = true): void + { + $this->__phpunit_getInvocationHandler()->verify(); + + if ($unsetInvocationMocker) { + $this->__phpunit_unsetInvocationMocker(); + } + } + + abstract public function __phpunit_state(): TestDoubleState; + + abstract public function __phpunit_getInvocationHandler(): InvocationHandler; + + abstract public function __phpunit_unsetInvocationMocker(): void; + + public function expects(InvocationOrder $matcher): InvocationStubber + { + return $this->__phpunit_getInvocationHandler()->expects($matcher); + } +} diff --git a/src/Framework/MockObject/Runtime/Api/ProxiedCloneMethod.php b/src/Framework/MockObject/Runtime/Api/ProxiedCloneMethod.php new file mode 100644 index 00000000000..88797884d77 --- /dev/null +++ b/src/Framework/MockObject/Runtime/Api/ProxiedCloneMethod.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This trait is not covered by the backward compatibility promise for PHPUnit + */ +trait ProxiedCloneMethod +{ + public function __clone(): void + { + $this->__phpunit_state = clone $this->__phpunit_state; + + $this->__phpunit_state()->cloneInvocationHandler(); + + parent::__clone(); + } + + abstract public function __phpunit_state(): TestDoubleState; +} diff --git a/src/Framework/MockObject/Runtime/Api/StubApi.php b/src/Framework/MockObject/Runtime/Api/StubApi.php new file mode 100644 index 00000000000..faba8d40fbb --- /dev/null +++ b/src/Framework/MockObject/Runtime/Api/StubApi.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This trait is not covered by the backward compatibility promise for PHPUnit + */ +trait StubApi +{ + private readonly TestDoubleState $__phpunit_state; + + public function __phpunit_state(): TestDoubleState + { + return $this->__phpunit_state; + } + + /** @noinspection MagicMethodsValidityInspection */ + public function __phpunit_getInvocationHandler(): InvocationHandler + { + return $this->__phpunit_state()->invocationHandler(); + } + + /** @noinspection MagicMethodsValidityInspection */ + public function __phpunit_unsetInvocationMocker(): void + { + $this->__phpunit_state()->unsetInvocationHandler(); + } +} diff --git a/src/Framework/MockObject/Runtime/Api/TestDoubleState.php b/src/Framework/MockObject/Runtime/Api/TestDoubleState.php new file mode 100644 index 00000000000..93b10c14da5 --- /dev/null +++ b/src/Framework/MockObject/Runtime/Api/TestDoubleState.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestDoubleState +{ + /** + * @var list + */ + private readonly array $configurableMethods; + private readonly bool $generateReturnValues; + private ?InvocationHandler $invocationHandler = null; + + /** + * @param list $configurableMethods + */ + public function __construct(array $configurableMethods, bool $generateReturnValues) + { + $this->configurableMethods = $configurableMethods; + $this->generateReturnValues = $generateReturnValues; + } + + public function invocationHandler(): InvocationHandler + { + if ($this->invocationHandler !== null) { + return $this->invocationHandler; + } + + $this->invocationHandler = new InvocationHandler( + $this->configurableMethods, + $this->generateReturnValues, + ); + + return $this->invocationHandler; + } + + public function cloneInvocationHandler(): void + { + if ($this->invocationHandler === null) { + return; + } + + $this->invocationHandler = clone $this->invocationHandler; + } + + public function unsetInvocationHandler(): void + { + $this->invocationHandler = null; + } + + /** + * @return list + */ + public function configurableMethods(): array + { + return $this->configurableMethods; + } + + public function generateReturnValues(): bool + { + return $this->generateReturnValues; + } +} diff --git a/src/Framework/MockObject/Runtime/Interface/InvocationStubber.php b/src/Framework/MockObject/Runtime/Interface/InvocationStubber.php new file mode 100644 index 00000000000..337c82fb3f3 --- /dev/null +++ b/src/Framework/MockObject/Runtime/Interface/InvocationStubber.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\Constraint\Constraint; +use PHPUnit\Framework\MockObject\Runtime\PropertyHook; +use PHPUnit\Framework\MockObject\Stub\Stub; +use Throwable; + +interface InvocationStubber +{ + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @param Constraint|non-empty-string|PropertyHook $constraint + * + * @return $this + */ + public function method(Constraint|PropertyHook|string $constraint): self; + + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @param non-empty-string $id + * + * @return $this + */ + public function id(string $id): self; + + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @param non-empty-string $id + * + * @return $this + */ + public function after(string $id): self; + + /** + * @return $this + */ + public function with(mixed ...$arguments): self; + + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @return $this + */ + public function withAnyParameters(): self; + + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @return $this + */ + public function will(Stub $stub): self; + + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @return $this + */ + public function willReturn(mixed $value, mixed ...$nextValues): self; + + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @return $this + */ + public function willReturnReference(mixed &$reference): self; + + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @param array> $valueMap + * + * @return $this + */ + public function willReturnMap(array $valueMap): self; + + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @return $this + */ + public function willReturnArgument(int $argumentIndex): self; + + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @return $this + */ + public function willReturnCallback(callable $callback): self; + + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @return $this + */ + public function willReturnSelf(): self; + + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @return $this + */ + public function willReturnOnConsecutiveCalls(mixed ...$values): self; + + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @return $this + */ + public function willThrowException(Throwable $exception): self; +} diff --git a/src/Framework/MockObject/Runtime/Interface/MockObject.php b/src/Framework/MockObject/Runtime/Interface/MockObject.php new file mode 100644 index 00000000000..0ddc46dc4fe --- /dev/null +++ b/src/Framework/MockObject/Runtime/Interface/MockObject.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\MockObject\Rule\InvocationOrder; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface MockObject extends Stub +{ + public function expects(InvocationOrder $invocationRule): InvocationStubber; +} diff --git a/src/Framework/MockObject/Runtime/Interface/MockObjectInternal.php b/src/Framework/MockObject/Runtime/Interface/MockObjectInternal.php new file mode 100644 index 00000000000..167d2466db4 --- /dev/null +++ b/src/Framework/MockObject/Runtime/Interface/MockObjectInternal.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface MockObjectInternal extends MockObject, StubInternal +{ + public function __phpunit_hasMatchers(): bool; + + public function __phpunit_verify(bool $unsetInvocationMocker = true): void; +} diff --git a/src/Framework/MockObject/Runtime/Interface/Stub.php b/src/Framework/MockObject/Runtime/Interface/Stub.php new file mode 100644 index 00000000000..6321c98bb39 --- /dev/null +++ b/src/Framework/MockObject/Runtime/Interface/Stub.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\Constraint\Constraint; +use PHPUnit\Framework\MockObject\Runtime\PropertyHook; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface Stub +{ + public function method(Constraint|PropertyHook|string $constraint): InvocationStubber; +} diff --git a/src/Framework/MockObject/Runtime/Interface/StubInternal.php b/src/Framework/MockObject/Runtime/Interface/StubInternal.php new file mode 100644 index 00000000000..6e428ea5ea1 --- /dev/null +++ b/src/Framework/MockObject/Runtime/Interface/StubInternal.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface StubInternal extends Stub +{ + public function __phpunit_state(): TestDoubleState; + + public function __phpunit_getInvocationHandler(): InvocationHandler; + + public function __phpunit_unsetInvocationMocker(): void; +} diff --git a/src/Framework/MockObject/Runtime/Invocation.php b/src/Framework/MockObject/Runtime/Invocation.php new file mode 100644 index 00000000000..d9e3c3f66ff --- /dev/null +++ b/src/Framework/MockObject/Runtime/Invocation.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function array_map; +use function implode; +use function sprintf; +use function str_starts_with; +use function strtolower; +use function substr; +use PHPUnit\Framework\SelfDescribing; +use PHPUnit\Util\Exporter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Invocation implements SelfDescribing +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @var array + */ + private array $parameters; + private string $returnType; + private bool $isReturnTypeNullable; + private MockObjectInternal|StubInternal $object; + + /** + * @param class-string $className + * @param non-empty-string $methodName + * @param array $parameters + */ + public function __construct(string $className, string $methodName, array $parameters, string $returnType, MockObjectInternal|StubInternal $object) + { + $this->className = $className; + $this->methodName = $methodName; + $this->parameters = $parameters; + $this->object = $object; + + if (strtolower($methodName) === '__tostring') { + $returnType = 'string'; + } + + if (str_starts_with($returnType, '?')) { + $returnType = substr($returnType, 1); + $this->isReturnTypeNullable = true; + } else { + $this->isReturnTypeNullable = false; + } + + $this->returnType = $returnType; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } + + /** + * @return array + */ + public function parameters(): array + { + return $this->parameters; + } + + /** + * @throws Exception + */ + public function generateReturnValue(): mixed + { + if ($this->returnType === 'never') { + throw new NeverReturningMethodException( + $this->className, + $this->methodName, + ); + } + + if ($this->isReturnTypeNullable) { + return null; + } + + return (new ReturnValueGenerator)->generate( + $this->className, + $this->methodName, + $this->object, + $this->returnType, + ); + } + + public function toString(): string + { + return sprintf( + '%s::%s(%s)%s', + $this->className, + $this->methodName, + implode( + ', ', + array_map( + [Exporter::class, 'shortenedExport'], + $this->parameters, + ), + ), + $this->returnType !== '' ? sprintf(': %s', $this->returnType) : '', + ); + } + + public function object(): MockObjectInternal|StubInternal + { + return $this->object; + } +} diff --git a/src/Framework/MockObject/Runtime/InvocationHandler.php b/src/Framework/MockObject/Runtime/InvocationHandler.php new file mode 100644 index 00000000000..13074df5ef7 --- /dev/null +++ b/src/Framework/MockObject/Runtime/InvocationHandler.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function array_any; +use function strtolower; +use Exception; +use PHPUnit\Framework\MockObject\Rule\InvocationOrder; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvocationHandler +{ + /** + * @var list + */ + private array $matchers = []; + + /** + * @var array + */ + private array $matcherMap = []; + + /** + * @var list + */ + private readonly array $configurableMethods; + private readonly bool $returnValueGeneration; + + /** + * @param list $configurableMethods + */ + public function __construct(array $configurableMethods, bool $returnValueGeneration) + { + $this->configurableMethods = $configurableMethods; + $this->returnValueGeneration = $returnValueGeneration; + } + + public function hasMatchers(): bool + { + return array_any( + $this->matchers, + static fn (Matcher $matcher) => $matcher->hasMatchers(), + ); + } + + /** + * Looks up the match builder with identification $id and returns it. + * + * @param non-empty-string $id + */ + public function lookupMatcher(string $id): ?Matcher + { + return $this->matcherMap[$id] ?? null; + } + + /** + * Registers a matcher with the identification $id. The matcher can later be + * looked up using lookupMatcher() to figure out if it has been invoked. + * + * @param non-empty-string $id + * + * @throws MatcherAlreadyRegisteredException + */ + public function registerMatcher(string $id, Matcher $matcher): void + { + if (isset($this->matcherMap[$id])) { + throw new MatcherAlreadyRegisteredException($id); + } + + $this->matcherMap[$id] = $matcher; + } + + public function expects(InvocationOrder $rule): InvocationStubber + { + $matcher = new Matcher($rule); + $this->addMatcher($matcher); + + return new InvocationStubberImplementation( + $this, + $matcher, + ...$this->configurableMethods, + ); + } + + /** + * @throws \PHPUnit\Framework\MockObject\Exception + * @throws Exception + */ + public function invoke(Invocation $invocation): mixed + { + $exception = null; + $hasReturnValue = false; + $returnValue = null; + + foreach ($this->matchers as $match) { + try { + if ($match->matches($invocation)) { + $value = $match->invoked($invocation); + + if (!$hasReturnValue) { + $returnValue = $value; + $hasReturnValue = true; + } + } + } catch (Exception $e) { + $exception = $e; + } + } + + if ($exception !== null) { + throw $exception; + } + + if ($hasReturnValue) { + return $returnValue; + } + + if (!$this->returnValueGeneration) { + if (strtolower($invocation->methodName()) === '__tostring') { + return ''; + } + + throw new ReturnValueNotConfiguredException($invocation); + } + + return $invocation->generateReturnValue(); + } + + /** + * @throws Throwable + */ + public function verify(): void + { + foreach ($this->matchers as $matcher) { + $matcher->verify(); + } + } + + private function addMatcher(Matcher $matcher): void + { + $this->matchers[] = $matcher; + } +} diff --git a/src/Framework/MockObject/Runtime/InvocationStubberImplementation.php b/src/Framework/MockObject/Runtime/InvocationStubberImplementation.php new file mode 100644 index 00000000000..3aca45a294b --- /dev/null +++ b/src/Framework/MockObject/Runtime/InvocationStubberImplementation.php @@ -0,0 +1,330 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function array_flip; +use function array_key_exists; +use function array_map; +use function array_merge; +use function array_pop; +use function assert; +use function count; +use function is_string; +use function range; +use function strtolower; +use PHPUnit\Framework\Constraint\Constraint; +use PHPUnit\Framework\InvalidArgumentException; +use PHPUnit\Framework\MockObject\Runtime\PropertyHook; +use PHPUnit\Framework\MockObject\Stub\ConsecutiveCalls; +use PHPUnit\Framework\MockObject\Stub\Exception; +use PHPUnit\Framework\MockObject\Stub\ReturnArgument; +use PHPUnit\Framework\MockObject\Stub\ReturnCallback; +use PHPUnit\Framework\MockObject\Stub\ReturnReference; +use PHPUnit\Framework\MockObject\Stub\ReturnSelf; +use PHPUnit\Framework\MockObject\Stub\ReturnStub; +use PHPUnit\Framework\MockObject\Stub\ReturnValueMap; +use PHPUnit\Framework\MockObject\Stub\Stub; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvocationStubberImplementation implements InvocationStubber +{ + private readonly InvocationHandler $invocationHandler; + private readonly Matcher $matcher; + + /** + * @var list + */ + private readonly array $configurableMethods; + + /** + * @var ?array + */ + private ?array $configurableMethodNames = null; + + public function __construct(InvocationHandler $handler, Matcher $matcher, ConfigurableMethod ...$configurableMethods) + { + $this->invocationHandler = $handler; + $this->matcher = $matcher; + $this->configurableMethods = $configurableMethods; + } + + /** + * @param Constraint|non-empty-string|PropertyHook $constraint + * + * @throws InvalidArgumentException + * @throws MethodCannotBeConfiguredException + * @throws MethodNameAlreadyConfiguredException + * + * @return $this + */ + public function method(Constraint|PropertyHook|string $constraint): InvocationStubber + { + if ($this->matcher->hasMethodNameRule()) { + throw new MethodNameAlreadyConfiguredException; + } + + if ($constraint instanceof PropertyHook) { + $constraint = $constraint->asString(); + } + + if (is_string($constraint)) { + $this->configurableMethodNames ??= array_flip( + array_map( + static fn (ConfigurableMethod $configurable) => strtolower($configurable->name()), + $this->configurableMethods, + ), + ); + + if (!array_key_exists(strtolower($constraint), $this->configurableMethodNames)) { + throw new MethodCannotBeConfiguredException($constraint); + } + } + + $this->matcher->setMethodNameRule(new Rule\MethodName($constraint)); + + return $this; + } + + /** + * @param non-empty-string $id + * + * @throws MatcherAlreadyRegisteredException + * + * @return $this + */ + public function id(string $id): InvocationStubber + { + $this->invocationHandler->registerMatcher($id, $this->matcher); + + return $this; + } + + /** + * @param non-empty-string $id + * + * @return $this + */ + public function after(string $id): InvocationStubber + { + $this->matcher->setAfterMatchBuilderId($id); + + return $this; + } + + /** + * @throws \PHPUnit\Framework\Exception + * @throws MethodNameNotConfiguredException + * @throws MethodParametersAlreadyConfiguredException + * + * @return $this + */ + public function with(mixed ...$arguments): InvocationStubber + { + $this->ensureParametersCanBeConfigured(); + + $this->matcher->setParametersRule(new Rule\Parameters($arguments)); + + return $this; + } + + /** + * @throws MethodNameNotConfiguredException + * @throws MethodParametersAlreadyConfiguredException + * + * @return $this + */ + public function withAnyParameters(): InvocationStubber + { + $this->ensureParametersCanBeConfigured(); + + $this->matcher->setParametersRule(new Rule\AnyParameters); + + return $this; + } + + /** + * @return $this + */ + public function will(Stub $stub): InvocationStubber + { + $this->matcher->setStub($stub); + + return $this; + } + + /** + * @throws IncompatibleReturnValueException + */ + public function willReturn(mixed $value, mixed ...$nextValues): InvocationStubber + { + if (count($nextValues) === 0) { + $this->ensureTypeOfReturnValues([$value]); + + $stub = $value instanceof Stub ? $value : new ReturnStub($value); + + return $this->will($stub); + } + + $values = array_merge([$value], $nextValues); + + $this->ensureTypeOfReturnValues($values); + + $stub = new ConsecutiveCalls($values); + + return $this->will($stub); + } + + public function willReturnReference(mixed &$reference): InvocationStubber + { + $stub = new ReturnReference($reference); + + return $this->will($stub); + } + + public function willReturnMap(array $valueMap): InvocationStubber + { + $method = $this->configuredMethod(); + + assert($method instanceof ConfigurableMethod); + + $numberOfParameters = $method->numberOfParameters(); + $defaultValues = $method->defaultParameterValues(); + $hasDefaultValues = $defaultValues !== []; + + $_valueMap = []; + + foreach ($valueMap as $mapping) { + $numberOfConfiguredParameters = count($mapping) - 1; + + if ($numberOfConfiguredParameters === $numberOfParameters || !$hasDefaultValues) { + $_valueMap[] = $mapping; + + continue; + } + + $_mapping = []; + $returnValue = array_pop($mapping); + + foreach (range(0, $numberOfParameters - 1) as $i) { + if (array_key_exists($i, $mapping)) { + $_mapping[] = $mapping[$i]; + + continue; + } + + if (array_key_exists($i, $defaultValues)) { + $_mapping[] = $defaultValues[$i]; + } + } + + $_mapping[] = $returnValue; + $_valueMap[] = $_mapping; + } + + $stub = new ReturnValueMap($_valueMap); + + return $this->will($stub); + } + + public function willReturnArgument(int $argumentIndex): InvocationStubber + { + $stub = new ReturnArgument($argumentIndex); + + return $this->will($stub); + } + + public function willReturnCallback(callable $callback): InvocationStubber + { + $stub = new ReturnCallback($callback); + + return $this->will($stub); + } + + public function willReturnSelf(): InvocationStubber + { + $stub = new ReturnSelf; + + return $this->will($stub); + } + + public function willReturnOnConsecutiveCalls(mixed ...$values): InvocationStubber + { + $stub = new ConsecutiveCalls($values); + + return $this->will($stub); + } + + public function willThrowException(Throwable $exception): InvocationStubber + { + $stub = new Exception($exception); + + return $this->will($stub); + } + + /** + * @throws MethodNameNotConfiguredException + * @throws MethodParametersAlreadyConfiguredException + */ + private function ensureParametersCanBeConfigured(): void + { + if (!$this->matcher->hasMethodNameRule()) { + throw new MethodNameNotConfiguredException; + } + + if ($this->matcher->hasParametersRule()) { + throw new MethodParametersAlreadyConfiguredException; + } + } + + private function configuredMethod(): ?ConfigurableMethod + { + $configuredMethod = null; + + foreach ($this->configurableMethods as $configurableMethod) { + if ($this->matcher->methodNameRule()->matchesName($configurableMethod->name())) { + if ($configuredMethod !== null) { + return null; + } + + $configuredMethod = $configurableMethod; + } + } + + return $configuredMethod; + } + + /** + * @param array $values + * + * @throws IncompatibleReturnValueException + */ + private function ensureTypeOfReturnValues(array $values): void + { + $configuredMethod = $this->configuredMethod(); + + if ($configuredMethod === null) { + return; + } + + foreach ($values as $value) { + if (!$configuredMethod->mayReturn($value)) { + throw new IncompatibleReturnValueException( + $configuredMethod, + $value, + ); + } + } + } +} diff --git a/src/Framework/MockObject/Runtime/Matcher.php b/src/Framework/MockObject/Runtime/Matcher.php new file mode 100644 index 00000000000..f1587f3b3f1 --- /dev/null +++ b/src/Framework/MockObject/Runtime/Matcher.php @@ -0,0 +1,219 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount; +use PHPUnit\Framework\MockObject\Rule\AnyParameters; +use PHPUnit\Framework\MockObject\Rule\InvocationOrder; +use PHPUnit\Framework\MockObject\Rule\InvokedAtMostCount; +use PHPUnit\Framework\MockObject\Rule\InvokedCount; +use PHPUnit\Framework\MockObject\Rule\MethodName; +use PHPUnit\Framework\MockObject\Rule\ParametersRule; +use PHPUnit\Framework\MockObject\Stub\Stub; +use PHPUnit\Util\ThrowableToStringMapper; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Matcher +{ + private readonly InvocationOrder $invocationRule; + + /** + * @var ?non-empty-string + */ + private ?string $afterMatchBuilderId = null; + private ?MethodName $methodNameRule = null; + private ?ParametersRule $parametersRule = null; + private ?Stub $stub = null; + + public function __construct(InvocationOrder $rule) + { + $this->invocationRule = $rule; + } + + public function hasMatchers(): bool + { + return !$this->invocationRule instanceof AnyInvokedCount; + } + + public function hasMethodNameRule(): bool + { + return $this->methodNameRule !== null; + } + + public function methodNameRule(): MethodName + { + return $this->methodNameRule; + } + + public function setMethodNameRule(MethodName $rule): void + { + $this->methodNameRule = $rule; + } + + public function hasParametersRule(): bool + { + return $this->parametersRule !== null; + } + + public function setParametersRule(ParametersRule $rule): void + { + $this->parametersRule = $rule; + } + + public function setStub(Stub $stub): void + { + $this->stub = $stub; + } + + /** + * @param non-empty-string $id + */ + public function setAfterMatchBuilderId(string $id): void + { + $this->afterMatchBuilderId = $id; + } + + /** + * @throws Exception + * @throws ExpectationFailedException + * @throws MatchBuilderNotFoundException + * @throws MethodNameNotConfiguredException + * @throws RuntimeException + */ + public function invoked(Invocation $invocation): mixed + { + if ($this->methodNameRule === null) { + throw new MethodNameNotConfiguredException; + } + + if ($this->afterMatchBuilderId !== null) { + $matcher = $invocation->object() + ->__phpunit_getInvocationHandler() + ->lookupMatcher($this->afterMatchBuilderId); + + if ($matcher === null) { + throw new MatchBuilderNotFoundException($this->afterMatchBuilderId); + } + } + + $this->invocationRule->invoked($invocation); + + try { + $this->parametersRule?->apply($invocation); + } catch (ExpectationFailedException $e) { + throw new ExpectationFailedException( + sprintf( + "Expectation failed for %s when %s\n%s", + $this->methodNameRule->toString(), + $this->invocationRule->toString(), + $e->getMessage(), + ), + $e->getComparisonFailure(), + ); + } + + if ($this->stub !== null) { + return $this->stub->invoke($invocation); + } + + return $invocation->generateReturnValue(); + } + + /** + * @throws ExpectationFailedException + * @throws MatchBuilderNotFoundException + * @throws MethodNameNotConfiguredException + * @throws RuntimeException + */ + public function matches(Invocation $invocation): bool + { + if ($this->afterMatchBuilderId !== null) { + $matcher = $invocation->object() + ->__phpunit_getInvocationHandler() + ->lookupMatcher($this->afterMatchBuilderId); + + if ($matcher === null) { + throw new MatchBuilderNotFoundException($this->afterMatchBuilderId); + } + + if (!$matcher->invocationRule->hasBeenInvoked()) { + return false; + } + } + + if ($this->methodNameRule === null) { + throw new MethodNameNotConfiguredException; + } + + if (!$this->invocationRule->matches($invocation)) { + return false; + } + + try { + if (!$this->methodNameRule->matches($invocation)) { + return false; + } + } catch (ExpectationFailedException $e) { + throw new ExpectationFailedException( + sprintf( + "Expectation failed for %s when %s\n%s", + $this->methodNameRule->toString(), + $this->invocationRule->toString(), + $e->getMessage(), + ), + $e->getComparisonFailure(), + ); + } + + return true; + } + + /** + * @throws ExpectationFailedException + * @throws MethodNameNotConfiguredException + */ + public function verify(): void + { + if ($this->methodNameRule === null) { + throw new MethodNameNotConfiguredException; + } + + try { + $this->invocationRule->verify(); + + if ($this->parametersRule === null) { + $this->parametersRule = new AnyParameters; + } + + $invocationIsAny = $this->invocationRule instanceof AnyInvokedCount; + $invocationIsNever = $this->invocationRule instanceof InvokedCount && $this->invocationRule->isNever(); + $invocationIsAtMost = $this->invocationRule instanceof InvokedAtMostCount; + + if (!$invocationIsAny && !$invocationIsNever && !$invocationIsAtMost) { + $this->parametersRule->verify(); + } + } catch (ExpectationFailedException $e) { + throw new ExpectationFailedException( + sprintf( + "Expectation failed for %s when %s.\n%s", + $this->methodNameRule->toString(), + $this->invocationRule->toString(), + ThrowableToStringMapper::map($e), + ), + ); + } + } +} diff --git a/src/Framework/MockObject/Runtime/MethodNameConstraint.php b/src/Framework/MockObject/Runtime/MethodNameConstraint.php new file mode 100644 index 00000000000..bb6bf60bdde --- /dev/null +++ b/src/Framework/MockObject/Runtime/MethodNameConstraint.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; +use function strtolower; +use PHPUnit\Framework\Constraint\Constraint; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MethodNameConstraint extends Constraint +{ + private string $methodName; + + public function __construct(string $methodName) + { + $this->methodName = $methodName; + } + + public function toString(): string + { + return sprintf( + 'is "%s"', + $this->methodName, + ); + } + + protected function matches(mixed $other): bool + { + return strtolower($this->methodName) === strtolower((string) $other); + } +} diff --git a/src/Framework/MockObject/Runtime/PropertyHook/PropertyGetHook.php b/src/Framework/MockObject/Runtime/PropertyHook/PropertyGetHook.php new file mode 100644 index 00000000000..14266f55731 --- /dev/null +++ b/src/Framework/MockObject/Runtime/PropertyHook/PropertyGetHook.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Runtime; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PropertyGetHook extends PropertyHook +{ + /** + * @return non-empty-string + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function asString(): string + { + return sprintf( + '$%s::get', + $this->propertyName(), + ); + } +} diff --git a/src/Framework/MockObject/Runtime/PropertyHook/PropertyHook.php b/src/Framework/MockObject/Runtime/PropertyHook/PropertyHook.php new file mode 100644 index 00000000000..a7664f8ed76 --- /dev/null +++ b/src/Framework/MockObject/Runtime/PropertyHook/PropertyHook.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Runtime; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class PropertyHook +{ + /** + * @var non-empty-string + */ + private string $propertyName; + + /** + * @param non-empty-string $propertyName + */ + public static function get(string $propertyName): PropertyGetHook + { + return new PropertyGetHook($propertyName); + } + + /** + * @param non-empty-string $propertyName + */ + public static function set(string $propertyName): PropertySetHook + { + return new PropertySetHook($propertyName); + } + + /** + * @param non-empty-string $propertyName + */ + protected function __construct(string $propertyName) + { + $this->propertyName = $propertyName; + } + + /** + * @return non-empty-string + */ + public function propertyName(): string + { + return $this->propertyName; + } + + /** + * @return non-empty-string + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + abstract public function asString(): string; +} diff --git a/src/Framework/MockObject/Runtime/PropertyHook/PropertySetHook.php b/src/Framework/MockObject/Runtime/PropertyHook/PropertySetHook.php new file mode 100644 index 00000000000..7d4918eb7c6 --- /dev/null +++ b/src/Framework/MockObject/Runtime/PropertyHook/PropertySetHook.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Runtime; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PropertySetHook extends PropertyHook +{ + /** + * @return non-empty-string + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function asString(): string + { + return sprintf( + '$%s::set', + $this->propertyName(), + ); + } +} diff --git a/src/Framework/MockObject/Runtime/ReturnValueGenerator.php b/src/Framework/MockObject/Runtime/ReturnValueGenerator.php new file mode 100644 index 00000000000..f28dfacc3c8 --- /dev/null +++ b/src/Framework/MockObject/Runtime/ReturnValueGenerator.php @@ -0,0 +1,259 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function array_map; +use function explode; +use function in_array; +use function interface_exists; +use function sprintf; +use function str_contains; +use function str_ends_with; +use function str_starts_with; +use function substr; +use PHPUnit\Framework\MockObject\Generator\Generator; +use ReflectionClass; +use ReflectionObject; +use stdClass; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ReturnValueGenerator +{ + /** + * @param class-string $className + * @param non-empty-string $methodName + * + * @throws Exception + */ + public function generate(string $className, string $methodName, StubInternal $testStub, string $returnType): mixed + { + $intersection = false; + $union = false; + + if (str_contains($returnType, '|')) { + $types = explode('|', $returnType); + $union = true; + + foreach ($types as $key => $type) { + if (str_starts_with($type, '(') && str_ends_with($type, ')')) { + $types[$key] = substr($type, 1, -1); + } + } + } elseif (str_contains($returnType, '&')) { + $types = explode('&', $returnType); + $intersection = true; + } else { + $types = [$returnType]; + } + + if (!$intersection) { + $lowerTypes = array_map('strtolower', $types); + + if (in_array('', $lowerTypes, true) || + in_array('null', $lowerTypes, true) || + in_array('mixed', $lowerTypes, true) || + in_array('void', $lowerTypes, true)) { + return null; + } + + if (in_array('true', $lowerTypes, true)) { + return true; + } + + if (in_array('false', $lowerTypes, true) || + in_array('bool', $lowerTypes, true)) { + return false; + } + + if (in_array('float', $lowerTypes, true)) { + return 0.0; + } + + if (in_array('int', $lowerTypes, true)) { + return 0; + } + + if (in_array('string', $lowerTypes, true)) { + return ''; + } + + if (in_array('array', $lowerTypes, true)) { + return []; + } + + if (in_array('static', $lowerTypes, true)) { + return $this->newInstanceOf($testStub, $className, $methodName); + } + + if (in_array('object', $lowerTypes, true)) { + return new stdClass; + } + + if (in_array('callable', $lowerTypes, true) || + in_array('closure', $lowerTypes, true)) { + return static function (): void + { + }; + } + + if (in_array('traversable', $lowerTypes, true) || + in_array('generator', $lowerTypes, true) || + in_array('iterable', $lowerTypes, true)) { + $generator = static function (): \Generator + { + yield from []; + }; + + return $generator(); + } + + if (!$union) { + return $this->testDoubleFor($returnType, $className, $methodName); + } + } + + if ($union) { + foreach ($types as $type) { + if (str_contains($type, '&')) { + $_types = explode('&', $type); + + if ($this->onlyInterfaces($_types)) { + return $this->testDoubleForIntersectionOfInterfaces($_types, $className, $methodName); + } + } + } + } + + if ($intersection && $this->onlyInterfaces($types)) { + return $this->testDoubleForIntersectionOfInterfaces($types, $className, $methodName); + } + + $reason = ''; + + if ($union) { + $reason = ' because the declared return type is a union'; + } elseif ($intersection) { + $reason = ' because the declared return type is an intersection'; + } + + throw new RuntimeException( + sprintf( + 'Return value for %s::%s() cannot be generated%s, please configure a return value for this method', + $className, + $methodName, + $reason, + ), + ); + } + + /** + * @param non-empty-list $types + */ + private function onlyInterfaces(array $types): bool + { + foreach ($types as $type) { + if (!interface_exists($type)) { + return false; + } + } + + return true; + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + * + * @throws RuntimeException + */ + private function newInstanceOf(StubInternal $testStub, string $className, string $methodName): Stub + { + try { + $object = new ReflectionClass($testStub::class)->newInstanceWithoutConstructor(); + $reflector = new ReflectionObject($object); + + $reflector->getProperty('__phpunit_state')->setValue( + $object, + new TestDoubleState( + $testStub->__phpunit_state()->configurableMethods(), + $testStub->__phpunit_state()->generateReturnValues(), + ), + ); + + return $object; + // @codeCoverageIgnoreStart + } catch (Throwable $t) { + throw new RuntimeException( + sprintf( + 'Return value for %s::%s() cannot be generated: %s', + $className, + $methodName, + $t->getMessage(), + ), + ); + // @codeCoverageIgnoreEnd + } + } + + /** + * @param class-string $type + * @param class-string $className + * @param non-empty-string $methodName + * + * @throws RuntimeException + */ + private function testDoubleFor(string $type, string $className, string $methodName): Stub + { + try { + return (new Generator)->testDouble($type, false, [], [], '', false); + // @codeCoverageIgnoreStart + } catch (Throwable $t) { + throw new RuntimeException( + sprintf( + 'Return value for %s::%s() cannot be generated: %s', + $className, + $methodName, + $t->getMessage(), + ), + ); + // @codeCoverageIgnoreEnd + } + } + + /** + * @param non-empty-list $types + * @param class-string $className + * @param non-empty-string $methodName + * + * @throws RuntimeException + */ + private function testDoubleForIntersectionOfInterfaces(array $types, string $className, string $methodName): Stub + { + try { + return (new Generator)->testDoubleForInterfaceIntersection($types, false); + // @codeCoverageIgnoreStart + } catch (Throwable $t) { + throw new RuntimeException( + sprintf( + 'Return value for %s::%s() cannot be generated: %s', + $className, + $methodName, + $t->getMessage(), + ), + ); + // @codeCoverageIgnoreEnd + } + } +} diff --git a/src/Framework/MockObject/Rule/AnyInvokedCount.php b/src/Framework/MockObject/Runtime/Rule/AnyInvokedCount.php similarity index 87% rename from src/Framework/MockObject/Rule/AnyInvokedCount.php rename to src/Framework/MockObject/Runtime/Rule/AnyInvokedCount.php index f93e5686bc4..382f9308116 100644 --- a/src/Framework/MockObject/Rule/AnyInvokedCount.php +++ b/src/Framework/MockObject/Runtime/Rule/AnyInvokedCount.php @@ -12,6 +12,8 @@ use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class AnyInvokedCount extends InvocationOrder @@ -29,8 +31,4 @@ public function matches(BaseInvocation $invocation): bool { return true; } - - protected function invokedDo(BaseInvocation $invocation): void - { - } } diff --git a/src/Framework/MockObject/Rule/AnyParameters.php b/src/Framework/MockObject/Runtime/Rule/AnyParameters.php similarity index 81% rename from src/Framework/MockObject/Rule/AnyParameters.php rename to src/Framework/MockObject/Runtime/Rule/AnyParameters.php index 61de788786b..0bca009951a 100644 --- a/src/Framework/MockObject/Rule/AnyParameters.php +++ b/src/Framework/MockObject/Runtime/Rule/AnyParameters.php @@ -12,15 +12,15 @@ use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class AnyParameters implements ParametersRule { - public function toString(): string - { - return 'with any parameters'; - } - + /** + * @throws void + */ public function apply(BaseInvocation $invocation): void { } diff --git a/src/Framework/MockObject/Runtime/Rule/InvocationOrder.php b/src/Framework/MockObject/Runtime/Rule/InvocationOrder.php new file mode 100644 index 00000000000..c3fb8f29d83 --- /dev/null +++ b/src/Framework/MockObject/Runtime/Rule/InvocationOrder.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Rule; + +use function count; +use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; +use PHPUnit\Framework\SelfDescribing; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract class InvocationOrder implements SelfDescribing +{ + /** + * @var list + */ + private array $invocations = []; + + public function numberOfInvocations(): int + { + return count($this->invocations); + } + + public function hasBeenInvoked(): bool + { + return count($this->invocations) > 0; + } + + final public function invoked(BaseInvocation $invocation): void + { + $this->invocations[] = $invocation; + + $this->invokedDo($invocation); + } + + abstract public function matches(BaseInvocation $invocation): bool; + + abstract public function verify(): void; + + protected function invokedDo(BaseInvocation $invocation): void + { + } +} diff --git a/src/Framework/MockObject/Runtime/Rule/InvokedAtLeastCount.php b/src/Framework/MockObject/Runtime/Rule/InvokedAtLeastCount.php new file mode 100644 index 00000000000..a78d933d194 --- /dev/null +++ b/src/Framework/MockObject/Runtime/Rule/InvokedAtLeastCount.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Rule; + +use function sprintf; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvokedAtLeastCount extends InvocationOrder +{ + private readonly int $requiredInvocations; + + public function __construct(int $requiredInvocations) + { + $this->requiredInvocations = $requiredInvocations; + } + + public function toString(): string + { + return sprintf( + 'invoked at least %d time%s', + $this->requiredInvocations, + $this->requiredInvocations !== 1 ? 's' : '', + ); + } + + /** + * Verifies that the current expectation is valid. If everything is OK the + * code should just return, if not it must throw an exception. + * + * @throws ExpectationFailedException + */ + public function verify(): void + { + $actualInvocations = $this->numberOfInvocations(); + + if ($actualInvocations < $this->requiredInvocations) { + throw new ExpectationFailedException( + sprintf( + 'Expected invocation at least %d time%s but it occurred %d time%s.', + $this->requiredInvocations, + $this->requiredInvocations !== 1 ? 's' : '', + $actualInvocations, + $actualInvocations !== 1 ? 's' : '', + ), + ); + } + } + + public function matches(BaseInvocation $invocation): bool + { + return true; + } +} diff --git a/src/Framework/MockObject/Rule/InvokedAtLeastOnce.php b/src/Framework/MockObject/Runtime/Rule/InvokedAtLeastOnce.php similarity index 87% rename from src/Framework/MockObject/Rule/InvokedAtLeastOnce.php rename to src/Framework/MockObject/Runtime/Rule/InvokedAtLeastOnce.php index d0ad1f80159..91026f53d3f 100644 --- a/src/Framework/MockObject/Rule/InvokedAtLeastOnce.php +++ b/src/Framework/MockObject/Runtime/Rule/InvokedAtLeastOnce.php @@ -13,6 +13,8 @@ use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class InvokedAtLeastOnce extends InvocationOrder @@ -30,11 +32,11 @@ public function toString(): string */ public function verify(): void { - $count = $this->getInvocationCount(); + $count = $this->numberOfInvocations(); if ($count < 1) { throw new ExpectationFailedException( - 'Expected invocation at least once but it never occurred.' + 'Expected invocation at least once but it never occurred.', ); } } @@ -43,8 +45,4 @@ public function matches(BaseInvocation $invocation): bool { return true; } - - protected function invokedDo(BaseInvocation $invocation): void - { - } } diff --git a/src/Framework/MockObject/Runtime/Rule/InvokedAtMostCount.php b/src/Framework/MockObject/Runtime/Rule/InvokedAtMostCount.php new file mode 100644 index 00000000000..0cfda5e182d --- /dev/null +++ b/src/Framework/MockObject/Runtime/Rule/InvokedAtMostCount.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Rule; + +use function sprintf; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvokedAtMostCount extends InvocationOrder +{ + private readonly int $allowedInvocations; + + public function __construct(int $allowedInvocations) + { + $this->allowedInvocations = $allowedInvocations; + } + + public function toString(): string + { + return sprintf( + 'invoked at most %d time%s', + $this->allowedInvocations, + $this->allowedInvocations !== 1 ? 's' : '', + ); + } + + /** + * Verifies that the current expectation is valid. If everything is OK the + * code should just return, if not it must throw an exception. + * + * @throws ExpectationFailedException + */ + public function verify(): void + { + $actualInvocations = $this->numberOfInvocations(); + + if ($actualInvocations > $this->allowedInvocations) { + throw new ExpectationFailedException( + sprintf( + 'Expected invocation at most %d time%s but it occurred %d time%s.', + $this->allowedInvocations, + $this->allowedInvocations !== 1 ? 's' : '', + $actualInvocations, + $actualInvocations !== 1 ? 's' : '', + ), + ); + } + } + + public function matches(BaseInvocation $invocation): bool + { + return true; + } +} diff --git a/src/Framework/MockObject/Runtime/Rule/InvokedCount.php b/src/Framework/MockObject/Runtime/Rule/InvokedCount.php new file mode 100644 index 00000000000..3f0e505a0db --- /dev/null +++ b/src/Framework/MockObject/Runtime/Rule/InvokedCount.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Rule; + +use function sprintf; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvokedCount extends InvocationOrder +{ + private readonly int $expectedCount; + + public function __construct(int $expectedCount) + { + $this->expectedCount = $expectedCount; + } + + public function isNever(): bool + { + return $this->expectedCount === 0; + } + + public function toString(): string + { + return sprintf( + 'invoked %d time%s', + $this->expectedCount, + $this->expectedCount !== 1 ? 's' : '', + ); + } + + public function matches(BaseInvocation $invocation): bool + { + return true; + } + + /** + * Verifies that the current expectation is valid. If everything is OK the + * code should just return, if not it must throw an exception. + * + * @throws ExpectationFailedException + */ + public function verify(): void + { + $actualCount = $this->numberOfInvocations(); + + if ($actualCount !== $this->expectedCount) { + throw new ExpectationFailedException( + sprintf( + 'Method was expected to be called %d time%s, actually called %d time%s.', + $this->expectedCount, + $this->expectedCount !== 1 ? 's' : '', + $actualCount, + $actualCount !== 1 ? 's' : '', + ), + ); + } + } + + /** + * @throws ExpectationFailedException + */ + protected function invokedDo(BaseInvocation $invocation): void + { + $count = $this->numberOfInvocations(); + + if ($count > $this->expectedCount) { + $message = $invocation->toString() . ' '; + + $message .= match ($this->expectedCount) { + 0 => 'was not expected to be called.', + 1 => 'was not expected to be called more than once.', + default => sprintf( + 'was not expected to be called more than %d times.', + $this->expectedCount, + ), + }; + + throw new ExpectationFailedException($message); + } + } +} diff --git a/src/Framework/MockObject/Runtime/Rule/MethodName.php b/src/Framework/MockObject/Runtime/Rule/MethodName.php new file mode 100644 index 00000000000..219a8087d8d --- /dev/null +++ b/src/Framework/MockObject/Runtime/Rule/MethodName.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Rule; + +use function is_string; +use PHPUnit\Framework\Constraint\Constraint; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\InvalidArgumentException; +use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; +use PHPUnit\Framework\MockObject\MethodNameConstraint; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class MethodName +{ + private Constraint $constraint; + + /** + * @throws InvalidArgumentException + */ + public function __construct(Constraint|string $constraint) + { + if (is_string($constraint)) { + $constraint = new MethodNameConstraint($constraint); + } + + $this->constraint = $constraint; + } + + public function toString(): string + { + return 'method name ' . $this->constraint->toString(); + } + + /** + * @throws ExpectationFailedException + */ + public function matches(BaseInvocation $invocation): bool + { + return $this->matchesName($invocation->methodName()); + } + + /** + * @throws ExpectationFailedException + */ + public function matchesName(string $methodName): bool + { + return (bool) $this->constraint->evaluate($methodName, '', true); + } +} diff --git a/src/Framework/MockObject/Runtime/Rule/Parameters.php b/src/Framework/MockObject/Runtime/Rule/Parameters.php new file mode 100644 index 00000000000..17df3f0a73a --- /dev/null +++ b/src/Framework/MockObject/Runtime/Rule/Parameters.php @@ -0,0 +1,154 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Rule; + +use function count; +use function sprintf; +use Exception; +use PHPUnit\Framework\Constraint\Callback; +use PHPUnit\Framework\Constraint\Constraint; +use PHPUnit\Framework\Constraint\IsAnything; +use PHPUnit\Framework\Constraint\IsEqual; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; +use PHPUnit\Util\Test; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Parameters implements ParametersRule +{ + /** + * @var list + */ + private array $parameters = []; + private ?BaseInvocation $invocation = null; + private null|bool|ExpectationFailedException $parameterVerificationResult; + + /** + * @param array $parameters + * + * @throws \PHPUnit\Framework\Exception + */ + public function __construct(array $parameters) + { + foreach ($parameters as $parameter) { + if (!$parameter instanceof Constraint) { + $parameter = new IsEqual( + $parameter, + ); + } + + $this->parameters[] = $parameter; + } + } + + /** + * @throws Exception + */ + public function apply(BaseInvocation $invocation): void + { + $this->invocation = $invocation; + $this->parameterVerificationResult = null; + + try { + $this->parameterVerificationResult = $this->doVerify(); + } catch (ExpectationFailedException $e) { + $this->parameterVerificationResult = $e; + + throw $this->parameterVerificationResult; + } + } + + /** + * Checks if the invocation $invocation matches the current rules. If it + * does the rule will get the invoked() method called which should check + * if an expectation is met. + * + * @throws ExpectationFailedException + */ + public function verify(): void + { + $this->doVerify(); + } + + /** + * @throws ExpectationFailedException + */ + private function doVerify(): bool + { + if (isset($this->parameterVerificationResult)) { + return $this->guardAgainstDuplicateEvaluationOfParameterConstraints(); + } + + if ($this->invocation === null) { + throw new ExpectationFailedException('Doubled method does not exist.'); + } + + if (count($this->invocation->parameters()) < count($this->parameters)) { + $message = 'Parameter count for invocation %s is too low.'; + + // The user called `->with($this->anything())`, but may have meant + // `->withAnyParameters()`. + // + // @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/199 + if (count($this->parameters) === 1 && + $this->parameters[0]::class === IsAnything::class) { + $message .= "\nTo allow 0 or more parameters with any value, omit ->with() or use ->withAnyParameters() instead."; + } + + $this->incrementAssertionCount(); + + throw new ExpectationFailedException( + sprintf($message, $this->invocation->toString()), + ); + } + + foreach ($this->parameters as $i => $parameter) { + if ($parameter instanceof Callback && $parameter->isVariadic()) { + $other = $this->invocation->parameters(); + } else { + $other = $this->invocation->parameters()[$i]; + } + + $this->incrementAssertionCount(); + + $parameter->evaluate( + $other, + sprintf( + 'Parameter %s for invocation %s does not match expected value.', + $i, + $this->invocation->toString(), + ), + ); + } + + return true; + } + + /** + * @throws ExpectationFailedException + */ + private function guardAgainstDuplicateEvaluationOfParameterConstraints(): bool + { + if ($this->parameterVerificationResult instanceof ExpectationFailedException) { + throw $this->parameterVerificationResult; + } + + return (bool) $this->parameterVerificationResult; + } + + private function incrementAssertionCount(): void + { + Test::currentTestCase()->addToAssertionCount(1); + } +} diff --git a/src/Framework/MockObject/Rule/ParametersRule.php b/src/Framework/MockObject/Runtime/Rule/ParametersRule.php similarity index 80% rename from src/Framework/MockObject/Rule/ParametersRule.php rename to src/Framework/MockObject/Runtime/Rule/ParametersRule.php index 0c9f1910d5d..03cfe2a48fa 100644 --- a/src/Framework/MockObject/Rule/ParametersRule.php +++ b/src/Framework/MockObject/Runtime/Rule/ParametersRule.php @@ -11,10 +11,11 @@ use PHPUnit\Framework\ExpectationFailedException; use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; -use PHPUnit\Framework\MockObject\Verifiable; -use PHPUnit\Framework\SelfDescribing; -interface ParametersRule extends SelfDescribing, Verifiable +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ParametersRule { /** * @throws ExpectationFailedException if the invocation violates the rule diff --git a/src/Framework/MockObject/Runtime/Stub/ConsecutiveCalls.php b/src/Framework/MockObject/Runtime/Stub/ConsecutiveCalls.php new file mode 100644 index 00000000000..b518f1cf4b3 --- /dev/null +++ b/src/Framework/MockObject/Runtime/Stub/ConsecutiveCalls.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use function array_shift; +use function count; +use PHPUnit\Framework\MockObject\Invocation; +use PHPUnit\Framework\MockObject\NoMoreReturnValuesConfiguredException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ConsecutiveCalls implements Stub +{ + /** + * @var array + */ + private array $stack; + private int $numberOfConfiguredReturnValues; + + /** + * @param array $stack + */ + public function __construct(array $stack) + { + $this->stack = $stack; + $this->numberOfConfiguredReturnValues = count($stack); + } + + /** + * @throws NoMoreReturnValuesConfiguredException + */ + public function invoke(Invocation $invocation): mixed + { + if ($this->stack === []) { + throw new NoMoreReturnValuesConfiguredException( + $invocation, + $this->numberOfConfiguredReturnValues, + ); + } + + $value = array_shift($this->stack); + + if ($value instanceof Stub) { + $value = $value->invoke($invocation); + } + + return $value; + } +} diff --git a/src/Framework/MockObject/Runtime/Stub/Exception.php b/src/Framework/MockObject/Runtime/Stub/Exception.php new file mode 100644 index 00000000000..ce0a6804a2c --- /dev/null +++ b/src/Framework/MockObject/Runtime/Stub/Exception.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use PHPUnit\Framework\MockObject\Invocation; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Exception implements Stub +{ + private Throwable $exception; + + public function __construct(Throwable $exception) + { + $this->exception = $exception; + } + + /** + * @throws Throwable + */ + public function invoke(Invocation $invocation): never + { + throw $this->exception; + } +} diff --git a/src/Framework/MockObject/Runtime/Stub/ReturnArgument.php b/src/Framework/MockObject/Runtime/Stub/ReturnArgument.php new file mode 100644 index 00000000000..daca5099d03 --- /dev/null +++ b/src/Framework/MockObject/Runtime/Stub/ReturnArgument.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use PHPUnit\Framework\MockObject\Invocation; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ReturnArgument implements Stub +{ + private int $argumentIndex; + + public function __construct(int $argumentIndex) + { + $this->argumentIndex = $argumentIndex; + } + + public function invoke(Invocation $invocation): mixed + { + return $invocation->parameters()[$this->argumentIndex] ?? null; + } +} diff --git a/src/Framework/MockObject/Runtime/Stub/ReturnCallback.php b/src/Framework/MockObject/Runtime/Stub/ReturnCallback.php new file mode 100644 index 00000000000..4e4cd531025 --- /dev/null +++ b/src/Framework/MockObject/Runtime/Stub/ReturnCallback.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use function call_user_func_array; +use PHPUnit\Framework\MockObject\Invocation; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ReturnCallback implements Stub +{ + /** + * @var callable + */ + private $callback; + + public function __construct(callable $callback) + { + $this->callback = $callback; + } + + public function invoke(Invocation $invocation): mixed + { + return call_user_func_array($this->callback, $invocation->parameters()); + } +} diff --git a/src/Framework/MockObject/Runtime/Stub/ReturnReference.php b/src/Framework/MockObject/Runtime/Stub/ReturnReference.php new file mode 100644 index 00000000000..448df45273d --- /dev/null +++ b/src/Framework/MockObject/Runtime/Stub/ReturnReference.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use PHPUnit\Framework\MockObject\Invocation; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ReturnReference implements Stub +{ + private mixed $reference; + + public function __construct(mixed &$reference) + { + $this->reference = &$reference; + } + + public function invoke(Invocation $invocation): mixed + { + return $this->reference; + } +} diff --git a/src/Framework/MockObject/Runtime/Stub/ReturnSelf.php b/src/Framework/MockObject/Runtime/Stub/ReturnSelf.php new file mode 100644 index 00000000000..4101d71acd1 --- /dev/null +++ b/src/Framework/MockObject/Runtime/Stub/ReturnSelf.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use PHPUnit\Framework\MockObject\Invocation; +use PHPUnit\Framework\MockObject\RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ReturnSelf implements Stub +{ + /** + * @throws RuntimeException + */ + public function invoke(Invocation $invocation): object + { + return $invocation->object(); + } +} diff --git a/src/Framework/MockObject/Runtime/Stub/ReturnStub.php b/src/Framework/MockObject/Runtime/Stub/ReturnStub.php new file mode 100644 index 00000000000..a2d881c3344 --- /dev/null +++ b/src/Framework/MockObject/Runtime/Stub/ReturnStub.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use PHPUnit\Framework\MockObject\Invocation; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ReturnStub implements Stub +{ + private mixed $value; + + public function __construct(mixed $value) + { + $this->value = $value; + } + + public function invoke(Invocation $invocation): mixed + { + return $this->value; + } +} diff --git a/src/Framework/MockObject/Runtime/Stub/ReturnValueMap.php b/src/Framework/MockObject/Runtime/Stub/ReturnValueMap.php new file mode 100644 index 00000000000..c54abc35370 --- /dev/null +++ b/src/Framework/MockObject/Runtime/Stub/ReturnValueMap.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use function array_pop; +use function count; +use function is_array; +use PHPUnit\Framework\MockObject\Invocation; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ReturnValueMap implements Stub +{ + /** + * @var array + */ + private array $valueMap; + + /** + * @param array $valueMap + */ + public function __construct(array $valueMap) + { + $this->valueMap = $valueMap; + } + + public function invoke(Invocation $invocation): mixed + { + $parameterCount = count($invocation->parameters()); + + foreach ($this->valueMap as $map) { + if (!is_array($map) || $parameterCount !== (count($map) - 1)) { + continue; + } + + $return = array_pop($map); + + if ($invocation->parameters() === $map) { + return $return; + } + } + + return null; + } +} diff --git a/src/Framework/MockObject/Runtime/Stub/Stub.php b/src/Framework/MockObject/Runtime/Stub/Stub.php new file mode 100644 index 00000000000..46d9e53a933 --- /dev/null +++ b/src/Framework/MockObject/Runtime/Stub/Stub.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use PHPUnit\Framework\MockObject\Invocation; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Stub +{ + /** + * Fakes the processing of the invocation $invocation by returning a + * specific value. + */ + public function invoke(Invocation $invocation): mixed; +} diff --git a/src/Framework/MockObject/Stub.php b/src/Framework/MockObject/Stub.php deleted file mode 100644 index f7358afabfc..00000000000 --- a/src/Framework/MockObject/Stub.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use PHPUnit\Framework\MockObject\Builder\InvocationStubber; - -/** - * @method InvocationStubber method($constraint) - */ -interface Stub -{ - public function __phpunit_getInvocationHandler(): InvocationHandler; - - public function __phpunit_hasMatchers(): bool; - - public function __phpunit_setReturnValueGeneration(bool $returnValueGeneration): void; -} diff --git a/src/Framework/MockObject/Stub/ConsecutiveCalls.php b/src/Framework/MockObject/Stub/ConsecutiveCalls.php deleted file mode 100644 index 0dcf386b3fb..00000000000 --- a/src/Framework/MockObject/Stub/ConsecutiveCalls.php +++ /dev/null @@ -1,57 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Stub; - -use function array_shift; -use function sprintf; -use PHPUnit\Framework\MockObject\Invocation; -use SebastianBergmann\Exporter\Exporter; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ConsecutiveCalls implements Stub -{ - /** - * @var array - */ - private $stack; - - /** - * @var mixed - */ - private $value; - - public function __construct(array $stack) - { - $this->stack = $stack; - } - - public function invoke(Invocation $invocation) - { - $this->value = array_shift($this->stack); - - if ($this->value instanceof Stub) { - $this->value = $this->value->invoke($invocation); - } - - return $this->value; - } - - public function toString(): string - { - $exporter = new Exporter; - - return sprintf( - 'return user-specified value %s', - $exporter->export($this->value) - ); - } -} diff --git a/src/Framework/MockObject/Stub/Exception.php b/src/Framework/MockObject/Stub/Exception.php deleted file mode 100644 index 5d64c96a500..00000000000 --- a/src/Framework/MockObject/Stub/Exception.php +++ /dev/null @@ -1,46 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Stub; - -use function sprintf; -use PHPUnit\Framework\MockObject\Invocation; -use SebastianBergmann\Exporter\Exporter; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Exception implements Stub -{ - private $exception; - - public function __construct(Throwable $exception) - { - $this->exception = $exception; - } - - /** - * @throws Throwable - */ - public function invoke(Invocation $invocation): void - { - throw $this->exception; - } - - public function toString(): string - { - $exporter = new Exporter; - - return sprintf( - 'raise user-specified exception %s', - $exporter->export($this->exception) - ); - } -} diff --git a/src/Framework/MockObject/Stub/ReturnArgument.php b/src/Framework/MockObject/Stub/ReturnArgument.php deleted file mode 100644 index c7b3f8f410a..00000000000 --- a/src/Framework/MockObject/Stub/ReturnArgument.php +++ /dev/null @@ -1,41 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Stub; - -use function sprintf; -use PHPUnit\Framework\MockObject\Invocation; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ReturnArgument implements Stub -{ - /** - * @var int - */ - private $argumentIndex; - - public function __construct($argumentIndex) - { - $this->argumentIndex = $argumentIndex; - } - - public function invoke(Invocation $invocation) - { - if (isset($invocation->getParameters()[$this->argumentIndex])) { - return $invocation->getParameters()[$this->argumentIndex]; - } - } - - public function toString(): string - { - return sprintf('return argument #%d', $this->argumentIndex); - } -} diff --git a/src/Framework/MockObject/Stub/ReturnCallback.php b/src/Framework/MockObject/Stub/ReturnCallback.php deleted file mode 100644 index e02181e90b5..00000000000 --- a/src/Framework/MockObject/Stub/ReturnCallback.php +++ /dev/null @@ -1,59 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Stub; - -use function call_user_func_array; -use function get_class; -use function is_array; -use function is_object; -use function sprintf; -use PHPUnit\Framework\MockObject\Invocation; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ReturnCallback implements Stub -{ - private $callback; - - public function __construct($callback) - { - $this->callback = $callback; - } - - public function invoke(Invocation $invocation) - { - return call_user_func_array($this->callback, $invocation->getParameters()); - } - - public function toString(): string - { - if (is_array($this->callback)) { - if (is_object($this->callback[0])) { - $class = get_class($this->callback[0]); - $type = '->'; - } else { - $class = $this->callback[0]; - $type = '::'; - } - - return sprintf( - 'return result of user defined callback %s%s%s() with the ' . - 'passed arguments', - $class, - $type, - $this->callback[1] - ); - } - - return 'return result of user defined callback ' . $this->callback . - ' with the passed arguments'; - } -} diff --git a/src/Framework/MockObject/Stub/ReturnReference.php b/src/Framework/MockObject/Stub/ReturnReference.php deleted file mode 100644 index 0d288cebe07..00000000000 --- a/src/Framework/MockObject/Stub/ReturnReference.php +++ /dev/null @@ -1,45 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Stub; - -use function sprintf; -use PHPUnit\Framework\MockObject\Invocation; -use SebastianBergmann\Exporter\Exporter; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ReturnReference implements Stub -{ - /** - * @var mixed - */ - private $reference; - - public function __construct(&$reference) - { - $this->reference = &$reference; - } - - public function invoke(Invocation $invocation) - { - return $this->reference; - } - - public function toString(): string - { - $exporter = new Exporter; - - return sprintf( - 'return user-specified reference %s', - $exporter->export($this->reference) - ); - } -} diff --git a/src/Framework/MockObject/Stub/ReturnSelf.php b/src/Framework/MockObject/Stub/ReturnSelf.php deleted file mode 100644 index 6d2137bfbf8..00000000000 --- a/src/Framework/MockObject/Stub/ReturnSelf.php +++ /dev/null @@ -1,32 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Stub; - -use PHPUnit\Framework\MockObject\Invocation; -use PHPUnit\Framework\MockObject\RuntimeException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ReturnSelf implements Stub -{ - /** - * @throws RuntimeException - */ - public function invoke(Invocation $invocation) - { - return $invocation->getObject(); - } - - public function toString(): string - { - return 'return the current object'; - } -} diff --git a/src/Framework/MockObject/Stub/ReturnStub.php b/src/Framework/MockObject/Stub/ReturnStub.php deleted file mode 100644 index fbcd0a07a80..00000000000 --- a/src/Framework/MockObject/Stub/ReturnStub.php +++ /dev/null @@ -1,45 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Stub; - -use function sprintf; -use PHPUnit\Framework\MockObject\Invocation; -use SebastianBergmann\Exporter\Exporter; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ReturnStub implements Stub -{ - /** - * @var mixed - */ - private $value; - - public function __construct($value) - { - $this->value = $value; - } - - public function invoke(Invocation $invocation) - { - return $this->value; - } - - public function toString(): string - { - $exporter = new Exporter; - - return sprintf( - 'return user-specified value %s', - $exporter->export($this->value) - ); - } -} diff --git a/src/Framework/MockObject/Stub/ReturnValueMap.php b/src/Framework/MockObject/Stub/ReturnValueMap.php deleted file mode 100644 index 5fcd3a09adf..00000000000 --- a/src/Framework/MockObject/Stub/ReturnValueMap.php +++ /dev/null @@ -1,53 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Stub; - -use function array_pop; -use function count; -use function is_array; -use PHPUnit\Framework\MockObject\Invocation; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ReturnValueMap implements Stub -{ - /** - * @var array - */ - private $valueMap; - - public function __construct(array $valueMap) - { - $this->valueMap = $valueMap; - } - - public function invoke(Invocation $invocation) - { - $parameterCount = count($invocation->getParameters()); - - foreach ($this->valueMap as $map) { - if (!is_array($map) || $parameterCount !== (count($map) - 1)) { - continue; - } - - $return = array_pop($map); - - if ($invocation->getParameters() === $map) { - return $return; - } - } - } - - public function toString(): string - { - return 'return value from a map'; - } -} diff --git a/src/Framework/MockObject/Stub/Stub.php b/src/Framework/MockObject/Stub/Stub.php deleted file mode 100644 index 15cfce5c3cd..00000000000 --- a/src/Framework/MockObject/Stub/Stub.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Stub; - -use PHPUnit\Framework\MockObject\Invocation; -use PHPUnit\Framework\SelfDescribing; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -interface Stub extends SelfDescribing -{ - /** - * Fakes the processing of the invocation $invocation by returning a - * specific value. - * - * @param Invocation $invocation The invocation which was mocked and matched by the current method and argument matchers - */ - public function invoke(Invocation $invocation); -} diff --git a/src/Framework/MockObject/Verifiable.php b/src/Framework/MockObject/Verifiable.php deleted file mode 100644 index 8c9a82c5aa7..00000000000 --- a/src/Framework/MockObject/Verifiable.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use PHPUnit\Framework\ExpectationFailedException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -interface Verifiable -{ - /** - * Verifies that the current expectation is valid. If everything is OK the - * code should just return, if not it must throw an exception. - * - * @throws ExpectationFailedException - */ - public function verify(): void; -} diff --git a/src/Framework/NativeType.php b/src/Framework/NativeType.php new file mode 100644 index 00000000000..0220e8845be --- /dev/null +++ b/src/Framework/NativeType.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +enum NativeType: string +{ + case Array = 'array'; + case Bool = 'bool'; + case Callable = 'callable'; + case ClosedResource = 'resource (closed)'; + case Float = 'float'; + case Int = 'int'; + case Iterable = 'iterable'; + case Null = 'null'; + case Numeric = 'numeric'; + case Object = 'object'; + case Resource = 'resource'; + case Scalar = 'scalar'; + case String = 'string'; +} diff --git a/src/Framework/Reorderable.php b/src/Framework/Reorderable.php index 34951f8dc97..10f6e9431e1 100644 --- a/src/Framework/Reorderable.php +++ b/src/Framework/Reorderable.php @@ -10,6 +10,8 @@ namespace PHPUnit\Framework; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ interface Reorderable diff --git a/src/Framework/SelfDescribing.php b/src/Framework/SelfDescribing.php index 73034f650ab..bdf91a4d118 100644 --- a/src/Framework/SelfDescribing.php +++ b/src/Framework/SelfDescribing.php @@ -10,7 +10,9 @@ namespace PHPUnit\Framework; /** - * @internal This class is not covered by the backward compatibility promise for PHPUnit + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit */ interface SelfDescribing { diff --git a/src/Framework/SkippedTest.php b/src/Framework/SkippedTest.php deleted file mode 100644 index a12aa402da5..00000000000 --- a/src/Framework/SkippedTest.php +++ /dev/null @@ -1,19 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -interface SkippedTest extends Throwable -{ -} diff --git a/src/Framework/SkippedTestCase.php b/src/Framework/SkippedTestCase.php deleted file mode 100644 index b88dca362ee..00000000000 --- a/src/Framework/SkippedTestCase.php +++ /dev/null @@ -1,71 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class SkippedTestCase extends TestCase -{ - /** - * @var bool - */ - protected $backupGlobals = false; - - /** - * @var bool - */ - protected $backupStaticAttributes = false; - - /** - * @var bool - */ - protected $runTestInSeparateProcess = false; - - /** - * @var bool - */ - protected $useErrorHandler = false; - - /** - * @var string - */ - private $message; - - public function __construct(string $className, string $methodName, string $message = '') - { - parent::__construct($className . '::' . $methodName); - - $this->message = $message; - } - - public function getMessage(): string - { - return $this->message; - } - - /** - * Returns a string representation of the test case. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function toString(): string - { - return $this->getName(); - } - - /** - * @throws Exception - */ - protected function runTest(): void - { - $this->markTestSkipped($this->message); - } -} diff --git a/src/Framework/Test.php b/src/Framework/Test.php index 7740afc271e..b3e862f7c4e 100644 --- a/src/Framework/Test.php +++ b/src/Framework/Test.php @@ -12,12 +12,9 @@ use Countable; /** - * A Test can be run and collect its results. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ interface Test extends Countable { - /** - * Runs a test and collects its result in a TestResult instance. - */ - public function run(TestResult $result = null): TestResult; + public function run(): void; } diff --git a/src/Framework/TestBuilder.php b/src/Framework/TestBuilder.php index 583a9f2c485..3eb4b1a02de 100644 --- a/src/Framework/TestBuilder.php +++ b/src/Framework/TestBuilder.php @@ -9,231 +9,272 @@ */ namespace PHPUnit\Framework; +use function array_merge; use function assert; -use function count; -use function get_class; -use function sprintf; -use function trim; -use PHPUnit\Util\Filter; -use PHPUnit\Util\InvalidDataSetException; -use PHPUnit\Util\Test as TestUtil; +use PHPUnit\Metadata\Api\DataProvider; +use PHPUnit\Metadata\Api\Groups; +use PHPUnit\Metadata\Api\ProvidedData; +use PHPUnit\Metadata\Api\Requirements; +use PHPUnit\Metadata\BackupGlobals; +use PHPUnit\Metadata\BackupStaticProperties; +use PHPUnit\Metadata\ExcludeGlobalVariableFromBackup; +use PHPUnit\Metadata\ExcludeStaticPropertyFromBackup; +use PHPUnit\Metadata\Parser\Registry as MetadataRegistry; +use PHPUnit\Metadata\PreserveGlobalState; +use PHPUnit\Runner\ErrorHandler; +use PHPUnit\TextUI\Configuration\Registry as ConfigurationRegistry; use ReflectionClass; -use Throwable; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class TestBuilder +final readonly class TestBuilder { - public function build(ReflectionClass $theClass, string $methodName): Test + /** + * @param ReflectionClass $theClass + * @param non-empty-string $methodName + * @param list $groups + * + * @throws InvalidDataProviderException + */ + public function build(ReflectionClass $theClass, string $methodName, array $groups = []): Test { $className = $theClass->getName(); - if (!$theClass->isInstantiable()) { - return new WarningTestCase( - sprintf('Cannot instantiate class "%s".', $className) + $data = null; + + if ($this->requirementsSatisfied($className, $methodName)) { + try { + ErrorHandler::instance()->enterTestCaseContext($className, $methodName); + + $data = (new DataProvider)->providedData($className, $methodName); + } finally { + ErrorHandler::instance()->leaveTestCaseContext(); + } + } + + if ($data !== null) { + return $this->buildDataProviderTestSuite( + $methodName, + $className, + $data, + $this->shouldTestMethodBeRunInSeparateProcess($className, $methodName), + $this->shouldGlobalStateBePreserved($className, $methodName), + $this->backupSettings($className, $methodName), + $groups, ); } - $backupSettings = TestUtil::getBackupSettings( - $className, - $methodName - ); + $test = new $className($methodName); - $preserveGlobalState = TestUtil::getPreserveGlobalStateSettings( - $className, - $methodName + $this->configureTestCase( + $test, + $this->shouldTestMethodBeRunInSeparateProcess($className, $methodName), + $this->shouldGlobalStateBePreserved($className, $methodName), + $this->backupSettings($className, $methodName), ); - $runTestInSeparateProcess = TestUtil::getProcessIsolationSettings( - $className, - $methodName - ); + return $test; + } - $runClassInSeparateProcess = TestUtil::getClassProcessIsolationSettings( - $className, - $methodName + /** + * @param non-empty-string $methodName + * @param class-string $className + * @param array $data + * @param array{backupGlobals: ?true, backupGlobalsExcludeList: list, backupStaticProperties: ?true, backupStaticPropertiesExcludeList: array>} $backupSettings + * @param list $groups + */ + private function buildDataProviderTestSuite(string $methodName, string $className, array $data, bool $runTestInSeparateProcess, ?bool $preserveGlobalState, array $backupSettings, array $groups): DataProviderTestSuite + { + $dataProviderTestSuite = DataProviderTestSuite::empty( + $className . '::' . $methodName, ); - $constructor = $theClass->getConstructor(); + $groups = array_merge( + $groups, + (new Groups)->groups($className, $methodName), + ); - if ($constructor === null) { - throw new Exception('No valid test provided.'); - } + foreach ($data as $_dataName => $_data) { + $_test = new $className($methodName); - $parameters = $constructor->getParameters(); + $_test->setData($_dataName, $_data->value()); - // TestCase() or TestCase($name) - if (count($parameters) < 2) { - $test = $this->buildTestWithoutData($className); - } // TestCase($name, $data) - else { - try { - $data = TestUtil::getProvidedData( - $className, - $methodName - ); - } catch (IncompleteTestError $e) { - $message = sprintf( - "Test for %s::%s marked incomplete by data provider\n%s", - $className, - $methodName, - $this->throwableToString($e) - ); - - $data = new IncompleteTestCase($className, $methodName, $message); - } catch (SkippedTestError $e) { - $message = sprintf( - "Test for %s::%s skipped by data provider\n%s", - $className, - $methodName, - $this->throwableToString($e) - ); - - $data = new SkippedTestCase($className, $methodName, $message); - } catch (Throwable $t) { - $message = sprintf( - "The data provider specified for %s::%s is invalid.\n%s", - $className, - $methodName, - $this->throwableToString($t) - ); - - $data = new WarningTestCase($message); - } - - // Test method with @dataProvider. - if (isset($data)) { - $test = $this->buildDataProviderTestSuite( - $methodName, - $className, - $data, - $runTestInSeparateProcess, - $preserveGlobalState, - $runClassInSeparateProcess, - $backupSettings - ); - } else { - $test = $this->buildTestWithoutData($className); - } - } - - if ($test instanceof TestCase) { - $test->setName($methodName); $this->configureTestCase( - $test, + $_test, $runTestInSeparateProcess, $preserveGlobalState, - $runClassInSeparateProcess, - $backupSettings + $backupSettings, ); + + $dataProviderTestSuite->addTest($_test, $groups); } - return $test; + return $dataProviderTestSuite; } - /** @psalm-param class-string $className */ - private function buildTestWithoutData(string $className) + /** + * @param array{backupGlobals: ?true, backupGlobalsExcludeList: list, backupStaticProperties: ?true, backupStaticPropertiesExcludeList: array>} $backupSettings + */ + private function configureTestCase(TestCase $test, bool $runTestInSeparateProcess, ?bool $preserveGlobalState, array $backupSettings): void { - return new $className; - } + if ($runTestInSeparateProcess) { + $test->setRunTestInSeparateProcess(true); + } - /** @psalm-param class-string $className */ - private function buildDataProviderTestSuite( - string $methodName, - string $className, - $data, - bool $runTestInSeparateProcess, - ?bool $preserveGlobalState, - bool $runClassInSeparateProcess, - array $backupSettings - ): DataProviderTestSuite { - $dataProviderTestSuite = new DataProviderTestSuite( - $className . '::' . $methodName - ); + if ($preserveGlobalState !== null) { + $test->setPreserveGlobalState($preserveGlobalState); + } - $groups = TestUtil::getGroups($className, $methodName); + if ($backupSettings['backupGlobals'] !== null) { + $test->setBackupGlobals($backupSettings['backupGlobals']); + } else { + $test->setBackupGlobals(ConfigurationRegistry::get()->backupGlobals()); + } - if ($data instanceof WarningTestCase || - $data instanceof SkippedTestCase || - $data instanceof IncompleteTestCase) { - $dataProviderTestSuite->addTest($data, $groups); + $test->setBackupGlobalsExcludeList($backupSettings['backupGlobalsExcludeList']); + + if ($backupSettings['backupStaticProperties'] !== null) { + $test->setBackupStaticProperties($backupSettings['backupStaticProperties']); } else { - foreach ($data as $_dataName => $_data) { - $_test = new $className($methodName, $_data, $_dataName); + $test->setBackupStaticProperties(ConfigurationRegistry::get()->backupStaticProperties()); + } + + $test->setBackupStaticPropertiesExcludeList($backupSettings['backupStaticPropertiesExcludeList']); + } - assert($_test instanceof TestCase); + /** + * @param class-string $className + * @param non-empty-string $methodName + * + * @return array{backupGlobals: ?true, backupGlobalsExcludeList: list, backupStaticProperties: ?true, backupStaticPropertiesExcludeList: array>} + */ + private function backupSettings(string $className, string $methodName): array + { + $metadataForClass = MetadataRegistry::parser()->forClass($className); + $metadataForMethod = MetadataRegistry::parser()->forMethod($className, $methodName); + $metadataForClassAndMethod = MetadataRegistry::parser()->forClassAndMethod($className, $methodName); + + $backupGlobals = null; + $backupGlobalsExcludeList = []; + + if ($metadataForMethod->isBackupGlobals()->isNotEmpty()) { + $metadata = $metadataForMethod->isBackupGlobals()->asArray()[0]; + + assert($metadata instanceof BackupGlobals); + + if ($metadata->enabled()) { + $backupGlobals = true; + } + } elseif ($metadataForClass->isBackupGlobals()->isNotEmpty()) { + $metadata = $metadataForClass->isBackupGlobals()->asArray()[0]; - $this->configureTestCase( - $_test, - $runTestInSeparateProcess, - $preserveGlobalState, - $runClassInSeparateProcess, - $backupSettings - ); + assert($metadata instanceof BackupGlobals); - $dataProviderTestSuite->addTest($_test, $groups); + if ($metadata->enabled()) { + $backupGlobals = true; } } - return $dataProviderTestSuite; - } + foreach ($metadataForClassAndMethod->isExcludeGlobalVariableFromBackup() as $metadata) { + assert($metadata instanceof ExcludeGlobalVariableFromBackup); - private function configureTestCase( - TestCase $test, - bool $runTestInSeparateProcess, - ?bool $preserveGlobalState, - bool $runClassInSeparateProcess, - array $backupSettings - ): void { - if ($runTestInSeparateProcess) { - $test->setRunTestInSeparateProcess(true); + $backupGlobalsExcludeList[] = $metadata->globalVariableName(); + } + + $backupStaticProperties = null; + $backupStaticPropertiesExcludeList = []; + + if ($metadataForMethod->isBackupStaticProperties()->isNotEmpty()) { + $metadata = $metadataForMethod->isBackupStaticProperties()->asArray()[0]; - if ($preserveGlobalState !== null) { - $test->setPreserveGlobalState($preserveGlobalState); + assert($metadata instanceof BackupStaticProperties); + + if ($metadata->enabled()) { + $backupStaticProperties = true; + } + } elseif ($metadataForClass->isBackupStaticProperties()->isNotEmpty()) { + $metadata = $metadataForClass->isBackupStaticProperties()->asArray()[0]; + + assert($metadata instanceof BackupStaticProperties); + + if ($metadata->enabled()) { + $backupStaticProperties = true; } } - if ($runClassInSeparateProcess) { - $test->setRunClassInSeparateProcess(true); + foreach ($metadataForClassAndMethod->isExcludeStaticPropertyFromBackup() as $metadata) { + assert($metadata instanceof ExcludeStaticPropertyFromBackup); - if ($preserveGlobalState !== null) { - $test->setPreserveGlobalState($preserveGlobalState); + if (!isset($backupStaticPropertiesExcludeList[$metadata->className()])) { + $backupStaticPropertiesExcludeList[$metadata->className()] = []; } + + $backupStaticPropertiesExcludeList[$metadata->className()][] = $metadata->propertyName(); } - if ($backupSettings['backupGlobals'] !== null) { - $test->setBackupGlobals($backupSettings['backupGlobals']); + return [ + 'backupGlobals' => $backupGlobals, + 'backupGlobalsExcludeList' => $backupGlobalsExcludeList, + 'backupStaticProperties' => $backupStaticProperties, + 'backupStaticPropertiesExcludeList' => $backupStaticPropertiesExcludeList, + ]; + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + private function shouldGlobalStateBePreserved(string $className, string $methodName): ?bool + { + $metadataForMethod = MetadataRegistry::parser()->forMethod($className, $methodName); + + if ($metadataForMethod->isPreserveGlobalState()->isNotEmpty()) { + $metadata = $metadataForMethod->isPreserveGlobalState()->asArray()[0]; + + assert($metadata instanceof PreserveGlobalState); + + return $metadata->enabled(); } - if ($backupSettings['backupStaticAttributes'] !== null) { - $test->setBackupStaticAttributes( - $backupSettings['backupStaticAttributes'] - ); + $metadataForClass = MetadataRegistry::parser()->forClass($className); + + if ($metadataForClass->isPreserveGlobalState()->isNotEmpty()) { + $metadata = $metadataForClass->isPreserveGlobalState()->asArray()[0]; + + assert($metadata instanceof PreserveGlobalState); + + return $metadata->enabled(); } + + return null; } - private function throwableToString(Throwable $t): string + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + private function shouldTestMethodBeRunInSeparateProcess(string $className, string $methodName): bool { - $message = $t->getMessage(); - - if (empty(trim($message))) { - $message = ''; + if (MetadataRegistry::parser()->forClass($className)->isRunTestsInSeparateProcesses()->isNotEmpty()) { + return true; } - if ($t instanceof InvalidDataSetException) { - return sprintf( - "%s\n%s", - $message, - Filter::getFilteredStacktrace($t) - ); + if (MetadataRegistry::parser()->forMethod($className, $methodName)->isRunInSeparateProcess()->isNotEmpty()) { + return true; } - return sprintf( - "%s: %s\n%s", - get_class($t), - $message, - Filter::getFilteredStacktrace($t) - ); + return false; + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + private function requirementsSatisfied(string $className, string $methodName): bool + { + return (new Requirements)->requirementsNotSatisfiedFor($className, $methodName) === []; } } diff --git a/src/Framework/TestCase.php b/src/Framework/TestCase.php index ea9daf8df02..90b3af107a6 100644 --- a/src/Framework/TestCase.php +++ b/src/Framework/TestCase.php @@ -9,2227 +9,1743 @@ */ namespace PHPUnit\Framework; -use const LC_ALL; -use const LC_COLLATE; -use const LC_CTYPE; -use const LC_MESSAGES; -use const LC_MONETARY; -use const LC_NUMERIC; -use const LC_TIME; -use const PATHINFO_FILENAME; use const PHP_EOL; -use const PHP_URL_PATH; -use function array_filter; -use function array_flip; +use function array_any; use function array_keys; use function array_merge; -use function array_search; -use function array_unique; +use function array_reverse; use function array_values; use function assert; -use function basename; -use function call_user_func; use function chdir; use function class_exists; use function clearstatcache; use function count; use function defined; +use function error_clear_last; use function explode; -use function get_class; -use function get_include_path; +use function fclose; use function getcwd; use function implode; use function in_array; +use function ini_get; use function ini_set; use function is_array; use function is_callable; use function is_int; use function is_object; use function is_string; +use function is_writable; use function libxml_clear_errors; use function method_exists; use function ob_end_clean; +use function ob_get_clean; use function ob_get_contents; use function ob_get_level; use function ob_start; -use function parse_url; -use function pathinfo; +use function preg_match; use function preg_replace; -use function serialize; -use function setlocale; +use function putenv; +use function restore_error_handler; +use function restore_exception_handler; +use function set_error_handler; +use function set_exception_handler; use function sprintf; -use function strpos; -use function substr; +use function str_contains; +use function str_starts_with; +use function stream_get_contents; +use function stream_get_meta_data; +use function tmpfile; use function trim; -use function var_export; +use AssertionError; use DeepCopy\DeepCopy; +use PHPUnit\Event; +use PHPUnit\Event\NoPreviousThrowableException; use PHPUnit\Framework\Constraint\Exception as ExceptionConstraint; use PHPUnit\Framework\Constraint\ExceptionCode; -use PHPUnit\Framework\Constraint\ExceptionMessage; -use PHPUnit\Framework\Constraint\ExceptionMessageRegularExpression; -use PHPUnit\Framework\Error\Deprecated; -use PHPUnit\Framework\Error\Error; -use PHPUnit\Framework\Error\Notice; -use PHPUnit\Framework\Error\Warning as WarningError; -use PHPUnit\Framework\MockObject\Generator as MockGenerator; +use PHPUnit\Framework\Constraint\ExceptionMessageIsOrContains; +use PHPUnit\Framework\Constraint\ExceptionMessageMatchesRegularExpression; +use PHPUnit\Framework\MockObject\Exception as MockObjectException; +use PHPUnit\Framework\MockObject\Generator\Generator as MockGenerator; use PHPUnit\Framework\MockObject\MockBuilder; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\MockObject\MockObjectInternal; use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount as AnyInvokedCountMatcher; -use PHPUnit\Framework\MockObject\Rule\InvokedAtIndex as InvokedAtIndexMatcher; use PHPUnit\Framework\MockObject\Rule\InvokedAtLeastCount as InvokedAtLeastCountMatcher; use PHPUnit\Framework\MockObject\Rule\InvokedAtLeastOnce as InvokedAtLeastOnceMatcher; use PHPUnit\Framework\MockObject\Rule\InvokedAtMostCount as InvokedAtMostCountMatcher; +use PHPUnit\Framework\MockObject\Rule\InvokedCount; use PHPUnit\Framework\MockObject\Rule\InvokedCount as InvokedCountMatcher; use PHPUnit\Framework\MockObject\Stub; -use PHPUnit\Framework\MockObject\Stub\ConsecutiveCalls as ConsecutiveCallsStub; use PHPUnit\Framework\MockObject\Stub\Exception as ExceptionStub; -use PHPUnit\Framework\MockObject\Stub\ReturnArgument as ReturnArgumentStub; -use PHPUnit\Framework\MockObject\Stub\ReturnCallback as ReturnCallbackStub; -use PHPUnit\Framework\MockObject\Stub\ReturnSelf as ReturnSelfStub; -use PHPUnit\Framework\MockObject\Stub\ReturnStub; -use PHPUnit\Framework\MockObject\Stub\ReturnValueMap as ReturnValueMapStub; -use PHPUnit\Runner\BaseTestRunner; -use PHPUnit\Runner\PhptTestCase; -use PHPUnit\Util\Exception as UtilException; -use PHPUnit\Util\GlobalState; -use PHPUnit\Util\PHP\AbstractPhpProcess; +use PHPUnit\Framework\TestSize\TestSize; +use PHPUnit\Framework\TestStatus\TestStatus; +use PHPUnit\Metadata\Api\Groups; +use PHPUnit\Metadata\Api\HookMethods; +use PHPUnit\Metadata\Api\Requirements; +use PHPUnit\Metadata\Parser\Registry as MetadataRegistry; +use PHPUnit\Metadata\WithEnvironmentVariable; +use PHPUnit\Runner\BackedUpEnvironmentVariable; +use PHPUnit\Runner\DeprecationCollector\Facade as DeprecationCollector; +use PHPUnit\Runner\HookMethodCollection; +use PHPUnit\Runner\ShutdownHandler; +use PHPUnit\TestRunner\TestResult\PassedTests; +use PHPUnit\TextUI\Configuration\Registry as ConfigurationRegistry; +use PHPUnit\Util\Exporter; use PHPUnit\Util\Test as TestUtil; -use PHPUnit\Util\Type; -use Prophecy\Exception\Prediction\PredictionException; -use Prophecy\Prophecy\MethodProphecy; -use Prophecy\Prophecy\ObjectProphecy; -use Prophecy\Prophet; use ReflectionClass; use ReflectionException; +use ReflectionObject; +use SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException; use SebastianBergmann\Comparator\Comparator; use SebastianBergmann\Comparator\Factory as ComparatorFactory; use SebastianBergmann\Diff\Differ; -use SebastianBergmann\Exporter\Exporter; -use SebastianBergmann\GlobalState\ExcludeList; +use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder; +use SebastianBergmann\GlobalState\ExcludeList as GlobalStateExcludeList; use SebastianBergmann\GlobalState\Restorer; use SebastianBergmann\GlobalState\Snapshot; +use SebastianBergmann\Invoker\TimeoutException; use SebastianBergmann\ObjectEnumerator\Enumerator; -use SebastianBergmann\Template\Template; -use SoapClient; use Throwable; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ abstract class TestCase extends Assert implements Reorderable, SelfDescribing, Test { - private const LOCALE_CATEGORIES = [LC_ALL, LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME]; - - /** - * @var ?bool - */ - protected $backupGlobals; - - /** - * @var string[] - */ - protected $backupGlobalsExcludeList = []; - - /** - * @var string[] - * - * @deprecated Use $backupGlobalsExcludeList instead - */ - protected $backupGlobalsBlacklist = []; + private ?bool $backupGlobals = null; /** - * @var bool + * @var list */ - protected $backupStaticAttributes; + private array $backupGlobalsExcludeList = []; + private ?bool $backupStaticProperties = null; /** - * @var array> + * @var array> */ - protected $backupStaticAttributesExcludeList = []; + private array $backupStaticPropertiesExcludeList = []; + private ?Snapshot $snapshot = null; /** - * @var array> - * - * @deprecated Use $backupStaticAttributesExcludeList instead + * @var list */ - protected $backupStaticAttributesBlacklist = []; + private ?array $backupGlobalErrorHandlers = null; /** - * @var bool + * @var list */ - protected $runTestInSeparateProcess; + private ?array $backupGlobalExceptionHandlers = null; + private ?bool $runTestInSeparateProcess = null; + private bool $preserveGlobalState = false; + private bool $inIsolation = false; + private ?string $expectedException = null; + private ?string $expectedExceptionMessage = null; + private ?string $expectedExceptionMessageRegExp = null; + private null|int|string $expectedExceptionCode = null; /** - * @var bool + * @var list */ - protected $preserveGlobalState = true; + private array $backupEnvironmentVariables = []; /** * @var list */ - protected $providedTests = []; - - /** - * @var bool - */ - private $runClassInSeparateProcess; - - /** - * @var bool - */ - private $inIsolation = false; - - /** - * @var array - */ - private $data; - - /** - * @var int|string - */ - private $dataName; + private array $providedTests = []; /** - * @var null|string + * @var array */ - private $expectedException; + private array $data = []; + private int|string $dataName = ''; /** - * @var null|string + * @var non-empty-string */ - private $expectedExceptionMessage; + private string $methodName; /** - * @var null|string + * @var list */ - private $expectedExceptionMessageRegExp; - - /** - * @var null|int|string - */ - private $expectedExceptionCode; - - /** - * @var string - */ - private $name = ''; + private array $groups = []; /** * @var list */ - private $dependencies = []; - - /** - * @var array - */ - private $dependencyInput = []; - - /** - * @var array - */ - private $iniSettings = []; - - /** - * @var array - */ - private $locale = []; - - /** - * @var MockObject[] - */ - private $mockObjects = []; - - /** - * @var MockGenerator - */ - private $mockObjectGenerator; - - /** - * @var int - */ - private $status = BaseTestRunner::STATUS_UNKNOWN; - - /** - * @var string - */ - private $statusMessage = ''; - - /** - * @var int - */ - private $numAssertions = 0; - - /** - * @var TestResult - */ - private $result; - - /** - * @var mixed - */ - private $testResult; - - /** - * @var string - */ - private $output = ''; - - /** - * @var string - */ - private $outputExpectedRegex; - - /** - * @var string - */ - private $outputExpectedString; - - /** - * @var mixed - */ - private $outputCallback = false; - - /** - * @var bool - */ - private $outputBufferingActive = false; - - /** - * @var int - */ - private $outputBufferingLevel; - - /** - * @var bool - */ - private $outputRetrievedForAssertion = false; - - /** - * @var Snapshot - */ - private $snapshot; + private array $dependencies = []; /** - * @var \Prophecy\Prophet + * @var array> */ - private $prophet; + private array $dependencyInput = []; /** - * @var bool + * @var list */ - private $beStrictAboutChangesToGlobalState = false; + private array $mockObjects = []; + private TestStatus $status; /** - * @var bool + * @var 0|positive-int */ - private $registerMockObjectsFromTestArgumentsRecursively = false; + private int $numberOfAssertionsPerformed = 0; + private mixed $testResult = null; + private string $output = ''; + private ?string $outputExpectedRegex = null; + private ?string $outputExpectedString = null; + private bool $outputBufferingActive = false; + private int $outputBufferingLevel; + private bool $outputRetrievedForAssertion = false; + private bool $doesNotPerformAssertions = false; + private bool $expectErrorLog = false; /** - * @var string[] + * @var list */ - private $warnings = []; + private array $customComparators = []; + private ?Event\Code\TestMethod $testValueObjectForEvents = null; + private bool $wasPrepared = false; /** - * @var string[] + * @var array */ - private $groups = []; + private array $failureTypes = []; /** - * @var bool + * @var list */ - private $doesNotPerformAssertions = false; + private array $expectedUserDeprecationMessage = []; /** - * @var Comparator[] + * @var list */ - private $customComparators = []; + private array $expectedUserDeprecationMessageRegularExpression = []; /** - * @var string[] + * @var false|resource */ - private $doubledTypes = []; + private mixed $errorLogCapture = false; + private false|string $previousErrorLogTarget = false; /** - * Returns a matcher that matches when the method is executed - * zero or more times. + * @param non-empty-string $name + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public static function any(): AnyInvokedCountMatcher + final public function __construct(string $name) { - return new AnyInvokedCountMatcher; + $this->methodName = $name; + $this->status = TestStatus::unknown(); + + if (is_callable($this->sortId(), true)) { + $this->providedTests = [new ExecutionOrderDependency($this->sortId())]; + } } /** - * Returns a matcher that matches when the method is never executed. + * This method is called before the first test of this test class is run. + * + * @codeCoverageIgnore */ - public static function never(): InvokedCountMatcher + public static function setUpBeforeClass(): void { - return new InvokedCountMatcher(0); } /** - * Returns a matcher that matches when the method is executed - * at least N times. + * This method is called after the last test of this test class is run. + * + * @codeCoverageIgnore */ - public static function atLeast(int $requiredInvocations): InvokedAtLeastCountMatcher + public static function tearDownAfterClass(): void { - return new InvokedAtLeastCountMatcher( - $requiredInvocations - ); } /** - * Returns a matcher that matches when the method is executed at least once. + * This method is called before each test. + * + * @codeCoverageIgnore */ - public static function atLeastOnce(): InvokedAtLeastOnceMatcher + protected function setUp(): void { - return new InvokedAtLeastOnceMatcher; } /** - * Returns a matcher that matches when the method is executed exactly once. + * Performs assertions shared by all tests of a test case. + * + * This method is called between setUp() and test. + * + * @codeCoverageIgnore */ - public static function once(): InvokedCountMatcher + protected function assertPreConditions(): void { - return new InvokedCountMatcher(1); } /** - * Returns a matcher that matches when the method is executed - * exactly $count times. + * Performs assertions shared by all tests of a test case. + * + * This method is called between test and tearDown(). + * + * @codeCoverageIgnore */ - public static function exactly(int $count): InvokedCountMatcher + protected function assertPostConditions(): void { - return new InvokedCountMatcher($count); } /** - * Returns a matcher that matches when the method is executed - * at most N times. + * This method is called after each test. + * + * @codeCoverageIgnore */ - public static function atMost(int $allowedInvocations): InvokedAtMostCountMatcher + protected function tearDown(): void { - return new InvokedAtMostCountMatcher($allowedInvocations); } /** - * Returns a matcher that matches when the method is executed - * at the given index. + * Returns a string representation of the test case. + * + * @throws Exception + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public static function at(int $index): InvokedAtIndexMatcher - { - return new InvokedAtIndexMatcher($index); - } - - public static function returnValue($value): ReturnStub + public function toString(): string { - return new ReturnStub($value); - } + $buffer = sprintf( + '%s::%s', + new ReflectionClass($this)->getName(), + $this->methodName, + ); - public static function returnValueMap(array $valueMap): ReturnValueMapStub - { - return new ReturnValueMapStub($valueMap); + return $buffer . $this->dataSetAsStringWithData(); } - public static function returnArgument(int $argumentIndex): ReturnArgumentStub + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + final public function count(): int { - return new ReturnArgumentStub($argumentIndex); + return 1; } - public static function returnCallback($callback): ReturnCallbackStub + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + final public function status(): TestStatus { - return new ReturnCallbackStub($callback); + return $this->status; } /** - * Returns the current object. + * @throws \PHPUnit\Runner\Exception + * @throws \PHPUnit\Util\Exception + * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException + * @throws \SebastianBergmann\Template\InvalidArgumentException + * @throws Exception + * @throws NoPreviousThrowableException + * @throws ProcessIsolationException + * @throws UnintentionallyCoveredCodeException * - * This method is useful when mocking a fluent interface. + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public static function returnSelf(): ReturnSelfStub + final public function run(): void { - return new ReturnSelfStub; - } + if (!$this->handleDependencies()) { + return; + } - public static function throwException(Throwable $exception): ExceptionStub - { - return new ExceptionStub($exception); - } + if (!$this->shouldRunInSeparateProcess() || $this->requirementsNotSatisfied()) { + try { + ShutdownHandler::setMessage(sprintf('Fatal error: Premature end of PHP process when running %s.', $this->toString())); + (new TestRunner)->run($this); + } finally { + ShutdownHandler::resetMessage(); + } - public static function onConsecutiveCalls(...$args): ConsecutiveCallsStub - { - return new ConsecutiveCallsStub($args); + return; + } + + IsolatedTestRunnerRegistry::run( + $this, + $this->preserveGlobalState, + $this->requiresXdebug(), + ); } /** - * @param int|string $dataName + * @return list * * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function __construct(?string $name = null, array $data = [], $dataName = '') + final public function groups(): array { - if ($name !== null) { - $this->setName($name); - } - - $this->data = $data; - $this->dataName = $dataName; + return $this->groups; } /** - * This method is called before the first test of this test class is run. + * @param list $groups + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public static function setUpBeforeClass(): void + final public function setGroups(array $groups): void { + $this->groups = $groups; } /** - * This method is called after the last test of this test class is run. + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public static function tearDownAfterClass(): void + final public function nameWithDataSet(): string { + return $this->methodName . $this->dataSetAsString(); } /** - * This method is called before each test. + * @return non-empty-string + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - protected function setUp(): void + final public function name(): string { + return $this->methodName; } /** - * This method is called after each test. + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - protected function tearDown(): void + final public function size(): TestSize { + return (new Groups)->size( + static::class, + $this->methodName, + ); } /** - * Returns a string representation of the test case. + * @internal This method is not covered by the backward compatibility promise for PHPUnit * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception + * @phpstan-assert-if-true non-empty-string $this->output() */ - public function toString(): string + final public function hasUnexpectedOutput(): bool { - try { - $class = new ReflectionClass($this); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); + if ($this->output === '') { + return false; } - // @codeCoverageIgnoreEnd - - $buffer = sprintf( - '%s::%s', - $class->name, - $this->getName(false) - ); - return $buffer . $this->getDataSetAsString(); - } + if ($this->expectsOutput()) { + return false; + } - public function count(): int - { - return 1; + return true; } - public function getActualOutputForAssertion(): string + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + final public function output(): string { - $this->outputRetrievedForAssertion = true; + if (!$this->outputBufferingActive) { + return $this->output; + } - return $this->getActualOutput(); + return (string) ob_get_contents(); } - public function expectOutputRegex(string $expectedRegex): void + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + final public function doesNotPerformAssertions(): bool { - $this->outputExpectedRegex = $expectedRegex; + return $this->doesNotPerformAssertions; } - public function expectOutputString(string $expectedString): void + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + final public function expectsOutput(): bool { - $this->outputExpectedString = $expectedString; + return $this->hasExpectationOnOutput() || $this->outputRetrievedForAssertion; } /** - * @psalm-param class-string<\Throwable> $exception + * @throws Throwable + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function expectException(string $exception): void + final public function runBare(): void { - // @codeCoverageIgnoreStart - switch ($exception) { - case Deprecated::class: - $this->addWarning('Support for using expectException() with PHPUnit\Framework\Error\Deprecated is deprecated and will be removed in PHPUnit 10. Use expectDeprecation() instead.'); + $emitter = Event\Facade::emitter(); - break; + error_clear_last(); + clearstatcache(); - case Error::class: - $this->addWarning('Support for using expectException() with PHPUnit\Framework\Error\Error is deprecated and will be removed in PHPUnit 10. Use expectError() instead.'); + $emitter->testPreparationStarted( + $this->valueObjectForEvents(), + ); - break; + $this->snapshotGlobalState(); + $this->snapshotGlobalErrorExceptionHandlers(); + $this->handleEnvironmentVariables(); + $this->startOutputBuffering(); - case Notice::class: - $this->addWarning('Support for using expectException() with PHPUnit\Framework\Error\Notice is deprecated and will be removed in PHPUnit 10. Use expectNotice() instead.'); + $hookMethods = (new HookMethods)->hookMethods(static::class); + $hasMetRequirements = false; + $this->numberOfAssertionsPerformed = 0; + $currentWorkingDirectory = getcwd(); - break; + try { + $this->checkRequirements(); + $hasMetRequirements = true; - case WarningError::class: - $this->addWarning('Support for using expectException() with PHPUnit\Framework\Error\Warning is deprecated and will be removed in PHPUnit 10. Use expectWarning() instead.'); + if ($this->inIsolation) { + // @codeCoverageIgnoreStart + $this->invokeBeforeClassHookMethods($hookMethods, $emitter); + // @codeCoverageIgnoreEnd + } - break; - } - // @codeCoverageIgnoreEnd + if (method_exists(static::class, $this->methodName) && + MetadataRegistry::parser()->forClassAndMethod(static::class, $this->methodName)->isDoesNotPerformAssertions()->isNotEmpty()) { + $this->doesNotPerformAssertions = true; + } - $this->expectedException = $exception; - } + $this->invokeBeforeTestHookMethods($hookMethods, $emitter); + $this->invokePreConditionHookMethods($hookMethods, $emitter); - /** - * @param int|string $code - */ - public function expectExceptionCode($code): void - { - $this->expectedExceptionCode = $code; - } + $emitter->testPrepared( + $this->valueObjectForEvents(), + ); - public function expectExceptionMessage(string $message): void - { - $this->expectedExceptionMessage = $message; - } + $this->wasPrepared = true; + $this->testResult = $this->runTest(); - public function expectExceptionMessageMatches(string $regularExpression): void - { - $this->expectedExceptionMessageRegExp = $regularExpression; - } - - /** - * Sets up an expectation for an exception to be raised by the code under test. - * Information for expected exception class, expected exception message, and - * expected exception code are retrieved from a given Exception object. - */ - public function expectExceptionObject(\Exception $exception): void - { - $this->expectException(get_class($exception)); - $this->expectExceptionMessage($exception->getMessage()); - $this->expectExceptionCode($exception->getCode()); - } - - public function expectNotToPerformAssertions(): void - { - $this->doesNotPerformAssertions = true; - } - - public function expectDeprecation(): void - { - $this->expectedException = Deprecated::class; - } - - public function expectDeprecationMessage(string $message): void - { - $this->expectExceptionMessage($message); - } - - public function expectDeprecationMessageMatches(string $regularExpression): void - { - $this->expectExceptionMessageMatches($regularExpression); - } - - public function expectNotice(): void - { - $this->expectedException = Notice::class; - } - - public function expectNoticeMessage(string $message): void - { - $this->expectExceptionMessage($message); - } - - public function expectNoticeMessageMatches(string $regularExpression): void - { - $this->expectExceptionMessageMatches($regularExpression); - } + $this->verifyDeprecationExpectations(); + $this->verifyMockObjects(); + $this->invokePostConditionHookMethods($hookMethods, $emitter); - public function expectWarning(): void - { - $this->expectedException = WarningError::class; - } + $this->status = TestStatus::success(); + } catch (IncompleteTest $e) { + $this->status = TestStatus::incomplete($e->getMessage()); - public function expectWarningMessage(string $message): void - { - $this->expectExceptionMessage($message); - } + $emitter->testMarkedAsIncomplete( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($e), + ); + } catch (SkippedTest $e) { + $this->status = TestStatus::skipped($e->getMessage()); - public function expectWarningMessageMatches(string $regularExpression): void - { - $this->expectExceptionMessageMatches($regularExpression); - } + $emitter->testSkipped( + $this->valueObjectForEvents(), + $e->getMessage(), + ); + } catch (AssertionError|AssertionFailedError $e) { + $this->handleExceptionFromInvokedCountMockObjectRule($e); - public function expectError(): void - { - $this->expectedException = Error::class; - } + if (!$this->wasPrepared) { + $this->wasPrepared = true; - public function expectErrorMessage(string $message): void - { - $this->expectExceptionMessage($message); - } + $emitter->testPreparationFailed( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($e), + ); + } - public function expectErrorMessageMatches(string $regularExpression): void - { - $this->expectExceptionMessageMatches($regularExpression); - } + $this->status = TestStatus::failure($e->getMessage()); - public function getStatus(): int - { - return $this->status; - } + $emitter->testFailed( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($e), + Event\Code\ComparisonFailureBuilder::from($e), + ); + } catch (TimeoutException $e) { + } catch (Throwable $_e) { + if ($this->isRegisteredFailure($_e)) { + $this->status = TestStatus::failure($_e->getMessage()); - public function markAsRisky(): void - { - $this->status = BaseTestRunner::STATUS_RISKY; - } + $emitter->testFailed( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($_e), + null, + ); + } else { + $e = $this->transformException($_e); + + $this->status = TestStatus::error($e->getMessage()); + + if (!$this->wasPrepared) { + if ($e instanceof AssertionFailedError) { + $emitter->testPreparationFailed( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($e), + ); + } else { + $emitter->testPreparationErrored( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($e), + ); + } + } - public function getStatusMessage(): string - { - return $this->statusMessage; - } + $emitter->testErrored( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($e), + ); + } + } - public function hasFailed(): bool - { - $status = $this->getStatus(); + $outputBufferingStopped = false; - return $status === BaseTestRunner::STATUS_FAILURE || $status === BaseTestRunner::STATUS_ERROR; - } + if (!isset($e) && + $this->hasExpectationOnOutput() && + $this->stopOutputBuffering()) { + $outputBufferingStopped = true; - /** - * Runs the test case and collects the results in a TestResult object. - * If no TestResult object is passed a new one will be created. - * - * @throws CodeCoverageException - * @throws UtilException - * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException - * @throws \SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function run(TestResult $result = null): TestResult - { - if ($result === null) { - $result = $this->createResult(); + $this->performAssertionsOnOutput(); } - if (!$this instanceof WarningTestCase) { - $this->setTestResultObject($result); - } + try { + $this->mockObjects = []; - if (!$this instanceof WarningTestCase && - !$this instanceof SkippedTestCase && - !$this->handleDependencies()) { - return $result; + /** @phpstan-ignore catch.neverThrown */ + } catch (Throwable $e) { + Event\Facade::emitter()->testErrored( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($e), + ); } - if ($this->runInSeparateProcess()) { - $runEntireClass = $this->runClassInSeparateProcess && !$this->runTestInSeparateProcess; + // Tear down the fixture. An exception raised in tearDown() will be + // caught and passed on when no exception was raised before. + try { + if ($hasMetRequirements) { + $this->invokeAfterTestHookMethods($hookMethods, $emitter); - try { - $class = new ReflectionClass($this); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); + if ($this->inIsolation) { + // @codeCoverageIgnoreStart + $this->invokeAfterClassHookMethods($hookMethods, $emitter); + // @codeCoverageIgnoreEnd + } } - // @codeCoverageIgnoreEnd + } catch (AssertionError|AssertionFailedError $e) { + $this->status = TestStatus::failure($e->getMessage()); - if ($runEntireClass) { - $template = new Template( - __DIR__ . '/../Util/PHP/Template/TestCaseClass.tpl' - ); - } else { - $template = new Template( - __DIR__ . '/../Util/PHP/Template/TestCaseMethod.tpl' + $emitter->testFailed( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($e), + Event\Code\ComparisonFailureBuilder::from($e), + ); + } catch (Throwable $exceptionRaisedDuringTearDown) { + if (!isset($e) || $e instanceof SkippedWithMessageException) { + $this->status = TestStatus::error($exceptionRaisedDuringTearDown->getMessage()); + $e = $exceptionRaisedDuringTearDown; + + $emitter->testErrored( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($exceptionRaisedDuringTearDown), ); } + } - if ($this->preserveGlobalState) { - $constants = GlobalState::getConstantsAsString(); - $globals = GlobalState::getGlobalsAsString(); - $includedFiles = GlobalState::getIncludedFilesAsString(); - $iniSettings = GlobalState::getIniSettingsAsString(); - } else { - $constants = ''; - - if (!empty($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { - $globals = '$GLOBALS[\'__PHPUNIT_BOOTSTRAP\'] = ' . var_export($GLOBALS['__PHPUNIT_BOOTSTRAP'], true) . ";\n"; - } else { - $globals = ''; - } - - $includedFiles = ''; - $iniSettings = ''; - } - - $coverage = $result->getCollectCodeCoverageInformation() ? 'true' : 'false'; - $isStrictAboutTestsThatDoNotTestAnything = $result->isStrictAboutTestsThatDoNotTestAnything() ? 'true' : 'false'; - $isStrictAboutOutputDuringTests = $result->isStrictAboutOutputDuringTests() ? 'true' : 'false'; - $enforcesTimeLimit = $result->enforcesTimeLimit() ? 'true' : 'false'; - $isStrictAboutTodoAnnotatedTests = $result->isStrictAboutTodoAnnotatedTests() ? 'true' : 'false'; - $isStrictAboutResourceUsageDuringSmallTests = $result->isStrictAboutResourceUsageDuringSmallTests() ? 'true' : 'false'; - - if (defined('PHPUNIT_COMPOSER_INSTALL')) { - $composerAutoload = var_export(PHPUNIT_COMPOSER_INSTALL, true); - } else { - $composerAutoload = '\'\''; - } + if (!isset($e) && !isset($_e)) { + $emitter->testPassed( + $this->valueObjectForEvents(), + ); - if (defined('__PHPUNIT_PHAR__')) { - $phar = var_export(__PHPUNIT_PHAR__, true); - } else { - $phar = '\'\''; + if (!$this->usesDataProvider()) { + PassedTests::instance()->testMethodPassed( + $this->valueObjectForEvents(), + $this->testResult, + ); } + } - $codeCoverage = $result->getCodeCoverage(); - $codeCoverageFilter = null; - $driverMethod = 'forLineCoverage'; + if (!$outputBufferingStopped) { + $this->stopOutputBuffering(); + } - if ($codeCoverage) { - $codeCoverageFilter = $codeCoverage->filter(); + clearstatcache(); - if ($codeCoverage->collectsBranchAndPathCoverage()) { - $driverMethod = 'forLineAndPathCoverage'; - } - } + if ($currentWorkingDirectory !== false && $currentWorkingDirectory !== getcwd()) { + chdir($currentWorkingDirectory); + } - $data = var_export(serialize($this->data), true); - $dataName = var_export($this->dataName, true); - $dependencyInput = var_export(serialize($this->dependencyInput), true); - $includePath = var_export(get_include_path(), true); - $codeCoverageFilter = var_export(serialize($codeCoverageFilter), true); - // must do these fixes because TestCaseMethod.tpl has unserialize('{data}') in it, and we can't break BC - // the lines above used to use addcslashes() rather than var_export(), which breaks null byte escape sequences - $data = "'." . $data . ".'"; - $dataName = "'.(" . $dataName . ").'"; - $dependencyInput = "'." . $dependencyInput . ".'"; - $includePath = "'." . $includePath . ".'"; - $codeCoverageFilter = "'." . $codeCoverageFilter . ".'"; - - $configurationFilePath = $GLOBALS['__PHPUNIT_CONFIGURATION_FILE'] ?? ''; - - $var = [ - 'composerAutoload' => $composerAutoload, - 'phar' => $phar, - 'filename' => $class->getFileName(), - 'className' => $class->getName(), - 'collectCodeCoverageInformation' => $coverage, - 'driverMethod' => $driverMethod, - 'data' => $data, - 'dataName' => $dataName, - 'dependencyInput' => $dependencyInput, - 'constants' => $constants, - 'globals' => $globals, - 'include_path' => $includePath, - 'included_files' => $includedFiles, - 'iniSettings' => $iniSettings, - 'isStrictAboutTestsThatDoNotTestAnything' => $isStrictAboutTestsThatDoNotTestAnything, - 'isStrictAboutOutputDuringTests' => $isStrictAboutOutputDuringTests, - 'enforcesTimeLimit' => $enforcesTimeLimit, - 'isStrictAboutTodoAnnotatedTests' => $isStrictAboutTodoAnnotatedTests, - 'isStrictAboutResourceUsageDuringSmallTests' => $isStrictAboutResourceUsageDuringSmallTests, - 'codeCoverageFilter' => $codeCoverageFilter, - 'configurationFilePath' => $configurationFilePath, - 'name' => $this->getName(false), - ]; - - if (!$runEntireClass) { - $var['methodName'] = $this->name; - } + $this->restoreEnvironmentVariables(); + $this->restoreGlobalErrorExceptionHandlers(); + $this->restoreGlobalState(); + $this->unregisterCustomComparators(); + libxml_clear_errors(); - $template->setVar($var); + $this->testValueObjectForEvents = null; - $php = AbstractPhpProcess::factory(); - $php->runTestJob($template->render(), $this, $result); - } else { - $result->run($this); + if (isset($e)) { + $this->onNotSuccessfulTest($e); } - - $this->result = null; - - return $result; } /** - * Returns a builder object to create mock objects using a fluent interface. + * @param list $dependencies * - * @psalm-template RealInstanceType of object - * @psalm-param class-string $className - * @psalm-return MockBuilder + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function getMockBuilder(string $className): MockBuilder + final public function setDependencies(array $dependencies): void { - $this->recordDoubledType($className); - - return new MockBuilder($this, $className); + $this->dependencies = $dependencies; } - public function registerComparator(Comparator $comparator): void + /** + * @param array> $dependencyInput + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + * + * @codeCoverageIgnore + */ + final public function setDependencyInput(array $dependencyInput): void { - ComparatorFactory::getInstance()->register($comparator); - - $this->customComparators[] = $comparator; + $this->dependencyInput = $dependencyInput; } /** - * @return string[] + * @return array> * * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function doubledTypes(): array + final public function dependencyInput(): array { - return array_unique($this->doubledTypes); + return $this->dependencyInput; } /** * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function getGroups(): array + final public function hasDependencyInput(): bool { - return $this->groups; + return $this->dependencyInput !== []; } /** * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function setGroups(array $groups): void + final public function setBackupGlobals(bool $backupGlobals): void { - $this->groups = $groups; + $this->backupGlobals = $backupGlobals; } /** + * @param list $backupGlobalsExcludeList + * * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function getAnnotations(): array + final public function setBackupGlobalsExcludeList(array $backupGlobalsExcludeList): void { - return TestUtil::parseTestMethodAnnotations( - get_class($this), - $this->name - ); + $this->backupGlobalsExcludeList = $backupGlobalsExcludeList; } /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function getName(bool $withDataSet = true): string + final public function setBackupStaticProperties(bool $backupStaticProperties): void { - if ($withDataSet) { - return $this->name . $this->getDataSetAsString(false); - } - - return $this->name; + $this->backupStaticProperties = $backupStaticProperties; } /** - * Returns the size of the test. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @param array> $backupStaticPropertiesExcludeList * * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function getSize(): int + final public function setBackupStaticPropertiesExcludeList(array $backupStaticPropertiesExcludeList): void { - return TestUtil::getSize( - get_class($this), - $this->getName(false) - ); + $this->backupStaticPropertiesExcludeList = $backupStaticPropertiesExcludeList; } /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function hasSize(): bool + final public function setRunTestInSeparateProcess(bool $runTestInSeparateProcess): void { - return $this->getSize() !== TestUtil::UNKNOWN; + if ($this->runTestInSeparateProcess === null) { + $this->runTestInSeparateProcess = $runTestInSeparateProcess; + } } /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function isSmall(): bool + final public function setPreserveGlobalState(bool $preserveGlobalState): void { - return $this->getSize() === TestUtil::SMALL; + $this->preserveGlobalState = $preserveGlobalState; } /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * * @internal This method is not covered by the backward compatibility promise for PHPUnit + * + * @codeCoverageIgnore */ - public function isMedium(): bool + final public function setInIsolation(bool $inIsolation): void { - return $this->getSize() === TestUtil::MEDIUM; + $this->inIsolation = $inIsolation; } /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @internal This method is not covered by the backward compatibility promise for PHPUnit * + * @codeCoverageIgnore + */ + final public function result(): mixed + { + return $this->testResult; + } + + /** * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function isLarge(): bool + final public function setResult(mixed $result): void { - return $this->getSize() === TestUtil::LARGE; + $this->testResult = $result; } /** + * @template RealInstanceType of object + * + * @param class-string $type + * * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function getActualOutput(): string + final public function registerMockObject(string $type, MockObject $mockObject): void { - if (!$this->outputBufferingActive) { - return $this->output; - } + assert($mockObject instanceof MockObjectInternal); - return (string) ob_get_contents(); + $this->mockObjects[] = [ + 'type' => $type, + 'mockObject' => $mockObject, + ]; } /** * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function hasOutput(): bool + final public function addToAssertionCount(int $count): void { - if ($this->output === '') { - return false; - } + $this->numberOfAssertionsPerformed += $count; + } - if ($this->hasExpectationOnOutput()) { - return false; - } + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + * + * @return 0|positive-int + */ + final public function numberOfAssertionsPerformed(): int + { + return $this->numberOfAssertionsPerformed; + } - return true; + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + final public function usesDataProvider(): bool + { + return $this->data !== []; } /** * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function doesNotPerformAssertions(): bool + final public function dataName(): int|string { - return $this->doesNotPerformAssertions; + return $this->dataName; } /** * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function hasExpectationOnOutput(): bool + final public function dataSetAsString(): string { - return is_string($this->outputExpectedString) || is_string($this->outputExpectedRegex) || $this->outputRetrievedForAssertion; + if ($this->data !== []) { + if (is_int($this->dataName)) { + return sprintf(' with data set #%s', $this->dataName); + } + + return sprintf(' with data set "%s"', $this->dataName); + } + + return ''; } /** * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function getExpectedException(): ?string + final public function dataSetAsStringWithData(): string { - return $this->expectedException; + if ($this->data === []) { + return ''; + } + + return sprintf( + '%s with data (%s)', + $this->dataSetAsFilterString(), + Exporter::shortenedRecursiveExport($this->data), + ); } /** - * @return null|int|string + * @return array * * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function getExpectedExceptionCode() + final public function providedData(): array { - return $this->expectedExceptionCode; + return $this->data; } /** * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function getExpectedExceptionMessage(): ?string + final public function sortId(): string { - return $this->expectedExceptionMessage; + $id = $this->methodName; + + if (!str_contains($id, '::')) { + $id = static::class . '::' . $id; + } + + if ($this->usesDataProvider()) { + $id .= $this->dataSetAsString(); + } + + return $id; } /** + * @return list + * * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function getExpectedExceptionMessageRegExp(): ?string + final public function provides(): array { - return $this->expectedExceptionMessageRegExp; + return $this->providedTests; } /** + * @return list + * * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function setRegisterMockObjectsFromTestArgumentsRecursively(bool $flag): void + final public function requires(): array { - $this->registerMockObjectsFromTestArgumentsRecursively = $flag; + return $this->dependencies; } /** - * @throws Throwable + * @param array $data * * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function runBare(): void + final public function setData(int|string $dataName, array $data): void { - $this->numAssertions = 0; - - $this->snapshotGlobalState(); - $this->startOutputBuffering(); - clearstatcache(); - $currentWorkingDirectory = getcwd(); + $this->dataName = $dataName; + $this->data = $data; + } - $hookMethods = TestUtil::getHookMethods(get_class($this)); + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + final public function valueObjectForEvents(): Event\Code\TestMethod + { + if ($this->testValueObjectForEvents !== null) { + return $this->testValueObjectForEvents; + } - $hasMetRequirements = false; + $this->testValueObjectForEvents = Event\Code\TestMethodBuilder::fromTestCase($this); - try { - $this->checkRequirements(); - $hasMetRequirements = true; + return $this->testValueObjectForEvents; + } - if ($this->inIsolation) { - foreach ($hookMethods['beforeClass'] as $method) { - $this->{$method}(); - } - } + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + final public function wasPrepared(): bool + { + return $this->wasPrepared; + } - $this->setDoesNotPerformAssertionsFromAnnotation(); + /** + * Returns a matcher that matches when the method is executed + * zero or more times. + */ + final protected function any(): AnyInvokedCountMatcher + { + return new AnyInvokedCountMatcher; + } - foreach ($hookMethods['before'] as $method) { - $this->{$method}(); - } - - foreach ($hookMethods['preCondition'] as $method) { - $this->{$method}(); - } - - $this->testResult = $this->runTest(); - $this->verifyMockObjects(); - - foreach ($hookMethods['postCondition'] as $method) { - $this->{$method}(); - } - - if (!empty($this->warnings)) { - throw new Warning( - implode( - "\n", - array_unique($this->warnings) - ) - ); - } - - $this->status = BaseTestRunner::STATUS_PASSED; - } catch (IncompleteTest $e) { - $this->status = BaseTestRunner::STATUS_INCOMPLETE; - $this->statusMessage = $e->getMessage(); - } catch (SkippedTest $e) { - $this->status = BaseTestRunner::STATUS_SKIPPED; - $this->statusMessage = $e->getMessage(); - } catch (Warning $e) { - $this->status = BaseTestRunner::STATUS_WARNING; - $this->statusMessage = $e->getMessage(); - } catch (AssertionFailedError $e) { - $this->status = BaseTestRunner::STATUS_FAILURE; - $this->statusMessage = $e->getMessage(); - } catch (PredictionException $e) { - $this->status = BaseTestRunner::STATUS_FAILURE; - $this->statusMessage = $e->getMessage(); - } catch (Throwable $_e) { - $e = $_e; - $this->status = BaseTestRunner::STATUS_ERROR; - $this->statusMessage = $_e->getMessage(); - } - - $this->mockObjects = []; - $this->prophet = null; - - // Tear down the fixture. An exception raised in tearDown() will be - // caught and passed on when no exception was raised before. - try { - if ($hasMetRequirements) { - foreach ($hookMethods['after'] as $method) { - $this->{$method}(); - } - - if ($this->inIsolation) { - foreach ($hookMethods['afterClass'] as $method) { - $this->{$method}(); - } - } - } - } catch (Throwable $_e) { - $e = $e ?? $_e; - } - - try { - $this->stopOutputBuffering(); - } catch (RiskyTestError $_e) { - $e = $e ?? $_e; - } - - if (isset($_e)) { - $this->status = BaseTestRunner::STATUS_ERROR; - $this->statusMessage = $_e->getMessage(); - } - - clearstatcache(); - - if ($currentWorkingDirectory !== getcwd()) { - chdir($currentWorkingDirectory); - } - - $this->restoreGlobalState(); - $this->unregisterCustomComparators(); - $this->cleanupIniSettings(); - $this->cleanupLocaleSettings(); - libxml_clear_errors(); - - // Perform assertion on output. - if (!isset($e)) { - try { - if ($this->outputExpectedRegex !== null) { - $this->assertMatchesRegularExpression($this->outputExpectedRegex, $this->output); - } elseif ($this->outputExpectedString !== null) { - $this->assertEquals($this->outputExpectedString, $this->output); - } - } catch (Throwable $_e) { - $e = $_e; - } - } - - // Workaround for missing "finally". - if (isset($e)) { - if ($e instanceof PredictionException) { - $e = new AssertionFailedError($e->getMessage()); - } - - $this->onNotSuccessfulTest($e); - } - } + /** + * Returns a matcher that matches when the method is never executed. + */ + final protected function never(): InvokedCountMatcher + { + return new InvokedCountMatcher(0); + } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * Returns a matcher that matches when the method is executed + * at least N times. */ - public function setName(string $name): void + final protected function atLeast(int $requiredInvocations): InvokedAtLeastCountMatcher { - $this->name = $name; - - if (is_callable($this->sortId(), true)) { - $this->providedTests = [new ExecutionOrderDependency($this->sortId())]; - } + return new InvokedAtLeastCountMatcher( + $requiredInvocations, + ); } /** - * @param list $dependencies - * - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * Returns a matcher that matches when the method is executed at least once. */ - public function setDependencies(array $dependencies): void + final protected function atLeastOnce(): InvokedAtLeastOnceMatcher { - $this->dependencies = $dependencies; + return new InvokedAtLeastOnceMatcher; } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * Returns a matcher that matches when the method is executed exactly once. */ - public function setDependencyInput(array $dependencyInput): void + final protected function once(): InvokedCountMatcher { - $this->dependencyInput = $dependencyInput; + return new InvokedCountMatcher(1); } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * Returns a matcher that matches when the method is executed + * exactly $count times. */ - public function setBeStrictAboutChangesToGlobalState(?bool $beStrictAboutChangesToGlobalState): void + final protected function exactly(int $count): InvokedCountMatcher { - $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; + return new InvokedCountMatcher($count); } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * Returns a matcher that matches when the method is executed + * at most N times. */ - public function setBackupGlobals(?bool $backupGlobals): void + final protected function atMost(int $allowedInvocations): InvokedAtMostCountMatcher { - if ($this->backupGlobals === null && $backupGlobals !== null) { - $this->backupGlobals = $backupGlobals; - } + return new InvokedAtMostCountMatcher($allowedInvocations); } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function setBackupStaticAttributes(?bool $backupStaticAttributes): void + final protected function throwException(Throwable $exception): ExceptionStub { - if ($this->backupStaticAttributes === null && $backupStaticAttributes !== null) { - $this->backupStaticAttributes = $backupStaticAttributes; - } + return new ExceptionStub($exception); } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function setRunTestInSeparateProcess(bool $runTestInSeparateProcess): void + final protected function getActualOutputForAssertion(): string { - if ($this->runTestInSeparateProcess === null) { - $this->runTestInSeparateProcess = $runTestInSeparateProcess; - } + $this->outputRetrievedForAssertion = true; + + return $this->output(); } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function setRunClassInSeparateProcess(bool $runClassInSeparateProcess): void + final protected function expectOutputRegex(string $expectedRegex): void { - if ($this->runClassInSeparateProcess === null) { - $this->runClassInSeparateProcess = $runClassInSeparateProcess; - } + $this->outputExpectedRegex = $expectedRegex; } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function setPreserveGlobalState(bool $preserveGlobalState): void + final protected function expectOutputString(string $expectedString): void { - $this->preserveGlobalState = $preserveGlobalState; + $this->outputExpectedString = $expectedString; } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function setInIsolation(bool $inIsolation): void + final protected function expectErrorLog(): void { - $this->inIsolation = $inIsolation; + $this->expectErrorLog = true; } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @param class-string $exception */ - public function isInIsolation(): bool + final protected function expectException(string $exception): void { - return $this->inIsolation; + $this->expectedException = $exception; } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function getResult() + final protected function expectExceptionCode(int|string $code): void { - return $this->testResult; + $this->expectedExceptionCode = $code; } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function setResult($result): void + final protected function expectExceptionMessage(string $message): void { - $this->testResult = $result; + $this->expectedExceptionMessage = $message; } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function setOutputCallback(callable $callback): void + final protected function expectExceptionMessageMatches(string $regularExpression): void { - $this->outputCallback = $callback; + $this->expectedExceptionMessageRegExp = $regularExpression; } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * Sets up an expectation for an exception to be raised by the code under test. + * Information for expected exception class, expected exception message, and + * expected exception code are retrieved from a given Exception object. */ - public function getTestResultObject(): ?TestResult + final protected function expectExceptionObject(Throwable $exception): void { - return $this->result; + $this->expectException($exception::class); + $this->expectExceptionMessage($exception->getMessage()); + $this->expectExceptionCode($exception->getCode()); } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function setTestResultObject(TestResult $result): void + final protected function expectNotToPerformAssertions(): void { - $this->result = $result; + $this->doesNotPerformAssertions = true; } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @param non-empty-string $expectedUserDeprecationMessage */ - public function registerMockObject(MockObject $mockObject): void + final protected function expectUserDeprecationMessage(string $expectedUserDeprecationMessage): void { - $this->mockObjects[] = $mockObject; + $this->expectedUserDeprecationMessage[] = $expectedUserDeprecationMessage; } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @param non-empty-string $expectedUserDeprecationMessageRegularExpression */ - public function addToAssertionCount(int $count): void + final protected function expectUserDeprecationMessageMatches(string $expectedUserDeprecationMessageRegularExpression): void { - $this->numAssertions += $count; + $this->expectedUserDeprecationMessageRegularExpression[] = $expectedUserDeprecationMessageRegularExpression; } /** - * Returns the number of assertions performed by this test. + * Returns a builder object to create mock objects using a fluent interface. * - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @template RealInstanceType of object + * + * @param class-string $className + * + * @return MockBuilder */ - public function getNumAssertions(): int + final protected function getMockBuilder(string $className): MockBuilder { - return $this->numAssertions; + return new MockBuilder($this, $className); } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function usesDataProvider(): bool + final protected function registerComparator(Comparator $comparator): void { - return !empty($this->data); + ComparatorFactory::getInstance()->register($comparator); + + Event\Facade::emitter()->testRegisteredComparator($comparator::class); + + $this->customComparators[] = $comparator; } /** - * @return int|string - * - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @param class-string $classOrInterface */ - public function dataName() + final protected function registerFailureType(string $classOrInterface): void { - return $this->dataName; + $this->failureTypes[$classOrInterface] = true; } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * Creates a mock object for the specified interface or class. + * + * @template RealInstanceType of object + * + * @param class-string $type + * + * @throws InvalidArgumentException + * @throws MockObjectException + * @throws NoPreviousThrowableException + * + * @return MockObject&RealInstanceType */ - public function getDataSetAsString(bool $includeData = true): string + final protected function createMock(string $type): MockObject { - $buffer = ''; + $mock = (new MockGenerator)->testDouble( + $type, + true, + callOriginalConstructor: false, + callOriginalClone: false, + returnValueGeneration: self::generateReturnValuesForTestDoubles(), + ); - if (!empty($this->data)) { - if (is_int($this->dataName)) { - $buffer .= sprintf(' with data set #%d', $this->dataName); - } else { - $buffer .= sprintf(' with data set "%s"', $this->dataName); - } + assert($mock instanceof $type); + assert($mock instanceof MockObject); - $exporter = new Exporter; + $this->registerMockObject($type, $mock); - if ($includeData) { - $buffer .= sprintf(' (%s)', $exporter->shortenedRecursiveExport($this->data)); - } - } + Event\Facade::emitter()->testCreatedMockObject($type); - return $buffer; + return $mock; } /** - * Gets the data set of a TestCase. + * @param list $interfaces * - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @throws MockObjectException */ - public function getProvidedData(): array + final protected function createMockForIntersectionOfInterfaces(array $interfaces): MockObject { - return $this->data; + $mock = (new MockGenerator)->testDoubleForInterfaceIntersection( + $interfaces, + true, + returnValueGeneration: self::generateReturnValuesForTestDoubles(), + ); + + assert($mock instanceof MockObject); + + $this->registerMockObject(implode('|', $interfaces), $mock); + + Event\Facade::emitter()->testCreatedMockObjectForIntersectionOfInterfaces($interfaces); + + return $mock; } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * Creates (and configures) a mock object for the specified interface or class. + * + * @template RealInstanceType of object + * + * @param class-string $type + * @param array $configuration + * + * @throws InvalidArgumentException + * @throws MockObjectException + * @throws NoPreviousThrowableException + * + * @return MockObject&RealInstanceType */ - public function addWarning(string $warning): void + final protected function createConfiguredMock(string $type, array $configuration): MockObject { - $this->warnings[] = $warning; + $o = $this->createMock($type); + + foreach ($configuration as $method => $return) { + $o->method($method)->willReturn($return); + } + + return $o; } - public function sortId(): string + /** + * Creates a partial mock object for the specified interface or class. + * + * @param class-string $type + * @param list $methods + * + * @template RealInstanceType of object + * + * @throws InvalidArgumentException + * @throws MockObjectException + * + * @return MockObject&RealInstanceType + */ + final protected function createPartialMock(string $type, array $methods): MockObject { - $id = $this->name; + $mockBuilder = $this->getMockBuilder($type) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->onlyMethods($methods); - if (strpos($id, '::') === false) { - $id = get_class($this) . '::' . $id; + if (!self::generateReturnValuesForTestDoubles()) { + $mockBuilder->disableAutoReturnValueGeneration(); } - if ($this->usesDataProvider()) { - $id .= $this->getDataSetAsString(false); - } + $partialMock = $mockBuilder->getMock(); - return $id; + Event\Facade::emitter()->testCreatedPartialMockObject( + $type, + ...$methods, + ); + + return $partialMock; } /** - * Returns the normalized test name as class::method. - * - * @return list + * @param non-empty-string $additionalInformation */ - public function provides(): array + final protected function provideAdditionalInformation(string $additionalInformation): void { - return $this->providedTests; + Event\Facade::emitter()->testProvidedAdditionalInformation( + $this->valueObjectForEvents(), + $additionalInformation, + ); + } + + protected function transformException(Throwable $t): Throwable + { + return $t; } /** - * Returns a list of normalized dependency names, class::method. - * - * This list can differ from the raw dependencies as the resolver has - * no need for the [!][shallow]clone prefix that is filtered out - * during normalization. + * This method is called when a test method did not execute successfully. * - * @return list + * @throws Throwable */ - public function requires(): array + protected function onNotSuccessfulTest(Throwable $t): never { - return $this->dependencies; + throw $t; } /** - * Override to run the test and assert its state. + * Returns the data set as a string compatible with the --filter CLI option. * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + private function dataSetAsFilterString(): string + { + if ($this->data !== []) { + if (is_int($this->dataName)) { + return sprintf('#%d', $this->dataName); + } + + return sprintf('@%s', $this->dataName); + } + + return ''; + } + + /** * @throws AssertionFailedError * @throws Exception * @throws ExpectationFailedException - * @throws \SebastianBergmann\ObjectEnumerator\InvalidArgumentException * @throws Throwable */ - protected function runTest() + private function runTest(): mixed { - if (trim($this->name) === '') { - throw new Exception( - 'PHPUnit\Framework\TestCase::$name must be a non-blank string.' - ); - } + $testArguments = array_merge($this->data, array_values($this->dependencyInput)); - $testArguments = array_merge($this->data, $this->dependencyInput); - - $this->registerMockObjectsFromTestArguments($testArguments); + $this->startErrorLogCapture(); try { - $testResult = $this->{$this->name}(...array_values($testArguments)); + /** @phpstan-ignore method.dynamicName */ + $testResult = $this->{$this->methodName}(...$testArguments); + + $this->verifyErrorLogExpectation(); } catch (Throwable $exception) { - if (!$this->checkExceptionExpectations($exception)) { + $this->handleErrorLogError(); + + if (!$this->shouldExceptionExpectationsBeVerified($exception)) { throw $exception; } - if ($this->expectedException !== null) { - $this->assertThat( - $exception, - new ExceptionConstraint( - $this->expectedException - ) - ); - } + $this->verifyExceptionExpectations($exception); - if ($this->expectedExceptionMessage !== null) { - $this->assertThat( - $exception, - new ExceptionMessage( - $this->expectedExceptionMessage - ) - ); - } + return null; + } finally { + $this->stopErrorLogCapture(); + } - if ($this->expectedExceptionMessageRegExp !== null) { - $this->assertThat( - $exception, - new ExceptionMessageRegularExpression( - $this->expectedExceptionMessageRegExp - ) - ); - } + $this->expectedExceptionWasNotRaised(); + + return $testResult; + } + + private function stripDateFromErrorLog(string $log): string + { + // https://github.com/php/php-src/blob/c696087e323263e941774ebbf902ac249774ec9f/main/main.c#L905 + return preg_replace('/\[\d+-\w+-\d+ \d+:\d+:\d+ [^\r\n[\]]+?\] /', '', $log); + } + + /** + * @throws ExpectationFailedException + */ + private function verifyDeprecationExpectations(): void + { + foreach ($this->expectedUserDeprecationMessage as $deprecationExpectation) { + $this->numberOfAssertionsPerformed++; - if ($this->expectedExceptionCode !== null) { - $this->assertThat( - $exception, - new ExceptionCode( - $this->expectedExceptionCode - ) + if (!in_array($deprecationExpectation, DeprecationCollector::deprecations(), true)) { + throw new ExpectationFailedException( + sprintf( + 'Expected deprecation with message "%s" was not triggered', + $deprecationExpectation, + ), ); } - - return; } - if ($this->expectedException !== null) { - $this->assertThat( - null, - new ExceptionConstraint( - $this->expectedException - ) - ); - } elseif ($this->expectedExceptionMessage !== null) { - $this->numAssertions++; + foreach ($this->expectedUserDeprecationMessageRegularExpression as $deprecationExpectation) { + $this->numberOfAssertionsPerformed++; - throw new AssertionFailedError( - sprintf( - 'Failed asserting that exception with message "%s" is thrown', - $this->expectedExceptionMessage - ) + $expectedDeprecationTriggered = array_any( + DeprecationCollector::deprecations(), + static fn (string $deprecation) => @preg_match($deprecationExpectation, $deprecation) > 0, ); - } elseif ($this->expectedExceptionMessageRegExp !== null) { - $this->numAssertions++; - throw new AssertionFailedError( - sprintf( - 'Failed asserting that exception with message matching "%s" is thrown', - $this->expectedExceptionMessageRegExp - ) - ); - } elseif ($this->expectedExceptionCode !== null) { - $this->numAssertions++; - - throw new AssertionFailedError( - sprintf( - 'Failed asserting that exception with code "%s" is thrown', - $this->expectedExceptionCode - ) - ); + if (!$expectedDeprecationTriggered) { + throw new ExpectationFailedException( + sprintf( + 'Expected deprecation with message matching regular expression "%s" was not triggered', + $deprecationExpectation, + ), + ); + } } - - return $testResult; } /** - * This method is a wrapper for the ini_set() function that automatically - * resets the modified php.ini setting to its original value after the - * test is run. - * - * @throws Exception + * @throws Throwable */ - protected function iniSet(string $varName, string $newValue): void + private function verifyMockObjects(): void { - $currentValue = ini_set($varName, $newValue); + foreach ($this->mockObjects as $mockObject) { + if (!$mockObject['mockObject']->__phpunit_hasMatchers()) { + if (!str_starts_with($this::class, 'PHPUnit\\')) { + Event\Facade::emitter()->testTriggeredPhpunitNotice( + $this->testValueObjectForEvents, + sprintf( + 'No expectations were configured for the mock object for %s. ' . + 'You should refactor your test code and use a test stub instead.', + $mockObject['type'], + ), + ); + } - if ($currentValue !== false) { - $this->iniSettings[$varName] = $currentValue; - } else { - throw new Exception( - sprintf( - 'INI setting "%s" could not be set to "%s".', - $varName, - $newValue - ) + continue; + } + + $this->numberOfAssertionsPerformed++; + + $mockObject['mockObject']->__phpunit_verify( + $this->shouldInvocationMockerBeReset($mockObject['mockObject']), ); } } /** - * This method is a wrapper for the setlocale() function that automatically - * resets the locale to its original value after the test is run. - * - * @throws Exception + * @throws SkippedTest */ - protected function setLocale(...$args): void + private function checkRequirements(): void { - if (count($args) < 2) { - throw new Exception; + if ($this->methodName === '' || !method_exists($this, $this->methodName)) { + return; } - [$category, $locale] = $args; - - if (defined('LC_MESSAGES')) { - $categories[] = LC_MESSAGES; - } + $missingRequirements = (new Requirements)->requirementsNotSatisfiedFor( + static::class, + $this->methodName, + ); - if (!in_array($category, self::LOCALE_CATEGORIES, true)) { - throw new Exception; + if ($missingRequirements !== []) { + $this->markTestSkipped(implode(PHP_EOL, $missingRequirements)); } + } - if (!is_array($locale) && !is_string($locale)) { - throw new Exception; + private function handleDependencies(): bool + { + if ([] === $this->dependencies || $this->inIsolation) { + return true; } - $this->locale[$category] = setlocale($category, 0); + $passedTests = PassedTests::instance(); - $result = setlocale(...$args); - - if ($result === false) { - throw new Exception( - 'The locale functionality is not implemented on your platform, ' . - 'the specified locale does not exist or the category name is ' . - 'invalid.' - ); - } - } + foreach ($this->dependencies as $dependency) { + if (!$dependency->isValid()) { + $this->markErrorForInvalidDependency(); - /** - * Makes configurable stub for the specified class. - * - * @psalm-template RealInstanceType of object - * @psalm-param class-string $originalClassName - * @psalm-return Stub&RealInstanceType - */ - protected function createStub(string $originalClassName): Stub - { - return $this->createMock($originalClassName); - } + return false; + } - /** - * Returns a mock object for the specified class. - * - * @psalm-template RealInstanceType of object - * @psalm-param class-string $originalClassName - * @psalm-return MockObject&RealInstanceType - */ - protected function createMock(string $originalClassName): MockObject - { - return $this->getMockBuilder($originalClassName) - ->disableOriginalConstructor() - ->disableOriginalClone() - ->disableArgumentCloning() - ->disallowMockingUnknownTypes() - ->getMock(); - } + if ($dependency->targetIsClass()) { + $dependencyClassName = $dependency->getTargetClassName(); - /** - * Returns a configured mock object for the specified class. - * - * @psalm-template RealInstanceType of object - * @psalm-param class-string $originalClassName - * @psalm-return MockObject&RealInstanceType - */ - protected function createConfiguredMock(string $originalClassName, array $configuration): MockObject - { - $o = $this->createMock($originalClassName); + if (!class_exists($dependencyClassName)) { + $this->markErrorForInvalidDependency($dependency); - foreach ($configuration as $method => $return) { - $o->method($method)->willReturn($return); - } + return false; + } - return $o; - } + if (!$passedTests->hasTestClassPassed($dependencyClassName)) { + $this->markSkippedForMissingDependency($dependency); - /** - * Returns a partial mock object for the specified class. - * - * @param string[] $methods - * - * @psalm-template RealInstanceType of object - * @psalm-param class-string $originalClassName - * @psalm-return MockObject&RealInstanceType - */ - protected function createPartialMock(string $originalClassName, array $methods): MockObject - { - try { - $reflector = new ReflectionClass($originalClassName); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd + return false; + } - $mockedMethodsThatDontExist = array_filter( - $methods, - static function (string $method) use ($reflector) { - return !$reflector->hasMethod($method); + continue; } - ); - if ($mockedMethodsThatDontExist) { - $this->addWarning( - sprintf( - 'createPartialMock() called with method(s) %s that do not exist in %s. This will not be allowed in future versions of PHPUnit.', - implode(', ', $mockedMethodsThatDontExist), - $originalClassName - ) - ); - } + $dependencyTarget = $dependency->getTarget(); - return $this->getMockBuilder($originalClassName) - ->disableOriginalConstructor() - ->disableOriginalClone() - ->disableArgumentCloning() - ->disallowMockingUnknownTypes() - ->setMethods(empty($methods) ? null : $methods) - ->getMock(); - } + if (!$passedTests->hasTestMethodPassed($dependencyTarget)) { + if (!$this->isCallableTestMethod($dependencyTarget)) { + $this->markErrorForInvalidDependency($dependency); + } else { + $this->markSkippedForMissingDependency($dependency); + } - /** - * Returns a test proxy for the specified class. - * - * @psalm-template RealInstanceType of object - * @psalm-param class-string $originalClassName - * @psalm-return MockObject&RealInstanceType - */ - protected function createTestProxy(string $originalClassName, array $constructorArguments = []): MockObject - { - return $this->getMockBuilder($originalClassName) - ->setConstructorArgs($constructorArguments) - ->enableProxyingToOriginalMethods() - ->getMock(); - } + return false; + } - /** - * Mocks the specified class and returns the name of the mocked class. - * - * @param null|array $methods $methods - * - * @psalm-template RealInstanceType of object - * @psalm-param class-string|string $originalClassName - * @psalm-return class-string - */ - protected function getMockClass(string $originalClassName, $methods = [], array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = false, bool $callOriginalClone = true, bool $callAutoload = true, bool $cloneArguments = false): string - { - $this->recordDoubledType($originalClassName); + if ($passedTests->isGreaterThan($dependencyTarget, $this->size())) { + Event\Facade::emitter()->testConsideredRisky( + $this->valueObjectForEvents(), + 'This test depends on a test that is larger than itself', + ); - $mock = $this->getMockObjectGenerator()->getMock( - $originalClassName, - $methods, - $arguments, - $mockClassName, - $callOriginalConstructor, - $callOriginalClone, - $callAutoload, - $cloneArguments - ); + return true; + } - return get_class($mock); - } + if (!$passedTests->hasReturnValue($dependencyTarget)) { + return true; + } - /** - * Returns a mock object for the specified abstract class with all abstract - * methods of the class mocked. Concrete methods are not mocked by default. - * To mock concrete methods, use the 7th parameter ($mockedMethods). - * - * @psalm-template RealInstanceType of object - * @psalm-param class-string $originalClassName - * @psalm-return MockObject&RealInstanceType - */ - protected function getMockForAbstractClass(string $originalClassName, array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $callAutoload = true, array $mockedMethods = [], bool $cloneArguments = false): MockObject - { - $this->recordDoubledType($originalClassName); + $returnValue = $passedTests->returnValue($dependencyTarget); - $mockObject = $this->getMockObjectGenerator()->getMockForAbstractClass( - $originalClassName, - $arguments, - $mockClassName, - $callOriginalConstructor, - $callOriginalClone, - $callAutoload, - $mockedMethods, - $cloneArguments - ); + if ($dependency->deepClone()) { + $deepCopy = new DeepCopy; + $deepCopy->skipUncloneable(false); - $this->registerMockObject($mockObject); + $this->dependencyInput[$dependencyTarget] = $deepCopy->copy($returnValue); + } elseif ($dependency->shallowClone()) { + $this->dependencyInput[$dependencyTarget] = clone $returnValue; + } else { + $this->dependencyInput[$dependencyTarget] = $returnValue; + } + } + + $this->testValueObjectForEvents = null; - return $mockObject; + return true; } /** - * Returns a mock object based on the given WSDL file. - * - * @psalm-template RealInstanceType of object - * @psalm-param class-string|string $originalClassName - * @psalm-return MockObject&RealInstanceType + * @throws Exception + * @throws NoPreviousThrowableException */ - protected function getMockFromWsdl(string $wsdlFile, string $originalClassName = '', string $mockClassName = '', array $methods = [], bool $callOriginalConstructor = true, array $options = []): MockObject + private function markErrorForInvalidDependency(?ExecutionOrderDependency $dependency = null): void { - $this->recordDoubledType(SoapClient::class); - - if ($originalClassName === '') { - $fileName = pathinfo(basename(parse_url(http://github.com/$wsdlFile,%20PHP_URL_PATH)), PATHINFO_FILENAME); - $originalClassName = preg_replace('/\W/', '', $fileName); - } + $message = 'This test has an invalid dependency'; - if (!class_exists($originalClassName)) { - eval( - $this->getMockObjectGenerator()->generateClassFromWsdl( - $wsdlFile, - $originalClassName, - $methods, - $options - ) + if ($dependency !== null) { + $message = sprintf( + 'This test depends on "%s" which does not exist', + $dependency->targetIsClass() ? $dependency->getTargetClassName() : $dependency->getTarget(), ); } - $mockObject = $this->getMockObjectGenerator()->getMock( - $originalClassName, - $methods, - ['', $options], - $mockClassName, - $callOriginalConstructor, - false, - false - ); + $exception = new InvalidDependencyException($message); - $this->registerMockObject($mockObject); + Event\Facade::emitter()->testErrored( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($exception), + ); - return $mockObject; + $this->status = TestStatus::error($message); } - /** - * Returns a mock object for the specified trait with all abstract methods - * of the trait mocked. Concrete methods to mock can be specified with the - * `$mockedMethods` parameter. - */ - protected function getMockForTrait(string $traitName, array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $callAutoload = true, array $mockedMethods = [], bool $cloneArguments = false): MockObject + private function markSkippedForMissingDependency(ExecutionOrderDependency $dependency): void { - $this->recordDoubledType($traitName); - - $mockObject = $this->getMockObjectGenerator()->getMockForTrait( - $traitName, - $arguments, - $mockClassName, - $callOriginalConstructor, - $callOriginalClone, - $callAutoload, - $mockedMethods, - $cloneArguments + $message = sprintf( + 'This test depends on "%s" to pass', + $dependency->getTarget(), ); - $this->registerMockObject($mockObject); + Event\Facade::emitter()->testSkipped( + $this->valueObjectForEvents(), + $message, + ); - return $mockObject; + $this->status = TestStatus::skipped($message); } - /** - * Returns an object for the specified trait. - */ - protected function getObjectForTrait(string $traitName, array $arguments = [], string $traitClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $callAutoload = true): object + private function startOutputBuffering(): void { - $this->recordDoubledType($traitName); + ob_start(); - return $this->getMockObjectGenerator()->getObjectForTrait( - $traitName, - $traitClassName, - $callAutoload, - $callOriginalConstructor, - $arguments - ); + $this->outputBufferingActive = true; + $this->outputBufferingLevel = ob_get_level(); } - /** - * @throws \Prophecy\Exception\Doubler\ClassNotFoundException - * @throws \Prophecy\Exception\Doubler\DoubleException - * @throws \Prophecy\Exception\Doubler\InterfaceNotFoundException - * - * @psalm-param class-string|null $classOrInterface - */ - protected function prophesize(?string $classOrInterface = null): ObjectProphecy + private function stopOutputBuffering(): bool { - $this->addWarning('PHPUnit\Framework\TestCase::prophesize() is deprecated and will be removed in PHPUnit 10. Please use the trait provided by phpspec/prophecy-phpunit.'); + $bufferingLevel = ob_get_level(); + + if ($bufferingLevel !== $this->outputBufferingLevel) { + if ($bufferingLevel > $this->outputBufferingLevel) { + $message = 'Test code or tested code did not close its own output buffers'; + } else { + $message = 'Test code or tested code closed output buffers other than its own'; + } + + while (ob_get_level() >= $this->outputBufferingLevel) { + ob_end_clean(); + } + + Event\Facade::emitter()->testConsideredRisky( + $this->valueObjectForEvents(), + $message, + ); - if (is_string($classOrInterface)) { - $this->recordDoubledType($classOrInterface); + return false; } - return $this->getProphet()->prophesize($classOrInterface); - } + $this->output = ob_get_clean(); - /** - * Creates a default TestResult object. - * - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - protected function createResult(): TestResult - { - return new TestResult; - } + $this->outputBufferingActive = false; + $this->outputBufferingLevel = ob_get_level(); - /** - * Performs assertions shared by all tests of a test case. - * - * This method is called between setUp() and test. - */ - protected function assertPreConditions(): void - { + return true; } - /** - * Performs assertions shared by all tests of a test case. - * - * This method is called between test and tearDown(). - */ - protected function assertPostConditions(): void + private function snapshotGlobalErrorExceptionHandlers(): void { + $this->backupGlobalErrorHandlers = $this->activeErrorHandlers(); + $this->backupGlobalExceptionHandlers = $this->activeExceptionHandlers(); } - /** - * This method is called when a test method did not execute successfully. - * - * @throws Throwable - */ - protected function onNotSuccessfulTest(Throwable $t): void + private function restoreGlobalErrorExceptionHandlers(): void { - throw $t; - } + $activeErrorHandlers = $this->activeErrorHandlers(); + $activeExceptionHandlers = $this->activeExceptionHandlers(); - protected function recordDoubledType(string $originalClassName): void - { - $this->doubledTypes[] = $originalClassName; - } + $message = null; - /** - * @throws Throwable - */ - private function verifyMockObjects(): void - { - foreach ($this->mockObjects as $mockObject) { - if ($mockObject->__phpunit_hasMatchers()) { - $this->numAssertions++; + if ($activeErrorHandlers !== $this->backupGlobalErrorHandlers) { + if (count($activeErrorHandlers) > count($this->backupGlobalErrorHandlers)) { + if (!$this->inIsolation) { + $message = 'Test code or tested code did not remove its own error handlers'; + } + } else { + $message = 'Test code or tested code removed error handlers other than its own'; + } + + foreach ($activeErrorHandlers as $handler) { + restore_error_handler(); + } + + foreach ($this->backupGlobalErrorHandlers as $handler) { + set_error_handler($handler); } + } - $mockObject->__phpunit_verify( - $this->shouldInvocationMockerBeReset($mockObject) + if ($message !== null) { + Event\Facade::emitter()->testConsideredRisky( + $this->valueObjectForEvents(), + $message, ); } - if ($this->prophet !== null) { - try { - $this->prophet->checkPredictions(); - } finally { - foreach ($this->prophet->getProphecies() as $objectProphecy) { - foreach ($objectProphecy->getMethodProphecies() as $methodProphecies) { - foreach ($methodProphecies as $methodProphecy) { - assert($methodProphecy instanceof MethodProphecy); + $message = null; - $this->numAssertions += count($methodProphecy->getCheckedPredictions()); - } - } + if ($activeExceptionHandlers !== $this->backupGlobalExceptionHandlers) { + if (count($activeExceptionHandlers) > count($this->backupGlobalExceptionHandlers)) { + if (!$this->inIsolation) { + $message = 'Test code or tested code did not remove its own exception handlers'; } + } else { + $message = 'Test code or tested code removed exception handlers other than its own'; } - } - } - /** - * @throws Warning - * @throws SkippedTestError - * @throws SyntheticSkippedError - */ - private function checkRequirements(): void - { - if (!$this->name || !method_exists($this, $this->name)) { - return; + foreach ($activeExceptionHandlers as $handler) { + restore_exception_handler(); + } + + foreach ($this->backupGlobalExceptionHandlers as $handler) { + set_exception_handler($handler); + } } - $missingRequirements = TestUtil::getMissingRequirements( - get_class($this), - $this->name - ); + $this->backupGlobalErrorHandlers = null; + $this->backupGlobalExceptionHandlers = null; - if (!empty($missingRequirements)) { - $this->markTestSkipped(implode(PHP_EOL, $missingRequirements)); + if ($message !== null) { + Event\Facade::emitter()->testConsideredRisky( + $this->valueObjectForEvents(), + $message, + ); } } - private function handleDependencies(): bool + /** + * @return list + */ + private function activeErrorHandlers(): array { - if ([] === $this->dependencies || $this->inIsolation) { - return true; - } + $activeErrorHandlers = []; - $passed = $this->result->passed(); - $passedKeys = array_keys($passed); - $numKeys = count($passedKeys); + while (true) { + $previousHandler = set_error_handler(static fn () => false); - for ($i = 0; $i < $numKeys; $i++) { - $pos = strpos($passedKeys[$i], ' with data set'); + restore_error_handler(); - if ($pos !== false) { - $passedKeys[$i] = substr($passedKeys[$i], 0, $pos); + if ($previousHandler === null) { + break; } - } - - $passedKeys = array_flip(array_unique($passedKeys)); - - foreach ($this->dependencies as $dependency) { - if (!$dependency->isValid()) { - $this->markSkippedForNotSpecifyingDependency(); - return false; - } + $activeErrorHandlers[] = $previousHandler; - if ($dependency->targetIsClass()) { - $dependencyClassName = $dependency->getTargetClassName(); + restore_error_handler(); + } - if (array_search($dependencyClassName, $this->result->passedClasses(), true) === false) { - $this->markSkippedForMissingDependency($dependency); + $activeErrorHandlers = array_reverse($activeErrorHandlers); + $invalidErrorHandlerStack = false; - return false; - } + foreach ($activeErrorHandlers as $handler) { + if (!is_callable($handler)) { + $invalidErrorHandlerStack = true; continue; } - $dependencyTarget = $dependency->getTarget(); + set_error_handler($handler); + } - if (!isset($passedKeys[$dependencyTarget])) { - if (!$this->isCallableTestMethod($dependencyTarget)) { - $this->markWarningForUncallableDependency($dependency); - } else { - $this->markSkippedForMissingDependency($dependency); - } + if ($invalidErrorHandlerStack) { + $message = 'At least one error handler is not callable outside the scope it was registered in'; - return false; - } + Event\Facade::emitter()->testConsideredRisky( + $this->valueObjectForEvents(), + $message, + ); + } - if (isset($passed[$dependencyTarget])) { - if ($passed[$dependencyTarget]['size'] != \PHPUnit\Util\Test::UNKNOWN && - $this->getSize() != \PHPUnit\Util\Test::UNKNOWN && - $passed[$dependencyTarget]['size'] > $this->getSize()) { - $this->result->addError( - $this, - new SkippedTestError( - 'This test depends on a test that is larger than itself.' - ), - 0 - ); + return $activeErrorHandlers; + } - return false; - } + /** + * @return list + */ + private function activeExceptionHandlers(): array + { + $res = []; - if ($dependency->useDeepClone()) { - $deepCopy = new DeepCopy; - $deepCopy->skipUncloneable(false); + while (true) { + $previousHandler = set_exception_handler(static fn () => null); + restore_exception_handler(); - $this->dependencyInput[$dependencyTarget] = $deepCopy->copy($passed[$dependencyTarget]['result']); - } elseif ($dependency->useShallowClone()) { - $this->dependencyInput[$dependencyTarget] = clone $passed[$dependencyTarget]['result']; - } else { - $this->dependencyInput[$dependencyTarget] = $passed[$dependencyTarget]['result']; - } - } else { - $this->dependencyInput[$dependencyTarget] = null; + if ($previousHandler === null) { + break; } + $res[] = $previousHandler; + restore_exception_handler(); } + $res = array_reverse($res); - return true; + foreach ($res as $handler) { + set_exception_handler($handler); + } + + return $res; } - private function markSkippedForNotSpecifyingDependency(): void - { - $this->status = BaseTestRunner::STATUS_SKIPPED; - - $this->result->startTest($this); - - $this->result->addError( - $this, - new SkippedTestError( - sprintf('This method has an invalid @depends annotation.') - ), - 0 - ); - - $this->result->endTest($this, 0); - } - - private function markSkippedForMissingDependency(ExecutionOrderDependency $dependency): void - { - $this->status = BaseTestRunner::STATUS_SKIPPED; - - $this->result->startTest($this); - - $this->result->addError( - $this, - new SkippedTestError( - sprintf( - 'This test depends on "%s" to pass.', - $dependency->getTarget() - ) - ), - 0 - ); - - $this->result->endTest($this, 0); - } - - private function markWarningForUncallableDependency(ExecutionOrderDependency $dependency): void - { - $this->status = BaseTestRunner::STATUS_WARNING; - - $this->result->startTest($this); - - $this->result->addWarning( - $this, - new Warning( - sprintf( - 'This test depends on "%s" which does not exist.', - $dependency->getTarget() - ) - ), - 0 - ); - - $this->result->endTest($this, 0); - } - - /** - * Get the mock object generator, creating it if it doesn't exist. - */ - private function getMockObjectGenerator(): MockGenerator - { - if ($this->mockObjectGenerator === null) { - $this->mockObjectGenerator = new MockGenerator; - } - - return $this->mockObjectGenerator; - } - - private function startOutputBuffering(): void - { - ob_start(); - - $this->outputBufferingActive = true; - $this->outputBufferingLevel = ob_get_level(); - } - - /** - * @throws RiskyTestError - */ - private function stopOutputBuffering(): void - { - if (ob_get_level() !== $this->outputBufferingLevel) { - while (ob_get_level() >= $this->outputBufferingLevel) { - ob_end_clean(); - } - - throw new RiskyTestError( - 'Test code or tested code did not (only) close its own output buffers' - ); - } - - $this->output = ob_get_contents(); - - if ($this->outputCallback !== false) { - $this->output = (string) call_user_func($this->outputCallback, $this->output); - } - - ob_end_clean(); - - $this->outputBufferingActive = false; - $this->outputBufferingLevel = ob_get_level(); - } - - private function snapshotGlobalState(): void + private function snapshotGlobalState(): void { if ($this->runTestInSeparateProcess || $this->inIsolation || - (!$this->backupGlobals && !$this->backupStaticAttributes)) { + (!$this->backupGlobals && !$this->backupStaticProperties)) { return; } - $this->snapshot = $this->createGlobalStateSnapshot($this->backupGlobals === true); + $snapshot = $this->createGlobalStateSnapshot($this->backupGlobals === true); + + $this->snapshot = $snapshot; } - /** - * @throws RiskyTestError - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ private function restoreGlobalState(): void { if (!$this->snapshot instanceof Snapshot) { return; } - if ($this->beStrictAboutChangesToGlobalState) { - try { - $this->compareGlobalStateSnapshots( - $this->snapshot, - $this->createGlobalStateSnapshot($this->backupGlobals === true) - ); - } catch (RiskyTestError $rte) { - // Intentionally left empty - } + if (ConfigurationRegistry::get()->beStrictAboutChangesToGlobalState()) { + $this->compareGlobalStateSnapshots( + $this->snapshot, + $this->createGlobalStateSnapshot($this->backupGlobals === true), + ); } $restorer = new Restorer; @@ -2238,33 +1754,21 @@ private function restoreGlobalState(): void $restorer->restoreGlobalVariables($this->snapshot); } - if ($this->backupStaticAttributes) { - $restorer->restoreStaticAttributes($this->snapshot); + if ($this->backupStaticProperties) { + $restorer->restoreStaticProperties($this->snapshot); } $this->snapshot = null; - - if (isset($rte)) { - throw $rte; - } } private function createGlobalStateSnapshot(bool $backupGlobals): Snapshot { - $excludeList = new ExcludeList; + $excludeList = new GlobalStateExcludeList; foreach ($this->backupGlobalsExcludeList as $globalVariable) { $excludeList->addGlobalVariable($globalVariable); } - if (!empty($this->backupGlobalsBlacklist)) { - $this->addWarning('PHPUnit\Framework\TestCase::$backupGlobalsBlacklist is deprecated and will be removed in PHPUnit 10. Please use PHPUnit\Framework\TestCase::$backupGlobalsExcludeList instead.'); - - foreach ($this->backupGlobalsBlacklist as $globalVariable) { - $excludeList->addGlobalVariable($globalVariable); - } - } - if (!defined('PHPUNIT_TESTSUITE')) { $excludeList->addClassNamePrefix('PHPUnit'); $excludeList->addClassNamePrefix('SebastianBergmann\CodeCoverage'); @@ -2272,46 +1776,43 @@ private function createGlobalStateSnapshot(bool $backupGlobals): Snapshot $excludeList->addClassNamePrefix('SebastianBergmann\Invoker'); $excludeList->addClassNamePrefix('SebastianBergmann\Template'); $excludeList->addClassNamePrefix('SebastianBergmann\Timer'); - $excludeList->addClassNamePrefix('PHP_Token'); - $excludeList->addClassNamePrefix('Symfony'); - $excludeList->addClassNamePrefix('Doctrine\Instantiator'); - $excludeList->addClassNamePrefix('Prophecy'); - - foreach ($this->backupStaticAttributesExcludeList as $class => $attributes) { - foreach ($attributes as $attribute) { - $excludeList->addStaticAttribute($class, $attribute); - } - } + $excludeList->addStaticProperty(ComparatorFactory::class, 'instance'); - if (!empty($this->backupStaticAttributesBlacklist)) { - $this->addWarning('PHPUnit\Framework\TestCase::$backupStaticAttributesBlacklist is deprecated and will be removed in PHPUnit 10. Please use PHPUnit\Framework\TestCase::$backupStaticAttributesExcludeList instead.'); - - foreach ($this->backupStaticAttributesBlacklist as $class => $attributes) { - foreach ($attributes as $attribute) { - $excludeList->addStaticAttribute($class, $attribute); - } + foreach ($this->backupStaticPropertiesExcludeList as $class => $properties) { + foreach ($properties as $property) { + $excludeList->addStaticProperty($class, $property); } } } - return new Snapshot( - $excludeList, - $backupGlobals, - (bool) $this->backupStaticAttributes, - false, - false, - false, - false, - false, - false, - false - ); + try { + return new Snapshot( + $excludeList, + $backupGlobals, + (bool) $this->backupStaticProperties, + false, + false, + false, + false, + false, + false, + false, + ); + } catch (Throwable $t) { + Event\Facade::emitter()->testPreparationFailed( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($t), + ); + + Event\Facade::emitter()->testErrored( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($t), + ); + + throw $t; + } } - /** - * @throws RiskyTestError - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ private function compareGlobalStateSnapshots(Snapshot $before, Snapshot $after): void { $backupGlobals = $this->backupGlobals === null || $this->backupGlobals; @@ -2320,65 +1821,87 @@ private function compareGlobalStateSnapshots(Snapshot $before, Snapshot $after): $this->compareGlobalStateSnapshotPart( $before->globalVariables(), $after->globalVariables(), - "--- Global variables before the test\n+++ Global variables after the test\n" + "--- Global variables before the test\n+++ Global variables after the test\n", ); $this->compareGlobalStateSnapshotPart( $before->superGlobalVariables(), $after->superGlobalVariables(), - "--- Super-global variables before the test\n+++ Super-global variables after the test\n" + "--- Super-global variables before the test\n+++ Super-global variables after the test\n", ); } - if ($this->backupStaticAttributes) { + if ($this->backupStaticProperties) { $this->compareGlobalStateSnapshotPart( - $before->staticAttributes(), - $after->staticAttributes(), - "--- Static attributes before the test\n+++ Static attributes after the test\n" + $before->staticProperties(), + $after->staticProperties(), + "--- Static properties before the test\n+++ Static properties after the test\n", ); } } /** - * @throws RiskyTestError + * @param array $before + * @param array $after */ private function compareGlobalStateSnapshotPart(array $before, array $after, string $header): void { - if ($before != $after) { - $differ = new Differ($header); - $exporter = new Exporter; - - $diff = $differ->diff( - $exporter->export($before), - $exporter->export($after) + if ($before !== $after) { + $differ = new Differ(new UnifiedDiffOutputBuilder($header)); + + Event\Facade::emitter()->testConsideredRisky( + $this->valueObjectForEvents(), + 'This test modified global state but was not expected to do so' . PHP_EOL . + trim( + $differ->diff( + Exporter::export($before), + Exporter::export($after), + ), + ), ); + } + } - throw new RiskyTestError( - $diff - ); + private function handleEnvironmentVariables(): void + { + $withEnvironmentVariables = MetadataRegistry::parser()->forClassAndMethod(static::class, $this->methodName)->isWithEnvironmentVariable(); + + $environmentVariables = []; + + foreach ($withEnvironmentVariables as $metadata) { + assert($metadata instanceof WithEnvironmentVariable); + + $environmentVariables[$metadata->environmentVariableName()] = $metadata->value(); + } + + foreach ($environmentVariables as $environmentVariableName => $environmentVariableValue) { + $this->backupEnvironmentVariables = [...$this->backupEnvironmentVariables, ...BackedUpEnvironmentVariable::create($environmentVariableName)]; + + if ($environmentVariableValue === null) { + unset($_ENV[$environmentVariableName]); + putenv($environmentVariableName); + } else { + $_ENV[$environmentVariableName] = $environmentVariableValue; + putenv("{$environmentVariableName}={$environmentVariableValue}"); + } } } - private function getProphet(): Prophet + private function restoreEnvironmentVariables(): void { - if ($this->prophet === null) { - $this->prophet = new Prophet; + foreach ($this->backupEnvironmentVariables as $backupEnvironmentVariable) { + $backupEnvironmentVariable->restore(); } - return $this->prophet; + $this->backupEnvironmentVariables = []; } - /** - * @throws \SebastianBergmann\ObjectEnumerator\InvalidArgumentException - */ private function shouldInvocationMockerBeReset(MockObject $mock): bool { $enumerator = new Enumerator; - foreach ($enumerator->enumerate($this->dependencyInput) as $object) { - if ($mock === $object) { - return false; - } + if (in_array($mock, $enumerator->enumerate($this->dependencyInput), true)) { + return false; } if (!is_array($this->testResult) && !is_object($this->testResult)) { @@ -2388,48 +1911,6 @@ private function shouldInvocationMockerBeReset(MockObject $mock): bool return !in_array($mock, $enumerator->enumerate($this->testResult), true); } - /** - * @throws \SebastianBergmann\ObjectEnumerator\InvalidArgumentException - * @throws \SebastianBergmann\ObjectReflector\InvalidArgumentException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - private function registerMockObjectsFromTestArguments(array $testArguments, array &$visited = []): void - { - if ($this->registerMockObjectsFromTestArgumentsRecursively) { - foreach ((new Enumerator)->enumerate($testArguments) as $object) { - if ($object instanceof MockObject) { - $this->registerMockObject($object); - } - } - } else { - foreach ($testArguments as $testArgument) { - if ($testArgument instanceof MockObject) { - if (Type::isCloneable($testArgument)) { - $testArgument = clone $testArgument; - } - - $this->registerMockObject($testArgument); - } elseif (is_array($testArgument) && !in_array($testArgument, $visited, true)) { - $visited[] = $testArgument; - - $this->registerMockObjectsFromTestArguments( - $testArgument, - $visited - ); - } - } - } - } - - private function setDoesNotPerformAssertionsFromAnnotation(): void - { - $annotations = $this->getAnnotations(); - - if (isset($annotations['method']['doesNotPerformAssertions'])) { - $this->doesNotPerformAssertions = true; - } - } - private function unregisterCustomComparators(): void { $factory = ComparatorFactory::getInstance(); @@ -2441,28 +1922,10 @@ private function unregisterCustomComparators(): void $this->customComparators = []; } - private function cleanupIniSettings(): void - { - foreach ($this->iniSettings as $varName => $oldValue) { - ini_set($varName, $oldValue); - } - - $this->iniSettings = []; - } - - private function cleanupLocaleSettings(): void - { - foreach ($this->locale as $category => $locale) { - setlocale($category, $locale); - } - - $this->locale = []; - } - /** * @throws Exception */ - private function checkExceptionExpectations(Throwable $throwable): bool + private function shouldExceptionExpectationsBeVerified(Throwable $throwable): bool { $result = false; @@ -2481,8 +1944,8 @@ private function checkExceptionExpectations(Throwable $throwable): bool } catch (ReflectionException $e) { throw new Exception( $e->getMessage(), - (int) $e->getCode(), - $e + $e->getCode(), + $e, ); } // @codeCoverageIgnoreEnd @@ -2497,10 +1960,17 @@ private function checkExceptionExpectations(Throwable $throwable): bool return $result; } - private function runInSeparateProcess(): bool + private function shouldRunInSeparateProcess(): bool { - return ($this->runTestInSeparateProcess || $this->runClassInSeparateProcess) && - !$this->inIsolation && !$this instanceof PhptTestCase; + if ($this->inIsolation) { + return false; + } + + if ($this->runTestInSeparateProcess) { + return true; + } + + return ConfigurationRegistry::get()->processIsolation(); } private function isCallableTestMethod(string $dependency): bool @@ -2511,11 +1981,7 @@ private function isCallableTestMethod(string $dependency): bool return false; } - try { - $class = new ReflectionClass($className); - } catch (ReflectionException $e) { - return false; - } + $class = new ReflectionClass($className); if (!$class->isSubclassOf(__CLASS__)) { return false; @@ -2525,12 +1991,519 @@ private function isCallableTestMethod(string $dependency): bool return false; } + return TestUtil::isTestMethod( + $class->getMethod($methodName), + ); + } + + /** + * @throws Exception + * @throws ExpectationFailedException + * @throws NoPreviousThrowableException + */ + private function performAssertionsOnOutput(): void + { try { - $method = $class->getMethod($methodName); - } catch (ReflectionException $e) { - return false; + if ($this->outputExpectedRegex !== null) { + $this->assertMatchesRegularExpression($this->outputExpectedRegex, $this->output); + } elseif ($this->outputExpectedString !== null) { + $this->assertSame($this->outputExpectedString, $this->output); + } + } catch (ExpectationFailedException $e) { + $this->status = TestStatus::failure($e->getMessage()); + + Event\Facade::emitter()->testFailed( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($e), + Event\Code\ComparisonFailureBuilder::from($e), + ); + + throw $e; + } + } + + /** + * @param array{beforeClass: HookMethodCollection, before: HookMethodCollection, preCondition: HookMethodCollection, postCondition: HookMethodCollection, after: HookMethodCollection, afterClass: HookMethodCollection} $hookMethods + * + * @throws Throwable + * + * @codeCoverageIgnore + */ + private function invokeBeforeClassHookMethods(array $hookMethods, Event\Emitter $emitter): void + { + $this->invokeHookMethods( + $hookMethods['beforeClass'], + $emitter, + 'beforeFirstTestMethodCalled', + 'beforeFirstTestMethodErrored', + 'beforeFirstTestMethodFailed', + 'beforeFirstTestMethodFinished', + false, + ); + } + + /** + * @param array{beforeClass: HookMethodCollection, before: HookMethodCollection, preCondition: HookMethodCollection, postCondition: HookMethodCollection, after: HookMethodCollection, afterClass: HookMethodCollection} $hookMethods + * + * @throws Throwable + */ + private function invokeBeforeTestHookMethods(array $hookMethods, Event\Emitter $emitter): void + { + $this->invokeHookMethods( + $hookMethods['before'], + $emitter, + 'beforeTestMethodCalled', + 'beforeTestMethodErrored', + 'beforeTestMethodFailed', + 'beforeTestMethodFinished', + ); + } + + /** + * @param array{beforeClass: HookMethodCollection, before: HookMethodCollection, preCondition: HookMethodCollection, postCondition: HookMethodCollection, after: HookMethodCollection, afterClass: HookMethodCollection} $hookMethods + * + * @throws Throwable + */ + private function invokePreConditionHookMethods(array $hookMethods, Event\Emitter $emitter): void + { + $this->invokeHookMethods( + $hookMethods['preCondition'], + $emitter, + 'preConditionCalled', + 'preConditionErrored', + 'preConditionFailed', + 'preConditionFinished', + ); + } + + /** + * @param array{beforeClass: HookMethodCollection, before: HookMethodCollection, preCondition: HookMethodCollection, postCondition: HookMethodCollection, after: HookMethodCollection, afterClass: HookMethodCollection} $hookMethods + * + * @throws Throwable + */ + private function invokePostConditionHookMethods(array $hookMethods, Event\Emitter $emitter): void + { + $this->invokeHookMethods( + $hookMethods['postCondition'], + $emitter, + 'postConditionCalled', + 'postConditionErrored', + 'postConditionFailed', + 'postConditionFinished', + ); + } + + /** + * @param array{beforeClass: HookMethodCollection, before: HookMethodCollection, preCondition: HookMethodCollection, postCondition: HookMethodCollection, after: HookMethodCollection, afterClass: HookMethodCollection} $hookMethods + * + * @throws Throwable + */ + private function invokeAfterTestHookMethods(array $hookMethods, Event\Emitter $emitter): void + { + $this->invokeHookMethods( + $hookMethods['after'], + $emitter, + 'afterTestMethodCalled', + 'afterTestMethodErrored', + 'afterTestMethodFailed', + 'afterTestMethodFinished', + ); + } + + /** + * @param array{beforeClass: HookMethodCollection, before: HookMethodCollection, preCondition: HookMethodCollection, postCondition: HookMethodCollection, after: HookMethodCollection, afterClass: HookMethodCollection} $hookMethods + * + * @throws Throwable + * + * @codeCoverageIgnore + */ + private function invokeAfterClassHookMethods(array $hookMethods, Event\Emitter $emitter): void + { + $this->invokeHookMethods( + $hookMethods['afterClass'], + $emitter, + 'afterLastTestMethodCalled', + 'afterLastTestMethodErrored', + 'afterLastTestMethodFailed', + 'afterLastTestMethodFinished', + false, + ); + } + + /** + * @param 'afterLastTestMethodCalled'|'afterTestMethodCalled'|'beforeFirstTestMethodCalled'|'beforeTestMethodCalled'|'postConditionCalled'|'preConditionCalled' $calledMethod + * @param 'afterLastTestMethodErrored'|'afterTestMethodErrored'|'beforeFirstTestMethodErrored'|'beforeTestMethodErrored'|'postConditionErrored'|'preConditionErrored' $erroredMethod + * @param 'afterLastTestMethodFailed'|'afterTestMethodFailed'|'beforeFirstTestMethodFailed'|'beforeTestMethodFailed'|'postConditionFailed'|'preConditionFailed' $failedMethod + * @param 'afterLastTestMethodFinished'|'afterTestMethodFinished'|'beforeFirstTestMethodFinished'|'beforeTestMethodFinished'|'postConditionFinished'|'preConditionFinished' $finishedMethod + * + * @throws Throwable + */ + private function invokeHookMethods(HookMethodCollection $hookMethods, Event\Emitter $emitter, string $calledMethod, string $erroredMethod, string $failedMethod, string $finishedMethod, bool $forTestCase = true): void + { + if ($forTestCase) { + $test = $this->valueObjectForEvents(); + } else { + $test = static::class; + } + + $methodsInvoked = []; + + foreach ($hookMethods->methodNamesSortedByPriority() as $methodName) { + if ($this->methodDoesNotExistOrIsDeclaredInTestCase($methodName)) { + continue; + } + + $methodInvoked = new Event\Code\ClassMethod( + static::class, + $methodName, + ); + + try { + /** @phpstan-ignore method.dynamicName */ + $this->{$methodName}(); + } catch (Throwable $t) { + } + + /** @phpstan-ignore method.dynamicName */ + $emitter->{$calledMethod}( + $test, + $methodInvoked + ); + + $methodsInvoked[] = $methodInvoked; + + if (isset($t) && !$t instanceof SkippedTest) { + if ($t instanceof AssertionFailedError) { + $method = $failedMethod; + } else { + $method = $erroredMethod; + } + + /** @phpstan-ignore method.dynamicName */ + $emitter->{$method}( + $test, + $methodInvoked, + Event\Code\ThrowableBuilder::from($t), + ); + + break; + } + } + + if ($methodsInvoked !== []) { + /** @phpstan-ignore method.dynamicName */ + $emitter->{$finishedMethod}( + $test, + ...$methodsInvoked + ); + } + + if (isset($t)) { + throw $t; + } + } + + /** + * @param non-empty-string $methodName + */ + private function methodDoesNotExistOrIsDeclaredInTestCase(string $methodName): bool + { + $reflector = new ReflectionObject($this); + + return !$reflector->hasMethod($methodName) || + $reflector->getMethod($methodName)->getDeclaringClass()->getName() === self::class; + } + + /** + * @throws ExpectationFailedException + */ + private function verifyExceptionExpectations(\Exception|Throwable $exception): void + { + if ($this->expectedException !== null) { + $this->assertThat( + $exception, + new ExceptionConstraint( + $this->expectedException, + ), + ); + } + + if ($this->expectedExceptionMessage !== null) { + $this->assertThat( + $exception->getMessage(), + new ExceptionMessageIsOrContains( + $this->expectedExceptionMessage, + ), + ); + } + + if ($this->expectedExceptionMessageRegExp !== null) { + $this->assertThat( + $exception->getMessage(), + new ExceptionMessageMatchesRegularExpression( + $this->expectedExceptionMessageRegExp, + ), + ); + } + + if ($this->expectedExceptionCode !== null) { + $this->assertThat( + $exception->getCode(), + new ExceptionCode( + $this->expectedExceptionCode, + ), + ); + } + } + + /** + * @throws AssertionFailedError + */ + private function expectedExceptionWasNotRaised(): void + { + if ($this->expectedException !== null) { + $this->assertThat( + null, + new ExceptionConstraint($this->expectedException), + ); + } elseif ($this->expectedExceptionMessage !== null) { + $this->numberOfAssertionsPerformed++; + + throw new AssertionFailedError( + sprintf( + 'Failed asserting that exception with message "%s" is thrown', + $this->expectedExceptionMessage, + ), + ); + } elseif ($this->expectedExceptionMessageRegExp !== null) { + $this->numberOfAssertionsPerformed++; + + throw new AssertionFailedError( + sprintf( + 'Failed asserting that exception with message matching "%s" is thrown', + $this->expectedExceptionMessageRegExp, + ), + ); + } elseif ($this->expectedExceptionCode !== null) { + $this->numberOfAssertionsPerformed++; + + throw new AssertionFailedError( + sprintf( + 'Failed asserting that exception with code "%s" is thrown', + $this->expectedExceptionCode, + ), + ); } + } - return TestUtil::isTestMethod($method); + private function isRegisteredFailure(Throwable $t): bool + { + return array_any( + array_keys($this->failureTypes), + static fn (string $failureType) => $t instanceof $failureType, + ); + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + private function hasExpectationOnOutput(): bool + { + return is_string($this->outputExpectedString) || is_string($this->outputExpectedRegex); + } + + private function requirementsNotSatisfied(): bool + { + return (new Requirements)->requirementsNotSatisfiedFor(static::class, $this->methodName) !== []; + } + + private function requiresXdebug(): bool + { + return (new Requirements)->requiresXdebug(static::class, $this->methodName); + } + + /** + * @see https://github.com/sebastianbergmann/phpunit/issues/6095 + */ + private function handleExceptionFromInvokedCountMockObjectRule(Throwable $t): void + { + if (!$t instanceof ExpectationFailedException) { + return; + } + + $trace = $t->getTrace(); + + if (isset($trace[0]['class']) && $trace[0]['class'] === InvokedCount::class) { + $this->numberOfAssertionsPerformed++; + } + } + + private function startErrorLogCapture(): void + { + if (ini_get('display_errors') === '0') { + ShutdownHandler::setMessage( + 'Fatal error: Premature end of PHPUnit\'s PHP process. Use display_errors=On to see the error message.', + ); + } + + $errorLogCapture = tmpfile(); + + if ($errorLogCapture === false) { + return; + } + + $capturePath = stream_get_meta_data($errorLogCapture)['uri']; + + if (!@is_writable($capturePath)) { + return; + } + + $this->errorLogCapture = $errorLogCapture; + $this->previousErrorLogTarget = ini_set('error_log', $capturePath); + } + + /** + * @throws ErrorLogNotWritableException + */ + private function verifyErrorLogExpectation(): void + { + if ($this->errorLogCapture === false) { + if ($this->expectErrorLog) { + throw new ErrorLogNotWritableException; + } + + return; + } + + $errorLogOutput = stream_get_contents($this->errorLogCapture); + + if ($this->expectErrorLog) { + $this->assertNotEmpty($errorLogOutput, 'error_log() was not called'); + + return; + } + + if ($errorLogOutput === false) { + return; + } + + print $this->stripDateFromErrorLog($errorLogOutput); + } + + private function handleErrorLogError(): void + { + if ($this->errorLogCapture === false) { + return; + } + + if ($this->expectErrorLog) { + return; + } + + $errorLogOutput = stream_get_contents($this->errorLogCapture); + + if ($errorLogOutput !== false) { + print $this->stripDateFromErrorLog($errorLogOutput); + } + } + + private function stopErrorLogCapture(): void + { + if ($this->errorLogCapture === false) { + return; + } + + ShutdownHandler::resetMessage(); + + fclose($this->errorLogCapture); + + $this->errorLogCapture = false; + + if ($this->previousErrorLogTarget === false) { + return; + } + + ini_set('error_log', $this->previousErrorLogTarget); + + $this->previousErrorLogTarget = false; + } + + /** + * Creates a test stub for the specified interface or class. + * + * @template RealInstanceType of object + * + * @param class-string $type + * + * @throws InvalidArgumentException + * @throws MockObjectException + * @throws NoPreviousThrowableException + * + * @return RealInstanceType&Stub + */ + final protected static function createStub(string $type): Stub + { + $stub = (new MockGenerator)->testDouble( + $type, + false, + callOriginalConstructor: false, + callOriginalClone: false, + returnValueGeneration: self::generateReturnValuesForTestDoubles(), + ); + + Event\Facade::emitter()->testCreatedStub($type); + + assert($stub instanceof $type); + assert($stub instanceof Stub); + + return $stub; + } + + /** + * @param list $interfaces + * + * @throws MockObjectException + */ + final protected static function createStubForIntersectionOfInterfaces(array $interfaces): Stub + { + $stub = (new MockGenerator)->testDoubleForInterfaceIntersection( + $interfaces, + false, + returnValueGeneration: self::generateReturnValuesForTestDoubles(), + ); + + Event\Facade::emitter()->testCreatedStubForIntersectionOfInterfaces($interfaces); + + return $stub; + } + + /** + * Creates (and configures) a test stub for the specified interface or class. + * + * @template RealInstanceType of object + * + * @param class-string $type + * @param array $configuration + * + * @throws InvalidArgumentException + * @throws MockObjectException + * @throws NoPreviousThrowableException + * + * @return RealInstanceType&Stub + */ + final protected static function createConfiguredStub(string $type, array $configuration): Stub + { + $o = self::createStub($type); + + foreach ($configuration as $method => $return) { + $o->method($method)->willReturn($return); + } + + return $o; + } + + private static function generateReturnValuesForTestDoubles(): bool + { + return MetadataRegistry::parser()->forClass(static::class)->isDisableReturnValueGenerationForTestDoubles()->isEmpty(); } } diff --git a/src/Framework/TestFailure.php b/src/Framework/TestFailure.php deleted file mode 100644 index 0764bc7896a..00000000000 --- a/src/Framework/TestFailure.php +++ /dev/null @@ -1,155 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use function get_class; -use function sprintf; -use function trim; -use PHPUnit\Framework\Error\Error; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class TestFailure -{ - /** - * @var null|Test - */ - private $failedTest; - - /** - * @var Throwable - */ - private $thrownException; - - /** - * @var string - */ - private $testName; - - /** - * Returns a description for an exception. - */ - public static function exceptionToString(Throwable $e): string - { - if ($e instanceof SelfDescribing) { - $buffer = $e->toString(); - - if ($e instanceof ExpectationFailedException && $e->getComparisonFailure()) { - $buffer .= $e->getComparisonFailure()->getDiff(); - } - - if ($e instanceof PHPTAssertionFailedError) { - $buffer .= $e->getDiff(); - } - - if (!empty($buffer)) { - $buffer = trim($buffer) . "\n"; - } - - return $buffer; - } - - if ($e instanceof Error) { - return $e->getMessage() . "\n"; - } - - if ($e instanceof ExceptionWrapper) { - return $e->getClassName() . ': ' . $e->getMessage() . "\n"; - } - - return get_class($e) . ': ' . $e->getMessage() . "\n"; - } - - /** - * Constructs a TestFailure with the given test and exception. - */ - public function __construct(Test $failedTest, Throwable $t) - { - if ($failedTest instanceof SelfDescribing) { - $this->testName = $failedTest->toString(); - } else { - $this->testName = get_class($failedTest); - } - - if (!$failedTest instanceof TestCase || !$failedTest->isInIsolation()) { - $this->failedTest = $failedTest; - } - - $this->thrownException = $t; - } - - /** - * Returns a short description of the failure. - */ - public function toString(): string - { - return sprintf( - '%s: %s', - $this->testName, - $this->thrownException->getMessage() - ); - } - - /** - * Returns a description for the thrown exception. - */ - public function getExceptionAsString(): string - { - return self::exceptionToString($this->thrownException); - } - - /** - * Returns the name of the failing test (including data set, if any). - */ - public function getTestName(): string - { - return $this->testName; - } - - /** - * Returns the failing test. - * - * Note: The test object is not set when the test is executed in process - * isolation. - * - * @see Exception - */ - public function failedTest(): ?Test - { - return $this->failedTest; - } - - /** - * Gets the thrown exception. - */ - public function thrownException(): Throwable - { - return $this->thrownException; - } - - /** - * Returns the exception's message. - */ - public function exceptionMessage(): string - { - return $this->thrownException()->getMessage(); - } - - /** - * Returns true if the thrown exception - * is of type AssertionFailedError. - */ - public function isFailure(): bool - { - return $this->thrownException() instanceof AssertionFailedError; - } -} diff --git a/src/Framework/TestListener.php b/src/Framework/TestListener.php deleted file mode 100644 index 96ce4eec737..00000000000 --- a/src/Framework/TestListener.php +++ /dev/null @@ -1,84 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use Throwable; - -/** - * @deprecated Use the `TestHook` interfaces instead - */ -interface TestListener -{ - /** - * An error occurred. - * - * @deprecated Use `AfterTestErrorHook::executeAfterTestError` instead - */ - public function addError(Test $test, Throwable $t, float $time): void; - - /** - * A warning occurred. - * - * @deprecated Use `AfterTestWarningHook::executeAfterTestWarning` instead - */ - public function addWarning(Test $test, Warning $e, float $time): void; - - /** - * A failure occurred. - * - * @deprecated Use `AfterTestFailureHook::executeAfterTestFailure` instead - */ - public function addFailure(Test $test, AssertionFailedError $e, float $time): void; - - /** - * Incomplete test. - * - * @deprecated Use `AfterIncompleteTestHook::executeAfterIncompleteTest` instead - */ - public function addIncompleteTest(Test $test, Throwable $t, float $time): void; - - /** - * Risky test. - * - * @deprecated Use `AfterRiskyTestHook::executeAfterRiskyTest` instead - */ - public function addRiskyTest(Test $test, Throwable $t, float $time): void; - - /** - * Skipped test. - * - * @deprecated Use `AfterSkippedTestHook::executeAfterSkippedTest` instead - */ - public function addSkippedTest(Test $test, Throwable $t, float $time): void; - - /** - * A test suite started. - */ - public function startTestSuite(TestSuite $suite): void; - - /** - * A test suite ended. - */ - public function endTestSuite(TestSuite $suite): void; - - /** - * A test started. - * - * @deprecated Use `BeforeTestHook::executeBeforeTest` instead - */ - public function startTest(Test $test): void; - - /** - * A test ended. - * - * @deprecated Use `AfterTestHook::executeAfterTest` instead - */ - public function endTest(Test $test, float $time): void; -} diff --git a/src/Framework/TestListenerDefaultImplementation.php b/src/Framework/TestListenerDefaultImplementation.php deleted file mode 100644 index 7c99f5cb240..00000000000 --- a/src/Framework/TestListenerDefaultImplementation.php +++ /dev/null @@ -1,58 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use Throwable; - -/** - * @deprecated The `TestListener` interface is deprecated - */ -trait TestListenerDefaultImplementation -{ - public function addError(Test $test, Throwable $t, float $time): void - { - } - - public function addWarning(Test $test, Warning $e, float $time): void - { - } - - public function addFailure(Test $test, AssertionFailedError $e, float $time): void - { - } - - public function addIncompleteTest(Test $test, Throwable $t, float $time): void - { - } - - public function addRiskyTest(Test $test, Throwable $t, float $time): void - { - } - - public function addSkippedTest(Test $test, Throwable $t, float $time): void - { - } - - public function startTestSuite(TestSuite $suite): void - { - } - - public function endTestSuite(TestSuite $suite): void - { - } - - public function startTest(Test $test): void - { - } - - public function endTest(Test $test, float $time): void - { - } -} diff --git a/src/Framework/TestResult.php b/src/Framework/TestResult.php deleted file mode 100644 index 818aa990a2a..00000000000 --- a/src/Framework/TestResult.php +++ /dev/null @@ -1,1273 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use const PHP_EOL; -use function count; -use function function_exists; -use function get_class; -use function sprintf; -use function xdebug_get_monitored_functions; -use function xdebug_start_function_monitor; -use function xdebug_stop_function_monitor; -use AssertionError; -use Countable; -use Error; -use PHPUnit\Framework\MockObject\Exception as MockObjectException; -use PHPUnit\Util\ErrorHandler; -use PHPUnit\Util\ExcludeList; -use PHPUnit\Util\Printer; -use PHPUnit\Util\Test as TestUtil; -use ReflectionClass; -use ReflectionException; -use SebastianBergmann\CodeCoverage\CodeCoverage; -use SebastianBergmann\CodeCoverage\Exception as OriginalCodeCoverageException; -use SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException; -use SebastianBergmann\Invoker\Invoker; -use SebastianBergmann\Invoker\TimeoutException; -use SebastianBergmann\ResourceOperations\ResourceOperations; -use SebastianBergmann\Timer\Timer; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class TestResult implements Countable -{ - /** - * @var array - */ - private $passed = []; - - /** - * @var array - */ - private $passedTestClasses = []; - - /** - * @var bool - */ - private $currentTestSuiteFailed = false; - - /** - * @var TestFailure[] - */ - private $errors = []; - - /** - * @var TestFailure[] - */ - private $failures = []; - - /** - * @var TestFailure[] - */ - private $warnings = []; - - /** - * @var TestFailure[] - */ - private $notImplemented = []; - - /** - * @var TestFailure[] - */ - private $risky = []; - - /** - * @var TestFailure[] - */ - private $skipped = []; - - /** - * @deprecated Use the `TestHook` interfaces instead - * - * @var TestListener[] - */ - private $listeners = []; - - /** - * @var int - */ - private $runTests = 0; - - /** - * @var float - */ - private $time = 0; - - /** - * @var TestSuite - */ - private $topTestSuite; - - /** - * Code Coverage information. - * - * @var CodeCoverage - */ - private $codeCoverage; - - /** - * @var bool - */ - private $convertDeprecationsToExceptions = true; - - /** - * @var bool - */ - private $convertErrorsToExceptions = true; - - /** - * @var bool - */ - private $convertNoticesToExceptions = true; - - /** - * @var bool - */ - private $convertWarningsToExceptions = true; - - /** - * @var bool - */ - private $stop = false; - - /** - * @var bool - */ - private $stopOnError = false; - - /** - * @var bool - */ - private $stopOnFailure = false; - - /** - * @var bool - */ - private $stopOnWarning = false; - - /** - * @var bool - */ - private $beStrictAboutTestsThatDoNotTestAnything = true; - - /** - * @var bool - */ - private $beStrictAboutOutputDuringTests = false; - - /** - * @var bool - */ - private $beStrictAboutTodoAnnotatedTests = false; - - /** - * @var bool - */ - private $beStrictAboutResourceUsageDuringSmallTests = false; - - /** - * @var bool - */ - private $enforceTimeLimit = false; - - /** - * @var bool - */ - private $forceCoversAnnotation = false; - - /** - * @var int - */ - private $timeoutForSmallTests = 1; - - /** - * @var int - */ - private $timeoutForMediumTests = 10; - - /** - * @var int - */ - private $timeoutForLargeTests = 60; - - /** - * @var bool - */ - private $stopOnRisky = false; - - /** - * @var bool - */ - private $stopOnIncomplete = false; - - /** - * @var bool - */ - private $stopOnSkipped = false; - - /** - * @var bool - */ - private $lastTestFailed = false; - - /** - * @var int - */ - private $defaultTimeLimit = 0; - - /** - * @var bool - */ - private $stopOnDefect = false; - - /** - * @var bool - */ - private $registerMockObjectsFromTestArgumentsRecursively = false; - - /** - * @deprecated Use the `TestHook` interfaces instead - * - * @codeCoverageIgnore - * - * Registers a TestListener. - */ - public function addListener(TestListener $listener): void - { - $this->listeners[] = $listener; - } - - /** - * @deprecated Use the `TestHook` interfaces instead - * - * @codeCoverageIgnore - * - * Unregisters a TestListener. - */ - public function removeListener(TestListener $listener): void - { - foreach ($this->listeners as $key => $_listener) { - if ($listener === $_listener) { - unset($this->listeners[$key]); - } - } - } - - /** - * @deprecated Use the `TestHook` interfaces instead - * - * @codeCoverageIgnore - * - * Flushes all flushable TestListeners. - */ - public function flushListeners(): void - { - foreach ($this->listeners as $listener) { - if ($listener instanceof Printer) { - $listener->flush(); - } - } - } - - /** - * Adds an error to the list of errors. - */ - public function addError(Test $test, Throwable $t, float $time): void - { - if ($t instanceof RiskyTestError) { - $this->risky[] = new TestFailure($test, $t); - $notifyMethod = 'addRiskyTest'; - - if ($test instanceof TestCase) { - $test->markAsRisky(); - } - - if ($this->stopOnRisky || $this->stopOnDefect) { - $this->stop(); - } - } elseif ($t instanceof IncompleteTest) { - $this->notImplemented[] = new TestFailure($test, $t); - $notifyMethod = 'addIncompleteTest'; - - if ($this->stopOnIncomplete) { - $this->stop(); - } - } elseif ($t instanceof SkippedTest) { - $this->skipped[] = new TestFailure($test, $t); - $notifyMethod = 'addSkippedTest'; - - if ($this->stopOnSkipped) { - $this->stop(); - } - } else { - $this->errors[] = new TestFailure($test, $t); - $notifyMethod = 'addError'; - - if ($this->stopOnError || $this->stopOnFailure) { - $this->stop(); - } - } - - // @see https://github.com/sebastianbergmann/phpunit/issues/1953 - if ($t instanceof Error) { - $t = new ExceptionWrapper($t); - } - - foreach ($this->listeners as $listener) { - $listener->{$notifyMethod}($test, $t, $time); - } - - $this->lastTestFailed = true; - $this->time += $time; - } - - /** - * Adds a warning to the list of warnings. - * The passed in exception caused the warning. - */ - public function addWarning(Test $test, Warning $e, float $time): void - { - if ($this->stopOnWarning || $this->stopOnDefect) { - $this->stop(); - } - - $this->warnings[] = new TestFailure($test, $e); - - foreach ($this->listeners as $listener) { - $listener->addWarning($test, $e, $time); - } - - $this->time += $time; - } - - /** - * Adds a failure to the list of failures. - * The passed in exception caused the failure. - */ - public function addFailure(Test $test, AssertionFailedError $e, float $time): void - { - if ($e instanceof RiskyTestError || $e instanceof OutputError) { - $this->risky[] = new TestFailure($test, $e); - $notifyMethod = 'addRiskyTest'; - - if ($test instanceof TestCase) { - $test->markAsRisky(); - } - - if ($this->stopOnRisky || $this->stopOnDefect) { - $this->stop(); - } - } elseif ($e instanceof IncompleteTest) { - $this->notImplemented[] = new TestFailure($test, $e); - $notifyMethod = 'addIncompleteTest'; - - if ($this->stopOnIncomplete) { - $this->stop(); - } - } elseif ($e instanceof SkippedTest) { - $this->skipped[] = new TestFailure($test, $e); - $notifyMethod = 'addSkippedTest'; - - if ($this->stopOnSkipped) { - $this->stop(); - } - } else { - $this->failures[] = new TestFailure($test, $e); - $notifyMethod = 'addFailure'; - - if ($this->stopOnFailure || $this->stopOnDefect) { - $this->stop(); - } - } - - foreach ($this->listeners as $listener) { - $listener->{$notifyMethod}($test, $e, $time); - } - - $this->lastTestFailed = true; - $this->time += $time; - } - - /** - * Informs the result that a test suite will be started. - */ - public function startTestSuite(TestSuite $suite): void - { - $this->currentTestSuiteFailed = false; - - if ($this->topTestSuite === null) { - $this->topTestSuite = $suite; - } - - foreach ($this->listeners as $listener) { - $listener->startTestSuite($suite); - } - } - - /** - * Informs the result that a test suite was completed. - */ - public function endTestSuite(TestSuite $suite): void - { - if (!$this->currentTestSuiteFailed) { - $this->passedTestClasses[] = $suite->getName(); - } - - foreach ($this->listeners as $listener) { - $listener->endTestSuite($suite); - } - } - - /** - * Informs the result that a test will be started. - */ - public function startTest(Test $test): void - { - $this->lastTestFailed = false; - $this->runTests += count($test); - - foreach ($this->listeners as $listener) { - $listener->startTest($test); - } - } - - /** - * Informs the result that a test was completed. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function endTest(Test $test, float $time): void - { - foreach ($this->listeners as $listener) { - $listener->endTest($test, $time); - } - - if (!$this->lastTestFailed && $test instanceof TestCase) { - $class = get_class($test); - $key = $class . '::' . $test->getName(); - - $this->passed[$key] = [ - 'result' => $test->getResult(), - 'size' => \PHPUnit\Util\Test::getSize( - $class, - $test->getName(false) - ), - ]; - - $this->time += $time; - } - - if ($this->lastTestFailed && $test instanceof TestCase) { - $this->currentTestSuiteFailed = true; - } - } - - /** - * Returns true if no risky test occurred. - */ - public function allHarmless(): bool - { - return $this->riskyCount() === 0; - } - - /** - * Gets the number of risky tests. - */ - public function riskyCount(): int - { - return count($this->risky); - } - - /** - * Returns true if no incomplete test occurred. - */ - public function allCompletelyImplemented(): bool - { - return $this->notImplementedCount() === 0; - } - - /** - * Gets the number of incomplete tests. - */ - public function notImplementedCount(): int - { - return count($this->notImplemented); - } - - /** - * Returns an array of TestFailure objects for the risky tests. - * - * @return TestFailure[] - */ - public function risky(): array - { - return $this->risky; - } - - /** - * Returns an array of TestFailure objects for the incomplete tests. - * - * @return TestFailure[] - */ - public function notImplemented(): array - { - return $this->notImplemented; - } - - /** - * Returns true if no test has been skipped. - */ - public function noneSkipped(): bool - { - return $this->skippedCount() === 0; - } - - /** - * Gets the number of skipped tests. - */ - public function skippedCount(): int - { - return count($this->skipped); - } - - /** - * Returns an array of TestFailure objects for the skipped tests. - * - * @return TestFailure[] - */ - public function skipped(): array - { - return $this->skipped; - } - - /** - * Gets the number of detected errors. - */ - public function errorCount(): int - { - return count($this->errors); - } - - /** - * Returns an array of TestFailure objects for the errors. - * - * @return TestFailure[] - */ - public function errors(): array - { - return $this->errors; - } - - /** - * Gets the number of detected failures. - */ - public function failureCount(): int - { - return count($this->failures); - } - - /** - * Returns an array of TestFailure objects for the failures. - * - * @return TestFailure[] - */ - public function failures(): array - { - return $this->failures; - } - - /** - * Gets the number of detected warnings. - */ - public function warningCount(): int - { - return count($this->warnings); - } - - /** - * Returns an array of TestFailure objects for the warnings. - * - * @return TestFailure[] - */ - public function warnings(): array - { - return $this->warnings; - } - - /** - * Returns the names of the tests that have passed. - */ - public function passed(): array - { - return $this->passed; - } - - /** - * Returns the names of the TestSuites that have passed. - * - * This enables @depends-annotations for TestClassName::class - */ - public function passedClasses(): array - { - return $this->passedTestClasses; - } - - /** - * Returns the (top) test suite. - */ - public function topTestSuite(): TestSuite - { - return $this->topTestSuite; - } - - /** - * Returns whether code coverage information should be collected. - */ - public function getCollectCodeCoverageInformation(): bool - { - return $this->codeCoverage !== null; - } - - /** - * Runs a TestCase. - * - * @throws CodeCoverageException - * @throws UnintentionallyCoveredCodeException - * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function run(Test $test): void - { - Assert::resetCount(); - - if ($test instanceof TestCase) { - $test->setRegisterMockObjectsFromTestArgumentsRecursively( - $this->registerMockObjectsFromTestArgumentsRecursively - ); - - $isAnyCoverageRequired = TestUtil::requiresCodeCoverageDataCollection($test); - } - - $error = false; - $failure = false; - $warning = false; - $incomplete = false; - $risky = false; - $skipped = false; - - $this->startTest($test); - - if ($this->convertDeprecationsToExceptions || $this->convertErrorsToExceptions || $this->convertNoticesToExceptions || $this->convertWarningsToExceptions) { - $errorHandler = new ErrorHandler( - $this->convertDeprecationsToExceptions, - $this->convertErrorsToExceptions, - $this->convertNoticesToExceptions, - $this->convertWarningsToExceptions - ); - - $errorHandler->register(); - } - - $collectCodeCoverage = $this->codeCoverage !== null && - !$test instanceof WarningTestCase && - $isAnyCoverageRequired; - - if ($collectCodeCoverage) { - $this->codeCoverage->start($test); - } - - $monitorFunctions = $this->beStrictAboutResourceUsageDuringSmallTests && - !$test instanceof WarningTestCase && - $test->getSize() === \PHPUnit\Util\Test::SMALL && - function_exists('xdebug_start_function_monitor'); - - if ($monitorFunctions) { - /* @noinspection ForgottenDebugOutputInspection */ - xdebug_start_function_monitor(ResourceOperations::getFunctions()); - } - - $timer = new Timer; - $timer->start(); - - try { - $invoker = new Invoker; - - if (!$test instanceof WarningTestCase && - $this->enforceTimeLimit && - ($this->defaultTimeLimit || $test->getSize() != \PHPUnit\Util\Test::UNKNOWN) && - $invoker->canInvokeWithTimeout()) { - switch ($test->getSize()) { - case \PHPUnit\Util\Test::SMALL: - $_timeout = $this->timeoutForSmallTests; - - break; - - case \PHPUnit\Util\Test::MEDIUM: - $_timeout = $this->timeoutForMediumTests; - - break; - - case \PHPUnit\Util\Test::LARGE: - $_timeout = $this->timeoutForLargeTests; - - break; - - case \PHPUnit\Util\Test::UNKNOWN: - $_timeout = $this->defaultTimeLimit; - - break; - } - - $invoker->invoke([$test, 'runBare'], [], $_timeout); - } else { - $test->runBare(); - } - } catch (TimeoutException $e) { - $this->addFailure( - $test, - new RiskyTestError( - $e->getMessage() - ), - $_timeout - ); - - $risky = true; - } catch (MockObjectException $e) { - $e = new Warning( - $e->getMessage() - ); - - $warning = true; - } catch (AssertionFailedError $e) { - $failure = true; - - if ($e instanceof RiskyTestError) { - $risky = true; - } elseif ($e instanceof IncompleteTestError) { - $incomplete = true; - } elseif ($e instanceof SkippedTestError) { - $skipped = true; - } - } catch (AssertionError $e) { - $test->addToAssertionCount(1); - - $failure = true; - $frame = $e->getTrace()[0]; - - $e = new AssertionFailedError( - sprintf( - '%s in %s:%s', - $e->getMessage(), - $frame['file'], - $frame['line'] - ) - ); - } catch (Warning $e) { - $warning = true; - } catch (Exception $e) { - $error = true; - } catch (Throwable $e) { - $e = new ExceptionWrapper($e); - $error = true; - } - - $time = $timer->stop()->asSeconds(); - - $test->addToAssertionCount(Assert::getCount()); - - if ($monitorFunctions) { - $excludeList = new ExcludeList; - - /** @noinspection ForgottenDebugOutputInspection */ - $functions = xdebug_get_monitored_functions(); - - /* @noinspection ForgottenDebugOutputInspection */ - xdebug_stop_function_monitor(); - - foreach ($functions as $function) { - if (!$excludeList->isExcluded($function['filename'])) { - $this->addFailure( - $test, - new RiskyTestError( - sprintf( - '%s() used in %s:%s', - $function['function'], - $function['filename'], - $function['lineno'] - ) - ), - $time - ); - } - } - } - - if ($this->beStrictAboutTestsThatDoNotTestAnything && - $test->getNumAssertions() === 0) { - $risky = true; - } - - if ($this->forceCoversAnnotation && !$incomplete && !$skipped) { - $annotations = $test->getAnnotations(); - - if (!isset($annotations['class']['covers']) && - !isset($annotations['method']['covers']) && - !isset($annotations['class']['coversNothing']) && - !isset($annotations['method']['coversNothing'])) { - $this->addFailure( - $test, - new MissingCoversAnnotationException( - 'This test does not have a @covers annotation but is expected to have one' - ), - $time - ); - - $risky = true; - } - } - - if ($collectCodeCoverage) { - $append = !$risky && !$incomplete && !$skipped; - $linesToBeCovered = []; - $linesToBeUsed = []; - - if ($append && $test instanceof TestCase) { - try { - $linesToBeCovered = \PHPUnit\Util\Test::getLinesToBeCovered( - get_class($test), - $test->getName(false) - ); - - $linesToBeUsed = \PHPUnit\Util\Test::getLinesToBeUsed( - get_class($test), - $test->getName(false) - ); - } catch (InvalidCoversTargetException $cce) { - $this->addWarning( - $test, - new Warning( - $cce->getMessage() - ), - $time - ); - } - } - - try { - $this->codeCoverage->stop( - $append, - $linesToBeCovered, - $linesToBeUsed - ); - } catch (UnintentionallyCoveredCodeException $cce) { - $this->addFailure( - $test, - new UnintentionallyCoveredCodeError( - 'This test executed code that is not listed as code to be covered or used:' . - PHP_EOL . $cce->getMessage() - ), - $time - ); - } catch (OriginalCodeCoverageException $cce) { - $error = true; - - $e = $e ?? $cce; - } - } - - if (isset($errorHandler)) { - $errorHandler->unregister(); - - unset($errorHandler); - } - - if ($error) { - $this->addError($test, $e, $time); - } elseif ($failure) { - $this->addFailure($test, $e, $time); - } elseif ($warning) { - $this->addWarning($test, $e, $time); - } elseif ($this->beStrictAboutTestsThatDoNotTestAnything && - !$test->doesNotPerformAssertions() && - $test->getNumAssertions() === 0) { - try { - $reflected = new ReflectionClass($test); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - $name = $test->getName(false); - - if ($name && $reflected->hasMethod($name)) { - try { - $reflected = $reflected->getMethod($name); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - } - - $this->addFailure( - $test, - new RiskyTestError( - sprintf( - "This test did not perform any assertions\n\n%s:%d", - $reflected->getFileName(), - $reflected->getStartLine() - ) - ), - $time - ); - } elseif ($this->beStrictAboutTestsThatDoNotTestAnything && - $test->doesNotPerformAssertions() && - $test->getNumAssertions() > 0) { - $this->addFailure( - $test, - new RiskyTestError( - sprintf( - 'This test is annotated with "@doesNotPerformAssertions" but performed %d assertions', - $test->getNumAssertions() - ) - ), - $time - ); - } elseif ($this->beStrictAboutOutputDuringTests && $test->hasOutput()) { - $this->addFailure( - $test, - new OutputError( - sprintf( - 'This test printed output: %s', - $test->getActualOutput() - ) - ), - $time - ); - } elseif ($this->beStrictAboutTodoAnnotatedTests && $test instanceof TestCase) { - $annotations = $test->getAnnotations(); - - if (isset($annotations['method']['todo'])) { - $this->addFailure( - $test, - new RiskyTestError( - 'Test method is annotated with @todo' - ), - $time - ); - } - } - - $this->endTest($test, $time); - } - - /** - * Gets the number of run tests. - */ - public function count(): int - { - return $this->runTests; - } - - /** - * Checks whether the test run should stop. - */ - public function shouldStop(): bool - { - return $this->stop; - } - - /** - * Marks that the test run should stop. - */ - public function stop(): void - { - $this->stop = true; - } - - /** - * Returns the code coverage object. - */ - public function getCodeCoverage(): ?CodeCoverage - { - return $this->codeCoverage; - } - - /** - * Sets the code coverage object. - */ - public function setCodeCoverage(CodeCoverage $codeCoverage): void - { - $this->codeCoverage = $codeCoverage; - } - - /** - * Enables or disables the deprecation-to-exception conversion. - */ - public function convertDeprecationsToExceptions(bool $flag): void - { - $this->convertDeprecationsToExceptions = $flag; - } - - /** - * Returns the deprecation-to-exception conversion setting. - */ - public function getConvertDeprecationsToExceptions(): bool - { - return $this->convertDeprecationsToExceptions; - } - - /** - * Enables or disables the error-to-exception conversion. - */ - public function convertErrorsToExceptions(bool $flag): void - { - $this->convertErrorsToExceptions = $flag; - } - - /** - * Returns the error-to-exception conversion setting. - */ - public function getConvertErrorsToExceptions(): bool - { - return $this->convertErrorsToExceptions; - } - - /** - * Enables or disables the notice-to-exception conversion. - */ - public function convertNoticesToExceptions(bool $flag): void - { - $this->convertNoticesToExceptions = $flag; - } - - /** - * Returns the notice-to-exception conversion setting. - */ - public function getConvertNoticesToExceptions(): bool - { - return $this->convertNoticesToExceptions; - } - - /** - * Enables or disables the warning-to-exception conversion. - */ - public function convertWarningsToExceptions(bool $flag): void - { - $this->convertWarningsToExceptions = $flag; - } - - /** - * Returns the warning-to-exception conversion setting. - */ - public function getConvertWarningsToExceptions(): bool - { - return $this->convertWarningsToExceptions; - } - - /** - * Enables or disables the stopping when an error occurs. - */ - public function stopOnError(bool $flag): void - { - $this->stopOnError = $flag; - } - - /** - * Enables or disables the stopping when a failure occurs. - */ - public function stopOnFailure(bool $flag): void - { - $this->stopOnFailure = $flag; - } - - /** - * Enables or disables the stopping when a warning occurs. - */ - public function stopOnWarning(bool $flag): void - { - $this->stopOnWarning = $flag; - } - - public function beStrictAboutTestsThatDoNotTestAnything(bool $flag): void - { - $this->beStrictAboutTestsThatDoNotTestAnything = $flag; - } - - public function isStrictAboutTestsThatDoNotTestAnything(): bool - { - return $this->beStrictAboutTestsThatDoNotTestAnything; - } - - public function beStrictAboutOutputDuringTests(bool $flag): void - { - $this->beStrictAboutOutputDuringTests = $flag; - } - - public function isStrictAboutOutputDuringTests(): bool - { - return $this->beStrictAboutOutputDuringTests; - } - - public function beStrictAboutResourceUsageDuringSmallTests(bool $flag): void - { - $this->beStrictAboutResourceUsageDuringSmallTests = $flag; - } - - public function isStrictAboutResourceUsageDuringSmallTests(): bool - { - return $this->beStrictAboutResourceUsageDuringSmallTests; - } - - public function enforceTimeLimit(bool $flag): void - { - $this->enforceTimeLimit = $flag; - } - - public function enforcesTimeLimit(): bool - { - return $this->enforceTimeLimit; - } - - public function beStrictAboutTodoAnnotatedTests(bool $flag): void - { - $this->beStrictAboutTodoAnnotatedTests = $flag; - } - - public function isStrictAboutTodoAnnotatedTests(): bool - { - return $this->beStrictAboutTodoAnnotatedTests; - } - - public function forceCoversAnnotation(): void - { - $this->forceCoversAnnotation = true; - } - - public function forcesCoversAnnotation(): bool - { - return $this->forceCoversAnnotation; - } - - /** - * Enables or disables the stopping for risky tests. - */ - public function stopOnRisky(bool $flag): void - { - $this->stopOnRisky = $flag; - } - - /** - * Enables or disables the stopping for incomplete tests. - */ - public function stopOnIncomplete(bool $flag): void - { - $this->stopOnIncomplete = $flag; - } - - /** - * Enables or disables the stopping for skipped tests. - */ - public function stopOnSkipped(bool $flag): void - { - $this->stopOnSkipped = $flag; - } - - /** - * Enables or disables the stopping for defects: error, failure, warning. - */ - public function stopOnDefect(bool $flag): void - { - $this->stopOnDefect = $flag; - } - - /** - * Returns the time spent running the tests. - */ - public function time(): float - { - return $this->time; - } - - /** - * Returns whether the entire test was successful or not. - */ - public function wasSuccessful(): bool - { - return $this->wasSuccessfulIgnoringWarnings() && empty($this->warnings); - } - - public function wasSuccessfulIgnoringWarnings(): bool - { - return empty($this->errors) && empty($this->failures); - } - - public function wasSuccessfulAndNoTestIsRiskyOrSkippedOrIncomplete(): bool - { - return $this->wasSuccessful() && $this->allHarmless() && $this->allCompletelyImplemented() && $this->noneSkipped(); - } - - /** - * Sets the default timeout for tests. - */ - public function setDefaultTimeLimit(int $timeout): void - { - $this->defaultTimeLimit = $timeout; - } - - /** - * Sets the timeout for small tests. - */ - public function setTimeoutForSmallTests(int $timeout): void - { - $this->timeoutForSmallTests = $timeout; - } - - /** - * Sets the timeout for medium tests. - */ - public function setTimeoutForMediumTests(int $timeout): void - { - $this->timeoutForMediumTests = $timeout; - } - - /** - * Sets the timeout for large tests. - */ - public function setTimeoutForLargeTests(int $timeout): void - { - $this->timeoutForLargeTests = $timeout; - } - - /** - * Returns the set timeout for large tests. - */ - public function getTimeoutForLargeTests(): int - { - return $this->timeoutForLargeTests; - } - - public function setRegisterMockObjectsFromTestArgumentsRecursively(bool $flag): void - { - $this->registerMockObjectsFromTestArgumentsRecursively = $flag; - } -} diff --git a/src/Framework/TestRunner/ChildProcessResultProcessor.php b/src/Framework/TestRunner/ChildProcessResultProcessor.php new file mode 100644 index 00000000000..af487c1964e --- /dev/null +++ b/src/Framework/TestRunner/ChildProcessResultProcessor.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function assert; +use function trim; +use function unserialize; +use PHPUnit\Event\Code\TestMethodBuilder; +use PHPUnit\Event\Code\ThrowableBuilder; +use PHPUnit\Event\Emitter; +use PHPUnit\Event\Facade; +use PHPUnit\Runner\CodeCoverage; +use PHPUnit\TestRunner\TestResult\PassedTests; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ChildProcessResultProcessor +{ + private Facade $eventFacade; + private Emitter $emitter; + private PassedTests $passedTests; + private CodeCoverage $codeCoverage; + + public function __construct(Facade $eventFacade, Emitter $emitter, PassedTests $passedTests, CodeCoverage $codeCoverage) + { + $this->eventFacade = $eventFacade; + $this->emitter = $emitter; + $this->passedTests = $passedTests; + $this->codeCoverage = $codeCoverage; + } + + public function process(Test $test, string $serializedProcessResult, string $stderr): void + { + if ($stderr !== '') { + $exception = new Exception(trim($stderr)); + + assert($test instanceof TestCase); + + $this->emitter->testErrored( + TestMethodBuilder::fromTestCase($test), + ThrowableBuilder::from($exception), + ); + + return; + } + + $childResult = @unserialize($serializedProcessResult); + + if ($childResult === false) { + $this->emitter->childProcessErrored(); + + $exception = new AssertionFailedError('Test was run in child process and ended unexpectedly'); + + assert($test instanceof TestCase); + + $this->emitter->testErrored( + TestMethodBuilder::fromTestCase($test), + ThrowableBuilder::from($exception), + ); + + $this->emitter->testFinished( + TestMethodBuilder::fromTestCase($test), + 0, + ); + + return; + } + + $this->eventFacade->forward($childResult->events); + $this->passedTests->import($childResult->passedTests); + + assert($test instanceof TestCase); + + $test->setResult($childResult->testResult); + $test->addToAssertionCount($childResult->numAssertions); + + if (!$this->codeCoverage->isActive()) { + return; + } + + // @codeCoverageIgnoreStart + if (!$childResult->codeCoverage instanceof \SebastianBergmann\CodeCoverage\CodeCoverage) { + return; + } + + CodeCoverage::instance()->codeCoverage()->merge( + $childResult->codeCoverage, + ); + // @codeCoverageIgnoreEnd + } +} diff --git a/src/Framework/TestRunner/IsolatedTestRunner.php b/src/Framework/TestRunner/IsolatedTestRunner.php new file mode 100644 index 00000000000..82ac6460757 --- /dev/null +++ b/src/Framework/TestRunner/IsolatedTestRunner.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface IsolatedTestRunner +{ + public function run(TestCase $test, bool $preserveGlobalState, bool $requiresXdebug): void; +} diff --git a/src/Framework/TestRunner/IsolatedTestRunnerRegistry.php b/src/Framework/TestRunner/IsolatedTestRunnerRegistry.php new file mode 100644 index 00000000000..68cf84c9c42 --- /dev/null +++ b/src/Framework/TestRunner/IsolatedTestRunnerRegistry.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class IsolatedTestRunnerRegistry +{ + private static ?IsolatedTestRunner $runner = null; + + public static function run(TestCase $test, bool $preserveGlobalState, bool $requiresXdebug): void + { + if (self::$runner === null) { + self::$runner = new SeparateProcessTestRunner; + } + + self::$runner->run($test, $preserveGlobalState, $requiresXdebug); + } + + public static function set(IsolatedTestRunner $runner): void + { + self::$runner = $runner; + } +} diff --git a/src/Framework/TestRunner/SeparateProcessTestRunner.php b/src/Framework/TestRunner/SeparateProcessTestRunner.php new file mode 100644 index 00000000000..f838684fad5 --- /dev/null +++ b/src/Framework/TestRunner/SeparateProcessTestRunner.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function assert; +use function defined; +use function get_include_path; +use function hrtime; +use function serialize; +use function sys_get_temp_dir; +use function tempnam; +use function unlink; +use function var_export; +use PHPUnit\Event\NoPreviousThrowableException; +use PHPUnit\Runner\CodeCoverage; +use PHPUnit\TextUI\Configuration\Registry as ConfigurationRegistry; +use PHPUnit\Util\GlobalState; +use PHPUnit\Util\PHP\Job; +use PHPUnit\Util\PHP\JobRunnerRegistry; +use ReflectionClass; +use SebastianBergmann\Template\InvalidArgumentException; +use SebastianBergmann\Template\Template; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SeparateProcessTestRunner implements IsolatedTestRunner +{ + /** + * @throws \PHPUnit\Runner\Exception + * @throws \PHPUnit\Util\Exception + * @throws Exception + * @throws InvalidArgumentException + * @throws NoPreviousThrowableException + * @throws ProcessIsolationException + */ + public function run(TestCase $test, bool $preserveGlobalState, bool $requiresXdebug): void + { + $class = new ReflectionClass($test); + + $bootstrap = ''; + $constants = ''; + $globals = ''; + $includedFiles = ''; + $iniSettings = ''; + + if (ConfigurationRegistry::get()->hasBootstrap()) { + $bootstrap = ConfigurationRegistry::get()->bootstrap(); + } + + if ($preserveGlobalState) { + $constants = GlobalState::getConstantsAsString(); + $globals = GlobalState::getGlobalsAsString(); + $includedFiles = GlobalState::getIncludedFilesAsString(); + $iniSettings = GlobalState::getIniSettingsAsString(); + } + + $coverage = CodeCoverage::instance()->isActive() ? 'true' : 'false'; + + if (defined('PHPUNIT_COMPOSER_INSTALL')) { + $composerAutoload = var_export(PHPUNIT_COMPOSER_INSTALL, true); + } else { + $composerAutoload = '\'\''; + } + + if (defined('__PHPUNIT_PHAR__')) { + $phar = var_export(__PHPUNIT_PHAR__, true); + } else { + $phar = '\'\''; + } + + $data = var_export(serialize($test->providedData()), true); + $dataName = var_export($test->dataName(), true); + $dependencyInput = var_export(serialize($test->dependencyInput()), true); + $includePath = var_export(get_include_path(), true); + // must do these fixes because TestCaseMethod.tpl has unserialize('{data}') in it, and we can't break BC + // the lines above used to use addcslashes() rather than var_export(), which breaks null byte escape sequences + $data = "'." . $data . ".'"; + $dataName = "'.(" . $dataName . ").'"; + $dependencyInput = "'." . $dependencyInput . ".'"; + $includePath = "'." . $includePath . ".'"; + $offset = hrtime(); + $serializedConfiguration = $this->saveConfigurationForChildProcess(); + $processResultFile = tempnam(sys_get_temp_dir(), 'phpunit_'); + + $file = $class->getFileName(); + + assert($file !== false); + + $var = [ + 'bootstrap' => $bootstrap, + 'composerAutoload' => $composerAutoload, + 'phar' => $phar, + 'filename' => $file, + 'className' => $class->getName(), + 'methodName' => $test->name(), + 'collectCodeCoverageInformation' => $coverage, + 'data' => $data, + 'dataName' => $dataName, + 'dependencyInput' => $dependencyInput, + 'constants' => $constants, + 'globals' => $globals, + 'include_path' => $includePath, + 'included_files' => $includedFiles, + 'iniSettings' => $iniSettings, + 'name' => $test->name(), + 'offsetSeconds' => (string) $offset[0], + 'offsetNanoseconds' => (string) $offset[1], + 'serializedConfiguration' => $serializedConfiguration, + 'processResultFile' => $processResultFile, + ]; + + $template = new Template(__DIR__ . '/templates/method.tpl'); + + $template->setVar($var); + + $code = $template->render(); + + assert($code !== ''); + + JobRunnerRegistry::runTestJob(new Job($code, requiresXdebug: $requiresXdebug), $processResultFile, $test); + + @unlink($serializedConfiguration); + } + + /** + * @throws ProcessIsolationException + */ + private function saveConfigurationForChildProcess(): string + { + $path = tempnam(sys_get_temp_dir(), 'phpunit_'); + + if ($path === false) { + throw new ProcessIsolationException; + } + + if (!ConfigurationRegistry::saveTo($path)) { + throw new ProcessIsolationException; + } + + return $path; + } +} diff --git a/src/Framework/TestRunner/TestRunner.php b/src/Framework/TestRunner/TestRunner.php new file mode 100644 index 00000000000..e6cf03f6b1c --- /dev/null +++ b/src/Framework/TestRunner/TestRunner.php @@ -0,0 +1,395 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const PHP_EOL; +use function array_diff_assoc; +use function array_intersect; +use function array_unique; +use function assert; +use function extension_loaded; +use function sprintf; +use function xdebug_is_debugger_active; +use AssertionError; +use PHPUnit\Event\Facade; +use PHPUnit\Metadata\Api\CodeCoverage as CodeCoverageMetadataApi; +use PHPUnit\Metadata\Parser\Registry as MetadataRegistry; +use PHPUnit\Runner\CodeCoverage; +use PHPUnit\Runner\ErrorHandler; +use PHPUnit\Runner\Exception; +use PHPUnit\TextUI\Configuration\Configuration; +use PHPUnit\TextUI\Configuration\Registry as ConfigurationRegistry; +use SebastianBergmann\CodeCoverage\Exception as CodeCoverageException; +use SebastianBergmann\CodeCoverage\InvalidArgumentException; +use SebastianBergmann\CodeCoverage\Test\Target\TargetCollection; +use SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException; +use SebastianBergmann\Invoker\Invoker; +use SebastianBergmann\Invoker\TimeoutException; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestRunner +{ + private ?bool $timeLimitCanBeEnforced = null; + private readonly Configuration $configuration; + + public function __construct() + { + $this->configuration = ConfigurationRegistry::get(); + } + + /** + * @throws Exception + * @throws InvalidArgumentException + * @throws UnintentionallyCoveredCodeException + */ + public function run(TestCase $test): void + { + Assert::resetCount(); + + $codeCoverageMetadataApi = new CodeCoverageMetadataApi; + + $coversTargets = $codeCoverageMetadataApi->coversTargets( + $test::class, + $test->name(), + ); + + $usesTargets = $codeCoverageMetadataApi->usesTargets( + $test::class, + $test->name(), + ); + + $shouldCodeCoverageBeCollected = $codeCoverageMetadataApi->shouldCodeCoverageBeCollectedFor($test); + + $this->performSanityChecks($test, $coversTargets, $usesTargets, $shouldCodeCoverageBeCollected); + + $error = false; + $failure = false; + $incomplete = false; + $risky = false; + $skipped = false; + + if ($this->shouldErrorHandlerBeUsed($test)) { + ErrorHandler::instance()->enable($test); + } + + $collectCodeCoverage = CodeCoverage::instance()->isActive() && + $shouldCodeCoverageBeCollected; + + if ($collectCodeCoverage) { + CodeCoverage::instance()->start($test); + } + + try { + if ($this->canTimeLimitBeEnforced() && + $this->shouldTimeLimitBeEnforced($test)) { + $risky = $this->runTestWithTimeout($test); + } else { + $test->runBare(); + } + } catch (AssertionFailedError $e) { + $failure = true; + + if ($e instanceof IncompleteTestError) { + $incomplete = true; + } elseif ($e instanceof SkippedTest) { + $skipped = true; + } + } catch (AssertionError $e) { + $test->addToAssertionCount(1); + + $failure = true; + $frame = $e->getTrace()[0]; + + assert(isset($frame['file'])); + assert(isset($frame['line'])); + + $e = new AssertionFailedError( + sprintf( + '%s in %s:%s', + $e->getMessage(), + $frame['file'], + $frame['line'], + ), + ); + } catch (Throwable $e) { + $error = true; + } + + $test->addToAssertionCount(Assert::getCount()); + + if ($this->configuration->reportUselessTests() && + !$test->doesNotPerformAssertions() && + $test->numberOfAssertionsPerformed() === 0) { + $risky = true; + } + + if (!$error && !$failure && !$incomplete && !$skipped && !$risky && + $this->configuration->requireCoverageMetadata() && + !$this->hasCoverageMetadata($test::class, $test->name())) { + Facade::emitter()->testConsideredRisky( + $test->valueObjectForEvents(), + 'This test does not define a code coverage target but is expected to do so', + ); + + $risky = true; + } + + if ($collectCodeCoverage) { + $append = !$risky && !$incomplete && !$skipped; + + if (!$append) { + $coversTargets = false; + $usesTargets = null; + } + + try { + CodeCoverage::instance()->stop( + $append, + $coversTargets, + $usesTargets, + ); + } catch (UnintentionallyCoveredCodeException $cce) { + Facade::emitter()->testConsideredRisky( + $test->valueObjectForEvents(), + 'This test executed code that is not listed as code to be covered or used:' . + PHP_EOL . + $cce->getMessage(), + ); + } catch (CodeCoverageException $cce) { + $error = true; + + $e = $e ?? $cce; + } + } + + ErrorHandler::instance()->disable(); + + if (!$error && + !$incomplete && + !$skipped && + $this->configuration->reportUselessTests() && + !$test->doesNotPerformAssertions() && + $test->numberOfAssertionsPerformed() === 0) { + Facade::emitter()->testConsideredRisky( + $test->valueObjectForEvents(), + 'This test did not perform any assertions', + ); + } + + if ($test->doesNotPerformAssertions() && + $test->numberOfAssertionsPerformed() > 0) { + Facade::emitter()->testConsideredRisky( + $test->valueObjectForEvents(), + sprintf( + 'This test is not expected to perform assertions but performed %d assertion%s', + $test->numberOfAssertionsPerformed(), + $test->numberOfAssertionsPerformed() > 1 ? 's' : '', + ), + ); + } + + if ($test->hasUnexpectedOutput()) { + Facade::emitter()->testPrintedUnexpectedOutput($test->output()); + } + + if ($this->configuration->disallowTestOutput() && $test->hasUnexpectedOutput()) { + Facade::emitter()->testConsideredRisky( + $test->valueObjectForEvents(), + sprintf( + 'Test code or tested code printed unexpected output: %s', + $test->output(), + ), + ); + } + + if ($test->wasPrepared()) { + Facade::emitter()->testFinished( + $test->valueObjectForEvents(), + $test->numberOfAssertionsPerformed(), + ); + } + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + private function hasCoverageMetadata(string $className, string $methodName): bool + { + foreach (MetadataRegistry::parser()->forClassAndMethod($className, $methodName) as $metadata) { + if ($metadata->isCoversNamespace()) { + return true; + } + + if ($metadata->isCoversTrait()) { + return true; + } + + if ($metadata->isCoversClass()) { + return true; + } + + if ($metadata->isCoversClassesThatExtendClass()) { + return true; + } + + if ($metadata->isCoversClassesThatImplementInterface()) { + return true; + } + + if ($metadata->isCoversMethod()) { + return true; + } + + if ($metadata->isCoversFunction()) { + return true; + } + + if ($metadata->isCoversNothing()) { + return true; + } + } + + return false; + } + + private function canTimeLimitBeEnforced(): bool + { + if ($this->timeLimitCanBeEnforced !== null) { + return $this->timeLimitCanBeEnforced; + } + + $this->timeLimitCanBeEnforced = (new Invoker)->canInvokeWithTimeout(); + + return $this->timeLimitCanBeEnforced; + } + + private function shouldTimeLimitBeEnforced(TestCase $test): bool + { + if (!$this->configuration->enforceTimeLimit()) { + return false; + } + + if (!(($this->configuration->defaultTimeLimit() > 0 || $test->size()->isKnown()))) { + return false; + } + + if (extension_loaded('xdebug') && xdebug_is_debugger_active()) { + return false; + } + + return true; + } + + /** + * @throws Throwable + */ + private function runTestWithTimeout(TestCase $test): bool + { + $_timeout = $this->configuration->defaultTimeLimit(); + $testSize = $test->size(); + + if ($testSize->isSmall()) { + $_timeout = $this->configuration->timeoutForSmallTests(); + } elseif ($testSize->isMedium()) { + $_timeout = $this->configuration->timeoutForMediumTests(); + } elseif ($testSize->isLarge()) { + $_timeout = $this->configuration->timeoutForLargeTests(); + } + + try { + (new Invoker)->invoke([$test, 'runBare'], [], $_timeout); + } catch (TimeoutException) { + Facade::emitter()->testConsideredRisky( + $test->valueObjectForEvents(), + sprintf( + 'This test was aborted after %d second%s', + $_timeout, + $_timeout !== 1 ? 's' : '', + ), + ); + + return true; + } + + return false; + } + + private function shouldErrorHandlerBeUsed(TestCase $test): bool + { + if (MetadataRegistry::parser()->forMethod($test::class, $test->name())->isWithoutErrorHandler()->isNotEmpty()) { + return false; + } + + return true; + } + + private function performSanityChecks(TestCase $test, TargetCollection $coversTargets, TargetCollection $usesTargets, bool $shouldCodeCoverageBeCollected): void + { + if (!$shouldCodeCoverageBeCollected) { + if ($coversTargets->isNotEmpty() || $usesTargets->isNotEmpty()) { + Facade::emitter()->testTriggeredPhpunitWarning( + $test->valueObjectForEvents(), + '#[Covers*] and #[Uses*] attributes do not have an effect when the #[CoversNothing] attribute is used', + ); + } + } + + $coversAsString = []; + $usesAsString = []; + + foreach ($coversTargets as $coversTarget) { + $coversAsString[] = $coversTarget->description(); + } + + foreach ($usesTargets as $usesTarget) { + $usesAsString[] = $usesTarget->description(); + } + + $coversDuplicates = array_unique(array_diff_assoc($coversAsString, array_unique($coversAsString))); + $usesDuplicates = array_unique(array_diff_assoc($usesAsString, array_unique($usesAsString))); + $coversAndUses = array_intersect($coversAsString, $usesAsString); + + foreach ($coversDuplicates as $target) { + Facade::emitter()->testTriggeredPhpunitWarning( + $test->valueObjectForEvents(), + sprintf( + '%s is targeted multiple times by the same "Covers" attribute', + $target, + ), + ); + } + + foreach ($usesDuplicates as $target) { + Facade::emitter()->testTriggeredPhpunitWarning( + $test->valueObjectForEvents(), + sprintf( + '%s is targeted multiple times by the same "Uses" attribute', + $target, + ), + ); + } + + foreach ($coversAndUses as $target) { + Facade::emitter()->testTriggeredPhpunitWarning( + $test->valueObjectForEvents(), + sprintf( + '%s is targeted by both "Covers" and "Uses" attributes', + $target, + ), + ); + } + } +} diff --git a/src/Framework/TestRunner/templates/method.tpl b/src/Framework/TestRunner/templates/method.tpl new file mode 100644 index 00000000000..ed45805afeb --- /dev/null +++ b/src/Framework/TestRunner/templates/method.tpl @@ -0,0 +1,137 @@ +initForIsolation( + PHPUnit\Event\Telemetry\HRTime::fromSecondsAndNanoseconds( + {offsetSeconds}, + {offsetNanoseconds} + ), + ); + + require_once '{filename}'; + + $configuration = ConfigurationRegistry::get(); + + if ({collectCodeCoverageInformation}) { + CodeCoverage::instance()->init($configuration, CodeCoverageFilterRegistry::instance(), true); + } + + $deprecationTriggers = [ + 'functions' => [], + 'methods' => [], + ]; + + foreach ($configuration->source()->deprecationTriggers()['functions'] as $function) { + $deprecationTriggers['functions'][] = $function; + } + + foreach ($configuration->source()->deprecationTriggers()['methods'] as $method) { + [$className, $methodName] = explode('::', $method); + + $deprecationTriggers['methods'][] = [ + 'className' => $className, + 'methodName' => $methodName, + ]; + } + + ErrorHandler::instance()->useDeprecationTriggers($deprecationTriggers); + + $test = new {className}('{methodName}'); + + $test->setData('{dataName}', unserialize('{data}')); + $test->setDependencyInput(unserialize('{dependencyInput}')); + $test->setInIsolation(true); + + ob_end_clean(); + + $test->run(); + + $output = ''; + + if (!$test->expectsOutput()) { + $output = $test->output(); + } + + ini_set('xdebug.scream', '0'); + + // Not every STDOUT target stream is rewindable + $hasRewound = @rewind(STDOUT); + + if ($hasRewound && $stdout = @stream_get_contents(STDOUT)) { + $output = $stdout . $output; + $streamMetaData = stream_get_meta_data(STDOUT); + + if (!empty($streamMetaData['stream_type']) && 'STDIO' === $streamMetaData['stream_type']) { + @ftruncate(STDOUT, 0); + @rewind(STDOUT); + } + } + + file_put_contents( + '{processResultFile}', + serialize( + (object)[ + 'testResult' => $test->result(), + 'codeCoverage' => {collectCodeCoverageInformation} ? CodeCoverage::instance()->codeCoverage() : null, + 'numAssertions' => $test->numberOfAssertionsPerformed(), + 'output' => $output, + 'events' => $dispatcher->flush(), + 'passedTests' => PassedTests::instance() + ] + ) + ); +} + +function __phpunit_error_handler($errno, $errstr, $errfile, $errline) +{ + return true; +} + +set_error_handler('__phpunit_error_handler'); + +{constants} +{included_files} +{globals} + +restore_error_handler(); + +ConfigurationRegistry::loadFrom('{serializedConfiguration}'); +(new PhpHandler)->handle(ConfigurationRegistry::get()->php()); + +if ('{bootstrap}' !== '') { + require_once '{bootstrap}'; +} + +__phpunit_run_isolated_test(); diff --git a/src/Framework/TestSize/Known.php b/src/Framework/TestSize/Known.php new file mode 100644 index 00000000000..a61a05a0017 --- /dev/null +++ b/src/Framework/TestSize/Known.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestSize; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +abstract readonly class Known extends TestSize +{ + public function isKnown(): true + { + return true; + } + + abstract public function isGreaterThan(self $other): bool; +} diff --git a/src/Framework/TestSize/Large.php b/src/Framework/TestSize/Large.php new file mode 100644 index 00000000000..71a254ed470 --- /dev/null +++ b/src/Framework/TestSize/Large.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestSize; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Large extends Known +{ + public function isLarge(): true + { + return true; + } + + public function isGreaterThan(TestSize $other): bool + { + return !$other->isLarge(); + } + + public function asString(): string + { + return 'large'; + } +} diff --git a/src/Framework/TestSize/Medium.php b/src/Framework/TestSize/Medium.php new file mode 100644 index 00000000000..beb574463dd --- /dev/null +++ b/src/Framework/TestSize/Medium.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestSize; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Medium extends Known +{ + public function isMedium(): true + { + return true; + } + + public function isGreaterThan(TestSize $other): bool + { + return $other->isSmall(); + } + + public function asString(): string + { + return 'medium'; + } +} diff --git a/src/Framework/TestSize/Small.php b/src/Framework/TestSize/Small.php new file mode 100644 index 00000000000..76bdec64344 --- /dev/null +++ b/src/Framework/TestSize/Small.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestSize; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Small extends Known +{ + public function isSmall(): true + { + return true; + } + + public function isGreaterThan(TestSize $other): bool + { + return false; + } + + public function asString(): string + { + return 'small'; + } +} diff --git a/src/Framework/TestSize/TestSize.php b/src/Framework/TestSize/TestSize.php new file mode 100644 index 00000000000..c341d8b934b --- /dev/null +++ b/src/Framework/TestSize/TestSize.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestSize; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +abstract readonly class TestSize +{ + public static function unknown(): self + { + return new Unknown; + } + + public static function small(): self + { + return new Small; + } + + public static function medium(): self + { + return new Medium; + } + + public static function large(): self + { + return new Large; + } + + /** + * @phpstan-assert-if-true Known $this + */ + public function isKnown(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Unknown $this + */ + public function isUnknown(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Small $this + */ + public function isSmall(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Medium $this + */ + public function isMedium(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Large $this + */ + public function isLarge(): bool + { + return false; + } + + abstract public function asString(): string; +} diff --git a/src/Framework/TestSize/Unknown.php b/src/Framework/TestSize/Unknown.php new file mode 100644 index 00000000000..daf1e7d35ba --- /dev/null +++ b/src/Framework/TestSize/Unknown.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestSize; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Unknown extends TestSize +{ + public function isUnknown(): true + { + return true; + } + + public function asString(): string + { + return 'unknown'; + } +} diff --git a/src/Framework/TestStatus/Deprecation.php b/src/Framework/TestStatus/Deprecation.php new file mode 100644 index 00000000000..0bc4957ca56 --- /dev/null +++ b/src/Framework/TestStatus/Deprecation.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Deprecation extends Known +{ + public function isDeprecation(): true + { + return true; + } + + public function asInt(): int + { + return 4; + } + + public function asString(): string + { + return 'deprecation'; + } +} diff --git a/src/Framework/TestStatus/Error.php b/src/Framework/TestStatus/Error.php new file mode 100644 index 00000000000..35e368e813c --- /dev/null +++ b/src/Framework/TestStatus/Error.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Error extends Known +{ + public function isError(): true + { + return true; + } + + public function asInt(): int + { + return 8; + } + + public function asString(): string + { + return 'error'; + } +} diff --git a/src/Framework/TestStatus/Failure.php b/src/Framework/TestStatus/Failure.php new file mode 100644 index 00000000000..ec5996c90cf --- /dev/null +++ b/src/Framework/TestStatus/Failure.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Failure extends Known +{ + public function isFailure(): true + { + return true; + } + + public function asInt(): int + { + return 7; + } + + public function asString(): string + { + return 'failure'; + } +} diff --git a/src/Framework/TestStatus/Incomplete.php b/src/Framework/TestStatus/Incomplete.php new file mode 100644 index 00000000000..92f86fba86d --- /dev/null +++ b/src/Framework/TestStatus/Incomplete.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Incomplete extends Known +{ + public function isIncomplete(): true + { + return true; + } + + public function asInt(): int + { + return 2; + } + + public function asString(): string + { + return 'incomplete'; + } +} diff --git a/src/Framework/TestStatus/Known.php b/src/Framework/TestStatus/Known.php new file mode 100644 index 00000000000..4d9e5e8b4b7 --- /dev/null +++ b/src/Framework/TestStatus/Known.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Known extends TestStatus +{ + public function isKnown(): true + { + return true; + } +} diff --git a/src/Framework/TestStatus/Notice.php b/src/Framework/TestStatus/Notice.php new file mode 100644 index 00000000000..be9bd4c03d7 --- /dev/null +++ b/src/Framework/TestStatus/Notice.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Notice extends Known +{ + public function isNotice(): true + { + return true; + } + + public function asInt(): int + { + return 3; + } + + public function asString(): string + { + return 'notice'; + } +} diff --git a/src/Framework/TestStatus/Risky.php b/src/Framework/TestStatus/Risky.php new file mode 100644 index 00000000000..63bde2972c2 --- /dev/null +++ b/src/Framework/TestStatus/Risky.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Risky extends Known +{ + public function isRisky(): true + { + return true; + } + + public function asInt(): int + { + return 5; + } + + public function asString(): string + { + return 'risky'; + } +} diff --git a/src/Framework/TestStatus/Skipped.php b/src/Framework/TestStatus/Skipped.php new file mode 100644 index 00000000000..9539e9e0d82 --- /dev/null +++ b/src/Framework/TestStatus/Skipped.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Skipped extends Known +{ + public function isSkipped(): true + { + return true; + } + + public function asInt(): int + { + return 1; + } + + public function asString(): string + { + return 'skipped'; + } +} diff --git a/src/Framework/TestStatus/Success.php b/src/Framework/TestStatus/Success.php new file mode 100644 index 00000000000..7891505165e --- /dev/null +++ b/src/Framework/TestStatus/Success.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Success extends Known +{ + public function isSuccess(): true + { + return true; + } + + public function asInt(): int + { + return 0; + } + + public function asString(): string + { + return 'success'; + } +} diff --git a/src/Framework/TestStatus/TestStatus.php b/src/Framework/TestStatus/TestStatus.php new file mode 100644 index 00000000000..81870826bd8 --- /dev/null +++ b/src/Framework/TestStatus/TestStatus.php @@ -0,0 +1,195 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class TestStatus +{ + private string $message; + + public static function from(int $status): self + { + return match ($status) { + 0 => self::success(), + 1 => self::skipped(), + 2 => self::incomplete(), + 3 => self::notice(), + 4 => self::deprecation(), + 5 => self::risky(), + 6 => self::warning(), + 7 => self::failure(), + 8 => self::error(), + default => self::unknown(), + }; + } + + public static function unknown(): self + { + return new Unknown; + } + + public static function success(): self + { + return new Success; + } + + public static function skipped(string $message = ''): self + { + return new Skipped($message); + } + + public static function incomplete(string $message = ''): self + { + return new Incomplete($message); + } + + public static function notice(string $message = ''): self + { + return new Notice($message); + } + + public static function deprecation(string $message = ''): self + { + return new Deprecation($message); + } + + public static function failure(string $message = ''): self + { + return new Failure($message); + } + + public static function error(string $message = ''): self + { + return new Error($message); + } + + public static function warning(string $message = ''): self + { + return new Warning($message); + } + + public static function risky(string $message = ''): self + { + return new Risky($message); + } + + private function __construct(string $message = '') + { + $this->message = $message; + } + + /** + * @phpstan-assert-if-true Known $this + */ + public function isKnown(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Unknown $this + */ + public function isUnknown(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Success $this + */ + public function isSuccess(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Skipped $this + */ + public function isSkipped(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Incomplete $this + */ + public function isIncomplete(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Notice $this + */ + public function isNotice(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Deprecation $this + */ + public function isDeprecation(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Failure $this + */ + public function isFailure(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Error $this + */ + public function isError(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Warning $this + */ + public function isWarning(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Risky $this + */ + public function isRisky(): bool + { + return false; + } + + public function message(): string + { + return $this->message; + } + + public function isMoreImportantThan(self $other): bool + { + return $this->asInt() > $other->asInt(); + } + + abstract public function asInt(): int; + + abstract public function asString(): string; +} diff --git a/src/Framework/TestStatus/Unknown.php b/src/Framework/TestStatus/Unknown.php new file mode 100644 index 00000000000..a5c1309b563 --- /dev/null +++ b/src/Framework/TestStatus/Unknown.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Unknown extends TestStatus +{ + public function isUnknown(): true + { + return true; + } + + public function asInt(): int + { + return -1; + } + + public function asString(): string + { + return 'unknown'; + } +} diff --git a/src/Framework/TestStatus/Warning.php b/src/Framework/TestStatus/Warning.php new file mode 100644 index 00000000000..c091262b8b8 --- /dev/null +++ b/src/Framework/TestStatus/Warning.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Warning extends Known +{ + public function isWarning(): true + { + return true; + } + + public function asInt(): int + { + return 6; + } + + public function asString(): string + { + return 'warning'; + } +} diff --git a/src/Framework/TestSuite.php b/src/Framework/TestSuite.php index 5b63ba743c9..acf9e2b763e 100644 --- a/src/Framework/TestSuite.php +++ b/src/Framework/TestSuite.php @@ -10,290 +10,175 @@ namespace PHPUnit\Framework; use const PHP_EOL; -use function array_diff; -use function array_keys; use function array_merge; -use function array_unique; -use function basename; +use function array_pop; +use function array_reverse; +use function assert; use function call_user_func; use function class_exists; use function count; -use function dirname; -use function file_exists; -use function get_declared_classes; use function implode; -use function is_bool; use function is_callable; -use function is_object; -use function is_string; -use function method_exists; -use function preg_match; -use function preg_quote; +use function is_file; +use function is_subclass_of; use function sprintf; -use function strpos; -use function substr; +use function str_ends_with; +use function str_starts_with; +use function trim; use Iterator; use IteratorAggregate; -use PHPUnit\Runner\BaseTestRunner; +use PHPUnit\Event; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\NoPreviousThrowableException; +use PHPUnit\Metadata\Api\Dependencies; +use PHPUnit\Metadata\Api\Groups; +use PHPUnit\Metadata\Api\HookMethods; +use PHPUnit\Metadata\Api\Requirements; +use PHPUnit\Metadata\MetadataCollection; +use PHPUnit\Runner\Exception as RunnerException; use PHPUnit\Runner\Filter\Factory; -use PHPUnit\Runner\PhptTestCase; -use PHPUnit\Util\FileLoader; +use PHPUnit\Runner\Phpt\TestCase as PhptTestCase; +use PHPUnit\Runner\TestSuiteLoader; +use PHPUnit\TestRunner\TestResult\Facade as TestResultFacade; +use PHPUnit\Util\Filter; +use PHPUnit\Util\Reflection; use PHPUnit\Util\Test as TestUtil; use ReflectionClass; -use ReflectionException; use ReflectionMethod; +use SebastianBergmann\CodeCoverage\InvalidArgumentException; +use SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException; use Throwable; /** + * @template-implements IteratorAggregate + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -class TestSuite implements IteratorAggregate, Reorderable, SelfDescribing, Test +class TestSuite implements IteratorAggregate, Reorderable, Test { /** - * Enable or disable the backup and restoration of the $GLOBALS array. - * - * @var bool - */ - protected $backupGlobals; - - /** - * Enable or disable the backup and restoration of static attributes. - * - * @var bool - */ - protected $backupStaticAttributes; - - /** - * @var bool - */ - protected $runTestInSeparateProcess = false; - - /** - * The name of the test suite. - * - * @var string - */ - protected $name = ''; - - /** - * The test groups of the test suite. - * - * @var array - */ - protected $groups = []; - - /** - * The tests in the test suite. - * - * @var Test[] - */ - protected $tests = []; - - /** - * The number of tests in the test suite. - * - * @var int - */ - protected $numTests = -1; - - /** - * @var bool - */ - protected $testCase = false; - - /** - * @var string[] - */ - protected $foundClasses = []; - - /** - * @var null|list + * @var non-empty-string */ - protected $providedTests; + private string $name; /** - * @var null|list + * @var array> */ - protected $requiredTests; + private array $groups = []; /** - * @var bool + * @var ?list */ - private $beStrictAboutChangesToGlobalState; + private ?array $requiredTests = null; /** - * @var Factory + * @var list */ - private $iteratorFilter; + private array $tests = []; /** - * @var string[] + * @var ?list */ - private $declaredClasses; + private ?array $providedTests = null; + private ?Factory $iteratorFilter = null; + private bool $wasRun = false; /** - * @psalm-var array + * @param non-empty-string $name */ - private $warnings = []; + public static function empty(string $name): static + { + return new static($name); + } /** - * Constructs a new TestSuite. - * - * - PHPUnit\Framework\TestSuite() constructs an empty TestSuite. - * - * - PHPUnit\Framework\TestSuite(ReflectionClass) constructs a - * TestSuite from the given class. - * - * - PHPUnit\Framework\TestSuite(ReflectionClass, String) - * constructs a TestSuite from the given class with the given - * name. - * - * - PHPUnit\Framework\TestSuite(String) either constructs a - * TestSuite from the given class (if the passed string is the - * name of an existing class) or constructs an empty TestSuite - * with the given name. - * - * @param ReflectionClass|string $theClass - * - * @throws Exception + * @param ReflectionClass $class + * @param list $groups */ - public function __construct($theClass = '', string $name = '') + public static function fromClassReflector(ReflectionClass $class, array $groups = []): static { - if (!is_string($theClass) && !$theClass instanceof ReflectionClass) { - throw InvalidArgumentException::create( - 1, - 'ReflectionClass object or string' - ); - } - - $this->declaredClasses = get_declared_classes(); + $testSuite = new static($class->getName()); - if (!$theClass instanceof ReflectionClass) { - if (class_exists($theClass, true)) { - if ($name === '') { - $name = $theClass; - } - - try { - $theClass = new ReflectionClass($theClass); - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - } else { - $this->setName($theClass); - - return; + foreach (Reflection::publicMethodsDeclaredDirectlyInTestClass($class) as $method) { + if (!TestUtil::isTestMethod($method)) { + continue; } - } - if (!$theClass->isSubclassOf(TestCase::class)) { - $this->setName((string) $theClass); - - return; - } - - if ($name !== '') { - $this->setName($name); - } else { - $this->setName($theClass->getName()); - } - - $constructor = $theClass->getConstructor(); - - if ($constructor !== null && - !$constructor->isPublic()) { - $this->addTest( - new WarningTestCase( + if ((new HookMethods)->isHookMethod($method)) { + Event\Facade::emitter()->testRunnerTriggeredPhpunitWarning( sprintf( - 'Class "%s" has no public constructor.', - $theClass->getName() - ) - ) - ); - - return; - } - - foreach ($theClass->getMethods() as $method) { - if ($method->getDeclaringClass()->getName() === Assert::class) { - continue; - } + 'Method %s::%s() cannot be used both as a hook method and as a test method', + $class->getName(), + $method->getName(), + ), + ); - if ($method->getDeclaringClass()->getName() === TestCase::class) { continue; } - $this->addTestMethod($theClass, $method); + $testSuite->addTestMethod($class, $method, $groups); } - if (empty($this->tests)) { - $this->addTest( - new WarningTestCase( - sprintf( - 'No tests found in class "%s".', - $theClass->getName() - ) - ) + if ($testSuite->isEmpty()) { + Event\Facade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'No tests found in class "%s".', + $class->getName(), + ), ); } - $this->testCase = true; + return $testSuite; } /** - * Returns a string representation of the test suite. + * @param non-empty-string $name */ - public function toString(): string + final private function __construct(string $name) { - return $this->getName(); + $this->name = $name; } /** * Adds a test to the suite. * - * @param array $groups + * @param list $groups */ - public function addTest(Test $test, $groups = []): void + public function addTest(Test $test, array $groups = []): void { - try { - $class = new ReflectionClass($test); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - if (!$class->isAbstract()) { + if ($test instanceof self) { $this->tests[] = $test; + $this->clearCaches(); - if ($test instanceof self && empty($groups)) { - $groups = $test->getGroups(); - } + return; + } - if (empty($groups)) { - $groups = ['default']; - } + assert($test instanceof TestCase || $test instanceof PhptTestCase); - foreach ($groups as $group) { - if (!isset($this->groups[$group])) { - $this->groups[$group] = [$test]; - } else { - $this->groups[$group][] = $test; - } - } + $this->tests[] = $test; + + $this->clearCaches(); + + if ($this->containsOnlyVirtualGroups($groups)) { + $groups[] = 'default'; + } + + if ($test instanceof TestCase) { + $id = $test->valueObjectForEvents()->id(); - if ($test instanceof TestCase) { - $test->setGroups($groups); + $test->setGroups($groups); + } else { + $id = $test->valueObjectForEvents()->id(); + } + + foreach ($groups as $group) { + if (!isset($this->groups[$group])) { + $this->groups[$group] = [$id]; + } else { + $this->groups[$group][] = $id; } } } @@ -301,73 +186,33 @@ public function addTest(Test $test, $groups = []): void /** * Adds the tests from the given class to the suite. * - * @param object|string $testClass + * @param ReflectionClass $testClass + * @param list $groups * * @throws Exception */ - public function addTestSuite($testClass): void + public function addTestSuite(ReflectionClass $testClass, array $groups = []): void { - if (!(is_object($testClass) || (is_string($testClass) && class_exists($testClass)))) { - throw InvalidArgumentException::create( - 1, - 'class name or object' + if ($testClass->isAbstract()) { + throw new Exception( + sprintf( + 'Class %s is abstract', + $testClass->getName(), + ), ); } - if (!is_object($testClass)) { - try { - $testClass = new ReflectionClass($testClass); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - } - - if ($testClass instanceof self) { - $this->addTest($testClass); - } elseif ($testClass instanceof ReflectionClass) { - $suiteMethod = false; - - if (!$testClass->isAbstract() && $testClass->hasMethod(BaseTestRunner::SUITE_METHODNAME)) { - try { - $method = $testClass->getMethod( - BaseTestRunner::SUITE_METHODNAME - ); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - if ($method->isStatic()) { - $this->addTest( - $method->invoke(null, $testClass->getName()) - ); - - $suiteMethod = true; - } - } - - if (!$suiteMethod && !$testClass->isAbstract() && $testClass->isSubclassOf(TestCase::class)) { - $this->addTest(new self($testClass)); - } - } else { - throw new Exception; + if (!$testClass->isSubclassOf(TestCase::class)) { + throw new Exception( + sprintf( + 'Class %s is not a subclass of %s', + $testClass->getName(), + TestCase::class, + ), + ); } - } - public function addWarning(string $warning): void - { - $this->warnings[] = $warning; + $this->addTest(self::fromClassReflector($testClass, $groups), $groups); } /** @@ -378,145 +223,33 @@ public function addWarning(string $warning): void * added, a PHPUnit\Framework\WarningTestCase will be created instead, * leaving the current test run untouched. * + * @param list $groups + * * @throws Exception */ - public function addTestFile(string $filename): void + public function addTestFile(string $filename, array $groups = []): void { - if (file_exists($filename) && substr($filename, -5) === '.phpt') { - $this->addTest(new PhptTestCase($filename)); - - $this->declaredClasses = get_declared_classes(); - - return; - } - - $numTests = count($this->tests); - - // The given file may contain further stub classes in addition to the - // test class itself. Figure out the actual test class. - $filename = FileLoader::checkAndLoad($filename); - $newClasses = array_diff(get_declared_classes(), $this->declaredClasses); - - // The diff is empty in case a parent class (with test methods) is added - // AFTER a child class that inherited from it. To account for that case, - // accumulate all discovered classes, so the parent class may be found in - // a later invocation. - if (!empty($newClasses)) { - // On the assumption that test classes are defined first in files, - // process discovered classes in approximate LIFO order, so as to - // avoid unnecessary reflection. - $this->foundClasses = array_merge($newClasses, $this->foundClasses); - $this->declaredClasses = get_declared_classes(); - } - - // The test class's name must match the filename, either in full, or as - // a PEAR/PSR-0 prefixed short name ('NameSpace_ShortName'), or as a - // PSR-1 local short name ('NameSpace\ShortName'). The comparison must be - // anchored to prevent false-positive matches (e.g., 'OtherShortName'). - $shortName = basename($filename, '.php'); - $shortNameRegEx = '/(?:^|_|\\\\)' . preg_quote($shortName, '/') . '$/'; - - foreach ($this->foundClasses as $i => $className) { - if (preg_match($shortNameRegEx, $className)) { - try { - $class = new ReflectionClass($className); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - if ($class->getFileName() == $filename) { - $newClasses = [$className]; - unset($this->foundClasses[$i]); - - break; - } - } - } - - foreach ($newClasses as $className) { - try { - $class = new ReflectionClass($className); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e + try { + if (str_ends_with($filename, '.phpt') && is_file($filename)) { + $this->addTest(new PhptTestCase($filename)); + } else { + $this->addTestSuite( + (new TestSuiteLoader)->load($filename), + $groups, ); } - // @codeCoverageIgnoreEnd - - if (dirname($class->getFileName()) === __DIR__) { - continue; - } - - if (!$class->isAbstract()) { - if ($class->hasMethod(BaseTestRunner::SUITE_METHODNAME)) { - try { - $method = $class->getMethod( - BaseTestRunner::SUITE_METHODNAME - ); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - if ($method->isStatic()) { - $this->addTest($method->invoke(null, $className)); - } - } elseif ($class->implementsInterface(Test::class)) { - $expectedClassName = $shortName; - - if (($pos = strpos($expectedClassName, '.')) !== false) { - $expectedClassName = substr( - $expectedClassName, - 0, - $pos - ); - } - - if ($class->getShortName() !== $expectedClassName) { - $this->addWarning( - sprintf( - "Test case class not matching filename is deprecated\n in %s\n Class name was '%s', expected '%s'", - $filename, - $class->getShortName(), - $expectedClassName - ) - ); - } - - $this->addTestSuite($class); - } - } - } - - if (count($this->tests) > ++$numTests) { - $this->addWarning( - sprintf( - "Multiple test case classes per file is deprecated\n in %s", - $filename - ) + } catch (RunnerException $e) { + Event\Facade::emitter()->testRunnerTriggeredPhpunitWarning( + $e->getMessage(), ); } - - $this->numTests = -1; } /** * Wrapper for addTestFile() that adds multiple test files. * + * @param iterable $fileNames + * * @throws Exception */ public function addTestFiles(iterable $fileNames): void @@ -528,180 +261,128 @@ public function addTestFiles(iterable $fileNames): void /** * Counts the number of test cases that will be run by this test. - * - * @todo refactor usage of numTests in DefaultResultPrinter */ public function count(): int { - $this->numTests = 0; + $numTests = 0; foreach ($this as $test) { - $this->numTests += count($test); + $numTests += count($test); } - return $this->numTests; + return $numTests; } - /** - * Returns the name of the suite. - */ - public function getName(): string + public function isEmpty(): bool { - return $this->name; + foreach ($this as $test) { + if (count($test) !== 0) { + return false; + } + } + + return true; } /** - * Returns the test groups of the suite. + * @return non-empty-string */ - public function getGroups(): array + public function name(): string { - return array_keys($this->groups); - } - - public function getGroupDetails(): array - { - return $this->groups; + return $this->name; } /** - * Set tests groups of the test case. + * @return array> */ - public function setGroupDetails(array $groups): void + public function groups(): array { - $this->groups = $groups; + return $this->groups; } /** - * Runs the tests and collects their result in a TestResult. - * - * @throws \PHPUnit\Framework\CodeCoverageException - * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException - * @throws \SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Warning + * @return list */ - public function run(TestResult $result = null): TestResult + public function collect(): array { - if ($result === null) { - $result = $this->createResult(); - } - - if (count($this) === 0) { - return $result; - } + $tests = []; - /** @psalm-var class-string $className */ - $className = $this->name; - $hookMethods = TestUtil::getHookMethods($className); - - $result->startTestSuite($this); + foreach ($this as $test) { + if ($test instanceof self) { + $tests = array_merge($tests, $test->collect()); - try { - foreach ($hookMethods['beforeClass'] as $beforeClassMethod) { - if ($this->testCase && - class_exists($this->name, false) && - method_exists($this->name, $beforeClassMethod)) { - if ($missingRequirements = TestUtil::getMissingRequirements($this->name, $beforeClassMethod)) { - $this->markTestSuiteSkipped(implode(PHP_EOL, $missingRequirements)); - } - - call_user_func([$this->name, $beforeClassMethod]); - } - } - } catch (SkippedTestSuiteError $error) { - foreach ($this->tests() as $test) { - $result->startTest($test); - $result->addFailure($test, $error, 0); - $result->endTest($test, 0); + continue; } - $result->endTestSuite($this); + assert($test instanceof TestCase || $test instanceof PhptTestCase); - return $result; - } catch (Throwable $t) { - $errorAdded = false; + $tests[] = $test; + } - foreach ($this->tests() as $test) { - if ($result->shouldStop()) { - break; - } + return $tests; + } - $result->startTest($test); + /** + * @throws Event\RuntimeException + * @throws Exception + * @throws InvalidArgumentException + * @throws NoPreviousThrowableException + * @throws UnintentionallyCoveredCodeException + */ + public function run(): void + { + if ($this->wasRun) { + // @codeCoverageIgnoreStart + throw new Exception('The tests aggregated by this TestSuite were already run'); + // @codeCoverageIgnoreEnd + } - if (!$errorAdded) { - $result->addError($test, $t, 0); + $this->wasRun = true; - $errorAdded = true; - } else { - $result->addFailure( - $test, - new SkippedTestError('Test skipped because of an error in hook method'), - 0 - ); - } + if ($this->isEmpty()) { + return; + } - $result->endTest($test, 0); - } + $emitter = Event\Facade::emitter(); + $testSuiteValueObjectForEvents = Event\TestSuite\TestSuiteBuilder::from($this); - $result->endTestSuite($this); + $emitter->testSuiteStarted($testSuiteValueObjectForEvents); - return $result; + if (!$this->invokeMethodsBeforeFirstTest($emitter, $testSuiteValueObjectForEvents)) { + return; } - foreach ($this as $test) { - if ($result->shouldStop()) { - break; - } + /** @var list $tests */ + $tests = []; - if ($test instanceof TestCase || $test instanceof self) { - $test->setBeStrictAboutChangesToGlobalState($this->beStrictAboutChangesToGlobalState); - $test->setBackupGlobals($this->backupGlobals); - $test->setBackupStaticAttributes($this->backupStaticAttributes); - $test->setRunTestInSeparateProcess($this->runTestInSeparateProcess); - } - - $test->run($result); + foreach ($this as $test) { + $tests[] = $test; } - try { - foreach ($hookMethods['afterClass'] as $afterClassMethod) { - if ($this->testCase && - class_exists($this->name, false) && - method_exists($this->name, $afterClassMethod)) { - call_user_func([$this->name, $afterClassMethod]); - } - } - } catch (Throwable $t) { - $message = "Exception in {$this->name}::{$afterClassMethod}" . PHP_EOL . $t->getMessage(); - $error = new SyntheticError($message, 0, $t->getFile(), $t->getLine(), $t->getTrace()); + $tests = array_reverse($tests); - $placeholderTest = clone $test; - $placeholderTest->setName($afterClassMethod); + $this->tests = []; + $this->groups = []; - $result->startTest($placeholderTest); - $result->addFailure($placeholderTest, $error, 0); - $result->endTest($placeholderTest, 0); - } + while (($test = array_pop($tests)) !== null) { + if (TestResultFacade::shouldStop()) { + $emitter->testRunnerExecutionAborted(); - $result->endTestSuite($this); + break; + } - return $result; - } + $test->run(); + } - public function setRunTestInSeparateProcess(bool $runTestInSeparateProcess): void - { - $this->runTestInSeparateProcess = $runTestInSeparateProcess; - } + $this->invokeMethodsAfterLastTest($emitter); - public function setName(string $name): void - { - $this->name = $name; + $emitter->testSuiteFinished($testSuiteValueObjectForEvents); } /** * Returns the tests as an enumeration. * - * @return Test[] + * @return list */ public function tests(): array { @@ -711,7 +392,7 @@ public function tests(): array /** * Set tests of the test suite. * - * @param Test[] $tests + * @param list $tests */ public function setTests(array $tests): void { @@ -721,47 +402,13 @@ public function setTests(array $tests): void /** * Mark the test suite as skipped. * - * @param string $message - * * @throws SkippedTestSuiteError - * - * @psalm-return never-return */ - public function markTestSuiteSkipped($message = ''): void + public function markTestSuiteSkipped(string $message = ''): never { throw new SkippedTestSuiteError($message); } - /** - * @param bool $beStrictAboutChangesToGlobalState - */ - public function setBeStrictAboutChangesToGlobalState($beStrictAboutChangesToGlobalState): void - { - if (null === $this->beStrictAboutChangesToGlobalState && is_bool($beStrictAboutChangesToGlobalState)) { - $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; - } - } - - /** - * @param bool $backupGlobals - */ - public function setBackupGlobals($backupGlobals): void - { - if (null === $this->backupGlobals && is_bool($backupGlobals)) { - $this->backupGlobals = $backupGlobals; - } - } - - /** - * @param bool $backupStaticAttributes - */ - public function setBackupStaticAttributes($backupStaticAttributes): void - { - if (null === $this->backupStaticAttributes && is_bool($backupStaticAttributes)) { - $this->backupStaticAttributes = $backupStaticAttributes; - } - } - /** * Returns an iterator for this test suite. */ @@ -787,14 +434,6 @@ public function injectFilter(Factory $filter): void } } - /** - * @psalm-return array - */ - public function warnings(): array - { - return array_unique($this->warnings); - } - /** * @return list */ @@ -808,11 +447,12 @@ public function provides(): array } foreach ($this->tests as $test) { - if (!($test instanceof Reorderable)) { + if (!$test instanceof Reorderable) { // @codeCoverageIgnoreStart continue; // @codeCoverageIgnoreEnd } + $this->providedTests = ExecutionOrderDependency::mergeUnique($this->providedTests, $test->provides()); } } @@ -829,14 +469,15 @@ public function requires(): array $this->requiredTests = []; foreach ($this->tests as $test) { - if (!($test instanceof Reorderable)) { + if (!$test instanceof Reorderable) { // @codeCoverageIgnoreStart continue; // @codeCoverageIgnoreEnd } + $this->requiredTests = ExecutionOrderDependency::mergeUnique( ExecutionOrderDependency::filterInvalid($this->requiredTests), - $test->requires() + $test->requires(), ); } @@ -848,60 +489,270 @@ public function requires(): array public function sortId(): string { - return $this->getName() . '::class'; + return $this->name() . '::class'; } /** - * Creates a default TestResult object. + * @phpstan-assert-if-true class-string $this->name */ - protected function createResult(): TestResult + public function isForTestClass(): bool { - return new TestResult; + return class_exists($this->name, false) && is_subclass_of($this->name, TestCase::class); } /** + * @param ReflectionClass $class + * @param list $groups + * * @throws Exception */ - protected function addTestMethod(ReflectionClass $class, ReflectionMethod $method): void + protected function addTestMethod(ReflectionClass $class, ReflectionMethod $method, array $groups): void { - if (!TestUtil::isTestMethod($method)) { - return; - } - + $className = $class->getName(); $methodName = $method->getName(); - if (!$method->isPublic()) { - $this->addTest( - new WarningTestCase( - sprintf( - 'Test method "%s" in test class "%s" is not public.', + try { + $test = (new TestBuilder)->build($class, $methodName, $groups); + } catch (InvalidDataProviderException $e) { + if ($e->getProviderLabel() === null) { + $message = sprintf( + "The data provider specified for %s::%s is invalid\n%s", + $className, + $methodName, + $this->exceptionToString($e), + ); + } else { + $message = sprintf( + "The data provider %s specified for %s::%s is invalid\n%s", + $e->getProviderLabel(), + $className, + $methodName, + $this->exceptionToString($e), + ); + } + + Event\Facade::emitter()->testTriggeredPhpunitError( + new TestMethod( + $className, + $methodName, + $class->getFileName(), + $method->getStartLine(), + Event\Code\TestDoxBuilder::fromClassNameAndMethodName( + $className, $methodName, - $class->getName() - ) - ) + ), + MetadataCollection::fromArray([]), + Event\TestData\TestDataCollection::fromArray([]), + ), + $message, ); return; } - $test = (new TestBuilder)->build($class, $methodName); - if ($test instanceof TestCase || $test instanceof DataProviderTestSuite) { $test->setDependencies( - TestUtil::getDependencies($class->getName(), $methodName) + Dependencies::dependencies($class->getName(), $methodName), ); } $this->addTest( $test, - TestUtil::getGroups($class->getName(), $methodName) + array_merge( + $groups, + (new Groups)->groups($class->getName(), $methodName), + ), ); } private function clearCaches(): void { - $this->numTests = -1; $this->providedTests = null; $this->requiredTests = null; } + + /** + * @param list $groups + */ + private function containsOnlyVirtualGroups(array $groups): bool + { + foreach ($groups as $group) { + if (!str_starts_with($group, '__phpunit_')) { + return false; + } + } + + return true; + } + + private function methodDoesNotExistOrIsDeclaredInTestCase(string $methodName): bool + { + $reflector = new ReflectionClass($this->name); + + return !$reflector->hasMethod($methodName) || + $reflector->getMethod($methodName)->getDeclaringClass()->getName() === TestCase::class; + } + + /** + * @throws Exception + */ + private function exceptionToString(InvalidDataProviderException $e): string + { + $message = $e->getMessage(); + + if (trim($message) === '') { + $message = ''; + } + + return sprintf( + "%s\n%s", + $message, + Filter::stackTraceFromThrowableAsString($e), + ); + } + + /** + * @throws Exception + * @throws NoPreviousThrowableException + */ + private function invokeMethodsBeforeFirstTest(Event\Emitter $emitter, Event\TestSuite\TestSuite $testSuiteValueObjectForEvents): bool + { + if (!$this->isForTestClass()) { + return true; + } + + $methods = (new HookMethods)->hookMethods($this->name)['beforeClass']->methodNamesSortedByPriority(); + $calledMethods = []; + $emitCalledEvent = true; + $result = true; + + foreach ($methods as $method) { + if ($this->methodDoesNotExistOrIsDeclaredInTestCase($method)) { + continue; + } + + $calledMethod = new Event\Code\ClassMethod( + $this->name, + $method, + ); + + try { + $missingRequirements = (new Requirements)->requirementsNotSatisfiedFor($this->name, $method); + + if ($missingRequirements !== []) { + $emitCalledEvent = false; + + $this->markTestSuiteSkipped(implode(PHP_EOL, $missingRequirements)); + } + + call_user_func([$this->name, $method]); + } catch (Throwable $t) { + } + + if ($emitCalledEvent) { + $emitter->beforeFirstTestMethodCalled( + $this->name, + $calledMethod, + ); + + $calledMethods[] = $calledMethod; + } + + if (isset($t) && $t instanceof SkippedTest) { + $emitter->testSuiteSkipped( + $testSuiteValueObjectForEvents, + $t->getMessage(), + ); + + return false; + } + + if (isset($t)) { + if ($t instanceof AssertionFailedError) { + $emitter->beforeFirstTestMethodFailed( + $this->name, + $calledMethod, + Event\Code\ThrowableBuilder::from($t), + ); + } else { + $emitter->beforeFirstTestMethodErrored( + $this->name, + $calledMethod, + Event\Code\ThrowableBuilder::from($t), + ); + } + + $result = false; + } + } + + if ($calledMethods !== []) { + $emitter->beforeFirstTestMethodFinished( + $this->name, + ...$calledMethods, + ); + } + + if (!$result) { + $emitter->testSuiteFinished($testSuiteValueObjectForEvents); + } + + return $result; + } + + private function invokeMethodsAfterLastTest(Event\Emitter $emitter): void + { + if (!$this->isForTestClass()) { + return; + } + + $methods = (new HookMethods)->hookMethods($this->name)['afterClass']->methodNamesSortedByPriority(); + $calledMethods = []; + + foreach ($methods as $method) { + if ($this->methodDoesNotExistOrIsDeclaredInTestCase($method)) { + continue; + } + + $calledMethod = new Event\Code\ClassMethod( + $this->name, + $method, + ); + + try { + call_user_func([$this->name, $method]); + } catch (Throwable $t) { + } + + $emitter->afterLastTestMethodCalled( + $this->name, + $calledMethod, + ); + + $calledMethods[] = $calledMethod; + + if (isset($t)) { + if ($t instanceof AssertionFailedError) { + $emitter->afterLastTestMethodFailed( + $this->name, + $calledMethod, + Event\Code\ThrowableBuilder::from($t), + ); + } else { + $emitter->afterLastTestMethodErrored( + $this->name, + $calledMethod, + Event\Code\ThrowableBuilder::from($t), + ); + } + } + } + + if ($calledMethods !== []) { + $emitter->afterLastTestMethodFinished( + $this->name, + ...$calledMethods, + ); + } + } } diff --git a/src/Framework/TestSuiteIterator.php b/src/Framework/TestSuiteIterator.php index e351622f39c..3cbf431164a 100644 --- a/src/Framework/TestSuiteIterator.php +++ b/src/Framework/TestSuiteIterator.php @@ -14,19 +14,20 @@ use RecursiveIterator; /** + * @template-implements RecursiveIterator + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class TestSuiteIterator implements RecursiveIterator { - /** - * @var int - */ - private $position = 0; + private int $position = 0; /** - * @var Test[] + * @var list */ - private $tests; + private readonly array $tests; public function __construct(TestSuite $testSuite) { @@ -65,7 +66,7 @@ public function getChildren(): self { if (!$this->hasChildren()) { throw new NoChildTestSuiteException( - 'The current item is not a TestSuite instance and therefore does not have any children.' + 'The current item is not a TestSuite instance and therefore does not have any children.', ); } diff --git a/src/Framework/WarningTestCase.php b/src/Framework/WarningTestCase.php deleted file mode 100644 index 8070c01290a..00000000000 --- a/src/Framework/WarningTestCase.php +++ /dev/null @@ -1,73 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class WarningTestCase extends TestCase -{ - /** - * @var bool - */ - protected $backupGlobals = false; - - /** - * @var bool - */ - protected $backupStaticAttributes = false; - - /** - * @var bool - */ - protected $runTestInSeparateProcess = false; - - /** - * @var bool - */ - protected $useErrorHandler = false; - - /** - * @var string - */ - private $message; - - /** - * @param string $message - */ - public function __construct($message = '') - { - $this->message = $message; - parent::__construct('Warning'); - } - - public function getMessage(): string - { - return $this->message; - } - - /** - * Returns a string representation of the test case. - */ - public function toString(): string - { - return 'Warning'; - } - - /** - * @throws Exception - * - * @psalm-return never-return - */ - protected function runTest(): void - { - throw new Warning($this->message); - } -} diff --git a/src/Logging/EventLogger.php b/src/Logging/EventLogger.php new file mode 100644 index 00000000000..738f3037799 --- /dev/null +++ b/src/Logging/EventLogger.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging; + +use const FILE_APPEND; +use const LOCK_EX; +use const PHP_EOL; +use const PHP_OS_FAMILY; +use function file_put_contents; +use function implode; +use function preg_split; +use function str_repeat; +use function strlen; +use PHPUnit\Event\Event; +use PHPUnit\Event\Tracer\Tracer; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class EventLogger implements Tracer +{ + private string $path; + private bool $includeTelemetryInfo; + + public function __construct(string $path, bool $includeTelemetryInfo) + { + $this->path = $path; + $this->includeTelemetryInfo = $includeTelemetryInfo; + } + + public function trace(Event $event): void + { + $telemetryInfo = $this->telemetryInfo($event); + $indentation = PHP_EOL . str_repeat(' ', strlen($telemetryInfo)); + $flags = FILE_APPEND; + + if (!(PHP_OS_FAMILY === 'Windows' || PHP_OS_FAMILY === 'Darwin') || + $this->path !== 'php://stdout') { + $flags |= LOCK_EX; + } + + $lines = preg_split('/\r\n|\r|\n/', $event->asString()); + + if ($lines === false) { + $lines = []; + } + + file_put_contents( + $this->path, + $telemetryInfo . implode($indentation, $lines) . PHP_EOL, + $flags, + ); + } + + private function telemetryInfo(Event $event): string + { + if (!$this->includeTelemetryInfo) { + return ''; + } + + return $event->telemetryInfo()->asString() . ' '; + } +} diff --git a/src/Logging/JUnit/JunitXmlLogger.php b/src/Logging/JUnit/JunitXmlLogger.php new file mode 100644 index 00000000000..14a71b769de --- /dev/null +++ b/src/Logging/JUnit/JunitXmlLogger.php @@ -0,0 +1,462 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use const PHP_EOL; +use function assert; +use function basename; +use function is_int; +use function sprintf; +use function str_replace; +use function trim; +use DOMDocument; +use DOMElement; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\Facade; +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Telemetry\HRTime; +use PHPUnit\Event\Telemetry\Info; +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\PreparationStarted; +use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\PrintedUnexpectedOutput; +use PHPUnit\Event\Test\Skipped; +use PHPUnit\Event\TestSuite\Started; +use PHPUnit\TextUI\Output\Printer; +use PHPUnit\Util\Xml; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class JunitXmlLogger +{ + private readonly Printer $printer; + private DOMDocument $document; + private DOMElement $root; + + /** + * @var array + */ + private array $testSuites = []; + + /** + * @var array + */ + private array $testSuiteTests = [0]; + + /** + * @var array + */ + private array $testSuiteAssertions = [0]; + + /** + * @var array + */ + private array $testSuiteErrors = [0]; + + /** + * @var array + */ + private array $testSuiteFailures = [0]; + + /** + * @var array + */ + private array $testSuiteSkipped = [0]; + + /** + * @var array + */ + private array $testSuiteTimes = [0.0]; + private int $testSuiteLevel = 0; + private ?DOMElement $currentTestCase = null; + private ?HRTime $time = null; + private bool $prepared = false; + private bool $preparationFailed = false; + private ?string $unexpectedOutput = null; + + public function __construct(Printer $printer, Facade $facade) + { + $this->printer = $printer; + + $this->registerSubscribers($facade); + $this->createDocument(); + } + + public function flush(): void + { + $xml = $this->document->saveXML(); + + if ($xml === false) { + $xml = ''; + } + + $this->printer->print($xml); + $this->printer->flush(); + } + + public function testSuiteStarted(Started $event): void + { + $testSuite = $this->document->createElement('testsuite'); + $testSuite->setAttribute('name', $event->testSuite()->name()); + + if ($event->testSuite()->isForTestClass()) { + $testSuite->setAttribute('file', $event->testSuite()->file()); + } + + if ($this->testSuiteLevel > 0) { + $this->testSuites[$this->testSuiteLevel]->appendChild($testSuite); + } else { + $this->root->appendChild($testSuite); + } + + $this->testSuiteLevel++; + $this->testSuites[$this->testSuiteLevel] = $testSuite; + $this->testSuiteTests[$this->testSuiteLevel] = 0; + $this->testSuiteAssertions[$this->testSuiteLevel] = 0; + $this->testSuiteErrors[$this->testSuiteLevel] = 0; + $this->testSuiteFailures[$this->testSuiteLevel] = 0; + $this->testSuiteSkipped[$this->testSuiteLevel] = 0; + $this->testSuiteTimes[$this->testSuiteLevel] = 0.0; + } + + public function testSuiteFinished(): void + { + $this->testSuites[$this->testSuiteLevel]->setAttribute( + 'tests', + (string) $this->testSuiteTests[$this->testSuiteLevel], + ); + + $this->testSuites[$this->testSuiteLevel]->setAttribute( + 'assertions', + (string) $this->testSuiteAssertions[$this->testSuiteLevel], + ); + + $this->testSuites[$this->testSuiteLevel]->setAttribute( + 'errors', + (string) $this->testSuiteErrors[$this->testSuiteLevel], + ); + + $this->testSuites[$this->testSuiteLevel]->setAttribute( + 'failures', + (string) $this->testSuiteFailures[$this->testSuiteLevel], + ); + + $this->testSuites[$this->testSuiteLevel]->setAttribute( + 'skipped', + (string) $this->testSuiteSkipped[$this->testSuiteLevel], + ); + + $this->testSuites[$this->testSuiteLevel]->setAttribute( + 'time', + sprintf('%F', $this->testSuiteTimes[$this->testSuiteLevel]), + ); + + if ($this->testSuiteLevel > 1) { + $this->testSuiteTests[$this->testSuiteLevel - 1] += $this->testSuiteTests[$this->testSuiteLevel]; + $this->testSuiteAssertions[$this->testSuiteLevel - 1] += $this->testSuiteAssertions[$this->testSuiteLevel]; + $this->testSuiteErrors[$this->testSuiteLevel - 1] += $this->testSuiteErrors[$this->testSuiteLevel]; + $this->testSuiteFailures[$this->testSuiteLevel - 1] += $this->testSuiteFailures[$this->testSuiteLevel]; + $this->testSuiteSkipped[$this->testSuiteLevel - 1] += $this->testSuiteSkipped[$this->testSuiteLevel]; + $this->testSuiteTimes[$this->testSuiteLevel - 1] += $this->testSuiteTimes[$this->testSuiteLevel]; + } + + $this->testSuiteLevel--; + } + + /** + * @throws InvalidArgumentException + */ + public function testPreparationStarted(PreparationStarted $event): void + { + $this->createTestCase($event); + + $this->preparationFailed = false; + } + + public function testPreparationErrored(): void + { + $this->preparationFailed = true; + } + + public function testPreparationFailed(): void + { + $this->preparationFailed = true; + } + + public function testPrepared(): void + { + $this->prepared = true; + } + + public function testPrintedUnexpectedOutput(PrintedUnexpectedOutput $event): void + { + $this->unexpectedOutput = $event->output(); + } + + /** + * @throws InvalidArgumentException + */ + public function testFinished(Finished $event): void + { + if (!$this->prepared || $this->preparationFailed) { + return; + } + + $this->handleFinish($event->telemetryInfo(), $event->numberOfAssertionsPerformed()); + } + + /** + * @throws InvalidArgumentException + */ + public function testMarkedIncomplete(MarkedIncomplete $event): void + { + $this->handleIncompleteOrSkipped($event); + } + + /** + * @throws InvalidArgumentException + */ + public function testSkipped(Skipped $event): void + { + $this->handleIncompleteOrSkipped($event); + } + + /** + * @throws InvalidArgumentException + */ + public function testErrored(Errored $event): void + { + $this->handleFault($event, 'error'); + + $this->testSuiteErrors[$this->testSuiteLevel]++; + } + + /** + * @throws InvalidArgumentException + */ + public function testFailed(Failed $event): void + { + $this->handleFault($event, 'failure'); + + $this->testSuiteFailures[$this->testSuiteLevel]++; + } + + /** + * @throws InvalidArgumentException + */ + private function handleFinish(Info $telemetryInfo, int $numberOfAssertionsPerformed): void + { + assert($this->currentTestCase !== null); + assert($this->time !== null); + + $time = $telemetryInfo->time()->duration($this->time)->asFloat(); + + $this->testSuiteAssertions[$this->testSuiteLevel] += $numberOfAssertionsPerformed; + + $this->currentTestCase->setAttribute( + 'assertions', + (string) $numberOfAssertionsPerformed, + ); + + $this->currentTestCase->setAttribute( + 'time', + sprintf('%F', $time), + ); + + if ($this->unexpectedOutput !== null) { + $systemOut = $this->document->createElement( + 'system-out', + Xml::prepareString($this->unexpectedOutput), + ); + + $this->currentTestCase->appendChild($systemOut); + } + + $this->testSuites[$this->testSuiteLevel]->appendChild( + $this->currentTestCase, + ); + + $this->testSuiteTests[$this->testSuiteLevel]++; + $this->testSuiteTimes[$this->testSuiteLevel] += $time; + + $this->currentTestCase = null; + $this->time = null; + $this->preparationFailed = false; + $this->prepared = false; + $this->unexpectedOutput = null; + } + + private function registerSubscribers(Facade $facade): void + { + $facade->registerSubscribers( + new TestSuiteStartedSubscriber($this), + new TestSuiteFinishedSubscriber($this), + new TestPreparationStartedSubscriber($this), + new TestPreparationErroredSubscriber($this), + new TestPreparationFailedSubscriber($this), + new TestPreparedSubscriber($this), + new TestPrintedUnexpectedOutputSubscriber($this), + new TestFinishedSubscriber($this), + new TestErroredSubscriber($this), + new TestFailedSubscriber($this), + new TestMarkedIncompleteSubscriber($this), + new TestSkippedSubscriber($this), + new TestRunnerExecutionFinishedSubscriber($this), + ); + } + + private function createDocument(): void + { + $this->document = new DOMDocument('1.0', 'UTF-8'); + $this->document->formatOutput = true; + + $this->root = $this->document->createElement('testsuites'); + $this->document->appendChild($this->root); + } + + /** + * @throws InvalidArgumentException + */ + private function handleFault(Errored|Failed $event, string $type): void + { + if (!$this->prepared) { + $this->createTestCase($event); + } + + assert($this->currentTestCase !== null); + + $buffer = $this->testAsString($event->test()); + + $throwable = $event->throwable(); + $buffer .= trim( + $throwable->description() . PHP_EOL . + $throwable->stackTrace(), + ); + + $fault = $this->document->createElement( + $type, + Xml::prepareString($buffer), + ); + + $fault->setAttribute('type', $throwable->className()); + + $this->currentTestCase->appendChild($fault); + + if (!$this->prepared) { + $this->handleFinish($event->telemetryInfo(), 0); + } + } + + /** + * @throws InvalidArgumentException + */ + private function handleIncompleteOrSkipped(MarkedIncomplete|Skipped $event): void + { + if (!$this->prepared) { + $this->createTestCase($event); + } + + assert($this->currentTestCase !== null); + + $skipped = $this->document->createElement('skipped'); + + $this->currentTestCase->appendChild($skipped); + + $this->testSuiteSkipped[$this->testSuiteLevel]++; + + if (!$this->prepared) { + $this->handleFinish($event->telemetryInfo(), 0); + } + } + + /** + * @throws InvalidArgumentException + */ + private function testAsString(Test $test): string + { + if ($test->isPhpt()) { + return basename($test->file()); + } + + assert($test instanceof TestMethod); + + return sprintf( + '%s::%s%s', + $test->className(), + $this->name($test), + PHP_EOL, + ); + } + + /** + * @throws InvalidArgumentException + */ + private function name(Test $test): string + { + if ($test->isPhpt()) { + return basename($test->file()); + } + + assert($test instanceof TestMethod); + + if (!$test->testData()->hasDataFromDataProvider()) { + return $test->methodName(); + } + + $dataSetName = $test->testData()->dataFromDataProvider()->dataSetName(); + + if (is_int($dataSetName)) { + return sprintf( + '%s with data set #%d', + $test->methodName(), + $dataSetName, + ); + } + + return sprintf( + '%s with data set "%s"', + $test->methodName(), + $dataSetName, + ); + } + + /** + * @throws InvalidArgumentException + * + * @phpstan-assert !null $this->currentTestCase + */ + private function createTestCase(Errored|Failed|MarkedIncomplete|PreparationStarted|Prepared|Skipped $event): void + { + $testCase = $this->document->createElement('testcase'); + + $test = $event->test(); + + $testCase->setAttribute('name', $this->name($test)); + $testCase->setAttribute('file', $test->file()); + + if ($test->isTestMethod()) { + assert($test instanceof TestMethod); + + $testCase->setAttribute('line', (string) $test->line()); + $testCase->setAttribute('class', $test->className()); + $testCase->setAttribute('classname', str_replace('\\', '.', $test->className())); + } + + $this->currentTestCase = $testCase; + $this->time = $event->telemetryInfo()->time(); + } +} diff --git a/src/Logging/JUnit/Subscriber/Subscriber.php b/src/Logging/JUnit/Subscriber/Subscriber.php new file mode 100644 index 00000000000..b81f30da409 --- /dev/null +++ b/src/Logging/JUnit/Subscriber/Subscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Subscriber +{ + private JunitXmlLogger $logger; + + public function __construct(JunitXmlLogger $logger) + { + $this->logger = $logger; + } + + protected function logger(): JunitXmlLogger + { + return $this->logger; + } +} diff --git a/src/Logging/JUnit/Subscriber/TestErroredSubscriber.php b/src/Logging/JUnit/Subscriber/TestErroredSubscriber.php new file mode 100644 index 00000000000..114b1c84d24 --- /dev/null +++ b/src/Logging/JUnit/Subscriber/TestErroredSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\ErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestErroredSubscriber extends Subscriber implements ErroredSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Errored $event): void + { + $this->logger()->testErrored($event); + } +} diff --git a/src/Logging/JUnit/Subscriber/TestFailedSubscriber.php b/src/Logging/JUnit/Subscriber/TestFailedSubscriber.php new file mode 100644 index 00000000000..e8050784329 --- /dev/null +++ b/src/Logging/JUnit/Subscriber/TestFailedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\FailedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFailedSubscriber extends Subscriber implements FailedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Failed $event): void + { + $this->logger()->testFailed($event); + } +} diff --git a/src/Logging/JUnit/Subscriber/TestFinishedSubscriber.php b/src/Logging/JUnit/Subscriber/TestFinishedSubscriber.php new file mode 100644 index 00000000000..55aed8c6614 --- /dev/null +++ b/src/Logging/JUnit/Subscriber/TestFinishedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Finished $event): void + { + $this->logger()->testFinished($event); + } +} diff --git a/src/Logging/JUnit/Subscriber/TestMarkedIncompleteSubscriber.php b/src/Logging/JUnit/Subscriber/TestMarkedIncompleteSubscriber.php new file mode 100644 index 00000000000..8732af9dd4b --- /dev/null +++ b/src/Logging/JUnit/Subscriber/TestMarkedIncompleteSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\MarkedIncompleteSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestMarkedIncompleteSubscriber extends Subscriber implements MarkedIncompleteSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(MarkedIncomplete $event): void + { + $this->logger()->testMarkedIncomplete($event); + } +} diff --git a/src/Logging/JUnit/Subscriber/TestPreparationErroredSubscriber.php b/src/Logging/JUnit/Subscriber/TestPreparationErroredSubscriber.php new file mode 100644 index 00000000000..c6fb388f885 --- /dev/null +++ b/src/Logging/JUnit/Subscriber/TestPreparationErroredSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\PreparationErrored; +use PHPUnit\Event\Test\PreparationErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPreparationErroredSubscriber extends Subscriber implements PreparationErroredSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(PreparationErrored $event): void + { + $this->logger()->testPreparationErrored(); + } +} diff --git a/src/Logging/JUnit/Subscriber/TestPreparationFailedSubscriber.php b/src/Logging/JUnit/Subscriber/TestPreparationFailedSubscriber.php new file mode 100644 index 00000000000..456466a189f --- /dev/null +++ b/src/Logging/JUnit/Subscriber/TestPreparationFailedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\PreparationFailed; +use PHPUnit\Event\Test\PreparationFailedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPreparationFailedSubscriber extends Subscriber implements PreparationFailedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(PreparationFailed $event): void + { + $this->logger()->testPreparationFailed(); + } +} diff --git a/src/Logging/JUnit/Subscriber/TestPreparationStartedSubscriber.php b/src/Logging/JUnit/Subscriber/TestPreparationStartedSubscriber.php new file mode 100644 index 00000000000..8d24d65546a --- /dev/null +++ b/src/Logging/JUnit/Subscriber/TestPreparationStartedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\PreparationStarted; +use PHPUnit\Event\Test\PreparationStartedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPreparationStartedSubscriber extends Subscriber implements PreparationStartedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(PreparationStarted $event): void + { + $this->logger()->testPreparationStarted($event); + } +} diff --git a/src/Logging/JUnit/Subscriber/TestPreparedSubscriber.php b/src/Logging/JUnit/Subscriber/TestPreparedSubscriber.php new file mode 100644 index 00000000000..2a80b8af55d --- /dev/null +++ b/src/Logging/JUnit/Subscriber/TestPreparedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\PreparedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPreparedSubscriber extends Subscriber implements PreparedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Prepared $event): void + { + $this->logger()->testPrepared(); + } +} diff --git a/src/Logging/JUnit/Subscriber/TestPrintedUnexpectedOutputSubscriber.php b/src/Logging/JUnit/Subscriber/TestPrintedUnexpectedOutputSubscriber.php new file mode 100644 index 00000000000..186bf1502e4 --- /dev/null +++ b/src/Logging/JUnit/Subscriber/TestPrintedUnexpectedOutputSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\Test\PrintedUnexpectedOutput; +use PHPUnit\Event\Test\PrintedUnexpectedOutputSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPrintedUnexpectedOutputSubscriber extends Subscriber implements PrintedUnexpectedOutputSubscriber +{ + public function notify(PrintedUnexpectedOutput $event): void + { + $this->logger()->testPrintedUnexpectedOutput($event); + } +} diff --git a/src/Logging/JUnit/Subscriber/TestRunnerExecutionFinishedSubscriber.php b/src/Logging/JUnit/Subscriber/TestRunnerExecutionFinishedSubscriber.php new file mode 100644 index 00000000000..00617621aba --- /dev/null +++ b/src/Logging/JUnit/Subscriber/TestRunnerExecutionFinishedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\TestRunner\ExecutionFinished; +use PHPUnit\Event\TestRunner\ExecutionFinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestRunnerExecutionFinishedSubscriber extends Subscriber implements ExecutionFinishedSubscriber +{ + public function notify(ExecutionFinished $event): void + { + $this->logger()->flush(); + } +} diff --git a/src/Logging/JUnit/Subscriber/TestSkippedSubscriber.php b/src/Logging/JUnit/Subscriber/TestSkippedSubscriber.php new file mode 100644 index 00000000000..c6ee84ac69e --- /dev/null +++ b/src/Logging/JUnit/Subscriber/TestSkippedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Skipped; +use PHPUnit\Event\Test\SkippedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSkippedSubscriber extends Subscriber implements SkippedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Skipped $event): void + { + $this->logger()->testSkipped($event); + } +} diff --git a/src/Logging/JUnit/Subscriber/TestSuiteFinishedSubscriber.php b/src/Logging/JUnit/Subscriber/TestSuiteFinishedSubscriber.php new file mode 100644 index 00000000000..47691770731 --- /dev/null +++ b/src/Logging/JUnit/Subscriber/TestSuiteFinishedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\TestSuite\Finished; +use PHPUnit\Event\TestSuite\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + public function notify(Finished $event): void + { + $this->logger()->testSuiteFinished(); + } +} diff --git a/src/Logging/JUnit/Subscriber/TestSuiteStartedSubscriber.php b/src/Logging/JUnit/Subscriber/TestSuiteStartedSubscriber.php new file mode 100644 index 00000000000..30e350d3419 --- /dev/null +++ b/src/Logging/JUnit/Subscriber/TestSuiteStartedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\TestSuite\Started; +use PHPUnit\Event\TestSuite\StartedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteStartedSubscriber extends Subscriber implements StartedSubscriber +{ + public function notify(Started $event): void + { + $this->logger()->testSuiteStarted($event); + } +} diff --git a/src/Logging/OpenTestReporting/Exception/CannotOpenUriForWritingException.php b/src/Logging/OpenTestReporting/Exception/CannotOpenUriForWritingException.php new file mode 100644 index 00000000000..a8ad7eba5f8 --- /dev/null +++ b/src/Logging/OpenTestReporting/Exception/CannotOpenUriForWritingException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use RuntimeException; + +final class CannotOpenUriForWritingException extends RuntimeException implements Exception +{ +} diff --git a/src/Logging/OpenTestReporting/Exception/Exception.php b/src/Logging/OpenTestReporting/Exception/Exception.php new file mode 100644 index 00000000000..30766ae9f15 --- /dev/null +++ b/src/Logging/OpenTestReporting/Exception/Exception.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +interface Exception extends \PHPUnit\Exception +{ +} diff --git a/src/Logging/OpenTestReporting/InfrastructureInformationProvider.php b/src/Logging/OpenTestReporting/InfrastructureInformationProvider.php new file mode 100644 index 00000000000..c02b6c26740 --- /dev/null +++ b/src/Logging/OpenTestReporting/InfrastructureInformationProvider.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use const DIRECTORY_SEPARATOR; +use const PHP_OS_FAMILY; +use function assert; +use function explode; +use function fclose; +use function function_exists; +use function getenv; +use function gethostname; +use function is_resource; +use function php_uname; +use function posix_geteuid; +use function posix_getpwuid; +use function preg_split; +use function proc_close; +use function proc_open; +use function str_contains; +use function str_replace; +use function str_starts_with; +use function stream_get_contents; +use function trim; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class InfrastructureInformationProvider +{ + /** + * @return non-empty-string + */ + public function operatingSystem(): string + { + return php_uname(); + } + + /** + * @return non-empty-string + */ + public function hostName(): string + { + $candidate = gethostname(); + + if ($candidate === false) { + return 'unknown'; + } + + $candidate = trim($candidate); + + if ($candidate === '') { + return 'unknown'; + } + + return $candidate; + } + + /** + * @return non-empty-string + */ + public function userName(): string + { + if (function_exists('posix_getpwuid') && function_exists('posix_geteuid')) { + $candidate = trim(posix_getpwuid(posix_geteuid())['name']); + } elseif (PHP_OS_FAMILY === 'Windows') { + $candidate = trim((string) getenv('USERNAME')); + } + + if (!isset($candidate) || $candidate === '') { + return 'unknown'; + } + + return $candidate; + } + + /** + * @return array{originUrl: non-empty-string, branch: non-empty-string, commit: non-empty-string, clean: bool, status: string}|false + */ + public function gitInformation(): array|false + { + $buffer = $this->executeGitCommand('remote show -n'); + + if ($buffer === false) { + return false; + } + + if (!str_contains($buffer, 'origin')) { + return false; + } + + $buffer = $this->executeGitCommand('remote show -n origin'); + + if ($buffer === false) { + return false; + } + + $lines = preg_split("/\r\n|\n|\r/", $buffer); + + if (!isset($lines[1]) || !str_starts_with($lines[1], ' Fetch URL: ')) { + return false; + } + + $originUrl = trim(str_replace(' Fetch URL: ', '', $lines[1])); + + if (str_contains($originUrl, '@')) { + $originUrl = explode('@', $originUrl)[1]; + } + + $branch = $this->executeGitCommand('symbolic-ref --short HEAD'); + + if ($branch === false) { + return false; + } + + $commit = $this->executeGitCommand('rev-parse HEAD'); + + if ($commit === false) { + return false; + } + + $status = $this->executeGitCommand('status --porcelain'); + + if ($status === false) { + return false; + } + + return [ + 'originUrl' => $originUrl, + 'branch' => $branch, + 'commit' => $commit, + 'clean' => $status === '', + 'status' => $status, + ]; + } + + /** + * @return false|non-empty-string + */ + private function executeGitCommand(string $command): false|string + { + $command = 'git ' . $command; + + if (DIRECTORY_SEPARATOR === '/') { + $command = 'LC_ALL=en_US.UTF-8 ' . $command; + } + + $process = @proc_open( + $command, + [ + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ], + $pipes, + ); + + if (!is_resource($process)) { + return false; + } + + assert(isset($pipes[1]) && is_resource($pipes[1])); + assert(isset($pipes[2]) && is_resource($pipes[2])); + + $result = trim((string) stream_get_contents($pipes[1])); + + fclose($pipes[1]); + fclose($pipes[2]); + + $returnCode = proc_close($process); + + if ($returnCode !== 0) { + return false; + } + + return $result; + } +} diff --git a/src/Logging/OpenTestReporting/OtrXmlLogger.php b/src/Logging/OpenTestReporting/OtrXmlLogger.php new file mode 100644 index 00000000000..7fbe9551e66 --- /dev/null +++ b/src/Logging/OpenTestReporting/OtrXmlLogger.php @@ -0,0 +1,472 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use const PHP_VERSION; +use const ZEND_THREAD_SAFE; +use function array_pop; +use function assert; +use function count; +use function error_get_last; +use function str_replace; +use DateTimeImmutable; +use DateTimeZone; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Facade; +use PHPUnit\Event\Test\AfterLastTestMethodErrored; +use PHPUnit\Event\Test\AfterLastTestMethodFailed; +use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; +use PHPUnit\Event\Test\BeforeFirstTestMethodFailed; +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\PreparationErrored; +use PHPUnit\Event\Test\PreparationFailed; +use PHPUnit\Event\Test\Prepared as TestStarted; +use PHPUnit\Event\Test\Skipped; +use PHPUnit\Event\TestSuite\Skipped as TestSuiteSkipped; +use PHPUnit\Event\TestSuite\Started as TestSuiteStarted; +use PHPUnit\Event\TestSuite\TestSuiteForTestClass; +use XMLWriter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class OtrXmlLogger +{ + private readonly XMLWriter $writer; + + /** + * @var non-negative-int + */ + private int $idSequence = 0; + + /** + * @var ?positive-int + */ + private ?int $parentId = null; + + /** + * @var list + */ + private array $parentIdStack = []; + + /** + * @var ?positive-int + */ + private ?int $testId = null; + private ?Throwable $parentErrored = null; + private ?Throwable $parentFailed = null; + private bool $alreadyFinished = false; + private bool $includeGitInformation; + + /** + * @param non-empty-string $uri + * + * @throws CannotOpenUriForWritingException + */ + public function __construct(Facade $facade, string $uri, bool $includeGitInformation) + { + $this->writer = new XMLWriter; + + if (@$this->writer->openUri($uri) === false) { + throw new CannotOpenUriForWritingException( + str_replace('XMLWriter::openUri(): ', '', error_get_last()['message']), + ); + } + + $this->writer->setIndent(true); + + $this->registerSubscribers($facade); + + $this->includeGitInformation = $includeGitInformation; + } + + public function testRunnerStarted(): void + { + $infrastructure = new InfrastructureInformationProvider; + $gitInformation = false; + + if ($this->includeGitInformation) { + $gitInformation = $infrastructure->gitInformation(); + } + + $this->writer->startDocument(); + + $this->writer->startElement('e:events'); + $this->writer->writeAttribute('xmlns', 'https://schemas.opentest4j.org/reporting/core/0.2.0'); + $this->writer->writeAttribute('xmlns:e', 'https://schemas.opentest4j.org/reporting/events/0.2.0'); + + if ($gitInformation !== false) { + $this->writer->writeAttribute('xmlns:git', 'https://schemas.opentest4j.org/reporting/git/0.2.0'); + } + + $this->writer->writeAttribute('xmlns:php', 'https://schema.phpunit.de/otr/php/0.0.1'); + $this->writer->writeAttribute('xmlns:phpunit', 'https://schema.phpunit.de/otr/phpunit/0.0.1'); + + $this->writer->startElement('infrastructure'); + $this->writer->writeElement('hostName', $infrastructure->hostName()); + $this->writer->writeElement('userName', $infrastructure->userName()); + $this->writer->writeElement('operatingSystem', $infrastructure->operatingSystem()); + + $this->writer->writeElement('php:phpVersion', PHP_VERSION); + $this->writer->writeElement('php:threadModel', ZEND_THREAD_SAFE ? 'ZTS' : 'NTS'); + + if ($gitInformation !== false) { + $this->writer->startElement('git:repository'); + $this->writer->writeAttribute('originUrl', $gitInformation['originUrl']); + $this->writer->endElement(); + + $this->writer->writeElement('git:branch', $gitInformation['branch']); + $this->writer->writeElement('git:commit', $gitInformation['commit']); + + $this->writer->startElement('git:status'); + $this->writer->writeAttribute('clean', $gitInformation['clean'] === true ? 'true' : 'false'); + $this->writer->writeCdata($gitInformation['status']); + $this->writer->endElement(); + } + + $this->writer->endElement(); + + $this->writer->flush(); + } + + public function testRunnerFinished(): void + { + $this->writer->endDocument(); + + $this->writer->flush(); + } + + public function testSuiteStarted(TestSuiteStarted $event): void + { + $id = $this->nextId(); + + $this->writer->startElement('e:started'); + $this->writer->writeAttribute('id', (string) $id); + + if ($this->parentId !== null) { + $this->writer->writeAttribute('parentId', (string) $this->parentId); + } + + $testSuite = $event->testSuite(); + + $this->writer->writeAttribute('name', $testSuite->name()); + $this->writer->writeAttribute('time', $this->timestamp()); + + if ($testSuite->isForTestClass()) { + assert($testSuite instanceof TestSuiteForTestClass); + + $this->writer->startElement('sources'); + + $this->writer->startElement('fileSource'); + $this->writer->writeAttribute('path', $testSuite->file()); + $this->writer->startElement('filePosition'); + $this->writer->writeAttribute('line', (string) $testSuite->line()); + $this->writer->endElement(); + $this->writer->endElement(); + + $this->writer->startElement('phpunit:classSource'); + $this->writer->writeAttribute('className', $testSuite->className()); + $this->writer->endElement(); + + $this->writer->endElement(); + } + + $this->writer->endElement(); + + $this->writer->flush(); + + $this->parentId = $id; + $this->parentIdStack[] = $id; + } + + public function testSuiteSkipped(TestSuiteSkipped $event): void + { + $this->writer->startElement('e:finished'); + $this->writer->writeAttribute('id', (string) $this->parentId); + $this->writer->writeAttribute('time', $this->timestamp()); + + $this->writer->startElement('result'); + $this->writer->writeAttribute('status', 'SKIPPED'); + $this->writer->writeElement('reason', $event->message()); + $this->writer->endElement(); + + $this->writer->endElement(); + + $this->writer->flush(); + + $this->reduceTestSuiteLevel(); + } + + public function testSuiteFinished(): void + { + $this->writer->startElement('e:finished'); + $this->writer->writeAttribute('id', (string) $this->parentId); + $this->writer->writeAttribute('time', $this->timestamp()); + + if ($this->parentErrored !== null) { + $this->writer->startElement('result'); + $this->writer->writeAttribute('status', 'ERRORED'); + $this->writer->writeElement('reason', $this->parentErrored->message()); + $this->writeThrowable($this->parentErrored, false); + $this->writer->endElement(); + } elseif ($this->parentFailed !== null) { + $this->writer->startElement('result'); + $this->writer->writeAttribute('status', 'FAILED'); + $this->writer->writeElement('reason', $this->parentFailed->message()); + $this->writeThrowable($this->parentFailed, true); + $this->writer->endElement(); + } + + $this->writer->endElement(); + + $this->writer->flush(); + + $this->parentErrored = null; + $this->parentFailed = null; + + $this->reduceTestSuiteLevel(); + } + + public function testPrepared(PreparationErrored|PreparationFailed|TestStarted $event): void + { + $this->testId = $this->nextId(); + + $this->writeTestStarted( + $event->test(), + $this->testId, + $this->parentId, + ); + } + + public function testFinished(): void + { + if (!$this->alreadyFinished) { + $this->writer->startElement('e:finished'); + $this->writer->writeAttribute('id', (string) $this->testId); + $this->writer->writeAttribute('time', $this->timestamp()); + $this->writer->startElement('result'); + $this->writer->writeAttribute('status', Status::Successful->value); + $this->writer->endElement(); + $this->writer->endElement(); + } + + $this->alreadyFinished = false; + $this->testId = null; + } + + public function testFailed(Failed $event): void + { + $this->writer->startElement('e:finished'); + $this->writer->writeAttribute('id', (string) $this->testId); + $this->writer->writeAttribute('time', $this->timestamp()); + $this->writer->startElement('result'); + $this->writer->writeAttribute('status', Status::Failed->value); + + $this->writer->writeElement('reason', $event->throwable()->message()); + $this->writeThrowable($event->throwable(), true); + + $this->writer->endElement(); + $this->writer->endElement(); + + $this->writer->flush(); + + $this->alreadyFinished = true; + } + + public function testErrored(Errored $event): void + { + $this->writer->startElement('e:finished'); + $this->writer->writeAttribute('id', (string) $this->testId); + $this->writer->writeAttribute('time', $this->timestamp()); + $this->writer->startElement('result'); + $this->writer->writeAttribute('status', Status::Errored->value); + + $this->writer->writeElement('reason', $event->throwable()->message()); + $this->writeThrowable($event->throwable(), false); + + $this->writer->endElement(); + $this->writer->endElement(); + + $this->writer->flush(); + + $this->alreadyFinished = true; + } + + public function testSkipped(Skipped $event): void + { + if ($this->testId === null) { + $this->testId = $this->nextId(); + + $this->writeTestStarted( + $event->test(), + $this->testId, + $this->parentId, + ); + } + + $this->writer->startElement('e:finished'); + $this->writer->writeAttribute('id', (string) $this->testId); + $this->writer->writeAttribute('time', $this->timestamp()); + $this->writer->startElement('result'); + $this->writer->writeAttribute('status', Status::Skipped->value); + + $this->writer->writeElement('reason', $event->message()); + + $this->writer->endElement(); + $this->writer->endElement(); + + $this->writer->flush(); + + $this->alreadyFinished = true; + } + + public function markTestIncomplete(MarkedIncomplete $event): void + { + $this->writer->startElement('e:finished'); + $this->writer->writeAttribute('id', (string) $this->testId); + $this->writer->writeAttribute('time', $this->timestamp()); + $this->writer->startElement('result'); + $this->writer->writeAttribute('status', Status::Aborted->value); + + $this->writer->writeElement('reason', $event->throwable()->message()); + $this->writeThrowable($event->throwable(), false); + + $this->writer->endElement(); + $this->writer->endElement(); + + $this->writer->flush(); + + $this->alreadyFinished = true; + } + + public function parentErrored(AfterLastTestMethodErrored|BeforeFirstTestMethodErrored $event): void + { + $this->parentErrored = $event->throwable(); + } + + public function parentFailed(AfterLastTestMethodFailed|BeforeFirstTestMethodFailed $event): void + { + $this->parentFailed = $event->throwable(); + } + + private function registerSubscribers(Facade $facade): void + { + $facade->registerSubscribers( + new TestRunnerStartedSubscriber($this), + new TestSuiteStartedSubscriber($this), + new TestSuiteSkippedSubscriber($this), + new BeforeFirstTestMethodErroredSubscriber($this), + new BeforeFirstTestMethodFailedSubscriber($this), + new AfterLastTestMethodErroredSubscriber($this), + new AfterLastTestMethodFailedSubscriber($this), + new TestPreparationErroredSubscriber($this), + new TestPreparationFailedSubscriber($this), + new TestPreparedSubscriber($this), + new TestAbortedSubscriber($this), + new TestErroredSubscriber($this), + new TestFailedSubscriber($this), + new TestSkippedSubscriber($this), + new TestFinishedSubscriber($this), + new TestSuiteFinishedSubscriber($this), + new TestRunnerFinishedSubscriber($this), + ); + } + + /** + * @param positive-int $id + * @param ?positive-int $parentId + */ + private function writeTestStarted(Test $test, int $id, ?int $parentId): void + { + $this->writer->startElement('e:started'); + $this->writer->writeAttribute('id', (string) $id); + + if ($parentId !== null) { + $this->writer->writeAttribute('parentId', (string) $parentId); + } + + $this->writer->writeAttribute('name', $test->name()); + $this->writer->writeAttribute('time', $this->timestamp()); + + $this->writer->startElement('sources'); + + $this->writer->startElement('fileSource'); + $this->writer->writeAttribute('path', $test->file()); + + if ($test->isTestMethod()) { + assert($test instanceof TestMethod); + + $this->writer->startElement('filePosition'); + $this->writer->writeAttribute('line', (string) $test->line()); + $this->writer->endElement(); + } + + $this->writer->endElement(); + + if ($test->isTestMethod()) { + assert($test instanceof TestMethod); + + $this->writer->startElement('phpunit:methodSource'); + $this->writer->writeAttribute('className', $test->className()); + $this->writer->writeAttribute('methodName', $test->methodName()); + $this->writer->endElement(); + } + + $this->writer->endElement(); + + $this->writer->endElement(); + + $this->writer->flush(); + } + + private function writeThrowable(Throwable $throwable, bool $assertionError): void + { + $this->writer->startElement('phpunit:throwable'); + $this->writer->writeAttribute('type', $throwable->className()); + $this->writer->writeAttribute('assertionError', $assertionError ? 'true' : 'false'); + $this->writer->writeCdata($throwable->asString()); + $this->writer->endElement(); + } + + /** + * @return non-empty-string + */ + private function timestamp(): string + { + return new DateTimeImmutable('now', new DateTimeZone('UTC'))->format('Y-m-d\TH:i:s.u\Z'); + } + + /** + * @return positive-int + */ + private function nextId(): int + { + return ++$this->idSequence; + } + + private function reduceTestSuiteLevel(): void + { + array_pop($this->parentIdStack); + + if ($this->parentIdStack !== []) { + $this->parentId = $this->parentIdStack[count($this->parentIdStack) - 1]; + + return; + } + + $this->parentId = null; + } +} diff --git a/src/Logging/OpenTestReporting/Status.php b/src/Logging/OpenTestReporting/Status.php new file mode 100644 index 00000000000..4ff399b2cfc --- /dev/null +++ b/src/Logging/OpenTestReporting/Status.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This enumeration is not covered by the backward compatibility promise for PHPUnit + */ +enum Status: string +{ + case Aborted = 'ABORTED'; + case Errored = 'ERRORED'; + case Failed = 'FAILED'; + case Skipped = 'SKIPPED'; + case Successful = 'SUCCESSFUL'; +} diff --git a/src/Logging/OpenTestReporting/Subscriber/AfterLastTestMethodErroredSubscriber.php b/src/Logging/OpenTestReporting/Subscriber/AfterLastTestMethodErroredSubscriber.php new file mode 100644 index 00000000000..5b31f11cfc5 --- /dev/null +++ b/src/Logging/OpenTestReporting/Subscriber/AfterLastTestMethodErroredSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\AfterLastTestMethodErrored; +use PHPUnit\Event\Test\AfterLastTestMethodErroredSubscriber as AfterLastTestMethodErroredSubscriberInterface; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterLastTestMethodErroredSubscriber extends Subscriber implements AfterLastTestMethodErroredSubscriberInterface +{ + /** + * @throws InvalidArgumentException + */ + public function notify(AfterLastTestMethodErrored $event): void + { + $this->logger()->parentErrored($event); + } +} diff --git a/src/Logging/OpenTestReporting/Subscriber/AfterLastTestMethodFailedSubscriber.php b/src/Logging/OpenTestReporting/Subscriber/AfterLastTestMethodFailedSubscriber.php new file mode 100644 index 00000000000..340d26d737e --- /dev/null +++ b/src/Logging/OpenTestReporting/Subscriber/AfterLastTestMethodFailedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\AfterLastTestMethodFailed; +use PHPUnit\Event\Test\AfterLastTestMethodFailedSubscriber as AfterLastTestMethodFailedSubscriberInterface; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterLastTestMethodFailedSubscriber extends Subscriber implements AfterLastTestMethodFailedSubscriberInterface +{ + /** + * @throws InvalidArgumentException + */ + public function notify(AfterLastTestMethodFailed $event): void + { + $this->logger()->parentFailed($event); + } +} diff --git a/src/Logging/OpenTestReporting/Subscriber/BeforeFirstTestMethodErroredSubscriber.php b/src/Logging/OpenTestReporting/Subscriber/BeforeFirstTestMethodErroredSubscriber.php new file mode 100644 index 00000000000..3fb8c1efc7d --- /dev/null +++ b/src/Logging/OpenTestReporting/Subscriber/BeforeFirstTestMethodErroredSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; +use PHPUnit\Event\Test\BeforeFirstTestMethodErroredSubscriber as BeforeFirstTestMethodErroredSubscriberInterface; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeFirstTestMethodErroredSubscriber extends Subscriber implements BeforeFirstTestMethodErroredSubscriberInterface +{ + /** + * @throws InvalidArgumentException + */ + public function notify(BeforeFirstTestMethodErrored $event): void + { + $this->logger()->parentErrored($event); + } +} diff --git a/src/Logging/OpenTestReporting/Subscriber/BeforeFirstTestMethodFailedSubscriber.php b/src/Logging/OpenTestReporting/Subscriber/BeforeFirstTestMethodFailedSubscriber.php new file mode 100644 index 00000000000..fad858cfe89 --- /dev/null +++ b/src/Logging/OpenTestReporting/Subscriber/BeforeFirstTestMethodFailedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\BeforeFirstTestMethodFailed; +use PHPUnit\Event\Test\BeforeFirstTestMethodFailedSubscriber as BeforeFirstTestMethodFailedSubscriberInterface; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeFirstTestMethodFailedSubscriber extends Subscriber implements BeforeFirstTestMethodFailedSubscriberInterface +{ + /** + * @throws InvalidArgumentException + */ + public function notify(BeforeFirstTestMethodFailed $event): void + { + $this->logger()->parentFailed($event); + } +} diff --git a/src/Logging/OpenTestReporting/Subscriber/Subscriber.php b/src/Logging/OpenTestReporting/Subscriber/Subscriber.php new file mode 100644 index 00000000000..84b71de679c --- /dev/null +++ b/src/Logging/OpenTestReporting/Subscriber/Subscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Subscriber +{ + private OtrXmlLogger $logger; + + public function __construct(OtrXmlLogger $logger) + { + $this->logger = $logger; + } + + protected function logger(): OtrXmlLogger + { + return $this->logger; + } +} diff --git a/src/Logging/OpenTestReporting/Subscriber/TestAbortedSubscriber.php b/src/Logging/OpenTestReporting/Subscriber/TestAbortedSubscriber.php new file mode 100644 index 00000000000..31376214a0b --- /dev/null +++ b/src/Logging/OpenTestReporting/Subscriber/TestAbortedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\MarkedIncompleteSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestAbortedSubscriber extends Subscriber implements MarkedIncompleteSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(MarkedIncomplete $event): void + { + $this->logger()->markTestIncomplete($event); + } +} diff --git a/src/Logging/OpenTestReporting/Subscriber/TestErroredSubscriber.php b/src/Logging/OpenTestReporting/Subscriber/TestErroredSubscriber.php new file mode 100644 index 00000000000..1a8a40be927 --- /dev/null +++ b/src/Logging/OpenTestReporting/Subscriber/TestErroredSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\ErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestErroredSubscriber extends Subscriber implements ErroredSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Errored $event): void + { + $this->logger()->testErrored($event); + } +} diff --git a/src/Logging/OpenTestReporting/Subscriber/TestFailedSubscriber.php b/src/Logging/OpenTestReporting/Subscriber/TestFailedSubscriber.php new file mode 100644 index 00000000000..0b0caf97175 --- /dev/null +++ b/src/Logging/OpenTestReporting/Subscriber/TestFailedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\FailedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFailedSubscriber extends Subscriber implements FailedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Failed $event): void + { + $this->logger()->testFailed($event); + } +} diff --git a/src/Logging/OpenTestReporting/Subscriber/TestFinishedSubscriber.php b/src/Logging/OpenTestReporting/Subscriber/TestFinishedSubscriber.php new file mode 100644 index 00000000000..d05de8b5ac2 --- /dev/null +++ b/src/Logging/OpenTestReporting/Subscriber/TestFinishedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Finished $event): void + { + $this->logger()->testFinished(); + } +} diff --git a/src/Logging/OpenTestReporting/Subscriber/TestPreparationErroredSubscriber.php b/src/Logging/OpenTestReporting/Subscriber/TestPreparationErroredSubscriber.php new file mode 100644 index 00000000000..6fb72562863 --- /dev/null +++ b/src/Logging/OpenTestReporting/Subscriber/TestPreparationErroredSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\PreparationErrored; +use PHPUnit\Event\Test\PreparationErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPreparationErroredSubscriber extends Subscriber implements PreparationErroredSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(PreparationErrored $event): void + { + $this->logger()->testPrepared($event); + } +} diff --git a/src/Logging/OpenTestReporting/Subscriber/TestPreparationFailedSubscriber.php b/src/Logging/OpenTestReporting/Subscriber/TestPreparationFailedSubscriber.php new file mode 100644 index 00000000000..f0167508aba --- /dev/null +++ b/src/Logging/OpenTestReporting/Subscriber/TestPreparationFailedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\PreparationFailed; +use PHPUnit\Event\Test\PreparationFailedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPreparationFailedSubscriber extends Subscriber implements PreparationFailedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(PreparationFailed $event): void + { + $this->logger()->testPrepared($event); + } +} diff --git a/src/Logging/OpenTestReporting/Subscriber/TestPreparedSubscriber.php b/src/Logging/OpenTestReporting/Subscriber/TestPreparedSubscriber.php new file mode 100644 index 00000000000..9138f142b8a --- /dev/null +++ b/src/Logging/OpenTestReporting/Subscriber/TestPreparedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\PreparedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPreparedSubscriber extends Subscriber implements PreparedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Prepared $event): void + { + $this->logger()->testPrepared($event); + } +} diff --git a/src/Logging/OpenTestReporting/Subscriber/TestRunnerFinishedSubscriber.php b/src/Logging/OpenTestReporting/Subscriber/TestRunnerFinishedSubscriber.php new file mode 100644 index 00000000000..d690ba567c2 --- /dev/null +++ b/src/Logging/OpenTestReporting/Subscriber/TestRunnerFinishedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\TestRunner\ExecutionFinished; +use PHPUnit\Event\TestRunner\ExecutionFinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestRunnerFinishedSubscriber extends Subscriber implements ExecutionFinishedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(ExecutionFinished $event): void + { + $this->logger()->testRunnerFinished(); + } +} diff --git a/src/Logging/OpenTestReporting/Subscriber/TestRunnerStartedSubscriber.php b/src/Logging/OpenTestReporting/Subscriber/TestRunnerStartedSubscriber.php new file mode 100644 index 00000000000..6aba01c9646 --- /dev/null +++ b/src/Logging/OpenTestReporting/Subscriber/TestRunnerStartedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\Application\Started; +use PHPUnit\Event\Application\StartedSubscriber; +use PHPUnit\Event\InvalidArgumentException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestRunnerStartedSubscriber extends Subscriber implements StartedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Started $event): void + { + $this->logger()->testRunnerStarted(); + } +} diff --git a/src/Logging/OpenTestReporting/Subscriber/TestSkippedSubscriber.php b/src/Logging/OpenTestReporting/Subscriber/TestSkippedSubscriber.php new file mode 100644 index 00000000000..32e67ca8cf1 --- /dev/null +++ b/src/Logging/OpenTestReporting/Subscriber/TestSkippedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Skipped; +use PHPUnit\Event\Test\SkippedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSkippedSubscriber extends Subscriber implements SkippedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Skipped $event): void + { + $this->logger()->testSkipped($event); + } +} diff --git a/src/Logging/OpenTestReporting/Subscriber/TestSuiteFinishedSubscriber.php b/src/Logging/OpenTestReporting/Subscriber/TestSuiteFinishedSubscriber.php new file mode 100644 index 00000000000..16ebf626ced --- /dev/null +++ b/src/Logging/OpenTestReporting/Subscriber/TestSuiteFinishedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\TestSuite\Finished; +use PHPUnit\Event\TestSuite\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Finished $event): void + { + $this->logger()->testSuiteFinished(); + } +} diff --git a/src/Logging/OpenTestReporting/Subscriber/TestSuiteSkippedSubscriber.php b/src/Logging/OpenTestReporting/Subscriber/TestSuiteSkippedSubscriber.php new file mode 100644 index 00000000000..58691b613af --- /dev/null +++ b/src/Logging/OpenTestReporting/Subscriber/TestSuiteSkippedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\TestSuite\Skipped; +use PHPUnit\Event\TestSuite\SkippedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteSkippedSubscriber extends Subscriber implements SkippedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Skipped $event): void + { + $this->logger()->testSuiteSkipped($event); + } +} diff --git a/src/Logging/OpenTestReporting/Subscriber/TestSuiteStartedSubscriber.php b/src/Logging/OpenTestReporting/Subscriber/TestSuiteStartedSubscriber.php new file mode 100644 index 00000000000..78678012520 --- /dev/null +++ b/src/Logging/OpenTestReporting/Subscriber/TestSuiteStartedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\TestSuite\Started; +use PHPUnit\Event\TestSuite\StartedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteStartedSubscriber extends Subscriber implements StartedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Started $event): void + { + $this->logger()->testSuiteStarted($event); + } +} diff --git a/src/Logging/OpenTestReporting/schema/core-0.2.0.xsd b/src/Logging/OpenTestReporting/schema/core-0.2.0.xsd new file mode 100644 index 00000000000..170e20cae52 --- /dev/null +++ b/src/Logging/OpenTestReporting/schema/core-0.2.0.xsd @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The media type of the file content, e.g. 'text/plain' or 'application/json', + see https://www.iana.org/assignments/media-types/media-types.xhtml. + For text files, the charset should be specified in the 'charset' attribute, + e.g. 'text/plain; charset=utf-8'. + + + + + + + + + + + + + + Typically 'stdout' or 'stderr' but may also be used for attaching other log output + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Logging/OpenTestReporting/schema/events-0.2.0.xsd b/src/Logging/OpenTestReporting/schema/events-0.2.0.xsd new file mode 100644 index 00000000000..e5c37ccc3e2 --- /dev/null +++ b/src/Logging/OpenTestReporting/schema/events-0.2.0.xsd @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Logging/OpenTestReporting/schema/git-0.2.0.xsd b/src/Logging/OpenTestReporting/schema/git-0.2.0.xsd new file mode 100644 index 00000000000..1a1dd6d6a08 --- /dev/null +++ b/src/Logging/OpenTestReporting/schema/git-0.2.0.xsd @@ -0,0 +1,69 @@ + + + + + + + + + the URL of the 'origin' remote of the Git repository + + + + + + + + + + + + + + + + + + + + the branch the HEAD commit is pointing to, if any + + + + + + + + + + + + the commit hash + + + + + + + + + + + + the output of `git status --porcelain`, potentially empty + + + + + whether the working directory clean contains no changes or untracked files + + + + + + + + + diff --git a/src/Logging/OpenTestReporting/schema/otr.xsd b/src/Logging/OpenTestReporting/schema/otr.xsd new file mode 100644 index 00000000000..982dac4dede --- /dev/null +++ b/src/Logging/OpenTestReporting/schema/otr.xsd @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/Logging/OpenTestReporting/schema/php.xsd b/src/Logging/OpenTestReporting/schema/php.xsd new file mode 100644 index 00000000000..f880345d108 --- /dev/null +++ b/src/Logging/OpenTestReporting/schema/php.xsd @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/src/Logging/OpenTestReporting/schema/phpunit.xsd b/src/Logging/OpenTestReporting/schema/phpunit.xsd new file mode 100644 index 00000000000..0a0c00edaab --- /dev/null +++ b/src/Logging/OpenTestReporting/schema/phpunit.xsd @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Logging/TeamCity/Subscriber/Subscriber.php b/src/Logging/TeamCity/Subscriber/Subscriber.php new file mode 100644 index 00000000000..b1ad46d833d --- /dev/null +++ b/src/Logging/TeamCity/Subscriber/Subscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Subscriber +{ + private TeamCityLogger $logger; + + public function __construct(TeamCityLogger $logger) + { + $this->logger = $logger; + } + + protected function logger(): TeamCityLogger + { + return $this->logger; + } +} diff --git a/src/Logging/TeamCity/Subscriber/TestConsideredRiskySubscriber.php b/src/Logging/TeamCity/Subscriber/TestConsideredRiskySubscriber.php new file mode 100644 index 00000000000..9482ccb22d1 --- /dev/null +++ b/src/Logging/TeamCity/Subscriber/TestConsideredRiskySubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\ConsideredRisky; +use PHPUnit\Event\Test\ConsideredRiskySubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestConsideredRiskySubscriber extends Subscriber implements ConsideredRiskySubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(ConsideredRisky $event): void + { + $this->logger()->testConsideredRisky($event); + } +} diff --git a/src/Logging/TeamCity/Subscriber/TestErroredSubscriber.php b/src/Logging/TeamCity/Subscriber/TestErroredSubscriber.php new file mode 100644 index 00000000000..4ce8d0cb774 --- /dev/null +++ b/src/Logging/TeamCity/Subscriber/TestErroredSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\ErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestErroredSubscriber extends Subscriber implements ErroredSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Errored $event): void + { + $this->logger()->testErrored($event); + } +} diff --git a/src/Logging/TeamCity/Subscriber/TestFailedSubscriber.php b/src/Logging/TeamCity/Subscriber/TestFailedSubscriber.php new file mode 100644 index 00000000000..8d8caa6308e --- /dev/null +++ b/src/Logging/TeamCity/Subscriber/TestFailedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\FailedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFailedSubscriber extends Subscriber implements FailedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Failed $event): void + { + $this->logger()->testFailed($event); + } +} diff --git a/src/Logging/TeamCity/Subscriber/TestFinishedSubscriber.php b/src/Logging/TeamCity/Subscriber/TestFinishedSubscriber.php new file mode 100644 index 00000000000..6b4bef3dcc1 --- /dev/null +++ b/src/Logging/TeamCity/Subscriber/TestFinishedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Finished $event): void + { + $this->logger()->testFinished($event); + } +} diff --git a/src/Logging/TeamCity/Subscriber/TestMarkedIncompleteSubscriber.php b/src/Logging/TeamCity/Subscriber/TestMarkedIncompleteSubscriber.php new file mode 100644 index 00000000000..b38d54c44a2 --- /dev/null +++ b/src/Logging/TeamCity/Subscriber/TestMarkedIncompleteSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\MarkedIncompleteSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestMarkedIncompleteSubscriber extends Subscriber implements MarkedIncompleteSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(MarkedIncomplete $event): void + { + $this->logger()->testMarkedIncomplete($event); + } +} diff --git a/src/Logging/TeamCity/Subscriber/TestPreparedSubscriber.php b/src/Logging/TeamCity/Subscriber/TestPreparedSubscriber.php new file mode 100644 index 00000000000..85476a059d4 --- /dev/null +++ b/src/Logging/TeamCity/Subscriber/TestPreparedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\PreparedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPreparedSubscriber extends Subscriber implements PreparedSubscriber +{ + public function notify(Prepared $event): void + { + $this->logger()->testPrepared($event); + } +} diff --git a/src/Logging/TeamCity/Subscriber/TestRunnerExecutionFinishedSubscriber.php b/src/Logging/TeamCity/Subscriber/TestRunnerExecutionFinishedSubscriber.php new file mode 100644 index 00000000000..824ea424438 --- /dev/null +++ b/src/Logging/TeamCity/Subscriber/TestRunnerExecutionFinishedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use PHPUnit\Event\TestRunner\ExecutionFinished; +use PHPUnit\Event\TestRunner\ExecutionFinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestRunnerExecutionFinishedSubscriber extends Subscriber implements ExecutionFinishedSubscriber +{ + public function notify(ExecutionFinished $event): void + { + $this->logger()->flush(); + } +} diff --git a/src/Logging/TeamCity/Subscriber/TestSkippedSubscriber.php b/src/Logging/TeamCity/Subscriber/TestSkippedSubscriber.php new file mode 100644 index 00000000000..0f55795fc5a --- /dev/null +++ b/src/Logging/TeamCity/Subscriber/TestSkippedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Skipped; +use PHPUnit\Event\Test\SkippedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSkippedSubscriber extends Subscriber implements SkippedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Skipped $event): void + { + $this->logger()->testSkipped($event); + } +} diff --git a/src/Logging/TeamCity/Subscriber/TestSuiteBeforeFirstTestMethodErroredSubscriber.php b/src/Logging/TeamCity/Subscriber/TestSuiteBeforeFirstTestMethodErroredSubscriber.php new file mode 100644 index 00000000000..25459197b96 --- /dev/null +++ b/src/Logging/TeamCity/Subscriber/TestSuiteBeforeFirstTestMethodErroredSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; +use PHPUnit\Event\Test\BeforeFirstTestMethodErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteBeforeFirstTestMethodErroredSubscriber extends Subscriber implements BeforeFirstTestMethodErroredSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(BeforeFirstTestMethodErrored $event): void + { + $this->logger()->beforeFirstTestMethodErrored($event); + } +} diff --git a/src/Logging/TeamCity/Subscriber/TestSuiteFinishedSubscriber.php b/src/Logging/TeamCity/Subscriber/TestSuiteFinishedSubscriber.php new file mode 100644 index 00000000000..71889d8ce65 --- /dev/null +++ b/src/Logging/TeamCity/Subscriber/TestSuiteFinishedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use PHPUnit\Event\TestSuite\Finished; +use PHPUnit\Event\TestSuite\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + public function notify(Finished $event): void + { + $this->logger()->testSuiteFinished($event); + } +} diff --git a/src/Logging/TeamCity/Subscriber/TestSuiteSkippedSubscriber.php b/src/Logging/TeamCity/Subscriber/TestSuiteSkippedSubscriber.php new file mode 100644 index 00000000000..2d6a3f3d248 --- /dev/null +++ b/src/Logging/TeamCity/Subscriber/TestSuiteSkippedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\TestSuite\Skipped; +use PHPUnit\Event\TestSuite\SkippedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteSkippedSubscriber extends Subscriber implements SkippedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Skipped $event): void + { + $this->logger()->testSuiteSkipped($event); + } +} diff --git a/src/Logging/TeamCity/Subscriber/TestSuiteStartedSubscriber.php b/src/Logging/TeamCity/Subscriber/TestSuiteStartedSubscriber.php new file mode 100644 index 00000000000..7caba60b6a5 --- /dev/null +++ b/src/Logging/TeamCity/Subscriber/TestSuiteStartedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use PHPUnit\Event\TestSuite\Started; +use PHPUnit\Event\TestSuite\StartedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteStartedSubscriber extends Subscriber implements StartedSubscriber +{ + public function notify(Started $event): void + { + $this->logger()->testSuiteStarted($event); + } +} diff --git a/src/Logging/TeamCity/TeamCityLogger.php b/src/Logging/TeamCity/TeamCityLogger.php new file mode 100644 index 00000000000..9a56fc3832d --- /dev/null +++ b/src/Logging/TeamCity/TeamCityLogger.php @@ -0,0 +1,418 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use function assert; +use function getmypid; +use function ini_get; +use function is_a; +use function round; +use function sprintf; +use function str_replace; +use function stripos; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Facade; +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Telemetry\HRTime; +use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; +use PHPUnit\Event\Test\ConsideredRisky; +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\Skipped; +use PHPUnit\Event\TestSuite\Finished as TestSuiteFinished; +use PHPUnit\Event\TestSuite\Skipped as TestSuiteSkipped; +use PHPUnit\Event\TestSuite\Started as TestSuiteStarted; +use PHPUnit\Event\TestSuite\TestSuiteForTestClass; +use PHPUnit\Event\TestSuite\TestSuiteForTestMethodWithDataProvider; +use PHPUnit\Framework\Exception as FrameworkException; +use PHPUnit\TextUI\Output\Printer; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TeamCityLogger +{ + private readonly Printer $printer; + private bool $isSummaryTestCountPrinted = false; + private ?HRTime $time = null; + private ?int $flowId = null; + + public function __construct(Printer $printer, Facade $facade) + { + $this->printer = $printer; + + $this->registerSubscribers($facade); + $this->setFlowId(); + } + + public function testSuiteStarted(TestSuiteStarted $event): void + { + $testSuite = $event->testSuite(); + + if (!$this->isSummaryTestCountPrinted) { + $this->isSummaryTestCountPrinted = true; + + $this->writeMessage( + 'testCount', + ['count' => $testSuite->count()], + ); + } + + $parameters = ['name' => $testSuite->name()]; + + if ($testSuite->isForTestClass()) { + assert($testSuite instanceof TestSuiteForTestClass); + + $parameters['locationHint'] = sprintf( + 'php_qn://%s::\\%s', + $testSuite->file(), + $testSuite->name(), + ); + } elseif ($testSuite->isForTestMethodWithDataProvider()) { + assert($testSuite instanceof TestSuiteForTestMethodWithDataProvider); + + $parameters['locationHint'] = sprintf( + 'php_qn://%s::\\%s', + $testSuite->file(), + $testSuite->name(), + ); + + $parameters['name'] = $testSuite->methodName(); + } + + $this->writeMessage('testSuiteStarted', $parameters); + } + + public function testSuiteFinished(TestSuiteFinished $event): void + { + $testSuite = $event->testSuite(); + + $parameters = ['name' => $testSuite->name()]; + + if ($testSuite->isForTestMethodWithDataProvider()) { + assert($testSuite instanceof TestSuiteForTestMethodWithDataProvider); + + $parameters['name'] = $testSuite->methodName(); + } + + $this->writeMessage('testSuiteFinished', $parameters); + } + + public function testPrepared(Prepared $event): void + { + $test = $event->test(); + + $parameters = [ + 'name' => $test->name(), + ]; + + if ($test->isTestMethod()) { + assert($test instanceof TestMethod); + + $parameters['locationHint'] = sprintf( + 'php_qn://%s::\\%s::%s', + $test->file(), + $test->className(), + $test->name(), + ); + } + + $this->writeMessage('testStarted', $parameters); + + $this->time = $event->telemetryInfo()->time(); + } + + /** + * @throws InvalidArgumentException + */ + public function testMarkedIncomplete(MarkedIncomplete $event): void + { + if ($this->time === null) { + // @codeCoverageIgnoreStart + $this->time = $event->telemetryInfo()->time(); + // @codeCoverageIgnoreEnd + } + + $this->writeMessage( + 'testIgnored', + [ + 'name' => $event->test()->name(), + 'message' => $event->throwable()->message(), + 'details' => $this->details($event->throwable()), + 'duration' => $this->duration($event), + ], + ); + } + + /** + * @throws InvalidArgumentException + */ + public function testSkipped(Skipped $event): void + { + if ($this->time === null) { + $this->time = $event->telemetryInfo()->time(); + } + + $parameters = [ + 'name' => $event->test()->name(), + 'message' => $event->message(), + ]; + + $parameters['duration'] = $this->duration($event); + + $this->writeMessage('testIgnored', $parameters); + } + + /** + * @throws InvalidArgumentException + */ + public function testSuiteSkipped(TestSuiteSkipped $event): void + { + if ($this->time === null) { + $this->time = $event->telemetryInfo()->time(); + } + + $parameters = [ + 'name' => $event->testSuite()->name(), + 'message' => $event->message(), + ]; + + $parameters['duration'] = $this->duration($event); + + $this->writeMessage('testIgnored', $parameters); + $this->writeMessage('testSuiteFinished', $parameters); + } + + /** + * @throws InvalidArgumentException + */ + public function beforeFirstTestMethodErrored(BeforeFirstTestMethodErrored $event): void + { + if ($this->time === null) { + $this->time = $event->telemetryInfo()->time(); + } + + $parameters = [ + 'name' => $event->testClassName(), + 'message' => $this->message($event->throwable()), + 'details' => $this->details($event->throwable()), + 'duration' => $this->duration($event), + ]; + + $this->writeMessage('testFailed', $parameters); + $this->writeMessage('testSuiteFinished', $parameters); + } + + /** + * @throws InvalidArgumentException + */ + public function testErrored(Errored $event): void + { + if ($this->time === null) { + $this->time = $event->telemetryInfo()->time(); + } + + $this->writeMessage( + 'testFailed', + [ + 'name' => $event->test()->name(), + 'message' => $this->message($event->throwable()), + 'details' => $this->details($event->throwable()), + 'duration' => $this->duration($event), + ], + ); + } + + /** + * @throws InvalidArgumentException + */ + public function testFailed(Failed $event): void + { + if ($this->time === null) { + // @codeCoverageIgnoreStart + $this->time = $event->telemetryInfo()->time(); + // @codeCoverageIgnoreEnd + } + + $parameters = [ + 'name' => $event->test()->name(), + 'message' => $this->message($event->throwable()), + 'details' => $this->details($event->throwable()), + 'duration' => $this->duration($event), + ]; + + if ($event->hasComparisonFailure()) { + $parameters['type'] = 'comparisonFailure'; + $parameters['actual'] = $event->comparisonFailure()->actual(); + $parameters['expected'] = $event->comparisonFailure()->expected(); + } + + $this->writeMessage('testFailed', $parameters); + } + + /** + * @throws InvalidArgumentException + */ + public function testConsideredRisky(ConsideredRisky $event): void + { + if ($this->time === null) { + // @codeCoverageIgnoreStart + $this->time = $event->telemetryInfo()->time(); + // @codeCoverageIgnoreEnd + } + + $this->writeMessage( + 'testFailed', + [ + 'name' => $event->test()->name(), + 'message' => $event->message(), + 'details' => '', + 'duration' => $this->duration($event), + ], + ); + } + + /** + * @throws InvalidArgumentException + */ + public function testFinished(Finished $event): void + { + $this->writeMessage( + 'testFinished', + [ + 'name' => $event->test()->name(), + 'duration' => $this->duration($event), + ], + ); + + $this->time = null; + } + + public function flush(): void + { + $this->printer->flush(); + } + + private function registerSubscribers(Facade $facade): void + { + $facade->registerSubscribers( + new TestSuiteStartedSubscriber($this), + new TestSuiteFinishedSubscriber($this), + new TestPreparedSubscriber($this), + new TestFinishedSubscriber($this), + new TestErroredSubscriber($this), + new TestFailedSubscriber($this), + new TestMarkedIncompleteSubscriber($this), + new TestSkippedSubscriber($this), + new TestSuiteSkippedSubscriber($this), + new TestConsideredRiskySubscriber($this), + new TestRunnerExecutionFinishedSubscriber($this), + new TestSuiteBeforeFirstTestMethodErroredSubscriber($this), + ); + } + + private function setFlowId(): void + { + if (stripos(ini_get('disable_functions'), 'getmypid') === false) { + $this->flowId = getmypid(); + } + } + + /** + * @param array $parameters + */ + private function writeMessage(string $eventName, array $parameters = []): void + { + $this->printer->print( + sprintf( + '##teamcity[%s', + $eventName, + ), + ); + + if ($this->flowId !== null) { + $parameters['flowId'] = $this->flowId; + } + + foreach ($parameters as $key => $value) { + $this->printer->print( + sprintf( + " %s='%s'", + $key, + $this->escape((string) $value), + ), + ); + } + + $this->printer->print("]\n"); + } + + /** + * @throws InvalidArgumentException + */ + private function duration(Event $event): int + { + if ($this->time === null) { + // @codeCoverageIgnoreStart + return 0; + // @codeCoverageIgnoreEnd + } + + return (int) round($event->telemetryInfo()->time()->duration($this->time)->asFloat() * 1000); + } + + private function escape(string $string): string + { + return str_replace( + ['|', "'", "\n", "\r", ']', '['], + ['||', "|'", '|n', '|r', '|]', '|['], + $string, + ); + } + + private function message(Throwable $throwable): string + { + if (is_a($throwable->className(), FrameworkException::class, true)) { + return $throwable->message(); + } + + $buffer = $throwable->className(); + + if ($throwable->message() !== '') { + $buffer .= ': ' . $throwable->message(); + } + + return $buffer; + } + + private function details(Throwable $throwable): string + { + $buffer = $throwable->stackTrace(); + + while ($throwable->hasPrevious()) { + $throwable = $throwable->previous(); + + $buffer .= sprintf( + "\nCaused by\n%s\n%s", + $throwable->description(), + $throwable->stackTrace(), + ); + } + + return $buffer; + } +} diff --git a/src/Logging/TestDox/HtmlRenderer.php b/src/Logging/TestDox/HtmlRenderer.php new file mode 100644 index 00000000000..3d74d593616 --- /dev/null +++ b/src/Logging/TestDox/HtmlRenderer.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class HtmlRenderer +{ + private const string PAGE_HEADER = <<<'EOT' + + + + + Test Documentation + + + +EOT; + private const string CLASS_HEADER = <<<'EOT' + +

%s

+
    + +EOT; + private const string CLASS_FOOTER = <<<'EOT' +
+EOT; + private const string PAGE_FOOTER = <<<'EOT' + + + +EOT; + + /** + * @param array $tests + */ + public function render(array $tests): string + { + $buffer = self::PAGE_HEADER; + + foreach ($tests as $prettifiedClassName => $_tests) { + $buffer .= sprintf( + self::CLASS_HEADER, + $prettifiedClassName, + ); + + foreach ($this->reduce($_tests) as $prettifiedMethodName => $outcome) { + $buffer .= sprintf( + "
  • %s
  • \n", + $outcome, + $prettifiedMethodName, + ); + } + + $buffer .= self::CLASS_FOOTER; + } + + return $buffer . self::PAGE_FOOTER; + } + + /** + * @return array + */ + private function reduce(TestResultCollection $tests): array + { + $result = []; + + foreach ($tests as $test) { + $prettifiedMethodName = $test->test()->testDox()->prettifiedMethodName(); + + if (!isset($result[$prettifiedMethodName])) { + $result[$prettifiedMethodName] = $test->status()->isSuccess() ? 'success' : 'defect'; + + continue; + } + + if ($test->status()->isSuccess()) { + continue; + } + + $result[$prettifiedMethodName] = 'defect'; + } + + return $result; + } +} diff --git a/src/Logging/TestDox/NamePrettifier.php b/src/Logging/TestDox/NamePrettifier.php new file mode 100644 index 00000000000..99d266c6c01 --- /dev/null +++ b/src/Logging/TestDox/NamePrettifier.php @@ -0,0 +1,441 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use const PHP_EOL; +use function array_key_exists; +use function array_keys; +use function array_map; +use function array_pop; +use function array_values; +use function assert; +use function class_exists; +use function explode; +use function gettype; +use function implode; +use function is_bool; +use function is_float; +use function is_int; +use function is_object; +use function is_scalar; +use function method_exists; +use function preg_quote; +use function preg_replace; +use function rtrim; +use function sprintf; +use function str_contains; +use function str_ends_with; +use function str_replace; +use function str_starts_with; +use function strlen; +use function strtolower; +use function strtoupper; +use function substr; +use function trim; +use PHPUnit\Event\Code\TestMethodBuilder; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Framework\TestCase; +use PHPUnit\Metadata\Parser\Registry as MetadataRegistry; +use PHPUnit\Metadata\TestDox; +use PHPUnit\Metadata\TestDoxFormatter; +use PHPUnit\Util\Color; +use PHPUnit\Util\Exporter; +use PHPUnit\Util\Filter; +use ReflectionEnum; +use ReflectionMethod; +use ReflectionObject; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NamePrettifier +{ + /** + * @var array + */ + private array $strings = []; + + /** + * @var array + */ + private array $prettifiedTestCases = []; + + /** + * @var array + */ + private array $erroredFormatters = []; + + /** + * @param class-string $className + */ + public function prettifyTestClassName(string $className): string + { + if (class_exists($className)) { + $classLevelTestDox = MetadataRegistry::parser()->forClass($className)->isTestDox(); + + if ($classLevelTestDox->isNotEmpty()) { + $classLevelTestDox = $classLevelTestDox->asArray()[0]; + + assert($classLevelTestDox instanceof TestDox); + + return $classLevelTestDox->text(); + } + } + + $parts = explode('\\', $className); + $className = array_pop($parts); + + if (str_ends_with($className, 'Test')) { + $className = substr($className, 0, strlen($className) - strlen('Test')); + } + + if (str_starts_with($className, 'Tests')) { + $className = substr($className, strlen('Tests')); + } elseif (str_starts_with($className, 'Test')) { + $className = substr($className, strlen('Test')); + } + + if ($className === '') { + $className = 'UnnamedTests'; + } + + if ($parts !== []) { + $parts[] = $className; + $fullyQualifiedName = implode('\\', $parts); + } else { + $fullyQualifiedName = $className; + } + + $result = preg_replace('/(?<=[[:lower:]])(?=[[:upper:]])/u', ' ', $className); + + if ($fullyQualifiedName !== $className) { + return $result . ' (' . $fullyQualifiedName . ')'; + } + + return $result; + } + + // NOTE: this method is on a hot path and very performance sensitive. change with care. + public function prettifyTestMethodName(string $name): string + { + if ($name === '') { + return ''; + } + + $string = rtrim($name, '0123456789'); + + if (array_key_exists($string, $this->strings)) { + $name = $string; + } elseif ($string === $name) { + $this->strings[$string] = 1; + } + + if (str_starts_with($name, 'test_')) { + $name = substr($name, 5); + } elseif (str_starts_with($name, 'test')) { + $name = substr($name, 4); + } + + if ($name === '') { + return ''; + } + + $name[0] = strtoupper($name[0]); + + $noUnderscore = str_replace('_', ' ', $name); + + if ($noUnderscore !== $name) { + return trim($noUnderscore); + } + + $wasNumeric = false; + + $buffer = ''; + + $len = strlen($name); + + for ($i = 0; $i < $len; $i++) { + if ($i > 0 && $name[$i] >= 'A' && $name[$i] <= 'Z') { + $buffer .= ' ' . strtolower($name[$i]); + } else { + $isNumeric = $name[$i] >= '0' && $name[$i] <= '9'; + + if (!$wasNumeric && $isNumeric) { + $buffer .= ' '; + $wasNumeric = true; + } + + if ($wasNumeric && !$isNumeric) { + $wasNumeric = false; + } + + $buffer .= $name[$i]; + } + } + + return trim($buffer); + } + + public function prettifyTestCase(TestCase $test, bool $colorize): string + { + $key = $test::class . '#' . $test->name(); + + if ($test->usesDataProvider()) { + $key .= '#' . $test->dataName(); + } + + if ($colorize) { + $key .= '#colorize'; + } + + if (isset($this->prettifiedTestCases[$key])) { + return $this->prettifiedTestCases[$key]; + } + + $metadataCollection = MetadataRegistry::parser()->forMethod($test::class, $test->name()); + $testDox = $metadataCollection->isTestDox()->isMethodLevel(); + $callback = $metadataCollection->isTestDoxFormatter(); + $isCustomized = false; + + if ($testDox->isNotEmpty()) { + $testDox = $testDox->asArray()[0]; + + assert($testDox instanceof TestDox); + + [$result, $isCustomized] = $this->processTestDox($test, $testDox, $colorize); + } elseif ($callback->isNotEmpty()) { + $callback = $callback->asArray()[0]; + + assert($callback instanceof TestDoxFormatter); + + [$result, $isCustomized] = $this->processTestDoxFormatter($test, $callback); + } else { + $result = $this->prettifyTestMethodName($test->name()); + } + + if (!$isCustomized && $test->usesDataProvider()) { + $result .= $this->prettifyDataSet($test, $colorize); + } + + $this->prettifiedTestCases[$key] = $result; + + return $result; + } + + public function prettifyDataSet(TestCase $test, bool $colorize): string + { + if (!$colorize) { + return $test->dataSetAsString(); + } + + if (is_int($test->dataName())) { + return Color::dim(' with data set ') . Color::colorize('fg-cyan', (string) $test->dataName()); + } + + return Color::dim(' with ') . Color::colorize('fg-cyan', Color::visualizeWhitespace($test->dataName())); + } + + /** + * @return array + */ + private function mapTestMethodParameterNamesToProvidedDataValues(TestCase $test, bool $colorize): array + { + assert(method_exists($test, $test->name())); + + /** @noinspection PhpUnhandledExceptionInspection */ + $reflector = new ReflectionMethod($test::class, $test->name()); + + $providedData = []; + $providedDataValues = array_values($test->providedData()); + $i = 0; + + $providedData['$_dataName'] = $test->dataName(); + + foreach ($reflector->getParameters() as $parameter) { + if (!array_key_exists($i, $providedDataValues) && $parameter->isDefaultValueAvailable()) { + $providedDataValues[$i] = $parameter->getDefaultValue(); + } + + $value = $providedDataValues[$i++] ?? null; + + if (is_object($value)) { + $value = $this->objectToString($value); + } + + if (!is_scalar($value)) { + $value = gettype($value); + + if ($value === 'NULL') { + $value = 'null'; + } + } + + if (is_bool($value) || is_int($value) || is_float($value)) { + $value = Exporter::export($value); + } + + if ($value === '') { + if ($colorize) { + $value = Color::colorize('dim,underlined', 'empty'); + } else { + $value = "''"; + } + } + + $providedData['$' . $parameter->getName()] = str_replace('$', '\\$', $value); + } + + if ($colorize) { + $providedData = array_map( + static fn (mixed $value) => Color::colorize('fg-cyan', Color::visualizeWhitespace((string) $value, true)), + $providedData, + ); + } + + return $providedData; + } + + /** + * @return non-empty-string + */ + private function objectToString(object $value): string + { + $reflector = new ReflectionObject($value); + + if ($reflector->isEnum()) { + $enumReflector = new ReflectionEnum($value); + + if ($enumReflector->isBacked()) { + return (string) $value->value; + } + + return $value->name; + } + + if ($reflector->hasMethod('__toString')) { + return $value->__toString(); + } + + return $value::class; + } + + /** + * @return array{0: string, 1: bool} + */ + private function processTestDox(TestCase $test, TestDox $testDox, bool $colorize): array + { + $placeholdersUsed = false; + + $result = $testDox->text(); + + if (str_contains($result, '$')) { + $annotation = $result; + $providedData = $this->mapTestMethodParameterNamesToProvidedDataValues($test, $colorize); + + $variables = array_map( + static fn (string $variable): string => sprintf( + '/%s(?=\b)/', + preg_quote($variable, '/'), + ), + array_keys($providedData), + ); + + $result = preg_replace($variables, $providedData, $annotation); + + $placeholdersUsed = true; + } + + return [$result, $placeholdersUsed]; + } + + /** + * @return array{0: string, 1: bool} + */ + private function processTestDoxFormatter(TestCase $test, TestDoxFormatter $formatter): array + { + $className = $formatter->className(); + $methodName = $formatter->methodName(); + $formatterIdentifier = $className . '::' . $methodName; + + if (isset($this->erroredFormatters[$formatterIdentifier])) { + return [$this->prettifyTestMethodName($test->name()), false]; + } + + if (!method_exists($className, $methodName)) { + EventFacade::emitter()->testTriggeredPhpunitError( + TestMethodBuilder::fromTestCase($test, false), + sprintf( + 'Method %s::%s() cannot be used as a TestDox formatter because it does not exist', + $className, + $methodName, + ), + ); + + $this->erroredFormatters[$formatterIdentifier] = true; + + return [$this->prettifyTestMethodName($test->name()), false]; + } + + $reflector = new ReflectionMethod($className, $methodName); + + if (!$reflector->isPublic()) { + EventFacade::emitter()->testTriggeredPhpunitError( + TestMethodBuilder::fromTestCase($test, false), + sprintf( + 'Method %s::%s() cannot be used as a TestDox formatter because it is not public', + $className, + $methodName, + ), + ); + + $this->erroredFormatters[$formatterIdentifier] = true; + + return [$this->prettifyTestMethodName($test->name()), false]; + } + + if (!$reflector->isStatic()) { + EventFacade::emitter()->testTriggeredPhpunitError( + TestMethodBuilder::fromTestCase($test, false), + sprintf( + 'Method %s::%s() cannot be used as a TestDox formatter because it is not static', + $className, + $methodName, + ), + ); + + $this->erroredFormatters[$formatterIdentifier] = true; + + return [$this->prettifyTestMethodName($test->name()), false]; + } + + try { + return [$reflector->invokeArgs(null, array_values($test->providedData())), true]; + } catch (Throwable $t) { + EventFacade::emitter()->testTriggeredPhpunitError( + TestMethodBuilder::fromTestCase($test, false), + sprintf( + 'TestDox formatter %s::%s() triggered an error: %s%s%s', + $className, + $methodName, + $t->getMessage(), + PHP_EOL, + Filter::stackTraceFromThrowableAsString($t), + ), + ); + + $this->erroredFormatters[$formatterIdentifier] = true; + + return [$this->prettifyTestMethodName($test->name()), false]; + } + } +} diff --git a/src/Logging/TestDox/PlainTextRenderer.php b/src/Logging/TestDox/PlainTextRenderer.php new file mode 100644 index 00000000000..db591ca9fe1 --- /dev/null +++ b/src/Logging/TestDox/PlainTextRenderer.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PlainTextRenderer +{ + /** + * @param array $tests + */ + public function render(array $tests): string + { + $buffer = ''; + + foreach ($tests as $prettifiedClassName => $_tests) { + $buffer .= $prettifiedClassName . "\n"; + + foreach ($this->reduce($_tests) as $prettifiedMethodName => $outcome) { + $buffer .= sprintf( + ' [%s] %s' . "\n", + $outcome, + $prettifiedMethodName, + ); + } + + $buffer .= "\n"; + } + + return $buffer; + } + + /** + * @return array + */ + private function reduce(TestResultCollection $tests): array + { + $result = []; + + foreach ($tests as $test) { + $prettifiedMethodName = $test->test()->testDox()->prettifiedMethodName(); + + $success = true; + + if ($test->status()->isError() || + $test->status()->isFailure() || + $test->status()->isIncomplete() || + $test->status()->isSkipped()) { + $success = false; + } + + if (!isset($result[$prettifiedMethodName])) { + $result[$prettifiedMethodName] = $success ? 'x' : ' '; + + continue; + } + + if ($success) { + continue; + } + + $result[$prettifiedMethodName] = ' '; + } + + return $result; + } +} diff --git a/src/Logging/TestDox/TestResult/Subscriber/Subscriber.php b/src/Logging/TestDox/TestResult/Subscriber/Subscriber.php new file mode 100644 index 00000000000..41fc465a120 --- /dev/null +++ b/src/Logging/TestDox/TestResult/Subscriber/Subscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Subscriber +{ + private TestResultCollector $collector; + + public function __construct(TestResultCollector $collector) + { + $this->collector = $collector; + } + + protected function collector(): TestResultCollector + { + return $this->collector; + } +} diff --git a/src/Logging/TestDox/TestResult/Subscriber/TestConsideredRiskySubscriber.php b/src/Logging/TestDox/TestResult/Subscriber/TestConsideredRiskySubscriber.php new file mode 100644 index 00000000000..150a486350b --- /dev/null +++ b/src/Logging/TestDox/TestResult/Subscriber/TestConsideredRiskySubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\ConsideredRisky; +use PHPUnit\Event\Test\ConsideredRiskySubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestConsideredRiskySubscriber extends Subscriber implements ConsideredRiskySubscriber +{ + public function notify(ConsideredRisky $event): void + { + $this->collector()->testConsideredRisky($event); + } +} diff --git a/src/Logging/TestDox/TestResult/Subscriber/TestErroredSubscriber.php b/src/Logging/TestDox/TestResult/Subscriber/TestErroredSubscriber.php new file mode 100644 index 00000000000..b210ffa3c32 --- /dev/null +++ b/src/Logging/TestDox/TestResult/Subscriber/TestErroredSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\ErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestErroredSubscriber extends Subscriber implements ErroredSubscriber +{ + public function notify(Errored $event): void + { + $this->collector()->testErrored($event); + } +} diff --git a/src/Logging/TestDox/TestResult/Subscriber/TestFailedSubscriber.php b/src/Logging/TestDox/TestResult/Subscriber/TestFailedSubscriber.php new file mode 100644 index 00000000000..b776227c38b --- /dev/null +++ b/src/Logging/TestDox/TestResult/Subscriber/TestFailedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\FailedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFailedSubscriber extends Subscriber implements FailedSubscriber +{ + public function notify(Failed $event): void + { + $this->collector()->testFailed($event); + } +} diff --git a/src/Logging/TestDox/TestResult/Subscriber/TestFinishedSubscriber.php b/src/Logging/TestDox/TestResult/Subscriber/TestFinishedSubscriber.php new file mode 100644 index 00000000000..14ddea33ea9 --- /dev/null +++ b/src/Logging/TestDox/TestResult/Subscriber/TestFinishedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Finished $event): void + { + $this->collector()->testFinished($event); + } +} diff --git a/src/Logging/TestDox/TestResult/Subscriber/TestMarkedIncompleteSubscriber.php b/src/Logging/TestDox/TestResult/Subscriber/TestMarkedIncompleteSubscriber.php new file mode 100644 index 00000000000..7e21545cb10 --- /dev/null +++ b/src/Logging/TestDox/TestResult/Subscriber/TestMarkedIncompleteSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\MarkedIncompleteSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestMarkedIncompleteSubscriber extends Subscriber implements MarkedIncompleteSubscriber +{ + public function notify(MarkedIncomplete $event): void + { + $this->collector()->testMarkedIncomplete($event); + } +} diff --git a/src/Logging/TestDox/TestResult/Subscriber/TestPassedSubscriber.php b/src/Logging/TestDox/TestResult/Subscriber/TestPassedSubscriber.php new file mode 100644 index 00000000000..1eb1a5777cd --- /dev/null +++ b/src/Logging/TestDox/TestResult/Subscriber/TestPassedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\Passed; +use PHPUnit\Event\Test\PassedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPassedSubscriber extends Subscriber implements PassedSubscriber +{ + public function notify(Passed $event): void + { + $this->collector()->testPassed($event); + } +} diff --git a/src/Logging/TestDox/TestResult/Subscriber/TestPreparedSubscriber.php b/src/Logging/TestDox/TestResult/Subscriber/TestPreparedSubscriber.php new file mode 100644 index 00000000000..cdaddb09a13 --- /dev/null +++ b/src/Logging/TestDox/TestResult/Subscriber/TestPreparedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\PreparedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPreparedSubscriber extends Subscriber implements PreparedSubscriber +{ + public function notify(Prepared $event): void + { + $this->collector()->testPrepared($event); + } +} diff --git a/src/Logging/TestDox/TestResult/Subscriber/TestSkippedSubscriber.php b/src/Logging/TestDox/TestResult/Subscriber/TestSkippedSubscriber.php new file mode 100644 index 00000000000..76d7e3bb094 --- /dev/null +++ b/src/Logging/TestDox/TestResult/Subscriber/TestSkippedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\Skipped; +use PHPUnit\Event\Test\SkippedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSkippedSubscriber extends Subscriber implements SkippedSubscriber +{ + public function notify(Skipped $event): void + { + $this->collector()->testSkipped($event); + } +} diff --git a/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredDeprecationSubscriber.php b/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredDeprecationSubscriber.php new file mode 100644 index 00000000000..8e080296632 --- /dev/null +++ b/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredDeprecationSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\Test\DeprecationTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredDeprecationSubscriber extends Subscriber implements DeprecationTriggeredSubscriber +{ + public function notify(DeprecationTriggered $event): void + { + $this->collector()->testTriggeredDeprecation($event); + } +} diff --git a/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredNoticeSubscriber.php b/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredNoticeSubscriber.php new file mode 100644 index 00000000000..18eff3a9d05 --- /dev/null +++ b/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredNoticeSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\NoticeTriggered; +use PHPUnit\Event\Test\NoticeTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredNoticeSubscriber extends Subscriber implements NoticeTriggeredSubscriber +{ + public function notify(NoticeTriggered $event): void + { + $this->collector()->testTriggeredNotice($event); + } +} diff --git a/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpDeprecationSubscriber.php b/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpDeprecationSubscriber.php new file mode 100644 index 00000000000..082bb3c334a --- /dev/null +++ b/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpDeprecationSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\PhpDeprecationTriggered; +use PHPUnit\Event\Test\PhpDeprecationTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpDeprecationSubscriber extends Subscriber implements PhpDeprecationTriggeredSubscriber +{ + public function notify(PhpDeprecationTriggered $event): void + { + $this->collector()->testTriggeredPhpDeprecation($event); + } +} diff --git a/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpNoticeSubscriber.php b/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpNoticeSubscriber.php new file mode 100644 index 00000000000..b743b64a3f8 --- /dev/null +++ b/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpNoticeSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\PhpNoticeTriggered; +use PHPUnit\Event\Test\PhpNoticeTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpNoticeSubscriber extends Subscriber implements PhpNoticeTriggeredSubscriber +{ + public function notify(PhpNoticeTriggered $event): void + { + $this->collector()->testTriggeredPhpNotice($event); + } +} diff --git a/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpWarningSubscriber.php b/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpWarningSubscriber.php new file mode 100644 index 00000000000..4e9c6ac1aeb --- /dev/null +++ b/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpWarningSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\PhpWarningTriggered; +use PHPUnit\Event\Test\PhpWarningTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpWarningSubscriber extends Subscriber implements PhpWarningTriggeredSubscriber +{ + public function notify(PhpWarningTriggered $event): void + { + $this->collector()->testTriggeredPhpWarning($event); + } +} diff --git a/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpunitDeprecationSubscriber.php b/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpunitDeprecationSubscriber.php new file mode 100644 index 00000000000..4423ff98cfc --- /dev/null +++ b/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpunitDeprecationSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\PhpunitDeprecationTriggered; +use PHPUnit\Event\Test\PhpunitDeprecationTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpunitDeprecationSubscriber extends Subscriber implements PhpunitDeprecationTriggeredSubscriber +{ + public function notify(PhpunitDeprecationTriggered $event): void + { + $this->collector()->testTriggeredPhpunitDeprecation($event); + } +} diff --git a/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpunitErrorSubscriber.php b/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpunitErrorSubscriber.php new file mode 100644 index 00000000000..e4e90f18d91 --- /dev/null +++ b/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpunitErrorSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\PhpunitErrorTriggered; +use PHPUnit\Event\Test\PhpunitErrorTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpunitErrorSubscriber extends Subscriber implements PhpunitErrorTriggeredSubscriber +{ + public function notify(PhpunitErrorTriggered $event): void + { + $this->collector()->testTriggeredPhpunitError($event); + } +} diff --git a/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpunitWarningSubscriber.php b/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpunitWarningSubscriber.php new file mode 100644 index 00000000000..72cb8af22c6 --- /dev/null +++ b/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpunitWarningSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\PhpunitWarningTriggered; +use PHPUnit\Event\Test\PhpunitWarningTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpunitWarningSubscriber extends Subscriber implements PhpunitWarningTriggeredSubscriber +{ + public function notify(PhpunitWarningTriggered $event): void + { + $this->collector()->testTriggeredPhpunitWarning($event); + } +} diff --git a/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredWarningSubscriber.php b/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredWarningSubscriber.php new file mode 100644 index 00000000000..d44f4005c6a --- /dev/null +++ b/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredWarningSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\WarningTriggered; +use PHPUnit\Event\Test\WarningTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredWarningSubscriber extends Subscriber implements WarningTriggeredSubscriber +{ + public function notify(WarningTriggered $event): void + { + $this->collector()->testTriggeredWarning($event); + } +} diff --git a/src/Logging/TestDox/TestResult/TestResult.php b/src/Logging/TestDox/TestResult/TestResult.php new file mode 100644 index 00000000000..2648a0dbcde --- /dev/null +++ b/src/Logging/TestDox/TestResult/TestResult.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Framework\TestStatus\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestResult +{ + private TestMethod $test; + private TestStatus $status; + private ?Throwable $throwable; + + public function __construct(TestMethod $test, TestStatus $status, ?Throwable $throwable) + { + $this->test = $test; + $this->status = $status; + $this->throwable = $throwable; + } + + public function test(): TestMethod + { + return $this->test; + } + + public function status(): TestStatus + { + return $this->status; + } + + /** + * @phpstan-assert-if-true !null $this->throwable + */ + public function hasThrowable(): bool + { + return $this->throwable !== null; + } + + public function throwable(): ?Throwable + { + return $this->throwable; + } +} diff --git a/src/Logging/TestDox/TestResult/TestResultCollection.php b/src/Logging/TestDox/TestResult/TestResultCollection.php new file mode 100644 index 00000000000..5c4a4d64435 --- /dev/null +++ b/src/Logging/TestDox/TestResult/TestResultCollection.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use IteratorAggregate; + +/** + * @template-implements IteratorAggregate + * + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestResultCollection implements IteratorAggregate +{ + /** + * @var list + */ + private array $testResults; + + /** + * @param list $testResults + */ + public static function fromArray(array $testResults): self + { + return new self(...$testResults); + } + + private function __construct(TestResult ...$testResults) + { + $this->testResults = $testResults; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->testResults; + } + + public function getIterator(): TestResultCollectionIterator + { + return new TestResultCollectionIterator($this); + } +} diff --git a/src/Logging/TestDox/TestResult/TestResultCollectionIterator.php b/src/Logging/TestDox/TestResult/TestResultCollectionIterator.php new file mode 100644 index 00000000000..cf8ae7ac8b1 --- /dev/null +++ b/src/Logging/TestDox/TestResult/TestResultCollectionIterator.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use function count; +use Iterator; + +/** + * @template-implements Iterator + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestResultCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $testResults; + private int $position = 0; + + public function __construct(TestResultCollection $testResults) + { + $this->testResults = $testResults->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->testResults); + } + + public function key(): int + { + return $this->position; + } + + public function current(): TestResult + { + return $this->testResults[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/src/Logging/TestDox/TestResult/TestResultCollector.php b/src/Logging/TestDox/TestResult/TestResultCollector.php new file mode 100644 index 00000000000..78bd819a615 --- /dev/null +++ b/src/Logging/TestDox/TestResult/TestResultCollector.php @@ -0,0 +1,383 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use function array_merge; +use function assert; +use function is_subclass_of; +use function ksort; +use function uksort; +use function usort; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Facade; +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\ConsideredRisky; +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\NoticeTriggered; +use PHPUnit\Event\Test\Passed; +use PHPUnit\Event\Test\PhpDeprecationTriggered; +use PHPUnit\Event\Test\PhpNoticeTriggered; +use PHPUnit\Event\Test\PhpunitDeprecationTriggered; +use PHPUnit\Event\Test\PhpunitErrorTriggered; +use PHPUnit\Event\Test\PhpunitWarningTriggered; +use PHPUnit\Event\Test\PhpWarningTriggered; +use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\Skipped; +use PHPUnit\Event\Test\WarningTriggered; +use PHPUnit\Framework\TestStatus\TestStatus; +use PHPUnit\Logging\TestDox\TestResult as TestDoxTestMethod; +use PHPUnit\TestRunner\IssueFilter; +use ReflectionMethod; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestResultCollector +{ + private readonly IssueFilter $issueFilter; + + /** + * @var array> + */ + private array $tests = []; + private ?TestStatus $status = null; + private ?Throwable $throwable = null; + private bool $prepared = false; + + public function __construct(Facade $facade, IssueFilter $issueFilter) + { + $this->issueFilter = $issueFilter; + + $this->registerSubscribers($facade); + } + + /** + * @return array + */ + public function testMethodsGroupedByClass(): array + { + $result = []; + + foreach ($this->tests as $prettifiedClassName => $tests) { + $testsByDeclaringClass = []; + + foreach ($tests as $test) { + $declaringClassName = new ReflectionMethod($test->test()->className(), $test->test()->methodName())->getDeclaringClass()->getName(); + + if (!isset($testsByDeclaringClass[$declaringClassName])) { + $testsByDeclaringClass[$declaringClassName] = []; + } + + $testsByDeclaringClass[$declaringClassName][] = $test; + } + + foreach ($testsByDeclaringClass as $declaringClassName) { + usort( + $declaringClassName, + static function (TestDoxTestMethod $a, TestDoxTestMethod $b): int + { + return $a->test()->line() <=> $b->test()->line(); + }, + ); + } + + uksort( + $testsByDeclaringClass, + /** + * @param class-string $a + * @param class-string $b + */ + static function (string $a, string $b): int + { + if (is_subclass_of($b, $a)) { + return -1; + } + + if (is_subclass_of($a, $b)) { + return 1; + } + + return 0; + }, + ); + + $tests = []; + + foreach ($testsByDeclaringClass as $_tests) { + $tests = array_merge($tests, $_tests); + } + + $result[$prettifiedClassName] = TestResultCollection::fromArray($tests); + } + + ksort($result); + + return $result; + } + + public function testPrepared(Prepared $event): void + { + if (!$event->test()->isTestMethod()) { + return; + } + + $this->status = TestStatus::unknown(); + $this->throwable = null; + $this->prepared = true; + } + + public function testErrored(Errored $event): void + { + if (!$event->test()->isTestMethod()) { + return; + } + + $this->status = TestStatus::error($event->throwable()->message()); + $this->throwable = $event->throwable(); + + if (!$this->prepared) { + $test = $event->test(); + + assert($test instanceof TestMethod); + + $this->process($test); + } + } + + public function testFailed(Failed $event): void + { + if (!$event->test()->isTestMethod()) { + return; + } + + $this->status = TestStatus::failure($event->throwable()->message()); + $this->throwable = $event->throwable(); + } + + public function testPassed(Passed $event): void + { + if (!$event->test()->isTestMethod()) { + return; + } + + $this->updateTestStatus(TestStatus::success()); + } + + public function testSkipped(Skipped $event): void + { + if (!$event->test()->isTestMethod()) { + return; + } + + $this->updateTestStatus(TestStatus::skipped($event->message())); + } + + public function testMarkedIncomplete(MarkedIncomplete $event): void + { + if (!$event->test()->isTestMethod()) { + return; + } + + $this->updateTestStatus(TestStatus::incomplete($event->throwable()->message())); + + $this->throwable = $event->throwable(); + } + + public function testConsideredRisky(ConsideredRisky $event): void + { + if (!$event->test()->isTestMethod()) { + return; + } + + $this->updateTestStatus(TestStatus::risky()); + } + + public function testTriggeredDeprecation(DeprecationTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event, true)) { + return; + } + + if ($event->ignoredByBaseline()) { + return; + } + + $this->updateTestStatus(TestStatus::deprecation()); + } + + public function testTriggeredNotice(NoticeTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event, true)) { + return; + } + + if ($event->ignoredByBaseline()) { + return; + } + + $this->updateTestStatus(TestStatus::notice()); + } + + public function testTriggeredWarning(WarningTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event, true)) { + return; + } + + if ($event->ignoredByBaseline()) { + return; + } + + $this->updateTestStatus(TestStatus::warning()); + } + + public function testTriggeredPhpDeprecation(PhpDeprecationTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event, true)) { + return; + } + + if ($event->ignoredByBaseline()) { + return; + } + + $this->updateTestStatus(TestStatus::deprecation()); + } + + public function testTriggeredPhpNotice(PhpNoticeTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event, true)) { + return; + } + + if ($event->ignoredByBaseline()) { + return; + } + + $this->updateTestStatus(TestStatus::notice()); + } + + public function testTriggeredPhpWarning(PhpWarningTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event, true)) { + return; + } + + if ($event->ignoredByBaseline()) { + return; + } + + $this->updateTestStatus(TestStatus::warning()); + } + + public function testTriggeredPhpunitDeprecation(PhpunitDeprecationTriggered $event): void + { + if (!$event->test()->isTestMethod()) { + return; + } + + $this->updateTestStatus(TestStatus::deprecation()); + } + + public function testTriggeredPhpunitError(PhpunitErrorTriggered $event): void + { + if (!$event->test()->isTestMethod()) { + return; + } + + $this->updateTestStatus(TestStatus::error()); + } + + public function testTriggeredPhpunitWarning(PhpunitWarningTriggered $event): void + { + if (!$event->test()->isTestMethod()) { + return; + } + + if ($event->ignoredByTest()) { + return; + } + + $this->updateTestStatus(TestStatus::warning()); + } + + /** + * @throws InvalidArgumentException + */ + public function testFinished(Finished $event): void + { + if (!$event->test()->isTestMethod()) { + return; + } + + $test = $event->test(); + + assert($test instanceof TestMethod); + + $this->process($test); + + $this->status = null; + $this->throwable = null; + $this->prepared = false; + } + + private function registerSubscribers(Facade $facade): void + { + $facade->registerSubscribers( + new TestConsideredRiskySubscriber($this), + new TestErroredSubscriber($this), + new TestFailedSubscriber($this), + new TestFinishedSubscriber($this), + new TestMarkedIncompleteSubscriber($this), + new TestPassedSubscriber($this), + new TestPreparedSubscriber($this), + new TestSkippedSubscriber($this), + new TestTriggeredDeprecationSubscriber($this), + new TestTriggeredNoticeSubscriber($this), + new TestTriggeredPhpDeprecationSubscriber($this), + new TestTriggeredPhpNoticeSubscriber($this), + new TestTriggeredPhpunitDeprecationSubscriber($this), + new TestTriggeredPhpunitErrorSubscriber($this), + new TestTriggeredPhpunitWarningSubscriber($this), + new TestTriggeredPhpWarningSubscriber($this), + new TestTriggeredWarningSubscriber($this), + ); + } + + private function updateTestStatus(TestStatus $status): void + { + if ($this->status !== null && + $this->status->isMoreImportantThan($status)) { + return; + } + + $this->status = $status; + } + + private function process(TestMethod $test): void + { + if (!isset($this->tests[$test->testDox()->prettifiedClassName()])) { + $this->tests[$test->testDox()->prettifiedClassName()] = []; + } + + $this->tests[$test->testDox()->prettifiedClassName()][] = new TestDoxTestMethod( + $test, + $this->status, + $this->throwable, + ); + } +} diff --git a/src/Metadata/After.php b/src/Metadata/After.php new file mode 100644 index 00000000000..14413639064 --- /dev/null +++ b/src/Metadata/After.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class After extends Metadata +{ + private int $priority; + + /** + * @param int<0, 1> $level + */ + protected function __construct(int $level, int $priority) + { + parent::__construct($level); + + $this->priority = $priority; + } + + public function isAfter(): true + { + return true; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/src/Metadata/AfterClass.php b/src/Metadata/AfterClass.php new file mode 100644 index 00000000000..7dcb96fd7c2 --- /dev/null +++ b/src/Metadata/AfterClass.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterClass extends Metadata +{ + private int $priority; + + /** + * @param int<0, 1> $level + */ + protected function __construct(int $level, int $priority) + { + parent::__construct($level); + + $this->priority = $priority; + } + + public function isAfterClass(): true + { + return true; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/src/Metadata/Api/CodeCoverage.php b/src/Metadata/Api/CodeCoverage.php new file mode 100644 index 00000000000..f276623b70a --- /dev/null +++ b/src/Metadata/Api/CodeCoverage.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Api; + +use function assert; +use PHPUnit\Framework\TestCase; +use PHPUnit\Metadata\CoversClass; +use PHPUnit\Metadata\CoversClassesThatExtendClass; +use PHPUnit\Metadata\CoversClassesThatImplementInterface; +use PHPUnit\Metadata\CoversFunction; +use PHPUnit\Metadata\CoversMethod; +use PHPUnit\Metadata\CoversNamespace; +use PHPUnit\Metadata\CoversTrait; +use PHPUnit\Metadata\Parser\Registry; +use PHPUnit\Metadata\UsesClass; +use PHPUnit\Metadata\UsesClassesThatExtendClass; +use PHPUnit\Metadata\UsesClassesThatImplementInterface; +use PHPUnit\Metadata\UsesFunction; +use PHPUnit\Metadata\UsesMethod; +use PHPUnit\Metadata\UsesNamespace; +use PHPUnit\Metadata\UsesTrait; +use SebastianBergmann\CodeCoverage\Test\Target\Target; +use SebastianBergmann\CodeCoverage\Test\Target\TargetCollection; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CodeCoverage +{ + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function coversTargets(string $className, string $methodName): TargetCollection + { + $targets = []; + + foreach (Registry::parser()->forClassAndMethod($className, $methodName) as $metadata) { + if ($metadata->isCoversNamespace()) { + assert($metadata instanceof CoversNamespace); + + $targets[] = Target::forNamespace($metadata->namespace()); + } + + if ($metadata->isCoversClass()) { + assert($metadata instanceof CoversClass); + + $targets[] = Target::forClass($metadata->className()); + } + + if ($metadata->isCoversClassesThatExtendClass()) { + assert($metadata instanceof CoversClassesThatExtendClass); + + $targets[] = Target::forClassesThatExtendClass($metadata->className()); + } + + if ($metadata->isCoversClassesThatImplementInterface()) { + assert($metadata instanceof CoversClassesThatImplementInterface); + + $targets[] = Target::forClassesThatImplementInterface($metadata->interfaceName()); + } + + if ($metadata->isCoversMethod()) { + assert($metadata instanceof CoversMethod); + + $targets[] = Target::forMethod($metadata->className(), $metadata->methodName()); + } + + if ($metadata->isCoversFunction()) { + assert($metadata instanceof CoversFunction); + + $targets[] = Target::forFunction($metadata->functionName()); + } + + if ($metadata->isCoversTrait()) { + assert($metadata instanceof CoversTrait); + + $targets[] = Target::forTrait($metadata->traitName()); + } + } + + return TargetCollection::fromArray($targets); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function usesTargets(string $className, string $methodName): TargetCollection + { + $targets = []; + + foreach (Registry::parser()->forClassAndMethod($className, $methodName) as $metadata) { + if ($metadata->isUsesNamespace()) { + assert($metadata instanceof UsesNamespace); + + $targets[] = Target::forNamespace($metadata->namespace()); + } + + if ($metadata->isUsesClass()) { + assert($metadata instanceof UsesClass); + + $targets[] = Target::forClass($metadata->className()); + } + + if ($metadata->isUsesClassesThatExtendClass()) { + assert($metadata instanceof UsesClassesThatExtendClass); + + $targets[] = Target::forClassesThatExtendClass($metadata->className()); + } + + if ($metadata->isUsesClassesThatImplementInterface()) { + assert($metadata instanceof UsesClassesThatImplementInterface); + + $targets[] = Target::forClassesThatImplementInterface($metadata->interfaceName()); + } + + if ($metadata->isUsesMethod()) { + assert($metadata instanceof UsesMethod); + + $targets[] = Target::forMethod($metadata->className(), $metadata->methodName()); + } + + if ($metadata->isUsesFunction()) { + assert($metadata instanceof UsesFunction); + + $targets[] = Target::forFunction($metadata->functionName()); + } + + if ($metadata->isUsesTrait()) { + assert($metadata instanceof UsesTrait); + + $targets[] = Target::forTrait($metadata->traitName()); + } + } + + return TargetCollection::fromArray($targets); + } + + public function shouldCodeCoverageBeCollectedFor(TestCase $test): bool + { + $parser = Registry::parser(); + + if ($parser->forClass($test::class)->isCoversNothing()->isNotEmpty()) { + return false; + } + + return true; + } +} diff --git a/src/Metadata/Api/DataProvider.php b/src/Metadata/Api/DataProvider.php new file mode 100644 index 00000000000..345672b8cc4 --- /dev/null +++ b/src/Metadata/Api/DataProvider.php @@ -0,0 +1,361 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Api; + +use function array_key_exists; +use function assert; +use function count; +use function get_debug_type; +use function is_array; +use function is_int; +use function is_iterable; +use function is_string; +use function sprintf; +use PHPUnit\Event; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Framework\InvalidDataProviderException; +use PHPUnit\Framework\TestCase; +use PHPUnit\Metadata\DataProvider as DataProviderMetadata; +use PHPUnit\Metadata\MetadataCollection; +use PHPUnit\Metadata\Parser\Registry as MetadataRegistry; +use PHPUnit\Metadata\TestWith; +use PHPUnit\Util\Test; +use ReflectionMethod; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DataProvider +{ + /** + * @param class-string $className + * @param non-empty-string $methodName + * + * @throws InvalidDataProviderException + * + * @return ?array + */ + public function providedData(string $className, string $methodName): ?array + { + $metadataCollection = MetadataRegistry::parser()->forMethod($className, $methodName); + $dataProvider = $metadataCollection->isDataProvider(); + $testWith = $metadataCollection->isTestWith(); + + if ($dataProvider->isEmpty() && $testWith->isEmpty()) { + return null; + } + + $testMethod = new ReflectionMethod($className, $methodName); + + if ($dataProvider->isNotEmpty()) { + if ($testWith->isNotEmpty()) { + $this->triggerWarningForMixingOfDataProviderAndTestWith($testMethod); + } + + return $this->dataProvidedByMethods($className, $testMethod, $dataProvider); + } + + return $this->dataProvidedByMetadata($testMethod, $testWith); + } + + /** + * @param class-string $testClassName + * + * @throws InvalidDataProviderException + * + * @return array + */ + private function dataProvidedByMethods(string $testClassName, ReflectionMethod $testMethod, MetadataCollection $dataProvider): array + { + $testMethodValueObject = new Event\Code\ClassMethod( + $testClassName, + $testMethod->getName(), + ); + + $methodsCalled = []; + $result = []; + $testMethodNumberOfParameters = $testMethod->getNumberOfParameters(); + $testMethodIsNonVariadic = !$testMethod->isVariadic(); + + foreach ($dataProvider as $_dataProvider) { + assert($_dataProvider instanceof DataProviderMetadata); + + $providerLabel = $_dataProvider->className() . '::' . $_dataProvider->methodName(); + $dataProviderMethod = new Event\Code\ClassMethod($_dataProvider->className(), $_dataProvider->methodName()); + $validateArgumentCount = $testMethodIsNonVariadic && $_dataProvider->validateArgumentCount(); + + Event\Facade::emitter()->dataProviderMethodCalled( + $testMethodValueObject, + $dataProviderMethod, + ); + + $methodsCalled[] = $dataProviderMethod; + + try { + $method = new ReflectionMethod($_dataProvider->className(), $_dataProvider->methodName()); + $className = $_dataProvider->className(); + $methodName = $_dataProvider->methodName(); + + if (Test::isTestMethod($method)) { + Event\Facade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Method %s::%s() used by test method %s::%s() is also a test method', + $_dataProvider->className(), + $_dataProvider->methodName(), + $testMethod->getDeclaringClass()->getName(), + $testMethod->getName(), + ), + ); + } + + if (!$method->isPublic()) { + throw new InvalidDataProviderException( + sprintf( + 'Data Provider method %s::%s() is not public', + $className, + $methodName, + ), + ); + } + + if (!$method->isStatic()) { + throw new InvalidDataProviderException( + sprintf( + 'Data Provider method %s::%s() is not static', + $className, + $methodName, + ), + ); + } + + if ($method->getNumberOfParameters() > 0) { + throw new InvalidDataProviderException( + sprintf( + 'Data Provider method %s::%s() expects an argument', + $className, + $methodName, + ), + ); + } + + /** @phpstan-ignore staticMethod.dynamicName */ + $data = $className::$methodName(); + + if (!is_iterable($data)) { + throw new InvalidDataProviderException( + sprintf( + 'Data Provider method %s::%s() does not return an iterable', + $className, + $methodName, + ), + ); + } + } catch (Throwable $e) { + Event\Facade::emitter()->dataProviderMethodFinished( + $testMethodValueObject, + ...$methodsCalled, + ); + + throw InvalidDataProviderException::forException($e, $providerLabel); + } + + foreach ($data as $key => $value) { + if (!is_int($key) && !is_string($key)) { + Event\Facade::emitter()->dataProviderMethodFinished( + $testMethodValueObject, + ...$methodsCalled, + ); + + throw new InvalidDataProviderException( + sprintf( + 'The key must be an integer or a string, %s given', + get_debug_type($key), + ), + ); + } + + if (!is_array($value)) { + Event\Facade::emitter()->dataProviderMethodFinished( + $testMethodValueObject, + ...$methodsCalled, + ); + + throw new InvalidDataProviderException( + sprintf( + 'Data set %s provided by %s is invalid, expected array but got %s', + $this->formatKey($key), + $providerLabel, + get_debug_type($value), + ), + ); + } + + if ($validateArgumentCount && $testMethodNumberOfParameters < count($value)) { + $this->triggerWarningForArgumentCount( + $testMethod, + $this->formatKey($key), + $providerLabel, + count($value), + $testMethodNumberOfParameters, + ); + } + + if (is_int($key)) { + $result[] = new ProvidedData($providerLabel, $value); + + continue; + } + + if (array_key_exists($key, $result)) { + Event\Facade::emitter()->dataProviderMethodFinished( + $testMethodValueObject, + ...$methodsCalled, + ); + + throw new InvalidDataProviderException( + sprintf( + 'The key "%s" has already been defined by provider %s', + $key, + $result[$key]->label(), + ), + ); + } + + $result[$key] = new ProvidedData($providerLabel, $value); + } + } + + Event\Facade::emitter()->dataProviderMethodFinished( + $testMethodValueObject, + ...$methodsCalled, + ); + + if ($result === []) { + throw new InvalidDataProviderException( + 'Empty data set provided by data provider', + ); + } + + return $result; + } + + /** + * @return array + */ + private function dataProvidedByMetadata(ReflectionMethod $testMethod, MetadataCollection $testWith): array + { + $result = []; + + foreach ($testWith as $i => $_testWith) { + assert($_testWith instanceof TestWith); + + $providerLabel = sprintf('TestWith#%s attribute', $i); + + if ($_testWith->hasName()) { + $key = $_testWith->name(); + + if (array_key_exists($key, $result)) { + throw new InvalidDataProviderException( + sprintf( + 'The key "%s" has already been defined by %s', + $key, + $result[$key]->label(), + ), + ); + } + + $result[$key] = new ProvidedData($providerLabel, $_testWith->data()); + } else { + $result[] = new ProvidedData($providerLabel, $_testWith->data()); + } + } + + $testMethodNumberOfParameters = $testMethod->getNumberOfParameters(); + $testMethodIsNonVariadic = !$testMethod->isVariadic(); + + foreach ($result as $key => $providedData) { + $value = $providedData->value(); + + if (!is_array($value)) { + throw new InvalidDataProviderException( + sprintf( + 'Data set %s provided by %s is invalid, expected array but got %s', + $this->formatKey($key), + $providedData->label(), + get_debug_type($value), + ), + ); + } + + if ($testMethodIsNonVariadic && $testMethodNumberOfParameters < count($value)) { + $this->triggerWarningForArgumentCount( + $testMethod, + $this->formatKey($key), + $providedData->label(), + count($value), + $testMethodNumberOfParameters, + ); + } + } + + return $result; + } + + /** + * @param int|non-empty-string $key + * + * @return non-empty-string + */ + private function formatKey(int|string $key): string + { + return is_int($key) ? '#' . $key : '"' . $key . '"'; + } + + private function triggerWarningForMixingOfDataProviderAndTestWith(ReflectionMethod $method): void + { + Event\Facade::emitter()->testTriggeredPhpunitWarning( + $this->testValueObject($method), + 'Mixing #[DataProvider*] and #[TestWith*] attributes is not supported, only the data provided by #[DataProvider*] will be used', + ); + } + + private function triggerWarningForArgumentCount(ReflectionMethod $method, string $key, string $label, int $numberOfValues, int $testMethodNumberOfParameters): void + { + Event\Facade::emitter()->testTriggeredPhpunitWarning( + $this->testValueObject($method), + sprintf( + 'Data set %s provided by %s has more arguments (%d) than the test method accepts (%d)', + $key, + $label, + $numberOfValues, + $testMethodNumberOfParameters, + ), + ); + } + + private function testValueObject(ReflectionMethod $method): TestMethod + { + return new TestMethod( + $method->getDeclaringClass()->getName(), + $method->getName(), + $method->getFileName(), + $method->getStartLine(), + Event\Code\TestDoxBuilder::fromClassNameAndMethodName( + $method->getDeclaringClass()->getName(), + $method->getName(), + ), + MetadataCollection::fromArray([]), + Event\TestData\TestDataCollection::fromArray([]), + ); + } +} diff --git a/src/Metadata/Api/Dependencies.php b/src/Metadata/Api/Dependencies.php new file mode 100644 index 00000000000..3ba35568fb9 --- /dev/null +++ b/src/Metadata/Api/Dependencies.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Api; + +use function assert; +use PHPUnit\Framework\ExecutionOrderDependency; +use PHPUnit\Metadata\DependsOnClass; +use PHPUnit\Metadata\DependsOnMethod; +use PHPUnit\Metadata\Parser\Registry; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Dependencies +{ + /** + * @param class-string $className + * @param non-empty-string $methodName + * + * @return list + */ + public static function dependencies(string $className, string $methodName): array + { + $dependencies = []; + + foreach (Registry::parser()->forClassAndMethod($className, $methodName)->isDepends() as $metadata) { + if ($metadata->isDependsOnClass()) { + assert($metadata instanceof DependsOnClass); + + $dependencies[] = ExecutionOrderDependency::forClass($metadata); + + continue; + } + + assert($metadata instanceof DependsOnMethod); + + if ($metadata->methodName() === '') { + $dependencies[] = ExecutionOrderDependency::invalid(); + + continue; + } + + $dependencies[] = ExecutionOrderDependency::forMethod($metadata); + } + + return $dependencies; + } +} diff --git a/src/Metadata/Api/Groups.php b/src/Metadata/Api/Groups.php new file mode 100644 index 00000000000..aa2cbf43fd9 --- /dev/null +++ b/src/Metadata/Api/Groups.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Api; + +use function array_flip; +use function array_key_exists; +use function array_unique; +use function assert; +use function strtolower; +use function trim; +use PHPUnit\Framework\TestSize\TestSize; +use PHPUnit\Metadata\CoversClass; +use PHPUnit\Metadata\CoversFunction; +use PHPUnit\Metadata\Group; +use PHPUnit\Metadata\Parser\Registry; +use PHPUnit\Metadata\RequiresPhpExtension; +use PHPUnit\Metadata\UsesClass; +use PHPUnit\Metadata\UsesFunction; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Groups +{ + /** + * @var array> + */ + private static array $groupCache = []; + + /** + * @param class-string $className + * @param non-empty-string $methodName + * + * @return list + */ + public function groups(string $className, string $methodName, bool $includeVirtual = true): array + { + $key = $className . '::' . $methodName . '::' . $includeVirtual; + + if (array_key_exists($key, self::$groupCache)) { + return self::$groupCache[$key]; + } + + $groups = []; + + foreach (Registry::parser()->forClassAndMethod($className, $methodName)->isGroup() as $group) { + assert($group instanceof Group); + + $groups[] = $group->groupName(); + } + + if (!$includeVirtual) { + return self::$groupCache[$key] = array_unique($groups); + } + + foreach (Registry::parser()->forClassAndMethod($className, $methodName) as $metadata) { + if ($metadata->isCoversClass()) { + assert($metadata instanceof CoversClass); + + $groups[] = '__phpunit_covers_' . $this->canonicalizeName($metadata->className()); + + continue; + } + + if ($metadata->isCoversFunction()) { + assert($metadata instanceof CoversFunction); + + $groups[] = '__phpunit_covers_' . $this->canonicalizeName($metadata->functionName()); + + continue; + } + + if ($metadata->isUsesClass()) { + assert($metadata instanceof UsesClass); + + $groups[] = '__phpunit_uses_' . $this->canonicalizeName($metadata->className()); + + continue; + } + + if ($metadata->isUsesFunction()) { + assert($metadata instanceof UsesFunction); + + $groups[] = '__phpunit_uses_' . $this->canonicalizeName($metadata->functionName()); + + continue; + } + + if ($metadata->isRequiresPhpExtension()) { + assert($metadata instanceof RequiresPhpExtension); + + $groups[] = '__phpunit_requires_php_extension' . $this->canonicalizeName($metadata->extension()); + } + } + + return self::$groupCache[$key] = array_unique($groups); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function size(string $className, string $methodName): TestSize + { + $groups = array_flip($this->groups($className, $methodName)); + + if (isset($groups['large'])) { + return TestSize::large(); + } + + if (isset($groups['medium'])) { + return TestSize::medium(); + } + + if (isset($groups['small'])) { + return TestSize::small(); + } + + return TestSize::unknown(); + } + + private function canonicalizeName(string $name): string + { + return strtolower(trim($name, '\\')); + } +} diff --git a/src/Metadata/Api/HookMethods.php b/src/Metadata/Api/HookMethods.php new file mode 100644 index 00000000000..8981459a4f8 --- /dev/null +++ b/src/Metadata/Api/HookMethods.php @@ -0,0 +1,162 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Api; + +use function assert; +use function class_exists; +use function in_array; +use function strtolower; +use PHPUnit\Framework\TestCase; +use PHPUnit\Metadata\After; +use PHPUnit\Metadata\AfterClass; +use PHPUnit\Metadata\Before; +use PHPUnit\Metadata\BeforeClass; +use PHPUnit\Metadata\Parser\Registry; +use PHPUnit\Metadata\PostCondition; +use PHPUnit\Metadata\PreCondition; +use PHPUnit\Runner\HookMethod; +use PHPUnit\Runner\HookMethodCollection; +use PHPUnit\Util\Reflection; +use ReflectionClass; +use ReflectionMethod; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class HookMethods +{ + /** + * @var array + */ + private static array $hookMethods = []; + + /** + * @param class-string $className + * + * @return array{beforeClass: HookMethodCollection, before: HookMethodCollection, preCondition: HookMethodCollection, postCondition: HookMethodCollection, after: HookMethodCollection, afterClass: HookMethodCollection} + */ + public function hookMethods(string $className): array + { + if (!class_exists($className)) { + return self::emptyHookMethodsArray(); + } + + if (isset(self::$hookMethods[$className])) { + return self::$hookMethods[$className]; + } + + self::$hookMethods[$className] = self::emptyHookMethodsArray(); + + foreach (Reflection::methodsDeclaredDirectlyInTestClass(new ReflectionClass($className)) as $method) { + $methodName = $method->getName(); + $metadata = Registry::parser()->forMethod($className, $methodName); + + if ($method->isStatic()) { + if ($metadata->isBeforeClass()->isNotEmpty()) { + $beforeClass = $metadata->isBeforeClass()->asArray()[0]; + assert($beforeClass instanceof BeforeClass); + + self::$hookMethods[$className]['beforeClass']->add( + new HookMethod($methodName, $beforeClass->priority()), + ); + } + + if ($metadata->isAfterClass()->isNotEmpty()) { + $afterClass = $metadata->isAfterClass()->asArray()[0]; + assert($afterClass instanceof AfterClass); + + self::$hookMethods[$className]['afterClass']->add( + new HookMethod($methodName, $afterClass->priority()), + ); + } + } + + if ($metadata->isBefore()->isNotEmpty()) { + $before = $metadata->isBefore()->asArray()[0]; + assert($before instanceof Before); + + self::$hookMethods[$className]['before']->add( + new HookMethod($methodName, $before->priority()), + ); + } + + if ($metadata->isPreCondition()->isNotEmpty()) { + $preCondition = $metadata->isPreCondition()->asArray()[0]; + assert($preCondition instanceof PreCondition); + + self::$hookMethods[$className]['preCondition']->add( + new HookMethod($methodName, $preCondition->priority()), + ); + } + + if ($metadata->isPostCondition()->isNotEmpty()) { + $postCondition = $metadata->isPostCondition()->asArray()[0]; + assert($postCondition instanceof PostCondition); + + self::$hookMethods[$className]['postCondition']->add( + new HookMethod($methodName, $postCondition->priority()), + ); + } + + if ($metadata->isAfter()->isNotEmpty()) { + $after = $metadata->isAfter()->asArray()[0]; + assert($after instanceof After); + + self::$hookMethods[$className]['after']->add( + new HookMethod($methodName, $after->priority()), + ); + } + } + + return self::$hookMethods[$className]; + } + + public function isHookMethod(ReflectionMethod $method): bool + { + $defaultNames = [ + 'setupbeforeclass', + 'setup', + 'assertpreconditions', + 'assertpostconditions', + 'teardown', + 'teardownafterclass', + ]; + + if (in_array(strtolower($method->getName()), $defaultNames, true)) { + return true; + } + + $metadata = Registry::parser()->forMethod($method->getDeclaringClass()->getName(), $method->getName()); + + return $metadata->isBeforeClass()->isNotEmpty() || + $metadata->isBefore()->isNotEmpty() || + $metadata->isPreCondition()->isNotEmpty() || + $metadata->isPostCondition()->isNotEmpty() || + $metadata->isAfter()->isNotEmpty() || + $metadata->isAfterClass()->isNotEmpty(); + } + + /** + * @return array{beforeClass: HookMethodCollection, before: HookMethodCollection, preCondition: HookMethodCollection, postCondition: HookMethodCollection, after: HookMethodCollection, afterClass: HookMethodCollection} + */ + private function emptyHookMethodsArray(): array + { + return [ + 'beforeClass' => HookMethodCollection::defaultBeforeClass(), + 'before' => HookMethodCollection::defaultBefore(), + 'preCondition' => HookMethodCollection::defaultPreCondition(), + 'postCondition' => HookMethodCollection::defaultPostCondition(), + 'after' => HookMethodCollection::defaultAfter(), + 'afterClass' => HookMethodCollection::defaultAfterClass(), + ]; + } +} diff --git a/src/Metadata/Api/ProvidedData.php b/src/Metadata/Api/ProvidedData.php new file mode 100644 index 00000000000..7d21766bc95 --- /dev/null +++ b/src/Metadata/Api/ProvidedData.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Api; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ProvidedData +{ + /** + * @var non-empty-string + */ + private string $label; + private mixed $value; + + /** + * @param non-empty-string $label + */ + public function __construct(string $label, mixed $value) + { + $this->label = $label; + $this->value = $value; + } + + /** + * @return non-empty-string + */ + public function label(): string + { + return $this->label; + } + + public function value(): mixed + { + return $this->value; + } +} diff --git a/src/Metadata/Api/Requirements.php b/src/Metadata/Api/Requirements.php new file mode 100644 index 00000000000..c6870ca7a3a --- /dev/null +++ b/src/Metadata/Api/Requirements.php @@ -0,0 +1,213 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Api; + +use const PHP_OS; +use const PHP_OS_FAMILY; +use const PHP_VERSION; +use function addcslashes; +use function array_column; +use function array_key_exists; +use function assert; +use function extension_loaded; +use function function_exists; +use function in_array; +use function ini_get; +use function method_exists; +use function phpversion; +use function preg_match; +use function sprintf; +use PHPUnit\Metadata\Parser\Registry; +use PHPUnit\Metadata\RequiresEnvironmentVariable; +use PHPUnit\Metadata\RequiresFunction; +use PHPUnit\Metadata\RequiresMethod; +use PHPUnit\Metadata\RequiresOperatingSystem; +use PHPUnit\Metadata\RequiresOperatingSystemFamily; +use PHPUnit\Metadata\RequiresPhp; +use PHPUnit\Metadata\RequiresPhpExtension; +use PHPUnit\Metadata\RequiresPhpunit; +use PHPUnit\Metadata\RequiresPhpunitExtension; +use PHPUnit\Metadata\RequiresSetting; +use PHPUnit\Runner\Version; +use PHPUnit\TextUI\Configuration\Registry as ConfigurationRegistry; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Requirements +{ + /** + * @param class-string $className + * @param non-empty-string $methodName + * + * @return list + */ + public function requirementsNotSatisfiedFor(string $className, string $methodName): array + { + $notSatisfied = []; + + foreach (Registry::parser()->forClassAndMethod($className, $methodName) as $metadata) { + if ($metadata->isRequiresPhp()) { + assert($metadata instanceof RequiresPhp); + + if (!$metadata->versionRequirement()->isSatisfiedBy(PHP_VERSION)) { + $notSatisfied[] = sprintf( + 'PHP %s is required.', + $metadata->versionRequirement()->asString(), + ); + } + } + + if ($metadata->isRequiresPhpExtension()) { + assert($metadata instanceof RequiresPhpExtension); + + $extensionVersion = phpversion($metadata->extension()); + + if ($extensionVersion === false) { + $extensionVersion = ''; + } + + if (!extension_loaded($metadata->extension()) || + ($metadata->hasVersionRequirement() && + !$metadata->versionRequirement()->isSatisfiedBy($extensionVersion))) { + $notSatisfied[] = sprintf( + 'PHP extension %s%s is required.', + $metadata->extension(), + $metadata->hasVersionRequirement() ? (' ' . $metadata->versionRequirement()->asString()) : '', + ); + } + } + + if ($metadata->isRequiresPhpunit()) { + assert($metadata instanceof RequiresPhpunit); + + if (!$metadata->versionRequirement()->isSatisfiedBy(Version::id())) { + $notSatisfied[] = sprintf( + 'PHPUnit %s is required.', + $metadata->versionRequirement()->asString(), + ); + } + } + + if ($metadata->isRequiresPhpunitExtension()) { + assert($metadata instanceof RequiresPhpunitExtension); + + $configuration = ConfigurationRegistry::get(); + + $extensionBootstrappers = array_column($configuration->extensionBootstrappers(), 'className'); + + if ($configuration->noExtensions() || !in_array($metadata->extensionClass(), $extensionBootstrappers, true)) { + $notSatisfied[] = sprintf( + 'PHPUnit extension "%s" is required.', + $metadata->extensionClass(), + ); + } + } + + if ($metadata->isRequiresEnvironmentVariable()) { + assert($metadata instanceof RequiresEnvironmentVariable); + + if (!array_key_exists($metadata->environmentVariableName(), $_ENV) || + $metadata->value() === null && $_ENV[$metadata->environmentVariableName()] === '') { + $notSatisfied[] = sprintf('Environment variable "%s" is required.', $metadata->environmentVariableName()); + + continue; + } + + if ($metadata->value() !== null && $_ENV[$metadata->environmentVariableName()] !== $metadata->value()) { + $notSatisfied[] = sprintf( + 'Environment variable "%s" is required to be "%s".', + $metadata->environmentVariableName(), + $metadata->value(), + ); + } + } + + if ($metadata->isRequiresOperatingSystemFamily()) { + assert($metadata instanceof RequiresOperatingSystemFamily); + + if ($metadata->operatingSystemFamily() !== PHP_OS_FAMILY) { + $notSatisfied[] = sprintf( + 'Operating system %s is required.', + $metadata->operatingSystemFamily(), + ); + } + } + + if ($metadata->isRequiresOperatingSystem()) { + assert($metadata instanceof RequiresOperatingSystem); + + $pattern = sprintf( + '/%s/i', + addcslashes($metadata->operatingSystem(), '/'), + ); + + if (preg_match($pattern, PHP_OS) === 0) { + $notSatisfied[] = sprintf( + 'Operating system %s is required.', + $metadata->operatingSystem(), + ); + } + } + + if ($metadata->isRequiresFunction()) { + assert($metadata instanceof RequiresFunction); + + if (!function_exists($metadata->functionName())) { + $notSatisfied[] = sprintf( + 'Function %s() is required.', + $metadata->functionName(), + ); + } + } + + if ($metadata->isRequiresMethod()) { + assert($metadata instanceof RequiresMethod); + + if (!method_exists($metadata->className(), $metadata->methodName())) { + $notSatisfied[] = sprintf( + 'Method %s::%s() is required.', + $metadata->className(), + $metadata->methodName(), + ); + } + } + + if ($metadata->isRequiresSetting()) { + assert($metadata instanceof RequiresSetting); + + if (ini_get($metadata->setting()) !== $metadata->value()) { + $notSatisfied[] = sprintf( + 'Setting "%s" is required to be "%s".', + $metadata->setting(), + $metadata->value(), + ); + } + } + } + + return $notSatisfied; + } + + public function requiresXdebug(string $className, string $methodName): bool + { + foreach (Registry::parser()->forClassAndMethod($className, $methodName) as $metadata) { + if ($metadata->isRequiresPhpExtension()) { + if ($metadata->extension() === 'xdebug') { + return true; + } + } + } + + return false; + } +} diff --git a/src/Metadata/BackupGlobals.php b/src/Metadata/BackupGlobals.php new file mode 100644 index 00000000000..5d9478443df --- /dev/null +++ b/src/Metadata/BackupGlobals.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BackupGlobals extends Metadata +{ + private bool $enabled; + + /** + * @param int<0, 1> $level + */ + protected function __construct(int $level, bool $enabled) + { + parent::__construct($level); + + $this->enabled = $enabled; + } + + public function isBackupGlobals(): true + { + return true; + } + + public function enabled(): bool + { + return $this->enabled; + } +} diff --git a/src/Metadata/BackupStaticProperties.php b/src/Metadata/BackupStaticProperties.php new file mode 100644 index 00000000000..a07698eeb13 --- /dev/null +++ b/src/Metadata/BackupStaticProperties.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BackupStaticProperties extends Metadata +{ + private bool $enabled; + + /** + * @param int<0, 1> $level + */ + protected function __construct(int $level, bool $enabled) + { + parent::__construct($level); + + $this->enabled = $enabled; + } + + public function isBackupStaticProperties(): true + { + return true; + } + + public function enabled(): bool + { + return $this->enabled; + } +} diff --git a/src/Metadata/Before.php b/src/Metadata/Before.php new file mode 100644 index 00000000000..08372f76ca3 --- /dev/null +++ b/src/Metadata/Before.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Before extends Metadata +{ + private int $priority; + + /** + * @param int<0, 1> $level + */ + protected function __construct(int $level, int $priority) + { + parent::__construct($level); + + $this->priority = $priority; + } + + public function isBefore(): true + { + return true; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/src/Metadata/BeforeClass.php b/src/Metadata/BeforeClass.php new file mode 100644 index 00000000000..c5646320789 --- /dev/null +++ b/src/Metadata/BeforeClass.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeClass extends Metadata +{ + private int $priority; + + /** + * @param int<0, 1> $level + */ + protected function __construct(int $level, int $priority) + { + parent::__construct($level); + + $this->priority = $priority; + } + + public function isBeforeClass(): true + { + return true; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/src/Metadata/CoversClass.php b/src/Metadata/CoversClass.php new file mode 100644 index 00000000000..e573952b639 --- /dev/null +++ b/src/Metadata/CoversClass.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class CoversClass extends Metadata +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param int<0, 1> $level + * @param class-string $className + */ + protected function __construct(int $level, string $className) + { + parent::__construct($level); + + $this->className = $className; + } + + public function isCoversClass(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } +} diff --git a/src/Metadata/CoversClassesThatExtendClass.php b/src/Metadata/CoversClassesThatExtendClass.php new file mode 100644 index 00000000000..7feb40d7098 --- /dev/null +++ b/src/Metadata/CoversClassesThatExtendClass.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class CoversClassesThatExtendClass extends Metadata +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param int<0, 1> $level + * @param class-string $className + */ + protected function __construct(int $level, string $className) + { + parent::__construct($level); + + $this->className = $className; + } + + public function isCoversClassesThatExtendClass(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } +} diff --git a/src/Metadata/CoversClassesThatImplementInterface.php b/src/Metadata/CoversClassesThatImplementInterface.php new file mode 100644 index 00000000000..e980801ecb3 --- /dev/null +++ b/src/Metadata/CoversClassesThatImplementInterface.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class CoversClassesThatImplementInterface extends Metadata +{ + /** + * @var class-string + */ + private string $interfaceName; + + /** + * @param int<0, 1> $level + * @param class-string $interfaceName + */ + protected function __construct(int $level, string $interfaceName) + { + parent::__construct($level); + + $this->interfaceName = $interfaceName; + } + + public function isCoversClassesThatImplementInterface(): true + { + return true; + } + + /** + * @return class-string + */ + public function interfaceName(): string + { + return $this->interfaceName; + } +} diff --git a/src/Metadata/CoversFunction.php b/src/Metadata/CoversFunction.php new file mode 100644 index 00000000000..4e953601f8e --- /dev/null +++ b/src/Metadata/CoversFunction.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class CoversFunction extends Metadata +{ + /** + * @var non-empty-string + */ + private string $functionName; + + /** + * @param int<0, 1> $level + * @param non-empty-string $functionName + */ + protected function __construct(int $level, string $functionName) + { + parent::__construct($level); + + $this->functionName = $functionName; + } + + public function isCoversFunction(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function functionName(): string + { + return $this->functionName; + } +} diff --git a/src/Metadata/CoversMethod.php b/src/Metadata/CoversMethod.php new file mode 100644 index 00000000000..73092ff780e --- /dev/null +++ b/src/Metadata/CoversMethod.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class CoversMethod extends Metadata +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param int<0, 1> $level + * @param class-string $className + * @param non-empty-string $methodName + */ + protected function __construct(int $level, string $className, string $methodName) + { + parent::__construct($level); + + $this->className = $className; + $this->methodName = $methodName; + } + + public function isCoversMethod(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/src/Metadata/CoversNamespace.php b/src/Metadata/CoversNamespace.php new file mode 100644 index 00000000000..ef7452a66a4 --- /dev/null +++ b/src/Metadata/CoversNamespace.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class CoversNamespace extends Metadata +{ + /** + * @var non-empty-string + */ + private string $namespace; + + /** + * @param int<0, 1> $level + * @param non-empty-string $namespace + */ + protected function __construct(int $level, string $namespace) + { + parent::__construct($level); + + $this->namespace = $namespace; + } + + public function isCoversNamespace(): true + { + return true; + } + + /** + * @return class-string + */ + public function namespace(): string + { + return $this->namespace; + } +} diff --git a/src/Metadata/CoversNothing.php b/src/Metadata/CoversNothing.php new file mode 100644 index 00000000000..c81e72765b4 --- /dev/null +++ b/src/Metadata/CoversNothing.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class CoversNothing extends Metadata +{ + public function isCoversNothing(): true + { + return true; + } +} diff --git a/src/Metadata/CoversTrait.php b/src/Metadata/CoversTrait.php new file mode 100644 index 00000000000..2cad9dfb4ab --- /dev/null +++ b/src/Metadata/CoversTrait.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class CoversTrait extends Metadata +{ + /** + * @var trait-string + */ + private string $traitName; + + /** + * @param 0|1 $level + * @param trait-string $traitName + */ + protected function __construct(int $level, string $traitName) + { + parent::__construct($level); + + $this->traitName = $traitName; + } + + public function isCoversTrait(): true + { + return true; + } + + /** + * @return trait-string + */ + public function traitName(): string + { + return $this->traitName; + } +} diff --git a/src/Metadata/DataProvider.php b/src/Metadata/DataProvider.php new file mode 100644 index 00000000000..ca048bbaac6 --- /dev/null +++ b/src/Metadata/DataProvider.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DataProvider extends Metadata +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + private bool $validateArgumentCount; + + /** + * @param int<0, 1> $level + * @param class-string $className + * @param non-empty-string $methodName + */ + protected function __construct(int $level, string $className, string $methodName, bool $validateArgumentCount) + { + parent::__construct($level); + + $this->className = $className; + $this->methodName = $methodName; + $this->validateArgumentCount = $validateArgumentCount; + } + + public function isDataProvider(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } + + public function validateArgumentCount(): bool + { + return $this->validateArgumentCount; + } +} diff --git a/src/Metadata/DependsOnClass.php b/src/Metadata/DependsOnClass.php new file mode 100644 index 00000000000..fbc40fd144e --- /dev/null +++ b/src/Metadata/DependsOnClass.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DependsOnClass extends Metadata +{ + /** + * @var class-string + */ + private string $className; + private bool $deepClone; + private bool $shallowClone; + + /** + * @param int<0, 1> $level + * @param class-string $className + */ + protected function __construct(int $level, string $className, bool $deepClone, bool $shallowClone) + { + parent::__construct($level); + + $this->className = $className; + $this->deepClone = $deepClone; + $this->shallowClone = $shallowClone; + } + + public function isDependsOnClass(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + public function deepClone(): bool + { + return $this->deepClone; + } + + public function shallowClone(): bool + { + return $this->shallowClone; + } +} diff --git a/src/Metadata/DependsOnMethod.php b/src/Metadata/DependsOnMethod.php new file mode 100644 index 00000000000..82056a996f2 --- /dev/null +++ b/src/Metadata/DependsOnMethod.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DependsOnMethod extends Metadata +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + private bool $deepClone; + private bool $shallowClone; + + /** + * @param int<0, 1> $level + * @param class-string $className + * @param non-empty-string $methodName + */ + protected function __construct(int $level, string $className, string $methodName, bool $deepClone, bool $shallowClone) + { + parent::__construct($level); + + $this->className = $className; + $this->methodName = $methodName; + $this->deepClone = $deepClone; + $this->shallowClone = $shallowClone; + } + + public function isDependsOnMethod(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } + + public function deepClone(): bool + { + return $this->deepClone; + } + + public function shallowClone(): bool + { + return $this->shallowClone; + } +} diff --git a/src/Metadata/DisableReturnValueGenerationForTestDoubles.php b/src/Metadata/DisableReturnValueGenerationForTestDoubles.php new file mode 100644 index 00000000000..59cf34e2833 --- /dev/null +++ b/src/Metadata/DisableReturnValueGenerationForTestDoubles.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DisableReturnValueGenerationForTestDoubles extends Metadata +{ + public function isDisableReturnValueGenerationForTestDoubles(): true + { + return true; + } +} diff --git a/src/Metadata/DoesNotPerformAssertions.php b/src/Metadata/DoesNotPerformAssertions.php new file mode 100644 index 00000000000..e2925c8576b --- /dev/null +++ b/src/Metadata/DoesNotPerformAssertions.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DoesNotPerformAssertions extends Metadata +{ + public function isDoesNotPerformAssertions(): true + { + return true; + } +} diff --git a/src/Metadata/Exception/Exception.php b/src/Metadata/Exception/Exception.php new file mode 100644 index 00000000000..5d562f1a2fa --- /dev/null +++ b/src/Metadata/Exception/Exception.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface Exception extends \PHPUnit\Exception +{ +} diff --git a/src/Metadata/Exception/InvalidAttributeException.php b/src/Metadata/Exception/InvalidAttributeException.php new file mode 100644 index 00000000000..9158de32851 --- /dev/null +++ b/src/Metadata/Exception/InvalidAttributeException.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Exception; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidAttributeException extends RuntimeException implements Exception +{ + /** + * @param non-empty-string $attributeName + * @param non-empty-string $target + * @param non-empty-string $file + * @param positive-int $line + * @param non-empty-string $message + */ + public function __construct(string $attributeName, string $target, string $file, int $line, string $message) + { + parent::__construct( + sprintf( + 'Invalid attribute %s for %s in %s:%d%s%s', + $attributeName, + $target, + $file, + $line, + PHP_EOL, + $message, + ), + ); + } +} diff --git a/src/Metadata/Exception/InvalidVersionRequirementException.php b/src/Metadata/Exception/InvalidVersionRequirementException.php new file mode 100644 index 00000000000..359f723c1dc --- /dev/null +++ b/src/Metadata/Exception/InvalidVersionRequirementException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidVersionRequirementException extends RuntimeException implements Exception +{ +} diff --git a/src/Metadata/Exception/NoVersionRequirementException.php b/src/Metadata/Exception/NoVersionRequirementException.php new file mode 100644 index 00000000000..299652cf0ab --- /dev/null +++ b/src/Metadata/Exception/NoVersionRequirementException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class NoVersionRequirementException extends RuntimeException implements Exception +{ +} diff --git a/src/Metadata/ExcludeGlobalVariableFromBackup.php b/src/Metadata/ExcludeGlobalVariableFromBackup.php new file mode 100644 index 00000000000..4f646dbdea7 --- /dev/null +++ b/src/Metadata/ExcludeGlobalVariableFromBackup.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ExcludeGlobalVariableFromBackup extends Metadata +{ + /** + * @var non-empty-string + */ + private string $globalVariableName; + + /** + * @param int<0, 1> $level + * @param non-empty-string $globalVariableName + */ + protected function __construct(int $level, string $globalVariableName) + { + parent::__construct($level); + + $this->globalVariableName = $globalVariableName; + } + + public function isExcludeGlobalVariableFromBackup(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function globalVariableName(): string + { + return $this->globalVariableName; + } +} diff --git a/src/Metadata/ExcludeStaticPropertyFromBackup.php b/src/Metadata/ExcludeStaticPropertyFromBackup.php new file mode 100644 index 00000000000..21725e939dd --- /dev/null +++ b/src/Metadata/ExcludeStaticPropertyFromBackup.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ExcludeStaticPropertyFromBackup extends Metadata +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $propertyName; + + /** + * @param int<0, 1> $level + * @param class-string $className + * @param non-empty-string $propertyName + */ + protected function __construct(int $level, string $className, string $propertyName) + { + parent::__construct($level); + + $this->className = $className; + $this->propertyName = $propertyName; + } + + public function isExcludeStaticPropertyFromBackup(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function propertyName(): string + { + return $this->propertyName; + } +} diff --git a/src/Metadata/Group.php b/src/Metadata/Group.php new file mode 100644 index 00000000000..2ee2784f275 --- /dev/null +++ b/src/Metadata/Group.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Group extends Metadata +{ + /** + * @var non-empty-string + */ + private string $groupName; + + /** + * @param int<0, 1> $level + * @param non-empty-string $groupName + */ + protected function __construct(int $level, string $groupName) + { + parent::__construct($level); + + $this->groupName = $groupName; + } + + public function isGroup(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function groupName(): string + { + return $this->groupName; + } +} diff --git a/src/Metadata/IgnoreDeprecations.php b/src/Metadata/IgnoreDeprecations.php new file mode 100644 index 00000000000..be0f3a37169 --- /dev/null +++ b/src/Metadata/IgnoreDeprecations.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class IgnoreDeprecations extends Metadata +{ + /** @var null|non-empty-string */ + private ?string $messagePattern; + + /** + * @param int<0, 1> $level + * @param null|non-empty-string $messagePattern + */ + protected function __construct(int $level, null|string $messagePattern) + { + parent::__construct($level); + + $this->messagePattern = $messagePattern; + } + + public function isIgnoreDeprecations(): true + { + return true; + } + + /** + * @return null|non-empty-string + */ + public function messagePattern(): ?string + { + return $this->messagePattern; + } +} diff --git a/src/Metadata/IgnorePhpunitDeprecations.php b/src/Metadata/IgnorePhpunitDeprecations.php new file mode 100644 index 00000000000..5abcfa48521 --- /dev/null +++ b/src/Metadata/IgnorePhpunitDeprecations.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class IgnorePhpunitDeprecations extends Metadata +{ + public function isIgnorePhpunitDeprecations(): true + { + return true; + } +} diff --git a/src/Metadata/IgnorePhpunitWarnings.php b/src/Metadata/IgnorePhpunitWarnings.php new file mode 100644 index 00000000000..e5f6ef524c5 --- /dev/null +++ b/src/Metadata/IgnorePhpunitWarnings.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class IgnorePhpunitWarnings extends Metadata +{ + /** @var null|non-empty-string */ + private ?string $messagePattern; + + /** + * @param int<0, 1> $level + * @param null|non-empty-string $messagePattern + */ + protected function __construct(int $level, null|string $messagePattern) + { + parent::__construct($level); + + $this->messagePattern = $messagePattern; + } + + public function isIgnorePhpunitWarnings(): true + { + return true; + } + + /** + * @return null|non-empty-string + */ + public function messagePattern(): ?string + { + return $this->messagePattern; + } +} diff --git a/src/Metadata/Metadata.php b/src/Metadata/Metadata.php new file mode 100644 index 00000000000..888f1e86389 --- /dev/null +++ b/src/Metadata/Metadata.php @@ -0,0 +1,997 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +use PHPUnit\Metadata\Version\Requirement; +use PHPUnit\Runner\Extension\Extension; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Metadata +{ + private const int CLASS_LEVEL = 0; + private const int METHOD_LEVEL = 1; + + /** + * @var int<0, 1> + */ + private int $level; + + public static function after(int $priority): After + { + return new After(self::METHOD_LEVEL, $priority); + } + + public static function afterClass(int $priority): AfterClass + { + return new AfterClass(self::METHOD_LEVEL, $priority); + } + + public static function backupGlobalsOnClass(bool $enabled): BackupGlobals + { + return new BackupGlobals(self::CLASS_LEVEL, $enabled); + } + + public static function backupGlobalsOnMethod(bool $enabled): BackupGlobals + { + return new BackupGlobals(self::METHOD_LEVEL, $enabled); + } + + public static function backupStaticPropertiesOnClass(bool $enabled): BackupStaticProperties + { + return new BackupStaticProperties(self::CLASS_LEVEL, $enabled); + } + + public static function backupStaticPropertiesOnMethod(bool $enabled): BackupStaticProperties + { + return new BackupStaticProperties(self::METHOD_LEVEL, $enabled); + } + + public static function before(int $priority): Before + { + return new Before(self::METHOD_LEVEL, $priority); + } + + public static function beforeClass(int $priority): BeforeClass + { + return new BeforeClass(self::METHOD_LEVEL, $priority); + } + + /** + * @param non-empty-string $namespace + */ + public static function coversNamespace(string $namespace): CoversNamespace + { + return new CoversNamespace(self::CLASS_LEVEL, $namespace); + } + + /** + * @param class-string $className + */ + public static function coversClass(string $className): CoversClass + { + return new CoversClass(self::CLASS_LEVEL, $className); + } + + /** + * @param class-string $className + */ + public static function coversClassesThatExtendClass(string $className): CoversClassesThatExtendClass + { + return new CoversClassesThatExtendClass(self::CLASS_LEVEL, $className); + } + + /** + * @param class-string $interfaceName + */ + public static function coversClassesThatImplementInterface(string $interfaceName): CoversClassesThatImplementInterface + { + return new CoversClassesThatImplementInterface(self::CLASS_LEVEL, $interfaceName); + } + + /** + * @param trait-string $traitName + */ + public static function coversTrait(string $traitName): CoversTrait + { + return new CoversTrait(self::CLASS_LEVEL, $traitName); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public static function coversMethod(string $className, string $methodName): CoversMethod + { + return new CoversMethod(self::CLASS_LEVEL, $className, $methodName); + } + + /** + * @param non-empty-string $functionName + */ + public static function coversFunction(string $functionName): CoversFunction + { + return new CoversFunction(self::CLASS_LEVEL, $functionName); + } + + public static function coversNothingOnClass(): CoversNothing + { + return new CoversNothing(self::CLASS_LEVEL); + } + + public static function coversNothingOnMethod(): CoversNothing + { + return new CoversNothing(self::METHOD_LEVEL); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public static function dataProvider(string $className, string $methodName, bool $validateArgumentCount): DataProvider + { + return new DataProvider(self::METHOD_LEVEL, $className, $methodName, $validateArgumentCount); + } + + /** + * @param class-string $className + */ + public static function dependsOnClass(string $className, bool $deepClone, bool $shallowClone): DependsOnClass + { + return new DependsOnClass(self::METHOD_LEVEL, $className, $deepClone, $shallowClone); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public static function dependsOnMethod(string $className, string $methodName, bool $deepClone, bool $shallowClone): DependsOnMethod + { + return new DependsOnMethod(self::METHOD_LEVEL, $className, $methodName, $deepClone, $shallowClone); + } + + public static function disableReturnValueGenerationForTestDoubles(): DisableReturnValueGenerationForTestDoubles + { + return new DisableReturnValueGenerationForTestDoubles(self::CLASS_LEVEL); + } + + public static function doesNotPerformAssertionsOnClass(): DoesNotPerformAssertions + { + return new DoesNotPerformAssertions(self::CLASS_LEVEL); + } + + public static function doesNotPerformAssertionsOnMethod(): DoesNotPerformAssertions + { + return new DoesNotPerformAssertions(self::METHOD_LEVEL); + } + + /** + * @param non-empty-string $globalVariableName + */ + public static function excludeGlobalVariableFromBackupOnClass(string $globalVariableName): ExcludeGlobalVariableFromBackup + { + return new ExcludeGlobalVariableFromBackup(self::CLASS_LEVEL, $globalVariableName); + } + + /** + * @param non-empty-string $globalVariableName + */ + public static function excludeGlobalVariableFromBackupOnMethod(string $globalVariableName): ExcludeGlobalVariableFromBackup + { + return new ExcludeGlobalVariableFromBackup(self::METHOD_LEVEL, $globalVariableName); + } + + /** + * @param class-string $className + * @param non-empty-string $propertyName + */ + public static function excludeStaticPropertyFromBackupOnClass(string $className, string $propertyName): ExcludeStaticPropertyFromBackup + { + return new ExcludeStaticPropertyFromBackup(self::CLASS_LEVEL, $className, $propertyName); + } + + /** + * @param class-string $className + * @param non-empty-string $propertyName + */ + public static function excludeStaticPropertyFromBackupOnMethod(string $className, string $propertyName): ExcludeStaticPropertyFromBackup + { + return new ExcludeStaticPropertyFromBackup(self::METHOD_LEVEL, $className, $propertyName); + } + + /** + * @param non-empty-string $groupName + */ + public static function groupOnClass(string $groupName): Group + { + return new Group(self::CLASS_LEVEL, $groupName); + } + + /** + * @param non-empty-string $groupName + */ + public static function groupOnMethod(string $groupName): Group + { + return new Group(self::METHOD_LEVEL, $groupName); + } + + /** + * @param null|non-empty-string $messagePattern + */ + public static function ignoreDeprecationsOnClass(?string $messagePattern = null): IgnoreDeprecations + { + return new IgnoreDeprecations(self::CLASS_LEVEL, $messagePattern); + } + + /** + * @param null|non-empty-string $messagePattern + */ + public static function ignoreDeprecationsOnMethod(?string $messagePattern = null): IgnoreDeprecations + { + return new IgnoreDeprecations(self::METHOD_LEVEL, $messagePattern); + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public static function ignorePhpunitDeprecationsOnClass(): IgnorePhpunitDeprecations + { + return new IgnorePhpunitDeprecations(self::CLASS_LEVEL); + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public static function ignorePhpunitDeprecationsOnMethod(): IgnorePhpunitDeprecations + { + return new IgnorePhpunitDeprecations(self::METHOD_LEVEL); + } + + public static function postCondition(int $priority): PostCondition + { + return new PostCondition(self::METHOD_LEVEL, $priority); + } + + public static function preCondition(int $priority): PreCondition + { + return new PreCondition(self::METHOD_LEVEL, $priority); + } + + public static function preserveGlobalStateOnClass(bool $enabled): PreserveGlobalState + { + return new PreserveGlobalState(self::CLASS_LEVEL, $enabled); + } + + public static function preserveGlobalStateOnMethod(bool $enabled): PreserveGlobalState + { + return new PreserveGlobalState(self::METHOD_LEVEL, $enabled); + } + + /** + * @param non-empty-string $functionName + */ + public static function requiresFunctionOnClass(string $functionName): RequiresFunction + { + return new RequiresFunction(self::CLASS_LEVEL, $functionName); + } + + /** + * @param non-empty-string $functionName + */ + public static function requiresFunctionOnMethod(string $functionName): RequiresFunction + { + return new RequiresFunction(self::METHOD_LEVEL, $functionName); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public static function requiresMethodOnClass(string $className, string $methodName): RequiresMethod + { + return new RequiresMethod(self::CLASS_LEVEL, $className, $methodName); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public static function requiresMethodOnMethod(string $className, string $methodName): RequiresMethod + { + return new RequiresMethod(self::METHOD_LEVEL, $className, $methodName); + } + + /** + * @param non-empty-string $operatingSystem + */ + public static function requiresOperatingSystemOnClass(string $operatingSystem): RequiresOperatingSystem + { + return new RequiresOperatingSystem(self::CLASS_LEVEL, $operatingSystem); + } + + /** + * @param non-empty-string $operatingSystem + */ + public static function requiresOperatingSystemOnMethod(string $operatingSystem): RequiresOperatingSystem + { + return new RequiresOperatingSystem(self::METHOD_LEVEL, $operatingSystem); + } + + /** + * @param non-empty-string $operatingSystemFamily + */ + public static function requiresOperatingSystemFamilyOnClass(string $operatingSystemFamily): RequiresOperatingSystemFamily + { + return new RequiresOperatingSystemFamily(self::CLASS_LEVEL, $operatingSystemFamily); + } + + /** + * @param non-empty-string $operatingSystemFamily + */ + public static function requiresOperatingSystemFamilyOnMethod(string $operatingSystemFamily): RequiresOperatingSystemFamily + { + return new RequiresOperatingSystemFamily(self::METHOD_LEVEL, $operatingSystemFamily); + } + + public static function requiresPhpOnClass(Requirement $versionRequirement): RequiresPhp + { + return new RequiresPhp(self::CLASS_LEVEL, $versionRequirement); + } + + public static function requiresPhpOnMethod(Requirement $versionRequirement): RequiresPhp + { + return new RequiresPhp(self::METHOD_LEVEL, $versionRequirement); + } + + /** + * @param non-empty-string $extension + */ + public static function requiresPhpExtensionOnClass(string $extension, ?Requirement $versionRequirement): RequiresPhpExtension + { + return new RequiresPhpExtension(self::CLASS_LEVEL, $extension, $versionRequirement); + } + + /** + * @param non-empty-string $extension + */ + public static function requiresPhpExtensionOnMethod(string $extension, ?Requirement $versionRequirement): RequiresPhpExtension + { + return new RequiresPhpExtension(self::METHOD_LEVEL, $extension, $versionRequirement); + } + + public static function requiresPhpunitOnClass(Requirement $versionRequirement): RequiresPhpunit + { + return new RequiresPhpunit(self::CLASS_LEVEL, $versionRequirement); + } + + public static function requiresPhpunitOnMethod(Requirement $versionRequirement): RequiresPhpunit + { + return new RequiresPhpunit(self::METHOD_LEVEL, $versionRequirement); + } + + /** + * @param class-string $extensionClass + */ + public static function requiresPhpunitExtensionOnClass(string $extensionClass): RequiresPhpunitExtension + { + return new RequiresPhpunitExtension(self::CLASS_LEVEL, $extensionClass); + } + + /** + * @param class-string $extensionClass + */ + public static function requiresPhpunitExtensionOnMethod(string $extensionClass): RequiresPhpunitExtension + { + return new RequiresPhpunitExtension(self::METHOD_LEVEL, $extensionClass); + } + + public static function requiresEnvironmentVariableOnClass(string $environmentVariableName, null|string $value): RequiresEnvironmentVariable + { + return new RequiresEnvironmentVariable(self::CLASS_LEVEL, $environmentVariableName, $value); + } + + public static function requiresEnvironmentVariableOnMethod(string $environmentVariableName, null|string $value): RequiresEnvironmentVariable + { + return new RequiresEnvironmentVariable(self::METHOD_LEVEL, $environmentVariableName, $value); + } + + public static function withEnvironmentVariableOnClass(string $environmentVariableName, null|string $value): WithEnvironmentVariable + { + return new WithEnvironmentVariable(self::CLASS_LEVEL, $environmentVariableName, $value); + } + + public static function withEnvironmentVariableOnMethod(string $environmentVariableName, null|string $value): WithEnvironmentVariable + { + return new WithEnvironmentVariable(self::METHOD_LEVEL, $environmentVariableName, $value); + } + + /** + * @param non-empty-string $setting + * @param non-empty-string $value + */ + public static function requiresSettingOnClass(string $setting, string $value): RequiresSetting + { + return new RequiresSetting(self::CLASS_LEVEL, $setting, $value); + } + + /** + * @param non-empty-string $setting + * @param non-empty-string $value + */ + public static function requiresSettingOnMethod(string $setting, string $value): RequiresSetting + { + return new RequiresSetting(self::METHOD_LEVEL, $setting, $value); + } + + public static function runTestsInSeparateProcesses(): RunTestsInSeparateProcesses + { + return new RunTestsInSeparateProcesses(self::CLASS_LEVEL); + } + + public static function runInSeparateProcess(): RunInSeparateProcess + { + return new RunInSeparateProcess(self::METHOD_LEVEL); + } + + public static function test(): Test + { + return new Test(self::METHOD_LEVEL); + } + + /** + * @param non-empty-string $text + */ + public static function testDoxOnClass(string $text): TestDox + { + return new TestDox(self::CLASS_LEVEL, $text); + } + + /** + * @param non-empty-string $text + */ + public static function testDoxOnMethod(string $text): TestDox + { + return new TestDox(self::METHOD_LEVEL, $text); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public static function testDoxFormatter(string $className, string $methodName): TestDoxFormatter + { + return new TestDoxFormatter(self::METHOD_LEVEL, $className, $methodName); + } + + /** + * @param ?non-empty-string $name + */ + public static function testWith(mixed $data, ?string $name = null): TestWith + { + return new TestWith(self::METHOD_LEVEL, $data, $name); + } + + /** + * @param non-empty-string $namespace + */ + public static function usesNamespace(string $namespace): UsesNamespace + { + return new UsesNamespace(self::CLASS_LEVEL, $namespace); + } + + /** + * @param class-string $className + */ + public static function usesClass(string $className): UsesClass + { + return new UsesClass(self::CLASS_LEVEL, $className); + } + + /** + * @param class-string $className + */ + public static function usesClassesThatExtendClass(string $className): UsesClassesThatExtendClass + { + return new UsesClassesThatExtendClass(self::CLASS_LEVEL, $className); + } + + /** + * @param class-string $interfaceName + */ + public static function usesClassesThatImplementInterface(string $interfaceName): UsesClassesThatImplementInterface + { + return new UsesClassesThatImplementInterface(self::CLASS_LEVEL, $interfaceName); + } + + /** + * @param trait-string $traitName + */ + public static function usesTrait(string $traitName): UsesTrait + { + return new UsesTrait(self::CLASS_LEVEL, $traitName); + } + + /** + * @param non-empty-string $functionName + */ + public static function usesFunction(string $functionName): UsesFunction + { + return new UsesFunction(self::CLASS_LEVEL, $functionName); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public static function usesMethod(string $className, string $methodName): UsesMethod + { + return new UsesMethod(self::CLASS_LEVEL, $className, $methodName); + } + + public static function withoutErrorHandler(): WithoutErrorHandler + { + return new WithoutErrorHandler(self::METHOD_LEVEL); + } + + /** + * @param null|non-empty-string $messagePattern + */ + public static function ignorePhpunitWarnings(?string $messagePattern): IgnorePhpunitWarnings + { + return new IgnorePhpunitWarnings(self::METHOD_LEVEL, $messagePattern); + } + + /** + * @param int<0, 1> $level + */ + protected function __construct(int $level) + { + $this->level = $level; + } + + public function isClassLevel(): bool + { + return $this->level === self::CLASS_LEVEL; + } + + public function isMethodLevel(): bool + { + return $this->level === self::METHOD_LEVEL; + } + + /** + * @phpstan-assert-if-true After $this + */ + public function isAfter(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true AfterClass $this + */ + public function isAfterClass(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true BackupGlobals $this + */ + public function isBackupGlobals(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true BackupStaticProperties $this + */ + public function isBackupStaticProperties(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true BeforeClass $this + */ + public function isBeforeClass(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Before $this + */ + public function isBefore(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true CoversNamespace $this + */ + public function isCoversNamespace(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true CoversClass $this + */ + public function isCoversClass(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true CoversClassesThatExtendClass $this + */ + public function isCoversClassesThatExtendClass(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true CoversClassesThatImplementInterface $this + */ + public function isCoversClassesThatImplementInterface(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true CoversTrait $this + */ + public function isCoversTrait(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true CoversFunction $this + */ + public function isCoversFunction(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true CoversMethod $this + */ + public function isCoversMethod(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true CoversNothing $this + */ + public function isCoversNothing(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true DataProvider $this + */ + public function isDataProvider(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true DependsOnClass $this + */ + public function isDependsOnClass(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true DependsOnMethod $this + */ + public function isDependsOnMethod(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true DisableReturnValueGenerationForTestDoubles $this + */ + public function isDisableReturnValueGenerationForTestDoubles(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true DoesNotPerformAssertions $this + */ + public function isDoesNotPerformAssertions(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true ExcludeGlobalVariableFromBackup $this + */ + public function isExcludeGlobalVariableFromBackup(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true ExcludeStaticPropertyFromBackup $this + */ + public function isExcludeStaticPropertyFromBackup(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Group $this + */ + public function isGroup(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true IgnoreDeprecations $this + */ + public function isIgnoreDeprecations(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true IgnorePhpunitDeprecations $this + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function isIgnorePhpunitDeprecations(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true RunInSeparateProcess $this + */ + public function isRunInSeparateProcess(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true RunTestsInSeparateProcesses $this + */ + public function isRunTestsInSeparateProcesses(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Test $this + */ + public function isTest(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true PreCondition $this + */ + public function isPreCondition(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true PostCondition $this + */ + public function isPostCondition(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true PreserveGlobalState $this + */ + public function isPreserveGlobalState(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true RequiresMethod $this + */ + public function isRequiresMethod(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true RequiresFunction $this + */ + public function isRequiresFunction(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true RequiresOperatingSystem $this + */ + public function isRequiresOperatingSystem(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true RequiresOperatingSystemFamily $this + */ + public function isRequiresOperatingSystemFamily(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true RequiresPhp $this + */ + public function isRequiresPhp(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true RequiresPhpExtension $this + */ + public function isRequiresPhpExtension(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true RequiresPhpunit $this + */ + public function isRequiresPhpunit(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true RequiresPhpunitExtension $this + */ + public function isRequiresPhpunitExtension(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true RequiresEnvironmentVariable $this + */ + public function isRequiresEnvironmentVariable(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true WithEnvironmentVariable $this + */ + public function isWithEnvironmentVariable(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true RequiresSetting $this + */ + public function isRequiresSetting(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true TestDox $this + */ + public function isTestDox(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true TestDoxFormatter $this + */ + public function isTestDoxFormatter(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true TestWith $this + */ + public function isTestWith(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true UsesNamespace $this + */ + public function isUsesNamespace(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true UsesClass $this + */ + public function isUsesClass(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true UsesClassesThatExtendClass $this + */ + public function isUsesClassesThatExtendClass(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true UsesClassesThatImplementInterface $this + */ + public function isUsesClassesThatImplementInterface(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true UsesTrait $this + */ + public function isUsesTrait(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true UsesFunction $this + */ + public function isUsesFunction(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true UsesMethod $this + */ + public function isUsesMethod(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true WithoutErrorHandler $this + */ + public function isWithoutErrorHandler(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true IgnorePhpunitWarnings $this + */ + public function isIgnorePhpunitWarnings(): bool + { + return false; + } +} diff --git a/src/Metadata/MetadataCollection.php b/src/Metadata/MetadataCollection.php new file mode 100644 index 00000000000..ee96dd9f530 --- /dev/null +++ b/src/Metadata/MetadataCollection.php @@ -0,0 +1,653 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +use function array_filter; +use function array_merge; +use function count; +use Countable; +use IteratorAggregate; + +/** + * @template-implements IteratorAggregate + * + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class MetadataCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $metadata; + + /** + * @param list $metadata + */ + public static function fromArray(array $metadata): self + { + return new self(...$metadata); + } + + private function __construct(Metadata ...$metadata) + { + $this->metadata = $metadata; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->metadata; + } + + public function count(): int + { + return count($this->metadata); + } + + /** + * @phpstan-assert-if-true 0 $this->count() + * @phpstan-assert-if-true array{} $this->asArray() + */ + public function isEmpty(): bool + { + return $this->count() === 0; + } + + /** + * @phpstan-assert-if-true positive-int $this->count() + * @phpstan-assert-if-true non-empty-list $this->asArray() + */ + public function isNotEmpty(): bool + { + return $this->count() > 0; + } + + public function getIterator(): MetadataCollectionIterator + { + return new MetadataCollectionIterator($this); + } + + public function mergeWith(self $other): self + { + return new self( + ...array_merge( + $this->asArray(), + $other->asArray(), + ), + ); + } + + public function isClassLevel(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isClassLevel(), + ), + ); + } + + public function isMethodLevel(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isMethodLevel(), + ), + ); + } + + public function isAfter(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isAfter(), + ), + ); + } + + public function isAfterClass(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isAfterClass(), + ), + ); + } + + public function isBackupGlobals(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isBackupGlobals(), + ), + ); + } + + public function isBackupStaticProperties(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isBackupStaticProperties(), + ), + ); + } + + public function isBeforeClass(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isBeforeClass(), + ), + ); + } + + public function isBefore(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isBefore(), + ), + ); + } + + public function isCoversNamespace(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isCoversNamespace(), + ), + ); + } + + public function isCoversClass(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isCoversClass(), + ), + ); + } + + public function isCoversClassesThatExtendClass(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isCoversClassesThatExtendClass(), + ), + ); + } + + public function isCoversClassesThatImplementInterface(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isCoversClassesThatImplementInterface(), + ), + ); + } + + public function isCoversTrait(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isCoversTrait(), + ), + ); + } + + public function isCoversFunction(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isCoversFunction(), + ), + ); + } + + public function isCoversMethod(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isCoversMethod(), + ), + ); + } + + public function isExcludeGlobalVariableFromBackup(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isExcludeGlobalVariableFromBackup(), + ), + ); + } + + public function isExcludeStaticPropertyFromBackup(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isExcludeStaticPropertyFromBackup(), + ), + ); + } + + public function isCoversNothing(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isCoversNothing(), + ), + ); + } + + public function isDataProvider(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isDataProvider(), + ), + ); + } + + public function isDepends(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isDependsOnClass() || $metadata->isDependsOnMethod(), + ), + ); + } + + public function isDependsOnClass(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isDependsOnClass(), + ), + ); + } + + public function isDependsOnMethod(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isDependsOnMethod(), + ), + ); + } + + public function isDisableReturnValueGenerationForTestDoubles(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isDisableReturnValueGenerationForTestDoubles(), + ), + ); + } + + public function isDoesNotPerformAssertions(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isDoesNotPerformAssertions(), + ), + ); + } + + public function isGroup(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isGroup(), + ), + ); + } + + public function isIgnoreDeprecations(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isIgnoreDeprecations(), + ), + ); + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function isIgnorePhpunitDeprecations(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isIgnorePhpunitDeprecations(), + ), + ); + } + + public function isIgnorePhpunitWarnings(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isIgnorePhpunitWarnings(), + ), + ); + } + + public function isRunInSeparateProcess(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isRunInSeparateProcess(), + ), + ); + } + + public function isRunTestsInSeparateProcesses(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isRunTestsInSeparateProcesses(), + ), + ); + } + + public function isTest(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isTest(), + ), + ); + } + + public function isPreCondition(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isPreCondition(), + ), + ); + } + + public function isPostCondition(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isPostCondition(), + ), + ); + } + + public function isPreserveGlobalState(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isPreserveGlobalState(), + ), + ); + } + + public function isRequiresMethod(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isRequiresMethod(), + ), + ); + } + + public function isRequiresFunction(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isRequiresFunction(), + ), + ); + } + + public function isRequiresOperatingSystem(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isRequiresOperatingSystem(), + ), + ); + } + + public function isRequiresOperatingSystemFamily(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isRequiresOperatingSystemFamily(), + ), + ); + } + + public function isRequiresPhp(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isRequiresPhp(), + ), + ); + } + + public function isRequiresPhpExtension(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isRequiresPhpExtension(), + ), + ); + } + + public function isRequiresPhpunit(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isRequiresPhpunit(), + ), + ); + } + + public function isRequiresPhpunitExtension(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isRequiresPhpunitExtension(), + ), + ); + } + + public function isRequiresEnvironmentVariable(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isRequiresEnvironmentVariable(), + ), + ); + } + + public function isWithEnvironmentVariable(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isWithEnvironmentVariable(), + ), + ); + } + + public function isRequiresSetting(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isRequiresSetting(), + ), + ); + } + + public function isTestDox(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isTestDox(), + ), + ); + } + + public function isTestDoxFormatter(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isTestDoxFormatter(), + ), + ); + } + + public function isTestWith(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isTestWith(), + ), + ); + } + + public function isUsesNamespace(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isUsesNamespace(), + ), + ); + } + + public function isUsesClass(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isUsesClass(), + ), + ); + } + + public function isUsesClassesThatExtendClass(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isUsesClassesThatExtendClass(), + ), + ); + } + + public function isUsesClassesThatImplementInterface(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isUsesClassesThatImplementInterface(), + ), + ); + } + + public function isUsesTrait(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isUsesTrait(), + ), + ); + } + + public function isUsesFunction(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isUsesFunction(), + ), + ); + } + + public function isUsesMethod(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isUsesMethod(), + ), + ); + } + + public function isWithoutErrorHandler(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isWithoutErrorHandler(), + ), + ); + } +} diff --git a/src/Metadata/MetadataCollectionIterator.php b/src/Metadata/MetadataCollectionIterator.php new file mode 100644 index 00000000000..bfe398999e3 --- /dev/null +++ b/src/Metadata/MetadataCollectionIterator.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +use function count; +use Iterator; + +/** + * @template-implements Iterator + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class MetadataCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $metadata; + private int $position = 0; + + public function __construct(MetadataCollection $metadata) + { + $this->metadata = $metadata->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->metadata); + } + + public function key(): int + { + return $this->position; + } + + public function current(): Metadata + { + return $this->metadata[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/src/Metadata/Parser/AttributeParser.php b/src/Metadata/Parser/AttributeParser.php new file mode 100644 index 00000000000..4986c64bed0 --- /dev/null +++ b/src/Metadata/Parser/AttributeParser.php @@ -0,0 +1,1005 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Parser; + +use const JSON_THROW_ON_ERROR; +use function assert; +use function class_exists; +use function is_numeric; +use function json_decode; +use function method_exists; +use function sprintf; +use function str_starts_with; +use function strtolower; +use function trim; +use Error; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Framework\Attributes\After; +use PHPUnit\Framework\Attributes\AfterClass; +use PHPUnit\Framework\Attributes\BackupGlobals; +use PHPUnit\Framework\Attributes\BackupStaticProperties; +use PHPUnit\Framework\Attributes\Before; +use PHPUnit\Framework\Attributes\BeforeClass; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversClassesThatExtendClass; +use PHPUnit\Framework\Attributes\CoversClassesThatImplementInterface; +use PHPUnit\Framework\Attributes\CoversFunction; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\CoversNamespace; +use PHPUnit\Framework\Attributes\CoversNothing; +use PHPUnit\Framework\Attributes\CoversTrait; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Depends; +use PHPUnit\Framework\Attributes\DependsExternal; +use PHPUnit\Framework\Attributes\DependsExternalUsingDeepClone; +use PHPUnit\Framework\Attributes\DependsExternalUsingShallowClone; +use PHPUnit\Framework\Attributes\DependsOnClass; +use PHPUnit\Framework\Attributes\DependsOnClassUsingDeepClone; +use PHPUnit\Framework\Attributes\DependsOnClassUsingShallowClone; +use PHPUnit\Framework\Attributes\DependsUsingDeepClone; +use PHPUnit\Framework\Attributes\DependsUsingShallowClone; +use PHPUnit\Framework\Attributes\DisableReturnValueGenerationForTestDoubles; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; +use PHPUnit\Framework\Attributes\ExcludeGlobalVariableFromBackup; +use PHPUnit\Framework\Attributes\ExcludeStaticPropertyFromBackup; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; +use PHPUnit\Framework\Attributes\IgnorePhpunitDeprecations; +use PHPUnit\Framework\Attributes\IgnorePhpunitWarnings; +use PHPUnit\Framework\Attributes\Large; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\Attributes\PostCondition; +use PHPUnit\Framework\Attributes\PreCondition; +use PHPUnit\Framework\Attributes\PreserveGlobalState; +use PHPUnit\Framework\Attributes\RequiresEnvironmentVariable; +use PHPUnit\Framework\Attributes\RequiresFunction; +use PHPUnit\Framework\Attributes\RequiresMethod; +use PHPUnit\Framework\Attributes\RequiresOperatingSystem; +use PHPUnit\Framework\Attributes\RequiresOperatingSystemFamily; +use PHPUnit\Framework\Attributes\RequiresPhp; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; +use PHPUnit\Framework\Attributes\RequiresPhpunit; +use PHPUnit\Framework\Attributes\RequiresPhpunitExtension; +use PHPUnit\Framework\Attributes\RequiresSetting; +use PHPUnit\Framework\Attributes\RunInSeparateProcess; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\Attributes\TestDoxFormatter; +use PHPUnit\Framework\Attributes\TestDoxFormatterExternal; +use PHPUnit\Framework\Attributes\TestWith; +use PHPUnit\Framework\Attributes\TestWithJson; +use PHPUnit\Framework\Attributes\Ticket; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\Attributes\UsesClassesThatExtendClass; +use PHPUnit\Framework\Attributes\UsesClassesThatImplementInterface; +use PHPUnit\Framework\Attributes\UsesFunction; +use PHPUnit\Framework\Attributes\UsesMethod; +use PHPUnit\Framework\Attributes\UsesNamespace; +use PHPUnit\Framework\Attributes\UsesTrait; +use PHPUnit\Framework\Attributes\WithEnvironmentVariable; +use PHPUnit\Framework\Attributes\WithoutErrorHandler; +use PHPUnit\Metadata\InvalidAttributeException; +use PHPUnit\Metadata\Metadata; +use PHPUnit\Metadata\MetadataCollection; +use PHPUnit\Metadata\Version\Requirement; +use ReflectionClass; +use ReflectionMethod; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AttributeParser implements Parser +{ + /** + * @param class-string $className + */ + public function forClass(string $className): MetadataCollection + { + assert(class_exists($className)); + + $reflector = new ReflectionClass($className); + $result = []; + + $small = false; + $medium = false; + $large = false; + + foreach ($reflector->getAttributes() as $attribute) { + if (!str_starts_with($attribute->getName(), 'PHPUnit\\Framework\\Attributes\\')) { + continue; + } + + if (!class_exists($attribute->getName())) { + continue; + } + + try { + $attributeInstance = $attribute->newInstance(); + } catch (Error $e) { + throw new InvalidAttributeException( + $attribute->getName(), + 'class ' . $className, + $reflector->getFileName(), + $reflector->getStartLine(), + $e->getMessage(), + ); + } + + switch ($attribute->getName()) { + case BackupGlobals::class: + assert($attributeInstance instanceof BackupGlobals); + + $result[] = Metadata::backupGlobalsOnClass($attributeInstance->enabled()); + + break; + + case BackupStaticProperties::class: + assert($attributeInstance instanceof BackupStaticProperties); + + $result[] = Metadata::backupStaticPropertiesOnClass($attributeInstance->enabled()); + + break; + + case CoversNamespace::class: + assert($attributeInstance instanceof CoversNamespace); + + $result[] = Metadata::coversNamespace($attributeInstance->namespace()); + + break; + + case CoversClass::class: + assert($attributeInstance instanceof CoversClass); + + $result[] = Metadata::coversClass($attributeInstance->className()); + + break; + + case CoversClassesThatExtendClass::class: + assert($attributeInstance instanceof CoversClassesThatExtendClass); + + $result[] = Metadata::coversClassesThatExtendClass($attributeInstance->className()); + + break; + + case CoversClassesThatImplementInterface::class: + assert($attributeInstance instanceof CoversClassesThatImplementInterface); + + $result[] = Metadata::coversClassesThatImplementInterface($attributeInstance->interfaceName()); + + break; + + case CoversTrait::class: + assert($attributeInstance instanceof CoversTrait); + + $result[] = Metadata::coversTrait($attributeInstance->traitName()); + + break; + + case CoversFunction::class: + assert($attributeInstance instanceof CoversFunction); + + $result[] = Metadata::coversFunction($attributeInstance->functionName()); + + break; + + case CoversMethod::class: + assert($attributeInstance instanceof CoversMethod); + + $result[] = Metadata::coversMethod( + $attributeInstance->className(), + $attributeInstance->methodName(), + ); + + break; + + case CoversNothing::class: + $result[] = Metadata::coversNothingOnClass(); + + break; + + case DisableReturnValueGenerationForTestDoubles::class: + $result[] = Metadata::disableReturnValueGenerationForTestDoubles(); + + break; + + case DoesNotPerformAssertions::class: + $result[] = Metadata::doesNotPerformAssertionsOnClass(); + + break; + + case ExcludeGlobalVariableFromBackup::class: + assert($attributeInstance instanceof ExcludeGlobalVariableFromBackup); + + $result[] = Metadata::excludeGlobalVariableFromBackupOnClass($attributeInstance->globalVariableName()); + + break; + + case ExcludeStaticPropertyFromBackup::class: + assert($attributeInstance instanceof ExcludeStaticPropertyFromBackup); + + $result[] = Metadata::excludeStaticPropertyFromBackupOnClass( + $attributeInstance->className(), + $attributeInstance->propertyName(), + ); + + break; + + case Group::class: + assert($attributeInstance instanceof Group); + + if (!$this->isSizeGroup($attributeInstance->name(), $className)) { + $result[] = Metadata::groupOnClass($attributeInstance->name()); + } + + break; + + case Small::class: + if (!$medium && !$large) { + $result[] = Metadata::groupOnClass('small'); + + $small = true; + } else { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + '#[Small] cannot be combined with #[Medium] or #[Large] for %s', + $this->testAsString($className), + ), + ); + } + + break; + + case Medium::class: + if (!$small && !$large) { + $result[] = Metadata::groupOnClass('medium'); + + $medium = true; + } else { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + '#[Medium] cannot be combined with #[Small] or #[Large] for %s', + $this->testAsString($className), + ), + ); + } + + break; + + case Large::class: + if (!$small && !$medium) { + $result[] = Metadata::groupOnClass('large'); + + $large = true; + } else { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + '#[Large] cannot be combined with #[Small] or #[Medium] for %s', + $this->testAsString($className), + ), + ); + } + + break; + + case IgnoreDeprecations::class: + assert($attributeInstance instanceof IgnoreDeprecations); + + $result[] = Metadata::ignoreDeprecationsOnClass($attributeInstance->messagePattern()); + + break; + + case IgnorePhpunitDeprecations::class: + assert($attributeInstance instanceof IgnorePhpunitDeprecations); + + $result[] = Metadata::ignorePhpunitDeprecationsOnClass(); + + break; + + case PreserveGlobalState::class: + assert($attributeInstance instanceof PreserveGlobalState); + + $result[] = Metadata::preserveGlobalStateOnClass($attributeInstance->enabled()); + + break; + + case RequiresMethod::class: + assert($attributeInstance instanceof RequiresMethod); + + $result[] = Metadata::requiresMethodOnClass( + $attributeInstance->className(), + $attributeInstance->methodName(), + ); + + break; + + case RequiresFunction::class: + assert($attributeInstance instanceof RequiresFunction); + + $result[] = Metadata::requiresFunctionOnClass($attributeInstance->functionName()); + + break; + + case RequiresOperatingSystem::class: + assert($attributeInstance instanceof RequiresOperatingSystem); + + $result[] = Metadata::requiresOperatingSystemOnClass($attributeInstance->regularExpression()); + + break; + + case RequiresOperatingSystemFamily::class: + assert($attributeInstance instanceof RequiresOperatingSystemFamily); + + $result[] = Metadata::requiresOperatingSystemFamilyOnClass($attributeInstance->operatingSystemFamily()); + + break; + + case RequiresPhp::class: + assert($attributeInstance instanceof RequiresPhp); + + $requirement = $this->requirement( + $attributeInstance->versionRequirement(), + $className, + ); + + if ($requirement !== null) { + $result[] = Metadata::requiresPhpOnClass($requirement); + } + + break; + + case RequiresPhpExtension::class: + assert($attributeInstance instanceof RequiresPhpExtension); + + $versionConstraint = null; + $versionRequirement = $attributeInstance->versionRequirement(); + + if ($versionRequirement !== null) { + $versionConstraint = $this->requirement( + $versionRequirement, + $className, + ); + } + + $result[] = Metadata::requiresPhpExtensionOnClass( + $attributeInstance->extension(), + $versionConstraint, + ); + + break; + + case RequiresPhpunit::class: + assert($attributeInstance instanceof RequiresPhpunit); + + $requirement = $this->requirement( + $attributeInstance->versionRequirement(), + $className, + ); + + if ($requirement !== null) { + $result[] = Metadata::requiresPhpunitOnClass($requirement); + } + + break; + + case RequiresPhpunitExtension::class: + assert($attributeInstance instanceof RequiresPhpunitExtension); + + $result[] = Metadata::requiresPhpunitExtensionOnClass( + $attributeInstance->extensionClass(), + ); + + break; + + case RequiresEnvironmentVariable::class: + assert($attributeInstance instanceof RequiresEnvironmentVariable); + + $result[] = Metadata::requiresEnvironmentVariableOnClass( + $attributeInstance->environmentVariableName(), + $attributeInstance->value(), + ); + + break; + + case WithEnvironmentVariable::class: + assert($attributeInstance instanceof WithEnvironmentVariable); + + $result[] = Metadata::withEnvironmentVariableOnClass( + $attributeInstance->environmentVariableName(), + $attributeInstance->value(), + ); + + break; + + case RequiresSetting::class: + assert($attributeInstance instanceof RequiresSetting); + + $result[] = Metadata::requiresSettingOnClass( + $attributeInstance->setting(), + $attributeInstance->value(), + ); + + break; + + case RunTestsInSeparateProcesses::class: + $result[] = Metadata::runTestsInSeparateProcesses(); + + break; + + case TestDox::class: + assert($attributeInstance instanceof TestDox); + + $result[] = Metadata::testDoxOnClass($attributeInstance->text()); + + break; + + case Ticket::class: + assert($attributeInstance instanceof Ticket); + + $result[] = Metadata::groupOnClass($attributeInstance->text()); + + break; + + case UsesNamespace::class: + assert($attributeInstance instanceof UsesNamespace); + + $result[] = Metadata::usesNamespace($attributeInstance->namespace()); + + break; + + case UsesClass::class: + assert($attributeInstance instanceof UsesClass); + + $result[] = Metadata::usesClass($attributeInstance->className()); + + break; + + case UsesClassesThatExtendClass::class: + assert($attributeInstance instanceof UsesClassesThatExtendClass); + + $result[] = Metadata::usesClassesThatExtendClass($attributeInstance->className()); + + break; + + case UsesClassesThatImplementInterface::class: + assert($attributeInstance instanceof UsesClassesThatImplementInterface); + + $result[] = Metadata::usesClassesThatImplementInterface($attributeInstance->interfaceName()); + + break; + + case UsesTrait::class: + assert($attributeInstance instanceof UsesTrait); + + $result[] = Metadata::usesTrait($attributeInstance->traitName()); + + break; + + case UsesFunction::class: + assert($attributeInstance instanceof UsesFunction); + + $result[] = Metadata::usesFunction($attributeInstance->functionName()); + + break; + + case UsesMethod::class: + assert($attributeInstance instanceof UsesMethod); + + $result[] = Metadata::usesMethod( + $attributeInstance->className(), + $attributeInstance->methodName(), + ); + + break; + } + } + + return MetadataCollection::fromArray($result); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function forMethod(string $className, string $methodName): MetadataCollection + { + assert(class_exists($className)); + assert(method_exists($className, $methodName)); + + $reflector = new ReflectionMethod($className, $methodName); + $result = []; + + foreach ($reflector->getAttributes() as $attribute) { + if (!str_starts_with($attribute->getName(), 'PHPUnit\\Framework\\Attributes\\')) { + continue; + } + + if (!class_exists($attribute->getName())) { + continue; + } + + try { + $attributeInstance = $attribute->newInstance(); + } catch (Error $e) { + throw new InvalidAttributeException( + $attribute->getName(), + 'method ' . $className . '::' . $methodName . '()', + $reflector->getFileName(), + $reflector->getStartLine(), + $e->getMessage(), + ); + } + + switch ($attribute->getName()) { + case After::class: + assert($attributeInstance instanceof After); + + $result[] = Metadata::after($attributeInstance->priority()); + + break; + + case AfterClass::class: + assert($attributeInstance instanceof AfterClass); + + $result[] = Metadata::afterClass($attributeInstance->priority()); + + break; + + case BackupGlobals::class: + assert($attributeInstance instanceof BackupGlobals); + + $result[] = Metadata::backupGlobalsOnMethod($attributeInstance->enabled()); + + break; + + case BackupStaticProperties::class: + assert($attributeInstance instanceof BackupStaticProperties); + + $result[] = Metadata::backupStaticPropertiesOnMethod($attributeInstance->enabled()); + + break; + + case Before::class: + assert($attributeInstance instanceof Before); + + $result[] = Metadata::before($attributeInstance->priority()); + + break; + + case BeforeClass::class: + assert($attributeInstance instanceof BeforeClass); + + $result[] = Metadata::beforeClass($attributeInstance->priority()); + + break; + + case CoversNothing::class: + $result[] = Metadata::coversNothingOnMethod(); + + break; + + case DataProvider::class: + assert($attributeInstance instanceof DataProvider); + + $result[] = Metadata::dataProvider($className, $attributeInstance->methodName(), $attributeInstance->validateArgumentCount()); + + break; + + case DataProviderExternal::class: + assert($attributeInstance instanceof DataProviderExternal); + + $result[] = Metadata::dataProvider($attributeInstance->className(), $attributeInstance->methodName(), $attributeInstance->validateArgumentCount()); + + break; + + case Depends::class: + assert($attributeInstance instanceof Depends); + + $result[] = Metadata::dependsOnMethod($className, $attributeInstance->methodName(), false, false); + + break; + + case DependsUsingDeepClone::class: + assert($attributeInstance instanceof DependsUsingDeepClone); + + $result[] = Metadata::dependsOnMethod($className, $attributeInstance->methodName(), true, false); + + break; + + case DependsUsingShallowClone::class: + assert($attributeInstance instanceof DependsUsingShallowClone); + + $result[] = Metadata::dependsOnMethod($className, $attributeInstance->methodName(), false, true); + + break; + + case DependsExternal::class: + assert($attributeInstance instanceof DependsExternal); + + $result[] = Metadata::dependsOnMethod($attributeInstance->className(), $attributeInstance->methodName(), false, false); + + break; + + case DependsExternalUsingDeepClone::class: + assert($attributeInstance instanceof DependsExternalUsingDeepClone); + + $result[] = Metadata::dependsOnMethod($attributeInstance->className(), $attributeInstance->methodName(), true, false); + + break; + + case DependsExternalUsingShallowClone::class: + assert($attributeInstance instanceof DependsExternalUsingShallowClone); + + $result[] = Metadata::dependsOnMethod($attributeInstance->className(), $attributeInstance->methodName(), false, true); + + break; + + case DependsOnClass::class: + assert($attributeInstance instanceof DependsOnClass); + + $result[] = Metadata::dependsOnClass($attributeInstance->className(), false, false); + + break; + + case DependsOnClassUsingDeepClone::class: + assert($attributeInstance instanceof DependsOnClassUsingDeepClone); + + $result[] = Metadata::dependsOnClass($attributeInstance->className(), true, false); + + break; + + case DependsOnClassUsingShallowClone::class: + assert($attributeInstance instanceof DependsOnClassUsingShallowClone); + + $result[] = Metadata::dependsOnClass($attributeInstance->className(), false, true); + + break; + + case DoesNotPerformAssertions::class: + assert($attributeInstance instanceof DoesNotPerformAssertions); + + $result[] = Metadata::doesNotPerformAssertionsOnMethod(); + + break; + + case ExcludeGlobalVariableFromBackup::class: + assert($attributeInstance instanceof ExcludeGlobalVariableFromBackup); + + $result[] = Metadata::excludeGlobalVariableFromBackupOnMethod($attributeInstance->globalVariableName()); + + break; + + case ExcludeStaticPropertyFromBackup::class: + assert($attributeInstance instanceof ExcludeStaticPropertyFromBackup); + + $result[] = Metadata::excludeStaticPropertyFromBackupOnMethod( + $attributeInstance->className(), + $attributeInstance->propertyName(), + ); + + break; + + case Group::class: + assert($attributeInstance instanceof Group); + + if (!$this->isSizeGroup($attributeInstance->name(), $className, $methodName)) { + $result[] = Metadata::groupOnMethod($attributeInstance->name()); + } + + break; + + case IgnoreDeprecations::class: + assert($attributeInstance instanceof IgnoreDeprecations); + + $result[] = Metadata::ignoreDeprecationsOnMethod($attributeInstance->messagePattern()); + + break; + + case IgnorePhpunitDeprecations::class: + assert($attributeInstance instanceof IgnorePhpunitDeprecations); + + $result[] = Metadata::ignorePhpunitDeprecationsOnMethod(); + + break; + + case PostCondition::class: + assert($attributeInstance instanceof PostCondition); + + $result[] = Metadata::postCondition($attributeInstance->priority()); + + break; + + case PreCondition::class: + assert($attributeInstance instanceof PreCondition); + + $result[] = Metadata::preCondition($attributeInstance->priority()); + + break; + + case PreserveGlobalState::class: + assert($attributeInstance instanceof PreserveGlobalState); + + $result[] = Metadata::preserveGlobalStateOnMethod($attributeInstance->enabled()); + + break; + + case RequiresMethod::class: + assert($attributeInstance instanceof RequiresMethod); + + $result[] = Metadata::requiresMethodOnMethod( + $attributeInstance->className(), + $attributeInstance->methodName(), + ); + + break; + + case RequiresFunction::class: + assert($attributeInstance instanceof RequiresFunction); + + $result[] = Metadata::requiresFunctionOnMethod($attributeInstance->functionName()); + + break; + + case RequiresOperatingSystem::class: + assert($attributeInstance instanceof RequiresOperatingSystem); + + $result[] = Metadata::requiresOperatingSystemOnMethod($attributeInstance->regularExpression()); + + break; + + case RequiresOperatingSystemFamily::class: + assert($attributeInstance instanceof RequiresOperatingSystemFamily); + + $result[] = Metadata::requiresOperatingSystemFamilyOnMethod($attributeInstance->operatingSystemFamily()); + + break; + + case RequiresPhp::class: + assert($attributeInstance instanceof RequiresPhp); + + $requirement = $this->requirement( + $attributeInstance->versionRequirement(), + $className, + $methodName, + ); + + if ($requirement !== null) { + $result[] = Metadata::requiresPhpOnMethod($requirement); + } + + break; + + case RequiresPhpExtension::class: + assert($attributeInstance instanceof RequiresPhpExtension); + + $versionConstraint = null; + $versionRequirement = $attributeInstance->versionRequirement(); + + if ($versionRequirement !== null) { + $versionConstraint = $this->requirement( + $versionRequirement, + $className, + $methodName, + ); + } + + $result[] = Metadata::requiresPhpExtensionOnMethod( + $attributeInstance->extension(), + $versionConstraint, + ); + + break; + + case RequiresPhpunit::class: + assert($attributeInstance instanceof RequiresPhpunit); + + $requirement = $this->requirement( + $attributeInstance->versionRequirement(), + $className, + $methodName, + ); + + if ($requirement !== null) { + $result[] = Metadata::requiresPhpunitOnMethod($requirement); + } + + break; + + case RequiresPhpunitExtension::class: + assert($attributeInstance instanceof RequiresPhpunitExtension); + + $result[] = Metadata::requiresPhpunitExtensionOnMethod( + $attributeInstance->extensionClass(), + ); + + break; + + case RequiresEnvironmentVariable::class: + assert($attributeInstance instanceof RequiresEnvironmentVariable); + + $result[] = Metadata::requiresEnvironmentVariableOnMethod( + $attributeInstance->environmentVariableName(), + $attributeInstance->value(), + ); + + break; + + case WithEnvironmentVariable::class: + assert($attributeInstance instanceof WithEnvironmentVariable); + + $result[] = Metadata::withEnvironmentVariableOnMethod( + $attributeInstance->environmentVariableName(), + $attributeInstance->value(), + ); + + break; + + case RequiresSetting::class: + assert($attributeInstance instanceof RequiresSetting); + + $result[] = Metadata::requiresSettingOnMethod( + $attributeInstance->setting(), + $attributeInstance->value(), + ); + + break; + + case RunInSeparateProcess::class: + $result[] = Metadata::runInSeparateProcess(); + + break; + + case Test::class: + $result[] = Metadata::test(); + + break; + + case TestDox::class: + assert($attributeInstance instanceof TestDox); + + $result[] = Metadata::testDoxOnMethod($attributeInstance->text()); + + break; + + case TestDoxFormatter::class: + assert($attributeInstance instanceof TestDoxFormatter); + + $result[] = Metadata::testDoxFormatter($className, $attributeInstance->methodName()); + + break; + + case TestDoxFormatterExternal::class: + assert($attributeInstance instanceof TestDoxFormatterExternal); + + $result[] = Metadata::testDoxFormatter($attributeInstance->className(), $attributeInstance->methodName()); + + break; + + case TestWith::class: + assert($attributeInstance instanceof TestWith); + + $result[] = Metadata::testWith($attributeInstance->data(), $attributeInstance->name()); + + break; + + case TestWithJson::class: + assert($attributeInstance instanceof TestWithJson); + + $result[] = Metadata::testWith( + json_decode($attributeInstance->json(), true, 512, JSON_THROW_ON_ERROR), + $attributeInstance->name(), + ); + + break; + + case Ticket::class: + assert($attributeInstance instanceof Ticket); + + $result[] = Metadata::groupOnMethod($attributeInstance->text()); + + break; + + case WithoutErrorHandler::class: + assert($attributeInstance instanceof WithoutErrorHandler); + + $result[] = Metadata::withoutErrorHandler(); + + break; + + case IgnorePhpunitWarnings::class: + assert($attributeInstance instanceof IgnorePhpunitWarnings); + + $result[] = Metadata::ignorePhpunitWarnings($attributeInstance->messagePattern()); + + break; + } + } + + return MetadataCollection::fromArray($result); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function forClassAndMethod(string $className, string $methodName): MetadataCollection + { + return $this->forClass($className)->mergeWith( + $this->forMethod($className, $methodName), + ); + } + + /** + * @param non-empty-string $groupName + * @param class-string $testClassName + * @param ?non-empty-string $testMethodName + */ + private function isSizeGroup(string $groupName, string $testClassName, ?string $testMethodName = null): bool + { + $_groupName = strtolower(trim($groupName)); + + if ($_groupName !== 'small' && $_groupName !== 'medium' && $_groupName !== 'large') { + return false; + } + + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Group name "%s" is not allowed for %s', + $_groupName, + $this->testAsString($testClassName, $testMethodName), + ), + ); + + return true; + } + + /** + * @param non-empty-string $versionRequirement + * @param class-string $testClassName + * @param ?non-empty-string $testMethodName + */ + private function requirement(string $versionRequirement, string $testClassName, ?string $testMethodName = null): ?Requirement + { + if (is_numeric(trim($versionRequirement))) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Test %s has attribute with version constraint string argument without explicit version comparison operator ("%s"), version constraint is ignored', + $this->testAsString($testClassName, $testMethodName), + $versionRequirement, + ), + ); + + return null; + } + + return Requirement::from($versionRequirement); + } + + /** + * @param class-string $testClassName + * @param ?non-empty-string $testMethodName + * + * @return non-empty-string + */ + private function testAsString(string $testClassName, ?string $testMethodName = null): string + { + return sprintf( + '%s %s%s%s', + $testMethodName !== null ? 'method' : 'class', + $testClassName, + $testMethodName !== null ? '::' : '', + $testMethodName !== null ? $testMethodName : '', + ); + } +} diff --git a/src/Metadata/Parser/CachingParser.php b/src/Metadata/Parser/CachingParser.php new file mode 100644 index 00000000000..7c274c15e14 --- /dev/null +++ b/src/Metadata/Parser/CachingParser.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Parser; + +use function assert; +use function class_exists; +use function method_exists; +use PHPUnit\Metadata\MetadataCollection; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CachingParser implements Parser +{ + private readonly Parser $reader; + + /** + * @var array + */ + private array $classCache = []; + + /** + * @var array + */ + private array $methodCache = []; + + /** + * @var array + */ + private array $classAndMethodCache = []; + + public function __construct(Parser $reader) + { + $this->reader = $reader; + } + + /** + * @param class-string $className + */ + public function forClass(string $className): MetadataCollection + { + assert(class_exists($className)); + + if (isset($this->classCache[$className])) { + return $this->classCache[$className]; + } + + $this->classCache[$className] = $this->reader->forClass($className); + + return $this->classCache[$className]; + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function forMethod(string $className, string $methodName): MetadataCollection + { + assert(class_exists($className)); + assert(method_exists($className, $methodName)); + + $key = $className . '::' . $methodName; + + if (isset($this->methodCache[$key])) { + return $this->methodCache[$key]; + } + + $this->methodCache[$key] = $this->reader->forMethod($className, $methodName); + + return $this->methodCache[$key]; + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function forClassAndMethod(string $className, string $methodName): MetadataCollection + { + $key = $className . '::' . $methodName; + + if (isset($this->classAndMethodCache[$key])) { + return $this->classAndMethodCache[$key]; + } + + $this->classAndMethodCache[$key] = $this->forClass($className)->mergeWith( + $this->forMethod($className, $methodName), + ); + + return $this->classAndMethodCache[$key]; + } +} diff --git a/src/Metadata/Parser/Parser.php b/src/Metadata/Parser/Parser.php new file mode 100644 index 00000000000..edc2d87f473 --- /dev/null +++ b/src/Metadata/Parser/Parser.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Parser; + +use PHPUnit\Metadata\MetadataCollection; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Parser +{ + /** + * @param class-string $className + */ + public function forClass(string $className): MetadataCollection; + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function forMethod(string $className, string $methodName): MetadataCollection; + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function forClassAndMethod(string $className, string $methodName): MetadataCollection; +} diff --git a/src/Metadata/Parser/Registry.php b/src/Metadata/Parser/Registry.php new file mode 100644 index 00000000000..0b30782ec09 --- /dev/null +++ b/src/Metadata/Parser/Registry.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Parser; + +/** + * Attribute information is static within a single PHP process. + * It is therefore okay to use a Singleton registry here. + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Registry +{ + private static ?Parser $instance = null; + + public static function parser(): Parser + { + return self::$instance ?? self::$instance = self::build(); + } + + private static function build(): Parser + { + return new CachingParser(new AttributeParser); + } +} diff --git a/src/Metadata/PostCondition.php b/src/Metadata/PostCondition.php new file mode 100644 index 00000000000..d52ae0628bd --- /dev/null +++ b/src/Metadata/PostCondition.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PostCondition extends Metadata +{ + private int $priority; + + /** + * @param int<0, 1> $level + */ + protected function __construct(int $level, int $priority) + { + parent::__construct($level); + + $this->priority = $priority; + } + + public function isPostCondition(): true + { + return true; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/src/Metadata/PreCondition.php b/src/Metadata/PreCondition.php new file mode 100644 index 00000000000..9243122bcb6 --- /dev/null +++ b/src/Metadata/PreCondition.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PreCondition extends Metadata +{ + private int $priority; + + /** + * @param int<0, 1> $level + */ + protected function __construct(int $level, int $priority) + { + parent::__construct($level); + + $this->priority = $priority; + } + + public function isPreCondition(): true + { + return true; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/src/Metadata/PreserveGlobalState.php b/src/Metadata/PreserveGlobalState.php new file mode 100644 index 00000000000..f888119159c --- /dev/null +++ b/src/Metadata/PreserveGlobalState.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PreserveGlobalState extends Metadata +{ + private bool $enabled; + + /** + * @param int<0, 1> $level + */ + protected function __construct(int $level, bool $enabled) + { + parent::__construct($level); + + $this->enabled = $enabled; + } + + public function isPreserveGlobalState(): true + { + return true; + } + + public function enabled(): bool + { + return $this->enabled; + } +} diff --git a/src/Metadata/RequiresEnvironmentVariable.php b/src/Metadata/RequiresEnvironmentVariable.php new file mode 100644 index 00000000000..0888c296d33 --- /dev/null +++ b/src/Metadata/RequiresEnvironmentVariable.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RequiresEnvironmentVariable extends Metadata +{ + private string $environmentVariableName; + private null|string $value; + + public function __construct(int $level, string $environmentVariableName, null|string $value) + { + parent::__construct($level); + + $this->environmentVariableName = $environmentVariableName; + $this->value = $value; + } + + public function isRequiresEnvironmentVariable(): true + { + return true; + } + + public function environmentVariableName(): string + { + return $this->environmentVariableName; + } + + public function value(): null|string + { + return $this->value; + } +} diff --git a/src/Metadata/RequiresFunction.php b/src/Metadata/RequiresFunction.php new file mode 100644 index 00000000000..f66c5180c47 --- /dev/null +++ b/src/Metadata/RequiresFunction.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RequiresFunction extends Metadata +{ + /** + * @var non-empty-string + */ + private string $functionName; + + /** + * @param int<0, 1> $level + * @param non-empty-string $functionName + */ + protected function __construct(int $level, string $functionName) + { + parent::__construct($level); + + $this->functionName = $functionName; + } + + public function isRequiresFunction(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function functionName(): string + { + return $this->functionName; + } +} diff --git a/src/Metadata/RequiresMethod.php b/src/Metadata/RequiresMethod.php new file mode 100644 index 00000000000..2ad7fbb9abc --- /dev/null +++ b/src/Metadata/RequiresMethod.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RequiresMethod extends Metadata +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param int<0, 1> $level + * @param class-string $className + * @param non-empty-string $methodName + */ + protected function __construct(int $level, string $className, string $methodName) + { + parent::__construct($level); + + $this->className = $className; + $this->methodName = $methodName; + } + + public function isRequiresMethod(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/src/Metadata/RequiresOperatingSystem.php b/src/Metadata/RequiresOperatingSystem.php new file mode 100644 index 00000000000..8719d296120 --- /dev/null +++ b/src/Metadata/RequiresOperatingSystem.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RequiresOperatingSystem extends Metadata +{ + /** + * @var non-empty-string + */ + private string $operatingSystem; + + /** + * @param int<0, 1> $level + * @param non-empty-string $operatingSystem + */ + public function __construct(int $level, string $operatingSystem) + { + parent::__construct($level); + + $this->operatingSystem = $operatingSystem; + } + + public function isRequiresOperatingSystem(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function operatingSystem(): string + { + return $this->operatingSystem; + } +} diff --git a/src/Metadata/RequiresOperatingSystemFamily.php b/src/Metadata/RequiresOperatingSystemFamily.php new file mode 100644 index 00000000000..4481dcf8ce5 --- /dev/null +++ b/src/Metadata/RequiresOperatingSystemFamily.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RequiresOperatingSystemFamily extends Metadata +{ + /** + * @var non-empty-string + */ + private string $operatingSystemFamily; + + /** + * @param int<0, 1> $level + * @param non-empty-string $operatingSystemFamily + */ + protected function __construct(int $level, string $operatingSystemFamily) + { + parent::__construct($level); + + $this->operatingSystemFamily = $operatingSystemFamily; + } + + public function isRequiresOperatingSystemFamily(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function operatingSystemFamily(): string + { + return $this->operatingSystemFamily; + } +} diff --git a/src/Metadata/RequiresPhp.php b/src/Metadata/RequiresPhp.php new file mode 100644 index 00000000000..c64a396281e --- /dev/null +++ b/src/Metadata/RequiresPhp.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +use PHPUnit\Metadata\Version\Requirement; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RequiresPhp extends Metadata +{ + private Requirement $versionRequirement; + + /** + * @param int<0, 1> $level + */ + protected function __construct(int $level, Requirement $versionRequirement) + { + parent::__construct($level); + + $this->versionRequirement = $versionRequirement; + } + + public function isRequiresPhp(): true + { + return true; + } + + public function versionRequirement(): Requirement + { + return $this->versionRequirement; + } +} diff --git a/src/Metadata/RequiresPhpExtension.php b/src/Metadata/RequiresPhpExtension.php new file mode 100644 index 00000000000..5c547ea9ec8 --- /dev/null +++ b/src/Metadata/RequiresPhpExtension.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +use PHPUnit\Metadata\Version\Requirement; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RequiresPhpExtension extends Metadata +{ + /** + * @var non-empty-string + */ + private string $extension; + private ?Requirement $versionRequirement; + + /** + * @param int<0, 1> $level + * @param non-empty-string $extension + */ + protected function __construct(int $level, string $extension, ?Requirement $versionRequirement) + { + parent::__construct($level); + + $this->extension = $extension; + $this->versionRequirement = $versionRequirement; + } + + public function isRequiresPhpExtension(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function extension(): string + { + return $this->extension; + } + + /** + * @phpstan-assert-if-true !null $this->versionRequirement + */ + public function hasVersionRequirement(): bool + { + return $this->versionRequirement !== null; + } + + /** + * @throws NoVersionRequirementException + */ + public function versionRequirement(): Requirement + { + if ($this->versionRequirement === null) { + throw new NoVersionRequirementException; + } + + return $this->versionRequirement; + } +} diff --git a/src/Metadata/RequiresPhpunit.php b/src/Metadata/RequiresPhpunit.php new file mode 100644 index 00000000000..dc8ae80e2b4 --- /dev/null +++ b/src/Metadata/RequiresPhpunit.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +use PHPUnit\Metadata\Version\Requirement; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RequiresPhpunit extends Metadata +{ + private Requirement $versionRequirement; + + /** + * @param int<0, 1> $level + */ + protected function __construct(int $level, Requirement $versionRequirement) + { + parent::__construct($level); + + $this->versionRequirement = $versionRequirement; + } + + public function isRequiresPhpunit(): true + { + return true; + } + + public function versionRequirement(): Requirement + { + return $this->versionRequirement; + } +} diff --git a/src/Metadata/RequiresPhpunitExtension.php b/src/Metadata/RequiresPhpunitExtension.php new file mode 100644 index 00000000000..726c3b961a8 --- /dev/null +++ b/src/Metadata/RequiresPhpunitExtension.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +use PHPUnit\Runner\Extension\Extension; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RequiresPhpunitExtension extends Metadata +{ + /** + * @var class-string + */ + private string $extensionClass; + + /** + * @param class-string $extensionClass + */ + public function __construct(int $level, string $extensionClass) + { + parent::__construct($level); + + $this->extensionClass = $extensionClass; + } + + public function isRequiresPhpunitExtension(): true + { + return true; + } + + /** + * @return class-string + */ + public function extensionClass(): string + { + return $this->extensionClass; + } +} diff --git a/src/Metadata/RequiresSetting.php b/src/Metadata/RequiresSetting.php new file mode 100644 index 00000000000..0d0b0230a40 --- /dev/null +++ b/src/Metadata/RequiresSetting.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RequiresSetting extends Metadata +{ + /** + * @var non-empty-string + */ + private string $setting; + + /** + * @var non-empty-string + */ + private string $value; + + /** + * @param int<0, 1> $level + * @param non-empty-string $setting + * @param non-empty-string $value + */ + protected function __construct(int $level, string $setting, string $value) + { + parent::__construct($level); + + $this->setting = $setting; + $this->value = $value; + } + + public function isRequiresSetting(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function setting(): string + { + return $this->setting; + } + + /** + * @return non-empty-string + */ + public function value(): string + { + return $this->value; + } +} diff --git a/src/Metadata/RunInSeparateProcess.php b/src/Metadata/RunInSeparateProcess.php new file mode 100644 index 00000000000..dc755219ce4 --- /dev/null +++ b/src/Metadata/RunInSeparateProcess.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RunInSeparateProcess extends Metadata +{ + public function isRunInSeparateProcess(): true + { + return true; + } +} diff --git a/src/Metadata/RunTestsInSeparateProcesses.php b/src/Metadata/RunTestsInSeparateProcesses.php new file mode 100644 index 00000000000..3a544a792c8 --- /dev/null +++ b/src/Metadata/RunTestsInSeparateProcesses.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RunTestsInSeparateProcesses extends Metadata +{ + public function isRunTestsInSeparateProcesses(): true + { + return true; + } +} diff --git a/src/Metadata/Test.php b/src/Metadata/Test.php new file mode 100644 index 00000000000..04007610a76 --- /dev/null +++ b/src/Metadata/Test.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Test extends Metadata +{ + public function isTest(): true + { + return true; + } +} diff --git a/src/Metadata/TestDox.php b/src/Metadata/TestDox.php new file mode 100644 index 00000000000..6e2944e5fe9 --- /dev/null +++ b/src/Metadata/TestDox.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestDox extends Metadata +{ + /** + * @var non-empty-string + */ + private string $text; + + /** + * @param int<0, 1> $level + * @param non-empty-string $text + */ + protected function __construct(int $level, string $text) + { + parent::__construct($level); + + $this->text = $text; + } + + public function isTestDox(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function text(): string + { + return $this->text; + } +} diff --git a/src/Metadata/TestDoxFormatter.php b/src/Metadata/TestDoxFormatter.php new file mode 100644 index 00000000000..a7f055b87ef --- /dev/null +++ b/src/Metadata/TestDoxFormatter.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestDoxFormatter extends Metadata +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param int<0, 1> $level + * @param class-string $className + * @param non-empty-string $methodName + */ + protected function __construct(int $level, string $className, string $methodName) + { + parent::__construct($level); + + $this->className = $className; + $this->methodName = $methodName; + } + + public function isTestDoxFormatter(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/src/Metadata/TestWith.php b/src/Metadata/TestWith.php new file mode 100644 index 00000000000..d704d863fc4 --- /dev/null +++ b/src/Metadata/TestWith.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestWith extends Metadata +{ + private mixed $data; + + /** + * @var ?non-empty-string + */ + private ?string $name; + + /** + * @param int<0, 1> $level + * @param ?non-empty-string $name + */ + protected function __construct(int $level, mixed $data, ?string $name = null) + { + parent::__construct($level); + + $this->data = $data; + $this->name = $name; + } + + public function isTestWith(): true + { + return true; + } + + public function data(): mixed + { + return $this->data; + } + + /** + * @phpstan-assert-if-true !null $this->name + */ + public function hasName(): bool + { + return $this->name !== null; + } + + /** + * @return ?non-empty-string + */ + public function name(): ?string + { + return $this->name; + } +} diff --git a/src/Metadata/UsesClass.php b/src/Metadata/UsesClass.php new file mode 100644 index 00000000000..edbd03ac59b --- /dev/null +++ b/src/Metadata/UsesClass.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class UsesClass extends Metadata +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param int<0, 1> $level + * @param class-string $className + */ + protected function __construct(int $level, string $className) + { + parent::__construct($level); + + $this->className = $className; + } + + public function isUsesClass(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } +} diff --git a/src/Metadata/UsesClassesThatExtendClass.php b/src/Metadata/UsesClassesThatExtendClass.php new file mode 100644 index 00000000000..baddfeb6b6f --- /dev/null +++ b/src/Metadata/UsesClassesThatExtendClass.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class UsesClassesThatExtendClass extends Metadata +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param int<0, 1> $level + * @param class-string $className + */ + protected function __construct(int $level, string $className) + { + parent::__construct($level); + + $this->className = $className; + } + + public function isUsesClassesThatExtendClass(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } +} diff --git a/src/Metadata/UsesClassesThatImplementInterface.php b/src/Metadata/UsesClassesThatImplementInterface.php new file mode 100644 index 00000000000..5cdc6f629a3 --- /dev/null +++ b/src/Metadata/UsesClassesThatImplementInterface.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class UsesClassesThatImplementInterface extends Metadata +{ + /** + * @var class-string + */ + private string $interfaceName; + + /** + * @param int<0, 1> $level + * @param class-string $interfaceName + */ + protected function __construct(int $level, string $interfaceName) + { + parent::__construct($level); + + $this->interfaceName = $interfaceName; + } + + public function isUsesClassesThatImplementInterface(): true + { + return true; + } + + /** + * @return class-string + */ + public function interfaceName(): string + { + return $this->interfaceName; + } +} diff --git a/src/Metadata/UsesFunction.php b/src/Metadata/UsesFunction.php new file mode 100644 index 00000000000..ec6cafee8e4 --- /dev/null +++ b/src/Metadata/UsesFunction.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class UsesFunction extends Metadata +{ + /** + * @var non-empty-string + */ + private string $functionName; + + /** + * @param int<0, 1> $level + * @param non-empty-string $functionName + */ + public function __construct(int $level, string $functionName) + { + parent::__construct($level); + + $this->functionName = $functionName; + } + + public function isUsesFunction(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function functionName(): string + { + return $this->functionName; + } +} diff --git a/src/Metadata/UsesMethod.php b/src/Metadata/UsesMethod.php new file mode 100644 index 00000000000..6fc78f8f2c7 --- /dev/null +++ b/src/Metadata/UsesMethod.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class UsesMethod extends Metadata +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param int<0, 1> $level + * @param class-string $className + * @param non-empty-string $methodName + */ + protected function __construct(int $level, string $className, string $methodName) + { + parent::__construct($level); + + $this->className = $className; + $this->methodName = $methodName; + } + + public function isUsesMethod(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/src/Metadata/UsesNamespace.php b/src/Metadata/UsesNamespace.php new file mode 100644 index 00000000000..f5e5d01ecd6 --- /dev/null +++ b/src/Metadata/UsesNamespace.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class UsesNamespace extends Metadata +{ + /** + * @var non-empty-string + */ + private string $namespace; + + /** + * @param int<0, 1> $level + * @param non-empty-string $namespace + */ + protected function __construct(int $level, string $namespace) + { + parent::__construct($level); + + $this->namespace = $namespace; + } + + public function isUsesNamespace(): true + { + return true; + } + + /** + * @return class-string + */ + public function namespace(): string + { + return $this->namespace; + } +} diff --git a/src/Metadata/UsesTrait.php b/src/Metadata/UsesTrait.php new file mode 100644 index 00000000000..19490f26c2f --- /dev/null +++ b/src/Metadata/UsesTrait.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class UsesTrait extends Metadata +{ + /** + * @var trait-string + */ + private string $traitName; + + /** + * @param 0|1 $level + * @param trait-string $traitName + */ + protected function __construct(int $level, string $traitName) + { + parent::__construct($level); + + $this->traitName = $traitName; + } + + public function isUsesTrait(): true + { + return true; + } + + /** + * @return trait-string + */ + public function traitName(): string + { + return $this->traitName; + } +} diff --git a/src/Metadata/Version/ComparisonRequirement.php b/src/Metadata/Version/ComparisonRequirement.php new file mode 100644 index 00000000000..b3b6a1fcadf --- /dev/null +++ b/src/Metadata/Version/ComparisonRequirement.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Version; + +use function version_compare; +use PHPUnit\Util\VersionComparisonOperator; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ComparisonRequirement extends Requirement +{ + private string $version; + private VersionComparisonOperator $operator; + + public function __construct(string $version, VersionComparisonOperator $operator) + { + $this->version = $version; + $this->operator = $operator; + } + + public function isSatisfiedBy(string $version): bool + { + return version_compare($version, $this->version, $this->operator->asString()); + } + + public function asString(): string + { + return $this->operator->asString() . ' ' . $this->version; + } +} diff --git a/src/Metadata/Version/ConstraintRequirement.php b/src/Metadata/Version/ConstraintRequirement.php new file mode 100644 index 00000000000..107544503b7 --- /dev/null +++ b/src/Metadata/Version/ConstraintRequirement.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Version; + +use function preg_replace; +use PharIo\Version\Version; +use PharIo\Version\VersionConstraint; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ConstraintRequirement extends Requirement +{ + private VersionConstraint $constraint; + + public function __construct(VersionConstraint $constraint) + { + $this->constraint = $constraint; + } + + public function isSatisfiedBy(string $version): bool + { + return $this->constraint->complies( + new Version($this->sanitize($version)), + ); + } + + public function asString(): string + { + return $this->constraint->asString(); + } + + private function sanitize(string $version): string + { + return preg_replace( + '/^(\d+\.\d+(?:.\d+)?).*$/', + '$1', + $version, + ); + } +} diff --git a/src/Metadata/Version/Requirement.php b/src/Metadata/Version/Requirement.php new file mode 100644 index 00000000000..01f98f7310f --- /dev/null +++ b/src/Metadata/Version/Requirement.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Version; + +use function preg_match; +use PharIo\Version\UnsupportedVersionConstraintException; +use PharIo\Version\VersionConstraintParser; +use PHPUnit\Metadata\InvalidVersionRequirementException; +use PHPUnit\Util\InvalidVersionOperatorException; +use PHPUnit\Util\VersionComparisonOperator; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Requirement +{ + private const string VERSION_COMPARISON = "/(?P!=|<|<=|<>|=|==|>|>=)?\s*(?P[\d\.-]+(dev|(RC|alpha|beta)[\d\.])?)[ \t]*\r?$/m"; + + /** + * @throws InvalidVersionOperatorException + * @throws InvalidVersionRequirementException + */ + public static function from(string $versionRequirement): self + { + try { + return new ConstraintRequirement( + (new VersionConstraintParser)->parse( + $versionRequirement, + ), + ); + } catch (UnsupportedVersionConstraintException) { + if (preg_match(self::VERSION_COMPARISON, $versionRequirement, $matches) > 0) { + return new ComparisonRequirement( + $matches['version'], + new VersionComparisonOperator( + $matches['operator'] !== '' ? $matches['operator'] : '>=', + ), + ); + } + } + + throw new InvalidVersionRequirementException; + } + + abstract public function isSatisfiedBy(string $version): bool; + + abstract public function asString(): string; +} diff --git a/src/Metadata/WithEnvironmentVariable.php b/src/Metadata/WithEnvironmentVariable.php new file mode 100644 index 00000000000..cc4d0fe7dd0 --- /dev/null +++ b/src/Metadata/WithEnvironmentVariable.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class WithEnvironmentVariable extends Metadata +{ + /** + * @var non-empty-string + */ + private string $environmentVariableName; + private null|string $value; + + /** + * @param non-empty-string $environmentVariableName + */ + public function __construct(int $level, string $environmentVariableName, null|string $value) + { + parent::__construct($level); + + $this->environmentVariableName = $environmentVariableName; + $this->value = $value; + } + + public function isWithEnvironmentVariable(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function environmentVariableName(): string + { + return $this->environmentVariableName; + } + + public function value(): null|string + { + return $this->value; + } +} diff --git a/src/Metadata/WithoutErrorHandler.php b/src/Metadata/WithoutErrorHandler.php new file mode 100644 index 00000000000..a8bf001475d --- /dev/null +++ b/src/Metadata/WithoutErrorHandler.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class WithoutErrorHandler extends Metadata +{ + public function isWithoutErrorHandler(): true + { + return true; + } +} diff --git a/src/Runner/BackedUpEnvironmentVariable.php b/src/Runner/BackedUpEnvironmentVariable.php new file mode 100644 index 00000000000..a62ebe95c17 --- /dev/null +++ b/src/Runner/BackedUpEnvironmentVariable.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use function getenv; +use function putenv; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BackedUpEnvironmentVariable +{ + private const string FROM_GETENV = 'getenv'; + private const string FROM_SUPERGLOBAL = 'superglobal'; + + /** + * @var self::FROM_GETENV|self::FROM_SUPERGLOBAL + */ + private string $from; + + /** + * @var non-empty-string + */ + private string $name; + private null|string $value; + + /** + * @param non-empty-string $name + * + * @return array{0: self, 1: self} + */ + public static function create(string $name): array + { + $getenv = getenv($name); + + if ($getenv === false) { + $getenv = null; + } + + return [ + new self(self::FROM_SUPERGLOBAL, $name, $_ENV[$name] ?? null), + new self(self::FROM_GETENV, $name, $getenv), + ]; + } + + /** + * @param self::FROM_GETENV|self::FROM_SUPERGLOBAL $from + * @param non-empty-string $name + */ + private function __construct(string $from, string $name, null|string $value) + { + $this->from = $from; + $this->name = $name; + $this->value = $value; + } + + public function restore(): void + { + if ($this->from === self::FROM_GETENV) { + $this->restoreGetEnv(); + } else { + $this->restoreSuperGlobal(); + } + } + + private function restoreGetEnv(): void + { + if ($this->value === null) { + putenv($this->name); + } else { + putenv("{$this->name}={$this->value}"); + } + } + + private function restoreSuperGlobal(): void + { + if ($this->value === null) { + unset($_ENV[$this->name]); + } else { + $_ENV[$this->name] = $this->value; + } + } +} diff --git a/src/Runner/BaseTestRunner.php b/src/Runner/BaseTestRunner.php deleted file mode 100644 index 75a70ca5fcc..00000000000 --- a/src/Runner/BaseTestRunner.php +++ /dev/null @@ -1,161 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -use function is_dir; -use function is_file; -use function substr; -use PHPUnit\Framework\Exception; -use PHPUnit\Framework\TestSuite; -use ReflectionClass; -use ReflectionException; -use SebastianBergmann\FileIterator\Facade as FileIteratorFacade; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -abstract class BaseTestRunner -{ - /** - * @var int - */ - public const STATUS_UNKNOWN = -1; - - /** - * @var int - */ - public const STATUS_PASSED = 0; - - /** - * @var int - */ - public const STATUS_SKIPPED = 1; - - /** - * @var int - */ - public const STATUS_INCOMPLETE = 2; - - /** - * @var int - */ - public const STATUS_FAILURE = 3; - - /** - * @var int - */ - public const STATUS_ERROR = 4; - - /** - * @var int - */ - public const STATUS_RISKY = 5; - - /** - * @var int - */ - public const STATUS_WARNING = 6; - - /** - * @var string - */ - public const SUITE_METHODNAME = 'suite'; - - /** - * Returns the loader to be used. - */ - public function getLoader(): TestSuiteLoader - { - return new StandardTestSuiteLoader; - } - - /** - * Returns the Test corresponding to the given suite. - * This is a template method, subclasses override - * the runFailed() and clearStatus() methods. - * - * @param string|string[] $suffixes - * - * @throws Exception - */ - public function getTest(string $suiteClassFile, $suffixes = ''): ?TestSuite - { - if (is_dir($suiteClassFile)) { - /** @var string[] $files */ - $files = (new FileIteratorFacade)->getFilesAsArray( - $suiteClassFile, - $suffixes - ); - - $suite = new TestSuite($suiteClassFile); - $suite->addTestFiles($files); - - return $suite; - } - - if (is_file($suiteClassFile) && substr($suiteClassFile, -5, 5) === '.phpt') { - $suite = new TestSuite; - $suite->addTestFile($suiteClassFile); - - return $suite; - } - - try { - $testClass = $this->loadSuiteClass( - $suiteClassFile - ); - } catch (\PHPUnit\Exception $e) { - $this->runFailed($e->getMessage()); - - return null; - } - - try { - $suiteMethod = $testClass->getMethod(self::SUITE_METHODNAME); - - if (!$suiteMethod->isStatic()) { - $this->runFailed( - 'suite() method must be static.' - ); - - return null; - } - - $test = $suiteMethod->invoke(null, $testClass->getName()); - } catch (ReflectionException $e) { - $test = new TestSuite($testClass); - } - - $this->clearStatus(); - - return $test; - } - - /** - * Returns the loaded ReflectionClass for a suite name. - */ - protected function loadSuiteClass(string $suiteClassFile): ReflectionClass - { - return $this->getLoader()->load($suiteClassFile); - } - - /** - * Clears the status message. - */ - protected function clearStatus(): void - { - } - - /** - * Override to define how to handle a failed loading of - * a test suite. - */ - abstract protected function runFailed(string $message): void; -} diff --git a/src/Runner/Baseline/Baseline.php b/src/Runner/Baseline/Baseline.php new file mode 100644 index 00000000000..cffc3791d58 --- /dev/null +++ b/src/Runner/Baseline/Baseline.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Baseline +{ + public const int VERSION = 1; + + /** + * @var array>> + */ + private array $issues = []; + + public function add(Issue $issue): void + { + if (!isset($this->issues[$issue->file()])) { + $this->issues[$issue->file()] = []; + } + + if (!isset($this->issues[$issue->file()][$issue->line()])) { + $this->issues[$issue->file()][$issue->line()] = []; + } + + $this->issues[$issue->file()][$issue->line()][] = $issue; + } + + public function has(Issue $issue): bool + { + if (!isset($this->issues[$issue->file()][$issue->line()])) { + return false; + } + + foreach ($this->issues[$issue->file()][$issue->line()] as $_issue) { + if ($_issue->equals($issue)) { + return true; + } + } + + return false; + } + + /** + * @return array>> + */ + public function groupedByFileAndLine(): array + { + return $this->issues; + } +} diff --git a/src/Runner/Baseline/Exception/CannotLoadBaselineException.php b/src/Runner/Baseline/Exception/CannotLoadBaselineException.php new file mode 100644 index 00000000000..c55901365e6 --- /dev/null +++ b/src/Runner/Baseline/Exception/CannotLoadBaselineException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use PHPUnit\Runner\Exception; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CannotLoadBaselineException extends RuntimeException implements Exception +{ +} diff --git a/src/Runner/Baseline/Exception/CannotWriteBaselineException.php b/src/Runner/Baseline/Exception/CannotWriteBaselineException.php new file mode 100644 index 00000000000..914a2c34db7 --- /dev/null +++ b/src/Runner/Baseline/Exception/CannotWriteBaselineException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use PHPUnit\Runner\Exception; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CannotWriteBaselineException extends RuntimeException implements Exception +{ +} diff --git a/src/Runner/Baseline/Exception/FileDoesNotHaveLineException.php b/src/Runner/Baseline/Exception/FileDoesNotHaveLineException.php new file mode 100644 index 00000000000..20c6ca03056 --- /dev/null +++ b/src/Runner/Baseline/Exception/FileDoesNotHaveLineException.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use function sprintf; +use PHPUnit\Runner\Exception; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class FileDoesNotHaveLineException extends RuntimeException implements Exception +{ + public function __construct(string $file, int $line) + { + parent::__construct( + sprintf( + 'File "%s" does not have line %d', + $file, + $line, + ), + ); + } +} diff --git a/src/Runner/Baseline/Generator.php b/src/Runner/Baseline/Generator.php new file mode 100644 index 00000000000..c5ce074de98 --- /dev/null +++ b/src/Runner/Baseline/Generator.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use PHPUnit\Event\Facade; +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\Test\NoticeTriggered; +use PHPUnit\Event\Test\PhpDeprecationTriggered; +use PHPUnit\Event\Test\PhpNoticeTriggered; +use PHPUnit\Event\Test\PhpWarningTriggered; +use PHPUnit\Event\Test\WarningTriggered; +use PHPUnit\Runner\FileDoesNotExistException; +use PHPUnit\TextUI\Configuration\Source; +use PHPUnit\TextUI\Configuration\SourceFilter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Generator +{ + private Baseline $baseline; + private Source $source; + + public function __construct(Facade $facade, Source $source) + { + $facade->registerSubscribers( + new TestTriggeredDeprecationSubscriber($this), + new TestTriggeredNoticeSubscriber($this), + new TestTriggeredPhpDeprecationSubscriber($this), + new TestTriggeredPhpNoticeSubscriber($this), + new TestTriggeredPhpWarningSubscriber($this), + new TestTriggeredWarningSubscriber($this), + ); + + $this->baseline = new Baseline; + $this->source = $source; + } + + public function baseline(): Baseline + { + return $this->baseline; + } + + /** + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + */ + public function testTriggeredIssue(DeprecationTriggered|NoticeTriggered|PhpDeprecationTriggered|PhpNoticeTriggered|PhpWarningTriggered|WarningTriggered $event): void + { + if ($event->wasSuppressed() && !$this->isSuppressionIgnored($event)) { + return; + } + + if ($this->restrict($event) && !SourceFilter::instance()->includes($event->file())) { + return; + } + + $this->baseline->add( + Issue::from( + $event->file(), + $event->line(), + null, + $event->message(), + ), + ); + } + + private function restrict(DeprecationTriggered|NoticeTriggered|PhpDeprecationTriggered|PhpNoticeTriggered|PhpWarningTriggered|WarningTriggered $event): bool + { + if ($event instanceof WarningTriggered || $event instanceof PhpWarningTriggered) { + return $this->source->restrictWarnings(); + } + + if ($event instanceof NoticeTriggered || $event instanceof PhpNoticeTriggered) { + return $this->source->restrictNotices(); + } + + return false; + } + + private function isSuppressionIgnored(DeprecationTriggered|NoticeTriggered|PhpDeprecationTriggered|PhpNoticeTriggered|PhpWarningTriggered|WarningTriggered $event): bool + { + if ($event instanceof WarningTriggered) { + return $this->source->ignoreSuppressionOfWarnings(); + } + + if ($event instanceof PhpWarningTriggered) { + return $this->source->ignoreSuppressionOfPhpWarnings(); + } + + if ($event instanceof PhpNoticeTriggered) { + return $this->source->ignoreSuppressionOfPhpNotices(); + } + + if ($event instanceof NoticeTriggered) { + return $this->source->ignoreSuppressionOfNotices(); + } + + if ($event instanceof PhpDeprecationTriggered) { + return $this->source->ignoreSuppressionOfPhpDeprecations(); + } + + return $this->source->ignoreSuppressionOfDeprecations(); + } +} diff --git a/src/Runner/Baseline/Issue.php b/src/Runner/Baseline/Issue.php new file mode 100644 index 00000000000..869ea26a4bc --- /dev/null +++ b/src/Runner/Baseline/Issue.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use const FILE_IGNORE_NEW_LINES; +use function assert; +use function file; +use function is_file; +use function sha1; +use PHPUnit\Runner\FileDoesNotExistException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Issue +{ + /** + * @var non-empty-string + */ + private string $file; + + /** + * @var positive-int + */ + private int $line; + + /** + * @var non-empty-string + */ + private string $hash; + + /** + * @var non-empty-string + */ + private string $description; + + /** + * @param non-empty-string $file + * @param positive-int $line + * @param ?non-empty-string $hash + * @param non-empty-string $description + * + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + */ + public static function from(string $file, int $line, ?string $hash, string $description): self + { + if ($hash === null) { + $hash = self::calculateHash($file, $line); + } + + return new self($file, $line, $hash, $description); + } + + /** + * @param non-empty-string $file + * @param positive-int $line + * @param non-empty-string $hash + * @param non-empty-string $description + */ + private function __construct(string $file, int $line, string $hash, string $description) + { + $this->file = $file; + $this->line = $line; + $this->hash = $hash; + $this->description = $description; + } + + /** + * @return non-empty-string + */ + public function file(): string + { + return $this->file; + } + + /** + * @return positive-int + */ + public function line(): int + { + return $this->line; + } + + /** + * @return non-empty-string + */ + public function hash(): string + { + return $this->hash; + } + + /** + * @return non-empty-string + */ + public function description(): string + { + return $this->description; + } + + public function equals(self $other): bool + { + return $this->file() === $other->file() && + $this->line() === $other->line() && + $this->hash() === $other->hash() && + $this->description() === $other->description(); + } + + /** + * @param non-empty-string $file + * @param positive-int $line + * + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + * + * @return non-empty-string + */ + private static function calculateHash(string $file, int $line): string + { + $lines = @file($file, FILE_IGNORE_NEW_LINES); + + if ($lines === false && !is_file($file)) { + throw new FileDoesNotExistException($file); + } + + $key = $line - 1; + + if (!isset($lines[$key])) { + throw new FileDoesNotHaveLineException($file, $line); + } + + $hash = sha1($lines[$key]); + + assert($hash !== ''); + + return $hash; + } +} diff --git a/src/Runner/Baseline/Reader.php b/src/Runner/Baseline/Reader.php new file mode 100644 index 00000000000..ce3a194ff16 --- /dev/null +++ b/src/Runner/Baseline/Reader.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use const DIRECTORY_SEPARATOR; +use function assert; +use function dirname; +use function is_file; +use function realpath; +use function sprintf; +use function str_replace; +use function trim; +use DOMElement; +use DOMXPath; +use PHPUnit\Util\Xml\Loader as XmlLoader; +use PHPUnit\Util\Xml\XmlException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Reader +{ + /** + * @param non-empty-string $baselineFile + * + * @throws CannotLoadBaselineException + */ + public function read(string $baselineFile): Baseline + { + if (!is_file($baselineFile)) { + throw new CannotLoadBaselineException( + sprintf( + 'Cannot read baseline %s, file does not exist', + $baselineFile, + ), + ); + } + + try { + $document = (new XmlLoader)->loadFile($baselineFile); + } catch (XmlException $e) { + throw new CannotLoadBaselineException( + sprintf( + 'Cannot read baseline %s: %s', + $baselineFile, + trim($e->getMessage()), + ), + ); + } + + $version = (int) $document->documentElement->getAttribute('version'); + + if ($version !== Baseline::VERSION) { + throw new CannotLoadBaselineException( + sprintf( + 'Cannot read baseline %s, version %d is not supported', + $baselineFile, + $version, + ), + ); + } + + $baseline = new Baseline; + $baselineDirectory = dirname(realpath($baselineFile)); + $xpath = new DOMXPath($document); + + foreach ($xpath->query('file') as $fileElement) { + assert($fileElement instanceof DOMElement); + + $file = $baselineDirectory . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $fileElement->getAttribute('path')); + + foreach ($xpath->query('line', $fileElement) as $lineElement) { + assert($lineElement instanceof DOMElement); + + $line = (int) $lineElement->getAttribute('number'); + $hash = $lineElement->getAttribute('hash'); + + foreach ($xpath->query('issue', $lineElement) as $issueElement) { + assert($issueElement instanceof DOMElement); + + $description = $issueElement->textContent; + + assert($line > 0); + assert($hash !== ''); + assert($description !== ''); + + $baseline->add(Issue::from($file, $line, $hash, $description)); + } + } + } + + return $baseline; + } +} diff --git a/src/Runner/Baseline/RelativePathCalculator.php b/src/Runner/Baseline/RelativePathCalculator.php new file mode 100644 index 00000000000..a05e6bbea09 --- /dev/null +++ b/src/Runner/Baseline/RelativePathCalculator.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use function array_fill; +use function array_merge; +use function array_slice; +use function assert; +use function count; +use function explode; +use function implode; +use function str_replace; +use function strpos; +use function substr; +use function trim; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @see Copied from https://github.com/phpstan/phpstan-src/blob/1.10.33/src/File/ParentDirectoryRelativePathHelper.php + */ +final readonly class RelativePathCalculator +{ + /** + * @var non-empty-string + */ + private string $baselineDirectory; + + /** + * @param non-empty-string $baselineDirectory + */ + public function __construct(string $baselineDirectory) + { + $this->baselineDirectory = $baselineDirectory; + } + + /** + * @param non-empty-string $filename + * + * @return non-empty-string + */ + public function calculate(string $filename): string + { + $result = implode('/', $this->parts($filename)); + + assert($result !== ''); + + return $result; + } + + /** + * @param non-empty-string $filename + * + * @return list + */ + public function parts(string $filename): array + { + $schemePosition = strpos($filename, '://'); + + if ($schemePosition !== false) { + $filename = substr($filename, $schemePosition + 3); + + assert($filename !== ''); + } + + $parentParts = explode('/', trim(str_replace('\\', '/', $this->baselineDirectory), '/')); + $parentPartsCount = count($parentParts); + $filenameParts = explode('/', trim(str_replace('\\', '/', $filename), '/')); + $filenamePartsCount = count($filenameParts); + + $i = 0; + + for (; $i < $filenamePartsCount; $i++) { + if ($parentPartsCount < $i + 1) { + break; + } + + $parentPath = implode('/', array_slice($parentParts, 0, $i + 1)); + $filenamePath = implode('/', array_slice($filenameParts, 0, $i + 1)); + + if ($parentPath !== $filenamePath) { + break; + } + } + + if ($i === 0) { + return [$filename]; + } + + $dotsCount = $parentPartsCount - $i; + + assert($dotsCount >= 0); + + return array_merge(array_fill(0, $dotsCount, '..'), array_slice($filenameParts, $i)); + } +} diff --git a/src/Runner/Baseline/Subscriber/Subscriber.php b/src/Runner/Baseline/Subscriber/Subscriber.php new file mode 100644 index 00000000000..59ca634b1be --- /dev/null +++ b/src/Runner/Baseline/Subscriber/Subscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Subscriber +{ + private Generator $generator; + + public function __construct(Generator $generator) + { + $this->generator = $generator; + } + + protected function generator(): Generator + { + return $this->generator; + } +} diff --git a/src/Runner/Baseline/Subscriber/TestTriggeredDeprecationSubscriber.php b/src/Runner/Baseline/Subscriber/TestTriggeredDeprecationSubscriber.php new file mode 100644 index 00000000000..72e26110609 --- /dev/null +++ b/src/Runner/Baseline/Subscriber/TestTriggeredDeprecationSubscriber.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\Test\DeprecationTriggeredSubscriber; +use PHPUnit\Runner\FileDoesNotExistException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredDeprecationSubscriber extends Subscriber implements DeprecationTriggeredSubscriber +{ + /** + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + */ + public function notify(DeprecationTriggered $event): void + { + $this->generator()->testTriggeredIssue($event); + } +} diff --git a/src/Runner/Baseline/Subscriber/TestTriggeredNoticeSubscriber.php b/src/Runner/Baseline/Subscriber/TestTriggeredNoticeSubscriber.php new file mode 100644 index 00000000000..288d0eff020 --- /dev/null +++ b/src/Runner/Baseline/Subscriber/TestTriggeredNoticeSubscriber.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use PHPUnit\Event\Test\NoticeTriggered; +use PHPUnit\Event\Test\NoticeTriggeredSubscriber; +use PHPUnit\Runner\FileDoesNotExistException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredNoticeSubscriber extends Subscriber implements NoticeTriggeredSubscriber +{ + /** + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + */ + public function notify(NoticeTriggered $event): void + { + $this->generator()->testTriggeredIssue($event); + } +} diff --git a/src/Runner/Baseline/Subscriber/TestTriggeredPhpDeprecationSubscriber.php b/src/Runner/Baseline/Subscriber/TestTriggeredPhpDeprecationSubscriber.php new file mode 100644 index 00000000000..f72095ac18d --- /dev/null +++ b/src/Runner/Baseline/Subscriber/TestTriggeredPhpDeprecationSubscriber.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use PHPUnit\Event\Test\PhpDeprecationTriggered; +use PHPUnit\Event\Test\PhpDeprecationTriggeredSubscriber; +use PHPUnit\Runner\FileDoesNotExistException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpDeprecationSubscriber extends Subscriber implements PhpDeprecationTriggeredSubscriber +{ + /** + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + */ + public function notify(PhpDeprecationTriggered $event): void + { + $this->generator()->testTriggeredIssue($event); + } +} diff --git a/src/Runner/Baseline/Subscriber/TestTriggeredPhpNoticeSubscriber.php b/src/Runner/Baseline/Subscriber/TestTriggeredPhpNoticeSubscriber.php new file mode 100644 index 00000000000..9707a46b3fb --- /dev/null +++ b/src/Runner/Baseline/Subscriber/TestTriggeredPhpNoticeSubscriber.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use PHPUnit\Event\Test\PhpNoticeTriggered; +use PHPUnit\Event\Test\PhpNoticeTriggeredSubscriber; +use PHPUnit\Runner\FileDoesNotExistException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpNoticeSubscriber extends Subscriber implements PhpNoticeTriggeredSubscriber +{ + /** + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + */ + public function notify(PhpNoticeTriggered $event): void + { + $this->generator()->testTriggeredIssue($event); + } +} diff --git a/src/Runner/Baseline/Subscriber/TestTriggeredPhpWarningSubscriber.php b/src/Runner/Baseline/Subscriber/TestTriggeredPhpWarningSubscriber.php new file mode 100644 index 00000000000..22af95db419 --- /dev/null +++ b/src/Runner/Baseline/Subscriber/TestTriggeredPhpWarningSubscriber.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use PHPUnit\Event\Test\PhpWarningTriggered; +use PHPUnit\Event\Test\PhpWarningTriggeredSubscriber; +use PHPUnit\Runner\FileDoesNotExistException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpWarningSubscriber extends Subscriber implements PhpWarningTriggeredSubscriber +{ + /** + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + */ + public function notify(PhpWarningTriggered $event): void + { + $this->generator()->testTriggeredIssue($event); + } +} diff --git a/src/Runner/Baseline/Subscriber/TestTriggeredWarningSubscriber.php b/src/Runner/Baseline/Subscriber/TestTriggeredWarningSubscriber.php new file mode 100644 index 00000000000..fd5e0db6fc1 --- /dev/null +++ b/src/Runner/Baseline/Subscriber/TestTriggeredWarningSubscriber.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use PHPUnit\Event\Test\WarningTriggered; +use PHPUnit\Event\Test\WarningTriggeredSubscriber; +use PHPUnit\Runner\FileDoesNotExistException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredWarningSubscriber extends Subscriber implements WarningTriggeredSubscriber +{ + /** + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + */ + public function notify(WarningTriggered $event): void + { + $this->generator()->testTriggeredIssue($event); + } +} diff --git a/src/Runner/Baseline/Writer.php b/src/Runner/Baseline/Writer.php new file mode 100644 index 00000000000..7d7d7645b71 --- /dev/null +++ b/src/Runner/Baseline/Writer.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use function dirname; +use function file_put_contents; +use function is_dir; +use function realpath; +use function sprintf; +use XMLWriter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Writer +{ + /** + * @param non-empty-string $baselineFile + * + * @throws CannotWriteBaselineException + */ + public function write(string $baselineFile, Baseline $baseline): void + { + $normalizedBaselineFile = realpath(dirname($baselineFile)); + + if ($normalizedBaselineFile === false || !is_dir($normalizedBaselineFile)) { + throw new CannotWriteBaselineException(sprintf('Cannot write baseline to "%s".', $baselineFile)); + } + + $pathCalculator = new RelativePathCalculator($normalizedBaselineFile); + + $writer = new XMLWriter; + + $writer->openMemory(); + $writer->setIndent(true); + $writer->startDocument(); + + $writer->startElement('files'); + $writer->writeAttribute('version', (string) Baseline::VERSION); + + foreach ($baseline->groupedByFileAndLine() as $file => $lines) { + $writer->startElement('file'); + $writer->writeAttribute('path', $pathCalculator->calculate($file)); + + foreach ($lines as $line => $issues) { + $writer->startElement('line'); + $writer->writeAttribute('number', (string) $line); + $writer->writeAttribute('hash', $issues[0]->hash()); + + foreach ($issues as $issue) { + $writer->startElement('issue'); + $writer->writeCdata($issue->description()); + $writer->endElement(); + } + + $writer->endElement(); + } + + $writer->endElement(); + } + + $writer->endElement(); + + file_put_contents($baselineFile, $writer->outputMemory()); + } +} diff --git a/src/Runner/CodeCoverage.php b/src/Runner/CodeCoverage.php new file mode 100644 index 00000000000..28620e4165e --- /dev/null +++ b/src/Runner/CodeCoverage.php @@ -0,0 +1,488 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use function assert; +use function file_put_contents; +use function sprintf; +use function sys_get_temp_dir; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Framework\TestCase; +use PHPUnit\TextUI\Configuration\CodeCoverageFilterRegistry; +use PHPUnit\TextUI\Configuration\Configuration; +use PHPUnit\TextUI\Output\Printer; +use PHPUnit\Util\Filesystem; +use SebastianBergmann\CodeCoverage\Driver\Driver; +use SebastianBergmann\CodeCoverage\Driver\Selector; +use SebastianBergmann\CodeCoverage\Exception as CodeCoverageException; +use SebastianBergmann\CodeCoverage\Filter; +use SebastianBergmann\CodeCoverage\Report\Clover as CloverReport; +use SebastianBergmann\CodeCoverage\Report\Cobertura as CoberturaReport; +use SebastianBergmann\CodeCoverage\Report\Crap4j as Crap4jReport; +use SebastianBergmann\CodeCoverage\Report\Html\Colors; +use SebastianBergmann\CodeCoverage\Report\Html\CustomCssFile; +use SebastianBergmann\CodeCoverage\Report\Html\Facade as HtmlReport; +use SebastianBergmann\CodeCoverage\Report\OpenClover as OpenCloverReport; +use SebastianBergmann\CodeCoverage\Report\PHP as PhpReport; +use SebastianBergmann\CodeCoverage\Report\Text as TextReport; +use SebastianBergmann\CodeCoverage\Report\Thresholds; +use SebastianBergmann\CodeCoverage\Report\Xml\Facade as XmlReport; +use SebastianBergmann\CodeCoverage\StaticAnalysis\CacheWarmer; +use SebastianBergmann\CodeCoverage\Test\Target\TargetCollection; +use SebastianBergmann\CodeCoverage\Test\Target\ValidationFailure; +use SebastianBergmann\CodeCoverage\Test\TestSize\TestSize; +use SebastianBergmann\CodeCoverage\Test\TestStatus\TestStatus; +use SebastianBergmann\Comparator\Comparator; +use SebastianBergmann\Timer\NoActiveTimerException; +use SebastianBergmann\Timer\Timer; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @codeCoverageIgnore + */ +final class CodeCoverage +{ + private static ?self $instance = null; + private ?\SebastianBergmann\CodeCoverage\CodeCoverage $codeCoverage = null; + + /** + * @phpstan-ignore property.internalClass + */ + private ?Driver $driver = null; + private bool $collecting = false; + private ?TestCase $test = null; + private ?Timer $timer = null; + + public static function instance(): self + { + if (self::$instance === null) { + self::$instance = new self; + } + + return self::$instance; + } + + public function init(Configuration $configuration, CodeCoverageFilterRegistry $codeCoverageFilterRegistry, bool $extensionRequiresCodeCoverageCollection): CodeCoverageInitializationStatus + { + $codeCoverageFilterRegistry->init($configuration); + + if (!$configuration->hasCoverageReport() && !$extensionRequiresCodeCoverageCollection) { + return CodeCoverageInitializationStatus::NOT_REQUESTED; + } + + $this->activate($codeCoverageFilterRegistry->get(), $configuration->pathCoverage()); + + if (!$this->isActive()) { + return CodeCoverageInitializationStatus::FAILED; + } + + if ($configuration->hasCoverageCacheDirectory()) { + $coverageCacheDirectory = $configuration->coverageCacheDirectory(); + } else { + $candidate = sys_get_temp_dir() . '/phpunit-code-coverage-cache'; + + if (Filesystem::createDirectory($candidate)) { + $coverageCacheDirectory = $candidate; + } + } + + if (isset($coverageCacheDirectory)) { + $this->codeCoverage()->cacheStaticAnalysis($coverageCacheDirectory); + } + + $this->codeCoverage()->excludeSubclassesOfThisClassFromUnintentionallyCoveredCodeCheck(Comparator::class); + + if ($configuration->strictCoverage()) { + $this->codeCoverage()->enableCheckForUnintentionallyCoveredCode(); + } + + if ($configuration->ignoreDeprecatedCodeUnitsFromCodeCoverage()) { + $this->codeCoverage()->ignoreDeprecatedCode(); + } else { + $this->codeCoverage()->doNotIgnoreDeprecatedCode(); + } + + if ($configuration->disableCodeCoverageIgnore()) { + $this->codeCoverage()->disableAnnotationsForIgnoringCode(); + } else { + $this->codeCoverage()->enableAnnotationsForIgnoringCode(); + } + + if ($configuration->includeUncoveredFiles()) { + $this->codeCoverage()->includeUncoveredFiles(); + } else { + $this->codeCoverage()->excludeUncoveredFiles(); + } + + if ($codeCoverageFilterRegistry->get()->isEmpty()) { + if (!$codeCoverageFilterRegistry->configured()) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + 'No filter is configured, code coverage will not be processed', + ); + } else { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + 'Incorrect filter configuration, code coverage will not be processed', + ); + } + + $this->deactivate(); + } + + if (isset($coverageCacheDirectory) && $configuration->includeUncoveredFiles()) { + EventFacade::emitter()->testRunnerStartedStaticAnalysisForCodeCoverage(); + + /** @phpstan-ignore new.internalClass,method.internalClass */ + $statistics = (new CacheWarmer)->warmCache( + $coverageCacheDirectory, + !$configuration->disableCodeCoverageIgnore(), + $configuration->ignoreDeprecatedCodeUnitsFromCodeCoverage(), + $codeCoverageFilterRegistry->get(), + ); + + EventFacade::emitter()->testRunnerFinishedStaticAnalysisForCodeCoverage( + $statistics['cacheHits'], + $statistics['cacheMisses'], + ); + } + + return CodeCoverageInitializationStatus::SUCCEEDED; + } + + /** + * @phpstan-assert-if-true !null $this->codeCoverage + */ + public function isActive(): bool + { + return $this->codeCoverage !== null; + } + + public function codeCoverage(): \SebastianBergmann\CodeCoverage\CodeCoverage + { + return $this->codeCoverage; + } + + /** + * @return non-empty-string + */ + public function driverNameAndVersion(): string + { + return $this->driver->nameAndVersion(); + } + + public function start(TestCase $test): void + { + if ($this->collecting) { + return; + } + + $size = TestSize::unknown(); + + if ($test->size()->isSmall()) { + $size = TestSize::small(); + } elseif ($test->size()->isMedium()) { + $size = TestSize::medium(); + } elseif ($test->size()->isLarge()) { + $size = TestSize::large(); + } + + $this->test = $test; + + $this->codeCoverage->start( + $test->valueObjectForEvents()->id(), + $size, + ); + + $this->collecting = true; + } + + public function stop(bool $append, null|false|TargetCollection $covers = null, ?TargetCollection $uses = null): void + { + if (!$this->collecting) { + return; + } + + $status = TestStatus::unknown(); + + if ($this->test !== null) { + if ($this->test->status()->isSuccess()) { + $status = TestStatus::success(); + } else { + $status = TestStatus::failure(); + } + } + + if ($covers instanceof TargetCollection) { + $result = $this->codeCoverage->validate($covers); + + if ($result->isFailure()) { + assert($result instanceof ValidationFailure); + + EventFacade::emitter()->testTriggeredPhpunitWarning( + $this->test->valueObjectForEvents(), + $result->message(), + ); + + $append = false; + } + } + + if ($uses instanceof TargetCollection) { + $result = $this->codeCoverage->validate($uses); + + if ($result->isFailure()) { + assert($result instanceof ValidationFailure); + + EventFacade::emitter()->testTriggeredPhpunitWarning( + $this->test->valueObjectForEvents(), + $result->message(), + ); + + $append = false; + } + } + + $this->codeCoverage->stop($append, $status, $covers, $uses); + + $this->test = null; + $this->collecting = false; + } + + public function deactivate(): void + { + $this->driver = null; + $this->codeCoverage = null; + $this->test = null; + } + + public function generateReports(Printer $printer, Configuration $configuration): void + { + if (!$this->isActive()) { + return; + } + + if ($configuration->hasCoveragePhp()) { + $this->codeCoverageGenerationStart($printer, 'PHP'); + + try { + $writer = new PhpReport; + $writer->process($this->codeCoverage(), $configuration->coveragePhp()); + + $this->codeCoverageGenerationSucceeded($printer); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($printer, $e); + } + } + + if ($configuration->hasCoverageClover()) { + $this->codeCoverageGenerationStart($printer, 'Clover XML'); + + try { + $writer = new CloverReport; + $writer->process($this->codeCoverage(), $configuration->coverageClover(), 'Clover Coverage'); + + $this->codeCoverageGenerationSucceeded($printer); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($printer, $e); + } + } + + if ($configuration->hasCoverageOpenClover()) { + $this->codeCoverageGenerationStart($printer, 'OpenClover XML'); + + try { + $writer = new OpenCloverReport; + $writer->process($this->codeCoverage(), $configuration->coverageOpenClover(), 'OpenClover Coverage'); + + $this->codeCoverageGenerationSucceeded($printer); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($printer, $e); + } + } + + if ($configuration->hasCoverageCobertura()) { + $this->codeCoverageGenerationStart($printer, 'Cobertura XML'); + + try { + $writer = new CoberturaReport; + $writer->process($this->codeCoverage(), $configuration->coverageCobertura()); + + $this->codeCoverageGenerationSucceeded($printer); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($printer, $e); + } + } + + if ($configuration->hasCoverageCrap4j()) { + $this->codeCoverageGenerationStart($printer, 'Crap4J XML'); + + try { + $writer = new Crap4jReport($configuration->coverageCrap4jThreshold()); + $writer->process($this->codeCoverage(), $configuration->coverageCrap4j()); + + $this->codeCoverageGenerationSucceeded($printer); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($printer, $e); + } + } + + if ($configuration->hasCoverageHtml()) { + $this->codeCoverageGenerationStart($printer, 'HTML'); + + try { + $customCssFile = CustomCssFile::default(); + + if ($configuration->hasCoverageHtmlCustomCssFile()) { + $customCssFile = CustomCssFile::from($configuration->coverageHtmlCustomCssFile()); + } + + $writer = new HtmlReport( + sprintf( + ' and PHPUnit %s', + Version::id(), + ), + Colors::from( + $configuration->coverageHtmlColorSuccessLow(), + $configuration->coverageHtmlColorSuccessMedium(), + $configuration->coverageHtmlColorSuccessHigh(), + $configuration->coverageHtmlColorWarning(), + $configuration->coverageHtmlColorDanger(), + ), + Thresholds::from( + $configuration->coverageHtmlLowUpperBound(), + $configuration->coverageHtmlHighLowerBound(), + ), + $customCssFile, + ); + + $writer->process($this->codeCoverage(), $configuration->coverageHtml()); + + $this->codeCoverageGenerationSucceeded($printer); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($printer, $e); + } + } + + if ($configuration->hasCoverageText()) { + $processor = new TextReport( + Thresholds::default(), + $configuration->coverageTextShowUncoveredFiles(), + $configuration->coverageTextShowOnlySummary(), + ); + + $textReport = $processor->process($this->codeCoverage(), $configuration->colors()); + + if ($configuration->coverageText() === 'php://stdout') { + if (!$configuration->noOutput() && !$configuration->debug()) { + $printer->print($textReport); + } + } else { + file_put_contents($configuration->coverageText(), $textReport); + } + } + + if ($configuration->hasCoverageXml()) { + $this->codeCoverageGenerationStart($printer, 'PHPUnit XML'); + + try { + $writer = new XmlReport(Version::id()); + $writer->process($this->codeCoverage(), $configuration->coverageXml()); + + $this->codeCoverageGenerationSucceeded($printer); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($printer, $e); + } + } + } + + private function activate(Filter $filter, bool $pathCoverage): void + { + try { + if ($pathCoverage) { + $this->driver = (new Selector)->forLineAndPathCoverage($filter); + } else { + $this->driver = (new Selector)->forLineCoverage($filter); + } + + $this->codeCoverage = new \SebastianBergmann\CodeCoverage\CodeCoverage( + $this->driver, + $filter, + ); + } catch (CodeCoverageException $e) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + $e->getMessage(), + ); + } + } + + private function codeCoverageGenerationStart(Printer $printer, string $format): void + { + $printer->print( + sprintf( + "\nGenerating code coverage report in %s format ... ", + $format, + ), + ); + + $this->timer()->start(); + } + + /** + * @throws NoActiveTimerException + */ + private function codeCoverageGenerationSucceeded(Printer $printer): void + { + $printer->print( + sprintf( + "done [%s]\n", + $this->timer()->stop()->asString(), + ), + ); + } + + /** + * @throws NoActiveTimerException + */ + private function codeCoverageGenerationFailed(Printer $printer, CodeCoverageException $e): void + { + $printer->print( + sprintf( + "failed [%s]\n%s\n", + $this->timer()->stop()->asString(), + $e->getMessage(), + ), + ); + } + + private function timer(): Timer + { + if ($this->timer === null) { + $this->timer = new Timer; + } + + return $this->timer; + } +} diff --git a/src/Runner/CodeCoverageInitializationStatus.php b/src/Runner/CodeCoverageInitializationStatus.php new file mode 100644 index 00000000000..ce895f6e6cf --- /dev/null +++ b/src/Runner/CodeCoverageInitializationStatus.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This enumeration is not covered by the backward compatibility promise for PHPUnit + */ +enum CodeCoverageInitializationStatus +{ + case NOT_REQUESTED; + case SUCCEEDED; + case FAILED; +} diff --git a/src/Runner/DefaultTestResultCache.php b/src/Runner/DefaultTestResultCache.php deleted file mode 100644 index 906a28f6d5a..00000000000 --- a/src/Runner/DefaultTestResultCache.php +++ /dev/null @@ -1,233 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -use const DIRECTORY_SEPARATOR; -use function assert; -use function defined; -use function dirname; -use function file_get_contents; -use function file_put_contents; -use function in_array; -use function is_dir; -use function is_file; -use function is_float; -use function is_int; -use function is_string; -use function serialize; -use function sprintf; -use function unserialize; -use PHPUnit\Util\ErrorHandler; -use PHPUnit\Util\Filesystem; -use Serializable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class DefaultTestResultCache implements Serializable, TestResultCache -{ - /** - * @var string - */ - public const DEFAULT_RESULT_CACHE_FILENAME = '.phpunit.result.cache'; - - /** - * Provide extra protection against incomplete or corrupt caches. - * - * @var int[] - */ - private const ALLOWED_CACHE_TEST_STATUSES = [ - BaseTestRunner::STATUS_SKIPPED, - BaseTestRunner::STATUS_INCOMPLETE, - BaseTestRunner::STATUS_FAILURE, - BaseTestRunner::STATUS_ERROR, - BaseTestRunner::STATUS_RISKY, - BaseTestRunner::STATUS_WARNING, - ]; - - /** - * Path and filename for result cache file. - * - * @var string - */ - private $cacheFilename; - - /** - * The list of defective tests. - * - * - * // Mark a test skipped - * $this->defects[$testName] = BaseTestRunner::TEST_SKIPPED; - * - * - * @var array - */ - private $defects = []; - - /** - * The list of execution duration of suites and tests (in seconds). - * - * - * // Record running time for test - * $this->times[$testName] = 1.234; - * - * - * @var array - */ - private $times = []; - - public function __construct(?string $filepath = null) - { - if ($filepath !== null && is_dir($filepath)) { - // cache path provided, use default cache filename in that location - $filepath .= DIRECTORY_SEPARATOR . self::DEFAULT_RESULT_CACHE_FILENAME; - } - - $this->cacheFilename = $filepath ?? $_ENV['PHPUNIT_RESULT_CACHE'] ?? self::DEFAULT_RESULT_CACHE_FILENAME; - } - - /** - * @throws Exception - */ - public function persist(): void - { - $this->saveToFile(); - } - - /** - * @throws Exception - */ - public function saveToFile(): void - { - if (defined('PHPUNIT_TESTSUITE_RESULTCACHE')) { - return; - } - - if (!Filesystem::createDirectory(dirname($this->cacheFilename))) { - throw new Exception( - sprintf( - 'Cannot create directory "%s" for result cache file', - $this->cacheFilename - ) - ); - } - - file_put_contents( - $this->cacheFilename, - serialize($this) - ); - } - - public function setState(string $testName, int $state): void - { - if ($state !== BaseTestRunner::STATUS_PASSED) { - $this->defects[$testName] = $state; - } - } - - public function getState(string $testName): int - { - return $this->defects[$testName] ?? BaseTestRunner::STATUS_UNKNOWN; - } - - public function setTime(string $testName, float $time): void - { - $this->times[$testName] = $time; - } - - public function getTime(string $testName): float - { - return $this->times[$testName] ?? 0.0; - } - - public function load(): void - { - $this->clear(); - - if (!is_file($this->cacheFilename)) { - return; - } - - $cacheData = @file_get_contents($this->cacheFilename); - - // @codeCoverageIgnoreStart - if ($cacheData === false) { - return; - } - // @codeCoverageIgnoreEnd - - $cache = ErrorHandler::invokeIgnoringWarnings( - static function () use ($cacheData) { - return @unserialize($cacheData, ['allowed_classes' => [self::class]]); - } - ); - - if ($cache === false) { - return; - } - - if ($cache instanceof self) { - /* @var DefaultTestResultCache $cache */ - $cache->copyStateToCache($this); - } - } - - public function copyStateToCache(self $targetCache): void - { - foreach ($this->defects as $name => $state) { - $targetCache->setState($name, $state); - } - - foreach ($this->times as $name => $time) { - $targetCache->setTime($name, $time); - } - } - - public function clear(): void - { - $this->defects = []; - $this->times = []; - } - - public function serialize(): string - { - return serialize([ - 'defects' => $this->defects, - 'times' => $this->times, - ]); - } - - /** - * @param string $serialized - */ - public function unserialize($serialized): void - { - $data = unserialize($serialized); - - if (isset($data['times'])) { - foreach ($data['times'] as $testName => $testTime) { - assert(is_string($testName)); - assert(is_float($testTime)); - $this->times[$testName] = $testTime; - } - } - - if (isset($data['defects'])) { - foreach ($data['defects'] as $testName => $testResult) { - assert(is_string($testName)); - assert(is_int($testResult)); - - if (in_array($testResult, self::ALLOWED_CACHE_TEST_STATUSES, true)) { - $this->defects[$testName] = $testResult; - } - } - } - } -} diff --git a/src/Runner/DeprecationCollector/Collector.php b/src/Runner/DeprecationCollector/Collector.php new file mode 100644 index 00000000000..575bc2dd335 --- /dev/null +++ b/src/Runner/DeprecationCollector/Collector.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\DeprecationCollector; + +use PHPUnit\Event\Facade; +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\TestRunner\IssueFilter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Collector +{ + private readonly IssueFilter $issueFilter; + + /** + * @var list + */ + private array $deprecations = []; + + /** + * @var list + */ + private array $filteredDeprecations = []; + + public function __construct(Facade $facade, IssueFilter $issueFilter) + { + $facade->registerSubscribers( + new TestPreparedSubscriber($this), + new TestTriggeredDeprecationSubscriber($this), + ); + + $this->issueFilter = $issueFilter; + } + + /** + * @return list + */ + public function deprecations(): array + { + return $this->deprecations; + } + + /** + * @return list + */ + public function filteredDeprecations(): array + { + return $this->filteredDeprecations; + } + + public function testPrepared(): void + { + $this->deprecations = []; + } + + public function testTriggeredDeprecation(DeprecationTriggered $event): void + { + $this->deprecations[] = $event->message(); + + if (!$this->issueFilter->shouldBeProcessed($event)) { + return; + } + + $this->filteredDeprecations[] = $event->message(); + } +} diff --git a/src/Runner/DeprecationCollector/Facade.php b/src/Runner/DeprecationCollector/Facade.php new file mode 100644 index 00000000000..a08fdc64c3b --- /dev/null +++ b/src/Runner/DeprecationCollector/Facade.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\DeprecationCollector; + +use PHPUnit\Event\EventFacadeIsSealedException; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Event\UnknownSubscriberTypeException; +use PHPUnit\TestRunner\IssueFilter; +use PHPUnit\TextUI\Configuration\Registry as ConfigurationRegistry; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Facade +{ + private static null|Collector|InIsolationCollector $collector = null; + private static bool $inIsolation = false; + + public static function init(): void + { + self::collector(); + } + + public static function initForIsolation(): void + { + self::collector(); + + self::$inIsolation = true; + } + + /** + * @return list + */ + public static function deprecations(): array + { + return self::collector()->deprecations(); + } + + /** + * @return list + */ + public static function filteredDeprecations(): array + { + return self::collector()->filteredDeprecations(); + } + + /** + * @throws EventFacadeIsSealedException + * @throws UnknownSubscriberTypeException + */ + public static function collector(): Collector|InIsolationCollector + { + if (self::$collector !== null) { + return self::$collector; + } + + $issueFilter = new IssueFilter( + ConfigurationRegistry::get()->source(), + ); + + if (self::$inIsolation) { + self::$collector = new InIsolationCollector( + $issueFilter, + ); + + return self::$collector; + } + + self::$collector = new Collector( + EventFacade::instance(), + $issueFilter, + ); + + return self::$collector; + } +} diff --git a/src/Runner/DeprecationCollector/InIsolationCollector.php b/src/Runner/DeprecationCollector/InIsolationCollector.php new file mode 100644 index 00000000000..31287622175 --- /dev/null +++ b/src/Runner/DeprecationCollector/InIsolationCollector.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\DeprecationCollector; + +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\TestRunner\IssueFilter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InIsolationCollector +{ + private readonly IssueFilter $issueFilter; + + /** + * @var list + */ + private array $deprecations = []; + + /** + * @var list + */ + private array $filteredDeprecations = []; + + public function __construct(IssueFilter $issueFilter) + { + $this->issueFilter = $issueFilter; + } + + /** + * @return list + */ + public function deprecations(): array + { + return $this->deprecations; + } + + /** + * @return list + */ + public function filteredDeprecations(): array + { + return $this->filteredDeprecations; + } + + public function testTriggeredDeprecation(DeprecationTriggered $event): void + { + $this->deprecations[] = $event->message(); + + if (!$this->issueFilter->shouldBeProcessed($event)) { + return; + } + + $this->filteredDeprecations[] = $event->message(); + } +} diff --git a/src/Runner/DeprecationCollector/Subscriber/Subscriber.php b/src/Runner/DeprecationCollector/Subscriber/Subscriber.php new file mode 100644 index 00000000000..65af6ab7060 --- /dev/null +++ b/src/Runner/DeprecationCollector/Subscriber/Subscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\DeprecationCollector; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract class Subscriber +{ + private readonly Collector|InIsolationCollector $collector; + + public function __construct(Collector|InIsolationCollector $collector) + { + $this->collector = $collector; + } + + protected function collector(): Collector|InIsolationCollector + { + return $this->collector; + } +} diff --git a/src/Runner/DeprecationCollector/Subscriber/TestPreparedSubscriber.php b/src/Runner/DeprecationCollector/Subscriber/TestPreparedSubscriber.php new file mode 100644 index 00000000000..0e78c31a422 --- /dev/null +++ b/src/Runner/DeprecationCollector/Subscriber/TestPreparedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\DeprecationCollector; + +use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\PreparedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestPreparedSubscriber extends Subscriber implements PreparedSubscriber +{ + public function notify(Prepared $event): void + { + $this->collector()->testPrepared(); + } +} diff --git a/src/Runner/DeprecationCollector/Subscriber/TestTriggeredDeprecationSubscriber.php b/src/Runner/DeprecationCollector/Subscriber/TestTriggeredDeprecationSubscriber.php new file mode 100644 index 00000000000..a01f1b61ea2 --- /dev/null +++ b/src/Runner/DeprecationCollector/Subscriber/TestTriggeredDeprecationSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\DeprecationCollector; + +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\Test\DeprecationTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestTriggeredDeprecationSubscriber extends Subscriber implements DeprecationTriggeredSubscriber +{ + public function notify(DeprecationTriggered $event): void + { + $this->collector()->testTriggeredDeprecation($event); + } +} diff --git a/src/Runner/ErrorHandler.php b/src/Runner/ErrorHandler.php new file mode 100644 index 00000000000..e97d47886ea --- /dev/null +++ b/src/Runner/ErrorHandler.php @@ -0,0 +1,512 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use const DEBUG_BACKTRACE_IGNORE_ARGS; +use const E_COMPILE_ERROR; +use const E_COMPILE_WARNING; +use const E_CORE_ERROR; +use const E_CORE_WARNING; +use const E_DEPRECATED; +use const E_ERROR; +use const E_NOTICE; +use const E_PARSE; +use const E_RECOVERABLE_ERROR; +use const E_USER_DEPRECATED; +use const E_USER_ERROR; +use const E_USER_NOTICE; +use const E_USER_WARNING; +use const E_WARNING; +use function array_keys; +use function array_values; +use function assert; +use function debug_backtrace; +use function defined; +use function error_reporting; +use function preg_match; +use function restore_error_handler; +use function set_error_handler; +use function sprintf; +use PHPUnit\Event; +use PHPUnit\Event\Code\IssueTrigger\IssueTrigger; +use PHPUnit\Event\Code\NoTestCaseObjectOnCallStackException; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Framework\TestCase; +use PHPUnit\Metadata\IgnoreDeprecations; +use PHPUnit\Runner\Baseline\Baseline; +use PHPUnit\Runner\Baseline\Issue; +use PHPUnit\TextUI\Configuration\Registry; +use PHPUnit\TextUI\Configuration\Source; +use PHPUnit\TextUI\Configuration\SourceFilter; +use PHPUnit\Util\ExcludeList; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ErrorHandler +{ + private const int UNHANDLEABLE_LEVELS = E_ERROR | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING; + private const int INSUPPRESSIBLE_LEVELS = E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR; + private static ?self $instance = null; + private ?Baseline $baseline = null; + private bool $enabled = false; + private ?int $originalErrorReportingLevel = null; + private readonly Source $source; + + /** + * @var list + */ + private array $globalDeprecations = []; + + /** + * @var array> + */ + private array $testCaseContextDeprecations = []; + private ?string $testCaseContext = null; + + /** + * @var ?array{functions: list, methods: list} + */ + private ?array $deprecationTriggers = null; + + public static function instance(): self + { + return self::$instance ?? self::$instance = new self(Registry::get()->source()); + } + + private function __construct(Source $source) + { + $this->source = $source; + } + + /** + * @throws NoTestCaseObjectOnCallStackException + */ + public function __invoke(int $errorNumber, string $errorString, string $errorFile, int $errorLine): false + { + $suppressed = (error_reporting() & ~self::INSUPPRESSIBLE_LEVELS) === 0; + + if ($suppressed && (new ExcludeList)->isExcluded($errorFile)) { + return false; + } + + /** + * E_STRICT is deprecated since PHP 8.4. + * + * @see https://github.com/sebastianbergmann/phpunit/issues/5956 + */ + if (defined('E_STRICT') && $errorNumber === 2048) { + $errorNumber = E_NOTICE; + } + + $test = Event\Code\TestMethodBuilder::fromCallStack(); + + if ($errorNumber === E_USER_DEPRECATED) { + $deprecationFrame = $this->guessDeprecationFrame(); + $errorFile = $deprecationFrame['file'] ?? $errorFile; + $errorLine = $deprecationFrame['line'] ?? $errorLine; + } + + $ignoredByBaseline = $this->ignoredByBaseline($errorFile, $errorLine, $errorString); + $ignoredByTest = $this->deprecationIgnoredByTest($test, $errorString); + + switch ($errorNumber) { + case E_NOTICE: + Event\Facade::emitter()->testTriggeredPhpNotice( + $test, + $errorString, + $errorFile, + $errorLine, + $suppressed, + $ignoredByBaseline, + ); + + break; + + case E_USER_NOTICE: + Event\Facade::emitter()->testTriggeredNotice( + $test, + $errorString, + $errorFile, + $errorLine, + $suppressed, + $ignoredByBaseline, + ); + + break; + + case E_WARNING: + Event\Facade::emitter()->testTriggeredPhpWarning( + $test, + $errorString, + $errorFile, + $errorLine, + $suppressed, + $ignoredByBaseline, + ); + + break; + + case E_USER_WARNING: + Event\Facade::emitter()->testTriggeredWarning( + $test, + $errorString, + $errorFile, + $errorLine, + $suppressed, + $ignoredByBaseline, + ); + + break; + + case E_DEPRECATED: + Event\Facade::emitter()->testTriggeredPhpDeprecation( + $test, + $errorString, + $errorFile, + $errorLine, + $suppressed, + $ignoredByBaseline, + $ignoredByTest, + $this->trigger($test, false), + ); + + break; + + case E_USER_DEPRECATED: + Event\Facade::emitter()->testTriggeredDeprecation( + $test, + $errorString, + $errorFile, + $errorLine, + $suppressed, + $ignoredByBaseline, + $ignoredByTest, + $this->trigger($test, true), + $this->stackTrace(), + ); + + break; + + case E_USER_ERROR: + Event\Facade::emitter()->testTriggeredError( + $test, + $errorString, + $errorFile, + $errorLine, + $suppressed, + ); + + throw new ErrorException('E_USER_ERROR was triggered'); + + default: + return false; + } + + return false; + } + + public function deprecationHandler(int $errorNumber, string $errorString, string $errorFile, int $errorLine): true + { + if ($this->testCaseContext !== null) { + $this->testCaseContextDeprecations[$this->testCaseContext][] = [$errorNumber, $errorString, $errorFile, $errorLine]; + } else { + $this->globalDeprecations[] = [$errorNumber, $errorString, $errorFile, $errorLine]; + } + + return true; + } + + public function registerDeprecationHandler(): void + { + set_error_handler([self::$instance, 'deprecationHandler'], E_USER_DEPRECATED | E_DEPRECATED); + } + + public function restoreDeprecationHandler(): void + { + restore_error_handler(); + } + + public function enable(TestCase $test): void + { + if ($this->enabled) { + return; + } + + $oldErrorHandler = set_error_handler($this); + + if ($oldErrorHandler !== null) { + restore_error_handler(); + + return; + } + + $this->enabled = true; + $this->originalErrorReportingLevel = error_reporting(); + + $this->triggerGlobalDeprecations($test); + + error_reporting($this->originalErrorReportingLevel & self::UNHANDLEABLE_LEVELS); + } + + public function disable(): void + { + if (!$this->enabled) { + return; + } + + restore_error_handler(); + + error_reporting(error_reporting() | $this->originalErrorReportingLevel); + + $this->enabled = false; + $this->originalErrorReportingLevel = null; + } + + public function useBaseline(Baseline $baseline): void + { + $this->baseline = $baseline; + } + + /** + * @param array{functions: list, methods: list} $deprecationTriggers + */ + public function useDeprecationTriggers(array $deprecationTriggers): void + { + $this->deprecationTriggers = $deprecationTriggers; + } + + public function enterTestCaseContext(string $className, string $methodName): void + { + $this->testCaseContext = $this->testCaseContext($className, $methodName); + } + + public function leaveTestCaseContext(): void + { + $this->testCaseContext = null; + } + + /** + * @param non-empty-string $file + * @param positive-int $line + * @param non-empty-string $description + */ + private function ignoredByBaseline(string $file, int $line, string $description): bool + { + if ($this->baseline === null) { + return false; + } + + return $this->baseline->has(Issue::from($file, $line, null, $description)); + } + + private function trigger(TestMethod $test, bool $filterTrigger): IssueTrigger + { + if (!$this->source->notEmpty()) { + return IssueTrigger::unknown(); + } + + $trace = $this->filteredStackTrace($filterTrigger); + + $triggeredInFirstPartyCode = false; + $triggerCalledFromFirstPartyCode = false; + + if (isset($trace[0]['file'])) { + if ($trace[0]['file'] === $test->file()) { + return IssueTrigger::test(); + } + + if (SourceFilter::instance()->includes($trace[0]['file'])) { + $triggeredInFirstPartyCode = true; + } + } + + if (isset($trace[1]['file']) && + ($trace[1]['file'] === $test->file() || + SourceFilter::instance()->includes($trace[1]['file']))) { + $triggerCalledFromFirstPartyCode = true; + } + + if ($triggerCalledFromFirstPartyCode) { + if ($triggeredInFirstPartyCode) { + return IssueTrigger::self(); + } + + return IssueTrigger::direct(); + } + + return IssueTrigger::indirect(); + } + + /** + * @return list + */ + private function filteredStackTrace(bool $filterDeprecationTriggers): array + { + $trace = $this->errorStackTrace(); + + if ($this->deprecationTriggers === null || !$filterDeprecationTriggers) { + return array_values($trace); + } + + foreach (array_keys($trace) as $frame) { + foreach ($this->deprecationTriggers['functions'] as $function) { + if ($this->frameIsFunction($trace[$frame], $function)) { + unset($trace[$frame]); + + continue 2; + } + } + + foreach ($this->deprecationTriggers['methods'] as $method) { + if ($this->frameIsMethod($trace[$frame], $method)) { + unset($trace[$frame]); + + continue 2; + } + } + } + + return array_values($trace); + } + + /** + * @return ?array{file: non-empty-string, line: positive-int} + */ + private function guessDeprecationFrame(): ?array + { + if ($this->deprecationTriggers === null) { + return null; + } + + $trace = $this->errorStackTrace(); + + foreach ($trace as $frame) { + foreach ($this->deprecationTriggers['functions'] as $function) { + if ($this->frameIsFunction($frame, $function)) { + return $frame; + } + } + + foreach ($this->deprecationTriggers['methods'] as $method) { + if ($this->frameIsMethod($frame, $method)) { + return $frame; + } + } + } + + return null; + } + + /** + * @return list + */ + private function errorStackTrace(): array + { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + + $i = 0; + + do { + unset($trace[$i]); + } while (self::class === ($trace[++$i]['class'] ?? null)); + + return array_values($trace); + } + + /** + * @param array{class? : class-string, function?: non-empty-string} $frame + * @param non-empty-string $function + */ + private function frameIsFunction(array $frame, string $function): bool + { + return !isset($frame['class']) && isset($frame['function']) && $frame['function'] === $function; + } + + /** + * @param array{class? : class-string, function?: non-empty-string} $frame + * @param array{className: class-string, methodName: non-empty-string} $method + */ + private function frameIsMethod(array $frame, array $method): bool + { + return isset($frame['class']) && + $frame['class'] === $method['className'] && + isset($frame['function']) && + $frame['function'] === $method['methodName']; + } + + /** + * @return non-empty-string + */ + private function stackTrace(): string + { + $buffer = ''; + $excludeList = new ExcludeList(true); + + foreach ($this->errorStackTrace() as $frame) { + /** + * @see https://github.com/sebastianbergmann/phpunit/issues/6043 + */ + if (!isset($frame['file'])) { + continue; + } + + if ($excludeList->isExcluded($frame['file'])) { + continue; + } + + $buffer .= sprintf( + "%s:%s\n", + $frame['file'], + $frame['line'] ?? '?', + ); + } + + return $buffer; + } + + private function triggerGlobalDeprecations(TestCase $test): void + { + foreach ($this->globalDeprecations ?? [] as $d) { + $this->__invoke(...$d); + } + + $testCaseContext = $this->testCaseContext($test::class, $test->name()); + + foreach ($this->testCaseContextDeprecations[$testCaseContext] ?? [] as $d) { + $this->__invoke(...$d); + } + } + + private function testCaseContext(string $className, string $methodName): string + { + return "{$className}::{$methodName}"; + } + + private function deprecationIgnoredByTest(TestMethod $test, string $message): bool + { + $metadata = \PHPUnit\Metadata\Parser\Registry::parser()->forClassAndMethod($test->className(), $test->methodName())->isIgnoreDeprecations()->asArray(); + + foreach ($metadata as $metadatum) { + assert($metadatum instanceof IgnoreDeprecations); + + $ignoreDeprecationMessagePattern = $metadatum->messagePattern(); + + if ($ignoreDeprecationMessagePattern === null || + (bool) preg_match('{' . $ignoreDeprecationMessagePattern . '}', $message)) { + return true; + } + } + + return false; + } +} diff --git a/src/Runner/Exception.php b/src/Runner/Exception.php deleted file mode 100644 index adcd1155806..00000000000 --- a/src/Runner/Exception.php +++ /dev/null @@ -1,19 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -use RuntimeException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Exception extends RuntimeException implements \PHPUnit\Exception -{ -} diff --git a/src/Runner/Exception/ClassCannotBeFoundException.php b/src/Runner/Exception/ClassCannotBeFoundException.php new file mode 100644 index 00000000000..701cbb5b143 --- /dev/null +++ b/src/Runner/Exception/ClassCannotBeFoundException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ClassCannotBeFoundException extends RuntimeException implements Exception +{ + public function __construct(string $className, string $file) + { + parent::__construct( + sprintf( + 'Class %s cannot be found in %s', + $className, + $file, + ), + ); + } +} diff --git a/src/Runner/Exception/ClassDoesNotExtendTestCaseException.php b/src/Runner/Exception/ClassDoesNotExtendTestCaseException.php new file mode 100644 index 00000000000..c9d5474e32c --- /dev/null +++ b/src/Runner/Exception/ClassDoesNotExtendTestCaseException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ClassDoesNotExtendTestCaseException extends RuntimeException implements Exception +{ + public function __construct(string $className, string $file) + { + parent::__construct( + sprintf( + 'Class %s declared in %s does not extend PHPUnit\Framework\TestCase', + $className, + $file, + ), + ); + } +} diff --git a/src/Runner/Exception/ClassIsAbstractException.php b/src/Runner/Exception/ClassIsAbstractException.php new file mode 100644 index 00000000000..bf947589979 --- /dev/null +++ b/src/Runner/Exception/ClassIsAbstractException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ClassIsAbstractException extends RuntimeException implements Exception +{ + public function __construct(string $className, string $file) + { + parent::__construct( + sprintf( + 'Class %s declared in %s is abstract', + $className, + $file, + ), + ); + } +} diff --git a/src/Runner/Exception/DirectoryDoesNotExistException.php b/src/Runner/Exception/DirectoryDoesNotExistException.php new file mode 100644 index 00000000000..626c422567f --- /dev/null +++ b/src/Runner/Exception/DirectoryDoesNotExistException.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class DirectoryDoesNotExistException extends RuntimeException implements Exception +{ + public function __construct(string $directory) + { + parent::__construct( + sprintf( + 'Directory "%s" does not exist and could not be created', + $directory, + ), + ); + } +} diff --git a/src/Runner/Exception/ErrorException.php b/src/Runner/Exception/ErrorException.php new file mode 100644 index 00000000000..954684e9fac --- /dev/null +++ b/src/Runner/Exception/ErrorException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use Error; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ErrorException extends Error implements Exception +{ +} diff --git a/src/Runner/Exception/Exception.php b/src/Runner/Exception/Exception.php new file mode 100644 index 00000000000..ea0cf4244d3 --- /dev/null +++ b/src/Runner/Exception/Exception.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Exception extends \PHPUnit\Exception +{ +} diff --git a/src/Runner/Exception/FileDoesNotExistException.php b/src/Runner/Exception/FileDoesNotExistException.php new file mode 100644 index 00000000000..5b84c785d5b --- /dev/null +++ b/src/Runner/Exception/FileDoesNotExistException.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class FileDoesNotExistException extends RuntimeException implements Exception +{ + public function __construct(string $file) + { + parent::__construct( + sprintf( + 'File "%s" does not exist', + $file, + ), + ); + } +} diff --git a/src/Runner/Exception/InvalidOrderException.php b/src/Runner/Exception/InvalidOrderException.php new file mode 100644 index 00000000000..016ec85e457 --- /dev/null +++ b/src/Runner/Exception/InvalidOrderException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidOrderException extends RuntimeException implements Exception +{ +} diff --git a/src/Runner/Exception/ParameterDoesNotExistException.php b/src/Runner/Exception/ParameterDoesNotExistException.php new file mode 100644 index 00000000000..5d7a096754a --- /dev/null +++ b/src/Runner/Exception/ParameterDoesNotExistException.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ParameterDoesNotExistException extends RuntimeException implements Exception +{ + public function __construct(string $name) + { + parent::__construct( + sprintf( + 'Parameter "%s" does not exist', + $name, + ), + ); + } +} diff --git a/src/Runner/Extension/Extension.php b/src/Runner/Extension/Extension.php new file mode 100644 index 00000000000..35610bc3d6a --- /dev/null +++ b/src/Runner/Extension/Extension.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Extension; + +use PHPUnit\TextUI\Configuration\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface Extension +{ + public function bootstrap(Configuration $configuration, Facade $facade, ParameterCollection $parameters): void; +} diff --git a/src/Runner/Extension/ExtensionBootstrapper.php b/src/Runner/Extension/ExtensionBootstrapper.php new file mode 100644 index 00000000000..89966aec0aa --- /dev/null +++ b/src/Runner/Extension/ExtensionBootstrapper.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Extension; + +use const PHP_EOL; +use function assert; +use function class_exists; +use function class_implements; +use function in_array; +use function sprintf; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\TextUI\Configuration\Configuration; +use ReflectionClass; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ExtensionBootstrapper +{ + private Configuration $configuration; + private Facade $facade; + + public function __construct(Configuration $configuration, Facade $facade) + { + $this->configuration = $configuration; + $this->facade = $facade; + } + + /** + * @param non-empty-string $className + * @param array $parameters + */ + public function bootstrap(string $className, array $parameters): void + { + if (!class_exists($className)) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Cannot bootstrap extension because class %s does not exist', + $className, + ), + ); + + return; + } + + if (!in_array(Extension::class, class_implements($className), true)) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Cannot bootstrap extension because class %s does not implement interface %s', + $className, + Extension::class, + ), + ); + + return; + } + + try { + $instance = new ReflectionClass($className)->newInstance(); + + assert($instance instanceof Extension); + + $instance->bootstrap( + $this->configuration, + $this->facade, + ParameterCollection::fromArray($parameters), + ); + } catch (Throwable $t) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Bootstrapping of extension %s failed: %s%s%s', + $className, + $t->getMessage(), + PHP_EOL, + $t->getTraceAsString(), + ), + ); + + return; + } + + EventFacade::emitter()->testRunnerBootstrappedExtension( + $className, + $parameters, + ); + } +} diff --git a/src/Runner/Extension/Facade.php b/src/Runner/Extension/Facade.php new file mode 100644 index 00000000000..910f4e5fe13 --- /dev/null +++ b/src/Runner/Extension/Facade.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Extension; + +use PHPUnit\Event\EventFacadeIsSealedException; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Event\Subscriber; +use PHPUnit\Event\Tracer\Tracer; +use PHPUnit\Event\UnknownSubscriberTypeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class Facade +{ + private bool $replacesOutput = false; + private bool $replacesProgressOutput = false; + private bool $replacesResultOutput = false; + private bool $requiresCodeCoverageCollection = false; + + /** + * @throws EventFacadeIsSealedException + * @throws UnknownSubscriberTypeException + */ + public function registerSubscribers(Subscriber ...$subscribers): void + { + EventFacade::instance()->registerSubscribers(...$subscribers); + } + + /** + * @throws EventFacadeIsSealedException + * @throws UnknownSubscriberTypeException + */ + public function registerSubscriber(Subscriber $subscriber): void + { + EventFacade::instance()->registerSubscriber($subscriber); + } + + /** + * @throws EventFacadeIsSealedException + */ + public function registerTracer(Tracer $tracer): void + { + EventFacade::instance()->registerTracer($tracer); + } + + public function replaceOutput(): void + { + $this->replacesOutput = true; + } + + public function replacesOutput(): bool + { + return $this->replacesOutput; + } + + public function replaceProgressOutput(): void + { + $this->replacesProgressOutput = true; + } + + public function replacesProgressOutput(): bool + { + return $this->replacesOutput || $this->replacesProgressOutput; + } + + public function replaceResultOutput(): void + { + $this->replacesResultOutput = true; + } + + public function replacesResultOutput(): bool + { + return $this->replacesOutput || $this->replacesResultOutput; + } + + public function requireCodeCoverageCollection(): void + { + $this->requiresCodeCoverageCollection = true; + } + + public function requiresCodeCoverageCollection(): bool + { + return $this->requiresCodeCoverageCollection; + } +} diff --git a/src/Runner/Extension/ParameterCollection.php b/src/Runner/Extension/ParameterCollection.php new file mode 100644 index 00000000000..fef1c9b1ae6 --- /dev/null +++ b/src/Runner/Extension/ParameterCollection.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Extension; + +use function array_key_exists; +use PHPUnit\Runner\ParameterDoesNotExistException; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ParameterCollection +{ + /** + * @var array + */ + private array $parameters; + + /** + * @param array $parameters + */ + public static function fromArray(array $parameters): self + { + return new self($parameters); + } + + /** + * @param array $parameters + */ + private function __construct(array $parameters) + { + $this->parameters = $parameters; + } + + public function has(string $name): bool + { + return array_key_exists($name, $this->parameters); + } + + /** + * @throws ParameterDoesNotExistException + */ + public function get(string $name): string + { + if (!$this->has($name)) { + throw new ParameterDoesNotExistException($name); + } + + return $this->parameters[$name]; + } +} diff --git a/src/Runner/Extension/PharLoader.php b/src/Runner/Extension/PharLoader.php new file mode 100644 index 00000000000..7f3c4b44914 --- /dev/null +++ b/src/Runner/Extension/PharLoader.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Extension; + +use function count; +use function explode; +use function extension_loaded; +use function implode; +use function is_file; +use function sprintf; +use function str_contains; +use PharIo\Manifest\ApplicationName; +use PharIo\Manifest\Exception as ManifestException; +use PharIo\Manifest\ManifestLoader; +use PharIo\Version\Version as PharIoVersion; +use PHPUnit\Event; +use PHPUnit\Runner\Version; +use SebastianBergmann\FileIterator\Facade as FileIteratorFacade; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PharLoader +{ + /** + * @param non-empty-string $directory + * + * @return list + */ + public function loadPharExtensionsInDirectory(string $directory): array + { + $pharExtensionLoaded = extension_loaded('phar'); + $loadedExtensions = []; + + foreach ((new FileIteratorFacade)->getFilesAsArray($directory, '.phar') as $file) { + if (!$pharExtensionLoaded) { + Event\Facade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Cannot load extension from %s because the PHAR extension is not available', + $file, + ), + ); + + continue; + } + + if (!is_file('phar://' . $file . '/manifest.xml')) { + Event\Facade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + '%s is not an extension for PHPUnit', + $file, + ), + ); + + continue; + } + + try { + $applicationName = new ApplicationName('phpunit/phpunit'); + $version = new PharIoVersion($this->phpunitVersion()); + $manifest = ManifestLoader::fromFile('phar://' . $file . '/manifest.xml'); + + if (!$manifest->isExtensionFor($applicationName)) { + Event\Facade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + '%s is not an extension for PHPUnit', + $file, + ), + ); + + continue; + } + + if (!$manifest->isExtensionFor($applicationName, $version)) { + Event\Facade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + '%s is not compatible with PHPUnit %s', + $file, + Version::series(), + ), + ); + + continue; + } + } catch (ManifestException $e) { + Event\Facade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Cannot load extension from %s: %s', + $file, + $e->getMessage(), + ), + ); + + continue; + } + + try { + @require $file; + } catch (Throwable $t) { + Event\Facade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Cannot load extension from %s: %s', + $file, + $t->getMessage(), + ), + ); + + continue; + } + + $loadedExtensions[] = $manifest->getName()->asString() . ' ' . $manifest->getVersion()->getVersionString(); + + Event\Facade::emitter()->testRunnerLoadedExtensionFromPhar( + $file, + $manifest->getName()->asString(), + $manifest->getVersion()->getVersionString(), + ); + } + + return $loadedExtensions; + } + + private function phpunitVersion(): string + { + $version = Version::id(); + + if (!str_contains($version, '-')) { + return $version; + } + + $parts = explode('.', explode('-', $version)[0]); + + if (count($parts) === 2) { + $parts[] = 0; + } + + return implode('.', $parts); + } +} diff --git a/src/Runner/Filter/ExcludeGroupFilterIterator.php b/src/Runner/Filter/ExcludeGroupFilterIterator.php index 4b26e5716e9..45296b2d199 100644 --- a/src/Runner/Filter/ExcludeGroupFilterIterator.php +++ b/src/Runner/Filter/ExcludeGroupFilterIterator.php @@ -12,12 +12,18 @@ use function in_array; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class ExcludeGroupFilterIterator extends GroupFilterIterator { - protected function doAccept(string $hash): bool + /** + * @param non-empty-string $id + * @param list $groupTests + */ + protected function doAccept(string $id, array $groupTests): bool { - return !in_array($hash, $this->groupTests, true); + return !in_array($id, $groupTests, true); } } diff --git a/src/Runner/Filter/ExcludeNameFilterIterator.php b/src/Runner/Filter/ExcludeNameFilterIterator.php new file mode 100644 index 00000000000..ff8459312ef --- /dev/null +++ b/src/Runner/Filter/ExcludeNameFilterIterator.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Filter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ExcludeNameFilterIterator extends NameFilterIterator +{ + protected function doAccept(bool $result): bool + { + return !$result; + } +} diff --git a/src/Runner/Filter/Factory.php b/src/Runner/Filter/Factory.php index 3f79da54122..0335e25b5bd 100644 --- a/src/Runner/Filter/Factory.php +++ b/src/Runner/Filter/Factory.php @@ -10,48 +10,91 @@ namespace PHPUnit\Runner\Filter; use function assert; -use function sprintf; use FilterIterator; use Iterator; +use PHPUnit\Framework\Test; use PHPUnit\Framework\TestSuite; -use PHPUnit\Runner\Exception; -use RecursiveFilterIterator; -use ReflectionClass; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class Factory { /** - * @psalm-var array + * @var list>>, argument: list|non-empty-string}> */ - private $filters = []; + private array $filters = []; /** - * @param array|string $args - * - * @throws Exception + * @param list $testIds */ - public function addFilter(ReflectionClass $filter, $args): void + public function addTestIdFilter(array $testIds): void { - if (!$filter->isSubclassOf(RecursiveFilterIterator::class)) { - throw new Exception( - sprintf( - 'Class "%s" does not extend RecursiveFilterIterator', - $filter->name - ) - ); - } + $this->filters[] = [ + 'className' => TestIdFilterIterator::class, + 'argument' => $testIds, + ]; + } + + /** + * @param list $groups + */ + public function addIncludeGroupFilter(array $groups): void + { + $this->filters[] = [ + 'className' => IncludeGroupFilterIterator::class, + 'argument' => $groups, + ]; + } - $this->filters[] = [$filter, $args]; + /** + * @param list $groups + */ + public function addExcludeGroupFilter(array $groups): void + { + $this->filters[] = [ + 'className' => ExcludeGroupFilterIterator::class, + 'argument' => $groups, + ]; } + /** + * @param non-empty-string $name + */ + public function addIncludeNameFilter(string $name): void + { + $this->filters[] = [ + 'className' => IncludeNameFilterIterator::class, + 'argument' => $name, + ]; + } + + /** + * @param non-empty-string $name + */ + public function addExcludeNameFilter(string $name): void + { + $this->filters[] = [ + 'className' => ExcludeNameFilterIterator::class, + 'argument' => $name, + ]; + } + + /** + * @param Iterator $iterator + * + * @return FilterIterator> + */ public function factory(Iterator $iterator, TestSuite $suite): FilterIterator { foreach ($this->filters as $filter) { - [$class, $args] = $filter; - $iterator = $class->newInstance($iterator, $args, $suite); + $iterator = new $filter['className']( + $iterator, + $filter['argument'], + $suite, + ); } assert($iterator instanceof FilterIterator); diff --git a/src/Runner/Filter/GroupFilterIterator.php b/src/Runner/Filter/GroupFilterIterator.php index 42ca77a3812..da45211d752 100644 --- a/src/Runner/Filter/GroupFilterIterator.php +++ b/src/Runner/Filter/GroupFilterIterator.php @@ -9,38 +9,47 @@ */ namespace PHPUnit\Runner\Filter; -use function array_map; use function array_merge; +use function array_push; use function in_array; -use function spl_object_hash; +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestSuite; +use PHPUnit\Runner\Phpt\TestCase as PhptTestCase; use RecursiveFilterIterator; use RecursiveIterator; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ abstract class GroupFilterIterator extends RecursiveFilterIterator { /** - * @var string[] + * @var list */ - protected $groupTests = []; + private readonly array $groupTests; + /** + * @param RecursiveIterator $iterator + * @param list $groups + */ public function __construct(RecursiveIterator $iterator, array $groups, TestSuite $suite) { parent::__construct($iterator); - foreach ($suite->getGroupDetails() as $group => $tests) { - if (in_array((string) $group, $groups, true)) { - $testHashes = array_map( - 'spl_object_hash', - $tests - ); + $groupTests = []; - $this->groupTests = array_merge($this->groupTests, $testHashes); + foreach ($suite->groups() as $group => $tests) { + if (in_array($group, $groups, true)) { + $groupTests = array_merge($groupTests, $tests); + + array_push($groupTests, ...$groupTests); } } + + $this->groupTests = $groupTests; } public function accept(): bool @@ -51,8 +60,16 @@ public function accept(): bool return true; } - return $this->doAccept(spl_object_hash($test)); + if ($test instanceof TestCase || $test instanceof PhptTestCase) { + return $this->doAccept($test->valueObjectForEvents()->id(), $this->groupTests); + } + + return true; } - abstract protected function doAccept(string $hash); + /** + * @param non-empty-string $id + * @param list $groupTests + */ + abstract protected function doAccept(string $id, array $groupTests): bool; } diff --git a/src/Runner/Filter/IncludeGroupFilterIterator.php b/src/Runner/Filter/IncludeGroupFilterIterator.php index 0346c60131a..afdaefda9fd 100644 --- a/src/Runner/Filter/IncludeGroupFilterIterator.php +++ b/src/Runner/Filter/IncludeGroupFilterIterator.php @@ -12,12 +12,18 @@ use function in_array; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class IncludeGroupFilterIterator extends GroupFilterIterator { - protected function doAccept(string $hash): bool + /** + * @param non-empty-string $id + * @param list $groupTests + */ + protected function doAccept(string $id, array $groupTests): bool { - return in_array($hash, $this->groupTests, true); + return in_array($id, $groupTests, true); } } diff --git a/src/Runner/Filter/IncludeNameFilterIterator.php b/src/Runner/Filter/IncludeNameFilterIterator.php new file mode 100644 index 00000000000..9bca65eb2ba --- /dev/null +++ b/src/Runner/Filter/IncludeNameFilterIterator.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Filter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class IncludeNameFilterIterator extends NameFilterIterator +{ + protected function doAccept(bool $result): bool + { + return $result; + } +} diff --git a/src/Runner/Filter/NameFilterIterator.php b/src/Runner/Filter/NameFilterIterator.php index 007109f2491..a2fc69b3d36 100644 --- a/src/Runner/Filter/NameFilterIterator.php +++ b/src/Runner/Filter/NameFilterIterator.php @@ -10,50 +10,44 @@ namespace PHPUnit\Runner\Filter; use function end; -use function implode; use function preg_match; use function sprintf; -use function str_replace; -use Exception; +use function substr; +use PHPUnit\Framework\Test; use PHPUnit\Framework\TestSuite; -use PHPUnit\Framework\WarningTestCase; -use PHPUnit\Util\RegularExpression; +use PHPUnit\Runner\Phpt\TestCase as PhptTestCase; use RecursiveFilterIterator; use RecursiveIterator; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class NameFilterIterator extends RecursiveFilterIterator +abstract class NameFilterIterator extends RecursiveFilterIterator { /** - * @var string - */ - private $filter; - - /** - * @var int + * @var non-empty-string */ - private $filterMin; + private readonly string $regularExpression; + private readonly ?int $dataSetMinimum; + private readonly ?int $dataSetMaximum; /** - * @var int - */ - private $filterMax; - - /** - * @throws Exception + * @param RecursiveIterator $iterator + * @param non-empty-string $filter */ public function __construct(RecursiveIterator $iterator, string $filter) { parent::__construct($iterator); - $this->setFilter($filter); + $preparedFilter = $this->prepareFilter($filter); + + $this->regularExpression = $preparedFilter['regularExpression']; + $this->dataSetMinimum = $preparedFilter['dataSetMinimum']; + $this->dataSetMaximum = $preparedFilter['dataSetMaximum']; } - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ public function accept(): bool { $test = $this->getInnerIterator()->current(); @@ -62,32 +56,35 @@ public function accept(): bool return true; } - $tmp = \PHPUnit\Util\Test::describe($test); - - if ($test instanceof WarningTestCase) { - $name = $test->getMessage(); - } elseif ($tmp[0] !== '') { - $name = implode('::', $tmp); - } else { - $name = $tmp[1]; + if ($test instanceof PhptTestCase) { + return false; } - $accepted = @preg_match($this->filter, $name, $matches); + $name = $test::class . '::' . $test->nameWithDataSet(); + + $accepted = @preg_match($this->regularExpression, $name, $matches) === 1; - if ($accepted && isset($this->filterMax)) { + if ($accepted && isset($this->dataSetMaximum)) { $set = end($matches); - $accepted = $set >= $this->filterMin && $set <= $this->filterMax; + $accepted = $set >= $this->dataSetMinimum && $set <= $this->dataSetMaximum; } - return (bool) $accepted; + return $this->doAccept($accepted); } + abstract protected function doAccept(bool $result): bool; + /** - * @throws Exception + * @param non-empty-string $filter + * + * @return array{regularExpression: non-empty-string, dataSetMinimum: ?int, dataSetMaximum: ?int} */ - private function setFilter(string $filter): void + private function prepareFilter(string $filter): array { - if (RegularExpression::safeMatch($filter, '') === false) { + $dataSetMinimum = null; + $dataSetMaximum = null; + + if (preg_match('/[a-zA-Z0-9]/', substr($filter, 0, 1)) === 1 || @preg_match($filter, '') === false) { // Handles: // * testAssertEqualsSucceeds#4 // * testAssertEqualsSucceeds#4-8 @@ -95,16 +92,16 @@ private function setFilter(string $filter): void if (isset($matches[3]) && $matches[2] < $matches[3]) { $filter = sprintf( '%s.*with data set #(\d+)$', - $matches[1] + $matches[1], ); - $this->filterMin = (int) $matches[2]; - $this->filterMax = (int) $matches[3]; + $dataSetMinimum = (int) $matches[2]; + $dataSetMaximum = (int) $matches[3]; } else { $filter = sprintf( '%s.*with data set #%s$', $matches[1], - $matches[2] + $matches[2], ); } } // Handles: @@ -114,19 +111,21 @@ private function setFilter(string $filter): void $filter = sprintf( '%s.*with data set "%s"$', $matches[1], - $matches[2] + $matches[2], ); } - // Escape delimiters in regular expression. Do NOT use preg_quote, - // to keep magic characters. - $filter = sprintf('/%s/i', str_replace( - '/', - '\\/', - $filter - )); + // Do NOT use preg_quote, to keep magic characters. + $filter = sprintf( + '{%s}i', + $filter, + ); } - $this->filter = $filter; + return [ + 'regularExpression' => $filter, + 'dataSetMinimum' => $dataSetMinimum, + 'dataSetMaximum' => $dataSetMaximum, + ]; } } diff --git a/src/Runner/Filter/TestIdFilterIterator.php b/src/Runner/Filter/TestIdFilterIterator.php new file mode 100644 index 00000000000..1180de4e780 --- /dev/null +++ b/src/Runner/Filter/TestIdFilterIterator.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Filter; + +use function in_array; +use PHPUnit\Event\TestData\NoDataSetFromDataProviderException; +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Runner\Phpt\TestCase as PhptTestCase; +use RecursiveFilterIterator; +use RecursiveIterator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestIdFilterIterator extends RecursiveFilterIterator +{ + /** + * @var non-empty-list + */ + private readonly array $testIds; + + /** + * @param RecursiveIterator $iterator + * @param non-empty-list $testIds + */ + public function __construct(RecursiveIterator $iterator, array $testIds) + { + parent::__construct($iterator); + + $this->testIds = $testIds; + } + + public function accept(): bool + { + $test = $this->getInnerIterator()->current(); + + if ($test instanceof TestSuite) { + return true; + } + + if (!$test instanceof TestCase && !$test instanceof PhptTestCase) { + return false; + } + + try { + return in_array($test->valueObjectForEvents()->id(), $this->testIds, true); + } catch (NoDataSetFromDataProviderException) { + return false; + } + } +} diff --git a/src/Runner/GarbageCollection/GarbageCollectionHandler.php b/src/Runner/GarbageCollection/GarbageCollectionHandler.php new file mode 100644 index 00000000000..c6cd2dd3b1b --- /dev/null +++ b/src/Runner/GarbageCollection/GarbageCollectionHandler.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\GarbageCollection; + +use function gc_collect_cycles; +use function gc_disable; +use function gc_enable; +use PHPUnit\Event\Facade; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class GarbageCollectionHandler +{ + private readonly Facade $facade; + private readonly int $threshold; + private int $tests = 0; + + public function __construct(Facade $facade, int $threshold) + { + $this->facade = $facade; + $this->threshold = $threshold; + + $this->registerSubscribers(); + } + + public function executionStarted(): void + { + gc_disable(); + + $this->facade->emitter()->testRunnerDisabledGarbageCollection(); + + gc_collect_cycles(); + + $this->facade->emitter()->testRunnerTriggeredGarbageCollection(); + } + + public function executionFinished(): void + { + gc_collect_cycles(); + + $this->facade->emitter()->testRunnerTriggeredGarbageCollection(); + + gc_enable(); + + $this->facade->emitter()->testRunnerEnabledGarbageCollection(); + } + + public function testFinished(): void + { + $this->tests++; + + if ($this->tests === $this->threshold) { + gc_collect_cycles(); + + $this->facade->emitter()->testRunnerTriggeredGarbageCollection(); + + $this->tests = 0; + } + } + + private function registerSubscribers(): void + { + $this->facade->registerSubscribers( + new ExecutionStartedSubscriber($this), + new ExecutionFinishedSubscriber($this), + new TestFinishedSubscriber($this), + ); + } +} diff --git a/src/Runner/GarbageCollection/Subscriber/ExecutionFinishedSubscriber.php b/src/Runner/GarbageCollection/Subscriber/ExecutionFinishedSubscriber.php new file mode 100644 index 00000000000..4ff8e11357e --- /dev/null +++ b/src/Runner/GarbageCollection/Subscriber/ExecutionFinishedSubscriber.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\GarbageCollection; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\TestRunner\ExecutionFinished; +use PHPUnit\Event\TestRunner\ExecutionFinishedSubscriber as TestRunnerExecutionFinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ExecutionFinishedSubscriber extends Subscriber implements TestRunnerExecutionFinishedSubscriber +{ + /** + * @throws \PHPUnit\Framework\InvalidArgumentException + * @throws InvalidArgumentException + */ + public function notify(ExecutionFinished $event): void + { + $this->handler()->executionFinished(); + } +} diff --git a/src/Runner/GarbageCollection/Subscriber/ExecutionStartedSubscriber.php b/src/Runner/GarbageCollection/Subscriber/ExecutionStartedSubscriber.php new file mode 100644 index 00000000000..1b99b8b72eb --- /dev/null +++ b/src/Runner/GarbageCollection/Subscriber/ExecutionStartedSubscriber.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\GarbageCollection; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\TestRunner\ExecutionStarted; +use PHPUnit\Event\TestRunner\ExecutionStartedSubscriber as TestRunnerExecutionStartedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ExecutionStartedSubscriber extends Subscriber implements TestRunnerExecutionStartedSubscriber +{ + /** + * @throws \PHPUnit\Framework\InvalidArgumentException + * @throws InvalidArgumentException + */ + public function notify(ExecutionStarted $event): void + { + $this->handler()->executionStarted(); + } +} diff --git a/src/Runner/GarbageCollection/Subscriber/Subscriber.php b/src/Runner/GarbageCollection/Subscriber/Subscriber.php new file mode 100644 index 00000000000..3c9abce8d70 --- /dev/null +++ b/src/Runner/GarbageCollection/Subscriber/Subscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\GarbageCollection; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Subscriber +{ + private GarbageCollectionHandler $handler; + + public function __construct(GarbageCollectionHandler $handler) + { + $this->handler = $handler; + } + + protected function handler(): GarbageCollectionHandler + { + return $this->handler; + } +} diff --git a/src/Runner/GarbageCollection/Subscriber/TestFinishedSubscriber.php b/src/Runner/GarbageCollection/Subscriber/TestFinishedSubscriber.php new file mode 100644 index 00000000000..4ffae389fbc --- /dev/null +++ b/src/Runner/GarbageCollection/Subscriber/TestFinishedSubscriber.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\GarbageCollection; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + /** + * @throws \PHPUnit\Framework\InvalidArgumentException + * @throws InvalidArgumentException + */ + public function notify(Finished $event): void + { + $this->handler()->testFinished(); + } +} diff --git a/src/Runner/Hook/AfterIncompleteTestHook.php b/src/Runner/Hook/AfterIncompleteTestHook.php deleted file mode 100644 index 35ded5d09da..00000000000 --- a/src/Runner/Hook/AfterIncompleteTestHook.php +++ /dev/null @@ -1,15 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -interface AfterIncompleteTestHook extends TestHook -{ - public function executeAfterIncompleteTest(string $test, string $message, float $time): void; -} diff --git a/src/Runner/Hook/AfterLastTestHook.php b/src/Runner/Hook/AfterLastTestHook.php deleted file mode 100644 index 7dee9f9e8b7..00000000000 --- a/src/Runner/Hook/AfterLastTestHook.php +++ /dev/null @@ -1,15 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -interface AfterLastTestHook extends Hook -{ - public function executeAfterLastTest(): void; -} diff --git a/src/Runner/Hook/AfterRiskyTestHook.php b/src/Runner/Hook/AfterRiskyTestHook.php deleted file mode 100644 index 7fe9ee72ecd..00000000000 --- a/src/Runner/Hook/AfterRiskyTestHook.php +++ /dev/null @@ -1,15 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -interface AfterRiskyTestHook extends TestHook -{ - public function executeAfterRiskyTest(string $test, string $message, float $time): void; -} diff --git a/src/Runner/Hook/AfterSkippedTestHook.php b/src/Runner/Hook/AfterSkippedTestHook.php deleted file mode 100644 index f9253b5ba46..00000000000 --- a/src/Runner/Hook/AfterSkippedTestHook.php +++ /dev/null @@ -1,15 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -interface AfterSkippedTestHook extends TestHook -{ - public function executeAfterSkippedTest(string $test, string $message, float $time): void; -} diff --git a/src/Runner/Hook/AfterSuccessfulTestHook.php b/src/Runner/Hook/AfterSuccessfulTestHook.php deleted file mode 100644 index 6b55cc877e9..00000000000 --- a/src/Runner/Hook/AfterSuccessfulTestHook.php +++ /dev/null @@ -1,15 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -interface AfterSuccessfulTestHook extends TestHook -{ - public function executeAfterSuccessfulTest(string $test, float $time): void; -} diff --git a/src/Runner/Hook/AfterTestErrorHook.php b/src/Runner/Hook/AfterTestErrorHook.php deleted file mode 100644 index f5c23fb2156..00000000000 --- a/src/Runner/Hook/AfterTestErrorHook.php +++ /dev/null @@ -1,15 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -interface AfterTestErrorHook extends TestHook -{ - public function executeAfterTestError(string $test, string $message, float $time): void; -} diff --git a/src/Runner/Hook/AfterTestFailureHook.php b/src/Runner/Hook/AfterTestFailureHook.php deleted file mode 100644 index 9ed2939bf13..00000000000 --- a/src/Runner/Hook/AfterTestFailureHook.php +++ /dev/null @@ -1,15 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -interface AfterTestFailureHook extends TestHook -{ - public function executeAfterTestFailure(string $test, string $message, float $time): void; -} diff --git a/src/Runner/Hook/AfterTestHook.php b/src/Runner/Hook/AfterTestHook.php deleted file mode 100644 index 7e0af80b12f..00000000000 --- a/src/Runner/Hook/AfterTestHook.php +++ /dev/null @@ -1,21 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -interface AfterTestHook extends TestHook -{ - /** - * This hook will fire after any test, regardless of the result. - * - * For more fine grained control, have a look at the other hooks - * that extend PHPUnit\Runner\Hook. - */ - public function executeAfterTest(string $test, float $time): void; -} diff --git a/src/Runner/Hook/AfterTestWarningHook.php b/src/Runner/Hook/AfterTestWarningHook.php deleted file mode 100644 index 12de80f9c91..00000000000 --- a/src/Runner/Hook/AfterTestWarningHook.php +++ /dev/null @@ -1,15 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -interface AfterTestWarningHook extends TestHook -{ - public function executeAfterTestWarning(string $test, string $message, float $time): void; -} diff --git a/src/Runner/Hook/BeforeFirstTestHook.php b/src/Runner/Hook/BeforeFirstTestHook.php deleted file mode 100644 index 59b66664984..00000000000 --- a/src/Runner/Hook/BeforeFirstTestHook.php +++ /dev/null @@ -1,15 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -interface BeforeFirstTestHook extends Hook -{ - public function executeBeforeFirstTest(): void; -} diff --git a/src/Runner/Hook/BeforeTestHook.php b/src/Runner/Hook/BeforeTestHook.php deleted file mode 100644 index 8bbf8a99d77..00000000000 --- a/src/Runner/Hook/BeforeTestHook.php +++ /dev/null @@ -1,15 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -interface BeforeTestHook extends TestHook -{ - public function executeBeforeTest(string $test): void; -} diff --git a/src/Runner/Hook/Hook.php b/src/Runner/Hook/Hook.php deleted file mode 100644 index 546f1a35164..00000000000 --- a/src/Runner/Hook/Hook.php +++ /dev/null @@ -1,14 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -interface Hook -{ -} diff --git a/src/Runner/Hook/TestHook.php b/src/Runner/Hook/TestHook.php deleted file mode 100644 index 47c41f9eba7..00000000000 --- a/src/Runner/Hook/TestHook.php +++ /dev/null @@ -1,14 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -interface TestHook extends Hook -{ -} diff --git a/src/Runner/Hook/TestListenerAdapter.php b/src/Runner/Hook/TestListenerAdapter.php deleted file mode 100644 index 60fbfba3187..00000000000 --- a/src/Runner/Hook/TestListenerAdapter.php +++ /dev/null @@ -1,141 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -use PHPUnit\Framework\AssertionFailedError; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestListener; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Framework\Warning; -use PHPUnit\Util\Test as TestUtil; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class TestListenerAdapter implements TestListener -{ - /** - * @var TestHook[] - */ - private $hooks = []; - - /** - * @var bool - */ - private $lastTestWasNotSuccessful; - - public function add(TestHook $hook): void - { - $this->hooks[] = $hook; - } - - public function startTest(Test $test): void - { - foreach ($this->hooks as $hook) { - if ($hook instanceof BeforeTestHook) { - $hook->executeBeforeTest(TestUtil::describeAsString($test)); - } - } - - $this->lastTestWasNotSuccessful = false; - } - - public function addError(Test $test, Throwable $t, float $time): void - { - foreach ($this->hooks as $hook) { - if ($hook instanceof AfterTestErrorHook) { - $hook->executeAfterTestError(TestUtil::describeAsString($test), $t->getMessage(), $time); - } - } - - $this->lastTestWasNotSuccessful = true; - } - - public function addWarning(Test $test, Warning $e, float $time): void - { - foreach ($this->hooks as $hook) { - if ($hook instanceof AfterTestWarningHook) { - $hook->executeAfterTestWarning(TestUtil::describeAsString($test), $e->getMessage(), $time); - } - } - - $this->lastTestWasNotSuccessful = true; - } - - public function addFailure(Test $test, AssertionFailedError $e, float $time): void - { - foreach ($this->hooks as $hook) { - if ($hook instanceof AfterTestFailureHook) { - $hook->executeAfterTestFailure(TestUtil::describeAsString($test), $e->getMessage(), $time); - } - } - - $this->lastTestWasNotSuccessful = true; - } - - public function addIncompleteTest(Test $test, Throwable $t, float $time): void - { - foreach ($this->hooks as $hook) { - if ($hook instanceof AfterIncompleteTestHook) { - $hook->executeAfterIncompleteTest(TestUtil::describeAsString($test), $t->getMessage(), $time); - } - } - - $this->lastTestWasNotSuccessful = true; - } - - public function addRiskyTest(Test $test, Throwable $t, float $time): void - { - foreach ($this->hooks as $hook) { - if ($hook instanceof AfterRiskyTestHook) { - $hook->executeAfterRiskyTest(TestUtil::describeAsString($test), $t->getMessage(), $time); - } - } - - $this->lastTestWasNotSuccessful = true; - } - - public function addSkippedTest(Test $test, Throwable $t, float $time): void - { - foreach ($this->hooks as $hook) { - if ($hook instanceof AfterSkippedTestHook) { - $hook->executeAfterSkippedTest(TestUtil::describeAsString($test), $t->getMessage(), $time); - } - } - - $this->lastTestWasNotSuccessful = true; - } - - public function endTest(Test $test, float $time): void - { - if (!$this->lastTestWasNotSuccessful) { - foreach ($this->hooks as $hook) { - if ($hook instanceof AfterSuccessfulTestHook) { - $hook->executeAfterSuccessfulTest(TestUtil::describeAsString($test), $time); - } - } - } - - foreach ($this->hooks as $hook) { - if ($hook instanceof AfterTestHook) { - $hook->executeAfterTest(TestUtil::describeAsString($test), $time); - } - } - } - - public function startTestSuite(TestSuite $suite): void - { - } - - public function endTestSuite(TestSuite $suite): void - { - } -} diff --git a/src/Runner/HookMethod/HookMethod.php b/src/Runner/HookMethod/HookMethod.php new file mode 100644 index 00000000000..2442f75ecac --- /dev/null +++ b/src/Runner/HookMethod/HookMethod.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class HookMethod +{ + /** + * @var non-empty-string + */ + private string $methodName; + private int $priority; + + /** + * @param non-empty-string $methodName + */ + public function __construct(string $methodName, int $priority) + { + $this->methodName = $methodName; + $this->priority = $priority; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/src/Runner/HookMethod/HookMethodCollection.php b/src/Runner/HookMethod/HookMethodCollection.php new file mode 100644 index 00000000000..a3593fd5fd3 --- /dev/null +++ b/src/Runner/HookMethod/HookMethodCollection.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use function array_map; +use function usort; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class HookMethodCollection +{ + private readonly bool $shouldPrepend; + + /** + * @var non-empty-list + */ + private array $hookMethods; + + public static function defaultBeforeClass(): self + { + return new self(new HookMethod('setUpBeforeClass', 0), true); + } + + public static function defaultBefore(): self + { + return new self(new HookMethod('setUp', 0), true); + } + + public static function defaultPreCondition(): self + { + return new self(new HookMethod('assertPreConditions', 0), true); + } + + public static function defaultPostCondition(): self + { + return new self(new HookMethod('assertPostConditions', 0), false); + } + + public static function defaultAfter(): self + { + return new self(new HookMethod('tearDown', 0), false); + } + + public static function defaultAfterClass(): self + { + return new self(new HookMethod('tearDownAfterClass', 0), false); + } + + private function __construct(HookMethod $default, bool $shouldPrepend) + { + $this->hookMethods = [$default]; + $this->shouldPrepend = $shouldPrepend; + } + + public function add(HookMethod $hookMethod): self + { + if ($this->shouldPrepend) { + $this->hookMethods = [$hookMethod, ...$this->hookMethods]; + } else { + $this->hookMethods[] = $hookMethod; + } + + return $this; + } + + /** + * @return list + */ + public function methodNamesSortedByPriority(): array + { + $hookMethods = $this->hookMethods; + + usort($hookMethods, static fn (HookMethod $a, HookMethod $b) => $b->priority() <=> $a->priority()); + + return array_map( + static fn (HookMethod $hookMethod) => $hookMethod->methodName(), + $hookMethods, + ); + } +} diff --git a/src/Runner/IssueFilter.php b/src/Runner/IssueFilter.php new file mode 100644 index 00000000000..445c15b7f79 --- /dev/null +++ b/src/Runner/IssueFilter.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner; + +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\Test\ErrorTriggered; +use PHPUnit\Event\Test\NoticeTriggered; +use PHPUnit\Event\Test\PhpDeprecationTriggered; +use PHPUnit\Event\Test\PhpNoticeTriggered; +use PHPUnit\Event\Test\PhpWarningTriggered; +use PHPUnit\Event\Test\WarningTriggered; +use PHPUnit\TextUI\Configuration\Source; +use PHPUnit\TextUI\Configuration\SourceFilter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class IssueFilter +{ + private Source $source; + + public function __construct(Source $source) + { + $this->source = $source; + } + + public function shouldBeProcessed(DeprecationTriggered|ErrorTriggered|NoticeTriggered|PhpDeprecationTriggered|PhpNoticeTriggered|PhpWarningTriggered|WarningTriggered $event, bool $onlyTestMethods = false): bool + { + if ($onlyTestMethods && !$event->test()->isTestMethod()) { + return false; + } + + if ($event instanceof DeprecationTriggered || $event instanceof PhpDeprecationTriggered) { + if ($event->ignoredByTest()) { + return false; + } + + if ($this->source->ignoreSelfDeprecations() && + ($event->trigger()->isTest() || $event->trigger()->isSelf())) { + return false; + } + + if ($this->source->ignoreDirectDeprecations() && $event->trigger()->isDirect()) { + return false; + } + + if ($this->source->ignoreIndirectDeprecations() && $event->trigger()->isIndirect()) { + return false; + } + + if (!$this->source->ignoreSuppressionOfDeprecations() && $event->wasSuppressed()) { + return false; + } + } + + if ($event instanceof NoticeTriggered) { + if (!$this->source->ignoreSuppressionOfNotices() && $event->wasSuppressed()) { + return false; + } + + if ($this->source->restrictNotices() && !SourceFilter::instance()->includes($event->file())) { + return false; + } + } + + if ($event instanceof PhpNoticeTriggered) { + if (!$this->source->ignoreSuppressionOfPhpNotices() && $event->wasSuppressed()) { + return false; + } + + if ($this->source->restrictNotices() && !SourceFilter::instance()->includes($event->file())) { + return false; + } + } + + if ($event instanceof WarningTriggered) { + if (!$this->source->ignoreSuppressionOfWarnings() && $event->wasSuppressed()) { + return false; + } + + if ($this->source->restrictWarnings() && !SourceFilter::instance()->includes($event->file())) { + return false; + } + } + + if ($event instanceof PhpWarningTriggered) { + if (!$this->source->ignoreSuppressionOfPhpWarnings() && $event->wasSuppressed()) { + return false; + } + + if ($this->source->restrictWarnings() && !SourceFilter::instance()->includes($event->file())) { + return false; + } + } + + if ($event instanceof ErrorTriggered) { + if (!$this->source->ignoreSuppressionOfErrors() && $event->wasSuppressed()) { + return false; + } + } + + return true; + } +} diff --git a/src/Runner/NullTestResultCache.php b/src/Runner/NullTestResultCache.php deleted file mode 100644 index 2aa86534a9c..00000000000 --- a/src/Runner/NullTestResultCache.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class NullTestResultCache implements TestResultCache -{ - public function setState(string $testName, int $state): void - { - } - - public function getState(string $testName): int - { - return BaseTestRunner::STATUS_UNKNOWN; - } - - public function setTime(string $testName, float $time): void - { - } - - public function getTime(string $testName): float - { - return 0; - } - - public function load(): void - { - } - - public function persist(): void - { - } -} diff --git a/src/Runner/Phpt/Exception/InvalidPhptFileException.php b/src/Runner/Phpt/Exception/InvalidPhptFileException.php new file mode 100644 index 00000000000..07a2953ec54 --- /dev/null +++ b/src/Runner/Phpt/Exception/InvalidPhptFileException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Phpt; + +use PHPUnit\Runner\Exception as RunnerException; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidPhptFileException extends RuntimeException implements RunnerException +{ +} diff --git a/src/Runner/Phpt/Exception/PhptExternalFileCannotBeLoadedException.php b/src/Runner/Phpt/Exception/PhptExternalFileCannotBeLoadedException.php new file mode 100644 index 00000000000..bdee6262219 --- /dev/null +++ b/src/Runner/Phpt/Exception/PhptExternalFileCannotBeLoadedException.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Phpt; + +use function sprintf; +use PHPUnit\Runner\Exception as RunnerException; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class PhptExternalFileCannotBeLoadedException extends RuntimeException implements RunnerException +{ + public function __construct(string $section, string $file) + { + parent::__construct( + sprintf( + 'Could not load --%s-- %s for PHPT file', + $section . '_EXTERNAL', + $file, + ), + ); + } +} diff --git a/src/Runner/Phpt/Exception/UnsupportedPhptSectionException.php b/src/Runner/Phpt/Exception/UnsupportedPhptSectionException.php new file mode 100644 index 00000000000..9079f9967cc --- /dev/null +++ b/src/Runner/Phpt/Exception/UnsupportedPhptSectionException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Phpt; + +use function sprintf; +use PHPUnit\Runner\Exception as RunnerException; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class UnsupportedPhptSectionException extends RuntimeException implements RunnerException +{ + public function __construct(string $section) + { + parent::__construct( + sprintf( + 'PHPUnit does not support PHPT --%s-- sections', + $section, + ), + ); + } +} diff --git a/src/Runner/Phpt/Parser.php b/src/Runner/Phpt/Parser.php new file mode 100644 index 00000000000..72582d733ee --- /dev/null +++ b/src/Runner/Phpt/Parser.php @@ -0,0 +1,213 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Phpt; + +use const DIRECTORY_SEPARATOR; +use function assert; +use function dirname; +use function explode; +use function file; +use function file_get_contents; +use function is_file; +use function is_readable; +use function is_string; +use function preg_match; +use function rtrim; +use function str_contains; +use function trim; +use PHPUnit\Runner\Exception; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @see https://qa.php.net/phpt_details.php + */ +final readonly class Parser +{ + /** + * @param non-empty-string $phptFile + * + * @throws Exception + * + * @return array + */ + public function parse(string $phptFile): array + { + $sections = []; + $section = ''; + + $unsupportedSections = [ + 'CGI', + 'COOKIE', + 'DEFLATE_POST', + 'EXPECTHEADERS', + 'EXTENSIONS', + 'GET', + 'GZIP_POST', + 'HEADERS', + 'PHPDBG', + 'POST', + 'POST_RAW', + 'PUT', + 'REDIRECTTEST', + 'REQUEST', + ]; + + $lineNr = 0; + + foreach (file($phptFile) as $line) { + $lineNr++; + + if (preg_match('/^--([_A-Z]+)--/', $line, $result)) { + $section = $result[1]; + $sections[$section] = ''; + $sections[$section . '_offset'] = $lineNr; + + continue; + } + + if ($section === '') { + throw new InvalidPhptFileException; + } + + $sections[$section] .= $line; + } + + if (isset($sections['FILEEOF'])) { + $sections['FILE'] = rtrim($sections['FILEEOF'], "\r\n"); + + unset($sections['FILEEOF']); + } + + $this->parseExternal($phptFile, $sections); + $this->validate($sections); + + foreach ($unsupportedSections as $unsupportedSection) { + if (isset($sections[$unsupportedSection])) { + throw new UnsupportedPhptSectionException($unsupportedSection); + } + } + + return $sections; + } + + /** + * @return array + */ + public function parseEnvSection(string $content): array + { + $env = []; + + foreach (explode("\n", trim($content)) as $e) { + $e = explode('=', trim($e), 2); + + if ($e[0] !== '' && isset($e[1])) { + $env[$e[0]] = $e[1]; + } + } + + return $env; + } + + /** + * @param array|string $content + * @param array|non-empty-string> $ini + * + * @return array|non-empty-string> + */ + public function parseIniSection(array|string $content, array $ini = []): array + { + if (is_string($content)) { + $content = explode("\n", trim($content)); + } + + foreach ($content as $setting) { + if (!str_contains($setting, '=')) { + continue; + } + + $setting = explode('=', $setting, 2); + $name = trim($setting[0]); + $value = trim($setting[1]); + + if ($name === 'extension' || $name === 'zend_extension') { + if (!isset($ini[$name])) { + $ini[$name] = []; + } + + $ini[$name][] = $value; + + continue; + } + + $ini[$name] = $value; + } + + return $ini; + } + + /** + * @param non-empty-string $phptFile + * @param array $sections + * + * @throws Exception + */ + private function parseExternal(string $phptFile, array &$sections): void + { + $allowSections = [ + 'FILE', + 'EXPECT', + 'EXPECTF', + 'EXPECTREGEX', + ]; + + $testDirectory = dirname($phptFile) . DIRECTORY_SEPARATOR; + + foreach ($allowSections as $section) { + if (isset($sections[$section . '_EXTERNAL'])) { + $externalFilename = trim($sections[$section . '_EXTERNAL']); + + if (!is_file($testDirectory . $externalFilename) || + !is_readable($testDirectory . $externalFilename)) { + throw new PhptExternalFileCannotBeLoadedException( + $section, + $testDirectory . $externalFilename, + ); + } + + $contents = file_get_contents($testDirectory . $externalFilename); + + assert($contents !== false && $contents !== ''); + + $sections[$section] = $contents; + } + } + } + + /** + * @param array $sections + * + * @throws InvalidPhptFileException + */ + private function validate(array $sections): void + { + if (!isset($sections['FILE'])) { + throw new InvalidPhptFileException; + } + + if (!isset($sections['EXPECT']) && + !isset($sections['EXPECTF']) && + !isset($sections['EXPECTREGEX'])) { + throw new InvalidPhptFileException; + } + } +} diff --git a/src/Runner/Phpt/Renderer.php b/src/Runner/Phpt/Renderer.php new file mode 100644 index 00000000000..0fe1de925e7 --- /dev/null +++ b/src/Runner/Phpt/Renderer.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Phpt; + +use function assert; +use function defined; +use function dirname; +use function file_put_contents; +use function str_replace; +use function var_export; +use PHPUnit\TextUI\Configuration\Registry as ConfigurationRegistry; +use SebastianBergmann\Template\InvalidArgumentException; +use SebastianBergmann\Template\Template; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @see https://qa.php.net/phpt_details.php + */ +final readonly class Renderer +{ + /** + * @param non-empty-string $phptFile + * @param non-empty-string $code + * + * @return non-empty-string + */ + public function render(string $phptFile, string $code): string + { + return str_replace( + [ + '__DIR__', + '__FILE__', + ], + [ + "'" . dirname($phptFile) . "'", + "'" . $phptFile . "'", + ], + $code, + ); + } + + /** + * @param non-empty-string $job + * @param array{coverage: non-empty-string, job: non-empty-string} $files + * + * @param-out non-empty-string $job + * + * @throws InvalidArgumentException + */ + public function renderForCoverage(string &$job, bool $pathCoverage, ?string $codeCoverageCacheDirectory, array $files): void + { + $template = new Template( + __DIR__ . '/templates/phpt.tpl', + ); + + $composerAutoload = '\'\''; + + if (defined('PHPUNIT_COMPOSER_INSTALL')) { + $composerAutoload = var_export(PHPUNIT_COMPOSER_INSTALL, true); + } + + $phar = '\'\''; + + if (defined('__PHPUNIT_PHAR__')) { + $phar = var_export(__PHPUNIT_PHAR__, true); + } + + if ($codeCoverageCacheDirectory === null) { + $codeCoverageCacheDirectory = 'null'; + } else { + $codeCoverageCacheDirectory = "'" . $codeCoverageCacheDirectory . "'"; + } + + $bootstrap = ''; + + if (ConfigurationRegistry::get()->hasBootstrap()) { + $bootstrap = ConfigurationRegistry::get()->bootstrap(); + } + + $template->setVar( + [ + 'bootstrap' => $bootstrap, + 'composerAutoload' => $composerAutoload, + 'phar' => $phar, + 'job' => $files['job'], + 'coverageFile' => $files['coverage'], + 'driverMethod' => $pathCoverage ? 'forLineAndPathCoverage' : 'forLineCoverage', + 'codeCoverageCacheDirectory' => $codeCoverageCacheDirectory, + ], + ); + + file_put_contents($files['job'], $job); + + $rendered = $template->render(); + + assert($rendered !== ''); + + $job = $rendered; + } +} diff --git a/src/Runner/Phpt/TestCase.php b/src/Runner/Phpt/TestCase.php new file mode 100644 index 00000000000..ab75397b20a --- /dev/null +++ b/src/Runner/Phpt/TestCase.php @@ -0,0 +1,709 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Phpt; + +use const DEBUG_BACKTRACE_IGNORE_ARGS; +use const DIRECTORY_SEPARATOR; +use function array_merge; +use function basename; +use function debug_backtrace; +use function dirname; +use function explode; +use function extension_loaded; +use function file_get_contents; +use function is_array; +use function is_file; +use function ltrim; +use function ob_get_clean; +use function ob_start; +use function preg_match; +use function preg_replace; +use function preg_split; +use function realpath; +use function sprintf; +use function str_contains; +use function str_starts_with; +use function strncasecmp; +use function substr; +use function trim; +use function unlink; +use function unserialize; +use PHPUnit\Event\Code\Phpt; +use PHPUnit\Event\Code\ThrowableBuilder; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Event\NoPreviousThrowableException; +use PHPUnit\Framework\Assert; +use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\ExecutionOrderDependency; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\IncompleteTestError; +use PHPUnit\Framework\PhptAssertionFailedError; +use PHPUnit\Framework\Reorderable; +use PHPUnit\Framework\SelfDescribing; +use PHPUnit\Framework\Test; +use PHPUnit\Runner\CodeCoverage; +use PHPUnit\Runner\Exception; +use PHPUnit\Util\PHP\Job; +use PHPUnit\Util\PHP\JobRunnerRegistry; +use SebastianBergmann\CodeCoverage\Data\RawCodeCoverageData; +use SebastianBergmann\CodeCoverage\InvalidArgumentException; +use SebastianBergmann\CodeCoverage\ReflectionException; +use SebastianBergmann\CodeCoverage\Test\TestSize\TestSize; +use SebastianBergmann\CodeCoverage\Test\TestStatus\TestStatus; +use SebastianBergmann\CodeCoverage\TestIdMissingException; +use SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException; +use staabm\SideEffectsDetector\SideEffect; +use staabm\SideEffectsDetector\SideEffectsDetector; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @see https://qa.php.net/phpt_details.php + */ +final readonly class TestCase implements Reorderable, SelfDescribing, Test +{ + /** + * @var non-empty-string + */ + private string $filename; + + /** + * @param non-empty-string $filename + */ + public function __construct(string $filename) + { + $this->filename = $filename; + } + + public function count(): int + { + return 1; + } + + /** + * @throws \PHPUnit\Framework\Exception + * @throws \SebastianBergmann\Template\InvalidArgumentException + * @throws Exception + * @throws InvalidArgumentException + * @throws NoPreviousThrowableException + * @throws ReflectionException + * @throws TestIdMissingException + * @throws UnintentionallyCoveredCodeException + */ + public function run(): void + { + $emitter = EventFacade::emitter(); + $parser = new Parser; + + $emitter->testPreparationStarted( + $this->valueObjectForEvents(), + ); + + try { + $sections = $parser->parse($this->filename); + } catch (Exception $e) { + $emitter->testPrepared($this->valueObjectForEvents()); + $emitter->testErrored($this->valueObjectForEvents(), ThrowableBuilder::from($e)); + $emitter->testFinished($this->valueObjectForEvents(), 0); + + return; + } + + $code = (new Renderer)->render($this->filename, $sections['FILE']); + $xfail = false; + $environmentVariables = []; + $phpSettings = $parser->parseIniSection($this->settings(CodeCoverage::instance()->isActive())); + $input = null; + $arguments = []; + + $emitter->testPrepared($this->valueObjectForEvents()); + + if (isset($sections['INI'])) { + $phpSettings = $parser->parseIniSection($sections['INI'], $phpSettings); + } + + if (isset($sections['ENV'])) { + $environmentVariables = $parser->parseEnvSection($sections['ENV']); + } + + if ($this->shouldTestBeSkipped($sections, $phpSettings)) { + return; + } + + if (isset($sections['XFAIL'])) { + $xfail = trim($sections['XFAIL']); + } + + if (isset($sections['STDIN'])) { + $input = $sections['STDIN']; + } + + if (isset($sections['ARGS'])) { + $arguments = explode(' ', $sections['ARGS']); + } + + if (CodeCoverage::instance()->isActive()) { + $codeCoverageCacheDirectory = null; + + if (CodeCoverage::instance()->codeCoverage()->cachesStaticAnalysis()) { + $codeCoverageCacheDirectory = CodeCoverage::instance()->codeCoverage()->cacheDirectory(); + } + + (new Renderer)->renderForCoverage( + $code, + CodeCoverage::instance()->codeCoverage()->collectsBranchAndPathCoverage(), + $codeCoverageCacheDirectory, + $this->coverageFiles(), + ); + } + + $jobResult = JobRunnerRegistry::run( + new Job( + $code, + $this->stringifyIni($phpSettings), + $environmentVariables, + $arguments, + $input, + true, + ), + ); + + EventFacade::emitter()->childProcessFinished($jobResult->stdout(), $jobResult->stderr()); + + $output = $jobResult->stdout(); + + if (CodeCoverage::instance()->isActive()) { + $coverage = $this->cleanupForCoverage(); + + CodeCoverage::instance()->codeCoverage()->start($this->filename, TestSize::large()); + + CodeCoverage::instance()->codeCoverage()->append( + $coverage, + $this->filename, + true, + TestStatus::unknown(), + ); + } + + $passed = true; + + try { + $this->assertPhptExpectation($sections, $output); + } catch (AssertionFailedError $e) { + $failure = $e; + + if ($xfail !== false) { + $failure = new IncompleteTestError($xfail, 0, $e); + } elseif ($e instanceof ExpectationFailedException) { + $comparisonFailure = $e->getComparisonFailure(); + + if ($comparisonFailure !== null) { + $diff = $comparisonFailure->getDiff(); + } else { + $diff = $e->getMessage(); + } + + $hint = $this->locationHintFromDiff($diff, $sections); + $trace = array_merge($hint, debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)); + $failure = new PhptAssertionFailedError( + $e->getMessage(), + 0, + (string) $trace[0]['file'], + (int) $trace[0]['line'], + $trace, + $comparisonFailure !== null ? $diff : '', + ); + } + + if ($failure instanceof IncompleteTestError) { + $emitter->testMarkedAsIncomplete($this->valueObjectForEvents(), ThrowableBuilder::from($failure)); + } else { + $emitter->testFailed($this->valueObjectForEvents(), ThrowableBuilder::from($failure), null); + } + + $passed = false; + } catch (Throwable $t) { + $emitter->testErrored($this->valueObjectForEvents(), ThrowableBuilder::from($t)); + + $passed = false; + } + + if ($passed) { + $emitter->testPassed($this->valueObjectForEvents()); + } + + $this->runClean($sections, CodeCoverage::instance()->isActive()); + + $emitter->testFinished($this->valueObjectForEvents(), 1); + } + + /** + * Returns the name of the test case. + */ + public function getName(): string + { + return $this->toString(); + } + + /** + * Returns a string representation of the test case. + */ + public function toString(): string + { + return $this->filename; + } + + public function sortId(): string + { + return $this->filename; + } + + /** + * @return list + */ + public function provides(): array + { + return []; + } + + /** + * @return list + */ + public function requires(): array + { + return []; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function valueObjectForEvents(): Phpt + { + return new Phpt($this->filename); + } + + /** + * @param array $sections + * + * @throws Exception + * @throws ExpectationFailedException + */ + private function assertPhptExpectation(array $sections, string $output): void + { + $assertions = [ + 'EXPECT' => 'assertEquals', + 'EXPECTF' => 'assertStringMatchesFormat', + 'EXPECTREGEX' => 'assertMatchesRegularExpression', + ]; + + $actual = preg_replace('/\r\n/', "\n", trim($output)); + + foreach ($assertions as $sectionName => $sectionAssertion) { + if (isset($sections[$sectionName])) { + $sectionContent = preg_replace('/\r\n/', "\n", trim($sections[$sectionName])); + $expected = $sectionName === 'EXPECTREGEX' ? "/{$sectionContent}/" : $sectionContent; + + /** @phpstan-ignore staticMethod.dynamicName */ + Assert::$sectionAssertion($expected, $actual); + + return; + } + } + + throw new InvalidPhptFileException; + } + + /** + * @param array $sections + * @param array|non-empty-string> $settings + */ + private function shouldTestBeSkipped(array $sections, array $settings): bool + { + if (!isset($sections['SKIPIF'])) { + return false; + } + + $skipIfCode = (new Renderer)->render($this->filename, $sections['SKIPIF']); + + if ($this->shouldRunInSubprocess($sections, $skipIfCode)) { + $jobResult = JobRunnerRegistry::run( + new Job( + $skipIfCode, + $this->stringifyIni($settings), + ), + ); + + $output = $jobResult->stdout(); + + EventFacade::emitter()->childProcessFinished($output, $jobResult->stderr()); + } else { + $output = $this->runCodeInLocalSandbox($skipIfCode); + } + + $this->triggerRunnerWarningOnPhpErrors('SKIPIF', $output); + + if (strncasecmp('skip', ltrim($output), 4) === 0) { + $message = ''; + + if (preg_match('/^\s*skip\s*(.+)\s*/i', $output, $skipMatch)) { + $message = substr($skipMatch[1], 2); + } + + EventFacade::emitter()->testSkipped( + $this->valueObjectForEvents(), + $message, + ); + + EventFacade::emitter()->testFinished($this->valueObjectForEvents(), 0); + + return true; + } + + return false; + } + + /** + * @param array $sections + */ + private function shouldRunInSubprocess(array $sections, string $cleanCode): bool + { + if (isset($sections['INI'])) { + // to get per-test INI settings, we need a dedicated subprocess + return true; + } + + $detector = new SideEffectsDetector; + $sideEffects = $detector->getSideEffects($cleanCode); + + if ($sideEffects === []) { + // no side-effects + return false; + } + + foreach ($sideEffects as $sideEffect) { + if ($sideEffect === SideEffect::STANDARD_OUTPUT) { + // stdout is fine, we will catch it using output-buffering + continue; + } + + if ($sideEffect === SideEffect::INPUT_OUTPUT) { + // IO is fine, as it doesn't pollute the main process + continue; + } + + return true; + } + + return false; + } + + private function runCodeInLocalSandbox(string $code): string + { + $code = preg_replace('/^<\?(?:php)?|\?>\s*+$/', '', $code); + $code = preg_replace('/declare\S?\([^)]+\)\S?;/', '', $code); + + // wrap in immediately invoked function to isolate local-side-effects of $code from our own process + $code = '(function() {' . $code . '})();'; + ob_start(); + @eval($code); + + return ob_get_clean(); + } + + /** + * @param array $sections + */ + private function runClean(array $sections, bool $collectCoverage): void + { + if (!isset($sections['CLEAN'])) { + return; + } + + $cleanCode = (new Renderer)->render($this->filename, $sections['CLEAN']); + + if ($this->shouldRunInSubprocess($sections, $cleanCode)) { + $jobResult = JobRunnerRegistry::run( + new Job( + $cleanCode, + $this->settings($collectCoverage), + ), + ); + + $output = $jobResult->stdout(); + + EventFacade::emitter()->childProcessFinished($jobResult->stdout(), $jobResult->stderr()); + } else { + $output = $this->runCodeInLocalSandbox($cleanCode); + } + + $this->triggerRunnerWarningOnPhpErrors('CLEAN', $output); + } + + /** + * @phpstan-ignore return.internalClass + */ + private function cleanupForCoverage(): RawCodeCoverageData + { + /** + * @phpstan-ignore staticMethod.internalClass + */ + $coverage = RawCodeCoverageData::fromXdebugWithoutPathCoverage([]); + $files = $this->coverageFiles(); + + $buffer = false; + + if (is_file($files['coverage'])) { + $buffer = @file_get_contents($files['coverage']); + } + + if ($buffer !== false) { + $coverage = @unserialize($buffer); + + if ($coverage === false) { + /** + * @phpstan-ignore staticMethod.internalClass + */ + $coverage = RawCodeCoverageData::fromXdebugWithoutPathCoverage([]); + } + } + + foreach ($files as $file) { + @unlink($file); + } + + return $coverage; + } + + /** + * @return array{coverage: non-empty-string, job: non-empty-string} + */ + private function coverageFiles(): array + { + $baseDir = dirname(realpath($this->filename)) . DIRECTORY_SEPARATOR; + $basename = basename($this->filename, 'phpt'); + + return [ + 'coverage' => $baseDir . $basename . 'coverage', + 'job' => $baseDir . $basename . 'php', + ]; + } + + /** + * @param array|non-empty-string> $ini + * + * @return list + */ + private function stringifyIni(array $ini): array + { + $settings = []; + + foreach ($ini as $key => $value) { + if (is_array($value)) { + foreach ($value as $val) { + $settings[] = $key . '=' . $val; + } + + continue; + } + + $settings[] = $key . '=' . $value; + } + + return $settings; + } + + /** + * @param array $sections + * + * @return non-empty-list + */ + private function locationHintFromDiff(string $message, array $sections): array + { + $needle = ''; + $previousLine = ''; + $block = 'message'; + + foreach (preg_split('/\r\n|\r|\n/', $message) as $line) { + $line = trim($line); + + if ($block === 'message' && $line === '--- Expected') { + $block = 'expected'; + } + + if ($block === 'expected' && $line === '@@ @@') { + $block = 'diff'; + } + + if ($block === 'diff') { + if (str_starts_with($line, '+')) { + $needle = $this->cleanDiffLine($previousLine); + + break; + } + + if (str_starts_with($line, '-')) { + $needle = $this->cleanDiffLine($line); + + break; + } + } + + if ($line !== '') { + $previousLine = $line; + } + } + + return $this->locationHint($needle, $sections); + } + + private function cleanDiffLine(string $line): string + { + if (preg_match('/^[\-+]([\'\"]?)(.*)\1$/', $line, $matches)) { + $line = $matches[2]; + } + + return $line; + } + + /** + * @param array $sections + * + * @return non-empty-list + */ + private function locationHint(string $needle, array $sections): array + { + $needle = trim($needle); + + if ($needle === '') { + return [[ + 'file' => realpath($this->filename), + 'line' => 1, + ]]; + } + + $search = [ + // 'FILE', + 'EXPECT', + 'EXPECTF', + 'EXPECTREGEX', + ]; + + foreach ($search as $section) { + if (!isset($sections[$section])) { + continue; + } + + if (isset($sections[$section . '_EXTERNAL'])) { + $externalFile = trim($sections[$section . '_EXTERNAL']); + + return [ + [ + 'file' => realpath(dirname($this->filename) . DIRECTORY_SEPARATOR . $externalFile), + 'line' => 1, + ], + [ + 'file' => realpath($this->filename), + 'line' => ($sections[$section . '_EXTERNAL_offset'] ?? 0) + 1, + ], + ]; + } + + $sectionOffset = $sections[$section . '_offset'] ?? 0; + $offset = $sectionOffset + 1; + + foreach (preg_split('/\r\n|\r|\n/', $sections[$section]) as $line) { + if (str_contains($line, $needle)) { + return [ + [ + 'file' => realpath($this->filename), + 'line' => $offset, + ], + ]; + } + + $offset++; + } + } + + return [ + [ + 'file' => realpath($this->filename), + 'line' => 1, + ], + ]; + } + + /** + * @return list + */ + private function settings(bool $collectCoverage): array + { + $settings = [ + 'allow_url_fopen=1', + 'auto_append_file=', + 'auto_prepend_file=', + 'disable_functions=', + 'display_errors=1', + 'docref_ext=.html', + 'docref_root=', + 'error_append_string=', + 'error_prepend_string=', + 'error_reporting=-1', + 'html_errors=0', + 'log_errors=0', + 'open_basedir=', + 'output_buffering=Off', + 'output_handler=', + 'report_zend_debug=0', + ]; + + if (extension_loaded('pcov')) { + if ($collectCoverage) { + $settings[] = 'pcov.enabled=1'; + } else { + $settings[] = 'pcov.enabled=0'; + } + } + + if (extension_loaded('xdebug')) { + if ($collectCoverage) { + $settings[] = 'xdebug.mode=coverage'; + } + } + + return $settings; + } + + private function triggerRunnerWarningOnPhpErrors(string $section, string $output): void + { + if (str_contains($output, 'Parse error:')) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + '%s section triggered a parse error: %s', + $section, + $output, + ), + ); + } + + if (str_contains($output, 'Fatal error:')) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + '%s section triggered a fatal error: %s', + $section, + $output, + ), + ); + } + } +} diff --git a/src/Runner/Phpt/templates/phpt.tpl b/src/Runner/Phpt/templates/phpt.tpl new file mode 100644 index 00000000000..c42518886b5 --- /dev/null +++ b/src/Runner/Phpt/templates/phpt.tpl @@ -0,0 +1,56 @@ +{driverMethod}($filter), + $filter + ); + + if ({codeCoverageCacheDirectory}) { + $coverage->cacheStaticAnalysis({codeCoverageCacheDirectory}); + } + + $coverage->start(__FILE__); +} + +register_shutdown_function( + function() use ($coverage) { + $output = null; + + if ($coverage) { + $output = $coverage->stop(); + } + + file_put_contents('{coverageFile}', serialize($output)); + } +); + +ob_end_clean(); + +require '{job}'; diff --git a/src/Runner/PhptTestCase.php b/src/Runner/PhptTestCase.php deleted file mode 100644 index e0adad0d93d..00000000000 --- a/src/Runner/PhptTestCase.php +++ /dev/null @@ -1,850 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -use const DEBUG_BACKTRACE_IGNORE_ARGS; -use const DIRECTORY_SEPARATOR; -use function array_merge; -use function basename; -use function debug_backtrace; -use function defined; -use function dirname; -use function explode; -use function extension_loaded; -use function file; -use function file_exists; -use function file_get_contents; -use function file_put_contents; -use function is_array; -use function is_file; -use function is_readable; -use function is_string; -use function ltrim; -use function phpversion; -use function preg_match; -use function preg_replace; -use function preg_split; -use function realpath; -use function rtrim; -use function sprintf; -use function str_replace; -use function strncasecmp; -use function strpos; -use function substr; -use function trim; -use function unlink; -use function unserialize; -use function var_export; -use function version_compare; -use PHPUnit\Framework\Assert; -use PHPUnit\Framework\AssertionFailedError; -use PHPUnit\Framework\ExecutionOrderDependency; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\IncompleteTestError; -use PHPUnit\Framework\PHPTAssertionFailedError; -use PHPUnit\Framework\Reorderable; -use PHPUnit\Framework\SelfDescribing; -use PHPUnit\Framework\SkippedTestError; -use PHPUnit\Framework\SyntheticSkippedError; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestResult; -use PHPUnit\Util\PHP\AbstractPhpProcess; -use SebastianBergmann\CodeCoverage\RawCodeCoverageData; -use SebastianBergmann\Template\Template; -use SebastianBergmann\Timer\Timer; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class PhptTestCase implements Reorderable, SelfDescribing, Test -{ - /** - * @var string - */ - private $filename; - - /** - * @var AbstractPhpProcess - */ - private $phpUtil; - - /** - * @var string - */ - private $output = ''; - - /** - * Constructs a test case with the given filename. - * - * @throws Exception - */ - public function __construct(string $filename, AbstractPhpProcess $phpUtil = null) - { - if (!is_file($filename)) { - throw new Exception( - sprintf( - 'File "%s" does not exist.', - $filename - ) - ); - } - - $this->filename = $filename; - $this->phpUtil = $phpUtil ?: AbstractPhpProcess::factory(); - } - - /** - * Counts the number of test cases executed by run(TestResult result). - */ - public function count(): int - { - return 1; - } - - /** - * Runs a test and collects its result in a TestResult instance. - * - * @throws Exception - * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException - * @throws \SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function run(TestResult $result = null): TestResult - { - if ($result === null) { - $result = new TestResult; - } - - try { - $sections = $this->parse(); - } catch (Exception $e) { - $result->startTest($this); - $result->addFailure($this, new SkippedTestError($e->getMessage()), 0); - $result->endTest($this, 0); - - return $result; - } - - $code = $this->render($sections['FILE']); - $xfail = false; - $settings = $this->parseIniSection($this->settings($result->getCollectCodeCoverageInformation())); - - $result->startTest($this); - - if (isset($sections['INI'])) { - $settings = $this->parseIniSection($sections['INI'], $settings); - } - - if (isset($sections['ENV'])) { - $env = $this->parseEnvSection($sections['ENV']); - $this->phpUtil->setEnv($env); - } - - $this->phpUtil->setUseStderrRedirection(true); - - if ($result->enforcesTimeLimit()) { - $this->phpUtil->setTimeout($result->getTimeoutForLargeTests()); - } - - $skip = $this->runSkip($sections, $result, $settings); - - if ($skip) { - return $result; - } - - if (isset($sections['XFAIL'])) { - $xfail = trim($sections['XFAIL']); - } - - if (isset($sections['STDIN'])) { - $this->phpUtil->setStdin($sections['STDIN']); - } - - if (isset($sections['ARGS'])) { - $this->phpUtil->setArgs($sections['ARGS']); - } - - if ($result->getCollectCodeCoverageInformation()) { - $pathCoverage = false; - $codeCoverage = $result->getCodeCoverage(); - - if ($codeCoverage) { - $pathCoverage = $codeCoverage->collectsBranchAndPathCoverage(); - } - - $this->renderForCoverage($code, $pathCoverage); - } - - $timer = new Timer; - $timer->start(); - - $jobResult = $this->phpUtil->runJob($code, $this->stringifyIni($settings)); - $time = $timer->stop()->asSeconds(); - $this->output = $jobResult['stdout'] ?? ''; - - if (isset($codeCoverage) && ($coverage = $this->cleanupForCoverage())) { - $codeCoverage->append($coverage, $this, true, [], []); - } - - try { - $this->assertPhptExpectation($sections, $this->output); - } catch (AssertionFailedError $e) { - $failure = $e; - - if ($xfail !== false) { - $failure = new IncompleteTestError($xfail, 0, $e); - } elseif ($e instanceof ExpectationFailedException) { - $comparisonFailure = $e->getComparisonFailure(); - - if ($comparisonFailure) { - $diff = $comparisonFailure->getDiff(); - } else { - $diff = $e->getMessage(); - } - - $hint = $this->getLocationHintFromDiff($diff, $sections); - $trace = array_merge($hint, debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)); - $failure = new PHPTAssertionFailedError( - $e->getMessage(), - 0, - $trace[0]['file'], - $trace[0]['line'], - $trace, - $comparisonFailure ? $diff : '' - ); - } - - $result->addFailure($this, $failure, $time); - } catch (Throwable $t) { - $result->addError($this, $t, $time); - } - - if ($xfail !== false && $result->allCompletelyImplemented()) { - $result->addFailure($this, new IncompleteTestError('XFAIL section but test passes'), $time); - } - - $this->runClean($sections, $result->getCollectCodeCoverageInformation()); - - $result->endTest($this, $time); - - return $result; - } - - /** - * Returns the name of the test case. - */ - public function getName(): string - { - return $this->toString(); - } - - /** - * Returns a string representation of the test case. - */ - public function toString(): string - { - return $this->filename; - } - - public function usesDataProvider(): bool - { - return false; - } - - public function getNumAssertions(): int - { - return 1; - } - - public function getActualOutput(): string - { - return $this->output; - } - - public function hasOutput(): bool - { - return !empty($this->output); - } - - public function sortId(): string - { - return $this->filename; - } - - /** - * @return list - */ - public function provides(): array - { - return []; - } - - /** - * @return list - */ - public function requires(): array - { - return []; - } - - /** - * Parse --INI-- section key value pairs and return as array. - * - * @param array|string $content - */ - private function parseIniSection($content, array $ini = []): array - { - if (is_string($content)) { - $content = explode("\n", trim($content)); - } - - foreach ($content as $setting) { - if (strpos($setting, '=') === false) { - continue; - } - - $setting = explode('=', $setting, 2); - $name = trim($setting[0]); - $value = trim($setting[1]); - - if ($name === 'extension' || $name === 'zend_extension') { - if (!isset($ini[$name])) { - $ini[$name] = []; - } - - $ini[$name][] = $value; - - continue; - } - - $ini[$name] = $value; - } - - return $ini; - } - - private function parseEnvSection(string $content): array - { - $env = []; - - foreach (explode("\n", trim($content)) as $e) { - $e = explode('=', trim($e), 2); - - if (!empty($e[0]) && isset($e[1])) { - $env[$e[0]] = $e[1]; - } - } - - return $env; - } - - /** - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - */ - private function assertPhptExpectation(array $sections, string $output): void - { - $assertions = [ - 'EXPECT' => 'assertEquals', - 'EXPECTF' => 'assertStringMatchesFormat', - 'EXPECTREGEX' => 'assertMatchesRegularExpression', - ]; - - $actual = preg_replace('/\r\n/', "\n", trim($output)); - - foreach ($assertions as $sectionName => $sectionAssertion) { - if (isset($sections[$sectionName])) { - $sectionContent = preg_replace('/\r\n/', "\n", trim($sections[$sectionName])); - $expected = $sectionName === 'EXPECTREGEX' ? "/{$sectionContent}/" : $sectionContent; - - if ($expected === '') { - throw new Exception('No PHPT expectation found'); - } - - Assert::$sectionAssertion($expected, $actual); - - return; - } - } - - throw new Exception('No PHPT assertion found'); - } - - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - private function runSkip(array &$sections, TestResult $result, array $settings): bool - { - if (!isset($sections['SKIPIF'])) { - return false; - } - - $skipif = $this->render($sections['SKIPIF']); - $jobResult = $this->phpUtil->runJob($skipif, $this->stringifyIni($settings)); - - if (!strncasecmp('skip', ltrim($jobResult['stdout']), 4)) { - $message = ''; - - if (preg_match('/^\s*skip\s*(.+)\s*/i', $jobResult['stdout'], $skipMatch)) { - $message = substr($skipMatch[1], 2); - } - - $hint = $this->getLocationHint($message, $sections, 'SKIPIF'); - $trace = array_merge($hint, debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)); - $result->addFailure( - $this, - new SyntheticSkippedError($message, 0, $trace[0]['file'], $trace[0]['line'], $trace), - 0 - ); - $result->endTest($this, 0); - - return true; - } - - return false; - } - - private function runClean(array &$sections, bool $collectCoverage): void - { - $this->phpUtil->setStdin(''); - $this->phpUtil->setArgs(''); - - if (isset($sections['CLEAN'])) { - $cleanCode = $this->render($sections['CLEAN']); - - $this->phpUtil->runJob($cleanCode, $this->settings($collectCoverage)); - } - } - - /** - * @throws Exception - */ - private function parse(): array - { - $sections = []; - $section = ''; - - $unsupportedSections = [ - 'CGI', - 'COOKIE', - 'DEFLATE_POST', - 'EXPECTHEADERS', - 'EXTENSIONS', - 'GET', - 'GZIP_POST', - 'HEADERS', - 'PHPDBG', - 'POST', - 'POST_RAW', - 'PUT', - 'REDIRECTTEST', - 'REQUEST', - ]; - - $lineNr = 0; - - foreach (file($this->filename) as $line) { - $lineNr++; - - if (preg_match('/^--([_A-Z]+)--/', $line, $result)) { - $section = $result[1]; - $sections[$section] = ''; - $sections[$section . '_offset'] = $lineNr; - - continue; - } - - if (empty($section)) { - throw new Exception('Invalid PHPT file: empty section header'); - } - - $sections[$section] .= $line; - } - - if (isset($sections['FILEEOF'])) { - $sections['FILE'] = rtrim($sections['FILEEOF'], "\r\n"); - unset($sections['FILEEOF']); - } - - $this->parseExternal($sections); - - if (!$this->validate($sections)) { - throw new Exception('Invalid PHPT file'); - } - - foreach ($unsupportedSections as $section) { - if (isset($sections[$section])) { - throw new Exception( - "PHPUnit does not support PHPT {$section} sections" - ); - } - } - - return $sections; - } - - /** - * @throws Exception - */ - private function parseExternal(array &$sections): void - { - $allowSections = [ - 'FILE', - 'EXPECT', - 'EXPECTF', - 'EXPECTREGEX', - ]; - $testDirectory = dirname($this->filename) . DIRECTORY_SEPARATOR; - - foreach ($allowSections as $section) { - if (isset($sections[$section . '_EXTERNAL'])) { - $externalFilename = trim($sections[$section . '_EXTERNAL']); - - if (!is_file($testDirectory . $externalFilename) || - !is_readable($testDirectory . $externalFilename)) { - throw new Exception( - sprintf( - 'Could not load --%s-- %s for PHPT file', - $section . '_EXTERNAL', - $testDirectory . $externalFilename - ) - ); - } - - $sections[$section] = file_get_contents($testDirectory . $externalFilename); - } - } - } - - private function validate(array &$sections): bool - { - $requiredSections = [ - 'FILE', - [ - 'EXPECT', - 'EXPECTF', - 'EXPECTREGEX', - ], - ]; - - foreach ($requiredSections as $section) { - if (is_array($section)) { - $foundSection = false; - - foreach ($section as $anySection) { - if (isset($sections[$anySection])) { - $foundSection = true; - - break; - } - } - - if (!$foundSection) { - return false; - } - - continue; - } - - if (!isset($sections[$section])) { - return false; - } - } - - return true; - } - - private function render(string $code): string - { - return str_replace( - [ - '__DIR__', - '__FILE__', - ], - [ - "'" . dirname($this->filename) . "'", - "'" . $this->filename . "'", - ], - $code - ); - } - - private function getCoverageFiles(): array - { - $baseDir = dirname(realpath($this->filename)) . DIRECTORY_SEPARATOR; - $basename = basename($this->filename, 'phpt'); - - return [ - 'coverage' => $baseDir . $basename . 'coverage', - 'job' => $baseDir . $basename . 'php', - ]; - } - - private function renderForCoverage(string &$job, bool $pathCoverage): void - { - $files = $this->getCoverageFiles(); - - $template = new Template( - __DIR__ . '/../Util/PHP/Template/PhptTestCase.tpl' - ); - - $composerAutoload = '\'\''; - - if (defined('PHPUNIT_COMPOSER_INSTALL') && !defined('PHPUNIT_TESTSUITE')) { - $composerAutoload = var_export(PHPUNIT_COMPOSER_INSTALL, true); - } - - $phar = '\'\''; - - if (defined('__PHPUNIT_PHAR__')) { - $phar = var_export(__PHPUNIT_PHAR__, true); - } - - $globals = ''; - - if (!empty($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { - $globals = '$GLOBALS[\'__PHPUNIT_BOOTSTRAP\'] = ' . var_export( - $GLOBALS['__PHPUNIT_BOOTSTRAP'], - true - ) . ";\n"; - } - - $template->setVar( - [ - 'composerAutoload' => $composerAutoload, - 'phar' => $phar, - 'globals' => $globals, - 'job' => $files['job'], - 'coverageFile' => $files['coverage'], - 'driverMethod' => $pathCoverage ? 'forLineAndPathCoverage' : 'forLineCoverage', - ] - ); - - file_put_contents($files['job'], $job); - - $job = $template->render(); - } - - private function cleanupForCoverage(): RawCodeCoverageData - { - $coverage = RawCodeCoverageData::fromXdebugWithoutPathCoverage([]); - $files = $this->getCoverageFiles(); - - if (file_exists($files['coverage'])) { - $buffer = @file_get_contents($files['coverage']); - - if ($buffer !== false) { - $coverage = @unserialize($buffer); - - if ($coverage === false) { - $coverage = RawCodeCoverageData::fromXdebugWithoutPathCoverage([]); - } - } - } - - foreach ($files as $file) { - @unlink($file); - } - - return $coverage; - } - - private function stringifyIni(array $ini): array - { - $settings = []; - - foreach ($ini as $key => $value) { - if (is_array($value)) { - foreach ($value as $val) { - $settings[] = $key . '=' . $val; - } - - continue; - } - - $settings[] = $key . '=' . $value; - } - - return $settings; - } - - private function getLocationHintFromDiff(string $message, array $sections): array - { - $needle = ''; - $previousLine = ''; - $block = 'message'; - - foreach (preg_split('/\r\n|\r|\n/', $message) as $line) { - $line = trim($line); - - if ($block === 'message' && $line === '--- Expected') { - $block = 'expected'; - } - - if ($block === 'expected' && $line === '@@ @@') { - $block = 'diff'; - } - - if ($block === 'diff') { - if (strpos($line, '+') === 0) { - $needle = $this->getCleanDiffLine($previousLine); - - break; - } - - if (strpos($line, '-') === 0) { - $needle = $this->getCleanDiffLine($line); - - break; - } - } - - if (!empty($line)) { - $previousLine = $line; - } - } - - return $this->getLocationHint($needle, $sections); - } - - private function getCleanDiffLine(string $line): string - { - if (preg_match('/^[\-+]([\'\"]?)(.*)\1$/', $line, $matches)) { - $line = $matches[2]; - } - - return $line; - } - - private function getLocationHint(string $needle, array $sections, ?string $sectionName = null): array - { - $needle = trim($needle); - - if (empty($needle)) { - return [[ - 'file' => realpath($this->filename), - 'line' => 1, - ]]; - } - - if ($sectionName) { - $search = [$sectionName]; - } else { - $search = [ - // 'FILE', - 'EXPECT', - 'EXPECTF', - 'EXPECTREGEX', - ]; - } - - foreach ($search as $section) { - if (!isset($sections[$section])) { - continue; - } - - if (isset($sections[$section . '_EXTERNAL'])) { - $externalFile = trim($sections[$section . '_EXTERNAL']); - - return [ - [ - 'file' => realpath(dirname($this->filename) . DIRECTORY_SEPARATOR . $externalFile), - 'line' => 1, - ], - [ - 'file' => realpath($this->filename), - 'line' => ($sections[$section . '_EXTERNAL_offset'] ?? 0) + 1, - ], - ]; - } - - $sectionOffset = $sections[$section . '_offset'] ?? 0; - $offset = $sectionOffset + 1; - - foreach (preg_split('/\r\n|\r|\n/', $sections[$section]) as $line) { - if (strpos($line, $needle) !== false) { - return [[ - 'file' => realpath($this->filename), - 'line' => $offset, - ]]; - } - $offset++; - } - } - - if ($sectionName) { - // String not found in specified section, show user the start of the named section - return [[ - 'file' => realpath($this->filename), - 'line' => $sectionOffset, - ]]; - } - - // No section specified, show user start of code - return [[ - 'file' => realpath($this->filename), - 'line' => 1, - ]]; - } - - /** - * @psalm-return list - */ - private function settings(bool $collectCoverage): array - { - $settings = [ - 'allow_url_fopen=1', - 'auto_append_file=', - 'auto_prepend_file=', - 'disable_functions=', - 'display_errors=1', - 'docref_ext=.html', - 'docref_root=', - 'error_append_string=', - 'error_prepend_string=', - 'error_reporting=-1', - 'html_errors=0', - 'log_errors=0', - 'open_basedir=', - 'output_buffering=Off', - 'output_handler=', - 'report_memleaks=0', - 'report_zend_debug=0', - ]; - - if (extension_loaded('pcov')) { - if ($collectCoverage) { - $settings[] = 'pcov.enabled=1'; - } else { - $settings[] = 'pcov.enabled=0'; - } - } - - if (extension_loaded('xdebug')) { - if (version_compare(phpversion('xdebug'), '3', '>=')) { - if ($collectCoverage) { - $settings[] = 'xdebug.mode=coverage'; - } else { - $settings[] = 'xdebug.mode=off'; - } - } else { - if ($collectCoverage) { - $settings[] = 'xdebug.coverage_enable=1'; - } else { - $settings[] = 'xdebug.default_enable=0'; - } - } - } - - return $settings; - } -} diff --git a/src/Runner/ResultCache/DefaultResultCache.php b/src/Runner/ResultCache/DefaultResultCache.php new file mode 100644 index 00000000000..0d700a9a035 --- /dev/null +++ b/src/Runner/ResultCache/DefaultResultCache.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use const DIRECTORY_SEPARATOR; +use const LOCK_EX; +use function array_keys; +use function assert; +use function dirname; +use function file_get_contents; +use function file_put_contents; +use function is_array; +use function is_dir; +use function is_file; +use function json_decode; +use function json_encode; +use PHPUnit\Framework\TestStatus\TestStatus; +use PHPUnit\Runner\DirectoryDoesNotExistException; +use PHPUnit\Runner\Exception; +use PHPUnit\Util\Filesystem; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class DefaultResultCache implements ResultCache +{ + private const int VERSION = 2; + private const string DEFAULT_RESULT_CACHE_FILENAME = '.phpunit.result.cache'; + private readonly string $cacheFilename; + + /** + * @var array + */ + private array $defects = []; + + /** + * @var array + */ + private array $times = []; + + public function __construct(?string $filepath = null) + { + if ($filepath !== null && is_dir($filepath)) { + $filepath .= DIRECTORY_SEPARATOR . self::DEFAULT_RESULT_CACHE_FILENAME; + } + + $this->cacheFilename = $filepath ?? $_ENV['PHPUNIT_RESULT_CACHE'] ?? self::DEFAULT_RESULT_CACHE_FILENAME; + } + + public function setStatus(ResultCacheId $id, TestStatus $status): void + { + if ($status->isSuccess()) { + return; + } + + $this->defects[$id->asString()] = $status; + } + + public function status(ResultCacheId $id): TestStatus + { + return $this->defects[$id->asString()] ?? TestStatus::unknown(); + } + + public function setTime(ResultCacheId $id, float $time): void + { + $this->times[$id->asString()] = $time; + } + + public function time(ResultCacheId $id): float + { + return $this->times[$id->asString()] ?? 0.0; + } + + public function mergeWith(self $other): void + { + foreach ($other->defects as $id => $defect) { + $this->defects[$id] = $defect; + } + + foreach ($other->times as $id => $time) { + $this->times[$id] = $time; + } + } + + public function load(): void + { + if (!is_file($this->cacheFilename)) { + return; + } + + $contents = file_get_contents($this->cacheFilename); + + if ($contents === false) { + return; + } + + $data = json_decode( + $contents, + true, + ); + + if ($data === null) { + return; + } + + if (!isset($data['version'])) { + return; + } + + if ($data['version'] !== self::VERSION) { + return; + } + + assert(isset($data['defects']) && is_array($data['defects'])); + assert(isset($data['times']) && is_array($data['times'])); + + foreach (array_keys($data['defects']) as $test) { + $data['defects'][$test] = TestStatus::from($data['defects'][$test]); + } + + $this->defects = $data['defects']; + $this->times = $data['times']; + } + + /** + * @throws Exception + */ + public function persist(): void + { + if (!Filesystem::createDirectory(dirname($this->cacheFilename))) { + throw new DirectoryDoesNotExistException(dirname($this->cacheFilename)); + } + + $data = [ + 'version' => self::VERSION, + 'defects' => [], + 'times' => $this->times, + ]; + + foreach ($this->defects as $test => $status) { + $data['defects'][$test] = $status->asInt(); + } + + file_put_contents( + $this->cacheFilename, + json_encode($data), + LOCK_EX, + ); + } +} diff --git a/src/Runner/ResultCache/NullResultCache.php b/src/Runner/ResultCache/NullResultCache.php new file mode 100644 index 00000000000..46417d40ae9 --- /dev/null +++ b/src/Runner/ResultCache/NullResultCache.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use PHPUnit\Framework\TestStatus\TestStatus; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class NullResultCache implements ResultCache +{ + public function setStatus(ResultCacheId $id, TestStatus $status): void + { + } + + public function status(ResultCacheId $id): TestStatus + { + return TestStatus::unknown(); + } + + public function setTime(ResultCacheId $id, float $time): void + { + } + + public function time(ResultCacheId $id): float + { + return 0; + } + + public function load(): void + { + } + + public function persist(): void + { + } +} diff --git a/src/Runner/ResultCache/ResultCache.php b/src/Runner/ResultCache/ResultCache.php new file mode 100644 index 00000000000..a3d62f50b3c --- /dev/null +++ b/src/Runner/ResultCache/ResultCache.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use PHPUnit\Framework\TestStatus\TestStatus; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface ResultCache +{ + public function setStatus(ResultCacheId $id, TestStatus $status): void; + + public function status(ResultCacheId $id): TestStatus; + + public function setTime(ResultCacheId $id, float $time): void; + + public function time(ResultCacheId $id): float; + + public function load(): void; + + public function persist(): void; +} diff --git a/src/Runner/ResultCache/ResultCacheHandler.php b/src/Runner/ResultCache/ResultCacheHandler.php new file mode 100644 index 00000000000..b0b45c6d5e9 --- /dev/null +++ b/src/Runner/ResultCache/ResultCacheHandler.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use function round; +use PHPUnit\Event\Event; +use PHPUnit\Event\Facade; +use PHPUnit\Event\Telemetry\HRTime; +use PHPUnit\Event\Test\ConsideredRisky; +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\Skipped; +use PHPUnit\Framework\InvalidArgumentException; +use PHPUnit\Framework\TestStatus\TestStatus; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ResultCacheHandler +{ + private readonly ResultCache $cache; + private ?HRTime $time = null; + private int $testSuite = 0; + + public function __construct(ResultCache $cache, Facade $facade) + { + $this->cache = $cache; + + $this->registerSubscribers($facade); + } + + public function testSuiteStarted(): void + { + $this->testSuite++; + } + + public function testSuiteFinished(): void + { + $this->testSuite--; + + if ($this->testSuite === 0) { + $this->cache->persist(); + } + } + + public function testPrepared(Prepared $event): void + { + $this->time = $event->telemetryInfo()->time(); + } + + public function testMarkedIncomplete(MarkedIncomplete $event): void + { + $this->cache->setStatus( + ResultCacheId::fromTest($event->test()), + TestStatus::incomplete($event->throwable()->message()), + ); + } + + public function testConsideredRisky(ConsideredRisky $event): void + { + $this->cache->setStatus( + ResultCacheId::fromTest($event->test()), + TestStatus::risky($event->message()), + ); + } + + public function testErrored(Errored $event): void + { + $this->cache->setStatus( + ResultCacheId::fromTest($event->test()), + TestStatus::error($event->throwable()->message()), + ); + } + + public function testFailed(Failed $event): void + { + $this->cache->setStatus( + ResultCacheId::fromTest($event->test()), + TestStatus::failure($event->throwable()->message()), + ); + } + + /** + * @throws \PHPUnit\Event\InvalidArgumentException + * @throws InvalidArgumentException + */ + public function testSkipped(Skipped $event): void + { + $this->cache->setStatus( + ResultCacheId::fromTest($event->test()), + TestStatus::skipped($event->message()), + ); + + $this->cache->setTime(ResultCacheId::fromTest($event->test()), $this->duration($event)); + } + + /** + * @throws \PHPUnit\Event\InvalidArgumentException + * @throws InvalidArgumentException + */ + public function testFinished(Finished $event): void + { + $this->cache->setTime(ResultCacheId::fromTest($event->test()), $this->duration($event)); + + $this->time = null; + } + + /** + * @throws \PHPUnit\Event\InvalidArgumentException + * @throws InvalidArgumentException + */ + private function duration(Event $event): float + { + if ($this->time === null) { + return 0.0; + } + + return round($event->telemetryInfo()->time()->duration($this->time)->asFloat(), 3); + } + + private function registerSubscribers(Facade $facade): void + { + $facade->registerSubscribers( + new TestSuiteStartedSubscriber($this), + new TestSuiteFinishedSubscriber($this), + new TestPreparedSubscriber($this), + new TestMarkedIncompleteSubscriber($this), + new TestConsideredRiskySubscriber($this), + new TestErroredSubscriber($this), + new TestFailedSubscriber($this), + new TestSkippedSubscriber($this), + new TestFinishedSubscriber($this), + ); + } +} diff --git a/src/Runner/ResultCache/ResultCacheId.php b/src/Runner/ResultCache/ResultCacheId.php new file mode 100644 index 00000000000..35a84f287e7 --- /dev/null +++ b/src/Runner/ResultCache/ResultCacheId.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Framework\Reorderable; +use PHPUnit\Framework\TestCase; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ResultCacheId +{ + public static function fromTest(Test $test): self + { + if ($test instanceof TestMethod) { + return new self($test->className() . '::' . $test->name()); + } + + return new self($test->id()); + } + + public static function fromReorderable(Reorderable $reorderable): self + { + return new self($reorderable->sortId()); + } + + /** + * For use in PHPUnit tests only! + * + * @param class-string $class + */ + public static function fromTestClassAndMethodName(string $class, string $methodName): self + { + return new self($class . '::' . $methodName); + } + + private function __construct( + private string $id, + ) { + } + + public function asString(): string + { + return $this->id; + } +} diff --git a/src/Runner/ResultCache/Subscriber/Subscriber.php b/src/Runner/ResultCache/Subscriber/Subscriber.php new file mode 100644 index 00000000000..d64dd9f4a22 --- /dev/null +++ b/src/Runner/ResultCache/Subscriber/Subscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Subscriber +{ + private ResultCacheHandler $handler; + + public function __construct(ResultCacheHandler $handler) + { + $this->handler = $handler; + } + + protected function handler(): ResultCacheHandler + { + return $this->handler; + } +} diff --git a/src/Runner/ResultCache/Subscriber/TestConsideredRiskySubscriber.php b/src/Runner/ResultCache/Subscriber/TestConsideredRiskySubscriber.php new file mode 100644 index 00000000000..b2d934013ab --- /dev/null +++ b/src/Runner/ResultCache/Subscriber/TestConsideredRiskySubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use PHPUnit\Event\Test\ConsideredRisky; +use PHPUnit\Event\Test\ConsideredRiskySubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestConsideredRiskySubscriber extends Subscriber implements ConsideredRiskySubscriber +{ + public function notify(ConsideredRisky $event): void + { + $this->handler()->testConsideredRisky($event); + } +} diff --git a/src/Runner/ResultCache/Subscriber/TestErroredSubscriber.php b/src/Runner/ResultCache/Subscriber/TestErroredSubscriber.php new file mode 100644 index 00000000000..ff34e0d8f33 --- /dev/null +++ b/src/Runner/ResultCache/Subscriber/TestErroredSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\ErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestErroredSubscriber extends Subscriber implements ErroredSubscriber +{ + public function notify(Errored $event): void + { + $this->handler()->testErrored($event); + } +} diff --git a/src/Runner/ResultCache/Subscriber/TestFailedSubscriber.php b/src/Runner/ResultCache/Subscriber/TestFailedSubscriber.php new file mode 100644 index 00000000000..082fa51bd6a --- /dev/null +++ b/src/Runner/ResultCache/Subscriber/TestFailedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\FailedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFailedSubscriber extends Subscriber implements FailedSubscriber +{ + public function notify(Failed $event): void + { + $this->handler()->testFailed($event); + } +} diff --git a/src/Runner/ResultCache/Subscriber/TestFinishedSubscriber.php b/src/Runner/ResultCache/Subscriber/TestFinishedSubscriber.php new file mode 100644 index 00000000000..65f75fcb6f5 --- /dev/null +++ b/src/Runner/ResultCache/Subscriber/TestFinishedSubscriber.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + /** + * @throws \PHPUnit\Framework\InvalidArgumentException + * @throws InvalidArgumentException + */ + public function notify(Finished $event): void + { + $this->handler()->testFinished($event); + } +} diff --git a/src/Runner/ResultCache/Subscriber/TestMarkedIncompleteSubscriber.php b/src/Runner/ResultCache/Subscriber/TestMarkedIncompleteSubscriber.php new file mode 100644 index 00000000000..d9c65cf8cff --- /dev/null +++ b/src/Runner/ResultCache/Subscriber/TestMarkedIncompleteSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\MarkedIncompleteSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestMarkedIncompleteSubscriber extends Subscriber implements MarkedIncompleteSubscriber +{ + public function notify(MarkedIncomplete $event): void + { + $this->handler()->testMarkedIncomplete($event); + } +} diff --git a/src/Runner/ResultCache/Subscriber/TestPreparedSubscriber.php b/src/Runner/ResultCache/Subscriber/TestPreparedSubscriber.php new file mode 100644 index 00000000000..a92b82777f0 --- /dev/null +++ b/src/Runner/ResultCache/Subscriber/TestPreparedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\PreparedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPreparedSubscriber extends Subscriber implements PreparedSubscriber +{ + public function notify(Prepared $event): void + { + $this->handler()->testPrepared($event); + } +} diff --git a/src/Runner/ResultCache/Subscriber/TestSkippedSubscriber.php b/src/Runner/ResultCache/Subscriber/TestSkippedSubscriber.php new file mode 100644 index 00000000000..0e493bdc25b --- /dev/null +++ b/src/Runner/ResultCache/Subscriber/TestSkippedSubscriber.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Skipped; +use PHPUnit\Event\Test\SkippedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSkippedSubscriber extends Subscriber implements SkippedSubscriber +{ + /** + * @throws \PHPUnit\Framework\InvalidArgumentException + * @throws InvalidArgumentException + */ + public function notify(Skipped $event): void + { + $this->handler()->testSkipped($event); + } +} diff --git a/src/Runner/ResultCache/Subscriber/TestSuiteFinishedSubscriber.php b/src/Runner/ResultCache/Subscriber/TestSuiteFinishedSubscriber.php new file mode 100644 index 00000000000..1ef0cc3fb46 --- /dev/null +++ b/src/Runner/ResultCache/Subscriber/TestSuiteFinishedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use PHPUnit\Event\TestSuite\Finished; +use PHPUnit\Event\TestSuite\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + public function notify(Finished $event): void + { + $this->handler()->testSuiteFinished(); + } +} diff --git a/src/Runner/ResultCache/Subscriber/TestSuiteStartedSubscriber.php b/src/Runner/ResultCache/Subscriber/TestSuiteStartedSubscriber.php new file mode 100644 index 00000000000..cddedf511d6 --- /dev/null +++ b/src/Runner/ResultCache/Subscriber/TestSuiteStartedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use PHPUnit\Event\TestSuite\Started; +use PHPUnit\Event\TestSuite\StartedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteStartedSubscriber extends Subscriber implements StartedSubscriber +{ + public function notify(Started $event): void + { + $this->handler()->testSuiteStarted(); + } +} diff --git a/src/Runner/ResultCacheExtension.php b/src/Runner/ResultCacheExtension.php deleted file mode 100644 index 31d7610e2bc..00000000000 --- a/src/Runner/ResultCacheExtension.php +++ /dev/null @@ -1,110 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -use function preg_match; -use function round; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ResultCacheExtension implements AfterIncompleteTestHook, AfterLastTestHook, AfterRiskyTestHook, AfterSkippedTestHook, AfterSuccessfulTestHook, AfterTestErrorHook, AfterTestFailureHook, AfterTestWarningHook -{ - /** - * @var TestResultCache - */ - private $cache; - - public function __construct(TestResultCache $cache) - { - $this->cache = $cache; - } - - public function flush(): void - { - $this->cache->persist(); - } - - public function executeAfterSuccessfulTest(string $test, float $time): void - { - $testName = $this->getTestName($test); - - $this->cache->setTime($testName, round($time, 3)); - } - - public function executeAfterIncompleteTest(string $test, string $message, float $time): void - { - $testName = $this->getTestName($test); - - $this->cache->setTime($testName, round($time, 3)); - $this->cache->setState($testName, BaseTestRunner::STATUS_INCOMPLETE); - } - - public function executeAfterRiskyTest(string $test, string $message, float $time): void - { - $testName = $this->getTestName($test); - - $this->cache->setTime($testName, round($time, 3)); - $this->cache->setState($testName, BaseTestRunner::STATUS_RISKY); - } - - public function executeAfterSkippedTest(string $test, string $message, float $time): void - { - $testName = $this->getTestName($test); - - $this->cache->setTime($testName, round($time, 3)); - $this->cache->setState($testName, BaseTestRunner::STATUS_SKIPPED); - } - - public function executeAfterTestError(string $test, string $message, float $time): void - { - $testName = $this->getTestName($test); - - $this->cache->setTime($testName, round($time, 3)); - $this->cache->setState($testName, BaseTestRunner::STATUS_ERROR); - } - - public function executeAfterTestFailure(string $test, string $message, float $time): void - { - $testName = $this->getTestName($test); - - $this->cache->setTime($testName, round($time, 3)); - $this->cache->setState($testName, BaseTestRunner::STATUS_FAILURE); - } - - public function executeAfterTestWarning(string $test, string $message, float $time): void - { - $testName = $this->getTestName($test); - - $this->cache->setTime($testName, round($time, 3)); - $this->cache->setState($testName, BaseTestRunner::STATUS_WARNING); - } - - public function executeAfterLastTest(): void - { - $this->flush(); - } - - /** - * @param string $test A long description format of the current test - * - * @return string The test name without TestSuiteClassName:: and @dataprovider details - */ - private function getTestName(string $test): string - { - $matches = []; - - if (preg_match('/^(?\S+::\S+)(?:(? with data set (?:#\d+|"[^"]+"))\s\()?/', $test, $matches)) { - $test = $matches['name'] . ($matches['dataname'] ?? ''); - } - - return $test; - } -} diff --git a/src/Runner/ShutdownHandler.php b/src/Runner/ShutdownHandler.php new file mode 100644 index 00000000000..faecf088c31 --- /dev/null +++ b/src/Runner/ShutdownHandler.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use const PHP_EOL; +use function register_shutdown_function; +use function rtrim; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ShutdownHandler +{ + private static bool $registered = false; + private static string $message = ''; + + public static function setMessage(string $message): void + { + self::register(); + + self::$message = $message; + } + + public static function resetMessage(): void + { + self::$message = ''; + } + + private static function register(): void + { + if (self::$registered) { + return; + } + + self::$registered = true; + + register_shutdown_function( + static function (): void + { + $message = rtrim(self::$message); + + if ($message === '') { + return; + } + + print $message . PHP_EOL; + }, + ); + } +} diff --git a/src/Runner/StandardTestSuiteLoader.php b/src/Runner/StandardTestSuiteLoader.php deleted file mode 100644 index 4a815aa4e72..00000000000 --- a/src/Runner/StandardTestSuiteLoader.php +++ /dev/null @@ -1,123 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -use function array_diff; -use function array_values; -use function basename; -use function class_exists; -use function get_declared_classes; -use function sprintf; -use function str_replace; -use function strlen; -use function substr; -use PHPUnit\Framework\TestCase; -use PHPUnit\Util\FileLoader; -use ReflectionClass; -use ReflectionException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 - */ -final class StandardTestSuiteLoader implements TestSuiteLoader -{ - /** - * @throws Exception - */ - public function load(string $suiteClassFile): ReflectionClass - { - $suiteClassName = basename($suiteClassFile, '.php'); - $loadedClasses = get_declared_classes(); - - if (!class_exists($suiteClassName, false)) { - /* @noinspection UnusedFunctionResultInspection */ - FileLoader::checkAndLoad($suiteClassFile); - - $loadedClasses = array_values( - array_diff(get_declared_classes(), $loadedClasses) - ); - - if (empty($loadedClasses)) { - throw $this->exceptionFor($suiteClassName, $suiteClassFile); - } - } - - if (!class_exists($suiteClassName, false)) { - $offset = 0 - strlen($suiteClassName); - - foreach ($loadedClasses as $loadedClass) { - if (substr($loadedClass, $offset) === $suiteClassName && - basename(str_replace('\\', '/', $loadedClass)) === $suiteClassName) { - $suiteClassName = $loadedClass; - - break; - } - } - } - - if (!class_exists($suiteClassName, false)) { - throw $this->exceptionFor($suiteClassName, $suiteClassFile); - } - - try { - $class = new ReflectionClass($suiteClassName); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - if ($class->isSubclassOf(TestCase::class) && !$class->isAbstract()) { - return $class; - } - - if ($class->hasMethod('suite')) { - try { - $method = $class->getMethod('suite'); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - if (!$method->isAbstract() && $method->isPublic() && $method->isStatic()) { - return $class; - } - } - - throw $this->exceptionFor($suiteClassName, $suiteClassFile); - } - - public function reload(ReflectionClass $aClass): ReflectionClass - { - return $aClass; - } - - private function exceptionFor(string $className, string $filename): Exception - { - return new Exception( - sprintf( - "Class '%s' could not be found in '%s'.", - $className, - $filename - ) - ); - } -} diff --git a/src/Runner/TestResult/Collector.php b/src/Runner/TestResult/Collector.php new file mode 100644 index 00000000000..5615a0dade7 --- /dev/null +++ b/src/Runner/TestResult/Collector.php @@ -0,0 +1,681 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use function array_values; +use function assert; +use function count; +use function implode; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\Facade; +use PHPUnit\Event\Test\AfterLastTestMethodErrored; +use PHPUnit\Event\Test\AfterLastTestMethodFailed; +use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; +use PHPUnit\Event\Test\BeforeFirstTestMethodFailed; +use PHPUnit\Event\Test\ConsideredRisky; +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\ErrorTriggered; +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\NoticeTriggered; +use PHPUnit\Event\Test\PhpDeprecationTriggered; +use PHPUnit\Event\Test\PhpNoticeTriggered; +use PHPUnit\Event\Test\PhpunitDeprecationTriggered; +use PHPUnit\Event\Test\PhpunitErrorTriggered; +use PHPUnit\Event\Test\PhpunitNoticeTriggered; +use PHPUnit\Event\Test\PhpunitWarningTriggered; +use PHPUnit\Event\Test\PhpWarningTriggered; +use PHPUnit\Event\Test\Skipped as TestSkipped; +use PHPUnit\Event\Test\WarningTriggered; +use PHPUnit\Event\TestRunner\ChildProcessErrored; +use PHPUnit\Event\TestRunner\DeprecationTriggered as TestRunnerDeprecationTriggered; +use PHPUnit\Event\TestRunner\ExecutionStarted; +use PHPUnit\Event\TestRunner\NoticeTriggered as TestRunnerNoticeTriggered; +use PHPUnit\Event\TestRunner\WarningTriggered as TestRunnerWarningTriggered; +use PHPUnit\Event\TestSuite\Finished as TestSuiteFinished; +use PHPUnit\Event\TestSuite\Skipped as TestSuiteSkipped; +use PHPUnit\Event\TestSuite\Started as TestSuiteStarted; +use PHPUnit\Event\TestSuite\TestSuiteForTestClass; +use PHPUnit\Event\TestSuite\TestSuiteForTestMethodWithDataProvider; +use PHPUnit\TestRunner\IssueFilter; +use PHPUnit\TestRunner\TestResult\Issues\Issue; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Collector +{ + private readonly IssueFilter $issueFilter; + private int $numberOfTests = 0; + private int $numberOfTestsRun = 0; + private int $numberOfAssertions = 0; + private bool $prepared = false; + private bool $childProcessErrored = false; + + /** + * @var non-negative-int + */ + private int $numberOfIssuesIgnoredByBaseline = 0; + + /** + * @var list + */ + private array $testErroredEvents = []; + + /** + * @var list + */ + private array $testFailedEvents = []; + + /** + * @var list + */ + private array $testMarkedIncompleteEvents = []; + + /** + * @var list + */ + private array $testSuiteSkippedEvents = []; + + /** + * @var list + */ + private array $testSkippedEvents = []; + + /** + * @var array> + */ + private array $testConsideredRiskyEvents = []; + + /** + * @var array> + */ + private array $testTriggeredPhpunitDeprecationEvents = []; + + /** + * @var array> + */ + private array $testTriggeredPhpunitErrorEvents = []; + + /** + * @var array> + */ + private array $testTriggeredPhpunitNoticeEvents = []; + + /** + * @var array> + */ + private array $testTriggeredPhpunitWarningEvents = []; + + /** + * @var list + */ + private array $testRunnerTriggeredDeprecationEvents = []; + + /** + * @var list + */ + private array $testRunnerTriggeredNoticeEvents = []; + + /** + * @var list + */ + private array $testRunnerTriggeredWarningEvents = []; + + /** + * @var array + */ + private array $errors = []; + + /** + * @var array + */ + private array $deprecations = []; + + /** + * @var array + */ + private array $notices = []; + + /** + * @var array + */ + private array $warnings = []; + + /** + * @var array + */ + private array $phpDeprecations = []; + + /** + * @var array + */ + private array $phpNotices = []; + + /** + * @var array + */ + private array $phpWarnings = []; + + public function __construct(Facade $facade, IssueFilter $issueFilter) + { + $facade->registerSubscribers( + new ExecutionStartedSubscriber($this), + new TestSuiteSkippedSubscriber($this), + new TestSuiteStartedSubscriber($this), + new TestSuiteFinishedSubscriber($this), + new TestPreparedSubscriber($this), + new TestFinishedSubscriber($this), + new BeforeTestClassMethodErroredSubscriber($this), + new BeforeTestClassMethodFailedSubscriber($this), + new AfterTestClassMethodErroredSubscriber($this), + new AfterTestClassMethodFailedSubscriber($this), + new TestErroredSubscriber($this), + new TestFailedSubscriber($this), + new TestMarkedIncompleteSubscriber($this), + new TestSkippedSubscriber($this), + new TestConsideredRiskySubscriber($this), + new TestTriggeredDeprecationSubscriber($this), + new TestTriggeredErrorSubscriber($this), + new TestTriggeredNoticeSubscriber($this), + new TestTriggeredPhpDeprecationSubscriber($this), + new TestTriggeredPhpNoticeSubscriber($this), + new TestTriggeredPhpunitDeprecationSubscriber($this), + new TestTriggeredPhpunitErrorSubscriber($this), + new TestTriggeredPhpunitNoticeSubscriber($this), + new TestTriggeredPhpunitWarningSubscriber($this), + new TestTriggeredPhpWarningSubscriber($this), + new TestTriggeredWarningSubscriber($this), + new TestRunnerTriggeredDeprecationSubscriber($this), + new TestRunnerTriggeredNoticeSubscriber($this), + new TestRunnerTriggeredWarningSubscriber($this), + new ChildProcessErroredSubscriber($this), + ); + + $this->issueFilter = $issueFilter; + } + + public function result(): TestResult + { + return new TestResult( + $this->numberOfTests, + $this->numberOfTestsRun, + $this->numberOfAssertions, + $this->testErroredEvents, + $this->testFailedEvents, + $this->testConsideredRiskyEvents, + $this->testSuiteSkippedEvents, + $this->testSkippedEvents, + $this->testMarkedIncompleteEvents, + $this->testTriggeredPhpunitDeprecationEvents, + $this->testTriggeredPhpunitErrorEvents, + $this->testTriggeredPhpunitNoticeEvents, + $this->testTriggeredPhpunitWarningEvents, + $this->testRunnerTriggeredDeprecationEvents, + $this->testRunnerTriggeredNoticeEvents, + $this->testRunnerTriggeredWarningEvents, + array_values($this->errors), + array_values($this->deprecations), + array_values($this->notices), + array_values($this->warnings), + array_values($this->phpDeprecations), + array_values($this->phpNotices), + array_values($this->phpWarnings), + $this->numberOfIssuesIgnoredByBaseline, + ); + } + + public function executionStarted(ExecutionStarted $event): void + { + $this->numberOfTests = $event->testSuite()->count(); + } + + public function testSuiteSkipped(TestSuiteSkipped $event): void + { + $testSuite = $event->testSuite(); + + if (!$testSuite->isForTestClass()) { + return; + } + + $this->testSuiteSkippedEvents[] = $event; + } + + public function testSuiteStarted(TestSuiteStarted $event): void + { + $testSuite = $event->testSuite(); + + if (!$testSuite->isForTestClass()) { + return; + } + } + + public function testSuiteFinished(TestSuiteFinished $event): void + { + $testSuite = $event->testSuite(); + + if ($testSuite->isWithName()) { + return; + } + + if ($testSuite->isForTestMethodWithDataProvider()) { + assert($testSuite instanceof TestSuiteForTestMethodWithDataProvider); + assert(count($testSuite->tests()->asArray()) > 0); + + $test = $testSuite->tests()->asArray()[0]; + + assert($test instanceof TestMethod); + + foreach ($this->testFailedEvents as $testFailedEvent) { + if ($testFailedEvent->test()->isTestMethod() && $testFailedEvent->test()->methodName() === $test->methodName()) { + return; + } + } + + PassedTests::instance()->testMethodPassed($test, null); + + return; + } + + assert($testSuite instanceof TestSuiteForTestClass); + + PassedTests::instance()->testClassPassed($testSuite->className()); + } + + public function testPrepared(): void + { + $this->prepared = true; + } + + public function testFinished(Finished $event): void + { + $this->numberOfAssertions += $event->numberOfAssertionsPerformed(); + + $this->numberOfTestsRun++; + + $this->prepared = false; + $this->childProcessErrored = false; + } + + public function beforeTestClassMethodErrored(BeforeFirstTestMethodErrored $event): void + { + $this->testErroredEvents[] = $event; + + $this->numberOfTestsRun++; + } + + public function beforeTestClassMethodFailed(BeforeFirstTestMethodFailed $event): void + { + $this->testFailedEvents[] = $event; + + $this->numberOfTestsRun++; + } + + public function afterTestClassMethodErrored(AfterLastTestMethodErrored $event): void + { + $this->testErroredEvents[] = $event; + } + + public function afterTestClassMethodFailed(AfterLastTestMethodFailed $event): void + { + $this->testFailedEvents[] = $event; + } + + public function testErrored(Errored $event): void + { + $this->testErroredEvents[] = $event; + + if ($this->childProcessErrored) { + return; + } + + if (!$this->prepared) { + $this->numberOfTestsRun++; + } + } + + public function testFailed(Failed $event): void + { + $this->testFailedEvents[] = $event; + } + + public function testMarkedIncomplete(MarkedIncomplete $event): void + { + $this->testMarkedIncompleteEvents[] = $event; + } + + public function testSkipped(TestSkipped $event): void + { + $this->testSkippedEvents[] = $event; + + if (!$this->prepared) { + $this->numberOfTestsRun++; + } + } + + public function testConsideredRisky(ConsideredRisky $event): void + { + if (!isset($this->testConsideredRiskyEvents[$event->test()->id()])) { + $this->testConsideredRiskyEvents[$event->test()->id()] = []; + } + + $this->testConsideredRiskyEvents[$event->test()->id()][] = $event; + } + + public function testTriggeredDeprecation(DeprecationTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event)) { + return; + } + + if ($event->ignoredByBaseline()) { + $this->numberOfIssuesIgnoredByBaseline++; + + return; + } + + $id = $this->issueId($event); + + if (!isset($this->deprecations[$id])) { + $this->deprecations[$id] = Issue::from( + $event->file(), + $event->line(), + $event->message(), + $event->test(), + $event->stackTrace(), + ); + + return; + } + + $this->deprecations[$id]->triggeredBy($event->test()); + } + + public function testTriggeredPhpDeprecation(PhpDeprecationTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event)) { + return; + } + + if ($event->ignoredByBaseline()) { + $this->numberOfIssuesIgnoredByBaseline++; + + return; + } + + $id = $this->issueId($event); + + if (!isset($this->phpDeprecations[$id])) { + $this->phpDeprecations[$id] = Issue::from( + $event->file(), + $event->line(), + $event->message(), + $event->test(), + ); + + return; + } + + $this->phpDeprecations[$id]->triggeredBy($event->test()); + } + + public function testTriggeredPhpunitDeprecation(PhpunitDeprecationTriggered $event): void + { + if (!isset($this->testTriggeredPhpunitDeprecationEvents[$event->test()->id()])) { + $this->testTriggeredPhpunitDeprecationEvents[$event->test()->id()] = []; + } + + $this->testTriggeredPhpunitDeprecationEvents[$event->test()->id()][] = $event; + } + + public function testTriggeredPhpunitNotice(PhpunitNoticeTriggered $event): void + { + if (!isset($this->testTriggeredPhpunitNoticeEvents[$event->test()->id()])) { + $this->testTriggeredPhpunitNoticeEvents[$event->test()->id()] = []; + } + + $this->testTriggeredPhpunitNoticeEvents[$event->test()->id()][] = $event; + } + + public function testTriggeredError(ErrorTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event)) { + return; + } + + $id = $this->issueId($event); + + if (!isset($this->errors[$id])) { + $this->errors[$id] = Issue::from( + $event->file(), + $event->line(), + $event->message(), + $event->test(), + ); + + return; + } + + $this->errors[$id]->triggeredBy($event->test()); + } + + public function testTriggeredNotice(NoticeTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event)) { + return; + } + + if ($event->ignoredByBaseline()) { + $this->numberOfIssuesIgnoredByBaseline++; + + return; + } + + $id = $this->issueId($event); + + if (!isset($this->notices[$id])) { + $this->notices[$id] = Issue::from( + $event->file(), + $event->line(), + $event->message(), + $event->test(), + ); + + return; + } + + $this->notices[$id]->triggeredBy($event->test()); + } + + public function testTriggeredPhpNotice(PhpNoticeTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event)) { + return; + } + + if ($event->ignoredByBaseline()) { + $this->numberOfIssuesIgnoredByBaseline++; + + return; + } + + $id = $this->issueId($event); + + if (!isset($this->phpNotices[$id])) { + $this->phpNotices[$id] = Issue::from( + $event->file(), + $event->line(), + $event->message(), + $event->test(), + ); + + return; + } + + $this->phpNotices[$id]->triggeredBy($event->test()); + } + + public function testTriggeredWarning(WarningTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event)) { + return; + } + + if ($event->ignoredByBaseline()) { + $this->numberOfIssuesIgnoredByBaseline++; + + return; + } + + $id = $this->issueId($event); + + if (!isset($this->warnings[$id])) { + $this->warnings[$id] = Issue::from( + $event->file(), + $event->line(), + $event->message(), + $event->test(), + ); + + return; + } + + $this->warnings[$id]->triggeredBy($event->test()); + } + + public function testTriggeredPhpWarning(PhpWarningTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event)) { + return; + } + + if ($event->ignoredByBaseline()) { + $this->numberOfIssuesIgnoredByBaseline++; + + return; + } + + $id = $this->issueId($event); + + if (!isset($this->phpWarnings[$id])) { + $this->phpWarnings[$id] = Issue::from( + $event->file(), + $event->line(), + $event->message(), + $event->test(), + ); + + return; + } + + $this->phpWarnings[$id]->triggeredBy($event->test()); + } + + public function testTriggeredPhpunitError(PhpunitErrorTriggered $event): void + { + if (!isset($this->testTriggeredPhpunitErrorEvents[$event->test()->id()])) { + $this->testTriggeredPhpunitErrorEvents[$event->test()->id()] = []; + } + + $this->testTriggeredPhpunitErrorEvents[$event->test()->id()][] = $event; + } + + public function testTriggeredPhpunitWarning(PhpunitWarningTriggered $event): void + { + if ($event->ignoredByTest()) { + return; + } + + if (!isset($this->testTriggeredPhpunitWarningEvents[$event->test()->id()])) { + $this->testTriggeredPhpunitWarningEvents[$event->test()->id()] = []; + } + + $this->testTriggeredPhpunitWarningEvents[$event->test()->id()][] = $event; + } + + public function testRunnerTriggeredDeprecation(TestRunnerDeprecationTriggered $event): void + { + $this->testRunnerTriggeredDeprecationEvents[] = $event; + } + + public function testRunnerTriggeredNotice(TestRunnerNoticeTriggered $event): void + { + $this->testRunnerTriggeredNoticeEvents[] = $event; + } + + public function testRunnerTriggeredWarning(TestRunnerWarningTriggered $event): void + { + $this->testRunnerTriggeredWarningEvents[] = $event; + } + + public function childProcessErrored(ChildProcessErrored $event): void + { + $this->childProcessErrored = true; + } + + public function hasErroredTests(): bool + { + return $this->testErroredEvents !== []; + } + + public function hasFailedTests(): bool + { + return $this->testFailedEvents !== []; + } + + public function hasRiskyTests(): bool + { + return $this->testConsideredRiskyEvents !== []; + } + + public function hasSkippedTests(): bool + { + return $this->testSkippedEvents !== []; + } + + public function hasIncompleteTests(): bool + { + return $this->testMarkedIncompleteEvents !== []; + } + + public function hasDeprecations(): bool + { + return $this->deprecations !== [] || + $this->phpDeprecations !== [] || + $this->testTriggeredPhpunitDeprecationEvents !== [] || + $this->testRunnerTriggeredDeprecationEvents !== []; + } + + public function hasNotices(): bool + { + return $this->notices !== [] || + $this->phpNotices !== []; + } + + public function hasWarnings(): bool + { + return $this->warnings !== [] || + $this->phpWarnings !== [] || + $this->testTriggeredPhpunitWarningEvents !== [] || + $this->testRunnerTriggeredWarningEvents !== []; + } + + /** + * @return non-empty-string + */ + private function issueId(DeprecationTriggered|ErrorTriggered|NoticeTriggered|PhpDeprecationTriggered|PhpNoticeTriggered|PhpWarningTriggered|WarningTriggered $event): string + { + return implode(':', [$event->file(), $event->line(), $event->message()]); + } +} diff --git a/src/Runner/TestResult/Facade.php b/src/Runner/TestResult/Facade.php new file mode 100644 index 00000000000..c95ff53b0fb --- /dev/null +++ b/src/Runner/TestResult/Facade.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use function array_any; +use function str_contains; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Runner\DeprecationCollector\Facade as DeprecationCollectorFacade; +use PHPUnit\TestRunner\IssueFilter; +use PHPUnit\TextUI\Configuration\Configuration; +use PHPUnit\TextUI\Configuration\Registry as ConfigurationRegistry; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Facade +{ + private static ?Collector $collector = null; + + public static function init(): void + { + self::collector(); + } + + public static function result(): TestResult + { + return self::collector()->result(); + } + + public static function shouldStop(): bool + { + $configuration = ConfigurationRegistry::get(); + $collector = self::collector(); + + if (($configuration->stopOnDefect() || $configuration->stopOnError()) && $collector->hasErroredTests()) { + return true; + } + + if (($configuration->stopOnDefect() || $configuration->stopOnFailure()) && $collector->hasFailedTests()) { + return true; + } + + if (($configuration->stopOnDefect() || $configuration->stopOnWarning()) && $collector->hasWarnings()) { + return true; + } + + if (($configuration->stopOnDefect() || $configuration->stopOnRisky()) && $collector->hasRiskyTests()) { + return true; + } + + if (self::stopOnDeprecation($configuration)) { + return true; + } + + if ($configuration->stopOnNotice() && $collector->hasNotices()) { + return true; + } + + if ($configuration->stopOnIncomplete() && $collector->hasIncompleteTests()) { + return true; + } + + if ($configuration->stopOnSkipped() && $collector->hasSkippedTests()) { + return true; + } + + return false; + } + + private static function collector(): Collector + { + if (self::$collector === null) { + $configuration = ConfigurationRegistry::get(); + + self::$collector = new Collector( + EventFacade::instance(), + new IssueFilter($configuration->source()), + ); + } + + return self::$collector; + } + + private static function stopOnDeprecation(Configuration $configuration): bool + { + if (!$configuration->stopOnDeprecation()) { + return false; + } + + $deprecations = DeprecationCollectorFacade::filteredDeprecations(); + + if (!$configuration->hasSpecificDeprecationToStopOn()) { + return $deprecations !== []; + } + + return array_any( + $deprecations, + static fn (string $deprecation) => str_contains( + $deprecation, + $configuration->specificDeprecationToStopOn(), + ), + ); + } +} diff --git a/src/Runner/TestResult/Issue.php b/src/Runner/TestResult/Issue.php new file mode 100644 index 00000000000..12ade5c3f5d --- /dev/null +++ b/src/Runner/TestResult/Issue.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult\Issues; + +use function array_keys; +use function count; +use PHPUnit\Event\Code\Test; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Issue +{ + /** + * @var non-empty-string + */ + private readonly string $file; + + /** + * @var positive-int + */ + private readonly int $line; + + /** + * @var non-empty-string + */ + private readonly string $description; + + /** + * @var non-empty-array + */ + private array $triggeringTests; + + /** + * @var ?non-empty-string + */ + private ?string $stackTrace; + + /** + * @param non-empty-string $file + * @param positive-int $line + * @param non-empty-string $description + */ + public static function from(string $file, int $line, string $description, Test $triggeringTest, ?string $stackTrace = null): self + { + return new self($file, $line, $description, $triggeringTest, $stackTrace); + } + + /** + * @param non-empty-string $file + * @param positive-int $line + * @param non-empty-string $description + */ + private function __construct(string $file, int $line, string $description, Test $triggeringTest, ?string $stackTrace) + { + $this->file = $file; + $this->line = $line; + $this->description = $description; + $this->stackTrace = $stackTrace; + + $this->triggeringTests = [ + $triggeringTest->id() => [ + 'test' => $triggeringTest, + 'count' => 1, + ], + ]; + } + + public function triggeredBy(Test $test): void + { + if (isset($this->triggeringTests[$test->id()])) { + $this->triggeringTests[$test->id()]['count']++; + + return; + } + + $this->triggeringTests[$test->id()] = [ + 'test' => $test, + 'count' => 1, + ]; + } + + /** + * @return non-empty-string + */ + public function file(): string + { + return $this->file; + } + + /** + * @return positive-int + */ + public function line(): int + { + return $this->line; + } + + /** + * @return non-empty-string + */ + public function description(): string + { + return $this->description; + } + + /** + * @return non-empty-array + */ + public function triggeringTests(): array + { + return $this->triggeringTests; + } + + /** + * @phpstan-assert-if-true !null $this->stackTrace + */ + public function hasStackTrace(): bool + { + return $this->stackTrace !== null; + } + + /** + * @return ?non-empty-string + */ + public function stackTrace(): ?string + { + return $this->stackTrace; + } + + public function triggeredInTest(): bool + { + return count($this->triggeringTests) === 1 && + $this->file === $this->triggeringTests[array_keys($this->triggeringTests)[0]]['test']->file(); + } +} diff --git a/src/Runner/TestResult/PassedTests.php b/src/Runner/TestResult/PassedTests.php new file mode 100644 index 00000000000..2f611461d6f --- /dev/null +++ b/src/Runner/TestResult/PassedTests.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use function array_merge; +use function assert; +use function explode; +use function in_array; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Framework\TestSize\Known; +use PHPUnit\Framework\TestSize\TestSize; +use PHPUnit\Metadata\Api\Groups; +use ReflectionMethod; +use ReflectionNamedType; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class PassedTests +{ + private static ?self $instance = null; + + /** + * @var list + */ + private array $passedTestClasses = []; + + /** + * @var array + */ + private array $passedTestMethods = []; + + public static function instance(): self + { + if (self::$instance !== null) { + return self::$instance; + } + + self::$instance = new self; + + return self::$instance; + } + + /** + * @param class-string $className + */ + public function testClassPassed(string $className): void + { + $this->passedTestClasses[] = $className; + } + + public function testMethodPassed(TestMethod $test, mixed $returnValue): void + { + $size = (new Groups)->size( + $test->className(), + $test->methodName(), + ); + + $this->passedTestMethods[$test->className() . '::' . $test->methodName()] = [ + 'returnValue' => $returnValue, + 'size' => $size, + ]; + } + + public function import(self $other): void + { + $this->passedTestClasses = array_merge( + $this->passedTestClasses, + $other->passedTestClasses, + ); + + $this->passedTestMethods = array_merge( + $this->passedTestMethods, + $other->passedTestMethods, + ); + } + + /** + * @param class-string $className + */ + public function hasTestClassPassed(string $className): bool + { + return in_array($className, $this->passedTestClasses, true); + } + + public function hasTestMethodPassed(string $method): bool + { + return isset($this->passedTestMethods[$method]); + } + + public function isGreaterThan(string $method, TestSize $other): bool + { + if ($other->isUnknown()) { + return false; + } + + assert($other instanceof Known); + + $size = $this->passedTestMethods[$method]['size']; + + if ($size->isUnknown()) { + return false; + } + + assert($size instanceof Known); + + return $size->isGreaterThan($other); + } + + public function hasReturnValue(string $method): bool + { + $returnType = new ReflectionMethod(...explode('::', $method))->getReturnType(); + + return !$returnType instanceof ReflectionNamedType || !in_array($returnType->getName(), ['never', 'void'], true); + } + + public function returnValue(string $method): mixed + { + if (isset($this->passedTestMethods[$method])) { + return $this->passedTestMethods[$method]['returnValue']; + } + + return null; + } +} diff --git a/src/Runner/TestResult/Subscriber/AfterTestClassMethodErroredSubscriber.php b/src/Runner/TestResult/Subscriber/AfterTestClassMethodErroredSubscriber.php new file mode 100644 index 00000000000..eb94433bdeb --- /dev/null +++ b/src/Runner/TestResult/Subscriber/AfterTestClassMethodErroredSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\AfterLastTestMethodErrored; +use PHPUnit\Event\Test\AfterLastTestMethodErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterTestClassMethodErroredSubscriber extends Subscriber implements AfterLastTestMethodErroredSubscriber +{ + public function notify(AfterLastTestMethodErrored $event): void + { + $this->collector()->afterTestClassMethodErrored($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/AfterTestClassMethodFailedSubscriber.php b/src/Runner/TestResult/Subscriber/AfterTestClassMethodFailedSubscriber.php new file mode 100644 index 00000000000..e207ba1d079 --- /dev/null +++ b/src/Runner/TestResult/Subscriber/AfterTestClassMethodFailedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\AfterLastTestMethodFailed; +use PHPUnit\Event\Test\AfterLastTestMethodFailedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterTestClassMethodFailedSubscriber extends Subscriber implements AfterLastTestMethodFailedSubscriber +{ + public function notify(AfterLastTestMethodFailed $event): void + { + $this->collector()->afterTestClassMethodFailed($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/BeforeTestClassMethodErroredSubscriber.php b/src/Runner/TestResult/Subscriber/BeforeTestClassMethodErroredSubscriber.php new file mode 100644 index 00000000000..1929125e573 --- /dev/null +++ b/src/Runner/TestResult/Subscriber/BeforeTestClassMethodErroredSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; +use PHPUnit\Event\Test\BeforeFirstTestMethodErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeTestClassMethodErroredSubscriber extends Subscriber implements BeforeFirstTestMethodErroredSubscriber +{ + public function notify(BeforeFirstTestMethodErrored $event): void + { + $this->collector()->beforeTestClassMethodErrored($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/BeforeTestClassMethodFailedSubscriber.php b/src/Runner/TestResult/Subscriber/BeforeTestClassMethodFailedSubscriber.php new file mode 100644 index 00000000000..0e69855a0ae --- /dev/null +++ b/src/Runner/TestResult/Subscriber/BeforeTestClassMethodFailedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\BeforeFirstTestMethodFailed; +use PHPUnit\Event\Test\BeforeFirstTestMethodFailedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeTestClassMethodFailedSubscriber extends Subscriber implements BeforeFirstTestMethodFailedSubscriber +{ + public function notify(BeforeFirstTestMethodFailed $event): void + { + $this->collector()->beforeTestClassMethodFailed($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/ChildProcessErroredSubscriber.php b/src/Runner/TestResult/Subscriber/ChildProcessErroredSubscriber.php new file mode 100644 index 00000000000..3d70d88ede8 --- /dev/null +++ b/src/Runner/TestResult/Subscriber/ChildProcessErroredSubscriber.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\TestRunner\ChildProcessErrored; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ChildProcessErroredSubscriber extends Subscriber implements \PHPUnit\Event\TestRunner\ChildProcessErroredSubscriber +{ + public function notify(ChildProcessErrored $event): void + { + $this->collector()->childProcessErrored($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/ExecutionStartedSubscriber.php b/src/Runner/TestResult/Subscriber/ExecutionStartedSubscriber.php new file mode 100644 index 00000000000..b54ae9e724a --- /dev/null +++ b/src/Runner/TestResult/Subscriber/ExecutionStartedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\TestRunner\ExecutionStarted; +use PHPUnit\Event\TestRunner\ExecutionStartedSubscriber as TestRunnerExecutionStartedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ExecutionStartedSubscriber extends Subscriber implements TestRunnerExecutionStartedSubscriber +{ + public function notify(ExecutionStarted $event): void + { + $this->collector()->executionStarted($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/Subscriber.php b/src/Runner/TestResult/Subscriber/Subscriber.php new file mode 100644 index 00000000000..36be4941b31 --- /dev/null +++ b/src/Runner/TestResult/Subscriber/Subscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Subscriber +{ + private Collector $collector; + + public function __construct(Collector $collector) + { + $this->collector = $collector; + } + + protected function collector(): Collector + { + return $this->collector; + } +} diff --git a/src/Runner/TestResult/Subscriber/TestConsideredRiskySubscriber.php b/src/Runner/TestResult/Subscriber/TestConsideredRiskySubscriber.php new file mode 100644 index 00000000000..8584fddcc27 --- /dev/null +++ b/src/Runner/TestResult/Subscriber/TestConsideredRiskySubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\ConsideredRisky; +use PHPUnit\Event\Test\ConsideredRiskySubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestConsideredRiskySubscriber extends Subscriber implements ConsideredRiskySubscriber +{ + public function notify(ConsideredRisky $event): void + { + $this->collector()->testConsideredRisky($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/TestErroredSubscriber.php b/src/Runner/TestResult/Subscriber/TestErroredSubscriber.php new file mode 100644 index 00000000000..a97c21a689e --- /dev/null +++ b/src/Runner/TestResult/Subscriber/TestErroredSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\ErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestErroredSubscriber extends Subscriber implements ErroredSubscriber +{ + public function notify(Errored $event): void + { + $this->collector()->testErrored($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/TestFailedSubscriber.php b/src/Runner/TestResult/Subscriber/TestFailedSubscriber.php new file mode 100644 index 00000000000..118b304cdc6 --- /dev/null +++ b/src/Runner/TestResult/Subscriber/TestFailedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\FailedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFailedSubscriber extends Subscriber implements FailedSubscriber +{ + public function notify(Failed $event): void + { + $this->collector()->testFailed($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/TestFinishedSubscriber.php b/src/Runner/TestResult/Subscriber/TestFinishedSubscriber.php new file mode 100644 index 00000000000..37fe67d0f47 --- /dev/null +++ b/src/Runner/TestResult/Subscriber/TestFinishedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + public function notify(Finished $event): void + { + $this->collector()->testFinished($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/TestMarkedIncompleteSubscriber.php b/src/Runner/TestResult/Subscriber/TestMarkedIncompleteSubscriber.php new file mode 100644 index 00000000000..c9d13ab5620 --- /dev/null +++ b/src/Runner/TestResult/Subscriber/TestMarkedIncompleteSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\MarkedIncompleteSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestMarkedIncompleteSubscriber extends Subscriber implements MarkedIncompleteSubscriber +{ + public function notify(MarkedIncomplete $event): void + { + $this->collector()->testMarkedIncomplete($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/TestPreparedSubscriber.php b/src/Runner/TestResult/Subscriber/TestPreparedSubscriber.php new file mode 100644 index 00000000000..6dd05ca75ec --- /dev/null +++ b/src/Runner/TestResult/Subscriber/TestPreparedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\PreparedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPreparedSubscriber extends Subscriber implements PreparedSubscriber +{ + public function notify(Prepared $event): void + { + $this->collector()->testPrepared(); + } +} diff --git a/src/Runner/TestResult/Subscriber/TestRunnerTriggeredDeprecationSubscriber.php b/src/Runner/TestResult/Subscriber/TestRunnerTriggeredDeprecationSubscriber.php new file mode 100644 index 00000000000..36b3ea03917 --- /dev/null +++ b/src/Runner/TestResult/Subscriber/TestRunnerTriggeredDeprecationSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\TestRunner\DeprecationTriggered; +use PHPUnit\Event\TestRunner\DeprecationTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestRunnerTriggeredDeprecationSubscriber extends Subscriber implements DeprecationTriggeredSubscriber +{ + public function notify(DeprecationTriggered $event): void + { + $this->collector()->testRunnerTriggeredDeprecation($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/TestRunnerTriggeredNoticeSubscriber.php b/src/Runner/TestResult/Subscriber/TestRunnerTriggeredNoticeSubscriber.php new file mode 100644 index 00000000000..8fd9bc4b86e --- /dev/null +++ b/src/Runner/TestResult/Subscriber/TestRunnerTriggeredNoticeSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\TestRunner\NoticeTriggered; +use PHPUnit\Event\TestRunner\NoticeTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestRunnerTriggeredNoticeSubscriber extends Subscriber implements NoticeTriggeredSubscriber +{ + public function notify(NoticeTriggered $event): void + { + $this->collector()->testRunnerTriggeredNotice($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/TestRunnerTriggeredWarningSubscriber.php b/src/Runner/TestResult/Subscriber/TestRunnerTriggeredWarningSubscriber.php new file mode 100644 index 00000000000..cc01d5db8c1 --- /dev/null +++ b/src/Runner/TestResult/Subscriber/TestRunnerTriggeredWarningSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\TestRunner\WarningTriggered; +use PHPUnit\Event\TestRunner\WarningTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestRunnerTriggeredWarningSubscriber extends Subscriber implements WarningTriggeredSubscriber +{ + public function notify(WarningTriggered $event): void + { + $this->collector()->testRunnerTriggeredWarning($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/TestSkippedSubscriber.php b/src/Runner/TestResult/Subscriber/TestSkippedSubscriber.php new file mode 100644 index 00000000000..152db85bf77 --- /dev/null +++ b/src/Runner/TestResult/Subscriber/TestSkippedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\Skipped; +use PHPUnit\Event\Test\SkippedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSkippedSubscriber extends Subscriber implements SkippedSubscriber +{ + public function notify(Skipped $event): void + { + $this->collector()->testSkipped($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/TestSuiteFinishedSubscriber.php b/src/Runner/TestResult/Subscriber/TestSuiteFinishedSubscriber.php new file mode 100644 index 00000000000..e5f2acac512 --- /dev/null +++ b/src/Runner/TestResult/Subscriber/TestSuiteFinishedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\TestSuite\Finished; +use PHPUnit\Event\TestSuite\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + public function notify(Finished $event): void + { + $this->collector()->testSuiteFinished($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/TestSuiteSkippedSubscriber.php b/src/Runner/TestResult/Subscriber/TestSuiteSkippedSubscriber.php new file mode 100644 index 00000000000..0c7cd7abb8d --- /dev/null +++ b/src/Runner/TestResult/Subscriber/TestSuiteSkippedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\TestSuite\Skipped; +use PHPUnit\Event\TestSuite\SkippedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteSkippedSubscriber extends Subscriber implements SkippedSubscriber +{ + public function notify(Skipped $event): void + { + $this->collector()->testSuiteSkipped($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/TestSuiteStartedSubscriber.php b/src/Runner/TestResult/Subscriber/TestSuiteStartedSubscriber.php new file mode 100644 index 00000000000..d3cb3bffdd0 --- /dev/null +++ b/src/Runner/TestResult/Subscriber/TestSuiteStartedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\TestSuite\Started; +use PHPUnit\Event\TestSuite\StartedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteStartedSubscriber extends Subscriber implements StartedSubscriber +{ + public function notify(Started $event): void + { + $this->collector()->testSuiteStarted($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/TestTriggeredDeprecationSubscriber.php b/src/Runner/TestResult/Subscriber/TestTriggeredDeprecationSubscriber.php new file mode 100644 index 00000000000..81e93eb3472 --- /dev/null +++ b/src/Runner/TestResult/Subscriber/TestTriggeredDeprecationSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\Test\DeprecationTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredDeprecationSubscriber extends Subscriber implements DeprecationTriggeredSubscriber +{ + public function notify(DeprecationTriggered $event): void + { + $this->collector()->testTriggeredDeprecation($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/TestTriggeredErrorSubscriber.php b/src/Runner/TestResult/Subscriber/TestTriggeredErrorSubscriber.php new file mode 100644 index 00000000000..0aef461dbcb --- /dev/null +++ b/src/Runner/TestResult/Subscriber/TestTriggeredErrorSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\ErrorTriggered; +use PHPUnit\Event\Test\ErrorTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredErrorSubscriber extends Subscriber implements ErrorTriggeredSubscriber +{ + public function notify(ErrorTriggered $event): void + { + $this->collector()->testTriggeredError($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/TestTriggeredNoticeSubscriber.php b/src/Runner/TestResult/Subscriber/TestTriggeredNoticeSubscriber.php new file mode 100644 index 00000000000..67b73c06a01 --- /dev/null +++ b/src/Runner/TestResult/Subscriber/TestTriggeredNoticeSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\NoticeTriggered; +use PHPUnit\Event\Test\NoticeTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredNoticeSubscriber extends Subscriber implements NoticeTriggeredSubscriber +{ + public function notify(NoticeTriggered $event): void + { + $this->collector()->testTriggeredNotice($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/TestTriggeredPhpDeprecationSubscriber.php b/src/Runner/TestResult/Subscriber/TestTriggeredPhpDeprecationSubscriber.php new file mode 100644 index 00000000000..5cd17e3f981 --- /dev/null +++ b/src/Runner/TestResult/Subscriber/TestTriggeredPhpDeprecationSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\PhpDeprecationTriggered; +use PHPUnit\Event\Test\PhpDeprecationTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpDeprecationSubscriber extends Subscriber implements PhpDeprecationTriggeredSubscriber +{ + public function notify(PhpDeprecationTriggered $event): void + { + $this->collector()->testTriggeredPhpDeprecation($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/TestTriggeredPhpNoticeSubscriber.php b/src/Runner/TestResult/Subscriber/TestTriggeredPhpNoticeSubscriber.php new file mode 100644 index 00000000000..9af0d3206fb --- /dev/null +++ b/src/Runner/TestResult/Subscriber/TestTriggeredPhpNoticeSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\PhpNoticeTriggered; +use PHPUnit\Event\Test\PhpNoticeTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpNoticeSubscriber extends Subscriber implements PhpNoticeTriggeredSubscriber +{ + public function notify(PhpNoticeTriggered $event): void + { + $this->collector()->testTriggeredPhpNotice($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/TestTriggeredPhpWarningSubscriber.php b/src/Runner/TestResult/Subscriber/TestTriggeredPhpWarningSubscriber.php new file mode 100644 index 00000000000..18eaf4f17a6 --- /dev/null +++ b/src/Runner/TestResult/Subscriber/TestTriggeredPhpWarningSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\PhpWarningTriggered; +use PHPUnit\Event\Test\PhpWarningTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpWarningSubscriber extends Subscriber implements PhpWarningTriggeredSubscriber +{ + public function notify(PhpWarningTriggered $event): void + { + $this->collector()->testTriggeredPhpWarning($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/TestTriggeredPhpunitDeprecationSubscriber.php b/src/Runner/TestResult/Subscriber/TestTriggeredPhpunitDeprecationSubscriber.php new file mode 100644 index 00000000000..3475f11aae1 --- /dev/null +++ b/src/Runner/TestResult/Subscriber/TestTriggeredPhpunitDeprecationSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\PhpunitDeprecationTriggered; +use PHPUnit\Event\Test\PhpunitDeprecationTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpunitDeprecationSubscriber extends Subscriber implements PhpunitDeprecationTriggeredSubscriber +{ + public function notify(PhpunitDeprecationTriggered $event): void + { + $this->collector()->testTriggeredPhpunitDeprecation($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/TestTriggeredPhpunitErrorSubscriber.php b/src/Runner/TestResult/Subscriber/TestTriggeredPhpunitErrorSubscriber.php new file mode 100644 index 00000000000..0ceba9caa29 --- /dev/null +++ b/src/Runner/TestResult/Subscriber/TestTriggeredPhpunitErrorSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\PhpunitErrorTriggered; +use PHPUnit\Event\Test\PhpunitErrorTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpunitErrorSubscriber extends Subscriber implements PhpunitErrorTriggeredSubscriber +{ + public function notify(PhpunitErrorTriggered $event): void + { + $this->collector()->testTriggeredPhpunitError($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/TestTriggeredPhpunitNoticeSubscriber.php b/src/Runner/TestResult/Subscriber/TestTriggeredPhpunitNoticeSubscriber.php new file mode 100644 index 00000000000..f7ebfd1463e --- /dev/null +++ b/src/Runner/TestResult/Subscriber/TestTriggeredPhpunitNoticeSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\PhpunitNoticeTriggered; +use PHPUnit\Event\Test\PhpunitNoticeTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpunitNoticeSubscriber extends Subscriber implements PhpunitNoticeTriggeredSubscriber +{ + public function notify(PhpunitNoticeTriggered $event): void + { + $this->collector()->testTriggeredPhpunitNotice($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/TestTriggeredPhpunitWarningSubscriber.php b/src/Runner/TestResult/Subscriber/TestTriggeredPhpunitWarningSubscriber.php new file mode 100644 index 00000000000..376c4b60e80 --- /dev/null +++ b/src/Runner/TestResult/Subscriber/TestTriggeredPhpunitWarningSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\PhpunitWarningTriggered; +use PHPUnit\Event\Test\PhpunitWarningTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpunitWarningSubscriber extends Subscriber implements PhpunitWarningTriggeredSubscriber +{ + public function notify(PhpunitWarningTriggered $event): void + { + $this->collector()->testTriggeredPhpunitWarning($event); + } +} diff --git a/src/Runner/TestResult/Subscriber/TestTriggeredWarningSubscriber.php b/src/Runner/TestResult/Subscriber/TestTriggeredWarningSubscriber.php new file mode 100644 index 00000000000..d5fe3ed5cd5 --- /dev/null +++ b/src/Runner/TestResult/Subscriber/TestTriggeredWarningSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\WarningTriggered; +use PHPUnit\Event\Test\WarningTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredWarningSubscriber extends Subscriber implements WarningTriggeredSubscriber +{ + public function notify(WarningTriggered $event): void + { + $this->collector()->testTriggeredWarning($event); + } +} diff --git a/src/Runner/TestResult/TestResult.php b/src/Runner/TestResult/TestResult.php new file mode 100644 index 00000000000..f491718f787 --- /dev/null +++ b/src/Runner/TestResult/TestResult.php @@ -0,0 +1,647 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use function count; +use PHPUnit\Event\Test\AfterLastTestMethodErrored; +use PHPUnit\Event\Test\AfterLastTestMethodFailed; +use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; +use PHPUnit\Event\Test\BeforeFirstTestMethodFailed; +use PHPUnit\Event\Test\ConsideredRisky; +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\PhpunitDeprecationTriggered; +use PHPUnit\Event\Test\PhpunitErrorTriggered; +use PHPUnit\Event\Test\PhpunitNoticeTriggered; +use PHPUnit\Event\Test\PhpunitWarningTriggered; +use PHPUnit\Event\Test\Skipped as TestSkipped; +use PHPUnit\Event\TestRunner\DeprecationTriggered as TestRunnerDeprecationTriggered; +use PHPUnit\Event\TestRunner\NoticeTriggered as TestRunnerNoticeTriggered; +use PHPUnit\Event\TestRunner\WarningTriggered as TestRunnerWarningTriggered; +use PHPUnit\Event\TestSuite\Skipped as TestSuiteSkipped; +use PHPUnit\TestRunner\TestResult\Issues\Issue; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestResult +{ + private int $numberOfTests; + private int $numberOfTestsRun; + private int $numberOfAssertions; + + /** + * @var list + */ + private array $testErroredEvents; + + /** + * @var list + */ + private array $testFailedEvents; + + /** + * @var list + */ + private array $testMarkedIncompleteEvents; + + /** + * @var list + */ + private array $testSuiteSkippedEvents; + + /** + * @var list + */ + private array $testSkippedEvents; + + /** + * @var array> + */ + private array $testConsideredRiskyEvents; + + /** + * @var array> + */ + private array $testTriggeredPhpunitDeprecationEvents; + + /** + * @var array> + */ + private array $testTriggeredPhpunitErrorEvents; + + /** + * @var array> + */ + private array $testTriggeredPhpunitNoticeEvents; + + /** + * @var array> + */ + private array $testTriggeredPhpunitWarningEvents; + + /** + * @var list + */ + private array $testRunnerTriggeredDeprecationEvents; + + /** + * @var list + */ + private array $testRunnerTriggeredNoticeEvents; + + /** + * @var list + */ + private array $testRunnerTriggeredWarningEvents; + + /** + * @var list + */ + private array $errors; + + /** + * @var list + */ + private array $deprecations; + + /** + * @var list + */ + private array $notices; + + /** + * @var list + */ + private array $warnings; + + /** + * @var list + */ + private array $phpDeprecations; + + /** + * @var list + */ + private array $phpNotices; + + /** + * @var list + */ + private array $phpWarnings; + + /** + * @var non-negative-int + */ + private int $numberOfIssuesIgnoredByBaseline; + + /** + * @param list $testErroredEvents + * @param list $testFailedEvents + * @param array> $testConsideredRiskyEvents + * @param list $testSuiteSkippedEvents + * @param list $testSkippedEvents + * @param list $testMarkedIncompleteEvents + * @param array> $testTriggeredPhpunitDeprecationEvents + * @param array> $testTriggeredPhpunitErrorEvents + * @param array> $testTriggeredPhpunitNoticeEvents + * @param array> $testTriggeredPhpunitWarningEvents + * @param list $testRunnerTriggeredDeprecationEvents + * @param list $testRunnerTriggeredNoticeEvents + * @param list $testRunnerTriggeredWarningEvents + * @param list $errors + * @param list $deprecations + * @param list $notices + * @param list $warnings + * @param list $phpDeprecations + * @param list $phpNotices + * @param list $phpWarnings + * @param non-negative-int $numberOfIssuesIgnoredByBaseline + */ + public function __construct(int $numberOfTests, int $numberOfTestsRun, int $numberOfAssertions, array $testErroredEvents, array $testFailedEvents, array $testConsideredRiskyEvents, array $testSuiteSkippedEvents, array $testSkippedEvents, array $testMarkedIncompleteEvents, array $testTriggeredPhpunitDeprecationEvents, array $testTriggeredPhpunitErrorEvents, array $testTriggeredPhpunitNoticeEvents, array $testTriggeredPhpunitWarningEvents, array $testRunnerTriggeredDeprecationEvents, array $testRunnerTriggeredNoticeEvents, array $testRunnerTriggeredWarningEvents, array $errors, array $deprecations, array $notices, array $warnings, array $phpDeprecations, array $phpNotices, array $phpWarnings, int $numberOfIssuesIgnoredByBaseline) + { + $this->numberOfTests = $numberOfTests; + $this->numberOfTestsRun = $numberOfTestsRun; + $this->numberOfAssertions = $numberOfAssertions; + $this->testErroredEvents = $testErroredEvents; + $this->testFailedEvents = $testFailedEvents; + $this->testConsideredRiskyEvents = $testConsideredRiskyEvents; + $this->testSuiteSkippedEvents = $testSuiteSkippedEvents; + $this->testSkippedEvents = $testSkippedEvents; + $this->testMarkedIncompleteEvents = $testMarkedIncompleteEvents; + $this->testTriggeredPhpunitDeprecationEvents = $testTriggeredPhpunitDeprecationEvents; + $this->testTriggeredPhpunitErrorEvents = $testTriggeredPhpunitErrorEvents; + $this->testTriggeredPhpunitNoticeEvents = $testTriggeredPhpunitNoticeEvents; + $this->testTriggeredPhpunitWarningEvents = $testTriggeredPhpunitWarningEvents; + $this->testRunnerTriggeredDeprecationEvents = $testRunnerTriggeredDeprecationEvents; + $this->testRunnerTriggeredNoticeEvents = $testRunnerTriggeredNoticeEvents; + $this->testRunnerTriggeredWarningEvents = $testRunnerTriggeredWarningEvents; + $this->errors = $errors; + $this->deprecations = $deprecations; + $this->notices = $notices; + $this->warnings = $warnings; + $this->phpDeprecations = $phpDeprecations; + $this->phpNotices = $phpNotices; + $this->phpWarnings = $phpWarnings; + $this->numberOfIssuesIgnoredByBaseline = $numberOfIssuesIgnoredByBaseline; + } + + public function numberOfTestsRun(): int + { + return $this->numberOfTestsRun; + } + + public function numberOfAssertions(): int + { + return $this->numberOfAssertions; + } + + /** + * @return list + */ + public function testErroredEvents(): array + { + return $this->testErroredEvents; + } + + public function numberOfTestErroredEvents(): int + { + return count($this->testErroredEvents); + } + + public function hasTestErroredEvents(): bool + { + return $this->numberOfTestErroredEvents() > 0; + } + + /** + * @return list + */ + public function testFailedEvents(): array + { + return $this->testFailedEvents; + } + + public function numberOfTestFailedEvents(): int + { + return count($this->testFailedEvents); + } + + public function hasTestFailedEvents(): bool + { + return $this->numberOfTestFailedEvents() > 0; + } + + /** + * @return array> + */ + public function testConsideredRiskyEvents(): array + { + return $this->testConsideredRiskyEvents; + } + + public function numberOfTestsWithTestConsideredRiskyEvents(): int + { + return count($this->testConsideredRiskyEvents); + } + + public function hasTestConsideredRiskyEvents(): bool + { + return $this->numberOfTestsWithTestConsideredRiskyEvents() > 0; + } + + /** + * @return list + */ + public function testSuiteSkippedEvents(): array + { + return $this->testSuiteSkippedEvents; + } + + public function numberOfTestSuiteSkippedEvents(): int + { + return count($this->testSuiteSkippedEvents); + } + + public function hasTestSuiteSkippedEvents(): bool + { + return $this->numberOfTestSuiteSkippedEvents() > 0; + } + + /** + * @return list + */ + public function testSkippedEvents(): array + { + return $this->testSkippedEvents; + } + + public function numberOfTestSkippedEvents(): int + { + return count($this->testSkippedEvents); + } + + public function hasTestSkippedEvents(): bool + { + return $this->numberOfTestSkippedEvents() > 0; + } + + /** + * @return list + */ + public function testMarkedIncompleteEvents(): array + { + return $this->testMarkedIncompleteEvents; + } + + public function numberOfTestMarkedIncompleteEvents(): int + { + return count($this->testMarkedIncompleteEvents); + } + + public function hasTestMarkedIncompleteEvents(): bool + { + return $this->numberOfTestMarkedIncompleteEvents() > 0; + } + + /** + * @return array> + */ + public function testTriggeredPhpunitDeprecationEvents(): array + { + return $this->testTriggeredPhpunitDeprecationEvents; + } + + public function numberOfTestsWithTestTriggeredPhpunitDeprecationEvents(): int + { + return count($this->testTriggeredPhpunitDeprecationEvents); + } + + public function hasTestTriggeredPhpunitDeprecationEvents(): bool + { + return $this->numberOfTestsWithTestTriggeredPhpunitDeprecationEvents() > 0; + } + + /** + * @return array> + */ + public function testTriggeredPhpunitErrorEvents(): array + { + return $this->testTriggeredPhpunitErrorEvents; + } + + public function numberOfTestsWithTestTriggeredPhpunitErrorEvents(): int + { + return count($this->testTriggeredPhpunitErrorEvents); + } + + public function hasTestTriggeredPhpunitErrorEvents(): bool + { + return $this->numberOfTestsWithTestTriggeredPhpunitErrorEvents() > 0; + } + + /** + * @return array> + */ + public function testTriggeredPhpunitNoticeEvents(): array + { + return $this->testTriggeredPhpunitNoticeEvents; + } + + public function numberOfTestsWithTestTriggeredPhpunitNoticeEvents(): int + { + return count($this->testTriggeredPhpunitNoticeEvents); + } + + public function hasTestTriggeredPhpunitNoticeEvents(): bool + { + return $this->numberOfTestsWithTestTriggeredPhpunitNoticeEvents() > 0; + } + + /** + * @return array> + */ + public function testTriggeredPhpunitWarningEvents(): array + { + return $this->testTriggeredPhpunitWarningEvents; + } + + public function numberOfTestsWithTestTriggeredPhpunitWarningEvents(): int + { + return count($this->testTriggeredPhpunitWarningEvents); + } + + public function hasTestTriggeredPhpunitWarningEvents(): bool + { + return $this->numberOfTestsWithTestTriggeredPhpunitWarningEvents() > 0; + } + + /** + * @return list + */ + public function testRunnerTriggeredDeprecationEvents(): array + { + return $this->testRunnerTriggeredDeprecationEvents; + } + + public function numberOfTestRunnerTriggeredDeprecationEvents(): int + { + return count($this->testRunnerTriggeredDeprecationEvents); + } + + public function hasTestRunnerTriggeredDeprecationEvents(): bool + { + return $this->numberOfTestRunnerTriggeredDeprecationEvents() > 0; + } + + /** + * @return list + */ + public function testRunnerTriggeredNoticeEvents(): array + { + return $this->testRunnerTriggeredNoticeEvents; + } + + public function numberOfTestRunnerTriggeredNoticeEvents(): int + { + return count($this->testRunnerTriggeredNoticeEvents); + } + + public function hasTestRunnerTriggeredNoticeEvents(): bool + { + return $this->numberOfTestRunnerTriggeredNoticeEvents() > 0; + } + + /** + * @return list + */ + public function testRunnerTriggeredWarningEvents(): array + { + return $this->testRunnerTriggeredWarningEvents; + } + + public function numberOfTestRunnerTriggeredWarningEvents(): int + { + return count($this->testRunnerTriggeredWarningEvents); + } + + public function hasTestRunnerTriggeredWarningEvents(): bool + { + return $this->numberOfTestRunnerTriggeredWarningEvents() > 0; + } + + public function wasSuccessful(): bool + { + return !$this->hasTestErroredEvents() && + !$this->hasTestFailedEvents() && + !$this->hasTestTriggeredPhpunitErrorEvents(); + } + + public function hasIssues(): bool + { + return $this->hasTestsWithIssues() || + $this->hasTestRunnerTriggeredWarningEvents(); + } + + public function hasTestsWithIssues(): bool + { + return $this->hasRiskyTests() || + $this->hasIncompleteTests() || + $this->hasDeprecations() || + $this->errors !== [] || + $this->hasNotices() || + $this->hasWarnings() || + $this->hasPhpunitNotices() || + $this->hasPhpunitWarnings(); + } + + /** + * @return list + */ + public function errors(): array + { + return $this->errors; + } + + /** + * @return list + */ + public function deprecations(): array + { + return $this->deprecations; + } + + /** + * @return list + */ + public function notices(): array + { + return $this->notices; + } + + /** + * @return list + */ + public function warnings(): array + { + return $this->warnings; + } + + /** + * @return list + */ + public function phpDeprecations(): array + { + return $this->phpDeprecations; + } + + /** + * @return list + */ + public function phpNotices(): array + { + return $this->phpNotices; + } + + /** + * @return list + */ + public function phpWarnings(): array + { + return $this->phpWarnings; + } + + public function hasTests(): bool + { + return $this->numberOfTests > 0; + } + + public function hasErrors(): bool + { + return $this->numberOfErrors() > 0; + } + + public function numberOfErrors(): int + { + return $this->numberOfTestErroredEvents() + + count($this->errors) + + $this->numberOfTestsWithTestTriggeredPhpunitErrorEvents(); + } + + public function hasDeprecations(): bool + { + return $this->numberOfDeprecations() > 0; + } + + public function hasPhpOrUserDeprecations(): bool + { + return $this->numberOfPhpOrUserDeprecations() > 0; + } + + public function numberOfPhpOrUserDeprecations(): int + { + return count($this->deprecations) + + count($this->phpDeprecations); + } + + public function hasPhpunitDeprecations(): bool + { + return $this->numberOfPhpunitDeprecations() > 0; + } + + public function numberOfPhpunitDeprecations(): int + { + return count($this->testTriggeredPhpunitDeprecationEvents) + + count($this->testRunnerTriggeredDeprecationEvents); + } + + public function hasPhpunitWarnings(): bool + { + return $this->numberOfPhpunitWarnings() > 0; + } + + public function numberOfPhpunitWarnings(): int + { + return count($this->testTriggeredPhpunitWarningEvents) + + count($this->testRunnerTriggeredWarningEvents); + } + + public function numberOfDeprecations(): int + { + return count($this->deprecations) + + count($this->phpDeprecations) + + count($this->testTriggeredPhpunitDeprecationEvents) + + count($this->testRunnerTriggeredDeprecationEvents); + } + + public function hasNotices(): bool + { + return $this->numberOfNotices() > 0; + } + + public function numberOfNotices(): int + { + return count($this->notices) + + count($this->phpNotices); + } + + public function hasWarnings(): bool + { + return $this->numberOfWarnings() > 0; + } + + public function numberOfWarnings(): int + { + return count($this->warnings) + + count($this->phpWarnings); + } + + public function hasIncompleteTests(): bool + { + return $this->testMarkedIncompleteEvents !== []; + } + + public function hasRiskyTests(): bool + { + return $this->testConsideredRiskyEvents !== []; + } + + public function hasSkippedTests(): bool + { + return $this->testSkippedEvents !== []; + } + + public function hasIssuesIgnoredByBaseline(): bool + { + return $this->numberOfIssuesIgnoredByBaseline > 0; + } + + /** + * @return non-negative-int + */ + public function numberOfIssuesIgnoredByBaseline(): int + { + return $this->numberOfIssuesIgnoredByBaseline; + } + + public function hasPhpunitNotices(): bool + { + return $this->numberOfPhpunitNotices() > 0; + } + + public function numberOfPhpunitNotices(): int + { + return $this->numberOfTestsWithTestTriggeredPhpunitNoticeEvents() + + $this->numberOfTestRunnerTriggeredNoticeEvents(); + } +} diff --git a/src/Runner/TestResultCache.php b/src/Runner/TestResultCache.php deleted file mode 100644 index 69e62828911..00000000000 --- a/src/Runner/TestResultCache.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -interface TestResultCache -{ - public function setState(string $testName, int $state): void; - - public function getState(string $testName): int; - - public function setTime(string $testName, float $time): void; - - public function getTime(string $testName): float; - - public function load(): void; - - public function persist(): void; -} diff --git a/src/Runner/TestSuiteLoader.php b/src/Runner/TestSuiteLoader.php index 63db0623afb..3d01964deaa 100644 --- a/src/Runner/TestSuiteLoader.php +++ b/src/Runner/TestSuiteLoader.php @@ -9,16 +9,134 @@ */ namespace PHPUnit\Runner; +use function array_diff; +use function basename; +use function get_declared_classes; +use function realpath; +use function str_ends_with; +use function strpos; +use function strtolower; +use function substr; +use PHPUnit\Framework\TestCase; use ReflectionClass; /** - * An interface to define how a test suite should be loaded. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * - * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 + * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -interface TestSuiteLoader +final class TestSuiteLoader { - public function load(string $suiteClassFile): ReflectionClass; + /** + * @var list + */ + private static array $declaredClasses = []; - public function reload(ReflectionClass $aClass): ReflectionClass; + /** + * @var array> + */ + private static array $fileToClassesMap = []; + + /** + * @throws Exception + * + * @return ReflectionClass + */ + public function load(string $suiteClassFile): ReflectionClass + { + $suiteClassFile = realpath($suiteClassFile); + $suiteClassName = $this->classNameFromFileName($suiteClassFile); + $loadedClasses = $this->loadSuiteClassFile($suiteClassFile); + + foreach ($loadedClasses as $className) { + /** @noinspection PhpUnhandledExceptionInspection */ + $class = new ReflectionClass($className); + + if ($class->isAnonymous()) { + continue; + } + + if ($class->getFileName() !== $suiteClassFile) { + continue; + } + + if (!$class->isSubclassOf(TestCase::class)) { + continue; + } + + if (!str_ends_with(strtolower($class->getShortName()), strtolower($suiteClassName))) { + continue; + } + + if (!$class->isAbstract()) { + return $class; + } + + $e = new ClassIsAbstractException($class->getName(), $suiteClassFile); + } + + if (isset($e)) { + throw $e; + } + + foreach ($loadedClasses as $className) { + if (str_ends_with(strtolower($className), strtolower($suiteClassName))) { + throw new ClassDoesNotExtendTestCaseException($className, $suiteClassFile); + } + } + + throw new ClassCannotBeFoundException($suiteClassName, $suiteClassFile); + } + + private function classNameFromFileName(string $suiteClassFile): string + { + $className = basename($suiteClassFile, '.php'); + $dotPos = strpos($className, '.'); + + if ($dotPos !== false) { + $className = substr($className, 0, $dotPos); + } + + return $className; + } + + /** + * @return array + */ + private function loadSuiteClassFile(string $suiteClassFile): array + { + if (isset(self::$fileToClassesMap[$suiteClassFile])) { + return self::$fileToClassesMap[$suiteClassFile]; + } + + if (self::$declaredClasses === []) { + self::$declaredClasses = get_declared_classes(); + } + + require_once $suiteClassFile; + + $loadedClasses = array_diff( + get_declared_classes(), + self::$declaredClasses, + ); + + foreach ($loadedClasses as $loadedClass) { + /** @noinspection PhpUnhandledExceptionInspection */ + $class = new ReflectionClass($loadedClass); + + if (!isset(self::$fileToClassesMap[$class->getFileName()])) { + self::$fileToClassesMap[$class->getFileName()] = []; + } + + self::$fileToClassesMap[$class->getFileName()][] = $class->getName(); + } + + self::$declaredClasses = get_declared_classes(); + + if ($loadedClasses === []) { + return self::$declaredClasses; + } + + return $loadedClasses; + } } diff --git a/src/Runner/TestSuiteSorter.php b/src/Runner/TestSuiteSorter.php index 8fa4283274b..b20ae250133 100644 --- a/src/Runner/TestSuiteSorter.php +++ b/src/Runner/TestSuiteSorter.php @@ -23,93 +23,57 @@ use PHPUnit\Framework\Test; use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestSuite; -use PHPUnit\Util\Test as TestUtil; +use PHPUnit\Runner\ResultCache\NullResultCache; +use PHPUnit\Runner\ResultCache\ResultCache; +use PHPUnit\Runner\ResultCache\ResultCacheId; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class TestSuiteSorter { - /** - * @var int - */ - public const ORDER_DEFAULT = 0; - - /** - * @var int - */ - public const ORDER_RANDOMIZED = 1; - - /** - * @var int - */ - public const ORDER_REVERSED = 2; - - /** - * @var int - */ - public const ORDER_DEFECTS_FIRST = 3; - - /** - * @var int - */ - public const ORDER_DURATION = 4; + public const int ORDER_DEFAULT = 0; + public const int ORDER_RANDOMIZED = 1; + public const int ORDER_REVERSED = 2; + public const int ORDER_DEFECTS_FIRST = 3; + public const int ORDER_DURATION = 4; + public const int ORDER_SIZE = 5; /** - * Order tests by @size annotation 'small', 'medium', 'large'. - * - * @var int + * @var non-empty-array */ - public const ORDER_SIZE = 5; - - /** - * List of sorting weights for all test result codes. A higher number gives higher priority. - */ - private const DEFECT_SORT_WEIGHT = [ - BaseTestRunner::STATUS_ERROR => 6, - BaseTestRunner::STATUS_FAILURE => 5, - BaseTestRunner::STATUS_WARNING => 4, - BaseTestRunner::STATUS_INCOMPLETE => 3, - BaseTestRunner::STATUS_RISKY => 2, - BaseTestRunner::STATUS_SKIPPED => 1, - BaseTestRunner::STATUS_UNKNOWN => 0, - ]; - - private const SIZE_SORT_WEIGHT = [ - TestUtil::SMALL => 1, - TestUtil::MEDIUM => 2, - TestUtil::LARGE => 3, - TestUtil::UNKNOWN => 4, + private const array SIZE_SORT_WEIGHT = [ + 'small' => 1, + 'medium' => 2, + 'large' => 3, + 'unknown' => 4, ]; /** * @var array Associative array of (string => DEFECT_SORT_WEIGHT) elements */ - private $defectSortOrder = []; - - /** - * @var TestResultCache - */ - private $cache; + private array $defectSortOrder = []; + private readonly ResultCache $cache; /** * @var array A list of normalized names of tests before reordering */ - private $originalExecutionOrder = []; + private array $originalExecutionOrder = []; /** * @var array A list of normalized names of tests affected by reordering */ - private $executionOrder = []; + private array $executionOrder = []; - public function __construct(?TestResultCache $cache = null) + public function __construct(?ResultCache $cache = null) { - $this->cache = $cache ?? new NullTestResultCache; + $this->cache = $cache ?? new NullResultCache; } /** * @throws Exception - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ public function reorderTestsInSuite(Test $suite, int $order, bool $resolveDependencies, int $orderDefects, bool $isRootTestSuite = true): void { @@ -122,9 +86,7 @@ public function reorderTestsInSuite(Test $suite, int $order, bool $resolveDepend ]; if (!in_array($order, $allowedOrders, true)) { - throw new Exception( - '$order must be one of TestSuiteSorter::ORDER_[DEFAULT|REVERSED|RANDOMIZED|DURATION|SIZE]' - ); + throw new InvalidOrderException; } $allowedOrderDefects = [ @@ -133,9 +95,7 @@ public function reorderTestsInSuite(Test $suite, int $order, bool $resolveDepend ]; if (!in_array($orderDefects, $allowedOrderDefects, true)) { - throw new Exception( - '$orderDefects must be one of TestSuiteSorter::ORDER_DEFAULT, TestSuiteSorter::ORDER_DEFECTS_FIRST' - ); + throw new InvalidOrderException; } if ($isRootTestSuite) { @@ -159,11 +119,17 @@ public function reorderTestsInSuite(Test $suite, int $order, bool $resolveDepend } } + /** + * @return array + */ public function getOriginalExecutionOrder(): array { return $this->originalExecutionOrder; } + /** + * @return array + */ public function getExecutionOrder(): array { return $this->executionOrder; @@ -171,7 +137,7 @@ public function getExecutionOrder(): array private function sort(TestSuite $suite, int $order, bool $resolveDependencies, int $orderDefects): void { - if (empty($suite->tests())) { + if ($suite->tests() === []) { return; } @@ -179,27 +145,25 @@ private function sort(TestSuite $suite, int $order, bool $resolveDependencies, i $suite->setTests($this->reverse($suite->tests())); } elseif ($order === self::ORDER_RANDOMIZED) { $suite->setTests($this->randomize($suite->tests())); - } elseif ($order === self::ORDER_DURATION && $this->cache !== null) { + } elseif ($order === self::ORDER_DURATION) { $suite->setTests($this->sortByDuration($suite->tests())); } elseif ($order === self::ORDER_SIZE) { $suite->setTests($this->sortBySize($suite->tests())); } - if ($orderDefects === self::ORDER_DEFECTS_FIRST && $this->cache !== null) { + if ($orderDefects === self::ORDER_DEFECTS_FIRST) { $suite->setTests($this->sortDefectsFirst($suite->tests())); } if ($resolveDependencies && !($suite instanceof DataProviderTestSuite)) { - /** @var TestCase[] $tests */ $tests = $suite->tests(); + /** @noinspection PhpParamsInspection */ + /** @phpstan-ignore argument.type */ $suite->setTests($this->resolveDependencies($tests)); } } - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ private function addSuiteToDefectSortOrder(TestSuite $suite): void { $max = 0; @@ -209,20 +173,32 @@ private function addSuiteToDefectSortOrder(TestSuite $suite): void continue; } - if (!isset($this->defectSortOrder[$test->sortId()])) { - $this->defectSortOrder[$test->sortId()] = self::DEFECT_SORT_WEIGHT[$this->cache->getState($test->sortId())]; - $max = max($max, $this->defectSortOrder[$test->sortId()]); + $sortId = $test->sortId(); + + if (!isset($this->defectSortOrder[$sortId])) { + $this->defectSortOrder[$sortId] = $this->cache->status(ResultCacheId::fromReorderable($test))->asInt(); + $max = max($max, $this->defectSortOrder[$sortId]); } } $this->defectSortOrder[$suite->sortId()] = $max; } + /** + * @param list $tests + * + * @return list + */ private function reverse(array $tests): array { return array_reverse($tests); } + /** + * @param list $tests + * + * @return list + */ private function randomize(array $tests): array { shuffle($tests); @@ -230,46 +206,46 @@ private function randomize(array $tests): array return $tests; } + /** + * @param list $tests + * + * @return list + */ private function sortDefectsFirst(array $tests): array { usort( $tests, - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - function ($left, $right) { - return $this->cmpDefectPriorityAndTime($left, $right); - } + fn (Test $left, Test $right) => $this->cmpDefectPriorityAndTime($left, $right), ); return $tests; } + /** + * @param list $tests + * + * @return list + */ private function sortByDuration(array $tests): array { usort( $tests, - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - function ($left, $right) { - return $this->cmpDuration($left, $right); - } + fn (Test $left, Test $right) => $this->cmpDuration($left, $right), ); return $tests; } + /** + * @param list $tests + * + * @return list + */ private function sortBySize(array $tests): array { usort( $tests, - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - function ($left, $right) { - return $this->cmpSize($left, $right); - } + fn (Test $left, Test $right) => $this->cmpSize($left, $right), ); return $tests; @@ -281,8 +257,6 @@ function ($left, $right) { * 1. sort tests by defect weight defined in self::DEFECT_SORT_WEIGHT * 2. when tests are equally defective, sort the fastest to the front * 3. do not reorder successful tests - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ private function cmpDefectPriorityAndTime(Test $a, Test $b): int { @@ -293,12 +267,12 @@ private function cmpDefectPriorityAndTime(Test $a, Test $b): int $priorityA = $this->defectSortOrder[$a->sortId()] ?? 0; $priorityB = $this->defectSortOrder[$b->sortId()] ?? 0; - if ($priorityB <=> $priorityA) { + if (($priorityB <=> $priorityA) > 0) { // Sort defect weight descending return $priorityB <=> $priorityA; } - if ($priorityA || $priorityB) { + if ($priorityA > 0 || $priorityB > 0) { return $this->cmpDuration($a, $b); } @@ -308,8 +282,6 @@ private function cmpDefectPriorityAndTime(Test $a, Test $b): int /** * Compares test duration for sorting tests by duration ascending. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ private function cmpDuration(Test $a, Test $b): int { @@ -317,7 +289,7 @@ private function cmpDuration(Test $a, Test $b): int return 0; } - return $this->cache->getTime($a->sortId()) <=> $this->cache->getTime($b->sortId()); + return $this->cache->time(ResultCacheId::fromReorderable($a)) <=> $this->cache->time(ResultCacheId::fromReorderable($b)); } /** @@ -326,11 +298,11 @@ private function cmpDuration(Test $a, Test $b): int private function cmpSize(Test $a, Test $b): int { $sizeA = ($a instanceof TestCase || $a instanceof DataProviderTestSuite) - ? $a->getSize() - : TestUtil::UNKNOWN; + ? $a->size()->asString() + : 'unknown'; $sizeB = ($b instanceof TestCase || $b instanceof DataProviderTestSuite) - ? $b->getSize() - : TestUtil::UNKNOWN; + ? $b->size()->asString() + : 'unknown'; return self::SIZE_SORT_WEIGHT[$sizeA] <=> self::SIZE_SORT_WEIGHT[$sizeB]; } @@ -346,9 +318,9 @@ private function cmpSize(Test $a, Test $b): int * 3. If the test has dependencies but none left to do: mark done, start again from the top * 4. When we reach the end add any leftover tests to the end. These will be marked 'skipped' during execution. * - * @param array $tests + * @param array $tests * - * @return array + * @return array */ private function resolveDependencies(array $tests): array { @@ -364,13 +336,13 @@ private function resolveDependencies(array $tests): array } else { $i++; } - } while (!empty($tests) && ($i < count($tests))); + } while ($tests !== [] && ($i < count($tests))); return array_merge($newTestOrder, $tests); } /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @return array */ private function calculateTestExecutionOrder(Test $suite): array { diff --git a/src/Runner/Version.php b/src/Runner/Version.php index 425a601367f..3820aa58090 100644 --- a/src/Runner/Version.php +++ b/src/Runner/Version.php @@ -10,26 +10,23 @@ namespace PHPUnit\Runner; use function array_slice; +use function assert; use function dirname; use function explode; use function implode; -use function strpos; +use function str_contains; use SebastianBergmann\Version as VersionId; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ final class Version { - /** - * @var string - */ - private static $pharVersion = ''; - - /** - * @var string - */ - private static $version = ''; + private static string $pharVersion = ''; + private static string $version = ''; /** - * Returns the current version of PHPUnit. + * @return non-empty-string */ public static function id(): string { @@ -38,16 +35,19 @@ public static function id(): string } if (self::$version === '') { - self::$version = (new VersionId('9.3', dirname(__DIR__, 2)))->getVersion(); + self::$version = new VersionId('13.0', dirname(__DIR__, 2))->asString(); } return self::$version; } + /** + * @return non-empty-string + */ public static function series(): string { - if (strpos(self::id(), '-')) { - $version = explode('-', self::id())[0]; + if (str_contains(self::id(), '-')) { + $version = explode('-', self::id(), 2)[0]; } else { $version = self::id(); } @@ -55,6 +55,20 @@ public static function series(): string return implode('.', array_slice(explode('.', $version), 0, 2)); } + /** + * @return positive-int + */ + public static function majorVersionNumber(): int + { + $majorVersion = (int) explode('.', self::series())[0]; + assert($majorVersion > 0); + + return $majorVersion; + } + + /** + * @return non-empty-string + */ public static function getVersionString(): string { return 'PHPUnit ' . self::id() . ' by Sebastian Bergmann and contributors.'; diff --git a/src/TextUI/Application.php b/src/TextUI/Application.php new file mode 100644 index 00000000000..e430df7c7b0 --- /dev/null +++ b/src/TextUI/Application.php @@ -0,0 +1,862 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +use const PHP_EOL; +use const PHP_VERSION; +use function assert; +use function class_exists; +use function defined; +use function dirname; +use function explode; +use function function_exists; +use function is_file; +use function method_exists; +use function printf; +use function realpath; +use function sprintf; +use function str_contains; +use function str_starts_with; +use function trim; +use function unlink; +use PHPUnit\Event\EventFacadeIsSealedException; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Event\UnknownSubscriberTypeException; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Logging\EventLogger; +use PHPUnit\Logging\JUnit\JunitXmlLogger; +use PHPUnit\Logging\OpenTestReporting\CannotOpenUriForWritingException; +use PHPUnit\Logging\OpenTestReporting\OtrXmlLogger; +use PHPUnit\Logging\TeamCity\TeamCityLogger; +use PHPUnit\Logging\TestDox\HtmlRenderer as TestDoxHtmlRenderer; +use PHPUnit\Logging\TestDox\PlainTextRenderer as TestDoxTextRenderer; +use PHPUnit\Logging\TestDox\TestResultCollector as TestDoxResultCollector; +use PHPUnit\Runner\Baseline\CannotLoadBaselineException; +use PHPUnit\Runner\Baseline\Generator as BaselineGenerator; +use PHPUnit\Runner\Baseline\Reader; +use PHPUnit\Runner\Baseline\Writer; +use PHPUnit\Runner\CodeCoverage; +use PHPUnit\Runner\CodeCoverageInitializationStatus; +use PHPUnit\Runner\DeprecationCollector\Facade as DeprecationCollector; +use PHPUnit\Runner\DirectoryDoesNotExistException; +use PHPUnit\Runner\ErrorHandler; +use PHPUnit\Runner\Extension\ExtensionBootstrapper; +use PHPUnit\Runner\Extension\Facade as ExtensionFacade; +use PHPUnit\Runner\Extension\PharLoader; +use PHPUnit\Runner\GarbageCollection\GarbageCollectionHandler; +use PHPUnit\Runner\Phpt\TestCase as PhptTestCase; +use PHPUnit\Runner\ResultCache\DefaultResultCache; +use PHPUnit\Runner\ResultCache\NullResultCache; +use PHPUnit\Runner\ResultCache\ResultCache; +use PHPUnit\Runner\ResultCache\ResultCacheHandler; +use PHPUnit\Runner\TestSuiteSorter; +use PHPUnit\Runner\Version; +use PHPUnit\TestRunner\IssueFilter; +use PHPUnit\TestRunner\TestResult\Facade as TestResultFacade; +use PHPUnit\TextUI\CliArguments\Builder; +use PHPUnit\TextUI\CliArguments\Configuration as CliConfiguration; +use PHPUnit\TextUI\CliArguments\Exception as ArgumentsException; +use PHPUnit\TextUI\CliArguments\XmlConfigurationFileFinder; +use PHPUnit\TextUI\Command\AtLeastVersionCommand; +use PHPUnit\TextUI\Command\CheckPhpConfigurationCommand; +use PHPUnit\TextUI\Command\GenerateConfigurationCommand; +use PHPUnit\TextUI\Command\ListGroupsCommand; +use PHPUnit\TextUI\Command\ListTestFilesCommand; +use PHPUnit\TextUI\Command\ListTestsAsTextCommand; +use PHPUnit\TextUI\Command\ListTestsAsXmlCommand; +use PHPUnit\TextUI\Command\ListTestSuitesCommand; +use PHPUnit\TextUI\Command\MigrateConfigurationCommand; +use PHPUnit\TextUI\Command\Result; +use PHPUnit\TextUI\Command\ShowHelpCommand; +use PHPUnit\TextUI\Command\ShowVersionCommand; +use PHPUnit\TextUI\Command\VersionCheckCommand; +use PHPUnit\TextUI\Command\WarmCodeCoverageCacheCommand; +use PHPUnit\TextUI\Configuration\BootstrapLoader; +use PHPUnit\TextUI\Configuration\BootstrapScriptDoesNotExistException; +use PHPUnit\TextUI\Configuration\BootstrapScriptException; +use PHPUnit\TextUI\Configuration\CodeCoverageFilterRegistry; +use PHPUnit\TextUI\Configuration\Configuration; +use PHPUnit\TextUI\Configuration\PhpHandler; +use PHPUnit\TextUI\Configuration\Registry; +use PHPUnit\TextUI\Configuration\TestSuiteBuilder; +use PHPUnit\TextUI\Output\DefaultPrinter; +use PHPUnit\TextUI\Output\Facade as OutputFacade; +use PHPUnit\TextUI\Output\Printer; +use PHPUnit\TextUI\XmlConfiguration\Configuration as XmlConfiguration; +use PHPUnit\TextUI\XmlConfiguration\DefaultConfiguration; +use PHPUnit\TextUI\XmlConfiguration\Loader; +use PHPUnit\Util\Http\PhpDownloader; +use SebastianBergmann\Timer\Timer; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Application +{ + /** + * @param list $argv + */ + public function run(array $argv): int + { + $this->preload(); + + try { + EventFacade::emitter()->applicationStarted(); + + $cliConfiguration = $this->buildCliConfiguration($argv); + $pathToXmlConfigurationFile = (new XmlConfigurationFileFinder)->find($cliConfiguration); + + $this->executeCommandsThatOnlyRequireCliConfiguration($cliConfiguration, $pathToXmlConfigurationFile); + + $xmlConfiguration = $this->loadXmlConfiguration($pathToXmlConfigurationFile); + + $configuration = Registry::init( + $cliConfiguration, + $xmlConfiguration, + ); + + (new PhpHandler)->handle($configuration->php()); + + try { + (new BootstrapLoader)->handle($configuration); + } catch (BootstrapScriptDoesNotExistException|BootstrapScriptException $e) { + $this->exitWithErrorMessage($e->getMessage()); + } + + $this->executeCommandsThatDoNotRequireTheTestSuite($configuration, $cliConfiguration); + + $pharExtensions = null; + $extensionRequiresCodeCoverageCollection = false; + $extensionReplacesOutput = false; + $extensionReplacesProgressOutput = false; + $extensionReplacesResultOutput = false; + + if (!$configuration->noExtensions()) { + if ($configuration->hasPharExtensionDirectory()) { + $pharExtensions = (new PharLoader)->loadPharExtensionsInDirectory( + $configuration->pharExtensionDirectory(), + ); + } + + $bootstrappedExtensions = $this->bootstrapExtensions($configuration); + $extensionRequiresCodeCoverageCollection = $bootstrappedExtensions['requiresCodeCoverageCollection']; + $extensionReplacesOutput = $bootstrappedExtensions['replacesOutput']; + $extensionReplacesProgressOutput = $bootstrappedExtensions['replacesProgressOutput']; + $extensionReplacesResultOutput = $bootstrappedExtensions['replacesResultOutput']; + } + + $printer = OutputFacade::init( + $configuration, + $extensionReplacesProgressOutput, + $extensionReplacesResultOutput, + ); + + if ($configuration->debug()) { + EventFacade::instance()->registerTracer( + new EventLogger( + 'php://stdout', + $configuration->withTelemetry(), + ), + ); + } + + TestResultFacade::init(); + DeprecationCollector::init(); + + $this->registerLogfileWriters($configuration); + + $testDoxResultCollector = $this->testDoxResultCollector($configuration); + + $resultCache = $this->initializeTestResultCache($configuration); + + if ($configuration->controlGarbageCollector()) { + new GarbageCollectionHandler( + EventFacade::instance(), + $configuration->numberOfTestsBeforeGarbageCollection(), + ); + } + + $baselineGenerator = $this->configureBaseline($configuration); + + EventFacade::instance()->seal(); + + ErrorHandler::instance()->registerDeprecationHandler(); + + $testSuite = $this->buildTestSuite($configuration); + + ErrorHandler::instance()->restoreDeprecationHandler(); + + $this->executeCommandsThatRequireTheTestSuite($configuration, $cliConfiguration, $testSuite); + + if ($testSuite->isEmpty() && !$configuration->hasCliArguments() && $configuration->testSuite()->isEmpty()) { + $this->execute(new ShowHelpCommand(Result::FAILURE)); + } + + $coverageInitializationStatus = CodeCoverage::instance()->init( + $configuration, + CodeCoverageFilterRegistry::instance(), + $extensionRequiresCodeCoverageCollection, + ); + + if (!$configuration->debug() && !$extensionReplacesOutput) { + $this->writeRuntimeInformation($printer, $configuration); + $this->writePharExtensionInformation($printer, $pharExtensions); + $this->writeRandomSeedInformation($printer, $configuration); + + $printer->print(PHP_EOL); + } + + $this->configureDeprecationTriggers($configuration); + + $timer = new Timer; + $timer->start(); + + if ($coverageInitializationStatus === CodeCoverageInitializationStatus::NOT_REQUESTED || + $coverageInitializationStatus === CodeCoverageInitializationStatus::SUCCEEDED) { + $runner = new TestRunner; + + $runner->run( + $configuration, + $resultCache, + $testSuite, + ); + } + + $duration = $timer->stop(); + + $testDoxResult = null; + + if (isset($testDoxResultCollector)) { + $testDoxResult = $testDoxResultCollector->testMethodsGroupedByClass(); + } + + if ($testDoxResult !== null && + $configuration->hasLogfileTestdoxHtml()) { + try { + OutputFacade::printerFor($configuration->logfileTestdoxHtml())->print( + (new TestDoxHtmlRenderer)->render($testDoxResult), + ); + } catch (DirectoryDoesNotExistException|InvalidSocketException $e) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Cannot log test results in TestDox HTML format to "%s": %s', + $configuration->logfileTestdoxHtml(), + $e->getMessage(), + ), + ); + } + } + + if ($testDoxResult !== null && + $configuration->hasLogfileTestdoxText()) { + try { + OutputFacade::printerFor($configuration->logfileTestdoxText())->print( + (new TestDoxTextRenderer)->render($testDoxResult), + ); + } catch (DirectoryDoesNotExistException|InvalidSocketException $e) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Cannot log test results in TestDox plain text format to "%s": %s', + $configuration->logfileTestdoxText(), + $e->getMessage(), + ), + ); + } + } + + $result = TestResultFacade::result(); + + if (!$extensionReplacesResultOutput && !$configuration->debug()) { + OutputFacade::printResult( + $result, + $testDoxResult, + $duration, + $configuration->hasSpecificDeprecationToStopOn(), + ); + } + + CodeCoverage::instance()->generateReports($printer, $configuration); + + if (isset($baselineGenerator)) { + (new Writer)->write( + $configuration->generateBaseline(), + $baselineGenerator->baseline(), + ); + + $printer->print( + sprintf( + PHP_EOL . 'Baseline written to %s.' . PHP_EOL, + realpath($configuration->generateBaseline()), + ), + ); + } + + $shellExitCode = (new ShellExitCodeCalculator)->calculate( + $configuration, + $result, + ); + + EventFacade::emitter()->applicationFinished($shellExitCode); + + return $shellExitCode; + // @codeCoverageIgnoreStart + } catch (Throwable $t) { + $this->exitWithCrashMessage($t); + } + // @codeCoverageIgnoreEnd + } + + private function execute(Command\Command $command, bool $requiresResultCollectedFromEvents = false): never + { + $errored = false; + + if ($requiresResultCollectedFromEvents) { + try { + TestResultFacade::init(); + EventFacade::instance()->seal(); + + $resultCollectedFromEvents = TestResultFacade::result(); + + $errored = $resultCollectedFromEvents->hasTestTriggeredPhpunitErrorEvents(); + } catch (EventFacadeIsSealedException|UnknownSubscriberTypeException) { + } + } + + print Version::getVersionString() . PHP_EOL . PHP_EOL; + + if (!$errored) { + $result = $command->execute(); + + print $result->output(); + + exit($result->shellExitCode()); + } + + assert(isset($resultCollectedFromEvents)); + + print 'There were errors:' . PHP_EOL; + + foreach ($resultCollectedFromEvents->testTriggeredPhpunitErrorEvents() as $events) { + foreach ($events as $event) { + print PHP_EOL . trim($event->message()) . PHP_EOL; + } + } + + exit(Result::EXCEPTION); + } + + /** + * @param list $argv + */ + private function buildCliConfiguration(array $argv): CliConfiguration + { + try { + $cliConfiguration = (new Builder)->fromParameters($argv); + } catch (ArgumentsException $e) { + $this->exitWithErrorMessage($e->getMessage()); + } + + return $cliConfiguration; + } + + private function loadXmlConfiguration(false|string $configurationFile): XmlConfiguration + { + if ($configurationFile === false) { + return DefaultConfiguration::create(); + } + + try { + return (new Loader)->load($configurationFile); + } catch (Throwable $e) { + $this->exitWithErrorMessage($e->getMessage()); + } + } + + private function buildTestSuite(Configuration $configuration): TestSuite + { + try { + return (new TestSuiteBuilder)->build($configuration); + } catch (Exception $e) { + $this->exitWithErrorMessage($e->getMessage()); + } + } + + /** + * @return array{requiresCodeCoverageCollection: bool, replacesOutput: bool, replacesProgressOutput: bool, replacesResultOutput: bool} + */ + private function bootstrapExtensions(Configuration $configuration): array + { + $facade = new ExtensionFacade; + + $extensionBootstrapper = new ExtensionBootstrapper( + $configuration, + $facade, + ); + + foreach ($configuration->extensionBootstrappers() as $bootstrapper) { + $extensionBootstrapper->bootstrap( + $bootstrapper['className'], + $bootstrapper['parameters'], + ); + } + + return [ + 'requiresCodeCoverageCollection' => $facade->requiresCodeCoverageCollection(), + 'replacesOutput' => $facade->replacesOutput(), + 'replacesProgressOutput' => $facade->replacesProgressOutput(), + 'replacesResultOutput' => $facade->replacesResultOutput(), + ]; + } + + private function executeCommandsThatOnlyRequireCliConfiguration(CliConfiguration $cliConfiguration, false|string $configurationFile): void + { + if ($cliConfiguration->generateConfiguration()) { + $this->execute(new GenerateConfigurationCommand); + } + + if ($cliConfiguration->migrateConfiguration()) { + if ($configurationFile === false) { + $this->exitWithErrorMessage('No configuration file found to migrate'); + } + + $this->execute(new MigrateConfigurationCommand(realpath($configurationFile))); + } + + if ($cliConfiguration->hasAtLeastVersion()) { + $this->execute(new AtLeastVersionCommand($cliConfiguration->atLeastVersion())); + } + + if ($cliConfiguration->version()) { + $this->execute(new ShowVersionCommand); + } + + if ($cliConfiguration->checkPhpConfiguration()) { + $this->execute(new CheckPhpConfigurationCommand); + } + + if ($cliConfiguration->checkVersion()) { + $this->execute(new VersionCheckCommand(new PhpDownloader, Version::majorVersionNumber(), Version::id())); + } + + if ($cliConfiguration->help()) { + $this->execute(new ShowHelpCommand(Result::SUCCESS)); + } + } + + private function executeCommandsThatDoNotRequireTheTestSuite(Configuration $configuration, CliConfiguration $cliConfiguration): void + { + if ($cliConfiguration->warmCoverageCache()) { + $this->execute(new WarmCodeCoverageCacheCommand($configuration, CodeCoverageFilterRegistry::instance())); + } + } + + private function executeCommandsThatRequireTheTestSuite(Configuration $configuration, CliConfiguration $cliConfiguration, TestSuite $testSuite): void + { + if ($cliConfiguration->listSuites()) { + $this->execute(new ListTestSuitesCommand($testSuite)); + } + + if ($cliConfiguration->listGroups()) { + $this->execute( + new ListGroupsCommand( + $this->filteredTests( + $configuration, + $testSuite, + ), + ), + true, + ); + } + + if ($cliConfiguration->listTests()) { + $this->execute( + new ListTestsAsTextCommand( + $this->filteredTests( + $configuration, + $testSuite, + ), + ), + true, + ); + } + + if ($cliConfiguration->hasListTestsXml()) { + $this->execute( + new ListTestsAsXmlCommand( + $this->filteredTests( + $configuration, + $testSuite, + ), + $cliConfiguration->listTestsXml(), + ), + true, + ); + } + + if ($cliConfiguration->listTestFiles()) { + $this->execute( + new ListTestFilesCommand( + $this->filteredTests( + $configuration, + $testSuite, + ), + ), + true, + ); + } + } + + private function writeRuntimeInformation(Printer $printer, Configuration $configuration): void + { + $printer->print(Version::getVersionString() . PHP_EOL . PHP_EOL); + + $runtime = 'PHP ' . PHP_VERSION; + + if (CodeCoverage::instance()->isActive()) { + $runtime .= ' with ' . CodeCoverage::instance()->driverNameAndVersion(); + } + + $this->writeMessage($printer, 'Runtime', $runtime); + + if ($configuration->hasConfigurationFile()) { + $this->writeMessage( + $printer, + 'Configuration', + $configuration->configurationFile(), + ); + } + } + + /** + * @param ?list $pharExtensions + */ + private function writePharExtensionInformation(Printer $printer, ?array $pharExtensions): void + { + if ($pharExtensions === null) { + return; + } + + foreach ($pharExtensions as $extension) { + $this->writeMessage( + $printer, + 'Extension', + $extension, + ); + } + } + + private function writeMessage(Printer $printer, string $type, string $message): void + { + $printer->print( + sprintf( + "%-15s%s\n", + $type . ':', + $message, + ), + ); + } + + private function writeRandomSeedInformation(Printer $printer, Configuration $configuration): void + { + if ($configuration->executionOrder() === TestSuiteSorter::ORDER_RANDOMIZED) { + $this->writeMessage( + $printer, + 'Random Seed', + (string) $configuration->randomOrderSeed(), + ); + } + } + + private function registerLogfileWriters(Configuration $configuration): void + { + if ($configuration->hasLogEventsText()) { + if (is_file($configuration->logEventsText())) { + unlink($configuration->logEventsText()); + } + + EventFacade::instance()->registerTracer( + new EventLogger( + $configuration->logEventsText(), + false, + ), + ); + } + + if ($configuration->hasLogEventsVerboseText()) { + if (is_file($configuration->logEventsVerboseText())) { + unlink($configuration->logEventsVerboseText()); + } + + EventFacade::instance()->registerTracer( + new EventLogger( + $configuration->logEventsVerboseText(), + true, + ), + ); + } + + if ($configuration->hasLogfileJunit()) { + try { + new JunitXmlLogger( + OutputFacade::printerFor($configuration->logfileJunit()), + EventFacade::instance(), + ); + } catch (DirectoryDoesNotExistException|InvalidSocketException $e) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Cannot log test results in JUnit XML format to "%s": %s', + $configuration->logfileJunit(), + $e->getMessage(), + ), + ); + } + } + + if ($configuration->hasLogfileOtr()) { + try { + new OtrXmlLogger( + EventFacade::instance(), + $configuration->logfileOtr(), + $configuration->includeGitInformationInOtrLogfile(), + ); + } catch (CannotOpenUriForWritingException $e) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Cannot log test results in Open Test Reporting XML format to "%s": %s', + $configuration->logfileOtr(), + $e->getMessage(), + ), + ); + } + } + + if ($configuration->hasLogfileTeamcity()) { + try { + new TeamCityLogger( + DefaultPrinter::from( + $configuration->logfileTeamcity(), + ), + EventFacade::instance(), + ); + } catch (DirectoryDoesNotExistException|InvalidSocketException $e) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Cannot log test results in TeamCity format to "%s": %s', + $configuration->logfileTeamcity(), + $e->getMessage(), + ), + ); + } + } + } + + private function testDoxResultCollector(Configuration $configuration): ?TestDoxResultCollector + { + if ($configuration->hasLogfileTestdoxHtml() || + $configuration->hasLogfileTestdoxText() || + $configuration->outputIsTestDox()) { + return new TestDoxResultCollector( + EventFacade::instance(), + new IssueFilter($configuration->source()), + ); + } + + return null; + } + + private function initializeTestResultCache(Configuration $configuration): ResultCache + { + if ($configuration->cacheResult()) { + $cache = new DefaultResultCache($configuration->testResultCacheFile()); + + new ResultCacheHandler($cache, EventFacade::instance()); + + return $cache; + } + + return new NullResultCache; + } + + private function configureBaseline(Configuration $configuration): ?BaselineGenerator + { + if ($configuration->hasGenerateBaseline()) { + return new BaselineGenerator( + EventFacade::instance(), + $configuration->source(), + ); + } + + if ($configuration->source()->useBaseline()) { + $baselineFile = $configuration->source()->baseline(); + $baseline = null; + + try { + $baseline = (new Reader)->read($baselineFile); + } catch (CannotLoadBaselineException $e) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning($e->getMessage()); + } + + if ($baseline !== null) { + ErrorHandler::instance()->useBaseline($baseline); + } + } + + return null; + } + + /** + * @codeCoverageIgnore + */ + private function exitWithCrashMessage(Throwable $t): never + { + $message = $t->getMessage(); + + if (trim($message) === '') { + $message = '(no message)'; + } + + printf( + '%s%sAn error occurred inside PHPUnit.%s%sMessage: %s', + PHP_EOL, + PHP_EOL, + PHP_EOL, + PHP_EOL, + $message, + ); + + $first = true; + + if ($t->getPrevious() !== null) { + $t = $t->getPrevious(); + } + + do { + printf( + '%s%s: %s:%d%s%s%s%s', + PHP_EOL, + $first ? 'Location' : 'Caused by', + $t->getFile(), + $t->getLine(), + PHP_EOL, + PHP_EOL, + $t->getTraceAsString(), + PHP_EOL, + ); + + $first = false; + } while ($t = $t->getPrevious()); + + exit(Result::CRASH); + } + + private function exitWithErrorMessage(string $message): never + { + print Version::getVersionString() . PHP_EOL . PHP_EOL . $message . PHP_EOL; + + exit(Result::EXCEPTION); + } + + /** + * @return list + */ + private function filteredTests(Configuration $configuration, TestSuite $suite): array + { + (new TestSuiteFilterProcessor)->process($configuration, $suite); + + return $suite->collect(); + } + + private function configureDeprecationTriggers(Configuration $configuration): void + { + $deprecationTriggers = [ + 'functions' => [], + 'methods' => [], + ]; + + foreach ($configuration->source()->deprecationTriggers()['functions'] as $function) { + if (!function_exists($function)) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Function %s cannot be configured as a deprecation trigger because it is not declared', + $function, + ), + ); + + continue; + } + + $deprecationTriggers['functions'][] = $function; + } + + foreach ($configuration->source()->deprecationTriggers()['methods'] as $method) { + if (!str_contains($method, '::')) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + '%s cannot be configured as a deprecation trigger because it is not in ClassName::methodName format', + $method, + ), + ); + + continue; + } + + [$className, $methodName] = explode('::', $method); + + if (!class_exists($className) || !method_exists($className, $methodName)) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Method %s::%s cannot be configured as a deprecation trigger because it is not declared', + $className, + $methodName, + ), + ); + + continue; + } + + $deprecationTriggers['methods'][] = [ + 'className' => $className, + 'methodName' => $methodName, + ]; + } + + ErrorHandler::instance()->useDeprecationTriggers($deprecationTriggers); + } + + private function preload(): void + { + if (!defined('PHPUNIT_COMPOSER_INSTALL')) { + return; + } + + $classMapFile = dirname(PHPUNIT_COMPOSER_INSTALL) . '/composer/autoload_classmap.php'; + + if (!is_file($classMapFile)) { + return; + } + + foreach (require $classMapFile as $codeUnitName => $sourceCodeFile) { + if (!str_starts_with($codeUnitName, 'PHPUnit\\') && + !str_starts_with($codeUnitName, 'SebastianBergmann\\')) { + continue; + } + + if (str_contains($sourceCodeFile, '/tests/')) { + continue; + } + + require_once $sourceCodeFile; + } + } +} diff --git a/src/TextUI/CliArguments/Builder.php b/src/TextUI/CliArguments/Builder.php deleted file mode 100644 index 74739ab858c..00000000000 --- a/src/TextUI/CliArguments/Builder.php +++ /dev/null @@ -1,828 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\CliArguments; - -use function array_merge; -use function class_exists; -use function explode; -use function is_numeric; -use function str_replace; -use PHPUnit\Runner\TestSuiteSorter; -use PHPUnit\TextUI\DefaultResultPrinter; -use PHPUnit\TextUI\XmlConfiguration\Extension; -use PHPUnit\Util\Exception as UtilException; -use PHPUnit\Util\Getopt; -use PHPUnit\Util\Log\TeamCity; -use PHPUnit\Util\TestDox\CliTestDoxPrinter; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Builder -{ - private const LONG_OPTIONS = [ - 'atleast-version=', - 'prepend=', - 'bootstrap=', - 'cache-result', - 'do-not-cache-result', - 'cache-result-file=', - 'check-version', - 'colors==', - 'columns=', - 'configuration=', - 'coverage-filter=', - 'coverage-clover=', - 'coverage-crap4j=', - 'coverage-html=', - 'coverage-php=', - 'coverage-text==', - 'coverage-xml=', - 'debug', - 'disallow-test-output', - 'disallow-resource-usage', - 'disallow-todo-tests', - 'default-time-limit=', - 'enforce-time-limit', - 'exclude-group=', - 'extensions=', - 'filter=', - 'generate-configuration', - 'globals-backup', - 'group=', - 'help', - 'resolve-dependencies', - 'ignore-dependencies', - 'include-path=', - 'list-groups', - 'list-suites', - 'list-tests', - 'list-tests-xml=', - 'loader=', - 'log-junit=', - 'log-teamcity=', - 'no-configuration', - 'no-coverage', - 'no-logging', - 'no-interaction', - 'no-extensions', - 'order-by=', - 'printer=', - 'process-isolation', - 'repeat=', - 'dont-report-useless-tests', - 'random-order', - 'random-order-seed=', - 'reverse-order', - 'reverse-list', - 'static-backup', - 'stderr', - 'stop-on-defect', - 'stop-on-error', - 'stop-on-failure', - 'stop-on-warning', - 'stop-on-incomplete', - 'stop-on-risky', - 'stop-on-skipped', - 'fail-on-empty-test-suite', - 'fail-on-warning', - 'fail-on-risky', - 'strict-coverage', - 'disable-coverage-ignore', - 'strict-global-state', - 'teamcity', - 'testdox', - 'testdox-group=', - 'testdox-exclude-group=', - 'testdox-html=', - 'testdox-text=', - 'testdox-xml=', - 'test-suffix=', - 'testsuite=', - 'verbose', - 'version', - 'whitelist=', - 'dump-xdebug-filter=', - ]; - - private const SHORT_OPTIONS = 'd:c:hv'; - - public function fromParameters(array $parameters, array $additionalLongOptions): Configuration - { - try { - $options = Getopt::parse( - $parameters, - self::SHORT_OPTIONS, - array_merge(self::LONG_OPTIONS, $additionalLongOptions) - ); - } catch (UtilException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - - $argument = null; - $atLeastVersion = null; - $backupGlobals = null; - $backupStaticAttributes = null; - $beStrictAboutChangesToGlobalState = null; - $beStrictAboutResourceUsageDuringSmallTests = null; - $bootstrap = null; - $cacheResult = null; - $cacheResultFile = null; - $checkVersion = null; - $colors = null; - $columns = null; - $configuration = null; - $coverageFilter = null; - $coverageClover = null; - $coverageCrap4J = null; - $coverageHtml = null; - $coveragePhp = null; - $coverageText = null; - $coverageTextShowUncoveredFiles = null; - $coverageTextShowOnlySummary = null; - $coverageXml = null; - $debug = null; - $defaultTimeLimit = null; - $disableCodeCoverageIgnore = null; - $disallowTestOutput = null; - $disallowTodoAnnotatedTests = null; - $enforceTimeLimit = null; - $excludeGroups = null; - $executionOrder = null; - $executionOrderDefects = null; - $extensions = []; - $unavailableExtensions = []; - $failOnEmptyTestSuite = null; - $failOnIncomplete = null; - $failOnRisky = null; - $failOnSkipped = null; - $failOnWarning = null; - $filter = null; - $generateConfiguration = null; - $groups = null; - $help = null; - $includePath = null; - $iniSettings = []; - $junitLogfile = null; - $listGroups = null; - $listSuites = null; - $listTests = null; - $listTestsXml = null; - $loader = null; - $noCoverage = null; - $noExtensions = null; - $noInteraction = null; - $noLogging = null; - $printer = null; - $processIsolation = null; - $randomOrderSeed = null; - $repeat = null; - $reportUselessTests = null; - $resolveDependencies = null; - $reverseList = null; - $stderr = null; - $strictCoverage = null; - $stopOnDefect = null; - $stopOnError = null; - $stopOnFailure = null; - $stopOnIncomplete = null; - $stopOnRisky = null; - $stopOnSkipped = null; - $stopOnWarning = null; - $teamcityLogfile = null; - $testdoxExcludeGroups = null; - $testdoxGroups = null; - $testdoxHtmlFile = null; - $testdoxTextFile = null; - $testdoxXmlFile = null; - $testSuffixes = null; - $testSuite = null; - $unrecognizedOptions = []; - $unrecognizedOrderBy = null; - $useDefaultConfiguration = null; - $verbose = null; - $version = null; - $xdebugFilterFile = null; - - if (isset($options[1][0])) { - $argument = $options[1][0]; - } - - foreach ($options[0] as $option) { - switch ($option[0]) { - case '--colors': - $colors = $option[1] ?: DefaultResultPrinter::COLOR_AUTO; - - break; - - case '--bootstrap': - $bootstrap = $option[1]; - - break; - - case '--cache-result': - $cacheResult = true; - - break; - - case '--do-not-cache-result': - $cacheResult = false; - - break; - - case '--cache-result-file': - $cacheResultFile = $option[1]; - - break; - - case '--columns': - if (is_numeric($option[1])) { - $columns = (int) $option[1]; - } elseif ($option[1] === 'max') { - $columns = 'max'; - } - - break; - - case 'c': - case '--configuration': - $configuration = $option[1]; - - break; - - case '--coverage-clover': - $coverageClover = $option[1]; - - break; - - case '--coverage-crap4j': - $coverageCrap4J = $option[1]; - - break; - - case '--coverage-html': - $coverageHtml = $option[1]; - - break; - - case '--coverage-php': - $coveragePhp = $option[1]; - - break; - - case '--coverage-text': - if ($option[1] === null) { - $option[1] = 'php://stdout'; - } - - $coverageText = $option[1]; - $coverageTextShowUncoveredFiles = false; - $coverageTextShowOnlySummary = false; - - break; - - case '--coverage-xml': - $coverageXml = $option[1]; - - break; - - case 'd': - $tmp = explode('=', $option[1]); - - if (isset($tmp[0])) { - if (isset($tmp[1])) { - $iniSettings[$tmp[0]] = $tmp[1]; - } else { - $iniSettings[$tmp[0]] = '1'; - } - } - - break; - - case '--debug': - $debug = true; - - break; - - case 'h': - case '--help': - $help = true; - - break; - - case '--filter': - $filter = $option[1]; - - break; - - case '--testsuite': - $testSuite = $option[1]; - - break; - - case '--generate-configuration': - $generateConfiguration = true; - - break; - - case '--group': - $groups = explode(',', $option[1]); - - break; - - case '--exclude-group': - $excludeGroups = explode(',', $option[1]); - - break; - - case '--test-suffix': - $testSuffixes = explode(',', $option[1]); - - break; - - case '--include-path': - $includePath = $option[1]; - - break; - - case '--list-groups': - $listGroups = true; - - break; - - case '--list-suites': - $listSuites = true; - - break; - - case '--list-tests': - $listTests = true; - - break; - - case '--list-tests-xml': - $listTestsXml = $option[1]; - - break; - - case '--printer': - $printer = $option[1]; - - break; - - case '--loader': - $loader = $option[1]; - - break; - - case '--log-junit': - $junitLogfile = $option[1]; - - break; - - case '--log-teamcity': - $teamcityLogfile = $option[1]; - - break; - - case '--order-by': - foreach (explode(',', $option[1]) as $order) { - switch ($order) { - case 'default': - $executionOrder = TestSuiteSorter::ORDER_DEFAULT; - $executionOrderDefects = TestSuiteSorter::ORDER_DEFAULT; - $resolveDependencies = true; - - break; - - case 'defects': - $executionOrderDefects = TestSuiteSorter::ORDER_DEFECTS_FIRST; - - break; - - case 'depends': - $resolveDependencies = true; - - break; - - case 'duration': - $executionOrder = TestSuiteSorter::ORDER_DURATION; - - break; - - case 'no-depends': - $resolveDependencies = false; - - break; - - case 'random': - $executionOrder = TestSuiteSorter::ORDER_RANDOMIZED; - - break; - - case 'reverse': - $executionOrder = TestSuiteSorter::ORDER_REVERSED; - - break; - - case 'size': - $executionOrder = TestSuiteSorter::ORDER_SIZE; - - break; - - default: - $unrecognizedOrderBy = $order; - } - } - - break; - - case '--process-isolation': - $processIsolation = true; - - break; - - case '--repeat': - $repeat = (int) $option[1]; - - break; - - case '--stderr': - $stderr = true; - - break; - - case '--stop-on-defect': - $stopOnDefect = true; - - break; - - case '--stop-on-error': - $stopOnError = true; - - break; - - case '--stop-on-failure': - $stopOnFailure = true; - - break; - - case '--stop-on-warning': - $stopOnWarning = true; - - break; - - case '--stop-on-incomplete': - $stopOnIncomplete = true; - - break; - - case '--stop-on-risky': - $stopOnRisky = true; - - break; - - case '--stop-on-skipped': - $stopOnSkipped = true; - - break; - - case '--fail-on-empty-test-suite': - $failOnEmptyTestSuite = true; - - break; - - case '--fail-on-incomplete': - $failOnIncomplete = true; - - break; - - case '--fail-on-risky': - $failOnRisky = true; - - break; - - case '--fail-on-Skipped': - $failOnSkipped = true; - - break; - - case '--fail-on-warning': - $failOnWarning = true; - - break; - - case '--teamcity': - $printer = TeamCity::class; - - break; - - case '--testdox': - $printer = CliTestDoxPrinter::class; - - break; - - case '--testdox-group': - $testdoxGroups = explode(',', $option[1]); - - break; - - case '--testdox-exclude-group': - $testdoxExcludeGroups = explode(',', $option[1]); - - break; - - case '--testdox-html': - $testdoxHtmlFile = $option[1]; - - break; - - case '--testdox-text': - $testdoxTextFile = $option[1]; - - break; - - case '--testdox-xml': - $testdoxXmlFile = $option[1]; - - break; - - case '--no-configuration': - $useDefaultConfiguration = false; - - break; - - case '--extensions': - foreach (explode(',', $option[1]) as $extensionClass) { - if (!class_exists($extensionClass)) { - $unavailableExtensions[] = $extensionClass; - - continue; - } - - $extensions[] = new Extension($extensionClass, '', []); - } - - break; - - case '--no-extensions': - $noExtensions = true; - - break; - - case '--no-coverage': - $noCoverage = true; - - break; - - case '--no-logging': - $noLogging = true; - - break; - - case '--no-interaction': - $noInteraction = true; - - break; - - case '--globals-backup': - $backupGlobals = true; - - break; - - case '--static-backup': - $backupStaticAttributes = true; - - break; - - case 'v': - case '--verbose': - $verbose = true; - - break; - - case '--atleast-version': - $atLeastVersion = $option[1]; - - break; - - case '--version': - $version = true; - - break; - - case '--dont-report-useless-tests': - $reportUselessTests = false; - - break; - - case '--strict-coverage': - $strictCoverage = true; - - break; - - case '--disable-coverage-ignore': - $disableCodeCoverageIgnore = true; - - break; - - case '--strict-global-state': - $beStrictAboutChangesToGlobalState = true; - - break; - - case '--disallow-test-output': - $disallowTestOutput = true; - - break; - - case '--disallow-resource-usage': - $beStrictAboutResourceUsageDuringSmallTests = true; - - break; - - case '--default-time-limit': - $defaultTimeLimit = (int) $option[1]; - - break; - - case '--enforce-time-limit': - $enforceTimeLimit = true; - - break; - - case '--disallow-todo-tests': - $disallowTodoAnnotatedTests = true; - - break; - - case '--reverse-list': - $reverseList = true; - - break; - - case '--check-version': - $checkVersion = true; - - break; - - case '--coverage-filter': - case '--whitelist': - if ($coverageFilter === null) { - $coverageFilter = []; - } - - $coverageFilter[] = $option[1]; - - break; - - case '--random-order': - $executionOrder = TestSuiteSorter::ORDER_RANDOMIZED; - - break; - - case '--random-order-seed': - $randomOrderSeed = (int) $option[1]; - - break; - - case '--resolve-dependencies': - $resolveDependencies = true; - - break; - - case '--ignore-dependencies': - $resolveDependencies = false; - - break; - - case '--reverse-order': - $executionOrder = TestSuiteSorter::ORDER_REVERSED; - - break; - - case '--dump-xdebug-filter': - $xdebugFilterFile = $option[1]; - - break; - - default: - $unrecognizedOptions[str_replace('--', '', $option[0])] = $option[1]; - } - } - - if (empty($extensions)) { - $extensions = null; - } - - if (empty($unavailableExtensions)) { - $unavailableExtensions = null; - } - - if (empty($iniSettings)) { - $iniSettings = null; - } - - if (empty($coverageFilter)) { - $coverageFilter = null; - } - - return new Configuration( - $argument, - $atLeastVersion, - $backupGlobals, - $backupStaticAttributes, - $beStrictAboutChangesToGlobalState, - $beStrictAboutResourceUsageDuringSmallTests, - $bootstrap, - $cacheResult, - $cacheResultFile, - $checkVersion, - $colors, - $columns, - $configuration, - $coverageClover, - $coverageCrap4J, - $coverageHtml, - $coveragePhp, - $coverageText, - $coverageTextShowUncoveredFiles, - $coverageTextShowOnlySummary, - $coverageXml, - $debug, - $defaultTimeLimit, - $disableCodeCoverageIgnore, - $disallowTestOutput, - $disallowTodoAnnotatedTests, - $enforceTimeLimit, - $excludeGroups, - $executionOrder, - $executionOrderDefects, - $extensions, - $unavailableExtensions, - $failOnEmptyTestSuite, - $failOnIncomplete, - $failOnRisky, - $failOnSkipped, - $failOnWarning, - $filter, - $generateConfiguration, - $groups, - $help, - $includePath, - $iniSettings, - $junitLogfile, - $listGroups, - $listSuites, - $listTests, - $listTestsXml, - $loader, - $noCoverage, - $noExtensions, - $noInteraction, - $noLogging, - $printer, - $processIsolation, - $randomOrderSeed, - $repeat, - $reportUselessTests, - $resolveDependencies, - $reverseList, - $stderr, - $strictCoverage, - $stopOnDefect, - $stopOnError, - $stopOnFailure, - $stopOnIncomplete, - $stopOnRisky, - $stopOnSkipped, - $stopOnWarning, - $teamcityLogfile, - $testdoxExcludeGroups, - $testdoxGroups, - $testdoxHtmlFile, - $testdoxTextFile, - $testdoxXmlFile, - $testSuffixes, - $testSuite, - $unrecognizedOptions, - $unrecognizedOrderBy, - $useDefaultConfiguration, - $verbose, - $version, - $coverageFilter, - $xdebugFilterFile - ); - } -} diff --git a/src/TextUI/CliArguments/Configuration.php b/src/TextUI/CliArguments/Configuration.php deleted file mode 100644 index d9b0d9da4dd..00000000000 --- a/src/TextUI/CliArguments/Configuration.php +++ /dev/null @@ -1,1697 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\CliArguments; - -use PHPUnit\TextUI\XmlConfiguration\Extension; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Configuration -{ - /** - * @var ?string - */ - private $argument; - - /** - * @var ?string - */ - private $atLeastVersion; - - /** - * @var ?bool - */ - private $backupGlobals; - - /** - * @var ?bool - */ - private $backupStaticAttributes; - - /** - * @var ?bool - */ - private $beStrictAboutChangesToGlobalState; - - /** - * @var ?bool - */ - private $beStrictAboutResourceUsageDuringSmallTests; - - /** - * @var ?string - */ - private $bootstrap; - - /** - * @var ?bool - */ - private $cacheResult; - - /** - * @var ?string - */ - private $cacheResultFile; - - /** - * @var ?bool - */ - private $checkVersion; - - /** - * @var ?string - */ - private $colors; - - /** - * @var null|int|string - */ - private $columns; - - /** - * @var ?string - */ - private $configuration; - - /** - * @var null|string[] - */ - private $coverageFilter; - - /** - * @var ?string - */ - private $coverageClover; - - /** - * @var ?string - */ - private $coverageCrap4J; - - /** - * @var ?string - */ - private $coverageHtml; - - /** - * @var ?string - */ - private $coveragePhp; - - /** - * @var ?string - */ - private $coverageText; - - /** - * @var ?bool - */ - private $coverageTextShowUncoveredFiles; - - /** - * @var ?bool - */ - private $coverageTextShowOnlySummary; - - /** - * @var ?string - */ - private $coverageXml; - - /** - * @var ?bool - */ - private $debug; - - /** - * @var ?int - */ - private $defaultTimeLimit; - - /** - * @var ?bool - */ - private $disableCodeCoverageIgnore; - - /** - * @var ?bool - */ - private $disallowTestOutput; - - /** - * @var ?bool - */ - private $disallowTodoAnnotatedTests; - - /** - * @var ?bool - */ - private $enforceTimeLimit; - - /** - * @var null|string[] - */ - private $excludeGroups; - - /** - * @var ?int - */ - private $executionOrder; - - /** - * @var ?int - */ - private $executionOrderDefects; - - /** - * @var null|Extension[] - */ - private $extensions; - - /** - * @var null|string[] - */ - private $unavailableExtensions; - - /** - * @var ?bool - */ - private $failOnEmptyTestSuite; - - /** - * @var ?bool - */ - private $failOnIncomplete; - - /** - * @var ?bool - */ - private $failOnRisky; - - /** - * @var ?bool - */ - private $failOnSkipped; - - /** - * @var ?bool - */ - private $failOnWarning; - - /** - * @var ?string - */ - private $filter; - - /** - * @var ?bool - */ - private $generateConfiguration; - - /** - * @var null|string[] - */ - private $groups; - - /** - * @var ?bool - */ - private $help; - - /** - * @var ?string - */ - private $includePath; - - /** - * @var null|string[] - */ - private $iniSettings; - - /** - * @var ?string - */ - private $junitLogfile; - - /** - * @var ?bool - */ - private $listGroups; - - /** - * @var ?bool - */ - private $listSuites; - - /** - * @var ?bool - */ - private $listTests; - - /** - * @var ?string - */ - private $listTestsXml; - - /** - * @var ?string - */ - private $loader; - - /** - * @var ?bool - */ - private $noCoverage; - - /** - * @var ?bool - */ - private $noExtensions; - - /** - * @var ?bool - */ - private $noInteraction; - - /** - * @var ?bool - */ - private $noLogging; - - /** - * @var ?string - */ - private $printer; - - /** - * @var ?bool - */ - private $processIsolation; - - /** - * @var ?int - */ - private $randomOrderSeed; - - /** - * @var ?int - */ - private $repeat; - - /** - * @var ?bool - */ - private $reportUselessTests; - - /** - * @var ?bool - */ - private $resolveDependencies; - - /** - * @var ?bool - */ - private $reverseList; - - /** - * @var ?bool - */ - private $stderr; - - /** - * @var ?bool - */ - private $strictCoverage; - - /** - * @var ?bool - */ - private $stopOnDefect; - - /** - * @var ?bool - */ - private $stopOnError; - - /** - * @var ?bool - */ - private $stopOnFailure; - - /** - * @var ?bool - */ - private $stopOnIncomplete; - - /** - * @var ?bool - */ - private $stopOnRisky; - - /** - * @var ?bool - */ - private $stopOnSkipped; - - /** - * @var ?bool - */ - private $stopOnWarning; - - /** - * @var ?string - */ - private $teamcityLogfile; - - /** - * @var null|string[] - */ - private $testdoxExcludeGroups; - - /** - * @var null|string[] - */ - private $testdoxGroups; - - /** - * @var ?string - */ - private $testdoxHtmlFile; - - /** - * @var ?string - */ - private $testdoxTextFile; - - /** - * @var ?string - */ - private $testdoxXmlFile; - - /** - * @var null|string[] - */ - private $testSuffixes; - - /** - * @var ?string - */ - private $testSuite; - - /** - * @var string[] - */ - private $unrecognizedOptions; - - /** - * @var ?string - */ - private $unrecognizedOrderBy; - - /** - * @var ?bool - */ - private $useDefaultConfiguration; - - /** - * @var ?bool - */ - private $verbose; - - /** - * @var ?bool - */ - private $version; - - /** - * @var ?string - */ - private $xdebugFilterFile; - - /** - * @param null|int|string $columns - */ - public function __construct(?string $argument, ?string $atLeastVersion, ?bool $backupGlobals, ?bool $backupStaticAttributes, ?bool $beStrictAboutChangesToGlobalState, ?bool $beStrictAboutResourceUsageDuringSmallTests, ?string $bootstrap, ?bool $cacheResult, ?string $cacheResultFile, ?bool $checkVersion, ?string $colors, $columns, ?string $configuration, ?string $coverageClover, ?string $coverageCrap4J, ?string $coverageHtml, ?string $coveragePhp, ?string $coverageText, ?bool $coverageTextShowUncoveredFiles, ?bool $coverageTextShowOnlySummary, ?string $coverageXml, ?bool $debug, ?int $defaultTimeLimit, ?bool $disableCodeCoverageIgnore, ?bool $disallowTestOutput, ?bool $disallowTodoAnnotatedTests, ?bool $enforceTimeLimit, ?array $excludeGroups, ?int $executionOrder, ?int $executionOrderDefects, ?array $extensions, ?array $unavailableExtensions, ?bool $failOnEmptyTestSuite, ?bool $failOnIncomplete, ?bool $failOnRisky, ?bool $failOnSkipped, ?bool $failOnWarning, ?string $filter, ?bool $generateConfiguration, ?array $groups, ?bool $help, ?string $includePath, ?array $iniSettings, ?string $junitLogfile, ?bool $listGroups, ?bool $listSuites, ?bool $listTests, ?string $listTestsXml, ?string $loader, ?bool $noCoverage, ?bool $noExtensions, ?bool $noInteraction, ?bool $noLogging, ?string $printer, ?bool $processIsolation, ?int $randomOrderSeed, ?int $repeat, ?bool $reportUselessTests, ?bool $resolveDependencies, ?bool $reverseList, ?bool $stderr, ?bool $strictCoverage, ?bool $stopOnDefect, ?bool $stopOnError, ?bool $stopOnFailure, ?bool $stopOnIncomplete, ?bool $stopOnRisky, ?bool $stopOnSkipped, ?bool $stopOnWarning, ?string $teamcityLogfile, ?array $testdoxExcludeGroups, ?array $testdoxGroups, ?string $testdoxHtmlFile, ?string $testdoxTextFile, ?string $testdoxXmlFile, ?array $testSuffixes, ?string $testSuite, array $unrecognizedOptions, ?string $unrecognizedOrderBy, ?bool $useDefaultConfiguration, ?bool $verbose, ?bool $version, ?array $coverageFilter, ?string $xdebugFilterFile) - { - $this->argument = $argument; - $this->atLeastVersion = $atLeastVersion; - $this->backupGlobals = $backupGlobals; - $this->backupStaticAttributes = $backupStaticAttributes; - $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; - $this->beStrictAboutResourceUsageDuringSmallTests = $beStrictAboutResourceUsageDuringSmallTests; - $this->bootstrap = $bootstrap; - $this->cacheResult = $cacheResult; - $this->cacheResultFile = $cacheResultFile; - $this->checkVersion = $checkVersion; - $this->colors = $colors; - $this->columns = $columns; - $this->configuration = $configuration; - $this->coverageFilter = $coverageFilter; - $this->coverageClover = $coverageClover; - $this->coverageCrap4J = $coverageCrap4J; - $this->coverageHtml = $coverageHtml; - $this->coveragePhp = $coveragePhp; - $this->coverageText = $coverageText; - $this->coverageTextShowUncoveredFiles = $coverageTextShowUncoveredFiles; - $this->coverageTextShowOnlySummary = $coverageTextShowOnlySummary; - $this->coverageXml = $coverageXml; - $this->debug = $debug; - $this->defaultTimeLimit = $defaultTimeLimit; - $this->disableCodeCoverageIgnore = $disableCodeCoverageIgnore; - $this->disallowTestOutput = $disallowTestOutput; - $this->disallowTodoAnnotatedTests = $disallowTodoAnnotatedTests; - $this->enforceTimeLimit = $enforceTimeLimit; - $this->excludeGroups = $excludeGroups; - $this->executionOrder = $executionOrder; - $this->executionOrderDefects = $executionOrderDefects; - $this->extensions = $extensions; - $this->unavailableExtensions = $unavailableExtensions; - $this->failOnEmptyTestSuite = $failOnEmptyTestSuite; - $this->failOnIncomplete = $failOnIncomplete; - $this->failOnRisky = $failOnRisky; - $this->failOnSkipped = $failOnSkipped; - $this->failOnWarning = $failOnWarning; - $this->filter = $filter; - $this->generateConfiguration = $generateConfiguration; - $this->groups = $groups; - $this->help = $help; - $this->includePath = $includePath; - $this->iniSettings = $iniSettings; - $this->junitLogfile = $junitLogfile; - $this->listGroups = $listGroups; - $this->listSuites = $listSuites; - $this->listTests = $listTests; - $this->listTestsXml = $listTestsXml; - $this->loader = $loader; - $this->noCoverage = $noCoverage; - $this->noExtensions = $noExtensions; - $this->noInteraction = $noInteraction; - $this->noLogging = $noLogging; - $this->printer = $printer; - $this->processIsolation = $processIsolation; - $this->randomOrderSeed = $randomOrderSeed; - $this->repeat = $repeat; - $this->reportUselessTests = $reportUselessTests; - $this->resolveDependencies = $resolveDependencies; - $this->reverseList = $reverseList; - $this->stderr = $stderr; - $this->strictCoverage = $strictCoverage; - $this->stopOnDefect = $stopOnDefect; - $this->stopOnError = $stopOnError; - $this->stopOnFailure = $stopOnFailure; - $this->stopOnIncomplete = $stopOnIncomplete; - $this->stopOnRisky = $stopOnRisky; - $this->stopOnSkipped = $stopOnSkipped; - $this->stopOnWarning = $stopOnWarning; - $this->teamcityLogfile = $teamcityLogfile; - $this->testdoxExcludeGroups = $testdoxExcludeGroups; - $this->testdoxGroups = $testdoxGroups; - $this->testdoxHtmlFile = $testdoxHtmlFile; - $this->testdoxTextFile = $testdoxTextFile; - $this->testdoxXmlFile = $testdoxXmlFile; - $this->testSuffixes = $testSuffixes; - $this->testSuite = $testSuite; - $this->unrecognizedOptions = $unrecognizedOptions; - $this->unrecognizedOrderBy = $unrecognizedOrderBy; - $this->useDefaultConfiguration = $useDefaultConfiguration; - $this->verbose = $verbose; - $this->version = $version; - $this->xdebugFilterFile = $xdebugFilterFile; - } - - public function hasArgument(): bool - { - return $this->argument !== null; - } - - public function argument(): string - { - if ($this->argument === null) { - throw new Exception; - } - - return $this->argument; - } - - public function hasAtLeastVersion(): bool - { - return $this->atLeastVersion !== null; - } - - public function atLeastVersion(): string - { - if ($this->atLeastVersion === null) { - throw new Exception; - } - - return $this->atLeastVersion; - } - - public function hasBackupGlobals(): bool - { - return $this->backupGlobals !== null; - } - - public function backupGlobals(): bool - { - if ($this->backupGlobals === null) { - throw new Exception; - } - - return $this->backupGlobals; - } - - public function hasBackupStaticAttributes(): bool - { - return $this->backupStaticAttributes !== null; - } - - public function backupStaticAttributes(): bool - { - if ($this->backupStaticAttributes === null) { - throw new Exception; - } - - return $this->backupStaticAttributes; - } - - public function hasBeStrictAboutChangesToGlobalState(): bool - { - return $this->beStrictAboutChangesToGlobalState !== null; - } - - public function beStrictAboutChangesToGlobalState(): bool - { - if ($this->beStrictAboutChangesToGlobalState === null) { - throw new Exception; - } - - return $this->beStrictAboutChangesToGlobalState; - } - - public function hasBeStrictAboutResourceUsageDuringSmallTests(): bool - { - return $this->beStrictAboutResourceUsageDuringSmallTests !== null; - } - - public function beStrictAboutResourceUsageDuringSmallTests(): bool - { - if ($this->beStrictAboutResourceUsageDuringSmallTests === null) { - throw new Exception; - } - - return $this->beStrictAboutResourceUsageDuringSmallTests; - } - - public function hasBootstrap(): bool - { - return $this->bootstrap !== null; - } - - public function bootstrap(): string - { - if ($this->bootstrap === null) { - throw new Exception; - } - - return $this->bootstrap; - } - - public function hasCacheResult(): bool - { - return $this->cacheResult !== null; - } - - public function cacheResult(): bool - { - if ($this->cacheResult === null) { - throw new Exception; - } - - return $this->cacheResult; - } - - public function hasCacheResultFile(): bool - { - return $this->cacheResultFile !== null; - } - - public function cacheResultFile(): string - { - if ($this->cacheResultFile === null) { - throw new Exception; - } - - return $this->cacheResultFile; - } - - public function hasCheckVersion(): bool - { - return $this->checkVersion !== null; - } - - public function checkVersion(): bool - { - if ($this->checkVersion === null) { - throw new Exception; - } - - return $this->checkVersion; - } - - public function hasColors(): bool - { - return $this->colors !== null; - } - - public function colors(): string - { - if ($this->colors === null) { - throw new Exception; - } - - return $this->colors; - } - - public function hasColumns(): bool - { - return $this->columns !== null; - } - - public function columns() - { - if ($this->columns === null) { - throw new Exception; - } - - return $this->columns; - } - - public function hasConfiguration(): bool - { - return $this->configuration !== null; - } - - public function configuration(): string - { - if ($this->configuration === null) { - throw new Exception; - } - - return $this->configuration; - } - - public function hasCoverageFilter(): bool - { - return $this->coverageFilter !== null; - } - - public function coverageFilter(): array - { - if ($this->coverageFilter === null) { - throw new Exception; - } - - return $this->coverageFilter; - } - - public function hasCoverageClover(): bool - { - return $this->coverageClover !== null; - } - - public function coverageClover(): string - { - if ($this->coverageClover === null) { - throw new Exception; - } - - return $this->coverageClover; - } - - public function hasCoverageCrap4J(): bool - { - return $this->coverageCrap4J !== null; - } - - public function coverageCrap4J(): string - { - if ($this->coverageCrap4J === null) { - throw new Exception; - } - - return $this->coverageCrap4J; - } - - public function hasCoverageHtml(): bool - { - return $this->coverageHtml !== null; - } - - public function coverageHtml(): string - { - if ($this->coverageHtml === null) { - throw new Exception; - } - - return $this->coverageHtml; - } - - public function hasCoveragePhp(): bool - { - return $this->coveragePhp !== null; - } - - public function coveragePhp(): string - { - if ($this->coveragePhp === null) { - throw new Exception; - } - - return $this->coveragePhp; - } - - public function hasCoverageText(): bool - { - return $this->coverageText !== null; - } - - public function coverageText(): string - { - if ($this->coverageText === null) { - throw new Exception; - } - - return $this->coverageText; - } - - public function hasCoverageTextShowUncoveredFiles(): bool - { - return $this->coverageTextShowUncoveredFiles !== null; - } - - public function coverageTextShowUncoveredFiles(): bool - { - if ($this->coverageTextShowUncoveredFiles === null) { - throw new Exception; - } - - return $this->coverageTextShowUncoveredFiles; - } - - public function hasCoverageTextShowOnlySummary(): bool - { - return $this->coverageTextShowOnlySummary !== null; - } - - public function coverageTextShowOnlySummary(): bool - { - if ($this->coverageTextShowOnlySummary === null) { - throw new Exception; - } - - return $this->coverageTextShowOnlySummary; - } - - public function hasCoverageXml(): bool - { - return $this->coverageXml !== null; - } - - public function coverageXml(): string - { - if ($this->coverageXml === null) { - throw new Exception; - } - - return $this->coverageXml; - } - - public function hasDebug(): bool - { - return $this->debug !== null; - } - - public function debug(): bool - { - if ($this->debug === null) { - throw new Exception; - } - - return $this->debug; - } - - public function hasDefaultTimeLimit(): bool - { - return $this->defaultTimeLimit !== null; - } - - public function defaultTimeLimit(): int - { - if ($this->defaultTimeLimit === null) { - throw new Exception; - } - - return $this->defaultTimeLimit; - } - - public function hasDisableCodeCoverageIgnore(): bool - { - return $this->disableCodeCoverageIgnore !== null; - } - - public function disableCodeCoverageIgnore(): bool - { - if ($this->disableCodeCoverageIgnore === null) { - throw new Exception; - } - - return $this->disableCodeCoverageIgnore; - } - - public function hasDisallowTestOutput(): bool - { - return $this->disallowTestOutput !== null; - } - - public function disallowTestOutput(): bool - { - if ($this->disallowTestOutput === null) { - throw new Exception; - } - - return $this->disallowTestOutput; - } - - public function hasDisallowTodoAnnotatedTests(): bool - { - return $this->disallowTodoAnnotatedTests !== null; - } - - public function disallowTodoAnnotatedTests(): bool - { - if ($this->disallowTodoAnnotatedTests === null) { - throw new Exception; - } - - return $this->disallowTodoAnnotatedTests; - } - - public function hasEnforceTimeLimit(): bool - { - return $this->enforceTimeLimit !== null; - } - - public function enforceTimeLimit(): bool - { - if ($this->enforceTimeLimit === null) { - throw new Exception; - } - - return $this->enforceTimeLimit; - } - - public function hasExcludeGroups(): bool - { - return $this->excludeGroups !== null; - } - - public function excludeGroups(): array - { - if ($this->excludeGroups === null) { - throw new Exception; - } - - return $this->excludeGroups; - } - - public function hasExecutionOrder(): bool - { - return $this->executionOrder !== null; - } - - public function executionOrder(): int - { - if ($this->executionOrder === null) { - throw new Exception; - } - - return $this->executionOrder; - } - - public function hasExecutionOrderDefects(): bool - { - return $this->executionOrderDefects !== null; - } - - public function executionOrderDefects(): int - { - if ($this->executionOrderDefects === null) { - throw new Exception; - } - - return $this->executionOrderDefects; - } - - public function hasFailOnEmptyTestSuite(): bool - { - return $this->failOnEmptyTestSuite !== null; - } - - public function failOnEmptyTestSuite(): bool - { - if ($this->failOnEmptyTestSuite === null) { - throw new Exception; - } - - return $this->failOnEmptyTestSuite; - } - - public function hasFailOnIncomplete(): bool - { - return $this->failOnIncomplete !== null; - } - - public function failOnIncomplete(): bool - { - if ($this->failOnIncomplete === null) { - throw new Exception; - } - - return $this->failOnIncomplete; - } - - public function hasFailOnRisky(): bool - { - return $this->failOnRisky !== null; - } - - public function failOnRisky(): bool - { - if ($this->failOnRisky === null) { - throw new Exception; - } - - return $this->failOnRisky; - } - - public function hasFailOnSkipped(): bool - { - return $this->failOnSkipped !== null; - } - - public function failOnSkipped(): bool - { - if ($this->failOnSkipped === null) { - throw new Exception; - } - - return $this->failOnSkipped; - } - - public function hasFailOnWarning(): bool - { - return $this->failOnWarning !== null; - } - - public function failOnWarning(): bool - { - if ($this->failOnWarning === null) { - throw new Exception; - } - - return $this->failOnWarning; - } - - public function hasFilter(): bool - { - return $this->filter !== null; - } - - public function filter(): string - { - if ($this->filter === null) { - throw new Exception; - } - - return $this->filter; - } - - public function hasGenerateConfiguration(): bool - { - return $this->generateConfiguration !== null; - } - - public function generateConfiguration(): bool - { - if ($this->generateConfiguration === null) { - throw new Exception; - } - - return $this->generateConfiguration; - } - - public function hasGroups(): bool - { - return $this->groups !== null; - } - - public function groups(): array - { - if ($this->groups === null) { - throw new Exception; - } - - return $this->groups; - } - - public function hasHelp(): bool - { - return $this->help !== null; - } - - public function help(): bool - { - if ($this->help === null) { - throw new Exception; - } - - return $this->help; - } - - public function hasIncludePath(): bool - { - return $this->includePath !== null; - } - - public function includePath(): string - { - if ($this->includePath === null) { - throw new Exception; - } - - return $this->includePath; - } - - public function hasIniSettings(): bool - { - return $this->iniSettings !== null; - } - - public function iniSettings(): array - { - if ($this->iniSettings === null) { - throw new Exception; - } - - return $this->iniSettings; - } - - public function hasJunitLogfile(): bool - { - return $this->junitLogfile !== null; - } - - public function junitLogfile(): string - { - if ($this->junitLogfile === null) { - throw new Exception; - } - - return $this->junitLogfile; - } - - public function hasListGroups(): bool - { - return $this->listGroups !== null; - } - - public function listGroups(): bool - { - if ($this->listGroups === null) { - throw new Exception; - } - - return $this->listGroups; - } - - public function hasListSuites(): bool - { - return $this->listSuites !== null; - } - - public function listSuites(): bool - { - if ($this->listSuites === null) { - throw new Exception; - } - - return $this->listSuites; - } - - public function hasListTests(): bool - { - return $this->listTests !== null; - } - - public function listTests(): bool - { - if ($this->listTests === null) { - throw new Exception; - } - - return $this->listTests; - } - - public function hasListTestsXml(): bool - { - return $this->listTestsXml !== null; - } - - public function listTestsXml(): string - { - if ($this->listTestsXml === null) { - throw new Exception; - } - - return $this->listTestsXml; - } - - public function hasLoader(): bool - { - return $this->loader !== null; - } - - public function loader(): string - { - if ($this->loader === null) { - throw new Exception; - } - - return $this->loader; - } - - public function hasNoCoverage(): bool - { - return $this->noCoverage !== null; - } - - public function noCoverage(): bool - { - if ($this->noCoverage === null) { - throw new Exception; - } - - return $this->noCoverage; - } - - public function hasNoExtensions(): bool - { - return $this->noExtensions !== null; - } - - public function noExtensions(): bool - { - if ($this->noExtensions === null) { - throw new Exception; - } - - return $this->noExtensions; - } - - public function hasExtensions(): bool - { - return $this->extensions !== null; - } - - public function extensions(): array - { - if ($this->extensions === null) { - throw new Exception; - } - - return $this->extensions; - } - - public function hasUnavailableExtensions(): bool - { - return $this->unavailableExtensions !== null; - } - - public function unavailableExtensions(): array - { - if ($this->unavailableExtensions === null) { - throw new Exception; - } - - return $this->unavailableExtensions; - } - - public function hasNoInteraction(): bool - { - return $this->noInteraction !== null; - } - - public function noInteraction(): bool - { - if ($this->noInteraction === null) { - throw new Exception; - } - - return $this->noInteraction; - } - - public function hasNoLogging(): bool - { - return $this->noLogging !== null; - } - - public function noLogging(): bool - { - if ($this->noLogging === null) { - throw new Exception; - } - - return $this->noLogging; - } - - public function hasPrinter(): bool - { - return $this->printer !== null; - } - - public function printer(): string - { - if ($this->printer === null) { - throw new Exception; - } - - return $this->printer; - } - - public function hasProcessIsolation(): bool - { - return $this->processIsolation !== null; - } - - public function processIsolation(): bool - { - if ($this->processIsolation === null) { - throw new Exception; - } - - return $this->processIsolation; - } - - public function hasRandomOrderSeer(): bool - { - return $this->randomOrderSeed !== null; - } - - public function randomOrderSeed(): int - { - if ($this->randomOrderSeed === null) { - throw new Exception; - } - - return $this->randomOrderSeed; - } - - public function hasRepeat(): bool - { - return $this->repeat !== null; - } - - public function repeat(): int - { - if ($this->repeat === null) { - throw new Exception; - } - - return $this->repeat; - } - - public function hasReportUselessTests(): bool - { - return $this->reportUselessTests !== null; - } - - public function reportUselessTests(): bool - { - if ($this->reportUselessTests === null) { - throw new Exception; - } - - return $this->reportUselessTests; - } - - public function hasResolveDependencies(): bool - { - return $this->resolveDependencies !== null; - } - - public function resolveDependencies(): bool - { - if ($this->resolveDependencies === null) { - throw new Exception; - } - - return $this->resolveDependencies; - } - - public function hasReverseList(): bool - { - return $this->reverseList !== null; - } - - public function reverseList(): bool - { - if ($this->reverseList === null) { - throw new Exception; - } - - return $this->reverseList; - } - - public function hasStderr(): bool - { - return $this->stderr !== null; - } - - public function stderr(): bool - { - if ($this->stderr === null) { - throw new Exception; - } - - return $this->stderr; - } - - public function hasStrictCoverage(): bool - { - return $this->strictCoverage !== null; - } - - public function strictCoverage(): bool - { - if ($this->strictCoverage === null) { - throw new Exception; - } - - return $this->strictCoverage; - } - - public function hasStopOnDefect(): bool - { - return $this->stopOnDefect !== null; - } - - public function stopOnDefect(): bool - { - if ($this->stopOnDefect === null) { - throw new Exception; - } - - return $this->stopOnDefect; - } - - public function hasStopOnError(): bool - { - return $this->stopOnError !== null; - } - - public function stopOnError(): bool - { - if ($this->stopOnError === null) { - throw new Exception; - } - - return $this->stopOnError; - } - - public function hasStopOnFailure(): bool - { - return $this->stopOnFailure !== null; - } - - public function stopOnFailure(): bool - { - if ($this->stopOnFailure === null) { - throw new Exception; - } - - return $this->stopOnFailure; - } - - public function hasStopOnIncomplete(): bool - { - return $this->stopOnIncomplete !== null; - } - - public function stopOnIncomplete(): bool - { - if ($this->stopOnIncomplete === null) { - throw new Exception; - } - - return $this->stopOnIncomplete; - } - - public function hasStopOnRisky(): bool - { - return $this->stopOnRisky !== null; - } - - public function stopOnRisky(): bool - { - if ($this->stopOnRisky === null) { - throw new Exception; - } - - return $this->stopOnRisky; - } - - public function hasStopOnSkipped(): bool - { - return $this->stopOnSkipped !== null; - } - - public function stopOnSkipped(): bool - { - if ($this->stopOnSkipped === null) { - throw new Exception; - } - - return $this->stopOnSkipped; - } - - public function hasStopOnWarning(): bool - { - return $this->stopOnWarning !== null; - } - - public function stopOnWarning(): bool - { - if ($this->stopOnWarning === null) { - throw new Exception; - } - - return $this->stopOnWarning; - } - - public function hasTeamcityLogfile(): bool - { - return $this->teamcityLogfile !== null; - } - - public function teamcityLogfile(): string - { - if ($this->teamcityLogfile === null) { - throw new Exception; - } - - return $this->teamcityLogfile; - } - - public function hasTestdoxExcludeGroups(): bool - { - return $this->testdoxExcludeGroups !== null; - } - - public function testdoxExcludeGroups(): array - { - if ($this->testdoxExcludeGroups === null) { - throw new Exception; - } - - return $this->testdoxExcludeGroups; - } - - public function hasTestdoxGroups(): bool - { - return $this->testdoxGroups !== null; - } - - public function testdoxGroups(): array - { - if ($this->testdoxGroups === null) { - throw new Exception; - } - - return $this->testdoxGroups; - } - - public function hasTestdoxHtmlFile(): bool - { - return $this->testdoxHtmlFile !== null; - } - - public function testdoxHtmlFile(): string - { - if ($this->testdoxHtmlFile === null) { - throw new Exception; - } - - return $this->testdoxHtmlFile; - } - - public function hasTestdoxTextFile(): bool - { - return $this->testdoxTextFile !== null; - } - - public function testdoxTextFile(): string - { - if ($this->testdoxTextFile === null) { - throw new Exception; - } - - return $this->testdoxTextFile; - } - - public function hasTestdoxXmlFile(): bool - { - return $this->testdoxXmlFile !== null; - } - - public function testdoxXmlFile(): string - { - if ($this->testdoxXmlFile === null) { - throw new Exception; - } - - return $this->testdoxXmlFile; - } - - public function hasTestSuffixes(): bool - { - return $this->testSuffixes !== null; - } - - public function testSuffixes(): array - { - if ($this->testSuffixes === null) { - throw new Exception; - } - - return $this->testSuffixes; - } - - public function hasTestSuite(): bool - { - return $this->testSuite !== null; - } - - public function testSuite(): string - { - if ($this->testSuite === null) { - throw new Exception; - } - - return $this->testSuite; - } - - public function unrecognizedOptions(): array - { - return $this->unrecognizedOptions; - } - - public function hasUnrecognizedOrderBy(): bool - { - return $this->unrecognizedOrderBy !== null; - } - - public function unrecognizedOrderBy(): string - { - if ($this->unrecognizedOrderBy === null) { - throw new Exception; - } - - return $this->unrecognizedOrderBy; - } - - public function hasUseDefaultConfiguration(): bool - { - return $this->useDefaultConfiguration !== null; - } - - public function useDefaultConfiguration(): bool - { - if ($this->useDefaultConfiguration === null) { - throw new Exception; - } - - return $this->useDefaultConfiguration; - } - - public function hasVerbose(): bool - { - return $this->verbose !== null; - } - - public function verbose(): bool - { - if ($this->verbose === null) { - throw new Exception; - } - - return $this->verbose; - } - - public function hasVersion(): bool - { - return $this->version !== null; - } - - public function version(): bool - { - if ($this->version === null) { - throw new Exception; - } - - return $this->version; - } - - public function hasXdebugFilterFile(): bool - { - return $this->xdebugFilterFile !== null; - } - - public function xdebugFilterFile(): string - { - if ($this->xdebugFilterFile === null) { - throw new Exception; - } - - return $this->xdebugFilterFile; - } -} diff --git a/src/TextUI/CliArguments/Exception.php b/src/TextUI/CliArguments/Exception.php deleted file mode 100644 index dd5536eaa50..00000000000 --- a/src/TextUI/CliArguments/Exception.php +++ /dev/null @@ -1,19 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\CliArguments; - -use RuntimeException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Exception extends RuntimeException implements \PHPUnit\Exception -{ -} diff --git a/src/TextUI/CliArguments/Mapper.php b/src/TextUI/CliArguments/Mapper.php deleted file mode 100644 index 4242ed129b2..00000000000 --- a/src/TextUI/CliArguments/Mapper.php +++ /dev/null @@ -1,338 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\CliArguments; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Mapper -{ - public function mapToLegacyArray(Configuration $arguments): array - { - $result = [ - 'extensions' => [], - 'listGroups' => false, - 'listSuites' => false, - 'listTests' => false, - 'listTestsXml' => false, - 'loader' => null, - 'useDefaultConfiguration' => true, - 'loadedExtensions' => [], - 'unavailableExtensions' => [], - 'notLoadedExtensions' => [], - ]; - - if ($arguments->hasColors()) { - $result['colors'] = $arguments->colors(); - } - - if ($arguments->hasBootstrap()) { - $result['bootstrap'] = $arguments->bootstrap(); - } - - if ($arguments->hasCacheResult()) { - $result['cacheResult'] = $arguments->cacheResult(); - } - - if ($arguments->hasCacheResultFile()) { - $result['cacheResultFile'] = $arguments->cacheResultFile(); - } - - if ($arguments->hasColumns()) { - $result['columns'] = $arguments->columns(); - } - - if ($arguments->hasConfiguration()) { - $result['configuration'] = $arguments->configuration(); - } - - if ($arguments->hasCoverageClover()) { - $result['coverageClover'] = $arguments->coverageClover(); - } - - if ($arguments->hasCoverageCrap4J()) { - $result['coverageCrap4J'] = $arguments->coverageCrap4J(); - } - - if ($arguments->hasCoverageHtml()) { - $result['coverageHtml'] = $arguments->coverageHtml(); - } - - if ($arguments->hasCoveragePhp()) { - $result['coveragePHP'] = $arguments->coveragePhp(); - } - - if ($arguments->hasCoverageText()) { - $result['coverageText'] = $arguments->coverageText(); - } - - if ($arguments->hasCoverageTextShowUncoveredFiles()) { - $result['coverageTextShowUncoveredFiles'] = $arguments->hasCoverageTextShowUncoveredFiles(); - } - - if ($arguments->hasCoverageTextShowOnlySummary()) { - $result['coverageTextShowOnlySummary'] = $arguments->coverageTextShowOnlySummary(); - } - - if ($arguments->hasCoverageXml()) { - $result['coverageXml'] = $arguments->coverageXml(); - } - - if ($arguments->hasDebug()) { - $result['debug'] = $arguments->debug(); - } - - if ($arguments->hasHelp()) { - $result['help'] = $arguments->help(); - } - - if ($arguments->hasFilter()) { - $result['filter'] = $arguments->filter(); - } - - if ($arguments->hasTestSuite()) { - $result['testsuite'] = $arguments->testSuite(); - } - - if ($arguments->hasGroups()) { - $result['groups'] = $arguments->groups(); - } - - if ($arguments->hasExcludeGroups()) { - $result['excludeGroups'] = $arguments->excludeGroups(); - } - - if ($arguments->hasTestSuffixes()) { - $result['testSuffixes'] = $arguments->testSuffixes(); - } - - if ($arguments->hasIncludePath()) { - $result['includePath'] = $arguments->includePath(); - } - - if ($arguments->hasListGroups()) { - $result['listGroups'] = $arguments->listGroups(); - } - - if ($arguments->hasListSuites()) { - $result['listSuites'] = $arguments->listSuites(); - } - - if ($arguments->hasListTests()) { - $result['listTests'] = $arguments->listTests(); - } - - if ($arguments->hasListTestsXml()) { - $result['listTestsXml'] = $arguments->listTestsXml(); - } - - if ($arguments->hasPrinter()) { - $result['printer'] = $arguments->printer(); - } - - if ($arguments->hasLoader()) { - $result['loader'] = $arguments->loader(); - } - - if ($arguments->hasJunitLogfile()) { - $result['junitLogfile'] = $arguments->junitLogfile(); - } - - if ($arguments->hasTeamcityLogfile()) { - $result['teamcityLogfile'] = $arguments->teamcityLogfile(); - } - - if ($arguments->hasExecutionOrder()) { - $result['executionOrder'] = $arguments->executionOrder(); - } - - if ($arguments->hasExecutionOrderDefects()) { - $result['executionOrderDefects'] = $arguments->executionOrderDefects(); - } - - if ($arguments->hasExtensions()) { - $result['extensions'] = $arguments->extensions(); - } - - if ($arguments->hasUnavailableExtensions()) { - $result['unavailableExtensions'] = $arguments->unavailableExtensions(); - } - - if ($arguments->hasResolveDependencies()) { - $result['resolveDependencies'] = $arguments->resolveDependencies(); - } - - if ($arguments->hasProcessIsolation()) { - $result['processIsolation'] = $arguments->processIsolation(); - } - - if ($arguments->hasRepeat()) { - $result['repeat'] = $arguments->repeat(); - } - - if ($arguments->hasStderr()) { - $result['stderr'] = $arguments->stderr(); - } - - if ($arguments->hasStopOnDefect()) { - $result['stopOnDefect'] = $arguments->stopOnDefect(); - } - - if ($arguments->hasStopOnError()) { - $result['stopOnError'] = $arguments->stopOnError(); - } - - if ($arguments->hasStopOnFailure()) { - $result['stopOnFailure'] = $arguments->stopOnFailure(); - } - - if ($arguments->hasStopOnWarning()) { - $result['stopOnWarning'] = $arguments->stopOnWarning(); - } - - if ($arguments->hasStopOnIncomplete()) { - $result['stopOnIncomplete'] = $arguments->stopOnIncomplete(); - } - - if ($arguments->hasStopOnRisky()) { - $result['stopOnRisky'] = $arguments->stopOnRisky(); - } - - if ($arguments->hasStopOnSkipped()) { - $result['stopOnSkipped'] = $arguments->stopOnSkipped(); - } - - if ($arguments->hasFailOnEmptyTestSuite()) { - $result['failOnEmptyTestSuite'] = $arguments->failOnEmptyTestSuite(); - } - - if ($arguments->hasFailOnIncomplete()) { - $result['failOnIncomplete'] = $arguments->failOnIncomplete(); - } - - if ($arguments->hasFailOnRisky()) { - $result['failOnRisky'] = $arguments->failOnRisky(); - } - - if ($arguments->hasFailOnSkipped()) { - $result['failOnSkipped'] = $arguments->failOnSkipped(); - } - - if ($arguments->hasFailOnWarning()) { - $result['failOnWarning'] = $arguments->failOnWarning(); - } - - if ($arguments->hasTestdoxGroups()) { - $result['testdoxGroups'] = $arguments->testdoxGroups(); - } - - if ($arguments->hasTestdoxExcludeGroups()) { - $result['testdoxExcludeGroups'] = $arguments->testdoxExcludeGroups(); - } - - if ($arguments->hasTestdoxHtmlFile()) { - $result['testdoxHTMLFile'] = $arguments->testdoxHtmlFile(); - } - - if ($arguments->hasTestdoxTextFile()) { - $result['testdoxTextFile'] = $arguments->testdoxTextFile(); - } - - if ($arguments->hasTestdoxXmlFile()) { - $result['testdoxXMLFile'] = $arguments->testdoxXmlFile(); - } - - if ($arguments->hasUseDefaultConfiguration()) { - $result['useDefaultConfiguration'] = $arguments->useDefaultConfiguration(); - } - - if ($arguments->hasNoExtensions()) { - $result['noExtensions'] = $arguments->noExtensions(); - } - - if ($arguments->hasNoCoverage()) { - $result['noCoverage'] = $arguments->noCoverage(); - } - - if ($arguments->hasNoLogging()) { - $result['notLogging'] = $arguments->noLogging(); - } - - if ($arguments->hasNoInteraction()) { - $result['noInteraction'] = $arguments->noInteraction(); - } - - if ($arguments->hasBackupGlobals()) { - $result['backupGlobals'] = $arguments->backupGlobals(); - } - - if ($arguments->hasBackupStaticAttributes()) { - $result['backupStaticAttributes'] = $arguments->backupStaticAttributes(); - } - - if ($arguments->hasVerbose()) { - $result['verbose'] = $arguments->verbose(); - } - - if ($arguments->hasReportUselessTests()) { - $result['reportUselessTests'] = $arguments->reportUselessTests(); - } - - if ($arguments->hasStrictCoverage()) { - $result['strictCoverage'] = $arguments->strictCoverage(); - } - - if ($arguments->hasDisableCodeCoverageIgnore()) { - $result['disableCodeCoverageIgnore'] = $arguments->disableCodeCoverageIgnore(); - } - - if ($arguments->hasBeStrictAboutChangesToGlobalState()) { - $result['beStrictAboutChangesToGlobalState'] = $arguments->beStrictAboutChangesToGlobalState(); - } - - if ($arguments->hasDisallowTestOutput()) { - $result['disallowTestOutput'] = $arguments->disallowTestOutput(); - } - - if ($arguments->hasBeStrictAboutResourceUsageDuringSmallTests()) { - $result['beStrictAboutResourceUsageDuringSmallTests'] = $arguments->beStrictAboutResourceUsageDuringSmallTests(); - } - - if ($arguments->hasDefaultTimeLimit()) { - $result['defaultTimeLimit'] = $arguments->defaultTimeLimit(); - } - - if ($arguments->hasEnforceTimeLimit()) { - $result['enforceTimeLimit'] = $arguments->enforceTimeLimit(); - } - - if ($arguments->hasDisallowTodoAnnotatedTests()) { - $result['disallowTodoAnnotatedTests'] = $arguments->disallowTodoAnnotatedTests(); - } - - if ($arguments->hasReverseList()) { - $result['reverseList'] = $arguments->reverseList(); - } - - if ($arguments->hasCoverageFilter()) { - $result['coverageFilter'] = $arguments->coverageFilter(); - } - - if ($arguments->hasRandomOrderSeer()) { - $result['randomOrderSeed'] = $arguments->randomOrderSeed(); - } - - if ($arguments->hasXdebugFilterFile()) { - $result['xdebugFilterFile'] = $arguments->xdebugFilterFile(); - } - - return $result; - } -} diff --git a/src/TextUI/Command.php b/src/TextUI/Command.php deleted file mode 100644 index 8a19c66f40d..00000000000 --- a/src/TextUI/Command.php +++ /dev/null @@ -1,778 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI; - -use const PATH_SEPARATOR; -use const PHP_EOL; -use const STDIN; -use function array_keys; -use function assert; -use function class_exists; -use function extension_loaded; -use function fgets; -use function file_exists; -use function file_get_contents; -use function file_put_contents; -use function getcwd; -use function ini_get; -use function ini_set; -use function is_callable; -use function is_dir; -use function is_string; -use function printf; -use function realpath; -use function sort; -use function sprintf; -use function stream_resolve_include_path; -use function trim; -use function version_compare; -use PharIo\Manifest\ApplicationName; -use PharIo\Manifest\Exception as ManifestException; -use PharIo\Manifest\ManifestLoader; -use PharIo\Version\Version as PharIoVersion; -use PHPUnit\Framework\Exception; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Runner\StandardTestSuiteLoader; -use PHPUnit\Runner\TestSuiteLoader; -use PHPUnit\Runner\Version; -use PHPUnit\TextUI\CliArguments\Builder; -use PHPUnit\TextUI\CliArguments\Configuration; -use PHPUnit\TextUI\CliArguments\Exception as ArgumentsException; -use PHPUnit\TextUI\CliArguments\Mapper; -use PHPUnit\TextUI\XmlConfiguration\Generator; -use PHPUnit\TextUI\XmlConfiguration\Loader; -use PHPUnit\TextUI\XmlConfiguration\PhpHandler; -use PHPUnit\TextUI\XmlConfiguration\TestSuiteMapper; -use PHPUnit\Util\FileLoader; -use PHPUnit\Util\Filesystem; -use PHPUnit\Util\Printer; -use PHPUnit\Util\TextTestListRenderer; -use PHPUnit\Util\XmlTestListRenderer; -use ReflectionClass; -use ReflectionException; -use SebastianBergmann\FileIterator\Facade as FileIteratorFacade; -use Throwable; - -/** - * A TestRunner for the Command Line Interface (CLI) - * PHP SAPI Module. - */ -class Command -{ - /** - * @var array - */ - protected $arguments = []; - - /** - * @var array - */ - protected $longOptions = []; - - /** - * @var bool - */ - private $versionStringPrinted = false; - - /** - * @psalm-var list - */ - private $warnings = []; - - /** - * @throws \PHPUnit\Framework\Exception - */ - public static function main(bool $exit = true): int - { - return (new static)->run($_SERVER['argv'], $exit); - } - - /** - * @throws Exception - */ - public function run(array $argv, bool $exit = true): int - { - $this->handleArguments($argv); - - $runner = $this->createRunner(); - - if ($this->arguments['test'] instanceof TestSuite) { - $suite = $this->arguments['test']; - } else { - $suite = $runner->getTest( - $this->arguments['test'], - $this->arguments['testSuffixes'] - ); - } - - if ($this->arguments['listGroups']) { - return $this->handleListGroups($suite, $exit); - } - - if ($this->arguments['listSuites']) { - return $this->handleListSuites($exit); - } - - if ($this->arguments['listTests']) { - return $this->handleListTests($suite, $exit); - } - - if ($this->arguments['listTestsXml']) { - return $this->handleListTestsXml($suite, $this->arguments['listTestsXml'], $exit); - } - - unset($this->arguments['test'], $this->arguments['testFile']); - - try { - $result = $runner->run($suite, $this->arguments, $this->warnings, $exit); - } catch (Exception $e) { - print $e->getMessage() . PHP_EOL; - } - - $return = TestRunner::FAILURE_EXIT; - - if (isset($result) && $result->wasSuccessful()) { - $return = TestRunner::SUCCESS_EXIT; - } elseif (!isset($result) || $result->errorCount() > 0) { - $return = TestRunner::EXCEPTION_EXIT; - } - - if ($exit) { - exit($return); - } - - return $return; - } - - /** - * Create a TestRunner, override in subclasses. - */ - protected function createRunner(): TestRunner - { - return new TestRunner($this->arguments['loader']); - } - - /** - * Handles the command-line arguments. - * - * A child class of PHPUnit\TextUI\Command can hook into the argument - * parsing by adding the switch(es) to the $longOptions array and point to a - * callback method that handles the switch(es) in the child class like this - * - * - * longOptions['my-switch'] = 'myHandler'; - * // my-secondswitch will accept a value - note the equals sign - * $this->longOptions['my-secondswitch='] = 'myOtherHandler'; - * } - * - * // --my-switch -> myHandler() - * protected function myHandler() - * { - * } - * - * // --my-secondswitch foo -> myOtherHandler('foo') - * protected function myOtherHandler ($value) - * { - * } - * - * // You will also need this - the static keyword in the - * // PHPUnit\TextUI\Command will mean that it'll be - * // PHPUnit\TextUI\Command that gets instantiated, - * // not MyCommand - * public static function main($exit = true) - * { - * $command = new static; - * - * return $command->run($_SERVER['argv'], $exit); - * } - * - * } - * - * - * @throws Exception - */ - protected function handleArguments(array $argv): void - { - try { - $arguments = (new Builder)->fromParameters($argv, array_keys($this->longOptions)); - } catch (ArgumentsException $e) { - $this->exitWithErrorMessage($e->getMessage()); - } - - assert(isset($arguments) && $arguments instanceof Configuration); - - if ($arguments->hasGenerateConfiguration() && $arguments->generateConfiguration()) { - $this->generateConfiguration(); - } - - if ($arguments->hasAtLeastVersion()) { - if (version_compare(Version::id(), $arguments->atLeastVersion(), '>=')) { - exit(TestRunner::SUCCESS_EXIT); - } - - exit(TestRunner::FAILURE_EXIT); - } - - if ($arguments->hasVersion() && $arguments->version()) { - $this->printVersionString(); - - exit(TestRunner::SUCCESS_EXIT); - } - - if ($arguments->hasCheckVersion() && $arguments->checkVersion()) { - $this->handleVersionCheck(); - } - - if ($arguments->hasHelp()) { - $this->showHelp(); - - exit(TestRunner::SUCCESS_EXIT); - } - - if ($arguments->hasUnrecognizedOrderBy()) { - $this->exitWithErrorMessage( - sprintf( - 'unrecognized --order-by option: %s', - $arguments->unrecognizedOrderBy() - ) - ); - } - - if ($arguments->hasIniSettings()) { - foreach ($arguments->iniSettings() as $name => $value) { - ini_set($name, $value); - } - } - - if ($arguments->hasIncludePath()) { - ini_set( - 'include_path', - $arguments->includePath() . PATH_SEPARATOR . ini_get('include_path') - ); - } - - $this->arguments = (new Mapper)->mapToLegacyArray($arguments); - - $this->handleCustomOptions($arguments->unrecognizedOptions()); - $this->handleCustomTestSuite(); - - if (!isset($this->arguments['testSuffixes'])) { - $this->arguments['testSuffixes'] = ['Test.php', '.phpt']; - } - - if (!isset($this->arguments['test']) && $arguments->hasArgument()) { - $this->arguments['test'] = realpath($arguments->argument()); - - if ($this->arguments['test'] === false) { - $this->exitWithErrorMessage( - sprintf( - 'Cannot open file "%s".', - $arguments->argument() - ) - ); - } - } - - if ($this->arguments['loader'] !== null) { - $this->arguments['loader'] = $this->handleLoader($this->arguments['loader']); - } - - if (isset($this->arguments['configuration']) && is_dir($this->arguments['configuration'])) { - $configurationFile = $this->arguments['configuration'] . '/phpunit.xml'; - - if (file_exists($configurationFile)) { - $this->arguments['configuration'] = realpath( - $configurationFile - ); - } elseif (file_exists($configurationFile . '.dist')) { - $this->arguments['configuration'] = realpath( - $configurationFile . '.dist' - ); - } - } elseif (!isset($this->arguments['configuration']) && $this->arguments['useDefaultConfiguration']) { - if (file_exists('phpunit.xml')) { - $this->arguments['configuration'] = realpath('phpunit.xml'); - } elseif (file_exists('phpunit.xml.dist')) { - $this->arguments['configuration'] = realpath( - 'phpunit.xml.dist' - ); - } - } - - if (isset($this->arguments['configuration'])) { - try { - $configuration = (new Loader)->load($this->arguments['configuration']); - } catch (Throwable $e) { - print $e->getMessage() . PHP_EOL; - - exit(TestRunner::FAILURE_EXIT); - } - - $phpunitConfiguration = $configuration->phpunit(); - - (new PhpHandler)->handle($configuration->php()); - - if (isset($this->arguments['bootstrap'])) { - $this->handleBootstrap($this->arguments['bootstrap']); - } elseif ($phpunitConfiguration->hasBootstrap()) { - $this->handleBootstrap($phpunitConfiguration->bootstrap()); - } - - if (!isset($this->arguments['stderr'])) { - $this->arguments['stderr'] = $phpunitConfiguration->stderr(); - } - - if (!isset($this->arguments['noExtensions']) && $phpunitConfiguration->hasExtensionsDirectory() && extension_loaded('phar')) { - $this->handleExtensions($phpunitConfiguration->extensionsDirectory()); - } - - if (!isset($this->arguments['columns'])) { - $this->arguments['columns'] = $phpunitConfiguration->columns(); - } - - if (!isset($this->arguments['printer']) && $phpunitConfiguration->hasPrinterClass()) { - $file = $phpunitConfiguration->hasPrinterFile() ? $phpunitConfiguration->printerFile() : ''; - - $this->arguments['printer'] = $this->handlePrinter( - $phpunitConfiguration->printerClass(), - $file - ); - } - - if ($phpunitConfiguration->hasTestSuiteLoaderClass()) { - $file = $phpunitConfiguration->hasTestSuiteLoaderFile() ? $phpunitConfiguration->testSuiteLoaderFile() : ''; - - $this->arguments['loader'] = $this->handleLoader( - $phpunitConfiguration->testSuiteLoaderClass(), - $file - ); - } - - if (!isset($this->arguments['testsuite']) && $phpunitConfiguration->hasDefaultTestSuite()) { - $this->arguments['testsuite'] = $phpunitConfiguration->defaultTestSuite(); - } - - if (!isset($this->arguments['test'])) { - $this->arguments['test'] = (new TestSuiteMapper)->map( - $configuration->testSuite(), - $this->arguments['testsuite'] ?? '' - ); - } - } elseif (isset($this->arguments['bootstrap'])) { - $this->handleBootstrap($this->arguments['bootstrap']); - } - - if (isset($this->arguments['printer']) && is_string($this->arguments['printer'])) { - $this->arguments['printer'] = $this->handlePrinter($this->arguments['printer']); - } - - if (!isset($this->arguments['test'])) { - $this->showHelp(); - - exit(TestRunner::EXCEPTION_EXIT); - } - } - - /** - * Handles the loading of the PHPUnit\Runner\TestSuiteLoader implementation. - * - * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 - */ - protected function handleLoader(string $loaderClass, string $loaderFile = ''): ?TestSuiteLoader - { - $this->warnings[] = 'Using a custom test suite loader is deprecated'; - - if (!class_exists($loaderClass, false)) { - if ($loaderFile == '') { - $loaderFile = Filesystem::classNameToFilename( - $loaderClass - ); - } - - $loaderFile = stream_resolve_include_path($loaderFile); - - if ($loaderFile) { - require $loaderFile; - } - } - - if (class_exists($loaderClass, false)) { - try { - $class = new ReflectionClass($loaderClass); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - if ($class->implementsInterface(TestSuiteLoader::class) && $class->isInstantiable()) { - $object = $class->newInstance(); - - assert($object instanceof TestSuiteLoader); - - return $object; - } - } - - if ($loaderClass == StandardTestSuiteLoader::class) { - return null; - } - - $this->exitWithErrorMessage( - sprintf( - 'Could not use "%s" as loader.', - $loaderClass - ) - ); - - return null; - } - - /** - * Handles the loading of the PHPUnit\Util\Printer implementation. - * - * @return null|Printer|string - */ - protected function handlePrinter(string $printerClass, string $printerFile = '') - { - if (!class_exists($printerClass, false)) { - if ($printerFile === '') { - $printerFile = Filesystem::classNameToFilename( - $printerClass - ); - } - - $printerFile = stream_resolve_include_path($printerFile); - - if ($printerFile) { - require $printerFile; - } - } - - if (!class_exists($printerClass)) { - $this->exitWithErrorMessage( - sprintf( - 'Could not use "%s" as printer: class does not exist', - $printerClass - ) - ); - } - - try { - $class = new ReflectionClass($printerClass); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - // @codeCoverageIgnoreEnd - } - - if (!$class->implementsInterface(ResultPrinter::class)) { - $this->exitWithErrorMessage( - sprintf( - 'Could not use "%s" as printer: class does not implement %s', - $printerClass, - ResultPrinter::class - ) - ); - } - - if (!$class->isInstantiable()) { - $this->exitWithErrorMessage( - sprintf( - 'Could not use "%s" as printer: class cannot be instantiated', - $printerClass - ) - ); - } - - if ($class->isSubclassOf(ResultPrinter::class)) { - return $printerClass; - } - - $outputStream = isset($this->arguments['stderr']) ? 'php://stderr' : null; - - return $class->newInstance($outputStream); - } - - /** - * Loads a bootstrap file. - */ - protected function handleBootstrap(string $filename): void - { - try { - FileLoader::checkAndLoad($filename); - } catch (Exception $e) { - $this->exitWithErrorMessage($e->getMessage()); - } - } - - protected function handleVersionCheck(): void - { - $this->printVersionString(); - - $latestVersion = file_get_contents('https://phar.phpunit.de/latest-version-of/phpunit'); - $isOutdated = version_compare($latestVersion, Version::id(), '>'); - - if ($isOutdated) { - printf( - 'You are not using the latest version of PHPUnit.' . PHP_EOL . - 'The latest version is PHPUnit %s.' . PHP_EOL, - $latestVersion - ); - } else { - print 'You are using the latest version of PHPUnit.' . PHP_EOL; - } - - exit(TestRunner::SUCCESS_EXIT); - } - - /** - * Show the help message. - */ - protected function showHelp(): void - { - $this->printVersionString(); - (new Help)->writeToConsole(); - } - - /** - * Custom callback for test suite discovery. - */ - protected function handleCustomTestSuite(): void - { - } - - private function printVersionString(): void - { - if ($this->versionStringPrinted) { - return; - } - - print Version::getVersionString() . PHP_EOL . PHP_EOL; - - $this->versionStringPrinted = true; - } - - private function exitWithErrorMessage(string $message): void - { - $this->printVersionString(); - - print $message . PHP_EOL; - - exit(TestRunner::FAILURE_EXIT); - } - - private function handleExtensions(string $directory): void - { - foreach ((new FileIteratorFacade)->getFilesAsArray($directory, '.phar') as $file) { - if (!file_exists('phar://' . $file . '/manifest.xml')) { - $this->arguments['notLoadedExtensions'][] = $file . ' is not an extension for PHPUnit'; - - continue; - } - - try { - $applicationName = new ApplicationName('phpunit/phpunit'); - $version = new PharIoVersion(Version::series()); - $manifest = ManifestLoader::fromFile('phar://' . $file . '/manifest.xml'); - - if (!$manifest->isExtensionFor($applicationName)) { - $this->arguments['notLoadedExtensions'][] = $file . ' is not an extension for PHPUnit'; - - continue; - } - - if (!$manifest->isExtensionFor($applicationName, $version)) { - $this->arguments['notLoadedExtensions'][] = $file . ' is not compatible with this version of PHPUnit'; - - continue; - } - } catch (ManifestException $e) { - $this->arguments['notLoadedExtensions'][] = $file . ': ' . $e->getMessage(); - - continue; - } - - require $file; - - $this->arguments['loadedExtensions'][] = $manifest->getName()->asString() . ' ' . $manifest->getVersion()->getVersionString(); - } - } - - private function handleListGroups(TestSuite $suite, bool $exit): int - { - $this->printVersionString(); - - print 'Available test group(s):' . PHP_EOL; - - $groups = $suite->getGroups(); - sort($groups); - - foreach ($groups as $group) { - printf( - ' - %s' . PHP_EOL, - $group - ); - } - - if ($exit) { - exit(TestRunner::SUCCESS_EXIT); - } - - return TestRunner::SUCCESS_EXIT; - } - - /** - * @throws \PHPUnit\Framework\Exception - */ - private function handleListSuites(bool $exit): int - { - $this->printVersionString(); - - print 'Available test suite(s):' . PHP_EOL; - - $configuration = (new Loader)->load($this->arguments['configuration']); - - foreach ($configuration->testSuite() as $testSuite) { - printf( - ' - %s' . PHP_EOL, - $testSuite->name() - ); - } - - if ($exit) { - exit(TestRunner::SUCCESS_EXIT); - } - - return TestRunner::SUCCESS_EXIT; - } - - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - private function handleListTests(TestSuite $suite, bool $exit): int - { - $this->printVersionString(); - - $renderer = new TextTestListRenderer; - - print $renderer->render($suite); - - if ($exit) { - exit(TestRunner::SUCCESS_EXIT); - } - - return TestRunner::SUCCESS_EXIT; - } - - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - private function handleListTestsXml(TestSuite $suite, string $target, bool $exit): int - { - $this->printVersionString(); - - $renderer = new XmlTestListRenderer; - - file_put_contents($target, $renderer->render($suite)); - - printf( - 'Wrote list of tests that would have been run to %s' . PHP_EOL, - $target - ); - - if ($exit) { - exit(TestRunner::SUCCESS_EXIT); - } - - return TestRunner::SUCCESS_EXIT; - } - - private function generateConfiguration(): void - { - $this->printVersionString(); - - print 'Generating phpunit.xml in ' . getcwd() . PHP_EOL . PHP_EOL; - print 'Bootstrap script (relative to path shown above; default: vendor/autoload.php): '; - - $bootstrapScript = trim(fgets(STDIN)); - - print 'Tests directory (relative to path shown above; default: tests): '; - - $testsDirectory = trim(fgets(STDIN)); - - print 'Source directory (relative to path shown above; default: src): '; - - $src = trim(fgets(STDIN)); - - if ($bootstrapScript === '') { - $bootstrapScript = 'vendor/autoload.php'; - } - - if ($testsDirectory === '') { - $testsDirectory = 'tests'; - } - - if ($src === '') { - $src = 'src'; - } - - $generator = new Generator; - - file_put_contents( - 'phpunit.xml', - $generator->generateDefaultConfiguration( - Version::series(), - $bootstrapScript, - $testsDirectory, - $src - ) - ); - - print PHP_EOL . 'Generated phpunit.xml in ' . getcwd() . PHP_EOL; - - exit(TestRunner::SUCCESS_EXIT); - } - - private function handleCustomOptions(array $unrecognizedOptions): void - { - foreach ($unrecognizedOptions as $name => $value) { - if (isset($this->longOptions[$name])) { - $handler = $this->longOptions[$name]; - } - - $name .= '='; - - if (isset($this->longOptions[$name])) { - $handler = $this->longOptions[$name]; - } - - if (isset($handler) && is_callable([$this, $handler])) { - $this->{$handler}($value); - - unset($handler); - } - } - } -} diff --git a/src/TextUI/Command/Command.php b/src/TextUI/Command/Command.php new file mode 100644 index 00000000000..4194551e41a --- /dev/null +++ b/src/TextUI/Command/Command.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Command; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Command +{ + public function execute(): Result; +} diff --git a/src/TextUI/Command/Commands/AtLeastVersionCommand.php b/src/TextUI/Command/Commands/AtLeastVersionCommand.php new file mode 100644 index 00000000000..7bace86c8fd --- /dev/null +++ b/src/TextUI/Command/Commands/AtLeastVersionCommand.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Command; + +use function version_compare; +use PHPUnit\Runner\Version; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AtLeastVersionCommand implements Command +{ + private string $version; + + public function __construct(string $version) + { + $this->version = $version; + } + + public function execute(): Result + { + if (version_compare(Version::id(), $this->version, '>=')) { + return Result::from(); + } + + return Result::from('', Result::FAILURE); + } +} diff --git a/src/TextUI/Command/Commands/CheckPhpConfigurationCommand.php b/src/TextUI/Command/Commands/CheckPhpConfigurationCommand.php new file mode 100644 index 00000000000..7f684cc0e18 --- /dev/null +++ b/src/TextUI/Command/Commands/CheckPhpConfigurationCommand.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Command; + +use const E_ALL; +use const PHP_EOL; +use function extension_loaded; +use function in_array; +use function ini_get; +use function max; +use function sprintf; +use function strlen; +use PHPUnit\Runner\Version; +use PHPUnit\Util\Color; +use SebastianBergmann\Environment\Console; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class CheckPhpConfigurationCommand implements Command +{ + private bool $colorize; + + public function __construct() + { + $this->colorize = (new Console)->hasColorSupport(); + } + + public function execute(): Result + { + $lines = []; + $shellExitCode = 0; + + foreach ($this->settings() as $name => $setting) { + foreach ($setting['requiredExtensions'] as $extension) { + if (!extension_loaded($extension)) { + // @codeCoverageIgnoreStart + continue 2; + // @codeCoverageIgnoreEnd + } + } + + $actualValue = ini_get($name); + + if (in_array($actualValue, $setting['expectedValues'], true)) { + $check = $this->ok(); + } else { + $check = $this->notOk($actualValue); + $shellExitCode = 1; + } + + $lines[] = [ + sprintf( + '%s = %s', + $name, + $setting['valueForConfiguration'], + ), + $check, + ]; + } + + $maxLength = 0; + + foreach ($lines as $line) { + $maxLength = max($maxLength, strlen($line[0])); + } + + $buffer = sprintf( + 'Checking whether PHP is configured according to https://docs.phpunit.de/en/%s/installation.html#configuring-php-for-development' . PHP_EOL . PHP_EOL, + Version::series(), + ); + + foreach ($lines as $line) { + $buffer .= sprintf( + '%-' . $maxLength . 's ... %s' . PHP_EOL, + $line[0], + $line[1], + ); + } + + return Result::from($buffer, $shellExitCode); + } + + /** + * @return non-empty-string + */ + private function ok(): string + { + if (!$this->colorize) { + return 'ok'; + } + + // @codeCoverageIgnoreStart + return Color::colorizeTextBox('fg-green, bold', 'ok'); + // @codeCoverageIgnoreEnd + } + + /** + * @return non-empty-string + */ + private function notOk(string $actualValue): string + { + $message = sprintf('not ok (%s)', $actualValue); + + if (!$this->colorize) { + return $message; + } + + // @codeCoverageIgnoreStart + return Color::colorizeTextBox('fg-red, bold', $message); + // @codeCoverageIgnoreEnd + } + + /** + * @return non-empty-array, valueForConfiguration: non-empty-string, requiredExtensions: list}> + */ + private function settings(): array + { + return [ + 'display_errors' => [ + 'expectedValues' => ['1'], + 'valueForConfiguration' => 'On', + 'requiredExtensions' => [], + ], + 'display_startup_errors' => [ + 'expectedValues' => ['1'], + 'valueForConfiguration' => 'On', + 'requiredExtensions' => [], + ], + 'error_reporting' => [ + 'expectedValues' => ['-1', (string) E_ALL], + 'valueForConfiguration' => '-1', + 'requiredExtensions' => [], + ], + 'xdebug.show_exception_trace' => [ + 'expectedValues' => ['0'], + 'valueForConfiguration' => '0', + 'requiredExtensions' => ['xdebug'], + ], + 'zend.assertions' => [ + 'expectedValues' => ['1'], + 'valueForConfiguration' => '1', + 'requiredExtensions' => [], + ], + 'assert.exception' => [ + 'expectedValues' => ['1'], + 'valueForConfiguration' => '1', + 'requiredExtensions' => [], + ], + 'memory_limit' => [ + 'expectedValues' => ['-1'], + 'valueForConfiguration' => '-1', + 'requiredExtensions' => [], + ], + ]; + } +} diff --git a/src/TextUI/Command/Commands/GenerateConfigurationCommand.php b/src/TextUI/Command/Commands/GenerateConfigurationCommand.php new file mode 100644 index 00000000000..cb1a9ac9513 --- /dev/null +++ b/src/TextUI/Command/Commands/GenerateConfigurationCommand.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Command; + +use const PHP_EOL; +use const STDIN; +use function assert; +use function defined; +use function fgets; +use function file_put_contents; +use function getcwd; +use function is_file; +use function sprintf; +use function trim; +use PHPUnit\Runner\Version; +use PHPUnit\TextUI\XmlConfiguration\Generator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class GenerateConfigurationCommand implements Command +{ + public function execute(): Result + { + $directory = getcwd(); + + print 'Generating phpunit.xml in ' . $directory . PHP_EOL . PHP_EOL; + print 'Bootstrap script (relative to path shown above; default: vendor/autoload.php): '; + + $bootstrapScript = $this->read(); + + print 'Tests directory (relative to path shown above; default: tests): '; + + $testsDirectory = $this->read(); + + print 'Source directory (relative to path shown above; default: src): '; + + $src = $this->read(); + + print 'Cache directory (relative to path shown above; default: .phpunit.cache): '; + + $cacheDirectory = $this->read(); + + if ($bootstrapScript === '') { + $bootstrapScript = 'vendor/autoload.php'; + } + + if ($testsDirectory === '') { + $testsDirectory = 'tests'; + } + + if ($src === '') { + $src = 'src'; + } + + if ($cacheDirectory === '') { + $cacheDirectory = '.phpunit.cache'; + } + + if (defined('PHPUNIT_COMPOSER_INSTALL') && + is_file($directory . '/vendor/phpunit/phpunit/phpunit.xsd')) { + $schemaLocation = 'vendor/phpunit/phpunit/phpunit.xsd'; + } else { + $schemaLocation = sprintf( + 'https://schema.phpunit.de/%s/phpunit.xsd', + Version::series(), + ); + } + + $generator = new Generator; + + $result = @file_put_contents( + $directory . '/phpunit.xml', + $generator->generateDefaultConfiguration( + $schemaLocation, + $bootstrapScript, + $testsDirectory, + $src, + $cacheDirectory, + ), + ); + + if ($result !== false) { + return Result::from( + sprintf( + PHP_EOL . 'Generated phpunit.xml in %s.' . PHP_EOL . + 'Make sure to exclude the %s directory from version control.' . PHP_EOL, + $directory, + $cacheDirectory, + ), + ); + } + + // @codeCoverageIgnoreStart + return Result::from( + sprintf( + PHP_EOL . 'Could not write phpunit.xml in %s.' . PHP_EOL, + $directory, + ), + Result::EXCEPTION, + ); + // @codeCoverageIgnoreEnd + } + + private function read(): string + { + $buffer = fgets(STDIN); + + assert($buffer !== false); + + return trim($buffer); + } +} diff --git a/src/TextUI/Command/Commands/ListGroupsCommand.php b/src/TextUI/Command/Commands/ListGroupsCommand.php new file mode 100644 index 00000000000..94eb1355ced --- /dev/null +++ b/src/TextUI/Command/Commands/ListGroupsCommand.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Command; + +use const PHP_EOL; +use function count; +use function ksort; +use function sprintf; +use function str_starts_with; +use PHPUnit\Framework\TestCase; +use PHPUnit\Runner\Phpt\TestCase as PhptTestCase; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ListGroupsCommand implements Command +{ + /** + * @var list + */ + private array $tests; + + /** + * @param list $tests + */ + public function __construct(array $tests) + { + $this->tests = $tests; + } + + public function execute(): Result + { + /** @var array $groups */ + $groups = []; + + foreach ($this->tests as $test) { + if ($test instanceof PhptTestCase) { + $_groups = ['default']; + } else { + $_groups = $test->groups(); + } + + foreach ($_groups as $group) { + if (!isset($groups[$group])) { + $groups[$group] = 1; + } else { + $groups[$group]++; + } + } + } + + ksort($groups); + + $buffer = sprintf( + 'Available test group%s:' . PHP_EOL, + count($groups) > 1 ? 's' : '', + ); + + foreach ($groups as $group => $numberOfTests) { + if (str_starts_with((string) $group, '__phpunit_')) { + continue; + } + + $buffer .= sprintf( + ' - %s (%d test%s)' . PHP_EOL, + (string) $group, + $numberOfTests, + $numberOfTests > 1 ? 's' : '', + ); + } + + return Result::from($buffer); + } +} diff --git a/src/TextUI/Command/Commands/ListTestFilesCommand.php b/src/TextUI/Command/Commands/ListTestFilesCommand.php new file mode 100644 index 00000000000..dfe2da27cf7 --- /dev/null +++ b/src/TextUI/Command/Commands/ListTestFilesCommand.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Command; + +use const PHP_EOL; +use function array_unique; +use function assert; +use function sprintf; +use PHPUnit\Framework\TestCase; +use PHPUnit\Runner\Phpt\TestCase as PhptTestCase; +use ReflectionClass; +use ReflectionException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ListTestFilesCommand implements Command +{ + /** + * @var list + */ + private array $tests; + + /** + * @param list $tests + */ + public function __construct(array $tests) + { + $this->tests = $tests; + } + + /** + * @throws ReflectionException + */ + public function execute(): Result + { + $buffer = 'Available test files:' . PHP_EOL; + + $results = []; + + foreach ($this->tests as $test) { + if ($test instanceof TestCase) { + $name = new ReflectionClass($test)->getFileName(); + + assert($name !== false); + + $results[] = $name; + + continue; + } + + $results[] = $test->getName(); + } + + foreach (array_unique($results) as $result) { + $buffer .= sprintf( + ' - %s' . PHP_EOL, + $result, + ); + } + + return Result::from($buffer); + } +} diff --git a/src/TextUI/Command/Commands/ListTestSuitesCommand.php b/src/TextUI/Command/Commands/ListTestSuitesCommand.php new file mode 100644 index 00000000000..fcaf7765a37 --- /dev/null +++ b/src/TextUI/Command/Commands/ListTestSuitesCommand.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Command; + +use const PHP_EOL; +use function assert; +use function count; +use function ksort; +use function sprintf; +use PHPUnit\Framework\TestSuite; +use PHPUnit\TextUI\Configuration\Registry; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ListTestSuitesCommand implements Command +{ + private TestSuite $testSuite; + + public function __construct(TestSuite $testSuite) + { + $this->testSuite = $testSuite; + } + + public function execute(): Result + { + /** @var array $suites */ + $suites = []; + + foreach ($this->testSuite->tests() as $test) { + assert($test instanceof TestSuite); + + $suites[$test->name()] = count($test->collect()); + } + + ksort($suites); + + $buffer = $this->warnAboutConflictingOptions(); + + $buffer .= sprintf( + 'Available test suite%s:' . PHP_EOL, + count($suites) > 1 ? 's' : '', + ); + + foreach ($suites as $suite => $numberOfTests) { + $buffer .= sprintf( + ' - %s (%d test%s)' . PHP_EOL, + $suite, + $numberOfTests, + $numberOfTests > 1 ? 's' : '', + ); + } + + return Result::from($buffer); + } + + private function warnAboutConflictingOptions(): string + { + $buffer = ''; + + $configuration = Registry::get(); + + if ($configuration->includeTestSuites() !== [] && !$configuration->hasDefaultTestSuite()) { + $buffer .= 'The --testsuite and --list-suites options cannot be combined, --testsuite is ignored' . PHP_EOL; + } + + if ($configuration->hasFilter()) { + $buffer .= 'The --filter and --list-suites options cannot be combined, --filter is ignored' . PHP_EOL; + } + + if ($configuration->hasGroups()) { + $buffer .= 'The --group (CLI) and (XML) options cannot be combined with --list-suites, --group and are ignored' . PHP_EOL; + } + + if ($configuration->hasExcludeGroups()) { + $buffer .= 'The --exclude-group (CLI) and (XML) options cannot be combined with --list-suites, --exclude-group and are ignored' . PHP_EOL; + } + + if ($buffer !== '') { + $buffer .= PHP_EOL; + } + + return $buffer; + } +} diff --git a/src/TextUI/Command/Commands/ListTestsAsTextCommand.php b/src/TextUI/Command/Commands/ListTestsAsTextCommand.php new file mode 100644 index 00000000000..c3d71b5d237 --- /dev/null +++ b/src/TextUI/Command/Commands/ListTestsAsTextCommand.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Command; + +use const PHP_EOL; +use function count; +use function sprintf; +use function str_replace; +use PHPUnit\Framework\TestCase; +use PHPUnit\Runner\Phpt\TestCase as PhptTestCase; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ListTestsAsTextCommand implements Command +{ + /** + * @var list + */ + private array $tests; + + /** + * @param list $tests + */ + public function __construct(array $tests) + { + $this->tests = $tests; + } + + public function execute(): Result + { + $buffer = sprintf( + 'Available test%s:' . PHP_EOL, + count($this->tests) > 1 ? 's' : '', + ); + + foreach ($this->tests as $test) { + if ($test instanceof TestCase) { + $name = sprintf( + '%s::%s', + $test::class, + str_replace(' with data set ', '', $test->nameWithDataSet()), + ); + } else { + $name = $test->getName(); + } + + $buffer .= sprintf( + ' - %s' . PHP_EOL, + $name, + ); + } + + return Result::from($buffer); + } +} diff --git a/src/TextUI/Command/Commands/ListTestsAsXmlCommand.php b/src/TextUI/Command/Commands/ListTestsAsXmlCommand.php new file mode 100644 index 00000000000..a07c62cbb02 --- /dev/null +++ b/src/TextUI/Command/Commands/ListTestsAsXmlCommand.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Command; + +use const PHP_EOL; +use function assert; +use function file_put_contents; +use function ksort; +use function sprintf; +use PHPUnit\Framework\TestCase; +use PHPUnit\Runner\Phpt\TestCase as PhptTestCase; +use ReflectionClass; +use XMLWriter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ListTestsAsXmlCommand implements Command +{ + /** + * @var list + */ + private array $tests; + private string $filename; + + /** + * @param list $tests + */ + public function __construct(array $tests, string $filename) + { + $this->tests = $tests; + $this->filename = $filename; + } + + public function execute(): Result + { + $writer = new XMLWriter; + + $writer->openMemory(); + $writer->setIndent(true); + $writer->startDocument(); + + $writer->startElement('testSuite'); + $writer->writeAttribute('xmlns', 'https://xml.phpunit.de/testSuite'); + + $writer->startElement('tests'); + + $currentTestClass = null; + $groups = []; + + foreach ($this->tests as $test) { + if ($test instanceof TestCase) { + foreach ($test->groups() as $group) { + if (!isset($groups[$group])) { + $groups[$group] = []; + } + + $groups[$group][] = $test->valueObjectForEvents()->id(); + } + + if ($test::class !== $currentTestClass) { + if ($currentTestClass !== null) { + $writer->endElement(); + } + + $file = new ReflectionClass($test)->getFileName(); + + assert($file !== false); + + $writer->startElement('testClass'); + $writer->writeAttribute('name', $test::class); + $writer->writeAttribute('file', $file); + + $currentTestClass = $test::class; + } + + $writer->startElement('testMethod'); + $writer->writeAttribute('id', $test->valueObjectForEvents()->id()); + $writer->writeAttribute('name', $test->valueObjectForEvents()->methodName()); + $writer->endElement(); + + continue; + } + + if ($currentTestClass !== null) { + $writer->endElement(); + + $currentTestClass = null; + } + + $writer->startElement('phpt'); + $writer->writeAttribute('file', $test->getName()); + $writer->endElement(); + } + + if ($currentTestClass !== null) { + $writer->endElement(); + } + + $writer->endElement(); + + ksort($groups); + + $writer->startElement('groups'); + + foreach ($groups as $groupName => $testIds) { + $writer->startElement('group'); + $writer->writeAttribute('name', (string) $groupName); + + foreach ($testIds as $testId) { + $writer->startElement('test'); + $writer->writeAttribute('id', $testId); + $writer->endElement(); + } + + $writer->endElement(); + } + + $writer->endElement(); + $writer->endElement(); + + file_put_contents($this->filename, $writer->outputMemory()); + + return Result::from( + sprintf( + 'Wrote list of tests that would have been run to %s' . PHP_EOL, + $this->filename, + ), + ); + } +} diff --git a/src/TextUI/Command/Commands/MigrateConfigurationCommand.php b/src/TextUI/Command/Commands/MigrateConfigurationCommand.php new file mode 100644 index 00000000000..507ff90f346 --- /dev/null +++ b/src/TextUI/Command/Commands/MigrateConfigurationCommand.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Command; + +use const PHP_EOL; +use function copy; +use function file_put_contents; +use function sprintf; +use PHPUnit\TextUI\XmlConfiguration\Migrator; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class MigrateConfigurationCommand implements Command +{ + private string $filename; + + public function __construct(string $filename) + { + $this->filename = $filename; + } + + public function execute(): Result + { + try { + $migrated = (new Migrator)->migrate($this->filename); + + copy($this->filename, $this->filename . '.bak'); + + file_put_contents($this->filename, $migrated); + + return Result::from( + sprintf( + 'Created backup: %s.bak%sMigrated configuration: %s%s', + $this->filename, + PHP_EOL, + $this->filename, + PHP_EOL, + ), + ); + } catch (Throwable $t) { + return Result::from( + sprintf( + 'Migration of %s failed:%s%s%s', + $this->filename, + PHP_EOL, + $t->getMessage(), + PHP_EOL, + ), + Result::FAILURE, + ); + } + } +} diff --git a/src/TextUI/Command/Commands/ShowHelpCommand.php b/src/TextUI/Command/Commands/ShowHelpCommand.php new file mode 100644 index 00000000000..1fd04811fc7 --- /dev/null +++ b/src/TextUI/Command/Commands/ShowHelpCommand.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Command; + +use PHPUnit\TextUI\Help; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ShowHelpCommand implements Command +{ + private int $shellExitCode; + + public function __construct(int $shellExitCode) + { + $this->shellExitCode = $shellExitCode; + } + + public function execute(): Result + { + return Result::from( + (new Help)->generate(), + $this->shellExitCode, + ); + } +} diff --git a/src/TextUI/Command/Commands/ShowVersionCommand.php b/src/TextUI/Command/Commands/ShowVersionCommand.php new file mode 100644 index 00000000000..4455a3d2359 --- /dev/null +++ b/src/TextUI/Command/Commands/ShowVersionCommand.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Command; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ShowVersionCommand implements Command +{ + public function execute(): Result + { + return Result::from(); + } +} diff --git a/src/TextUI/Command/Commands/VersionCheckCommand.php b/src/TextUI/Command/Commands/VersionCheckCommand.php new file mode 100644 index 00000000000..3e076ebeedc --- /dev/null +++ b/src/TextUI/Command/Commands/VersionCheckCommand.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Command; + +use const PHP_EOL; +use function assert; +use function sprintf; +use function version_compare; +use PHPUnit\Util\Http\Downloader; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class VersionCheckCommand implements Command +{ + private Downloader $downloader; + private int $majorVersionNumber; + private string $versionId; + + public function __construct(Downloader $downloader, int $majorVersionNumber, string $versionId) + { + $this->downloader = $downloader; + $this->majorVersionNumber = $majorVersionNumber; + $this->versionId = $versionId; + } + + public function execute(): Result + { + $latestVersion = $this->downloader->download('https://phar.phpunit.de/latest-version-of/phpunit'); + + assert($latestVersion !== false); + + $latestCompatibleVersion = $this->downloader->download('https://phar.phpunit.de/latest-version-of/phpunit-' . $this->majorVersionNumber); + + $notLatest = version_compare($latestVersion, $this->versionId, '>'); + $notLatestCompatible = false; + + if ($latestCompatibleVersion !== false) { + $notLatestCompatible = version_compare($latestCompatibleVersion, $this->versionId, '>'); + } + + if (!$notLatest && !$notLatestCompatible) { + return Result::from( + 'You are using the latest version of PHPUnit.' . PHP_EOL, + ); + } + + $buffer = 'You are not using the latest version of PHPUnit.' . PHP_EOL; + + if ($notLatestCompatible) { + $buffer .= sprintf( + 'The latest version compatible with PHPUnit %s is PHPUnit %s.' . PHP_EOL, + $this->versionId, + $latestCompatibleVersion, + ); + } + + if ($notLatest) { + $buffer .= sprintf( + 'The latest version is PHPUnit %s.' . PHP_EOL, + $latestVersion, + ); + } + + return Result::from($buffer, Result::FAILURE); + } +} diff --git a/src/TextUI/Command/Commands/WarmCodeCoverageCacheCommand.php b/src/TextUI/Command/Commands/WarmCodeCoverageCacheCommand.php new file mode 100644 index 00000000000..7d1afafe322 --- /dev/null +++ b/src/TextUI/Command/Commands/WarmCodeCoverageCacheCommand.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Command; + +use const PHP_EOL; +use function printf; +use PHPUnit\TextUI\Configuration\CodeCoverageFilterRegistry; +use PHPUnit\TextUI\Configuration\Configuration; +use PHPUnit\TextUI\Configuration\NoCoverageCacheDirectoryException; +use SebastianBergmann\CodeCoverage\StaticAnalysis\CacheWarmer; +use SebastianBergmann\Timer\NoActiveTimerException; +use SebastianBergmann\Timer\Timer; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @codeCoverageIgnore + */ +final readonly class WarmCodeCoverageCacheCommand implements Command +{ + private Configuration $configuration; + private CodeCoverageFilterRegistry $codeCoverageFilterRegistry; + + public function __construct(Configuration $configuration, CodeCoverageFilterRegistry $codeCoverageFilterRegistry) + { + $this->configuration = $configuration; + $this->codeCoverageFilterRegistry = $codeCoverageFilterRegistry; + } + + /** + * @throws NoActiveTimerException + * @throws NoCoverageCacheDirectoryException + */ + public function execute(): Result + { + if (!$this->configuration->hasCoverageCacheDirectory()) { + return Result::from( + 'Cache for static analysis has not been configured' . PHP_EOL, + Result::FAILURE, + ); + } + + $this->codeCoverageFilterRegistry->init($this->configuration, true); + + if (!$this->codeCoverageFilterRegistry->configured()) { + return Result::from( + 'Filter for code coverage has not been configured' . PHP_EOL, + Result::FAILURE, + ); + } + + $timer = new Timer; + $timer->start(); + + print 'Warming cache for static analysis ... '; + + /** @phpstan-ignore new.internalClass,method.internalClass */ + $statistics = (new CacheWarmer)->warmCache( + $this->configuration->coverageCacheDirectory(), + !$this->configuration->disableCodeCoverageIgnore(), + $this->configuration->ignoreDeprecatedCodeUnitsFromCodeCoverage(), + $this->codeCoverageFilterRegistry->get(), + ); + + printf( + '[%s]%s%s%d file%s processed, %d cache hit%s, %d cache miss%s%s', + $timer->stop()->asString(), + PHP_EOL, + PHP_EOL, + $statistics['cacheHits'] + $statistics['cacheMisses'], + ($statistics['cacheHits'] + $statistics['cacheMisses']) !== 1 ? 's' : '', + $statistics['cacheHits'], + $statistics['cacheHits'] !== 1 ? 's' : '', + $statistics['cacheMisses'], + $statistics['cacheMisses'] !== 1 ? 'es' : '', + PHP_EOL, + ); + + return Result::from(); + } +} diff --git a/src/TextUI/Command/Result.php b/src/TextUI/Command/Result.php new file mode 100644 index 00000000000..ae4a3e27fe8 --- /dev/null +++ b/src/TextUI/Command/Result.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Command; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Result +{ + public const int SUCCESS = 0; + public const int FAILURE = 1; + public const int EXCEPTION = 2; + public const int CRASH = 255; + private string $output; + private int $shellExitCode; + + public static function from(string $output = '', int $shellExitCode = self::SUCCESS): self + { + return new self($output, $shellExitCode); + } + + private function __construct(string $output, int $shellExitCode) + { + $this->output = $output; + $this->shellExitCode = $shellExitCode; + } + + public function output(): string + { + return $this->output; + } + + public function shellExitCode(): int + { + return $this->shellExitCode; + } +} diff --git a/src/TextUI/Configuration/BootstrapLoader.php b/src/TextUI/Configuration/BootstrapLoader.php new file mode 100644 index 00000000000..f52d66475bd --- /dev/null +++ b/src/TextUI/Configuration/BootstrapLoader.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use const PHP_EOL; +use function in_array; +use function is_readable; +use function sprintf; +use PHPUnit\Event\Facade as EventFacade; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class BootstrapLoader +{ + /** + * @throws BootstrapScriptDoesNotExistException + * @throws BootstrapScriptException + */ + public function handle(Configuration $configuration): void + { + if (!$configuration->hasBootstrap()) { + return; + } + + $this->load($configuration->bootstrap()); + + foreach ($configuration->bootstrapForTestSuite() as $testSuiteName => $bootstrapForTestSuite) { + if ($configuration->includeTestSuites() !== [] && !in_array($testSuiteName, $configuration->includeTestSuites(), true)) { + continue; + } + + if ($configuration->excludeTestSuites() !== [] && in_array($testSuiteName, $configuration->excludeTestSuites(), true)) { + continue; + } + + $this->load($bootstrapForTestSuite); + } + } + + /** + * @param non-empty-string $filename + */ + private function load(string $filename): void + { + if (!is_readable($filename)) { + throw new BootstrapScriptDoesNotExistException($filename); + } + + try { + include_once $filename; + } catch (Throwable $t) { + $message = sprintf( + 'Error in bootstrap script: %s:%s%s%s%s', + $t::class, + PHP_EOL, + $t->getMessage(), + PHP_EOL, + $t->getTraceAsString(), + ); + + while ($t = $t->getPrevious()) { + $message .= sprintf( + '%s%sPrevious error: %s:%s%s%s%s', + PHP_EOL, + PHP_EOL, + $t::class, + PHP_EOL, + $t->getMessage(), + PHP_EOL, + $t->getTraceAsString(), + ); + } + + throw new BootstrapScriptException($message); + } + + EventFacade::emitter()->testRunnerBootstrapFinished($filename); + } +} diff --git a/src/TextUI/Configuration/Builder.php b/src/TextUI/Configuration/Builder.php new file mode 100644 index 00000000000..6f9e81a1558 --- /dev/null +++ b/src/TextUI/Configuration/Builder.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\TextUI\CliArguments\Builder as CliConfigurationBuilder; +use PHPUnit\TextUI\CliArguments\Exception as CliConfigurationException; +use PHPUnit\TextUI\CliArguments\XmlConfigurationFileFinder; +use PHPUnit\TextUI\XmlConfiguration\DefaultConfiguration; +use PHPUnit\TextUI\XmlConfiguration\Exception as XmlConfigurationException; +use PHPUnit\TextUI\XmlConfiguration\Loader; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @codeCoverageIgnore + */ +final readonly class Builder +{ + /** + * @param list $argv + * + * @throws ConfigurationCannotBeBuiltException + */ + public function build(array $argv): Configuration + { + try { + $cliConfiguration = (new CliConfigurationBuilder)->fromParameters($argv); + $configurationFile = (new XmlConfigurationFileFinder)->find($cliConfiguration); + $xmlConfiguration = DefaultConfiguration::create(); + + if ($configurationFile !== false) { + $xmlConfiguration = (new Loader)->load($configurationFile); + } + + return Registry::init( + $cliConfiguration, + $xmlConfiguration, + ); + } catch (CliConfigurationException|XmlConfigurationException $e) { + throw new ConfigurationCannotBeBuiltException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + } +} diff --git a/src/TextUI/Configuration/Cli/Builder.php b/src/TextUI/Configuration/Cli/Builder.php new file mode 100644 index 00000000000..1accafe3a8f --- /dev/null +++ b/src/TextUI/Configuration/Cli/Builder.php @@ -0,0 +1,1409 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\CliArguments; + +use const DIRECTORY_SEPARATOR; +use function assert; +use function basename; +use function explode; +use function getcwd; +use function is_file; +use function is_numeric; +use function sprintf; +use function strtolower; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Runner\TestSuiteSorter; +use PHPUnit\Util\Filesystem; +use SebastianBergmann\CliParser\Exception as CliParserException; +use SebastianBergmann\CliParser\Parser as CliParser; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Builder +{ + /** + * @var non-empty-list + */ + private const array LONG_OPTIONS = [ + 'all', + 'atleast-version=', + 'bootstrap=', + 'cache-result', + 'do-not-cache-result', + 'cache-directory=', + 'check-version', + 'check-php-configuration', + 'colors==', + 'columns=', + 'configuration=', + 'warm-coverage-cache', + 'coverage-filter=', + 'coverage-clover=', + 'coverage-cobertura=', + 'coverage-crap4j=', + 'coverage-html=', + 'coverage-openclover=', + 'coverage-php=', + 'coverage-text==', + 'only-summary-for-coverage-text', + 'show-uncovered-for-coverage-text', + 'coverage-xml=', + 'path-coverage', + 'disallow-test-output', + 'display-all-issues', + 'display-incomplete', + 'display-skipped', + 'display-deprecations', + 'display-phpunit-deprecations', + 'display-phpunit-notices', + 'display-errors', + 'display-notices', + 'display-warnings', + 'default-time-limit=', + 'enforce-time-limit', + 'exclude-group=', + 'filter=', + 'exclude-filter=', + 'generate-baseline=', + 'use-baseline=', + 'ignore-baseline', + 'generate-configuration', + 'globals-backup', + 'group=', + 'covers=', + 'uses=', + 'requires-php-extension=', + 'help', + 'resolve-dependencies', + 'ignore-dependencies', + 'include-path=', + 'list-groups', + 'list-suites', + 'list-test-files', + 'list-tests', + 'list-tests-xml=', + 'log-junit=', + 'log-otr=', + 'include-git-information', + 'log-teamcity=', + 'migrate-configuration', + 'no-configuration', + 'no-coverage', + 'no-logging', + 'no-extensions', + 'no-output', + 'no-progress', + 'no-results', + 'order-by=', + 'process-isolation', + 'do-not-report-useless-tests', + 'random-order', + 'random-order-seed=', + 'reverse-order', + 'reverse-list', + 'static-backup', + 'stderr', + 'fail-on-all-issues', + 'fail-on-deprecation', + 'fail-on-phpunit-deprecation', + 'fail-on-phpunit-notice', + 'fail-on-phpunit-warning', + 'fail-on-empty-test-suite', + 'fail-on-incomplete', + 'fail-on-notice', + 'fail-on-risky', + 'fail-on-skipped', + 'fail-on-warning', + 'do-not-fail-on-deprecation', + 'do-not-fail-on-phpunit-deprecation', + 'do-not-fail-on-phpunit-notice', + 'do-not-fail-on-phpunit-warning', + 'do-not-fail-on-empty-test-suite', + 'do-not-fail-on-incomplete', + 'do-not-fail-on-notice', + 'do-not-fail-on-risky', + 'do-not-fail-on-skipped', + 'do-not-fail-on-warning', + 'stop-on-defect', + 'stop-on-deprecation==', + 'stop-on-error', + 'stop-on-failure', + 'stop-on-incomplete', + 'stop-on-notice', + 'stop-on-risky', + 'stop-on-skipped', + 'stop-on-warning', + 'strict-coverage', + 'disable-coverage-ignore', + 'strict-global-state', + 'teamcity', + 'testdox', + 'testdox-summary', + 'testdox-html=', + 'testdox-text=', + 'test-suffix=', + 'testsuite=', + 'exclude-testsuite=', + 'log-events-text=', + 'log-events-verbose-text=', + 'version', + 'debug', + 'with-telemetry', + 'extension=', + ]; + + private const string SHORT_OPTIONS = 'd:c:h'; + + /** + * @var array + */ + private array $processed = []; + + /** + * @param list $parameters + * + * @throws Exception + */ + public function fromParameters(array $parameters): Configuration + { + try { + $options = (new CliParser)->parse( + $parameters, + self::SHORT_OPTIONS, + self::LONG_OPTIONS, + ); + } catch (CliParserException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + + $all = null; + $atLeastVersion = null; + $backupGlobals = null; + $backupStaticProperties = null; + $beStrictAboutChangesToGlobalState = null; + $bootstrap = null; + $cacheDirectory = null; + $cacheResult = null; + $checkPhpConfiguration = false; + $checkVersion = false; + $colors = null; + $columns = null; + $configuration = null; + $warmCoverageCache = false; + $coverageFilter = null; + $coverageClover = null; + $coverageCobertura = null; + $coverageCrap4J = null; + $coverageHtml = null; + $coverageOpenClover = null; + $coveragePhp = null; + $coverageText = null; + $coverageTextShowUncoveredFiles = null; + $coverageTextShowOnlySummary = null; + $coverageXml = null; + $pathCoverage = null; + $defaultTimeLimit = null; + $disableCodeCoverageIgnore = null; + $disallowTestOutput = null; + $displayAllIssues = null; + $displayIncomplete = null; + $displaySkipped = null; + $displayDeprecations = null; + $displayPhpunitDeprecations = null; + $displayPhpunitNotices = null; + $displayErrors = null; + $displayNotices = null; + $displayWarnings = null; + $enforceTimeLimit = null; + $excludeGroups = null; + $executionOrder = null; + $executionOrderDefects = null; + $failOnAllIssues = null; + $failOnDeprecation = null; + $failOnPhpunitDeprecation = null; + $failOnPhpunitNotice = null; + $failOnPhpunitWarning = null; + $failOnEmptyTestSuite = null; + $failOnIncomplete = null; + $failOnNotice = null; + $failOnRisky = null; + $failOnSkipped = null; + $failOnWarning = null; + $doNotFailOnDeprecation = null; + $doNotFailOnPhpunitDeprecation = null; + $doNotFailOnPhpunitNotice = null; + $doNotFailOnPhpunitWarning = null; + $doNotFailOnEmptyTestSuite = null; + $doNotFailOnIncomplete = null; + $doNotFailOnNotice = null; + $doNotFailOnRisky = null; + $doNotFailOnSkipped = null; + $doNotFailOnWarning = null; + $stopOnDefect = null; + $stopOnDeprecation = null; + $specificDeprecationToStopOn = null; + $stopOnError = null; + $stopOnFailure = null; + $stopOnIncomplete = null; + $stopOnNotice = null; + $stopOnRisky = null; + $stopOnSkipped = null; + $stopOnWarning = null; + $filter = null; + $excludeFilter = null; + $generateBaseline = null; + $useBaseline = null; + $ignoreBaseline = false; + $generateConfiguration = false; + $migrateConfiguration = false; + $groups = null; + $testsCovering = null; + $testsUsing = null; + $testsRequiringPhpExtension = null; + $help = false; + $includePath = null; + $iniSettings = []; + $junitLogfile = null; + $otrLogfile = null; + $includeGitInformation = null; + $listGroups = false; + $listSuites = false; + $listTestFiles = false; + $listTests = false; + $listTestsXml = null; + $noCoverage = null; + $noExtensions = null; + $noOutput = null; + $noProgress = null; + $noResults = null; + $noLogging = null; + $processIsolation = null; + $randomOrderSeed = null; + $reportUselessTests = null; + $resolveDependencies = null; + $reverseList = null; + $stderr = null; + $strictCoverage = null; + $teamcityLogfile = null; + $testdoxHtmlFile = null; + $testdoxTextFile = null; + $testSuffixes = null; + $testSuite = null; + $excludeTestSuite = null; + $useDefaultConfiguration = true; + $version = false; + $logEventsText = null; + $logEventsVerboseText = null; + $printerTeamCity = null; + $printerTestDox = null; + $printerTestDoxSummary = null; + $debug = false; + $withTelemetry = false; + $extensions = []; + + foreach ($options[0] as $option) { + $optionAllowedMultipleTimes = false; + + switch ($option[0]) { + case '--all': + $all = true; + + break; + + case '--colors': + $colors = \PHPUnit\TextUI\Configuration\Configuration::COLOR_AUTO; + + if ($option[1] !== null) { + $colors = $option[1]; + } + + break; + + case '--bootstrap': + $bootstrap = $option[1]; + + break; + + case '--cache-directory': + $cacheDirectory = $option[1]; + + break; + + case '--cache-result': + $cacheResult = true; + + break; + + case '--do-not-cache-result': + $cacheResult = false; + + break; + + case '--columns': + if (is_numeric($option[1])) { + $columns = (int) $option[1]; + } elseif ($option[1] === 'max') { + $columns = 'max'; + } + + break; + + case 'c': + case '--configuration': + $configuration = $option[1]; + + break; + + case '--warm-coverage-cache': + $warmCoverageCache = true; + + break; + + case '--coverage-clover': + $coverageClover = $option[1]; + + break; + + case '--coverage-cobertura': + $coverageCobertura = $option[1]; + + break; + + case '--coverage-crap4j': + $coverageCrap4J = $option[1]; + + break; + + case '--coverage-html': + $coverageHtml = $option[1]; + + break; + + case '--coverage-php': + $coveragePhp = $option[1]; + + break; + + case '--coverage-openclover': + $coverageOpenClover = $option[1]; + + break; + + case '--coverage-text': + if ($option[1] === null) { + $option[1] = 'php://stdout'; + } + + $coverageText = $option[1]; + + break; + + case '--only-summary-for-coverage-text': + $coverageTextShowOnlySummary = true; + + break; + + case '--show-uncovered-for-coverage-text': + $coverageTextShowUncoveredFiles = true; + + break; + + case '--coverage-xml': + $coverageXml = $option[1]; + + break; + + case '--path-coverage': + $pathCoverage = true; + + break; + + case 'd': + $tmp = explode('=', $option[1]); + + if (isset($tmp[0])) { + assert($tmp[0] !== ''); + + if (isset($tmp[1])) { + assert($tmp[1] !== ''); + + $iniSettings[$tmp[0]] = $tmp[1]; + } else { + $iniSettings[$tmp[0]] = '1'; + } + } + + $optionAllowedMultipleTimes = true; + + break; + + case 'h': + case '--help': + $help = true; + + break; + + case '--filter': + $filter = $option[1]; + + break; + + case '--exclude-filter': + $excludeFilter = $option[1]; + + break; + + case '--testsuite': + $testSuite = $option[1]; + + break; + + case '--exclude-testsuite': + $excludeTestSuite = $option[1]; + + break; + + case '--generate-baseline': + $generateBaseline = $option[1]; + + if (basename($generateBaseline) === $generateBaseline) { + $generateBaseline = getcwd() . DIRECTORY_SEPARATOR . $generateBaseline; + } + + break; + + case '--use-baseline': + $useBaseline = $option[1]; + + if (basename($useBaseline) === $useBaseline && !is_file($useBaseline)) { + $useBaseline = getcwd() . DIRECTORY_SEPARATOR . $useBaseline; + } + + break; + + case '--ignore-baseline': + $ignoreBaseline = true; + + break; + + case '--generate-configuration': + $generateConfiguration = true; + + break; + + case '--migrate-configuration': + $migrateConfiguration = true; + + break; + + case '--group': + if ($groups === null) { + $groups = []; + } + + $groups[] = $option[1]; + + $optionAllowedMultipleTimes = true; + + break; + + case '--exclude-group': + if ($excludeGroups === null) { + $excludeGroups = []; + } + + $excludeGroups[] = $option[1]; + + $optionAllowedMultipleTimes = true; + + break; + + case '--covers': + if ($testsCovering === null) { + $testsCovering = []; + } + + $testsCovering[] = strtolower($option[1]); + + $optionAllowedMultipleTimes = true; + + break; + + case '--uses': + if ($testsUsing === null) { + $testsUsing = []; + } + + $testsUsing[] = strtolower($option[1]); + + $optionAllowedMultipleTimes = true; + + break; + + case '--requires-php-extension': + if ($testsRequiringPhpExtension === null) { + $testsRequiringPhpExtension = []; + } + + $testsRequiringPhpExtension[] = strtolower($option[1]); + + $optionAllowedMultipleTimes = true; + + break; + + case '--test-suffix': + if ($testSuffixes === null) { + $testSuffixes = []; + } + + $testSuffixes[] = $option[1]; + + $optionAllowedMultipleTimes = true; + + break; + + case '--include-path': + $includePath = $option[1]; + + break; + + case '--list-groups': + $listGroups = true; + + break; + + case '--list-suites': + $listSuites = true; + + break; + + case '--list-test-files': + $listTestFiles = true; + + break; + + case '--list-tests': + $listTests = true; + + break; + + case '--list-tests-xml': + $listTestsXml = $option[1]; + + break; + + case '--log-junit': + $junitLogfile = $option[1]; + + break; + + case '--log-otr': + $otrLogfile = $option[1]; + + break; + + case '--include-git-information': + $includeGitInformation = true; + + break; + + case '--log-teamcity': + $teamcityLogfile = $option[1]; + + break; + + case '--order-by': + foreach (explode(',', $option[1]) as $order) { + switch ($order) { + case 'default': + $executionOrder = TestSuiteSorter::ORDER_DEFAULT; + $executionOrderDefects = TestSuiteSorter::ORDER_DEFAULT; + $resolveDependencies = true; + + break; + + case 'defects': + $executionOrderDefects = TestSuiteSorter::ORDER_DEFECTS_FIRST; + + break; + + case 'depends': + $resolveDependencies = true; + + break; + + case 'duration': + $executionOrder = TestSuiteSorter::ORDER_DURATION; + + break; + + case 'no-depends': + $resolveDependencies = false; + + break; + + case 'random': + $executionOrder = TestSuiteSorter::ORDER_RANDOMIZED; + + break; + + case 'reverse': + $executionOrder = TestSuiteSorter::ORDER_REVERSED; + + break; + + case 'size': + $executionOrder = TestSuiteSorter::ORDER_SIZE; + + break; + + default: + throw new Exception( + sprintf( + 'unrecognized --order-by option: %s', + $order, + ), + ); + } + } + + break; + + case '--process-isolation': + $processIsolation = true; + + break; + + case '--stderr': + $stderr = true; + + break; + + case '--fail-on-all-issues': + $failOnAllIssues = true; + + break; + + case '--fail-on-deprecation': + $this->warnWhenOptionsConflict( + $doNotFailOnDeprecation, + '--fail-on-deprecation', + '--do-not-fail-on-deprecation', + ); + + $failOnDeprecation = true; + + break; + + case '--fail-on-phpunit-deprecation': + $this->warnWhenOptionsConflict( + $doNotFailOnPhpunitDeprecation, + '--fail-on-phpunit-deprecation', + '--do-not-fail-on-phpunit-deprecation', + ); + + $failOnPhpunitDeprecation = true; + + break; + + case '--fail-on-phpunit-notice': + $this->warnWhenOptionsConflict( + $doNotFailOnPhpunitNotice, + '--fail-on-phpunit-notice', + '--do-not-fail-on-phpunit-notice', + ); + + $failOnPhpunitNotice = true; + + break; + + case '--fail-on-phpunit-warning': + $this->warnWhenOptionsConflict( + $doNotFailOnPhpunitWarning, + '--fail-on-phpunit-warning', + '--do-not-fail-on-phpunit-warning', + ); + + $failOnPhpunitWarning = true; + + break; + + case '--fail-on-empty-test-suite': + $this->warnWhenOptionsConflict( + $doNotFailOnEmptyTestSuite, + '--fail-on-empty-test-suite', + '--do-not-fail-on-empty-test-suite', + ); + + $failOnEmptyTestSuite = true; + + break; + + case '--fail-on-incomplete': + $this->warnWhenOptionsConflict( + $doNotFailOnIncomplete, + '--fail-on-incomplete', + '--do-not-fail-on-incomplete', + ); + + $failOnIncomplete = true; + + break; + + case '--fail-on-notice': + $this->warnWhenOptionsConflict( + $doNotFailOnNotice, + '--fail-on-notice', + '--do-not-fail-on-notice', + ); + + $failOnNotice = true; + + break; + + case '--fail-on-risky': + $this->warnWhenOptionsConflict( + $doNotFailOnRisky, + '--fail-on-risky', + '--do-not-fail-on-risky', + ); + + $failOnRisky = true; + + break; + + case '--fail-on-skipped': + $this->warnWhenOptionsConflict( + $doNotFailOnSkipped, + '--fail-on-skipped', + '--do-not-fail-on-skipped', + ); + + $failOnSkipped = true; + + break; + + case '--fail-on-warning': + $this->warnWhenOptionsConflict( + $doNotFailOnWarning, + '--fail-on-warning', + '--do-not-fail-on-warning', + ); + + $failOnWarning = true; + + break; + + case '--do-not-fail-on-deprecation': + $this->warnWhenOptionsConflict( + $failOnDeprecation, + '--do-not-fail-on-deprecation', + '--fail-on-deprecation', + ); + + $doNotFailOnDeprecation = true; + + break; + + case '--do-not-fail-on-phpunit-deprecation': + $this->warnWhenOptionsConflict( + $failOnPhpunitDeprecation, + '--do-not-fail-on-phpunit-deprecation', + '--fail-on-phpunit-deprecation', + ); + + $doNotFailOnPhpunitDeprecation = true; + + break; + + case '--do-not-fail-on-phpunit-notice': + $this->warnWhenOptionsConflict( + $failOnPhpunitNotice, + '--do-not-fail-on-phpunit-notice', + '--fail-on-phpunit-notice', + ); + + $doNotFailOnPhpunitNotice = true; + + break; + + case '--do-not-fail-on-phpunit-warning': + $this->warnWhenOptionsConflict( + $failOnPhpunitWarning, + '--do-not-fail-on-phpunit-warning', + '--fail-on-phpunit-warning', + ); + + $doNotFailOnPhpunitWarning = true; + + break; + + case '--do-not-fail-on-empty-test-suite': + $this->warnWhenOptionsConflict( + $failOnEmptyTestSuite, + '--do-not-fail-on-empty-test-suite', + '--fail-on-empty-test-suite', + ); + + $doNotFailOnEmptyTestSuite = true; + + break; + + case '--do-not-fail-on-incomplete': + $this->warnWhenOptionsConflict( + $failOnIncomplete, + '--do-not-fail-on-incomplete', + '--fail-on-incomplete', + ); + + $doNotFailOnIncomplete = true; + + break; + + case '--do-not-fail-on-notice': + $this->warnWhenOptionsConflict( + $failOnNotice, + '--do-not-fail-on-notice', + '--fail-on-notice', + ); + + $doNotFailOnNotice = true; + + break; + + case '--do-not-fail-on-risky': + $this->warnWhenOptionsConflict( + $failOnRisky, + '--do-not-fail-on-risky', + '--fail-on-risky', + ); + + $doNotFailOnRisky = true; + + break; + + case '--do-not-fail-on-skipped': + $this->warnWhenOptionsConflict( + $failOnSkipped, + '--do-not-fail-on-skipped', + '--fail-on-skipped', + ); + + $doNotFailOnSkipped = true; + + break; + + case '--do-not-fail-on-warning': + $this->warnWhenOptionsConflict( + $failOnWarning, + '--do-not-fail-on-warning', + '--fail-on-warning', + ); + + $doNotFailOnWarning = true; + + break; + + case '--stop-on-defect': + $stopOnDefect = true; + + break; + + case '--stop-on-deprecation': + $stopOnDeprecation = true; + + if ($option[1] !== null) { + $specificDeprecationToStopOn = $option[1]; + } + + break; + + case '--stop-on-error': + $stopOnError = true; + + break; + + case '--stop-on-failure': + $stopOnFailure = true; + + break; + + case '--stop-on-incomplete': + $stopOnIncomplete = true; + + break; + + case '--stop-on-notice': + $stopOnNotice = true; + + break; + + case '--stop-on-risky': + $stopOnRisky = true; + + break; + + case '--stop-on-skipped': + $stopOnSkipped = true; + + break; + + case '--stop-on-warning': + $stopOnWarning = true; + + break; + + case '--teamcity': + $printerTeamCity = true; + + break; + + case '--testdox': + $printerTestDox = true; + + break; + + case '--testdox-summary': + $printerTestDoxSummary = true; + + break; + + case '--testdox-html': + $testdoxHtmlFile = $option[1]; + + break; + + case '--testdox-text': + $testdoxTextFile = $option[1]; + + break; + + case '--no-configuration': + $useDefaultConfiguration = false; + + break; + + case '--no-extensions': + $noExtensions = true; + + break; + + case '--no-coverage': + $noCoverage = true; + + break; + + case '--no-logging': + $noLogging = true; + + break; + + case '--no-output': + $noOutput = true; + + break; + + case '--no-progress': + $noProgress = true; + + break; + + case '--no-results': + $noResults = true; + + break; + + case '--globals-backup': + $backupGlobals = true; + + break; + + case '--static-backup': + $backupStaticProperties = true; + + break; + + case '--atleast-version': + $atLeastVersion = $option[1]; + + break; + + case '--version': + $version = true; + + break; + + case '--do-not-report-useless-tests': + $reportUselessTests = false; + + break; + + case '--strict-coverage': + $strictCoverage = true; + + break; + + case '--disable-coverage-ignore': + $disableCodeCoverageIgnore = true; + + break; + + case '--strict-global-state': + $beStrictAboutChangesToGlobalState = true; + + break; + + case '--disallow-test-output': + $disallowTestOutput = true; + + break; + + case '--display-all-issues': + $displayAllIssues = true; + + break; + + case '--display-incomplete': + $displayIncomplete = true; + + break; + + case '--display-skipped': + $displaySkipped = true; + + break; + + case '--display-deprecations': + $displayDeprecations = true; + + break; + + case '--display-phpunit-deprecations': + $displayPhpunitDeprecations = true; + + break; + + case '--display-phpunit-notices': + $displayPhpunitNotices = true; + + break; + + case '--display-errors': + $displayErrors = true; + + break; + + case '--display-notices': + $displayNotices = true; + + break; + + case '--display-warnings': + $displayWarnings = true; + + break; + + case '--default-time-limit': + $defaultTimeLimit = (int) $option[1]; + + break; + + case '--enforce-time-limit': + $enforceTimeLimit = true; + + break; + + case '--reverse-list': + $reverseList = true; + + break; + + case '--check-php-configuration': + $checkPhpConfiguration = true; + + break; + + case '--check-version': + $checkVersion = true; + + break; + + case '--coverage-filter': + if ($coverageFilter === null) { + $coverageFilter = []; + } + + $coverageFilter[] = $option[1]; + + $optionAllowedMultipleTimes = true; + + break; + + case '--random-order': + $executionOrder = TestSuiteSorter::ORDER_RANDOMIZED; + + break; + + case '--random-order-seed': + $randomOrderSeed = (int) $option[1]; + + break; + + case '--resolve-dependencies': + $resolveDependencies = true; + + break; + + case '--ignore-dependencies': + $resolveDependencies = false; + + break; + + case '--reverse-order': + $executionOrder = TestSuiteSorter::ORDER_REVERSED; + + break; + + case '--log-events-text': + $logEventsText = Filesystem::resolveStreamOrFile($option[1]); + + if ($logEventsText === false) { + throw new Exception( + sprintf( + 'The path "%s" specified for the --log-events-text option could not be resolved', + $option[1], + ), + ); + } + + break; + + case '--log-events-verbose-text': + $logEventsVerboseText = Filesystem::resolveStreamOrFile($option[1]); + + if ($logEventsVerboseText === false) { + throw new Exception( + sprintf( + 'The path "%s" specified for the --log-events-verbose-text option could not be resolved', + $option[1], + ), + ); + } + + break; + + case '--debug': + $debug = true; + + break; + + case '--with-telemetry': + $withTelemetry = true; + + break; + + case '--extension': + $extensions[] = $option[1]; + + $optionAllowedMultipleTimes = true; + + break; + } + + if (!$optionAllowedMultipleTimes) { + $this->markProcessed($option[0]); + } + } + + if ($iniSettings === []) { + $iniSettings = null; + } + + if ($extensions === []) { + $extensions = null; + } + + return new Configuration( + $options[1], + $all, + $atLeastVersion, + $backupGlobals, + $backupStaticProperties, + $beStrictAboutChangesToGlobalState, + $bootstrap, + $cacheDirectory, + $cacheResult, + $checkPhpConfiguration, + $checkVersion, + $colors, + $columns, + $configuration, + $coverageClover, + $coverageCobertura, + $coverageCrap4J, + $coverageHtml, + $coverageOpenClover, + $coveragePhp, + $coverageText, + $coverageTextShowUncoveredFiles, + $coverageTextShowOnlySummary, + $coverageXml, + $pathCoverage, + $warmCoverageCache, + $defaultTimeLimit, + $disableCodeCoverageIgnore, + $disallowTestOutput, + $enforceTimeLimit, + $excludeGroups, + $executionOrder, + $executionOrderDefects, + $failOnAllIssues, + $failOnDeprecation, + $failOnPhpunitDeprecation, + $failOnPhpunitNotice, + $failOnPhpunitWarning, + $failOnEmptyTestSuite, + $failOnIncomplete, + $failOnNotice, + $failOnRisky, + $failOnSkipped, + $failOnWarning, + $doNotFailOnDeprecation, + $doNotFailOnPhpunitDeprecation, + $doNotFailOnPhpunitNotice, + $doNotFailOnPhpunitWarning, + $doNotFailOnEmptyTestSuite, + $doNotFailOnIncomplete, + $doNotFailOnNotice, + $doNotFailOnRisky, + $doNotFailOnSkipped, + $doNotFailOnWarning, + $stopOnDefect, + $stopOnDeprecation, + $specificDeprecationToStopOn, + $stopOnError, + $stopOnFailure, + $stopOnIncomplete, + $stopOnNotice, + $stopOnRisky, + $stopOnSkipped, + $stopOnWarning, + $filter, + $excludeFilter, + $generateBaseline, + $useBaseline, + $ignoreBaseline, + $generateConfiguration, + $migrateConfiguration, + $groups, + $testsCovering, + $testsUsing, + $testsRequiringPhpExtension, + $help, + $includePath, + $iniSettings, + $junitLogfile, + $otrLogfile, + $includeGitInformation, + $listGroups, + $listSuites, + $listTestFiles, + $listTests, + $listTestsXml, + $noCoverage, + $noExtensions, + $noOutput, + $noProgress, + $noResults, + $noLogging, + $processIsolation, + $randomOrderSeed, + $reportUselessTests, + $resolveDependencies, + $reverseList, + $stderr, + $strictCoverage, + $teamcityLogfile, + $testdoxHtmlFile, + $testdoxTextFile, + $testSuffixes, + $testSuite, + $excludeTestSuite, + $useDefaultConfiguration, + $displayAllIssues, + $displayIncomplete, + $displaySkipped, + $displayDeprecations, + $displayPhpunitDeprecations, + $displayPhpunitNotices, + $displayErrors, + $displayNotices, + $displayWarnings, + $version, + $coverageFilter, + $logEventsText, + $logEventsVerboseText, + $printerTeamCity, + $printerTestDox, + $printerTestDoxSummary, + $debug, + $withTelemetry, + $extensions, + ); + } + + /** + * @param non-empty-string $option + */ + private function markProcessed(string $option): void + { + if (!isset($this->processed[$option])) { + $this->processed[$option] = 1; + + return; + } + + $this->processed[$option]++; + + if ($this->processed[$option] === 2) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Option %s cannot be used more than once', + $option, + ), + ); + } + } + + /** + * @param non-empty-string $option + */ + private function warnWhenOptionsConflict(?bool $current, string $option, string $opposite): void + { + if ($current === null) { + return; + } + + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Options %s and %s cannot be used together', + $option, + $opposite, + ), + ); + } +} diff --git a/src/TextUI/Configuration/Cli/Configuration.php b/src/TextUI/Configuration/Cli/Configuration.php new file mode 100644 index 00000000000..e637281c698 --- /dev/null +++ b/src/TextUI/Configuration/Cli/Configuration.php @@ -0,0 +1,2607 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\CliArguments; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Configuration +{ + /** + * @var list + */ + private array $arguments; + private ?bool $all; + private ?string $atLeastVersion; + private ?bool $backupGlobals; + private ?bool $backupStaticProperties; + private ?bool $beStrictAboutChangesToGlobalState; + private ?string $bootstrap; + private ?string $cacheDirectory; + private ?bool $cacheResult; + private bool $checkPhpConfiguration; + private bool $checkVersion; + private ?string $colors; + private null|int|string $columns; + private ?string $configurationFile; + + /** + * @var ?non-empty-list + */ + private ?array $coverageFilter; + private ?string $coverageClover; + private ?string $coverageCobertura; + private ?string $coverageCrap4J; + private ?string $coverageHtml; + private ?string $coverageOpenClover; + private ?string $coveragePhp; + private ?string $coverageText; + private ?bool $coverageTextShowUncoveredFiles; + private ?bool $coverageTextShowOnlySummary; + private ?string $coverageXml; + private ?bool $pathCoverage; + private bool $warmCoverageCache; + private ?int $defaultTimeLimit; + private ?bool $disableCodeCoverageIgnore; + private ?bool $disallowTestOutput; + private ?bool $enforceTimeLimit; + + /** + * @var ?non-empty-list + */ + private ?array $excludeGroups; + private ?int $executionOrder; + private ?int $executionOrderDefects; + private ?bool $failOnAllIssues; + private ?bool $failOnDeprecation; + private ?bool $failOnPhpunitDeprecation; + private ?bool $failOnPhpunitNotice; + private ?bool $failOnPhpunitWarning; + private ?bool $failOnEmptyTestSuite; + private ?bool $failOnIncomplete; + private ?bool $failOnNotice; + private ?bool $failOnRisky; + private ?bool $failOnSkipped; + private ?bool $failOnWarning; + private ?bool $doNotFailOnDeprecation; + private ?bool $doNotFailOnPhpunitDeprecation; + private ?bool $doNotFailOnPhpunitNotice; + private ?bool $doNotFailOnPhpunitWarning; + private ?bool $doNotFailOnEmptyTestSuite; + private ?bool $doNotFailOnIncomplete; + private ?bool $doNotFailOnNotice; + private ?bool $doNotFailOnRisky; + private ?bool $doNotFailOnSkipped; + private ?bool $doNotFailOnWarning; + private ?bool $stopOnDefect; + private ?bool $stopOnDeprecation; + private ?string $specificDeprecationToStopOn; + private ?bool $stopOnError; + private ?bool $stopOnFailure; + private ?bool $stopOnIncomplete; + private ?bool $stopOnNotice; + private ?bool $stopOnRisky; + private ?bool $stopOnSkipped; + private ?bool $stopOnWarning; + private ?string $filter; + private ?string $excludeFilter; + private ?string $generateBaseline; + private ?string $useBaseline; + private bool $ignoreBaseline; + private bool $generateConfiguration; + private bool $migrateConfiguration; + + /** + * @var ?non-empty-list + */ + private ?array $groups; + + /** + * @var ?non-empty-list + */ + private ?array $testsCovering; + + /** + * @var ?non-empty-list + */ + private ?array $testsUsing; + + /** + * @var ?non-empty-list + */ + private ?array $testsRequiringPhpExtension; + private bool $help; + private ?string $includePath; + + /** + * @var ?non-empty-array + */ + private ?array $iniSettings; + private ?string $junitLogfile; + private ?string $otrLogfile; + private ?bool $includeGitInformationInOtrLogfile; + private bool $listGroups; + private bool $listSuites; + private bool $listTestFiles; + private bool $listTests; + private ?string $listTestsXml; + private ?bool $noCoverage; + private ?bool $noExtensions; + private ?bool $noOutput; + private ?bool $noProgress; + private ?bool $noResults; + private ?bool $noLogging; + private ?bool $processIsolation; + private ?int $randomOrderSeed; + private ?bool $reportUselessTests; + private ?bool $resolveDependencies; + private ?bool $reverseList; + private ?bool $stderr; + private ?bool $strictCoverage; + private ?string $teamcityLogfile; + private ?bool $teamCityPrinter; + private ?string $testdoxHtmlFile; + private ?string $testdoxTextFile; + private ?bool $testdoxPrinter; + private ?bool $testdoxPrinterSummary; + + /** + * @var ?non-empty-list + */ + private ?array $testSuffixes; + private ?string $testSuite; + private ?string $excludeTestSuite; + private bool $useDefaultConfiguration; + private ?bool $displayDetailsOnAllIssues; + private ?bool $displayDetailsOnIncompleteTests; + private ?bool $displayDetailsOnSkippedTests; + private ?bool $displayDetailsOnTestsThatTriggerDeprecations; + private ?bool $displayDetailsOnPhpunitDeprecations; + private ?bool $displayDetailsOnPhpunitNotices; + private ?bool $displayDetailsOnTestsThatTriggerErrors; + private ?bool $displayDetailsOnTestsThatTriggerNotices; + private ?bool $displayDetailsOnTestsThatTriggerWarnings; + private bool $version; + private ?string $logEventsText; + private ?string $logEventsVerboseText; + private bool $debug; + private bool $withTelemetry; + + /** + * @var ?non-empty-list + */ + private ?array $extensions; + + /** + * @param list $arguments + * @param ?non-empty-list $excludeGroups + * @param ?non-empty-list $groups + * @param ?non-empty-list $testsCovering + * @param ?non-empty-list $testsUsing + * @param ?non-empty-list $testsRequiringPhpExtension + * @param ?non-empty-array $iniSettings + * @param ?non-empty-list $testSuffixes + * @param ?non-empty-list $coverageFilter + * @param ?non-empty-list $extensions + */ + public function __construct(array $arguments, ?bool $all, ?string $atLeastVersion, ?bool $backupGlobals, ?bool $backupStaticProperties, ?bool $beStrictAboutChangesToGlobalState, ?string $bootstrap, ?string $cacheDirectory, ?bool $cacheResult, bool $checkPhpConfiguration, bool $checkVersion, ?string $colors, null|int|string $columns, ?string $configurationFile, ?string $coverageClover, ?string $coverageCobertura, ?string $coverageCrap4J, ?string $coverageHtml, ?string $coverageOpenClover, ?string $coveragePhp, ?string $coverageText, ?bool $coverageTextShowUncoveredFiles, ?bool $coverageTextShowOnlySummary, ?string $coverageXml, ?bool $pathCoverage, bool $warmCoverageCache, ?int $defaultTimeLimit, ?bool $disableCodeCoverageIgnore, ?bool $disallowTestOutput, ?bool $enforceTimeLimit, ?array $excludeGroups, ?int $executionOrder, ?int $executionOrderDefects, ?bool $failOnAllIssues, ?bool $failOnDeprecation, ?bool $failOnPhpunitDeprecation, ?bool $failOnPhpunitNotice, ?bool $failOnPhpunitWarning, ?bool $failOnEmptyTestSuite, ?bool $failOnIncomplete, ?bool $failOnNotice, ?bool $failOnRisky, ?bool $failOnSkipped, ?bool $failOnWarning, ?bool $doNotFailOnDeprecation, ?bool $doNotFailOnPhpunitDeprecation, ?bool $doNotFailOnPhpunitNotice, ?bool $doNotFailOnPhpunitWarning, ?bool $doNotFailOnEmptyTestSuite, ?bool $doNotFailOnIncomplete, ?bool $doNotFailOnNotice, ?bool $doNotFailOnRisky, ?bool $doNotFailOnSkipped, ?bool $doNotFailOnWarning, ?bool $stopOnDefect, ?bool $stopOnDeprecation, ?string $specificDeprecationToStopOn, ?bool $stopOnError, ?bool $stopOnFailure, ?bool $stopOnIncomplete, ?bool $stopOnNotice, ?bool $stopOnRisky, ?bool $stopOnSkipped, ?bool $stopOnWarning, ?string $filter, ?string $excludeFilter, ?string $generateBaseline, ?string $useBaseline, bool $ignoreBaseline, bool $generateConfiguration, bool $migrateConfiguration, ?array $groups, ?array $testsCovering, ?array $testsUsing, ?array $testsRequiringPhpExtension, bool $help, ?string $includePath, ?array $iniSettings, ?string $junitLogfile, ?string $otrLogfile, ?bool $includeGitInformation, bool $listGroups, bool $listSuites, bool $listTestFiles, bool $listTests, ?string $listTestsXml, ?bool $noCoverage, ?bool $noExtensions, ?bool $noOutput, ?bool $noProgress, ?bool $noResults, ?bool $noLogging, ?bool $processIsolation, ?int $randomOrderSeed, ?bool $reportUselessTests, ?bool $resolveDependencies, ?bool $reverseList, ?bool $stderr, ?bool $strictCoverage, ?string $teamcityLogfile, ?string $testdoxHtmlFile, ?string $testdoxTextFile, ?array $testSuffixes, ?string $testSuite, ?string $excludeTestSuite, bool $useDefaultConfiguration, ?bool $displayDetailsOnAllIssues, ?bool $displayDetailsOnIncompleteTests, ?bool $displayDetailsOnSkippedTests, ?bool $displayDetailsOnTestsThatTriggerDeprecations, ?bool $displayDetailsOnPhpunitDeprecations, ?bool $displayDetailsOnPhpunitNotices, ?bool $displayDetailsOnTestsThatTriggerErrors, ?bool $displayDetailsOnTestsThatTriggerNotices, ?bool $displayDetailsOnTestsThatTriggerWarnings, bool $version, ?array $coverageFilter, ?string $logEventsText, ?string $logEventsVerboseText, ?bool $printerTeamCity, ?bool $testdoxPrinter, ?bool $testdoxPrinterSummary, bool $debug, bool $withTelemetry, ?array $extensions) + { + $this->arguments = $arguments; + $this->all = $all; + $this->atLeastVersion = $atLeastVersion; + $this->backupGlobals = $backupGlobals; + $this->backupStaticProperties = $backupStaticProperties; + $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; + $this->bootstrap = $bootstrap; + $this->cacheDirectory = $cacheDirectory; + $this->cacheResult = $cacheResult; + $this->checkPhpConfiguration = $checkPhpConfiguration; + $this->checkVersion = $checkVersion; + $this->colors = $colors; + $this->columns = $columns; + $this->configurationFile = $configurationFile; + $this->coverageFilter = $coverageFilter; + $this->coverageClover = $coverageClover; + $this->coverageCobertura = $coverageCobertura; + $this->coverageCrap4J = $coverageCrap4J; + $this->coverageHtml = $coverageHtml; + $this->coverageOpenClover = $coverageOpenClover; + $this->coveragePhp = $coveragePhp; + $this->coverageText = $coverageText; + $this->coverageTextShowUncoveredFiles = $coverageTextShowUncoveredFiles; + $this->coverageTextShowOnlySummary = $coverageTextShowOnlySummary; + $this->coverageXml = $coverageXml; + $this->pathCoverage = $pathCoverage; + $this->warmCoverageCache = $warmCoverageCache; + $this->defaultTimeLimit = $defaultTimeLimit; + $this->disableCodeCoverageIgnore = $disableCodeCoverageIgnore; + $this->disallowTestOutput = $disallowTestOutput; + $this->enforceTimeLimit = $enforceTimeLimit; + $this->excludeGroups = $excludeGroups; + $this->executionOrder = $executionOrder; + $this->executionOrderDefects = $executionOrderDefects; + $this->failOnAllIssues = $failOnAllIssues; + $this->failOnDeprecation = $failOnDeprecation; + $this->failOnPhpunitDeprecation = $failOnPhpunitDeprecation; + $this->failOnPhpunitNotice = $failOnPhpunitNotice; + $this->failOnPhpunitWarning = $failOnPhpunitWarning; + $this->failOnEmptyTestSuite = $failOnEmptyTestSuite; + $this->failOnIncomplete = $failOnIncomplete; + $this->failOnNotice = $failOnNotice; + $this->failOnRisky = $failOnRisky; + $this->failOnSkipped = $failOnSkipped; + $this->failOnWarning = $failOnWarning; + $this->doNotFailOnDeprecation = $doNotFailOnDeprecation; + $this->doNotFailOnPhpunitDeprecation = $doNotFailOnPhpunitDeprecation; + $this->doNotFailOnPhpunitNotice = $doNotFailOnPhpunitNotice; + $this->doNotFailOnPhpunitWarning = $doNotFailOnPhpunitWarning; + $this->doNotFailOnEmptyTestSuite = $doNotFailOnEmptyTestSuite; + $this->doNotFailOnIncomplete = $doNotFailOnIncomplete; + $this->doNotFailOnNotice = $doNotFailOnNotice; + $this->doNotFailOnRisky = $doNotFailOnRisky; + $this->doNotFailOnSkipped = $doNotFailOnSkipped; + $this->doNotFailOnWarning = $doNotFailOnWarning; + $this->stopOnDefect = $stopOnDefect; + $this->stopOnDeprecation = $stopOnDeprecation; + $this->specificDeprecationToStopOn = $specificDeprecationToStopOn; + $this->stopOnError = $stopOnError; + $this->stopOnFailure = $stopOnFailure; + $this->stopOnIncomplete = $stopOnIncomplete; + $this->stopOnNotice = $stopOnNotice; + $this->stopOnRisky = $stopOnRisky; + $this->stopOnSkipped = $stopOnSkipped; + $this->stopOnWarning = $stopOnWarning; + $this->filter = $filter; + $this->excludeFilter = $excludeFilter; + $this->generateBaseline = $generateBaseline; + $this->useBaseline = $useBaseline; + $this->ignoreBaseline = $ignoreBaseline; + $this->generateConfiguration = $generateConfiguration; + $this->migrateConfiguration = $migrateConfiguration; + $this->groups = $groups; + $this->testsCovering = $testsCovering; + $this->testsUsing = $testsUsing; + $this->testsRequiringPhpExtension = $testsRequiringPhpExtension; + $this->help = $help; + $this->includePath = $includePath; + $this->iniSettings = $iniSettings; + $this->junitLogfile = $junitLogfile; + $this->otrLogfile = $otrLogfile; + $this->includeGitInformationInOtrLogfile = $includeGitInformation; + $this->listGroups = $listGroups; + $this->listSuites = $listSuites; + $this->listTestFiles = $listTestFiles; + $this->listTests = $listTests; + $this->listTestsXml = $listTestsXml; + $this->noCoverage = $noCoverage; + $this->noExtensions = $noExtensions; + $this->noOutput = $noOutput; + $this->noProgress = $noProgress; + $this->noResults = $noResults; + $this->noLogging = $noLogging; + $this->processIsolation = $processIsolation; + $this->randomOrderSeed = $randomOrderSeed; + $this->reportUselessTests = $reportUselessTests; + $this->resolveDependencies = $resolveDependencies; + $this->reverseList = $reverseList; + $this->stderr = $stderr; + $this->strictCoverage = $strictCoverage; + $this->teamcityLogfile = $teamcityLogfile; + $this->testdoxHtmlFile = $testdoxHtmlFile; + $this->testdoxTextFile = $testdoxTextFile; + $this->testSuffixes = $testSuffixes; + $this->testSuite = $testSuite; + $this->excludeTestSuite = $excludeTestSuite; + $this->useDefaultConfiguration = $useDefaultConfiguration; + $this->displayDetailsOnAllIssues = $displayDetailsOnAllIssues; + $this->displayDetailsOnIncompleteTests = $displayDetailsOnIncompleteTests; + $this->displayDetailsOnSkippedTests = $displayDetailsOnSkippedTests; + $this->displayDetailsOnTestsThatTriggerDeprecations = $displayDetailsOnTestsThatTriggerDeprecations; + $this->displayDetailsOnPhpunitDeprecations = $displayDetailsOnPhpunitDeprecations; + $this->displayDetailsOnPhpunitNotices = $displayDetailsOnPhpunitNotices; + $this->displayDetailsOnTestsThatTriggerErrors = $displayDetailsOnTestsThatTriggerErrors; + $this->displayDetailsOnTestsThatTriggerNotices = $displayDetailsOnTestsThatTriggerNotices; + $this->displayDetailsOnTestsThatTriggerWarnings = $displayDetailsOnTestsThatTriggerWarnings; + $this->version = $version; + $this->logEventsText = $logEventsText; + $this->logEventsVerboseText = $logEventsVerboseText; + $this->teamCityPrinter = $printerTeamCity; + $this->testdoxPrinter = $testdoxPrinter; + $this->testdoxPrinterSummary = $testdoxPrinterSummary; + $this->debug = $debug; + $this->withTelemetry = $withTelemetry; + $this->extensions = $extensions; + } + + /** + * @return list + */ + public function arguments(): array + { + return $this->arguments; + } + + /** + * @phpstan-assert-if-true !null $this->all + */ + public function hasAll(): bool + { + return $this->all !== null; + } + + /** + * @throws Exception + */ + public function all(): bool + { + if (!$this->hasAll()) { + throw new Exception; + } + + return $this->all; + } + + /** + * @phpstan-assert-if-true !null $this->atLeastVersion + */ + public function hasAtLeastVersion(): bool + { + return $this->atLeastVersion !== null; + } + + /** + * @throws Exception + */ + public function atLeastVersion(): string + { + if (!$this->hasAtLeastVersion()) { + throw new Exception; + } + + return $this->atLeastVersion; + } + + /** + * @phpstan-assert-if-true !null $this->backupGlobals + */ + public function hasBackupGlobals(): bool + { + return $this->backupGlobals !== null; + } + + /** + * @throws Exception + */ + public function backupGlobals(): bool + { + if (!$this->hasBackupGlobals()) { + throw new Exception; + } + + return $this->backupGlobals; + } + + /** + * @phpstan-assert-if-true !null $this->backupStaticProperties + */ + public function hasBackupStaticProperties(): bool + { + return $this->backupStaticProperties !== null; + } + + /** + * @throws Exception + */ + public function backupStaticProperties(): bool + { + if (!$this->hasBackupStaticProperties()) { + throw new Exception; + } + + return $this->backupStaticProperties; + } + + /** + * @phpstan-assert-if-true !null $this->beStrictAboutChangesToGlobalState + */ + public function hasBeStrictAboutChangesToGlobalState(): bool + { + return $this->beStrictAboutChangesToGlobalState !== null; + } + + /** + * @throws Exception + */ + public function beStrictAboutChangesToGlobalState(): bool + { + if (!$this->hasBeStrictAboutChangesToGlobalState()) { + throw new Exception; + } + + return $this->beStrictAboutChangesToGlobalState; + } + + /** + * @phpstan-assert-if-true !null $this->bootstrap + */ + public function hasBootstrap(): bool + { + return $this->bootstrap !== null; + } + + /** + * @throws Exception + */ + public function bootstrap(): string + { + if (!$this->hasBootstrap()) { + throw new Exception; + } + + return $this->bootstrap; + } + + /** + * @phpstan-assert-if-true !null $this->cacheDirectory + */ + public function hasCacheDirectory(): bool + { + return $this->cacheDirectory !== null; + } + + /** + * @throws Exception + */ + public function cacheDirectory(): string + { + if (!$this->hasCacheDirectory()) { + throw new Exception; + } + + return $this->cacheDirectory; + } + + /** + * @phpstan-assert-if-true !null $this->cacheResult + */ + public function hasCacheResult(): bool + { + return $this->cacheResult !== null; + } + + /** + * @throws Exception + */ + public function cacheResult(): bool + { + if (!$this->hasCacheResult()) { + throw new Exception; + } + + return $this->cacheResult; + } + + public function checkPhpConfiguration(): bool + { + return $this->checkPhpConfiguration; + } + + public function checkVersion(): bool + { + return $this->checkVersion; + } + + /** + * @phpstan-assert-if-true !null $this->colors + */ + public function hasColors(): bool + { + return $this->colors !== null; + } + + /** + * @throws Exception + */ + public function colors(): string + { + if (!$this->hasColors()) { + throw new Exception; + } + + return $this->colors; + } + + /** + * @phpstan-assert-if-true !null $this->columns + */ + public function hasColumns(): bool + { + return $this->columns !== null; + } + + /** + * @throws Exception + */ + public function columns(): int|string + { + if (!$this->hasColumns()) { + throw new Exception; + } + + return $this->columns; + } + + /** + * @phpstan-assert-if-true !null $this->configurationFile + */ + public function hasConfigurationFile(): bool + { + return $this->configurationFile !== null; + } + + /** + * @throws Exception + */ + public function configurationFile(): string + { + if (!$this->hasConfigurationFile()) { + throw new Exception; + } + + return $this->configurationFile; + } + + /** + * @phpstan-assert-if-true !null $this->coverageFilter + */ + public function hasCoverageFilter(): bool + { + return $this->coverageFilter !== null; + } + + /** + * @throws Exception + * + * @return non-empty-list + */ + public function coverageFilter(): array + { + if (!$this->hasCoverageFilter()) { + throw new Exception; + } + + return $this->coverageFilter; + } + + /** + * @phpstan-assert-if-true !null $this->coverageClover + */ + public function hasCoverageClover(): bool + { + return $this->coverageClover !== null; + } + + /** + * @throws Exception + */ + public function coverageClover(): string + { + if (!$this->hasCoverageClover()) { + throw new Exception; + } + + return $this->coverageClover; + } + + /** + * @phpstan-assert-if-true !null $this->coverageCobertura + */ + public function hasCoverageCobertura(): bool + { + return $this->coverageCobertura !== null; + } + + /** + * @throws Exception + */ + public function coverageCobertura(): string + { + if (!$this->hasCoverageCobertura()) { + throw new Exception; + } + + return $this->coverageCobertura; + } + + /** + * @phpstan-assert-if-true !null $this->coverageCrap4J + */ + public function hasCoverageCrap4J(): bool + { + return $this->coverageCrap4J !== null; + } + + /** + * @throws Exception + */ + public function coverageCrap4J(): string + { + if (!$this->hasCoverageCrap4J()) { + throw new Exception; + } + + return $this->coverageCrap4J; + } + + /** + * @phpstan-assert-if-true !null $this->coverageHtml + */ + public function hasCoverageHtml(): bool + { + return $this->coverageHtml !== null; + } + + /** + * @throws Exception + */ + public function coverageHtml(): string + { + if (!$this->hasCoverageHtml()) { + throw new Exception; + } + + return $this->coverageHtml; + } + + /** + * @phpstan-assert-if-true !null $this->coverageOpenClover + */ + public function hasCoverageOpenClover(): bool + { + return $this->coverageOpenClover !== null; + } + + /** + * @throws Exception + */ + public function coverageOpenClover(): string + { + if (!$this->hasCoverageOpenClover()) { + throw new Exception; + } + + return $this->coverageOpenClover; + } + + /** + * @phpstan-assert-if-true !null $this->coveragePhp + */ + public function hasCoveragePhp(): bool + { + return $this->coveragePhp !== null; + } + + /** + * @throws Exception + */ + public function coveragePhp(): string + { + if (!$this->hasCoveragePhp()) { + throw new Exception; + } + + return $this->coveragePhp; + } + + /** + * @phpstan-assert-if-true !null $this->coverageText + */ + public function hasCoverageText(): bool + { + return $this->coverageText !== null; + } + + /** + * @throws Exception + */ + public function coverageText(): string + { + if (!$this->hasCoverageText()) { + throw new Exception; + } + + return $this->coverageText; + } + + /** + * @phpstan-assert-if-true !null $this->coverageTextShowUncoveredFiles + */ + public function hasCoverageTextShowUncoveredFiles(): bool + { + return $this->coverageTextShowUncoveredFiles !== null; + } + + /** + * @throws Exception + */ + public function coverageTextShowUncoveredFiles(): bool + { + if (!$this->hasCoverageTextShowUncoveredFiles()) { + throw new Exception; + } + + return $this->coverageTextShowUncoveredFiles; + } + + /** + * @phpstan-assert-if-true !null $this->coverageTextShowOnlySummary + */ + public function hasCoverageTextShowOnlySummary(): bool + { + return $this->coverageTextShowOnlySummary !== null; + } + + /** + * @throws Exception + */ + public function coverageTextShowOnlySummary(): bool + { + if (!$this->hasCoverageTextShowOnlySummary()) { + throw new Exception; + } + + return $this->coverageTextShowOnlySummary; + } + + /** + * @phpstan-assert-if-true !null $this->coverageXml + */ + public function hasCoverageXml(): bool + { + return $this->coverageXml !== null; + } + + /** + * @throws Exception + */ + public function coverageXml(): string + { + if (!$this->hasCoverageXml()) { + throw new Exception; + } + + return $this->coverageXml; + } + + /** + * @phpstan-assert-if-true !null $this->pathCoverage + */ + public function hasPathCoverage(): bool + { + return $this->pathCoverage !== null; + } + + /** + * @throws Exception + */ + public function pathCoverage(): bool + { + if (!$this->hasPathCoverage()) { + throw new Exception; + } + + return $this->pathCoverage; + } + + public function warmCoverageCache(): bool + { + return $this->warmCoverageCache; + } + + /** + * @phpstan-assert-if-true !null $this->defaultTimeLimit + */ + public function hasDefaultTimeLimit(): bool + { + return $this->defaultTimeLimit !== null; + } + + /** + * @throws Exception + */ + public function defaultTimeLimit(): int + { + if (!$this->hasDefaultTimeLimit()) { + throw new Exception; + } + + return $this->defaultTimeLimit; + } + + /** + * @phpstan-assert-if-true !null $this->disableCodeCoverageIgnore + */ + public function hasDisableCodeCoverageIgnore(): bool + { + return $this->disableCodeCoverageIgnore !== null; + } + + /** + * @throws Exception + */ + public function disableCodeCoverageIgnore(): bool + { + if (!$this->hasDisableCodeCoverageIgnore()) { + throw new Exception; + } + + return $this->disableCodeCoverageIgnore; + } + + /** + * @phpstan-assert-if-true !null $this->disallowTestOutput + */ + public function hasDisallowTestOutput(): bool + { + return $this->disallowTestOutput !== null; + } + + /** + * @throws Exception + */ + public function disallowTestOutput(): bool + { + if (!$this->hasDisallowTestOutput()) { + throw new Exception; + } + + return $this->disallowTestOutput; + } + + /** + * @phpstan-assert-if-true !null $this->enforceTimeLimit + */ + public function hasEnforceTimeLimit(): bool + { + return $this->enforceTimeLimit !== null; + } + + /** + * @throws Exception + */ + public function enforceTimeLimit(): bool + { + if (!$this->hasEnforceTimeLimit()) { + throw new Exception; + } + + return $this->enforceTimeLimit; + } + + /** + * @phpstan-assert-if-true !null $this->excludeGroups + */ + public function hasExcludeGroups(): bool + { + return $this->excludeGroups !== null; + } + + /** + * @throws Exception + * + * @return non-empty-list + */ + public function excludeGroups(): array + { + if (!$this->hasExcludeGroups()) { + throw new Exception; + } + + return $this->excludeGroups; + } + + /** + * @phpstan-assert-if-true !null $this->executionOrder + */ + public function hasExecutionOrder(): bool + { + return $this->executionOrder !== null; + } + + /** + * @throws Exception + */ + public function executionOrder(): int + { + if (!$this->hasExecutionOrder()) { + throw new Exception; + } + + return $this->executionOrder; + } + + /** + * @phpstan-assert-if-true !null $this->executionOrderDefects + */ + public function hasExecutionOrderDefects(): bool + { + return $this->executionOrderDefects !== null; + } + + /** + * @throws Exception + */ + public function executionOrderDefects(): int + { + if (!$this->hasExecutionOrderDefects()) { + throw new Exception; + } + + return $this->executionOrderDefects; + } + + /** + * @phpstan-assert-if-true !null $this->failOnAllIssues + */ + public function hasFailOnAllIssues(): bool + { + return $this->failOnAllIssues !== null; + } + + /** + * @throws Exception + */ + public function failOnAllIssues(): bool + { + if (!$this->hasFailOnAllIssues()) { + throw new Exception; + } + + return $this->failOnAllIssues; + } + + /** + * @phpstan-assert-if-true !null $this->failOnDeprecation + */ + public function hasFailOnDeprecation(): bool + { + return $this->failOnDeprecation !== null; + } + + /** + * @throws Exception + */ + public function failOnDeprecation(): bool + { + if (!$this->hasFailOnDeprecation()) { + throw new Exception; + } + + return $this->failOnDeprecation; + } + + /** + * @phpstan-assert-if-true !null $this->failOnPhpunitDeprecation + */ + public function hasFailOnPhpunitDeprecation(): bool + { + return $this->failOnPhpunitDeprecation !== null; + } + + /** + * @throws Exception + */ + public function failOnPhpunitDeprecation(): bool + { + if (!$this->hasFailOnPhpunitDeprecation()) { + throw new Exception; + } + + return $this->failOnPhpunitDeprecation; + } + + /** + * @phpstan-assert-if-true !null $this->failOnPhpunitNotice + */ + public function hasFailOnPhpunitNotice(): bool + { + return $this->failOnPhpunitNotice !== null; + } + + /** + * @throws Exception + */ + public function failOnPhpunitNotice(): bool + { + if (!$this->hasFailOnPhpunitNotice()) { + throw new Exception; + } + + return $this->failOnPhpunitNotice; + } + + /** + * @phpstan-assert-if-true !null $this->failOnPhpunitWarning + */ + public function hasFailOnPhpunitWarning(): bool + { + return $this->failOnPhpunitWarning !== null; + } + + /** + * @throws Exception + */ + public function failOnPhpunitWarning(): bool + { + if (!$this->hasFailOnPhpunitWarning()) { + throw new Exception; + } + + return $this->failOnPhpunitWarning; + } + + /** + * @phpstan-assert-if-true !null $this->failOnEmptyTestSuite + */ + public function hasFailOnEmptyTestSuite(): bool + { + return $this->failOnEmptyTestSuite !== null; + } + + /** + * @throws Exception + */ + public function failOnEmptyTestSuite(): bool + { + if (!$this->hasFailOnEmptyTestSuite()) { + throw new Exception; + } + + return $this->failOnEmptyTestSuite; + } + + /** + * @phpstan-assert-if-true !null $this->failOnIncomplete + */ + public function hasFailOnIncomplete(): bool + { + return $this->failOnIncomplete !== null; + } + + /** + * @throws Exception + */ + public function failOnIncomplete(): bool + { + if (!$this->hasFailOnIncomplete()) { + throw new Exception; + } + + return $this->failOnIncomplete; + } + + /** + * @phpstan-assert-if-true !null $this->failOnNotice + */ + public function hasFailOnNotice(): bool + { + return $this->failOnNotice !== null; + } + + /** + * @throws Exception + */ + public function failOnNotice(): bool + { + if (!$this->hasFailOnNotice()) { + throw new Exception; + } + + return $this->failOnNotice; + } + + /** + * @phpstan-assert-if-true !null $this->failOnRisky + */ + public function hasFailOnRisky(): bool + { + return $this->failOnRisky !== null; + } + + /** + * @throws Exception + */ + public function failOnRisky(): bool + { + if (!$this->hasFailOnRisky()) { + throw new Exception; + } + + return $this->failOnRisky; + } + + /** + * @phpstan-assert-if-true !null $this->failOnSkipped + */ + public function hasFailOnSkipped(): bool + { + return $this->failOnSkipped !== null; + } + + /** + * @throws Exception + */ + public function failOnSkipped(): bool + { + if (!$this->hasFailOnSkipped()) { + throw new Exception; + } + + return $this->failOnSkipped; + } + + /** + * @phpstan-assert-if-true !null $this->failOnWarning + */ + public function hasFailOnWarning(): bool + { + return $this->failOnWarning !== null; + } + + /** + * @throws Exception + */ + public function failOnWarning(): bool + { + if (!$this->hasFailOnWarning()) { + throw new Exception; + } + + return $this->failOnWarning; + } + + /** + * @phpstan-assert-if-true !null $this->doNotFailOnDeprecation + */ + public function hasDoNotFailOnDeprecation(): bool + { + return $this->doNotFailOnDeprecation !== null; + } + + /** + * @throws Exception + */ + public function doNotFailOnDeprecation(): bool + { + if (!$this->hasDoNotFailOnDeprecation()) { + throw new Exception; + } + + return $this->doNotFailOnDeprecation; + } + + /** + * @phpstan-assert-if-true !null $this->doNotFailOnPhpunitDeprecation + */ + public function hasDoNotFailOnPhpunitDeprecation(): bool + { + return $this->doNotFailOnPhpunitDeprecation !== null; + } + + /** + * @throws Exception + */ + public function doNotFailOnPhpunitDeprecation(): bool + { + if (!$this->hasDoNotFailOnPhpunitDeprecation()) { + throw new Exception; + } + + return $this->doNotFailOnPhpunitDeprecation; + } + + /** + * @phpstan-assert-if-true !null $this->doNotFailOnPhpunitNotice + */ + public function hasDoNotFailOnPhpunitNotice(): bool + { + return $this->doNotFailOnPhpunitNotice !== null; + } + + /** + * @throws Exception + */ + public function doNotFailOnPhpunitNotice(): bool + { + if (!$this->hasDoNotFailOnPhpunitNotice()) { + throw new Exception; + } + + return $this->doNotFailOnPhpunitNotice; + } + + /** + * @phpstan-assert-if-true !null $this->doNotFailOnPhpunitWarning + */ + public function hasDoNotFailOnPhpunitWarning(): bool + { + return $this->doNotFailOnPhpunitWarning !== null; + } + + /** + * @throws Exception + */ + public function doNotFailOnPhpunitWarning(): bool + { + if (!$this->hasDoNotFailOnPhpunitWarning()) { + throw new Exception; + } + + return $this->doNotFailOnPhpunitWarning; + } + + /** + * @phpstan-assert-if-true !null $this->doNotFailOnEmptyTestSuite + */ + public function hasDoNotFailOnEmptyTestSuite(): bool + { + return $this->doNotFailOnEmptyTestSuite !== null; + } + + /** + * @throws Exception + */ + public function doNotFailOnEmptyTestSuite(): bool + { + if (!$this->hasDoNotFailOnEmptyTestSuite()) { + throw new Exception; + } + + return $this->doNotFailOnEmptyTestSuite; + } + + /** + * @phpstan-assert-if-true !null $this->doNotFailOnIncomplete + */ + public function hasDoNotFailOnIncomplete(): bool + { + return $this->doNotFailOnIncomplete !== null; + } + + /** + * @throws Exception + */ + public function doNotFailOnIncomplete(): bool + { + if (!$this->hasDoNotFailOnIncomplete()) { + throw new Exception; + } + + return $this->doNotFailOnIncomplete; + } + + /** + * @phpstan-assert-if-true !null $this->doNotFailOnNotice + */ + public function hasDoNotFailOnNotice(): bool + { + return $this->doNotFailOnNotice !== null; + } + + /** + * @throws Exception + */ + public function doNotFailOnNotice(): bool + { + if (!$this->hasDoNotFailOnNotice()) { + throw new Exception; + } + + return $this->doNotFailOnNotice; + } + + /** + * @phpstan-assert-if-true !null $this->doNotFailOnRisky + */ + public function hasDoNotFailOnRisky(): bool + { + return $this->doNotFailOnRisky !== null; + } + + /** + * @throws Exception + */ + public function doNotFailOnRisky(): bool + { + if (!$this->hasDoNotFailOnRisky()) { + throw new Exception; + } + + return $this->doNotFailOnRisky; + } + + /** + * @phpstan-assert-if-true !null $this->doNotFailOnSkipped + */ + public function hasDoNotFailOnSkipped(): bool + { + return $this->doNotFailOnSkipped !== null; + } + + /** + * @throws Exception + */ + public function doNotFailOnSkipped(): bool + { + if (!$this->hasDoNotFailOnSkipped()) { + throw new Exception; + } + + return $this->doNotFailOnSkipped; + } + + /** + * @phpstan-assert-if-true !null $this->doNotFailOnWarning + */ + public function hasDoNotFailOnWarning(): bool + { + return $this->doNotFailOnWarning !== null; + } + + /** + * @throws Exception + */ + public function doNotFailOnWarning(): bool + { + if (!$this->hasDoNotFailOnWarning()) { + throw new Exception; + } + + return $this->doNotFailOnWarning; + } + + /** + * @phpstan-assert-if-true !null $this->stopOnDefect + */ + public function hasStopOnDefect(): bool + { + return $this->stopOnDefect !== null; + } + + /** + * @throws Exception + */ + public function stopOnDefect(): bool + { + if (!$this->hasStopOnDefect()) { + throw new Exception; + } + + return $this->stopOnDefect; + } + + /** + * @phpstan-assert-if-true !null $this->stopOnDeprecation + */ + public function hasStopOnDeprecation(): bool + { + return $this->stopOnDeprecation !== null; + } + + /** + * @throws Exception + */ + public function stopOnDeprecation(): bool + { + if (!$this->hasStopOnDeprecation()) { + throw new Exception; + } + + return $this->stopOnDeprecation; + } + + /** + * @phpstan-assert-if-true !null $this->specificDeprecationToStopOn + */ + public function hasSpecificDeprecationToStopOn(): bool + { + return $this->specificDeprecationToStopOn !== null; + } + + /** + * @throws Exception + */ + public function specificDeprecationToStopOn(): string + { + if (!$this->hasSpecificDeprecationToStopOn()) { + throw new Exception; + } + + return $this->specificDeprecationToStopOn; + } + + /** + * @phpstan-assert-if-true !null $this->stopOnError + */ + public function hasStopOnError(): bool + { + return $this->stopOnError !== null; + } + + /** + * @throws Exception + */ + public function stopOnError(): bool + { + if (!$this->hasStopOnError()) { + throw new Exception; + } + + return $this->stopOnError; + } + + /** + * @phpstan-assert-if-true !null $this->stopOnFailure + */ + public function hasStopOnFailure(): bool + { + return $this->stopOnFailure !== null; + } + + /** + * @throws Exception + */ + public function stopOnFailure(): bool + { + if (!$this->hasStopOnFailure()) { + throw new Exception; + } + + return $this->stopOnFailure; + } + + /** + * @phpstan-assert-if-true !null $this->stopOnIncomplete + */ + public function hasStopOnIncomplete(): bool + { + return $this->stopOnIncomplete !== null; + } + + /** + * @throws Exception + */ + public function stopOnIncomplete(): bool + { + if (!$this->hasStopOnIncomplete()) { + throw new Exception; + } + + return $this->stopOnIncomplete; + } + + /** + * @phpstan-assert-if-true !null $this->stopOnNotice + */ + public function hasStopOnNotice(): bool + { + return $this->stopOnNotice !== null; + } + + /** + * @throws Exception + */ + public function stopOnNotice(): bool + { + if (!$this->hasStopOnNotice()) { + throw new Exception; + } + + return $this->stopOnNotice; + } + + /** + * @phpstan-assert-if-true !null $this->stopOnRisky + */ + public function hasStopOnRisky(): bool + { + return $this->stopOnRisky !== null; + } + + /** + * @throws Exception + */ + public function stopOnRisky(): bool + { + if (!$this->hasStopOnRisky()) { + throw new Exception; + } + + return $this->stopOnRisky; + } + + /** + * @phpstan-assert-if-true !null $this->stopOnSkipped + */ + public function hasStopOnSkipped(): bool + { + return $this->stopOnSkipped !== null; + } + + /** + * @throws Exception + */ + public function stopOnSkipped(): bool + { + if (!$this->hasStopOnSkipped()) { + throw new Exception; + } + + return $this->stopOnSkipped; + } + + /** + * @phpstan-assert-if-true !null $this->stopOnWarning + */ + public function hasStopOnWarning(): bool + { + return $this->stopOnWarning !== null; + } + + /** + * @throws Exception + */ + public function stopOnWarning(): bool + { + if (!$this->hasStopOnWarning()) { + throw new Exception; + } + + return $this->stopOnWarning; + } + + /** + * @phpstan-assert-if-true !null $this->excludeFilter + */ + public function hasExcludeFilter(): bool + { + return $this->excludeFilter !== null; + } + + /** + * @throws Exception + */ + public function excludeFilter(): string + { + if (!$this->hasExcludeFilter()) { + throw new Exception; + } + + return $this->excludeFilter; + } + + /** + * @phpstan-assert-if-true !null $this->filter + */ + public function hasFilter(): bool + { + return $this->filter !== null; + } + + /** + * @throws Exception + */ + public function filter(): string + { + if (!$this->hasFilter()) { + throw new Exception; + } + + return $this->filter; + } + + /** + * @phpstan-assert-if-true !null $this->generateBaseline + */ + public function hasGenerateBaseline(): bool + { + return $this->generateBaseline !== null; + } + + /** + * @throws Exception + */ + public function generateBaseline(): string + { + if (!$this->hasGenerateBaseline()) { + throw new Exception; + } + + return $this->generateBaseline; + } + + /** + * @phpstan-assert-if-true !null $this->useBaseline + */ + public function hasUseBaseline(): bool + { + return $this->useBaseline !== null; + } + + /** + * @throws Exception + */ + public function useBaseline(): string + { + if (!$this->hasUseBaseline()) { + throw new Exception; + } + + return $this->useBaseline; + } + + public function ignoreBaseline(): bool + { + return $this->ignoreBaseline; + } + + public function generateConfiguration(): bool + { + return $this->generateConfiguration; + } + + public function migrateConfiguration(): bool + { + return $this->migrateConfiguration; + } + + /** + * @phpstan-assert-if-true !null $this->groups + */ + public function hasGroups(): bool + { + return $this->groups !== null; + } + + /** + * @throws Exception + * + * @return non-empty-list + */ + public function groups(): array + { + if (!$this->hasGroups()) { + throw new Exception; + } + + return $this->groups; + } + + /** + * @phpstan-assert-if-true !null $this->testsCovering + */ + public function hasTestsCovering(): bool + { + return $this->testsCovering !== null; + } + + /** + * @throws Exception + * + * @return non-empty-list + */ + public function testsCovering(): array + { + if (!$this->hasTestsCovering()) { + throw new Exception; + } + + return $this->testsCovering; + } + + /** + * @phpstan-assert-if-true !null $this->testsUsing + */ + public function hasTestsUsing(): bool + { + return $this->testsUsing !== null; + } + + /** + * @throws Exception + * + * @return non-empty-list + */ + public function testsUsing(): array + { + if (!$this->hasTestsUsing()) { + throw new Exception; + } + + return $this->testsUsing; + } + + /** + * @phpstan-assert-if-true !null $this->testsRequiringPhpExtension + */ + public function hasTestsRequiringPhpExtension(): bool + { + return $this->testsRequiringPhpExtension !== null; + } + + /** + * @throws Exception + * + * @return non-empty-list + */ + public function testsRequiringPhpExtension(): array + { + if (!$this->hasTestsRequiringPhpExtension()) { + throw new Exception; + } + + return $this->testsRequiringPhpExtension; + } + + public function help(): bool + { + return $this->help; + } + + /** + * @phpstan-assert-if-true !null $this->includePath + */ + public function hasIncludePath(): bool + { + return $this->includePath !== null; + } + + /** + * @throws Exception + */ + public function includePath(): string + { + if (!$this->hasIncludePath()) { + throw new Exception; + } + + return $this->includePath; + } + + /** + * @phpstan-assert-if-true !null $this->iniSettings + */ + public function hasIniSettings(): bool + { + return $this->iniSettings !== null; + } + + /** + * @throws Exception + * + * @return non-empty-array + */ + public function iniSettings(): array + { + if (!$this->hasIniSettings()) { + throw new Exception; + } + + return $this->iniSettings; + } + + /** + * @phpstan-assert-if-true !null $this->junitLogfile + */ + public function hasJunitLogfile(): bool + { + return $this->junitLogfile !== null; + } + + /** + * @throws Exception + */ + public function junitLogfile(): string + { + if (!$this->hasJunitLogfile()) { + throw new Exception; + } + + return $this->junitLogfile; + } + + /** + * @phpstan-assert-if-true !null $this->otrLogfile + */ + public function hasOtrLogfile(): bool + { + return $this->otrLogfile !== null; + } + + /** + * @throws Exception + */ + public function otrLogfile(): string + { + if (!$this->hasOtrLogfile()) { + throw new Exception; + } + + return $this->otrLogfile; + } + + /** + * @phpstan-assert-if-true !null $this->includeGitInformationInOtrLogfile + */ + public function hasIncludeGitInformationInOtrLogfile(): bool + { + return $this->includeGitInformationInOtrLogfile !== null; + } + + /** + * @throws Exception + */ + public function includeGitInformationInOtrLogfile(): bool + { + if (!$this->hasIncludeGitInformationInOtrLogfile()) { + throw new Exception; + } + + return $this->includeGitInformationInOtrLogfile; + } + + public function listGroups(): bool + { + return $this->listGroups; + } + + public function listSuites(): bool + { + return $this->listSuites; + } + + public function listTestFiles(): bool + { + return $this->listTestFiles; + } + + public function listTests(): bool + { + return $this->listTests; + } + + /** + * @phpstan-assert-if-true !null $this->listTestsXml + */ + public function hasListTestsXml(): bool + { + return $this->listTestsXml !== null; + } + + /** + * @throws Exception + */ + public function listTestsXml(): string + { + if (!$this->hasListTestsXml()) { + throw new Exception; + } + + return $this->listTestsXml; + } + + /** + * @phpstan-assert-if-true !null $this->noCoverage + */ + public function hasNoCoverage(): bool + { + return $this->noCoverage !== null; + } + + /** + * @throws Exception + */ + public function noCoverage(): bool + { + if (!$this->hasNoCoverage()) { + throw new Exception; + } + + return $this->noCoverage; + } + + /** + * @phpstan-assert-if-true !null $this->noExtensions + */ + public function hasNoExtensions(): bool + { + return $this->noExtensions !== null; + } + + /** + * @throws Exception + */ + public function noExtensions(): bool + { + if (!$this->hasNoExtensions()) { + throw new Exception; + } + + return $this->noExtensions; + } + + /** + * @phpstan-assert-if-true !null $this->noOutput + */ + public function hasNoOutput(): bool + { + return $this->noOutput !== null; + } + + /** + * @throws Exception + */ + public function noOutput(): bool + { + if ($this->noOutput === null) { + throw new Exception; + } + + return $this->noOutput; + } + + /** + * @phpstan-assert-if-true !null $this->noProgress + */ + public function hasNoProgress(): bool + { + return $this->noProgress !== null; + } + + /** + * @throws Exception + */ + public function noProgress(): bool + { + if ($this->noProgress === null) { + throw new Exception; + } + + return $this->noProgress; + } + + /** + * @phpstan-assert-if-true !null $this->noResults + */ + public function hasNoResults(): bool + { + return $this->noResults !== null; + } + + /** + * @throws Exception + */ + public function noResults(): bool + { + if ($this->noResults === null) { + throw new Exception; + } + + return $this->noResults; + } + + /** + * @phpstan-assert-if-true !null $this->noLogging + */ + public function hasNoLogging(): bool + { + return $this->noLogging !== null; + } + + /** + * @throws Exception + */ + public function noLogging(): bool + { + if (!$this->hasNoLogging()) { + throw new Exception; + } + + return $this->noLogging; + } + + /** + * @phpstan-assert-if-true !null $this->processIsolation + */ + public function hasProcessIsolation(): bool + { + return $this->processIsolation !== null; + } + + /** + * @throws Exception + */ + public function processIsolation(): bool + { + if (!$this->hasProcessIsolation()) { + throw new Exception; + } + + return $this->processIsolation; + } + + /** + * @phpstan-assert-if-true !null $this->randomOrderSeed + */ + public function hasRandomOrderSeed(): bool + { + return $this->randomOrderSeed !== null; + } + + /** + * @throws Exception + */ + public function randomOrderSeed(): int + { + if (!$this->hasRandomOrderSeed()) { + throw new Exception; + } + + return $this->randomOrderSeed; + } + + /** + * @phpstan-assert-if-true !null $this->reportUselessTests + */ + public function hasReportUselessTests(): bool + { + return $this->reportUselessTests !== null; + } + + /** + * @throws Exception + */ + public function reportUselessTests(): bool + { + if (!$this->hasReportUselessTests()) { + throw new Exception; + } + + return $this->reportUselessTests; + } + + /** + * @phpstan-assert-if-true !null $this->resolveDependencies + */ + public function hasResolveDependencies(): bool + { + return $this->resolveDependencies !== null; + } + + /** + * @throws Exception + */ + public function resolveDependencies(): bool + { + if (!$this->hasResolveDependencies()) { + throw new Exception; + } + + return $this->resolveDependencies; + } + + /** + * @phpstan-assert-if-true !null $this->reverseList + */ + public function hasReverseList(): bool + { + return $this->reverseList !== null; + } + + /** + * @throws Exception + */ + public function reverseList(): bool + { + if (!$this->hasReverseList()) { + throw new Exception; + } + + return $this->reverseList; + } + + /** + * @phpstan-assert-if-true !null $this->stderr + */ + public function hasStderr(): bool + { + return $this->stderr !== null; + } + + /** + * @throws Exception + */ + public function stderr(): bool + { + if (!$this->hasStderr()) { + throw new Exception; + } + + return $this->stderr; + } + + /** + * @phpstan-assert-if-true !null $this->strictCoverage + */ + public function hasStrictCoverage(): bool + { + return $this->strictCoverage !== null; + } + + /** + * @throws Exception + */ + public function strictCoverage(): bool + { + if (!$this->hasStrictCoverage()) { + throw new Exception; + } + + return $this->strictCoverage; + } + + /** + * @phpstan-assert-if-true !null $this->teamcityLogfile + */ + public function hasTeamcityLogfile(): bool + { + return $this->teamcityLogfile !== null; + } + + /** + * @throws Exception + */ + public function teamcityLogfile(): string + { + if (!$this->hasTeamcityLogfile()) { + throw new Exception; + } + + return $this->teamcityLogfile; + } + + /** + * @phpstan-assert-if-true !null $this->teamCityPrinter + */ + public function hasTeamCityPrinter(): bool + { + return $this->teamCityPrinter !== null; + } + + /** + * @throws Exception + */ + public function teamCityPrinter(): bool + { + if (!$this->hasTeamCityPrinter()) { + throw new Exception; + } + + return $this->teamCityPrinter; + } + + /** + * @phpstan-assert-if-true !null $this->testdoxHtmlFile + */ + public function hasTestdoxHtmlFile(): bool + { + return $this->testdoxHtmlFile !== null; + } + + /** + * @throws Exception + */ + public function testdoxHtmlFile(): string + { + if (!$this->hasTestdoxHtmlFile()) { + throw new Exception; + } + + return $this->testdoxHtmlFile; + } + + /** + * @phpstan-assert-if-true !null $this->testdoxTextFile + */ + public function hasTestdoxTextFile(): bool + { + return $this->testdoxTextFile !== null; + } + + /** + * @throws Exception + */ + public function testdoxTextFile(): string + { + if (!$this->hasTestdoxTextFile()) { + throw new Exception; + } + + return $this->testdoxTextFile; + } + + /** + * @phpstan-assert-if-true !null $this->testdoxPrinter + */ + public function hasTestDoxPrinter(): bool + { + return $this->testdoxPrinter !== null; + } + + /** + * @throws Exception + */ + public function testdoxPrinter(): bool + { + if (!$this->hasTestDoxPrinter()) { + throw new Exception; + } + + return $this->testdoxPrinter; + } + + /** + * @phpstan-assert-if-true !null $this->testdoxPrinterSummary + */ + public function hasTestDoxPrinterSummary(): bool + { + return $this->testdoxPrinterSummary !== null; + } + + /** + * @throws Exception + */ + public function testdoxPrinterSummary(): bool + { + if (!$this->hasTestDoxPrinterSummary()) { + throw new Exception; + } + + return $this->testdoxPrinterSummary; + } + + /** + * @phpstan-assert-if-true !null $this->testSuffixes + */ + public function hasTestSuffixes(): bool + { + return $this->testSuffixes !== null; + } + + /** + * @throws Exception + * + * @return non-empty-list + */ + public function testSuffixes(): array + { + if (!$this->hasTestSuffixes()) { + throw new Exception; + } + + return $this->testSuffixes; + } + + /** + * @phpstan-assert-if-true !null $this->testSuite + */ + public function hasTestSuite(): bool + { + return $this->testSuite !== null; + } + + /** + * @throws Exception + */ + public function testSuite(): string + { + if (!$this->hasTestSuite()) { + throw new Exception; + } + + return $this->testSuite; + } + + /** + * @phpstan-assert-if-true !null $this->excludeTestSuite + */ + public function hasExcludedTestSuite(): bool + { + return $this->excludeTestSuite !== null; + } + + /** + * @throws Exception + */ + public function excludedTestSuite(): string + { + if (!$this->hasExcludedTestSuite()) { + throw new Exception; + } + + return $this->excludeTestSuite; + } + + public function useDefaultConfiguration(): bool + { + return $this->useDefaultConfiguration; + } + + /** + * @phpstan-assert-if-true !null $this->displayDetailsOnAllIssues + */ + public function hasDisplayDetailsOnAllIssues(): bool + { + return $this->displayDetailsOnAllIssues !== null; + } + + /** + * @throws Exception + */ + public function displayDetailsOnAllIssues(): bool + { + if (!$this->hasDisplayDetailsOnAllIssues()) { + throw new Exception; + } + + return $this->displayDetailsOnAllIssues; + } + + /** + * @phpstan-assert-if-true !null $this->displayDetailsOnIncompleteTests + */ + public function hasDisplayDetailsOnIncompleteTests(): bool + { + return $this->displayDetailsOnIncompleteTests !== null; + } + + /** + * @throws Exception + */ + public function displayDetailsOnIncompleteTests(): bool + { + if (!$this->hasDisplayDetailsOnIncompleteTests()) { + throw new Exception; + } + + return $this->displayDetailsOnIncompleteTests; + } + + /** + * @phpstan-assert-if-true !null $this->displayDetailsOnSkippedTests + */ + public function hasDisplayDetailsOnSkippedTests(): bool + { + return $this->displayDetailsOnSkippedTests !== null; + } + + /** + * @throws Exception + */ + public function displayDetailsOnSkippedTests(): bool + { + if (!$this->hasDisplayDetailsOnSkippedTests()) { + throw new Exception; + } + + return $this->displayDetailsOnSkippedTests; + } + + /** + * @phpstan-assert-if-true !null $this->displayDetailsOnTestsThatTriggerDeprecations + */ + public function hasDisplayDetailsOnTestsThatTriggerDeprecations(): bool + { + return $this->displayDetailsOnTestsThatTriggerDeprecations !== null; + } + + /** + * @throws Exception + */ + public function displayDetailsOnTestsThatTriggerDeprecations(): bool + { + if (!$this->hasDisplayDetailsOnTestsThatTriggerDeprecations()) { + throw new Exception; + } + + return $this->displayDetailsOnTestsThatTriggerDeprecations; + } + + /** + * @phpstan-assert-if-true !null $this->displayDetailsOnPhpunitDeprecations + */ + public function hasDisplayDetailsOnPhpunitDeprecations(): bool + { + return $this->displayDetailsOnPhpunitDeprecations !== null; + } + + /** + * @throws Exception + */ + public function displayDetailsOnPhpunitDeprecations(): bool + { + if (!$this->hasDisplayDetailsOnPhpunitDeprecations()) { + throw new Exception; + } + + return $this->displayDetailsOnPhpunitDeprecations; + } + + /** + * @phpstan-assert-if-true !null $this->displayDetailsOnPhpunitNotices + */ + public function hasDisplayDetailsOnPhpunitNotices(): bool + { + return $this->displayDetailsOnPhpunitNotices !== null; + } + + /** + * @throws Exception + */ + public function displayDetailsOnPhpunitNotices(): bool + { + if (!$this->hasDisplayDetailsOnPhpunitNotices()) { + throw new Exception; + } + + return $this->displayDetailsOnPhpunitNotices; + } + + /** + * @phpstan-assert-if-true !null $this->displayDetailsOnTestsThatTriggerErrors + */ + public function hasDisplayDetailsOnTestsThatTriggerErrors(): bool + { + return $this->displayDetailsOnTestsThatTriggerErrors !== null; + } + + /** + * @throws Exception + */ + public function displayDetailsOnTestsThatTriggerErrors(): bool + { + if (!$this->hasDisplayDetailsOnTestsThatTriggerErrors()) { + throw new Exception; + } + + return $this->displayDetailsOnTestsThatTriggerErrors; + } + + /** + * @phpstan-assert-if-true !null $this->displayDetailsOnTestsThatTriggerNotices + */ + public function hasDisplayDetailsOnTestsThatTriggerNotices(): bool + { + return $this->displayDetailsOnTestsThatTriggerNotices !== null; + } + + /** + * @throws Exception + */ + public function displayDetailsOnTestsThatTriggerNotices(): bool + { + if (!$this->hasDisplayDetailsOnTestsThatTriggerNotices()) { + throw new Exception; + } + + return $this->displayDetailsOnTestsThatTriggerNotices; + } + + /** + * @phpstan-assert-if-true !null $this->displayDetailsOnTestsThatTriggerWarnings + */ + public function hasDisplayDetailsOnTestsThatTriggerWarnings(): bool + { + return $this->displayDetailsOnTestsThatTriggerWarnings !== null; + } + + /** + * @throws Exception + */ + public function displayDetailsOnTestsThatTriggerWarnings(): bool + { + if (!$this->hasDisplayDetailsOnTestsThatTriggerWarnings()) { + throw new Exception; + } + + return $this->displayDetailsOnTestsThatTriggerWarnings; + } + + public function version(): bool + { + return $this->version; + } + + /** + * @phpstan-assert-if-true !null $this->logEventsText + */ + public function hasLogEventsText(): bool + { + return $this->logEventsText !== null; + } + + /** + * @throws Exception + */ + public function logEventsText(): string + { + if (!$this->hasLogEventsText()) { + throw new Exception; + } + + return $this->logEventsText; + } + + /** + * @phpstan-assert-if-true !null $this->logEventsVerboseText + */ + public function hasLogEventsVerboseText(): bool + { + return $this->logEventsVerboseText !== null; + } + + /** + * @throws Exception + */ + public function logEventsVerboseText(): string + { + if (!$this->hasLogEventsVerboseText()) { + throw new Exception; + } + + return $this->logEventsVerboseText; + } + + public function debug(): bool + { + return $this->debug; + } + + public function withTelemetry(): bool + { + return $this->withTelemetry; + } + + /** + * @phpstan-assert-if-true !null $this->extensions + */ + public function hasExtensions(): bool + { + return $this->extensions !== null; + } + + /** + * @throws Exception + * + * @return non-empty-list + */ + public function extensions(): array + { + if (!$this->hasExtensions()) { + throw new Exception; + } + + return $this->extensions; + } +} diff --git a/src/TextUI/Configuration/Cli/Exception.php b/src/TextUI/Configuration/Cli/Exception.php new file mode 100644 index 00000000000..0d9a5a00e6e --- /dev/null +++ b/src/TextUI/Configuration/Cli/Exception.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\CliArguments; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Exception extends RuntimeException implements \PHPUnit\Exception +{ +} diff --git a/src/TextUI/Configuration/Cli/XmlConfigurationFileFinder.php b/src/TextUI/Configuration/Cli/XmlConfigurationFileFinder.php new file mode 100644 index 00000000000..5357ef63d0a --- /dev/null +++ b/src/TextUI/Configuration/Cli/XmlConfigurationFileFinder.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\CliArguments; + +use function getcwd; +use function is_dir; +use function is_file; +use function realpath; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class XmlConfigurationFileFinder +{ + public function find(Configuration $configuration): false|string + { + $useDefaultConfiguration = $configuration->useDefaultConfiguration(); + + if ($configuration->hasConfigurationFile()) { + if (is_dir($configuration->configurationFile())) { + $candidate = $this->configurationFileInDirectory($configuration->configurationFile()); + + if ($candidate !== false) { + return $candidate; + } + + return false; + } + + return $configuration->configurationFile(); + } + + if ($useDefaultConfiguration) { + $directory = getcwd(); + + if ($directory !== false) { + $candidate = $this->configurationFileInDirectory($directory); + + if ($candidate !== false) { + return $candidate; + } + } + } + + return false; + } + + private function configurationFileInDirectory(string $directory): false|string + { + $candidates = [ + $directory . '/phpunit.xml', + $directory . '/phpunit.dist.xml', + $directory . '/phpunit.xml.dist', + ]; + + foreach ($candidates as $candidate) { + if (is_file($candidate)) { + return realpath($candidate); + } + } + + return false; + } +} diff --git a/src/TextUI/Configuration/CodeCoverageFilterRegistry.php b/src/TextUI/Configuration/CodeCoverageFilterRegistry.php new file mode 100644 index 00000000000..7051d2840d8 --- /dev/null +++ b/src/TextUI/Configuration/CodeCoverageFilterRegistry.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function array_keys; +use function assert; +use SebastianBergmann\CodeCoverage\Filter; + +/** + * CLI options and XML configuration are static within a single PHPUnit process. + * It is therefore okay to use a Singleton registry here. + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CodeCoverageFilterRegistry +{ + private static ?self $instance = null; + private ?Filter $filter = null; + private bool $configured = false; + + public static function instance(): self + { + if (self::$instance === null) { + self::$instance = new self; + } + + return self::$instance; + } + + /** + * @codeCoverageIgnore + */ + public function get(): Filter + { + assert($this->filter !== null); + + return $this->filter; + } + + /** + * @codeCoverageIgnore + */ + public function init(Configuration $configuration, bool $force = false): void + { + if (!$configuration->hasCoverageReport() && !$force) { + return; + } + + if ($this->configured && !$force) { + return; + } + + $this->filter = new Filter; + + if ($configuration->source()->notEmpty()) { + $this->filter->includeFiles(array_keys((new SourceMapper)->map($configuration->source()))); + + $this->configured = true; + } + } + + /** + * @codeCoverageIgnore + */ + public function configured(): bool + { + return $this->configured; + } +} diff --git a/src/TextUI/Configuration/Configuration.php b/src/TextUI/Configuration/Configuration.php new file mode 100644 index 00000000000..82631e5e675 --- /dev/null +++ b/src/TextUI/Configuration/Configuration.php @@ -0,0 +1,1529 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function explode; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Configuration +{ + public const string COLOR_NEVER = 'never'; + public const string COLOR_AUTO = 'auto'; + public const string COLOR_ALWAYS = 'always'; + public const string COLOR_DEFAULT = self::COLOR_NEVER; + + /** + * @var list + */ + private array $cliArguments; + private ?string $configurationFile; + private ?string $bootstrap; + + /** + * @var array + */ + private array $bootstrapForTestSuite; + private bool $cacheResult; + private ?string $cacheDirectory; + private ?string $coverageCacheDirectory; + private Source $source; + private bool $pathCoverage; + private ?string $coverageClover; + private ?string $coverageCobertura; + private ?string $coverageCrap4j; + private int $coverageCrap4jThreshold; + private ?string $coverageHtml; + private int $coverageHtmlLowUpperBound; + private int $coverageHtmlHighLowerBound; + private string $coverageHtmlColorSuccessLow; + private string $coverageHtmlColorSuccessMedium; + private string $coverageHtmlColorSuccessHigh; + private string $coverageHtmlColorWarning; + private string $coverageHtmlColorDanger; + private ?string $coverageHtmlCustomCssFile; + private ?string $coverageOpenClover; + private ?string $coveragePhp; + private ?string $coverageText; + private bool $coverageTextShowUncoveredFiles; + private bool $coverageTextShowOnlySummary; + private ?string $coverageXml; + private string $testResultCacheFile; + private bool $ignoreDeprecatedCodeUnitsFromCodeCoverage; + private bool $disableCodeCoverageIgnore; + private bool $failOnAllIssues; + private bool $failOnDeprecation; + private bool $failOnPhpunitDeprecation; + private bool $failOnPhpunitNotice; + private bool $failOnPhpunitWarning; + private bool $failOnEmptyTestSuite; + private bool $failOnIncomplete; + private bool $failOnNotice; + private bool $failOnRisky; + private bool $failOnSkipped; + private bool $failOnWarning; + private bool $doNotFailOnDeprecation; + private bool $doNotFailOnPhpunitDeprecation; + private bool $doNotFailOnPhpunitNotice; + private bool $doNotFailOnPhpunitWarning; + private bool $doNotFailOnEmptyTestSuite; + private bool $doNotFailOnIncomplete; + private bool $doNotFailOnNotice; + private bool $doNotFailOnRisky; + private bool $doNotFailOnSkipped; + private bool $doNotFailOnWarning; + private bool $stopOnDefect; + private bool $stopOnDeprecation; + private ?string $specificDeprecationToStopOn; + private bool $stopOnError; + private bool $stopOnFailure; + private bool $stopOnIncomplete; + private bool $stopOnNotice; + private bool $stopOnRisky; + private bool $stopOnSkipped; + private bool $stopOnWarning; + private bool $outputToStandardErrorStream; + private int $columns; + private bool $noExtensions; + + /** + * @var ?non-empty-string + */ + private ?string $pharExtensionDirectory; + + /** + * @var list}> + */ + private array $extensionBootstrappers; + private bool $backupGlobals; + private bool $backupStaticProperties; + private bool $beStrictAboutChangesToGlobalState; + private bool $colors; + private bool $processIsolation; + private bool $enforceTimeLimit; + private int $defaultTimeLimit; + private int $timeoutForSmallTests; + private int $timeoutForMediumTests; + private int $timeoutForLargeTests; + private bool $reportUselessTests; + private bool $strictCoverage; + private bool $disallowTestOutput; + private bool $displayDetailsOnAllIssues; + private bool $displayDetailsOnIncompleteTests; + private bool $displayDetailsOnSkippedTests; + private bool $displayDetailsOnTestsThatTriggerDeprecations; + private bool $displayDetailsOnPhpunitDeprecations; + private bool $displayDetailsOnPhpunitNotices; + private bool $displayDetailsOnTestsThatTriggerErrors; + private bool $displayDetailsOnTestsThatTriggerNotices; + private bool $displayDetailsOnTestsThatTriggerWarnings; + private bool $reverseDefectList; + private bool $requireCoverageMetadata; + private bool $noProgress; + private bool $noResults; + private bool $noOutput; + private int $executionOrder; + private int $executionOrderDefects; + private bool $resolveDependencies; + private ?string $logfileTeamcity; + private ?string $logfileJunit; + private ?string $logfileOtr; + private bool $includeGitInformationInOtrLogfile; + private ?string $logfileTestdoxHtml; + private ?string $logfileTestdoxText; + private ?string $logEventsText; + private ?string $logEventsVerboseText; + + /** + * @var ?non-empty-list + */ + private ?array $testsCovering; + + /** + * @var ?non-empty-list + */ + private ?array $testsUsing; + + /** + * @var ?non-empty-list + */ + private ?array $testsRequiringPhpExtension; + private bool $teamCityOutput; + private bool $testDoxOutput; + private bool $testDoxOutputSummary; + private ?string $filter; + private ?string $excludeFilter; + + /** + * @var list + */ + private array $groups; + + /** + * @var list + */ + private array $excludeGroups; + private int $randomOrderSeed; + private bool $includeUncoveredFiles; + private TestSuiteCollection $testSuite; + private string $includeTestSuite; + private string $excludeTestSuite; + private ?string $defaultTestSuite; + private bool $ignoreTestSelectionInXmlConfiguration; + + /** + * @var non-empty-list + */ + private array $testSuffixes; + private Php $php; + private bool $controlGarbageCollector; + private int $numberOfTestsBeforeGarbageCollection; + + /** + * @var null|non-empty-string + */ + private ?string $generateBaseline; + private bool $debug; + private bool $withTelemetry; + + /** + * @var non-negative-int + */ + private int $shortenArraysForExportThreshold; + + /** + * @param list $cliArguments + * @param array $bootstrapForTestSuite + * @param ?non-empty-string $pharExtensionDirectory + * @param list}> $extensionBootstrappers + * @param ?non-empty-list $testsCovering + * @param ?non-empty-list $testsUsing + * @param ?non-empty-list $testsRequiringPhpExtension + * @param list $groups + * @param list $excludeGroups + * @param non-empty-list $testSuffixes + * @param null|non-empty-string $generateBaseline + * @param non-negative-int $shortenArraysForExportThreshold + */ + public function __construct(array $cliArguments, ?string $configurationFile, ?string $bootstrap, array $bootstrapForTestSuite, bool $cacheResult, ?string $cacheDirectory, ?string $coverageCacheDirectory, Source $source, string $testResultCacheFile, ?string $coverageClover, ?string $coverageCobertura, ?string $coverageCrap4j, int $coverageCrap4jThreshold, ?string $coverageHtml, int $coverageHtmlLowUpperBound, int $coverageHtmlHighLowerBound, string $coverageHtmlColorSuccessLow, string $coverageHtmlColorSuccessMedium, string $coverageHtmlColorSuccessHigh, string $coverageHtmlColorWarning, string $coverageHtmlColorDanger, ?string $coverageHtmlCustomCssFile, ?string $coverageOpenClover, ?string $coveragePhp, ?string $coverageText, bool $coverageTextShowUncoveredFiles, bool $coverageTextShowOnlySummary, ?string $coverageXml, bool $pathCoverage, bool $ignoreDeprecatedCodeUnitsFromCodeCoverage, bool $disableCodeCoverageIgnore, bool $failOnAllIssues, bool $failOnDeprecation, bool $failOnPhpunitDeprecation, bool $failOnPhpunitNotice, bool $failOnPhpunitWarning, bool $failOnEmptyTestSuite, bool $failOnIncomplete, bool $failOnNotice, bool $failOnRisky, bool $failOnSkipped, bool $failOnWarning, bool $doNotFailOnDeprecation, bool $doNotFailOnPhpunitDeprecation, bool $doNotFailOnPhpunitNotice, bool $doNotFailOnPhpunitWarning, bool $doNotFailOnEmptyTestSuite, bool $doNotFailOnIncomplete, bool $doNotFailOnNotice, bool $doNotFailOnRisky, bool $doNotFailOnSkipped, bool $doNotFailOnWarning, bool $stopOnDefect, bool $stopOnDeprecation, ?string $specificDeprecationToStopOn, bool $stopOnError, bool $stopOnFailure, bool $stopOnIncomplete, bool $stopOnNotice, bool $stopOnRisky, bool $stopOnSkipped, bool $stopOnWarning, bool $outputToStandardErrorStream, int $columns, bool $noExtensions, ?string $pharExtensionDirectory, array $extensionBootstrappers, bool $backupGlobals, bool $backupStaticProperties, bool $beStrictAboutChangesToGlobalState, bool $colors, bool $processIsolation, bool $enforceTimeLimit, int $defaultTimeLimit, int $timeoutForSmallTests, int $timeoutForMediumTests, int $timeoutForLargeTests, bool $reportUselessTests, bool $strictCoverage, bool $disallowTestOutput, bool $displayDetailsOnAllIssues, bool $displayDetailsOnIncompleteTests, bool $displayDetailsOnSkippedTests, bool $displayDetailsOnTestsThatTriggerDeprecations, bool $displayDetailsOnPhpunitDeprecations, bool $displayDetailsOnPhpunitNotices, bool $displayDetailsOnTestsThatTriggerErrors, bool $displayDetailsOnTestsThatTriggerNotices, bool $displayDetailsOnTestsThatTriggerWarnings, bool $reverseDefectList, bool $requireCoverageMetadata, bool $noProgress, bool $noResults, bool $noOutput, int $executionOrder, int $executionOrderDefects, bool $resolveDependencies, ?string $logfileTeamcity, ?string $logfileJunit, ?string $logfileOtr, bool $includeGitInformationInOtrLogfile, ?string $logfileTestdoxHtml, ?string $logfileTestdoxText, ?string $logEventsText, ?string $logEventsVerboseText, bool $teamCityOutput, bool $testDoxOutput, bool $testDoxOutputSummary, ?array $testsCovering, ?array $testsUsing, ?array $testsRequiringPhpExtension, ?string $filter, ?string $excludeFilter, array $groups, array $excludeGroups, int $randomOrderSeed, bool $includeUncoveredFiles, TestSuiteCollection $testSuite, string $includeTestSuite, string $excludeTestSuite, ?string $defaultTestSuite, bool $ignoreTestSelectionInXmlConfiguration, array $testSuffixes, Php $php, bool $controlGarbageCollector, int $numberOfTestsBeforeGarbageCollection, ?string $generateBaseline, bool $debug, bool $withTelemetry, int $shortenArraysForExportThreshold) + { + $this->cliArguments = $cliArguments; + $this->configurationFile = $configurationFile; + $this->bootstrap = $bootstrap; + $this->bootstrapForTestSuite = $bootstrapForTestSuite; + $this->cacheResult = $cacheResult; + $this->cacheDirectory = $cacheDirectory; + $this->coverageCacheDirectory = $coverageCacheDirectory; + $this->source = $source; + $this->testResultCacheFile = $testResultCacheFile; + $this->coverageClover = $coverageClover; + $this->coverageCobertura = $coverageCobertura; + $this->coverageCrap4j = $coverageCrap4j; + $this->coverageCrap4jThreshold = $coverageCrap4jThreshold; + $this->coverageHtml = $coverageHtml; + $this->coverageHtmlLowUpperBound = $coverageHtmlLowUpperBound; + $this->coverageHtmlHighLowerBound = $coverageHtmlHighLowerBound; + $this->coverageHtmlColorSuccessLow = $coverageHtmlColorSuccessLow; + $this->coverageHtmlColorSuccessMedium = $coverageHtmlColorSuccessMedium; + $this->coverageHtmlColorSuccessHigh = $coverageHtmlColorSuccessHigh; + $this->coverageHtmlColorWarning = $coverageHtmlColorWarning; + $this->coverageHtmlColorDanger = $coverageHtmlColorDanger; + $this->coverageHtmlCustomCssFile = $coverageHtmlCustomCssFile; + $this->coverageOpenClover = $coverageOpenClover; + $this->coveragePhp = $coveragePhp; + $this->coverageText = $coverageText; + $this->coverageTextShowUncoveredFiles = $coverageTextShowUncoveredFiles; + $this->coverageTextShowOnlySummary = $coverageTextShowOnlySummary; + $this->coverageXml = $coverageXml; + $this->pathCoverage = $pathCoverage; + $this->ignoreDeprecatedCodeUnitsFromCodeCoverage = $ignoreDeprecatedCodeUnitsFromCodeCoverage; + $this->disableCodeCoverageIgnore = $disableCodeCoverageIgnore; + $this->failOnAllIssues = $failOnAllIssues; + $this->failOnDeprecation = $failOnDeprecation; + $this->failOnPhpunitDeprecation = $failOnPhpunitDeprecation; + $this->failOnPhpunitNotice = $failOnPhpunitNotice; + $this->failOnPhpunitWarning = $failOnPhpunitWarning; + $this->failOnEmptyTestSuite = $failOnEmptyTestSuite; + $this->failOnIncomplete = $failOnIncomplete; + $this->failOnNotice = $failOnNotice; + $this->failOnRisky = $failOnRisky; + $this->failOnSkipped = $failOnSkipped; + $this->failOnWarning = $failOnWarning; + $this->doNotFailOnDeprecation = $doNotFailOnDeprecation; + $this->doNotFailOnPhpunitDeprecation = $doNotFailOnPhpunitDeprecation; + $this->doNotFailOnPhpunitNotice = $doNotFailOnPhpunitNotice; + $this->doNotFailOnPhpunitWarning = $doNotFailOnPhpunitWarning; + $this->doNotFailOnEmptyTestSuite = $doNotFailOnEmptyTestSuite; + $this->doNotFailOnIncomplete = $doNotFailOnIncomplete; + $this->doNotFailOnNotice = $doNotFailOnNotice; + $this->doNotFailOnRisky = $doNotFailOnRisky; + $this->doNotFailOnSkipped = $doNotFailOnSkipped; + $this->doNotFailOnWarning = $doNotFailOnWarning; + $this->stopOnDefect = $stopOnDefect; + $this->stopOnDeprecation = $stopOnDeprecation; + $this->specificDeprecationToStopOn = $specificDeprecationToStopOn; + $this->stopOnError = $stopOnError; + $this->stopOnFailure = $stopOnFailure; + $this->stopOnIncomplete = $stopOnIncomplete; + $this->stopOnNotice = $stopOnNotice; + $this->stopOnRisky = $stopOnRisky; + $this->stopOnSkipped = $stopOnSkipped; + $this->stopOnWarning = $stopOnWarning; + $this->outputToStandardErrorStream = $outputToStandardErrorStream; + $this->columns = $columns; + $this->noExtensions = $noExtensions; + $this->pharExtensionDirectory = $pharExtensionDirectory; + $this->extensionBootstrappers = $extensionBootstrappers; + $this->backupGlobals = $backupGlobals; + $this->backupStaticProperties = $backupStaticProperties; + $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; + $this->colors = $colors; + $this->processIsolation = $processIsolation; + $this->enforceTimeLimit = $enforceTimeLimit; + $this->defaultTimeLimit = $defaultTimeLimit; + $this->timeoutForSmallTests = $timeoutForSmallTests; + $this->timeoutForMediumTests = $timeoutForMediumTests; + $this->timeoutForLargeTests = $timeoutForLargeTests; + $this->reportUselessTests = $reportUselessTests; + $this->strictCoverage = $strictCoverage; + $this->disallowTestOutput = $disallowTestOutput; + $this->displayDetailsOnAllIssues = $displayDetailsOnAllIssues; + $this->displayDetailsOnIncompleteTests = $displayDetailsOnIncompleteTests; + $this->displayDetailsOnSkippedTests = $displayDetailsOnSkippedTests; + $this->displayDetailsOnTestsThatTriggerDeprecations = $displayDetailsOnTestsThatTriggerDeprecations; + $this->displayDetailsOnPhpunitDeprecations = $displayDetailsOnPhpunitDeprecations; + $this->displayDetailsOnPhpunitNotices = $displayDetailsOnPhpunitNotices; + $this->displayDetailsOnTestsThatTriggerErrors = $displayDetailsOnTestsThatTriggerErrors; + $this->displayDetailsOnTestsThatTriggerNotices = $displayDetailsOnTestsThatTriggerNotices; + $this->displayDetailsOnTestsThatTriggerWarnings = $displayDetailsOnTestsThatTriggerWarnings; + $this->reverseDefectList = $reverseDefectList; + $this->requireCoverageMetadata = $requireCoverageMetadata; + $this->noProgress = $noProgress; + $this->noResults = $noResults; + $this->noOutput = $noOutput; + $this->executionOrder = $executionOrder; + $this->executionOrderDefects = $executionOrderDefects; + $this->resolveDependencies = $resolveDependencies; + $this->logfileTeamcity = $logfileTeamcity; + $this->logfileJunit = $logfileJunit; + $this->logfileOtr = $logfileOtr; + $this->includeGitInformationInOtrLogfile = $includeGitInformationInOtrLogfile; + $this->logfileTestdoxHtml = $logfileTestdoxHtml; + $this->logfileTestdoxText = $logfileTestdoxText; + $this->logEventsText = $logEventsText; + $this->logEventsVerboseText = $logEventsVerboseText; + $this->teamCityOutput = $teamCityOutput; + $this->testDoxOutput = $testDoxOutput; + $this->testDoxOutputSummary = $testDoxOutputSummary; + $this->testsCovering = $testsCovering; + $this->testsUsing = $testsUsing; + $this->testsRequiringPhpExtension = $testsRequiringPhpExtension; + $this->filter = $filter; + $this->excludeFilter = $excludeFilter; + $this->groups = $groups; + $this->excludeGroups = $excludeGroups; + $this->randomOrderSeed = $randomOrderSeed; + $this->includeUncoveredFiles = $includeUncoveredFiles; + $this->testSuite = $testSuite; + $this->includeTestSuite = $includeTestSuite; + $this->excludeTestSuite = $excludeTestSuite; + $this->defaultTestSuite = $defaultTestSuite; + $this->ignoreTestSelectionInXmlConfiguration = $ignoreTestSelectionInXmlConfiguration; + $this->testSuffixes = $testSuffixes; + $this->php = $php; + $this->controlGarbageCollector = $controlGarbageCollector; + $this->numberOfTestsBeforeGarbageCollection = $numberOfTestsBeforeGarbageCollection; + $this->generateBaseline = $generateBaseline; + $this->debug = $debug; + $this->withTelemetry = $withTelemetry; + $this->shortenArraysForExportThreshold = $shortenArraysForExportThreshold; + } + + /** + * @phpstan-assert-if-true !empty $this->cliArguments + */ + public function hasCliArguments(): bool + { + return $this->cliArguments !== []; + } + + /** + * @return list + */ + public function cliArguments(): array + { + return $this->cliArguments; + } + + /** + * @phpstan-assert-if-true !null $this->configurationFile + */ + public function hasConfigurationFile(): bool + { + return $this->configurationFile !== null; + } + + /** + * @throws NoConfigurationFileException + */ + public function configurationFile(): string + { + if (!$this->hasConfigurationFile()) { + throw new NoConfigurationFileException; + } + + return $this->configurationFile; + } + + /** + * @phpstan-assert-if-true !null $this->bootstrap + */ + public function hasBootstrap(): bool + { + return $this->bootstrap !== null; + } + + /** + * @throws NoBootstrapException + */ + public function bootstrap(): string + { + if (!$this->hasBootstrap()) { + throw new NoBootstrapException; + } + + return $this->bootstrap; + } + + /** + * @return array + */ + public function bootstrapForTestSuite(): array + { + return $this->bootstrapForTestSuite; + } + + public function cacheResult(): bool + { + return $this->cacheResult; + } + + /** + * @phpstan-assert-if-true !null $this->cacheDirectory + */ + public function hasCacheDirectory(): bool + { + return $this->cacheDirectory !== null; + } + + /** + * @throws NoCacheDirectoryException + */ + public function cacheDirectory(): string + { + if (!$this->hasCacheDirectory()) { + throw new NoCacheDirectoryException; + } + + return $this->cacheDirectory; + } + + /** + * @phpstan-assert-if-true !null $this->coverageCacheDirectory + */ + public function hasCoverageCacheDirectory(): bool + { + return $this->coverageCacheDirectory !== null; + } + + /** + * @throws NoCoverageCacheDirectoryException + */ + public function coverageCacheDirectory(): string + { + if (!$this->hasCoverageCacheDirectory()) { + throw new NoCoverageCacheDirectoryException; + } + + return $this->coverageCacheDirectory; + } + + public function source(): Source + { + return $this->source; + } + + public function testResultCacheFile(): string + { + return $this->testResultCacheFile; + } + + public function ignoreDeprecatedCodeUnitsFromCodeCoverage(): bool + { + return $this->ignoreDeprecatedCodeUnitsFromCodeCoverage; + } + + public function disableCodeCoverageIgnore(): bool + { + return $this->disableCodeCoverageIgnore; + } + + public function pathCoverage(): bool + { + return $this->pathCoverage; + } + + public function hasCoverageReport(): bool + { + return $this->hasCoverageClover() || + $this->hasCoverageCobertura() || + $this->hasCoverageCrap4j() || + $this->hasCoverageHtml() || + $this->hasCoverageOpenClover() || + $this->hasCoveragePhp() || + $this->hasCoverageText() || + $this->hasCoverageXml(); + } + + /** + * @phpstan-assert-if-true !null $this->coverageClover + */ + public function hasCoverageClover(): bool + { + return $this->coverageClover !== null; + } + + /** + * @throws CodeCoverageReportNotConfiguredException + */ + public function coverageClover(): string + { + if (!$this->hasCoverageClover()) { + throw new CodeCoverageReportNotConfiguredException; + } + + return $this->coverageClover; + } + + /** + * @phpstan-assert-if-true !null $this->coverageCobertura + */ + public function hasCoverageCobertura(): bool + { + return $this->coverageCobertura !== null; + } + + /** + * @throws CodeCoverageReportNotConfiguredException + */ + public function coverageCobertura(): string + { + if (!$this->hasCoverageCobertura()) { + throw new CodeCoverageReportNotConfiguredException; + } + + return $this->coverageCobertura; + } + + /** + * @phpstan-assert-if-true !null $this->coverageCrap4j + */ + public function hasCoverageCrap4j(): bool + { + return $this->coverageCrap4j !== null; + } + + /** + * @throws CodeCoverageReportNotConfiguredException + */ + public function coverageCrap4j(): string + { + if (!$this->hasCoverageCrap4j()) { + throw new CodeCoverageReportNotConfiguredException; + } + + return $this->coverageCrap4j; + } + + public function coverageCrap4jThreshold(): int + { + return $this->coverageCrap4jThreshold; + } + + /** + * @phpstan-assert-if-true !null $this->coverageHtml + */ + public function hasCoverageHtml(): bool + { + return $this->coverageHtml !== null; + } + + /** + * @throws CodeCoverageReportNotConfiguredException + */ + public function coverageHtml(): string + { + if (!$this->hasCoverageHtml()) { + throw new CodeCoverageReportNotConfiguredException; + } + + return $this->coverageHtml; + } + + public function coverageHtmlLowUpperBound(): int + { + return $this->coverageHtmlLowUpperBound; + } + + public function coverageHtmlHighLowerBound(): int + { + return $this->coverageHtmlHighLowerBound; + } + + public function coverageHtmlColorSuccessLow(): string + { + return $this->coverageHtmlColorSuccessLow; + } + + public function coverageHtmlColorSuccessMedium(): string + { + return $this->coverageHtmlColorSuccessMedium; + } + + public function coverageHtmlColorSuccessHigh(): string + { + return $this->coverageHtmlColorSuccessHigh; + } + + public function coverageHtmlColorWarning(): string + { + return $this->coverageHtmlColorWarning; + } + + public function coverageHtmlColorDanger(): string + { + return $this->coverageHtmlColorDanger; + } + + /** + * @phpstan-assert-if-true !null $this->coverageHtmlCustomCssFile + */ + public function hasCoverageHtmlCustomCssFile(): bool + { + return $this->coverageHtmlCustomCssFile !== null; + } + + /** + * @throws NoCustomCssFileException + */ + public function coverageHtmlCustomCssFile(): string + { + if (!$this->hasCoverageHtmlCustomCssFile()) { + throw new NoCustomCssFileException; + } + + return $this->coverageHtmlCustomCssFile; + } + + /** + * @phpstan-assert-if-true !null $this->coverageOpenClover + */ + public function hasCoverageOpenClover(): bool + { + return $this->coverageOpenClover !== null; + } + + /** + * @throws CodeCoverageReportNotConfiguredException + */ + public function coverageOpenClover(): string + { + if (!$this->hasCoverageOpenClover()) { + throw new CodeCoverageReportNotConfiguredException; + } + + return $this->coverageOpenClover; + } + + /** + * @phpstan-assert-if-true !null $this->coveragePhp + */ + public function hasCoveragePhp(): bool + { + return $this->coveragePhp !== null; + } + + /** + * @throws CodeCoverageReportNotConfiguredException + */ + public function coveragePhp(): string + { + if (!$this->hasCoveragePhp()) { + throw new CodeCoverageReportNotConfiguredException; + } + + return $this->coveragePhp; + } + + /** + * @phpstan-assert-if-true !null $this->coverageText + */ + public function hasCoverageText(): bool + { + return $this->coverageText !== null; + } + + /** + * @throws CodeCoverageReportNotConfiguredException + */ + public function coverageText(): string + { + if (!$this->hasCoverageText()) { + throw new CodeCoverageReportNotConfiguredException; + } + + return $this->coverageText; + } + + public function coverageTextShowUncoveredFiles(): bool + { + return $this->coverageTextShowUncoveredFiles; + } + + public function coverageTextShowOnlySummary(): bool + { + return $this->coverageTextShowOnlySummary; + } + + /** + * @phpstan-assert-if-true !null $this->coverageXml + */ + public function hasCoverageXml(): bool + { + return $this->coverageXml !== null; + } + + /** + * @throws CodeCoverageReportNotConfiguredException + */ + public function coverageXml(): string + { + if (!$this->hasCoverageXml()) { + throw new CodeCoverageReportNotConfiguredException; + } + + return $this->coverageXml; + } + + public function failOnAllIssues(): bool + { + return $this->failOnAllIssues; + } + + public function failOnDeprecation(): bool + { + return $this->failOnDeprecation; + } + + public function failOnPhpunitDeprecation(): bool + { + return $this->failOnPhpunitDeprecation; + } + + public function failOnPhpunitNotice(): bool + { + return $this->failOnPhpunitNotice; + } + + public function failOnPhpunitWarning(): bool + { + return $this->failOnPhpunitWarning; + } + + public function failOnEmptyTestSuite(): bool + { + return $this->failOnEmptyTestSuite; + } + + public function failOnIncomplete(): bool + { + return $this->failOnIncomplete; + } + + public function failOnNotice(): bool + { + return $this->failOnNotice; + } + + public function failOnRisky(): bool + { + return $this->failOnRisky; + } + + public function failOnSkipped(): bool + { + return $this->failOnSkipped; + } + + public function failOnWarning(): bool + { + return $this->failOnWarning; + } + + public function doNotFailOnDeprecation(): bool + { + return $this->doNotFailOnDeprecation; + } + + public function doNotFailOnPhpunitDeprecation(): bool + { + return $this->doNotFailOnPhpunitDeprecation; + } + + public function doNotFailOnPhpunitNotice(): bool + { + return $this->doNotFailOnPhpunitNotice; + } + + public function doNotFailOnPhpunitWarning(): bool + { + return $this->doNotFailOnPhpunitWarning; + } + + public function doNotFailOnEmptyTestSuite(): bool + { + return $this->doNotFailOnEmptyTestSuite; + } + + public function doNotFailOnIncomplete(): bool + { + return $this->doNotFailOnIncomplete; + } + + public function doNotFailOnNotice(): bool + { + return $this->doNotFailOnNotice; + } + + public function doNotFailOnRisky(): bool + { + return $this->doNotFailOnRisky; + } + + public function doNotFailOnSkipped(): bool + { + return $this->doNotFailOnSkipped; + } + + public function doNotFailOnWarning(): bool + { + return $this->doNotFailOnWarning; + } + + public function stopOnDefect(): bool + { + return $this->stopOnDefect; + } + + public function stopOnDeprecation(): bool + { + return $this->stopOnDeprecation; + } + + /** + * @phpstan-assert-if-true !null $this->specificDeprecationToStopOn + */ + public function hasSpecificDeprecationToStopOn(): bool + { + return $this->specificDeprecationToStopOn !== null; + } + + /** + * @throws SpecificDeprecationToStopOnNotConfiguredException + */ + public function specificDeprecationToStopOn(): string + { + if (!$this->hasSpecificDeprecationToStopOn()) { + throw new SpecificDeprecationToStopOnNotConfiguredException; + } + + return $this->specificDeprecationToStopOn; + } + + public function stopOnError(): bool + { + return $this->stopOnError; + } + + public function stopOnFailure(): bool + { + return $this->stopOnFailure; + } + + public function stopOnIncomplete(): bool + { + return $this->stopOnIncomplete; + } + + public function stopOnNotice(): bool + { + return $this->stopOnNotice; + } + + public function stopOnRisky(): bool + { + return $this->stopOnRisky; + } + + public function stopOnSkipped(): bool + { + return $this->stopOnSkipped; + } + + public function stopOnWarning(): bool + { + return $this->stopOnWarning; + } + + public function outputToStandardErrorStream(): bool + { + return $this->outputToStandardErrorStream; + } + + public function columns(): int + { + return $this->columns; + } + + public function noExtensions(): bool + { + return $this->noExtensions; + } + + /** + * @phpstan-assert-if-true !null $this->pharExtensionDirectory + */ + public function hasPharExtensionDirectory(): bool + { + return $this->pharExtensionDirectory !== null; + } + + /** + * @throws NoPharExtensionDirectoryException + * + * @return non-empty-string + */ + public function pharExtensionDirectory(): string + { + if (!$this->hasPharExtensionDirectory()) { + throw new NoPharExtensionDirectoryException; + } + + return $this->pharExtensionDirectory; + } + + /** + * @return list}> + */ + public function extensionBootstrappers(): array + { + return $this->extensionBootstrappers; + } + + public function backupGlobals(): bool + { + return $this->backupGlobals; + } + + public function backupStaticProperties(): bool + { + return $this->backupStaticProperties; + } + + public function beStrictAboutChangesToGlobalState(): bool + { + return $this->beStrictAboutChangesToGlobalState; + } + + public function colors(): bool + { + return $this->colors; + } + + public function processIsolation(): bool + { + return $this->processIsolation; + } + + public function enforceTimeLimit(): bool + { + return $this->enforceTimeLimit; + } + + public function defaultTimeLimit(): int + { + return $this->defaultTimeLimit; + } + + public function timeoutForSmallTests(): int + { + return $this->timeoutForSmallTests; + } + + public function timeoutForMediumTests(): int + { + return $this->timeoutForMediumTests; + } + + public function timeoutForLargeTests(): int + { + return $this->timeoutForLargeTests; + } + + public function reportUselessTests(): bool + { + return $this->reportUselessTests; + } + + public function strictCoverage(): bool + { + return $this->strictCoverage; + } + + public function disallowTestOutput(): bool + { + return $this->disallowTestOutput; + } + + public function displayDetailsOnAllIssues(): bool + { + return $this->displayDetailsOnAllIssues; + } + + public function displayDetailsOnIncompleteTests(): bool + { + return $this->displayDetailsOnIncompleteTests; + } + + public function displayDetailsOnSkippedTests(): bool + { + return $this->displayDetailsOnSkippedTests; + } + + public function displayDetailsOnTestsThatTriggerDeprecations(): bool + { + return $this->displayDetailsOnTestsThatTriggerDeprecations; + } + + public function displayDetailsOnPhpunitDeprecations(): bool + { + return $this->displayDetailsOnPhpunitDeprecations; + } + + public function displayDetailsOnPhpunitNotices(): bool + { + return $this->displayDetailsOnPhpunitNotices; + } + + public function displayDetailsOnTestsThatTriggerErrors(): bool + { + return $this->displayDetailsOnTestsThatTriggerErrors; + } + + public function displayDetailsOnTestsThatTriggerNotices(): bool + { + return $this->displayDetailsOnTestsThatTriggerNotices; + } + + public function displayDetailsOnTestsThatTriggerWarnings(): bool + { + return $this->displayDetailsOnTestsThatTriggerWarnings; + } + + public function reverseDefectList(): bool + { + return $this->reverseDefectList; + } + + public function requireCoverageMetadata(): bool + { + return $this->requireCoverageMetadata; + } + + public function noProgress(): bool + { + return $this->noProgress; + } + + public function noResults(): bool + { + return $this->noResults; + } + + public function noOutput(): bool + { + return $this->noOutput; + } + + public function executionOrder(): int + { + return $this->executionOrder; + } + + public function executionOrderDefects(): int + { + return $this->executionOrderDefects; + } + + public function resolveDependencies(): bool + { + return $this->resolveDependencies; + } + + /** + * @phpstan-assert-if-true !null $this->logfileTeamcity + */ + public function hasLogfileTeamcity(): bool + { + return $this->logfileTeamcity !== null; + } + + /** + * @throws LoggingNotConfiguredException + */ + public function logfileTeamcity(): string + { + if (!$this->hasLogfileTeamcity()) { + throw new LoggingNotConfiguredException; + } + + return $this->logfileTeamcity; + } + + /** + * @phpstan-assert-if-true !null $this->logfileJunit + */ + public function hasLogfileJunit(): bool + { + return $this->logfileJunit !== null; + } + + /** + * @throws LoggingNotConfiguredException + */ + public function logfileJunit(): string + { + if (!$this->hasLogfileJunit()) { + throw new LoggingNotConfiguredException; + } + + return $this->logfileJunit; + } + + /** + * @phpstan-assert-if-true !null $this->logfileOtr + */ + public function hasLogfileOtr(): bool + { + return $this->logfileOtr !== null; + } + + /** + * @throws LoggingNotConfiguredException + */ + public function logfileOtr(): string + { + if (!$this->hasLogfileOtr()) { + throw new LoggingNotConfiguredException; + } + + return $this->logfileOtr; + } + + public function includeGitInformationInOtrLogfile(): bool + { + return $this->includeGitInformationInOtrLogfile; + } + + /** + * @phpstan-assert-if-true !null $this->logfileTestdoxHtml + */ + public function hasLogfileTestdoxHtml(): bool + { + return $this->logfileTestdoxHtml !== null; + } + + /** + * @throws LoggingNotConfiguredException + */ + public function logfileTestdoxHtml(): string + { + if (!$this->hasLogfileTestdoxHtml()) { + throw new LoggingNotConfiguredException; + } + + return $this->logfileTestdoxHtml; + } + + /** + * @phpstan-assert-if-true !null $this->logfileTestdoxText + */ + public function hasLogfileTestdoxText(): bool + { + return $this->logfileTestdoxText !== null; + } + + /** + * @throws LoggingNotConfiguredException + */ + public function logfileTestdoxText(): string + { + if (!$this->hasLogfileTestdoxText()) { + throw new LoggingNotConfiguredException; + } + + return $this->logfileTestdoxText; + } + + /** + * @phpstan-assert-if-true !null $this->logEventsText + */ + public function hasLogEventsText(): bool + { + return $this->logEventsText !== null; + } + + /** + * @throws LoggingNotConfiguredException + */ + public function logEventsText(): string + { + if (!$this->hasLogEventsText()) { + throw new LoggingNotConfiguredException; + } + + return $this->logEventsText; + } + + /** + * @phpstan-assert-if-true !null $this->logEventsVerboseText + */ + public function hasLogEventsVerboseText(): bool + { + return $this->logEventsVerboseText !== null; + } + + /** + * @throws LoggingNotConfiguredException + */ + public function logEventsVerboseText(): string + { + if (!$this->hasLogEventsVerboseText()) { + throw new LoggingNotConfiguredException; + } + + return $this->logEventsVerboseText; + } + + public function outputIsTeamCity(): bool + { + return $this->teamCityOutput; + } + + public function outputIsTestDox(): bool + { + return $this->testDoxOutput; + } + + public function testDoxOutputWithSummary(): bool + { + return $this->testDoxOutputSummary; + } + + /** + * @phpstan-assert-if-true !empty $this->testsCovering + */ + public function hasTestsCovering(): bool + { + return $this->testsCovering !== null; + } + + /** + * @throws FilterNotConfiguredException + * + * @return list + */ + public function testsCovering(): array + { + if (!$this->hasTestsCovering()) { + throw new FilterNotConfiguredException; + } + + return $this->testsCovering; + } + + /** + * @phpstan-assert-if-true !empty $this->testsUsing + */ + public function hasTestsUsing(): bool + { + return $this->testsUsing !== null; + } + + /** + * @throws FilterNotConfiguredException + * + * @return list + */ + public function testsUsing(): array + { + if (!$this->hasTestsUsing()) { + throw new FilterNotConfiguredException; + } + + return $this->testsUsing; + } + + /** + * @phpstan-assert-if-true !empty $this->testsRequiringPhpExtension + */ + public function hasTestsRequiringPhpExtension(): bool + { + return $this->testsRequiringPhpExtension !== null; + } + + /** + * @throws FilterNotConfiguredException + * + * @return non-empty-list + */ + public function testsRequiringPhpExtension(): array + { + if (!$this->hasTestsRequiringPhpExtension()) { + throw new FilterNotConfiguredException; + } + + return $this->testsRequiringPhpExtension; + } + + /** + * @phpstan-assert-if-true !null $this->filter + */ + public function hasFilter(): bool + { + return $this->filter !== null; + } + + /** + * @throws FilterNotConfiguredException + */ + public function filter(): string + { + if (!$this->hasFilter()) { + throw new FilterNotConfiguredException; + } + + return $this->filter; + } + + /** + * @phpstan-assert-if-true !null $this->excludeFilter + */ + public function hasExcludeFilter(): bool + { + return $this->excludeFilter !== null; + } + + /** + * @throws FilterNotConfiguredException + */ + public function excludeFilter(): string + { + if (!$this->hasExcludeFilter()) { + throw new FilterNotConfiguredException; + } + + return $this->excludeFilter; + } + + /** + * @phpstan-assert-if-true !empty $this->groups + */ + public function hasGroups(): bool + { + return $this->groups !== []; + } + + /** + * @throws FilterNotConfiguredException + * + * @return non-empty-list + */ + public function groups(): array + { + if (!$this->hasGroups()) { + throw new FilterNotConfiguredException; + } + + return $this->groups; + } + + /** + * @phpstan-assert-if-true !empty $this->excludeGroups + */ + public function hasExcludeGroups(): bool + { + return $this->excludeGroups !== []; + } + + /** + * @throws FilterNotConfiguredException + * + * @return non-empty-list + */ + public function excludeGroups(): array + { + if (!$this->hasExcludeGroups()) { + throw new FilterNotConfiguredException; + } + + return $this->excludeGroups; + } + + public function randomOrderSeed(): int + { + return $this->randomOrderSeed; + } + + public function includeUncoveredFiles(): bool + { + return $this->includeUncoveredFiles; + } + + public function testSuite(): TestSuiteCollection + { + return $this->testSuite; + } + + /** + * @return list + */ + public function includeTestSuites(): array + { + if ($this->includeTestSuite === '') { + return []; + } + + return explode(',', $this->includeTestSuite); + } + + /** + * @return list + */ + public function excludeTestSuites(): array + { + if ($this->excludeTestSuite === '') { + return []; + } + + return explode(',', $this->excludeTestSuite); + } + + /** + * @phpstan-assert-if-true !null $this->defaultTestSuite + */ + public function hasDefaultTestSuite(): bool + { + return $this->defaultTestSuite !== null; + } + + /** + * @throws NoDefaultTestSuiteException + */ + public function defaultTestSuite(): string + { + if (!$this->hasDefaultTestSuite()) { + throw new NoDefaultTestSuiteException; + } + + return $this->defaultTestSuite; + } + + public function ignoreTestSelectionInXmlConfiguration(): bool + { + return $this->ignoreTestSelectionInXmlConfiguration; + } + + /** + * @return non-empty-list + */ + public function testSuffixes(): array + { + return $this->testSuffixes; + } + + public function php(): Php + { + return $this->php; + } + + public function controlGarbageCollector(): bool + { + return $this->controlGarbageCollector; + } + + public function numberOfTestsBeforeGarbageCollection(): int + { + return $this->numberOfTestsBeforeGarbageCollection; + } + + /** + * @phpstan-assert-if-true !null $this->generateBaseline + */ + public function hasGenerateBaseline(): bool + { + return $this->generateBaseline !== null; + } + + /** + * @throws NoBaselineException + * + * @return non-empty-string + */ + public function generateBaseline(): string + { + if (!$this->hasGenerateBaseline()) { + throw new NoBaselineException; + } + + return $this->generateBaseline; + } + + public function debug(): bool + { + return $this->debug; + } + + public function withTelemetry(): bool + { + return $this->withTelemetry; + } + + /** + * @return non-negative-int + */ + public function shortenArraysForExportThreshold(): int + { + return $this->shortenArraysForExportThreshold; + } +} diff --git a/src/TextUI/Configuration/Exception/BootstrapScriptDoesNotExistException.php b/src/TextUI/Configuration/Exception/BootstrapScriptDoesNotExistException.php new file mode 100644 index 00000000000..6146df2b558 --- /dev/null +++ b/src/TextUI/Configuration/Exception/BootstrapScriptDoesNotExistException.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class BootstrapScriptDoesNotExistException extends RuntimeException implements Exception +{ + public function __construct(string $filename) + { + parent::__construct( + sprintf( + 'Cannot open bootstrap script "%s"', + $filename, + ), + ); + } +} diff --git a/src/TextUI/Configuration/Exception/BootstrapScriptException.php b/src/TextUI/Configuration/Exception/BootstrapScriptException.php new file mode 100644 index 00000000000..123b9aeea0c --- /dev/null +++ b/src/TextUI/Configuration/Exception/BootstrapScriptException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class BootstrapScriptException extends RuntimeException implements Exception +{ +} diff --git a/src/TextUI/Configuration/Exception/CannotFindSchemaException.php b/src/TextUI/Configuration/Exception/CannotFindSchemaException.php new file mode 100644 index 00000000000..6eef052db7a --- /dev/null +++ b/src/TextUI/Configuration/Exception/CannotFindSchemaException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use PHPUnit\TextUI\Configuration\Exception; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CannotFindSchemaException extends RuntimeException implements Exception +{ +} diff --git a/src/TextUI/Configuration/Exception/CodeCoverageReportNotConfiguredException.php b/src/TextUI/Configuration/Exception/CodeCoverageReportNotConfiguredException.php new file mode 100644 index 00000000000..83faa0a2d67 --- /dev/null +++ b/src/TextUI/Configuration/Exception/CodeCoverageReportNotConfiguredException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CodeCoverageReportNotConfiguredException extends RuntimeException implements Exception +{ +} diff --git a/src/TextUI/Configuration/Exception/ConfigurationCannotBeBuiltException.php b/src/TextUI/Configuration/Exception/ConfigurationCannotBeBuiltException.php new file mode 100644 index 00000000000..e95e09428d0 --- /dev/null +++ b/src/TextUI/Configuration/Exception/ConfigurationCannotBeBuiltException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ConfigurationCannotBeBuiltException extends RuntimeException implements Exception +{ +} diff --git a/src/TextUI/Configuration/Exception/Exception.php b/src/TextUI/Configuration/Exception/Exception.php new file mode 100644 index 00000000000..dc49125a5f6 --- /dev/null +++ b/src/TextUI/Configuration/Exception/Exception.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface Exception extends \PHPUnit\TextUI\Exception +{ +} diff --git a/src/TextUI/Configuration/Exception/FilterNotConfiguredException.php b/src/TextUI/Configuration/Exception/FilterNotConfiguredException.php new file mode 100644 index 00000000000..5ae4331f6c5 --- /dev/null +++ b/src/TextUI/Configuration/Exception/FilterNotConfiguredException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class FilterNotConfiguredException extends RuntimeException implements Exception +{ +} diff --git a/src/TextUI/Configuration/Exception/LoggingNotConfiguredException.php b/src/TextUI/Configuration/Exception/LoggingNotConfiguredException.php new file mode 100644 index 00000000000..63cf9b07010 --- /dev/null +++ b/src/TextUI/Configuration/Exception/LoggingNotConfiguredException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class LoggingNotConfiguredException extends RuntimeException implements Exception +{ +} diff --git a/src/TextUI/Configuration/Exception/NoBaselineException.php b/src/TextUI/Configuration/Exception/NoBaselineException.php new file mode 100644 index 00000000000..7611dceb17d --- /dev/null +++ b/src/TextUI/Configuration/Exception/NoBaselineException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NoBaselineException extends RuntimeException implements Exception +{ +} diff --git a/src/TextUI/Configuration/Exception/NoBootstrapException.php b/src/TextUI/Configuration/Exception/NoBootstrapException.php new file mode 100644 index 00000000000..ff1bddf0ea2 --- /dev/null +++ b/src/TextUI/Configuration/Exception/NoBootstrapException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NoBootstrapException extends RuntimeException implements Exception +{ +} diff --git a/src/TextUI/Configuration/Exception/NoCacheDirectoryException.php b/src/TextUI/Configuration/Exception/NoCacheDirectoryException.php new file mode 100644 index 00000000000..215fe21f6f8 --- /dev/null +++ b/src/TextUI/Configuration/Exception/NoCacheDirectoryException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NoCacheDirectoryException extends RuntimeException implements Exception +{ +} diff --git a/src/TextUI/Configuration/Exception/NoConfigurationFileException.php b/src/TextUI/Configuration/Exception/NoConfigurationFileException.php new file mode 100644 index 00000000000..f8ceb80ba2e --- /dev/null +++ b/src/TextUI/Configuration/Exception/NoConfigurationFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NoConfigurationFileException extends RuntimeException implements Exception +{ +} diff --git a/src/TextUI/Configuration/Exception/NoCoverageCacheDirectoryException.php b/src/TextUI/Configuration/Exception/NoCoverageCacheDirectoryException.php new file mode 100644 index 00000000000..113950b5d3d --- /dev/null +++ b/src/TextUI/Configuration/Exception/NoCoverageCacheDirectoryException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NoCoverageCacheDirectoryException extends RuntimeException implements Exception +{ +} diff --git a/src/TextUI/Configuration/Exception/NoCustomCssFileException.php b/src/TextUI/Configuration/Exception/NoCustomCssFileException.php new file mode 100644 index 00000000000..e524c8db50a --- /dev/null +++ b/src/TextUI/Configuration/Exception/NoCustomCssFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NoCustomCssFileException extends RuntimeException implements Exception +{ +} diff --git a/src/TextUI/Configuration/Exception/NoDefaultTestSuiteException.php b/src/TextUI/Configuration/Exception/NoDefaultTestSuiteException.php new file mode 100644 index 00000000000..96e7a7ada86 --- /dev/null +++ b/src/TextUI/Configuration/Exception/NoDefaultTestSuiteException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NoDefaultTestSuiteException extends RuntimeException implements Exception +{ +} diff --git a/src/TextUI/Configuration/Exception/NoPharExtensionDirectoryException.php b/src/TextUI/Configuration/Exception/NoPharExtensionDirectoryException.php new file mode 100644 index 00000000000..ce573ca79ec --- /dev/null +++ b/src/TextUI/Configuration/Exception/NoPharExtensionDirectoryException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NoPharExtensionDirectoryException extends RuntimeException implements Exception +{ +} diff --git a/src/TextUI/Configuration/Exception/SpecificDeprecationToStopOnNotConfiguredException.php b/src/TextUI/Configuration/Exception/SpecificDeprecationToStopOnNotConfiguredException.php new file mode 100644 index 00000000000..73074db1306 --- /dev/null +++ b/src/TextUI/Configuration/Exception/SpecificDeprecationToStopOnNotConfiguredException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SpecificDeprecationToStopOnNotConfiguredException extends RuntimeException implements Exception +{ +} diff --git a/src/TextUI/Configuration/Merger.php b/src/TextUI/Configuration/Merger.php new file mode 100644 index 00000000000..bdc22be7d24 --- /dev/null +++ b/src/TextUI/Configuration/Merger.php @@ -0,0 +1,1071 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use const DIRECTORY_SEPARATOR; +use const PATH_SEPARATOR; +use function array_diff; +use function assert; +use function dirname; +use function explode; +use function is_int; +use function realpath; +use function time; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Runner\TestSuiteSorter; +use PHPUnit\TextUI\CliArguments\Configuration as CliConfiguration; +use PHPUnit\TextUI\CliArguments\Exception; +use PHPUnit\TextUI\XmlConfiguration\Configuration as XmlConfiguration; +use PHPUnit\TextUI\XmlConfiguration\LoadedFromFileConfiguration; +use PHPUnit\TextUI\XmlConfiguration\SchemaDetector; +use PHPUnit\Util\Filesystem; +use SebastianBergmann\CodeCoverage\Report\Html\Colors; +use SebastianBergmann\CodeCoverage\Report\Thresholds; +use SebastianBergmann\Environment\Console; +use SebastianBergmann\Invoker\Invoker; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Merger +{ + /** + * @throws \PHPUnit\TextUI\XmlConfiguration\Exception + * @throws Exception + * @throws NoCustomCssFileException + */ + public function merge(CliConfiguration $cliConfiguration, XmlConfiguration $xmlConfiguration): Configuration + { + $configurationFile = null; + + if ($xmlConfiguration->wasLoadedFromFile()) { + assert($xmlConfiguration instanceof LoadedFromFileConfiguration); + + $configurationFile = $xmlConfiguration->filename(); + } + + $bootstrap = null; + + if ($cliConfiguration->hasBootstrap()) { + $bootstrap = $cliConfiguration->bootstrap(); + } elseif ($xmlConfiguration->phpunit()->hasBootstrap()) { + $bootstrap = $xmlConfiguration->phpunit()->bootstrap(); + } + + if ($cliConfiguration->hasCacheResult()) { + $cacheResult = $cliConfiguration->cacheResult(); + } else { + $cacheResult = $xmlConfiguration->phpunit()->cacheResult(); + } + + $cacheDirectory = null; + $coverageCacheDirectory = null; + + if ($cliConfiguration->hasCacheDirectory() && Filesystem::createDirectory($cliConfiguration->cacheDirectory())) { + $cacheDirectory = realpath($cliConfiguration->cacheDirectory()); + } elseif ($xmlConfiguration->phpunit()->hasCacheDirectory() && Filesystem::createDirectory($xmlConfiguration->phpunit()->cacheDirectory())) { + $cacheDirectory = realpath($xmlConfiguration->phpunit()->cacheDirectory()); + } + + if ($cacheDirectory !== null) { + $coverageCacheDirectory = $cacheDirectory . DIRECTORY_SEPARATOR . 'code-coverage'; + $testResultCacheFile = $cacheDirectory . DIRECTORY_SEPARATOR . 'test-results'; + } + + if (!isset($testResultCacheFile)) { + if ($xmlConfiguration->wasLoadedFromFile()) { + $testResultCacheFile = dirname(realpath($xmlConfiguration->filename())) . DIRECTORY_SEPARATOR . '.phpunit.result.cache'; + } else { + $candidate = realpath($_SERVER['PHP_SELF']); + + if ($candidate) { + $testResultCacheFile = dirname($candidate) . DIRECTORY_SEPARATOR . '.phpunit.result.cache'; + } else { + $testResultCacheFile = '.phpunit.result.cache'; + } + } + } + + if ($cliConfiguration->hasDisableCodeCoverageIgnore()) { + $disableCodeCoverageIgnore = $cliConfiguration->disableCodeCoverageIgnore(); + } else { + $disableCodeCoverageIgnore = $xmlConfiguration->codeCoverage()->disableCodeCoverageIgnore(); + } + + if ($cliConfiguration->hasFailOnAllIssues()) { + $failOnAllIssues = $cliConfiguration->failOnAllIssues(); + } else { + $failOnAllIssues = $xmlConfiguration->phpunit()->failOnAllIssues(); + } + + if ($cliConfiguration->hasFailOnDeprecation()) { + $failOnDeprecation = $cliConfiguration->failOnDeprecation(); + } else { + $failOnDeprecation = $xmlConfiguration->phpunit()->failOnDeprecation(); + } + + if ($cliConfiguration->hasFailOnPhpunitDeprecation()) { + $failOnPhpunitDeprecation = $cliConfiguration->failOnPhpunitDeprecation(); + } else { + $failOnPhpunitDeprecation = $xmlConfiguration->phpunit()->failOnPhpunitDeprecation(); + } + + if ($cliConfiguration->hasFailOnPhpunitNotice()) { + $failOnPhpunitNotice = $cliConfiguration->failOnPhpunitNotice(); + } else { + $failOnPhpunitNotice = $xmlConfiguration->phpunit()->failOnPhpunitNotice(); + } + + if ($cliConfiguration->hasFailOnPhpunitWarning()) { + $failOnPhpunitWarning = $cliConfiguration->failOnPhpunitWarning(); + } else { + $failOnPhpunitWarning = $xmlConfiguration->phpunit()->failOnPhpunitWarning(); + } + + if ($cliConfiguration->hasFailOnEmptyTestSuite()) { + $failOnEmptyTestSuite = $cliConfiguration->failOnEmptyTestSuite(); + } else { + $failOnEmptyTestSuite = $xmlConfiguration->phpunit()->failOnEmptyTestSuite(); + } + + if ($cliConfiguration->hasFailOnIncomplete()) { + $failOnIncomplete = $cliConfiguration->failOnIncomplete(); + } else { + $failOnIncomplete = $xmlConfiguration->phpunit()->failOnIncomplete(); + } + + if ($cliConfiguration->hasFailOnNotice()) { + $failOnNotice = $cliConfiguration->failOnNotice(); + } else { + $failOnNotice = $xmlConfiguration->phpunit()->failOnNotice(); + } + + if ($cliConfiguration->hasFailOnRisky()) { + $failOnRisky = $cliConfiguration->failOnRisky(); + } else { + $failOnRisky = $xmlConfiguration->phpunit()->failOnRisky(); + } + + if ($cliConfiguration->hasFailOnSkipped()) { + $failOnSkipped = $cliConfiguration->failOnSkipped(); + } else { + $failOnSkipped = $xmlConfiguration->phpunit()->failOnSkipped(); + } + + if ($cliConfiguration->hasFailOnWarning()) { + $failOnWarning = $cliConfiguration->failOnWarning(); + } else { + $failOnWarning = $xmlConfiguration->phpunit()->failOnWarning(); + } + + $doNotFailOnDeprecation = false; + + if ($cliConfiguration->hasDoNotFailOnDeprecation()) { + $doNotFailOnDeprecation = $cliConfiguration->doNotFailOnDeprecation(); + } + + $doNotFailOnPhpunitDeprecation = false; + + if ($cliConfiguration->hasDoNotFailOnPhpunitDeprecation()) { + $doNotFailOnPhpunitDeprecation = $cliConfiguration->doNotFailOnPhpunitDeprecation(); + } + + $doNotFailOnPhpunitNotice = false; + + if ($cliConfiguration->hasDoNotFailOnPhpunitNotice()) { + $doNotFailOnPhpunitNotice = $cliConfiguration->doNotFailOnPhpunitNotice(); + } + + $doNotFailOnPhpunitWarning = false; + + if ($cliConfiguration->hasDoNotFailOnPhpunitWarning()) { + $doNotFailOnPhpunitWarning = $cliConfiguration->doNotFailOnPhpunitWarning(); + } + + $doNotFailOnEmptyTestSuite = false; + + if ($cliConfiguration->hasDoNotFailOnEmptyTestSuite()) { + $doNotFailOnEmptyTestSuite = $cliConfiguration->doNotFailOnEmptyTestSuite(); + } + + $doNotFailOnIncomplete = false; + + if ($cliConfiguration->hasDoNotFailOnIncomplete()) { + $doNotFailOnIncomplete = $cliConfiguration->doNotFailOnIncomplete(); + } + + $doNotFailOnNotice = false; + + if ($cliConfiguration->hasDoNotFailOnNotice()) { + $doNotFailOnNotice = $cliConfiguration->doNotFailOnNotice(); + } + + $doNotFailOnRisky = false; + + if ($cliConfiguration->hasDoNotFailOnRisky()) { + $doNotFailOnRisky = $cliConfiguration->doNotFailOnRisky(); + } + + $doNotFailOnSkipped = false; + + if ($cliConfiguration->hasDoNotFailOnSkipped()) { + $doNotFailOnSkipped = $cliConfiguration->doNotFailOnSkipped(); + } + + $doNotFailOnWarning = false; + + if ($cliConfiguration->hasDoNotFailOnWarning()) { + $doNotFailOnWarning = $cliConfiguration->doNotFailOnWarning(); + } + + if ($cliConfiguration->hasStopOnDefect()) { + $stopOnDefect = $cliConfiguration->stopOnDefect(); + } else { + $stopOnDefect = $xmlConfiguration->phpunit()->stopOnDefect(); + } + + if ($cliConfiguration->hasStopOnDeprecation()) { + $stopOnDeprecation = $cliConfiguration->stopOnDeprecation(); + } else { + $stopOnDeprecation = $xmlConfiguration->phpunit()->stopOnDeprecation(); + } + + $specificDeprecationToStopOn = null; + + if ($cliConfiguration->hasSpecificDeprecationToStopOn()) { + $specificDeprecationToStopOn = $cliConfiguration->specificDeprecationToStopOn(); + } + + if ($cliConfiguration->hasStopOnError()) { + $stopOnError = $cliConfiguration->stopOnError(); + } else { + $stopOnError = $xmlConfiguration->phpunit()->stopOnError(); + } + + if ($cliConfiguration->hasStopOnFailure()) { + $stopOnFailure = $cliConfiguration->stopOnFailure(); + } else { + $stopOnFailure = $xmlConfiguration->phpunit()->stopOnFailure(); + } + + if ($cliConfiguration->hasStopOnIncomplete()) { + $stopOnIncomplete = $cliConfiguration->stopOnIncomplete(); + } else { + $stopOnIncomplete = $xmlConfiguration->phpunit()->stopOnIncomplete(); + } + + if ($cliConfiguration->hasStopOnNotice()) { + $stopOnNotice = $cliConfiguration->stopOnNotice(); + } else { + $stopOnNotice = $xmlConfiguration->phpunit()->stopOnNotice(); + } + + if ($cliConfiguration->hasStopOnRisky()) { + $stopOnRisky = $cliConfiguration->stopOnRisky(); + } else { + $stopOnRisky = $xmlConfiguration->phpunit()->stopOnRisky(); + } + + if ($cliConfiguration->hasStopOnSkipped()) { + $stopOnSkipped = $cliConfiguration->stopOnSkipped(); + } else { + $stopOnSkipped = $xmlConfiguration->phpunit()->stopOnSkipped(); + } + + if ($cliConfiguration->hasStopOnWarning()) { + $stopOnWarning = $cliConfiguration->stopOnWarning(); + } else { + $stopOnWarning = $xmlConfiguration->phpunit()->stopOnWarning(); + } + + if ($cliConfiguration->hasStderr() && $cliConfiguration->stderr()) { + $outputToStandardErrorStream = true; + } else { + $outputToStandardErrorStream = $xmlConfiguration->phpunit()->stderr(); + } + + if ($cliConfiguration->hasColumns()) { + $columns = $cliConfiguration->columns(); + } else { + $columns = $xmlConfiguration->phpunit()->columns(); + } + + if ($columns === 'max') { + $columns = (new Console)->getNumberOfColumns(); + } + + if ($columns < 16) { + $columns = 16; + + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + 'Less than 16 columns requested, number of columns set to 16', + ); + } + + assert(is_int($columns)); + + $noExtensions = false; + + if ($cliConfiguration->hasNoExtensions() && $cliConfiguration->noExtensions()) { + $noExtensions = true; + } + + $pharExtensionDirectory = null; + + if ($xmlConfiguration->phpunit()->hasExtensionsDirectory()) { + $pharExtensionDirectory = $xmlConfiguration->phpunit()->extensionsDirectory(); + } + + $extensionBootstrappers = []; + + if ($cliConfiguration->hasExtensions()) { + foreach ($cliConfiguration->extensions() as $extension) { + $extensionBootstrappers[] = [ + 'className' => $extension, + 'parameters' => [], + ]; + } + } + + foreach ($xmlConfiguration->extensions() as $extension) { + $extensionBootstrappers[] = [ + 'className' => $extension->className(), + 'parameters' => $extension->parameters(), + ]; + } + + if ($cliConfiguration->hasPathCoverage() && $cliConfiguration->pathCoverage()) { + $pathCoverage = $cliConfiguration->pathCoverage(); + } else { + $pathCoverage = $xmlConfiguration->codeCoverage()->pathCoverage(); + } + + $defaultColors = Colors::default(); + $defaultThresholds = Thresholds::default(); + + $coverageClover = null; + $coverageCobertura = null; + $coverageCrap4j = null; + $coverageCrap4jThreshold = 30; + $coverageHtml = null; + $coverageHtmlLowUpperBound = $defaultThresholds->lowUpperBound(); + $coverageHtmlHighLowerBound = $defaultThresholds->highLowerBound(); + $coverageHtmlColorSuccessLow = $defaultColors->successLow(); + $coverageHtmlColorSuccessMedium = $defaultColors->successMedium(); + $coverageHtmlColorSuccessHigh = $defaultColors->successHigh(); + $coverageHtmlColorWarning = $defaultColors->warning(); + $coverageHtmlColorDanger = $defaultColors->danger(); + $coverageHtmlCustomCssFile = null; + $coverageOpenClover = null; + $coveragePhp = null; + $coverageText = null; + $coverageTextShowUncoveredFiles = false; + $coverageTextShowOnlySummary = false; + $coverageXml = null; + $coverageFromXmlConfiguration = true; + + if ($cliConfiguration->hasNoCoverage() && $cliConfiguration->noCoverage()) { + $coverageFromXmlConfiguration = false; + } + + if ($cliConfiguration->hasCoverageClover()) { + $coverageClover = $cliConfiguration->coverageClover(); + } elseif ($coverageFromXmlConfiguration && $xmlConfiguration->codeCoverage()->hasClover()) { + $coverageClover = $xmlConfiguration->codeCoverage()->clover()->target()->path(); + } + + if ($cliConfiguration->hasCoverageCobertura()) { + $coverageCobertura = $cliConfiguration->coverageCobertura(); + } elseif ($coverageFromXmlConfiguration && $xmlConfiguration->codeCoverage()->hasCobertura()) { + $coverageCobertura = $xmlConfiguration->codeCoverage()->cobertura()->target()->path(); + } + + if ($xmlConfiguration->codeCoverage()->hasCrap4j()) { + $coverageCrap4jThreshold = $xmlConfiguration->codeCoverage()->crap4j()->threshold(); + } + + if ($cliConfiguration->hasCoverageCrap4J()) { + $coverageCrap4j = $cliConfiguration->coverageCrap4J(); + } elseif ($coverageFromXmlConfiguration && $xmlConfiguration->codeCoverage()->hasCrap4j()) { + $coverageCrap4j = $xmlConfiguration->codeCoverage()->crap4j()->target()->path(); + } + + if ($xmlConfiguration->codeCoverage()->hasHtml()) { + $coverageHtmlHighLowerBound = $xmlConfiguration->codeCoverage()->html()->highLowerBound(); + $coverageHtmlLowUpperBound = $xmlConfiguration->codeCoverage()->html()->lowUpperBound(); + + if ($coverageHtmlLowUpperBound > $coverageHtmlHighLowerBound) { + $coverageHtmlLowUpperBound = $defaultThresholds->lowUpperBound(); + $coverageHtmlHighLowerBound = $defaultThresholds->highLowerBound(); + } + + $coverageHtmlColorSuccessLow = $xmlConfiguration->codeCoverage()->html()->colorSuccessLow(); + $coverageHtmlColorSuccessMedium = $xmlConfiguration->codeCoverage()->html()->colorSuccessMedium(); + $coverageHtmlColorSuccessHigh = $xmlConfiguration->codeCoverage()->html()->colorSuccessHigh(); + $coverageHtmlColorWarning = $xmlConfiguration->codeCoverage()->html()->colorWarning(); + $coverageHtmlColorDanger = $xmlConfiguration->codeCoverage()->html()->colorDanger(); + + if ($xmlConfiguration->codeCoverage()->html()->hasCustomCssFile()) { + $coverageHtmlCustomCssFile = $xmlConfiguration->codeCoverage()->html()->customCssFile(); + } + } + + if ($cliConfiguration->hasCoverageHtml()) { + $coverageHtml = $cliConfiguration->coverageHtml(); + } elseif ($coverageFromXmlConfiguration && $xmlConfiguration->codeCoverage()->hasHtml()) { + $coverageHtml = $xmlConfiguration->codeCoverage()->html()->target()->path(); + } + + if ($cliConfiguration->hasCoverageOpenClover()) { + $coverageOpenClover = $cliConfiguration->coverageOpenClover(); + } elseif ($coverageFromXmlConfiguration && $xmlConfiguration->codeCoverage()->hasOpenClover()) { + $coverageOpenClover = $xmlConfiguration->codeCoverage()->openClover()->target()->path(); + } + + if ($cliConfiguration->hasCoveragePhp()) { + $coveragePhp = $cliConfiguration->coveragePhp(); + } elseif ($coverageFromXmlConfiguration && $xmlConfiguration->codeCoverage()->hasPhp()) { + $coveragePhp = $xmlConfiguration->codeCoverage()->php()->target()->path(); + } + + if ($xmlConfiguration->codeCoverage()->hasText()) { + $coverageTextShowUncoveredFiles = $xmlConfiguration->codeCoverage()->text()->showUncoveredFiles(); + $coverageTextShowOnlySummary = $xmlConfiguration->codeCoverage()->text()->showOnlySummary(); + } + + if ($cliConfiguration->hasCoverageTextShowUncoveredFiles()) { + $coverageTextShowUncoveredFiles = $cliConfiguration->coverageTextShowUncoveredFiles(); + } + + if ($cliConfiguration->hasCoverageTextShowOnlySummary()) { + $coverageTextShowOnlySummary = $cliConfiguration->coverageTextShowOnlySummary(); + } + + if ($cliConfiguration->hasCoverageText()) { + $coverageText = $cliConfiguration->coverageText(); + } elseif ($coverageFromXmlConfiguration && $xmlConfiguration->codeCoverage()->hasText()) { + $coverageText = $xmlConfiguration->codeCoverage()->text()->target()->path(); + } + + if ($cliConfiguration->hasCoverageXml()) { + $coverageXml = $cliConfiguration->coverageXml(); + } elseif ($coverageFromXmlConfiguration && $xmlConfiguration->codeCoverage()->hasXml()) { + $coverageXml = $xmlConfiguration->codeCoverage()->xml()->target()->path(); + } + + if ($cliConfiguration->hasBackupGlobals()) { + $backupGlobals = $cliConfiguration->backupGlobals(); + } else { + $backupGlobals = $xmlConfiguration->phpunit()->backupGlobals(); + } + + if ($cliConfiguration->hasBackupStaticProperties()) { + $backupStaticProperties = $cliConfiguration->backupStaticProperties(); + } else { + $backupStaticProperties = $xmlConfiguration->phpunit()->backupStaticProperties(); + } + + if ($cliConfiguration->hasBeStrictAboutChangesToGlobalState()) { + $beStrictAboutChangesToGlobalState = $cliConfiguration->beStrictAboutChangesToGlobalState(); + } else { + $beStrictAboutChangesToGlobalState = $xmlConfiguration->phpunit()->beStrictAboutChangesToGlobalState(); + } + + if ($cliConfiguration->hasProcessIsolation()) { + $processIsolation = $cliConfiguration->processIsolation(); + } else { + $processIsolation = $xmlConfiguration->phpunit()->processIsolation(); + } + + if ($cliConfiguration->hasEnforceTimeLimit()) { + $enforceTimeLimit = $cliConfiguration->enforceTimeLimit(); + } else { + $enforceTimeLimit = $xmlConfiguration->phpunit()->enforceTimeLimit(); + } + + if ($enforceTimeLimit && !(new Invoker)->canInvokeWithTimeout()) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + 'The pcntl extension is required for enforcing time limits', + ); + } + + if ($cliConfiguration->hasDefaultTimeLimit()) { + $defaultTimeLimit = $cliConfiguration->defaultTimeLimit(); + } else { + $defaultTimeLimit = $xmlConfiguration->phpunit()->defaultTimeLimit(); + } + + $timeoutForSmallTests = $xmlConfiguration->phpunit()->timeoutForSmallTests(); + $timeoutForMediumTests = $xmlConfiguration->phpunit()->timeoutForMediumTests(); + $timeoutForLargeTests = $xmlConfiguration->phpunit()->timeoutForLargeTests(); + + if ($cliConfiguration->hasReportUselessTests()) { + $reportUselessTests = $cliConfiguration->reportUselessTests(); + } else { + $reportUselessTests = $xmlConfiguration->phpunit()->beStrictAboutTestsThatDoNotTestAnything(); + } + + if ($cliConfiguration->hasStrictCoverage()) { + $strictCoverage = $cliConfiguration->strictCoverage(); + } else { + $strictCoverage = $xmlConfiguration->phpunit()->beStrictAboutCoverageMetadata(); + } + + if ($cliConfiguration->hasDisallowTestOutput()) { + $disallowTestOutput = $cliConfiguration->disallowTestOutput(); + } else { + $disallowTestOutput = $xmlConfiguration->phpunit()->beStrictAboutOutputDuringTests(); + } + + if ($cliConfiguration->hasDisplayDetailsOnAllIssues()) { + $displayDetailsOnAllIssues = $cliConfiguration->displayDetailsOnAllIssues(); + } else { + $displayDetailsOnAllIssues = $xmlConfiguration->phpunit()->displayDetailsOnAllIssues(); + } + + if ($cliConfiguration->hasDisplayDetailsOnIncompleteTests()) { + $displayDetailsOnIncompleteTests = $cliConfiguration->displayDetailsOnIncompleteTests(); + } else { + $displayDetailsOnIncompleteTests = $xmlConfiguration->phpunit()->displayDetailsOnIncompleteTests(); + } + + if ($cliConfiguration->hasDisplayDetailsOnSkippedTests()) { + $displayDetailsOnSkippedTests = $cliConfiguration->displayDetailsOnSkippedTests(); + } else { + $displayDetailsOnSkippedTests = $xmlConfiguration->phpunit()->displayDetailsOnSkippedTests(); + } + + if ($cliConfiguration->hasDisplayDetailsOnTestsThatTriggerDeprecations()) { + $displayDetailsOnTestsThatTriggerDeprecations = $cliConfiguration->displayDetailsOnTestsThatTriggerDeprecations(); + } else { + $displayDetailsOnTestsThatTriggerDeprecations = $xmlConfiguration->phpunit()->displayDetailsOnTestsThatTriggerDeprecations(); + } + + if ($cliConfiguration->hasDisplayDetailsOnPhpunitDeprecations()) { + $displayDetailsOnPhpunitDeprecations = $cliConfiguration->displayDetailsOnPhpunitDeprecations(); + } else { + $displayDetailsOnPhpunitDeprecations = $xmlConfiguration->phpunit()->displayDetailsOnPhpunitDeprecations(); + } + + if ($cliConfiguration->hasDisplayDetailsOnPhpunitNotices()) { + $displayDetailsOnPhpunitNotices = $cliConfiguration->displayDetailsOnPhpunitNotices(); + } else { + $displayDetailsOnPhpunitNotices = $xmlConfiguration->phpunit()->displayDetailsOnPhpunitNotices(); + } + + if ($cliConfiguration->hasDisplayDetailsOnTestsThatTriggerErrors()) { + $displayDetailsOnTestsThatTriggerErrors = $cliConfiguration->displayDetailsOnTestsThatTriggerErrors(); + } else { + $displayDetailsOnTestsThatTriggerErrors = $xmlConfiguration->phpunit()->displayDetailsOnTestsThatTriggerErrors(); + } + + if ($cliConfiguration->hasDisplayDetailsOnTestsThatTriggerNotices()) { + $displayDetailsOnTestsThatTriggerNotices = $cliConfiguration->displayDetailsOnTestsThatTriggerNotices(); + } else { + $displayDetailsOnTestsThatTriggerNotices = $xmlConfiguration->phpunit()->displayDetailsOnTestsThatTriggerNotices(); + } + + if ($cliConfiguration->hasDisplayDetailsOnTestsThatTriggerWarnings()) { + $displayDetailsOnTestsThatTriggerWarnings = $cliConfiguration->displayDetailsOnTestsThatTriggerWarnings(); + } else { + $displayDetailsOnTestsThatTriggerWarnings = $xmlConfiguration->phpunit()->displayDetailsOnTestsThatTriggerWarnings(); + } + + if ($cliConfiguration->hasReverseList()) { + $reverseDefectList = $cliConfiguration->reverseList(); + } else { + $reverseDefectList = $xmlConfiguration->phpunit()->reverseDefectList(); + } + + $requireCoverageMetadata = $xmlConfiguration->phpunit()->requireCoverageMetadata(); + + if ($cliConfiguration->hasExecutionOrder()) { + $executionOrder = $cliConfiguration->executionOrder(); + } else { + $executionOrder = $xmlConfiguration->phpunit()->executionOrder(); + } + + $executionOrderDefects = TestSuiteSorter::ORDER_DEFAULT; + + if ($cliConfiguration->hasExecutionOrderDefects()) { + $executionOrderDefects = $cliConfiguration->executionOrderDefects(); + } elseif ($xmlConfiguration->phpunit()->defectsFirst()) { + $executionOrderDefects = TestSuiteSorter::ORDER_DEFECTS_FIRST; + } + + if ($cliConfiguration->hasResolveDependencies()) { + $resolveDependencies = $cliConfiguration->resolveDependencies(); + } else { + $resolveDependencies = $xmlConfiguration->phpunit()->resolveDependencies(); + } + + $colors = false; + $colorsSupported = (new Console)->hasColorSupport(); + + if ($cliConfiguration->hasColors()) { + if ($cliConfiguration->colors() === Configuration::COLOR_ALWAYS) { + $colors = true; + } elseif ($colorsSupported && $cliConfiguration->colors() === Configuration::COLOR_AUTO) { + $colors = true; + } + } elseif ($xmlConfiguration->phpunit()->colors() === Configuration::COLOR_ALWAYS) { + $colors = true; + } elseif ($colorsSupported && $xmlConfiguration->phpunit()->colors() === Configuration::COLOR_AUTO) { + $colors = true; + } + + $logfileTeamcity = null; + $logfileJunit = null; + $logfileOtr = null; + $logfileTestdoxHtml = null; + $logfileTestdoxText = null; + $loggingFromXmlConfiguration = true; + + if ($cliConfiguration->hasNoLogging() && $cliConfiguration->noLogging()) { + $loggingFromXmlConfiguration = false; + } + + if ($cliConfiguration->hasTeamcityLogfile()) { + $logfileTeamcity = $cliConfiguration->teamcityLogfile(); + } elseif ($loggingFromXmlConfiguration && $xmlConfiguration->logging()->hasTeamCity()) { + $logfileTeamcity = $xmlConfiguration->logging()->teamCity()->target()->path(); + } + + if ($cliConfiguration->hasJunitLogfile()) { + $logfileJunit = $cliConfiguration->junitLogfile(); + } elseif ($loggingFromXmlConfiguration && $xmlConfiguration->logging()->hasJunit()) { + $logfileJunit = $xmlConfiguration->logging()->junit()->target()->path(); + } + + if ($cliConfiguration->hasOtrLogfile()) { + $logfileOtr = $cliConfiguration->otrLogfile(); + } elseif ($loggingFromXmlConfiguration && $xmlConfiguration->logging()->hasOtr()) { + $logfileOtr = $xmlConfiguration->logging()->otr()->target()->path(); + } + + $includeGitInformationInOtrLogfile = false; + + if ($cliConfiguration->hasIncludeGitInformationInOtrLogfile()) { + $includeGitInformationInOtrLogfile = $cliConfiguration->includeGitInformationInOtrLogfile(); + } elseif ($loggingFromXmlConfiguration && $xmlConfiguration->logging()->hasOtr()) { + $includeGitInformationInOtrLogfile = $xmlConfiguration->logging()->otr()->includeGitInformation(); + } + + if ($cliConfiguration->hasTestdoxHtmlFile()) { + $logfileTestdoxHtml = $cliConfiguration->testdoxHtmlFile(); + } elseif ($loggingFromXmlConfiguration && $xmlConfiguration->logging()->hasTestDoxHtml()) { + $logfileTestdoxHtml = $xmlConfiguration->logging()->testDoxHtml()->target()->path(); + } + + if ($cliConfiguration->hasTestdoxTextFile()) { + $logfileTestdoxText = $cliConfiguration->testdoxTextFile(); + } elseif ($loggingFromXmlConfiguration && $xmlConfiguration->logging()->hasTestDoxText()) { + $logfileTestdoxText = $xmlConfiguration->logging()->testDoxText()->target()->path(); + } + + $logEventsText = null; + + if ($cliConfiguration->hasLogEventsText()) { + $logEventsText = $cliConfiguration->logEventsText(); + } + + $logEventsVerboseText = null; + + if ($cliConfiguration->hasLogEventsVerboseText()) { + $logEventsVerboseText = $cliConfiguration->logEventsVerboseText(); + } + + $teamCityOutput = false; + + if ($cliConfiguration->hasTeamCityPrinter() && $cliConfiguration->teamCityPrinter()) { + $teamCityOutput = true; + } + + if ($cliConfiguration->hasTestDoxPrinter() && $cliConfiguration->testdoxPrinter()) { + $testDoxOutput = true; + } else { + $testDoxOutput = $xmlConfiguration->phpunit()->testdoxPrinter(); + } + + if ($cliConfiguration->hasTestDoxPrinterSummary() && $cliConfiguration->testdoxPrinterSummary()) { + $testDoxOutputSummary = true; + } else { + $testDoxOutputSummary = $xmlConfiguration->phpunit()->testdoxPrinterSummary(); + } + + $noProgress = false; + + if ($cliConfiguration->hasNoProgress() && $cliConfiguration->noProgress()) { + $noProgress = true; + } + + $noResults = false; + + if ($cliConfiguration->hasNoResults() && $cliConfiguration->noResults()) { + $noResults = true; + } + + $noOutput = false; + + if ($cliConfiguration->hasNoOutput() && $cliConfiguration->noOutput()) { + $noOutput = true; + } + + $testsCovering = null; + + if ($cliConfiguration->hasTestsCovering()) { + $testsCovering = $cliConfiguration->testsCovering(); + } + + $testsUsing = null; + + if ($cliConfiguration->hasTestsUsing()) { + $testsUsing = $cliConfiguration->testsUsing(); + } + + $testsRequiringPhpExtension = null; + + if ($cliConfiguration->hasTestsRequiringPhpExtension()) { + $testsRequiringPhpExtension = $cliConfiguration->testsRequiringPhpExtension(); + } + + $filter = null; + + if ($cliConfiguration->hasFilter()) { + $filter = $cliConfiguration->filter(); + } + + $excludeFilter = null; + + if ($cliConfiguration->hasExcludeFilter()) { + $excludeFilter = $cliConfiguration->excludeFilter(); + } + + $ignoreTestSelectionInXmlConfiguration = false; + + if ($cliConfiguration->hasAll()) { + $ignoreTestSelectionInXmlConfiguration = true; + } + + $groups = []; + + if ($cliConfiguration->hasGroups()) { + $groups = $cliConfiguration->groups(); + } elseif (!$ignoreTestSelectionInXmlConfiguration) { + $groups = $xmlConfiguration->groups()->include()->asArrayOfStrings(); + } + + $excludeGroups = []; + + if ($cliConfiguration->hasExcludeGroups()) { + $excludeGroups = $cliConfiguration->excludeGroups(); + } elseif (!$ignoreTestSelectionInXmlConfiguration) { + $excludeGroups = $xmlConfiguration->groups()->exclude()->asArrayOfStrings(); + } + + $excludeGroups = array_diff($excludeGroups, $groups); + + if ($cliConfiguration->hasRandomOrderSeed()) { + $randomOrderSeed = $cliConfiguration->randomOrderSeed(); + } else { + $randomOrderSeed = time(); + } + + if ($xmlConfiguration->wasLoadedFromFile() && $xmlConfiguration->hasValidationErrors()) { + if ((new SchemaDetector)->detect($xmlConfiguration->filename())->detected()) { + EventFacade::emitter()->testRunnerTriggeredPhpunitDeprecation( + 'Your XML configuration validates against a deprecated schema. Migrate your XML configuration using "--migrate-configuration"!', + ); + } else { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + "Test results may not be as expected because the XML configuration file did not pass validation:\n" . + $xmlConfiguration->validationErrors(), + ); + } + } + + $includeUncoveredFiles = $xmlConfiguration->codeCoverage()->includeUncoveredFiles(); + + $includePaths = []; + + if ($cliConfiguration->hasIncludePath()) { + foreach (explode(PATH_SEPARATOR, $cliConfiguration->includePath()) as $includePath) { + $includePaths[] = new Directory($includePath); + } + } + + foreach ($xmlConfiguration->php()->includePaths() as $includePath) { + $includePaths[] = $includePath; + } + + $iniSettings = []; + + if ($cliConfiguration->hasIniSettings()) { + foreach ($cliConfiguration->iniSettings() as $name => $value) { + $iniSettings[] = new IniSetting($name, $value); + } + } + + foreach ($xmlConfiguration->php()->iniSettings() as $iniSetting) { + $iniSettings[] = $iniSetting; + } + + $includeTestSuite = ''; + + if ($cliConfiguration->hasTestSuite()) { + $includeTestSuite = $cliConfiguration->testSuite(); + } elseif ($xmlConfiguration->phpunit()->hasDefaultTestSuite()) { + $includeTestSuite = $xmlConfiguration->phpunit()->defaultTestSuite(); + } + + $excludeTestSuite = ''; + + if ($cliConfiguration->hasExcludedTestSuite()) { + $excludeTestSuite = $cliConfiguration->excludedTestSuite(); + } + + $testSuffixes = ['Test.php', '.phpt']; + + if ($cliConfiguration->hasTestSuffixes()) { + $testSuffixes = $cliConfiguration->testSuffixes(); + } + + $sourceIncludeDirectories = []; + + if ($cliConfiguration->hasCoverageFilter()) { + foreach ($cliConfiguration->coverageFilter() as $directory) { + $sourceIncludeDirectories[] = new FilterDirectory($directory, '', '.php'); + } + } + + foreach ($xmlConfiguration->source()->includeDirectories() as $directory) { + $sourceIncludeDirectories[] = $directory; + } + + $sourceIncludeFiles = $xmlConfiguration->source()->includeFiles(); + $sourceExcludeDirectories = $xmlConfiguration->source()->excludeDirectories(); + $sourceExcludeFiles = $xmlConfiguration->source()->excludeFiles(); + + $useBaseline = null; + $generateBaseline = null; + + if (!$cliConfiguration->hasGenerateBaseline()) { + if ($cliConfiguration->hasUseBaseline()) { + $useBaseline = $cliConfiguration->useBaseline(); + } elseif ($xmlConfiguration->source()->hasBaseline()) { + $useBaseline = $xmlConfiguration->source()->baseline(); + } + } else { + $generateBaseline = $cliConfiguration->generateBaseline(); + } + + assert($useBaseline !== ''); + assert($generateBaseline !== ''); + + if ($failOnAllIssues) { + $displayDetailsOnAllIssues = true; + } + + if ($failOnDeprecation && !$doNotFailOnDeprecation) { + $displayDetailsOnTestsThatTriggerDeprecations = true; + } + + if ($failOnPhpunitDeprecation && !$doNotFailOnPhpunitDeprecation) { + $displayDetailsOnPhpunitDeprecations = true; + } + + if ($failOnPhpunitNotice && !$doNotFailOnPhpunitNotice) { + $displayDetailsOnPhpunitNotices = true; + } + + if ($failOnNotice && !$doNotFailOnNotice) { + $displayDetailsOnTestsThatTriggerNotices = true; + } + + if ($failOnWarning && !$doNotFailOnWarning) { + $displayDetailsOnTestsThatTriggerWarnings = true; + } + + if ($failOnIncomplete && !$doNotFailOnIncomplete) { + $displayDetailsOnIncompleteTests = true; + } + + if ($failOnSkipped && !$doNotFailOnSkipped) { + $displayDetailsOnSkippedTests = true; + } + + return new Configuration( + $cliConfiguration->arguments(), + $configurationFile, + $bootstrap, + $xmlConfiguration->phpunit()->bootstrapForTestSuite(), + $cacheResult, + $cacheDirectory, + $coverageCacheDirectory, + new Source( + $useBaseline, + $cliConfiguration->ignoreBaseline(), + FilterDirectoryCollection::fromArray($sourceIncludeDirectories), + $sourceIncludeFiles, + $sourceExcludeDirectories, + $sourceExcludeFiles, + $xmlConfiguration->source()->restrictNotices(), + $xmlConfiguration->source()->restrictWarnings(), + $xmlConfiguration->source()->ignoreSuppressionOfDeprecations(), + $xmlConfiguration->source()->ignoreSuppressionOfPhpDeprecations(), + $xmlConfiguration->source()->ignoreSuppressionOfErrors(), + $xmlConfiguration->source()->ignoreSuppressionOfNotices(), + $xmlConfiguration->source()->ignoreSuppressionOfPhpNotices(), + $xmlConfiguration->source()->ignoreSuppressionOfWarnings(), + $xmlConfiguration->source()->ignoreSuppressionOfPhpWarnings(), + $xmlConfiguration->source()->deprecationTriggers(), + $xmlConfiguration->source()->ignoreSelfDeprecations(), + $xmlConfiguration->source()->ignoreDirectDeprecations(), + $xmlConfiguration->source()->ignoreIndirectDeprecations(), + ), + $testResultCacheFile, + $coverageClover, + $coverageCobertura, + $coverageCrap4j, + $coverageCrap4jThreshold, + $coverageHtml, + $coverageHtmlLowUpperBound, + $coverageHtmlHighLowerBound, + $coverageHtmlColorSuccessLow, + $coverageHtmlColorSuccessMedium, + $coverageHtmlColorSuccessHigh, + $coverageHtmlColorWarning, + $coverageHtmlColorDanger, + $coverageHtmlCustomCssFile, + $coverageOpenClover, + $coveragePhp, + $coverageText, + $coverageTextShowUncoveredFiles, + $coverageTextShowOnlySummary, + $coverageXml, + $pathCoverage, + $xmlConfiguration->codeCoverage()->ignoreDeprecatedCodeUnits(), + $disableCodeCoverageIgnore, + $failOnAllIssues, + $failOnDeprecation, + $failOnPhpunitDeprecation, + $failOnPhpunitNotice, + $failOnPhpunitWarning, + $failOnEmptyTestSuite, + $failOnIncomplete, + $failOnNotice, + $failOnRisky, + $failOnSkipped, + $failOnWarning, + $doNotFailOnDeprecation, + $doNotFailOnPhpunitDeprecation, + $doNotFailOnPhpunitNotice, + $doNotFailOnPhpunitWarning, + $doNotFailOnEmptyTestSuite, + $doNotFailOnIncomplete, + $doNotFailOnNotice, + $doNotFailOnRisky, + $doNotFailOnSkipped, + $doNotFailOnWarning, + $stopOnDefect, + $stopOnDeprecation, + $specificDeprecationToStopOn, + $stopOnError, + $stopOnFailure, + $stopOnIncomplete, + $stopOnNotice, + $stopOnRisky, + $stopOnSkipped, + $stopOnWarning, + $outputToStandardErrorStream, + $columns, + $noExtensions, + $pharExtensionDirectory, + $extensionBootstrappers, + $backupGlobals, + $backupStaticProperties, + $beStrictAboutChangesToGlobalState, + $colors, + $processIsolation, + $enforceTimeLimit, + $defaultTimeLimit, + $timeoutForSmallTests, + $timeoutForMediumTests, + $timeoutForLargeTests, + $reportUselessTests, + $strictCoverage, + $disallowTestOutput, + $displayDetailsOnAllIssues, + $displayDetailsOnIncompleteTests, + $displayDetailsOnSkippedTests, + $displayDetailsOnTestsThatTriggerDeprecations, + $displayDetailsOnPhpunitDeprecations, + $displayDetailsOnPhpunitNotices, + $displayDetailsOnTestsThatTriggerErrors, + $displayDetailsOnTestsThatTriggerNotices, + $displayDetailsOnTestsThatTriggerWarnings, + $reverseDefectList, + $requireCoverageMetadata, + $noProgress, + $noResults, + $noOutput, + $executionOrder, + $executionOrderDefects, + $resolveDependencies, + $logfileTeamcity, + $logfileJunit, + $logfileOtr, + $includeGitInformationInOtrLogfile, + $logfileTestdoxHtml, + $logfileTestdoxText, + $logEventsText, + $logEventsVerboseText, + $teamCityOutput, + $testDoxOutput, + $testDoxOutputSummary, + $testsCovering, + $testsUsing, + $testsRequiringPhpExtension, + $filter, + $excludeFilter, + $groups, + $excludeGroups, + $randomOrderSeed, + $includeUncoveredFiles, + $xmlConfiguration->testSuite(), + $includeTestSuite, + $excludeTestSuite, + $xmlConfiguration->phpunit()->hasDefaultTestSuite() ? $xmlConfiguration->phpunit()->defaultTestSuite() : null, + $ignoreTestSelectionInXmlConfiguration, + $testSuffixes, + new Php( + DirectoryCollection::fromArray($includePaths), + IniSettingCollection::fromArray($iniSettings), + $xmlConfiguration->php()->constants(), + $xmlConfiguration->php()->globalVariables(), + $xmlConfiguration->php()->envVariables(), + $xmlConfiguration->php()->postVariables(), + $xmlConfiguration->php()->getVariables(), + $xmlConfiguration->php()->cookieVariables(), + $xmlConfiguration->php()->serverVariables(), + $xmlConfiguration->php()->filesVariables(), + $xmlConfiguration->php()->requestVariables(), + ), + $xmlConfiguration->phpunit()->controlGarbageCollector(), + $xmlConfiguration->phpunit()->numberOfTestsBeforeGarbageCollection(), + $generateBaseline, + $cliConfiguration->debug(), + $cliConfiguration->withTelemetry(), + $xmlConfiguration->phpunit()->shortenArraysForExportThreshold(), + ); + } +} diff --git a/src/TextUI/XmlConfiguration/PHP/PhpHandler.php b/src/TextUI/Configuration/PhpHandler.php similarity index 94% rename from src/TextUI/XmlConfiguration/PHP/PhpHandler.php rename to src/TextUI/Configuration/PhpHandler.php index 5fb0c72b21c..3aa13160e16 100644 --- a/src/TextUI/XmlConfiguration/PHP/PhpHandler.php +++ b/src/TextUI/Configuration/PhpHandler.php @@ -7,7 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\TextUI\XmlConfiguration; +namespace PHPUnit\TextUI\Configuration; use const PATH_SEPARATOR; use function constant; @@ -20,9 +20,11 @@ use function putenv; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class PhpHandler +final readonly class PhpHandler { public function handle(Php $configuration): void { @@ -52,7 +54,7 @@ private function handleIncludePaths(DirectoryCollection $includePaths): void 'include_path', implode(PATH_SEPARATOR, $includePathsAsStrings) . PATH_SEPARATOR . - ini_get('include_path') + ini_get('include_path'), ); } } diff --git a/src/TextUI/Configuration/Registry.php b/src/TextUI/Configuration/Registry.php new file mode 100644 index 00000000000..ad8075235d2 --- /dev/null +++ b/src/TextUI/Configuration/Registry.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function assert; +use function file_get_contents; +use function file_put_contents; +use function serialize; +use function unserialize; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\TextUI\CliArguments\Configuration as CliConfiguration; +use PHPUnit\TextUI\CliArguments\Exception; +use PHPUnit\TextUI\XmlConfiguration\Configuration as XmlConfiguration; +use PHPUnit\Util\VersionComparisonOperator; + +/** + * CLI options and XML configuration are static within a single PHPUnit process. + * It is therefore okay to use a Singleton registry here. + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Registry +{ + private static ?Configuration $instance = null; + + public static function saveTo(string $path): bool + { + $result = file_put_contents( + $path, + serialize(self::get()), + ); + + if ($result) { + return true; + } + + // @codeCoverageIgnoreStart + return false; + // @codeCoverageIgnoreEnd + } + + /** + * This method is used by the "run test(s) in separate process" templates. + * + * @noinspection PhpUnused + * + * @codeCoverageIgnore + */ + public static function loadFrom(string $path): void + { + $buffer = file_get_contents($path); + + assert($buffer !== false); + + self::$instance = unserialize( + $buffer, + [ + 'allowed_classes' => [ + Configuration::class, + Php::class, + ConstantCollection::class, + Constant::class, + IniSettingCollection::class, + IniSetting::class, + VariableCollection::class, + Variable::class, + DirectoryCollection::class, + Directory::class, + FileCollection::class, + File::class, + FilterDirectoryCollection::class, + FilterDirectory::class, + TestDirectoryCollection::class, + TestDirectory::class, + TestFileCollection::class, + TestFile::class, + TestSuiteCollection::class, + TestSuite::class, + VersionComparisonOperator::class, + Source::class, + ], + ], + ); + } + + public static function get(): Configuration + { + assert(self::$instance instanceof Configuration); + + return self::$instance; + } + + /** + * @throws \PHPUnit\TextUI\XmlConfiguration\Exception + * @throws Exception + * @throws NoCustomCssFileException + */ + public static function init(CliConfiguration $cliConfiguration, XmlConfiguration $xmlConfiguration): Configuration + { + self::$instance = (new Merger)->merge($cliConfiguration, $xmlConfiguration); + + EventFacade::emitter()->testRunnerConfigured(self::$instance); + + return self::$instance; + } +} diff --git a/src/TextUI/Configuration/SourceFilter.php b/src/TextUI/Configuration/SourceFilter.php new file mode 100644 index 00000000000..845a9b3763f --- /dev/null +++ b/src/TextUI/Configuration/SourceFilter.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SourceFilter +{ + private static ?self $instance = null; + + /** + * @var array + */ + private readonly array $map; + + public static function instance(): self + { + if (self::$instance === null) { + self::$instance = new self( + (new SourceMapper)->map( + Registry::get()->source(), + ), + ); + } + + return self::$instance; + } + + /** + * @param array $map + */ + public function __construct(array $map) + { + $this->map = $map; + } + + public function includes(string $path): bool + { + return isset($this->map[$path]); + } +} diff --git a/src/TextUI/Configuration/SourceMapper.php b/src/TextUI/Configuration/SourceMapper.php new file mode 100644 index 00000000000..c2c5483822f --- /dev/null +++ b/src/TextUI/Configuration/SourceMapper.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function realpath; +use SebastianBergmann\FileIterator\Facade as FileIteratorFacade; +use SplObjectStorage; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SourceMapper +{ + /** + * @var ?SplObjectStorage> + */ + private static ?SplObjectStorage $files = null; + + /** + * @return array + */ + public function map(Source $source): array + { + if (self::$files === null) { + self::$files = new SplObjectStorage; + } + + if (isset(self::$files[$source])) { + return self::$files[$source]; + } + + $files = []; + + $directories = $this->aggregateDirectories($source->includeDirectories()); + + foreach ($directories as $path => [$prefixes, $suffixes]) { + foreach ((new FileIteratorFacade)->getFilesAsArray($path, $suffixes, $prefixes) as $file) { + $file = realpath($file); + + if (!$file) { + continue; + } + + $files[$file] = true; + } + } + + foreach ($source->includeFiles() as $file) { + $file = realpath($file->path()); + + if (!$file) { + continue; + } + + $files[$file] = true; + } + + $directories = $this->aggregateDirectories($source->excludeDirectories()); + + foreach ($directories as $path => [$prefixes, $suffixes]) { + foreach ((new FileIteratorFacade)->getFilesAsArray($path, $suffixes, $prefixes) as $file) { + $file = realpath($file); + + if (!$file) { + continue; + } + + if (!isset($files[$file])) { + continue; + } + + unset($files[$file]); + } + } + + foreach ($source->excludeFiles() as $file) { + $file = realpath($file->path()); + + if (!$file) { + continue; + } + + if (!isset($files[$file])) { + continue; + } + + unset($files[$file]); + } + + self::$files[$source] = $files; + + return $files; + } + + /** + * @return array,list}> + */ + private function aggregateDirectories(FilterDirectoryCollection $directories): array + { + $aggregated = []; + + foreach ($directories as $directory) { + if (!isset($aggregated[$directory->path()])) { + $aggregated[$directory->path()] = [ + 0 => [], + 1 => [], + ]; + } + + $prefix = $directory->prefix(); + + if ($prefix !== '') { + $aggregated[$directory->path()][0][] = $prefix; + } + + $suffix = $directory->suffix(); + + if ($suffix !== '') { + $aggregated[$directory->path()][1][] = $suffix; + } + } + + return $aggregated; + } +} diff --git a/src/TextUI/Configuration/TestSuiteBuilder.php b/src/TextUI/Configuration/TestSuiteBuilder.php new file mode 100644 index 00000000000..a474b02f548 --- /dev/null +++ b/src/TextUI/Configuration/TestSuiteBuilder.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use const PHP_EOL; +use function assert; +use function count; +use function is_dir; +use function is_file; +use function realpath; +use function str_ends_with; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Exception; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Runner\TestSuiteLoader; +use PHPUnit\TextUI\RuntimeException; +use PHPUnit\TextUI\TestDirectoryNotFoundException; +use PHPUnit\TextUI\TestFileNotFoundException; +use PHPUnit\TextUI\XmlConfiguration\TestSuiteMapper; +use SebastianBergmann\FileIterator\Facade as FileIteratorFacade; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteBuilder +{ + /** + * @throws \PHPUnit\Framework\Exception + * @throws RuntimeException + * @throws TestDirectoryNotFoundException + * @throws TestFileNotFoundException + */ + public function build(Configuration $configuration): TestSuite + { + if ($configuration->hasCliArguments()) { + $arguments = []; + + foreach ($configuration->cliArguments() as $cliArgument) { + $argument = realpath($cliArgument); + + if (!$argument) { + throw new TestFileNotFoundException($cliArgument); + } + + $arguments[] = $argument; + } + + if (count($arguments) === 1) { + $testSuite = $this->testSuiteFromPath( + $arguments[0], + $configuration->testSuffixes(), + ); + } else { + $testSuite = $this->testSuiteFromPathList( + $arguments, + $configuration->testSuffixes(), + ); + } + } + + if (!isset($testSuite)) { + $xmlConfigurationFile = $configuration->hasConfigurationFile() ? $configuration->configurationFile() : 'Root Test Suite'; + + assert($xmlConfigurationFile !== ''); + + $testSuite = (new TestSuiteMapper)->map( + $xmlConfigurationFile, + $configuration->testSuite(), + $configuration->ignoreTestSelectionInXmlConfiguration() ? [] : $configuration->includeTestSuites(), + $configuration->ignoreTestSelectionInXmlConfiguration() ? [] : $configuration->excludeTestSuites(), + ); + } + + EventFacade::emitter()->testSuiteLoaded(\PHPUnit\Event\TestSuite\TestSuiteBuilder::from($testSuite)); + + return $testSuite; + } + + /** + * @param non-empty-string $path + * @param list $suffixes + * + * @throws \PHPUnit\Framework\Exception + */ + private function testSuiteFromPath(string $path, array $suffixes, ?TestSuite $suite = null): TestSuite + { + if (str_ends_with($path, '.phpt') && is_file($path)) { + if ($suite === null) { + $suite = TestSuite::empty($path); + } + + $suite->addTestFile($path); + + return $suite; + } + + if (is_dir($path)) { + $files = (new FileIteratorFacade)->getFilesAsArray($path, $suffixes); + + if ($suite === null) { + $suite = TestSuite::empty('CLI Arguments'); + } + + $suite->addTestFiles($files); + + return $suite; + } + + try { + $testClass = (new TestSuiteLoader)->load($path); + } catch (Exception $e) { + print $e->getMessage() . PHP_EOL; + + exit(1); + } + + if ($suite === null) { + return TestSuite::fromClassReflector($testClass); + } + + $suite->addTestSuite($testClass); + + return $suite; + } + + /** + * @param list $paths + * @param list $suffixes + * + * @throws \PHPUnit\Framework\Exception + */ + private function testSuiteFromPathList(array $paths, array $suffixes): TestSuite + { + $suite = TestSuite::empty('CLI Arguments'); + + foreach ($paths as $path) { + $this->testSuiteFromPath($path, $suffixes, $suite); + } + + return $suite; + } +} diff --git a/src/TextUI/Configuration/Value/Constant.php b/src/TextUI/Configuration/Value/Constant.php new file mode 100644 index 00000000000..0ff240dd03e --- /dev/null +++ b/src/TextUI/Configuration/Value/Constant.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Constant +{ + private string $name; + private bool|string $value; + + public function __construct(string $name, bool|string $value) + { + $this->name = $name; + $this->value = $value; + } + + public function name(): string + { + return $this->name; + } + + public function value(): bool|string + { + return $this->value; + } +} diff --git a/src/TextUI/Configuration/Value/ConstantCollection.php b/src/TextUI/Configuration/Value/ConstantCollection.php new file mode 100644 index 00000000000..3e34d7434ec --- /dev/null +++ b/src/TextUI/Configuration/Value/ConstantCollection.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + * + * @template-implements IteratorAggregate + */ +final readonly class ConstantCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $constants; + + /** + * @param list $constants + */ + public static function fromArray(array $constants): self + { + return new self(...$constants); + } + + private function __construct(Constant ...$constants) + { + $this->constants = $constants; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->constants; + } + + public function count(): int + { + return count($this->constants); + } + + public function getIterator(): ConstantCollectionIterator + { + return new ConstantCollectionIterator($this); + } +} diff --git a/src/TextUI/Configuration/Value/ConstantCollectionIterator.php b/src/TextUI/Configuration/Value/ConstantCollectionIterator.php new file mode 100644 index 00000000000..f385b7faf4f --- /dev/null +++ b/src/TextUI/Configuration/Value/ConstantCollectionIterator.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function count; +use Iterator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class ConstantCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $constants; + private int $position = 0; + + public function __construct(ConstantCollection $constants) + { + $this->constants = $constants->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->constants); + } + + public function key(): int + { + return $this->position; + } + + public function current(): Constant + { + return $this->constants[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/src/TextUI/Configuration/Value/Directory.php b/src/TextUI/Configuration/Value/Directory.php new file mode 100644 index 00000000000..f44e28b1559 --- /dev/null +++ b/src/TextUI/Configuration/Value/Directory.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Directory +{ + private string $path; + + public function __construct(string $path) + { + $this->path = $path; + } + + public function path(): string + { + return $this->path; + } +} diff --git a/src/TextUI/Configuration/Value/DirectoryCollection.php b/src/TextUI/Configuration/Value/DirectoryCollection.php new file mode 100644 index 00000000000..dc1e840c94c --- /dev/null +++ b/src/TextUI/Configuration/Value/DirectoryCollection.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + * + * @template-implements IteratorAggregate + */ +final readonly class DirectoryCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $directories; + + /** + * @param list $directories + */ + public static function fromArray(array $directories): self + { + return new self(...$directories); + } + + private function __construct(Directory ...$directories) + { + $this->directories = $directories; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->directories; + } + + public function count(): int + { + return count($this->directories); + } + + public function getIterator(): DirectoryCollectionIterator + { + return new DirectoryCollectionIterator($this); + } + + public function isEmpty(): bool + { + return $this->count() === 0; + } +} diff --git a/src/TextUI/Configuration/Value/DirectoryCollectionIterator.php b/src/TextUI/Configuration/Value/DirectoryCollectionIterator.php new file mode 100644 index 00000000000..73d2cff6344 --- /dev/null +++ b/src/TextUI/Configuration/Value/DirectoryCollectionIterator.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function count; +use Iterator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class DirectoryCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $directories; + private int $position = 0; + + public function __construct(DirectoryCollection $directories) + { + $this->directories = $directories->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->directories); + } + + public function key(): int + { + return $this->position; + } + + public function current(): Directory + { + return $this->directories[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/src/TextUI/Configuration/Value/ExtensionBootstrap.php b/src/TextUI/Configuration/Value/ExtensionBootstrap.php new file mode 100644 index 00000000000..09430c7f3f7 --- /dev/null +++ b/src/TextUI/Configuration/Value/ExtensionBootstrap.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class ExtensionBootstrap +{ + /** + * @var non-empty-string + */ + private string $className; + + /** + * @var array + */ + private array $parameters; + + /** + * @param non-empty-string $className + * @param array $parameters + */ + public function __construct(string $className, array $parameters) + { + $this->className = $className; + $this->parameters = $parameters; + } + + /** + * @return non-empty-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return array + */ + public function parameters(): array + { + return $this->parameters; + } +} diff --git a/src/TextUI/Configuration/Value/ExtensionBootstrapCollection.php b/src/TextUI/Configuration/Value/ExtensionBootstrapCollection.php new file mode 100644 index 00000000000..16ca1e07049 --- /dev/null +++ b/src/TextUI/Configuration/Value/ExtensionBootstrapCollection.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use IteratorAggregate; + +/** + * @template-implements IteratorAggregate + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class ExtensionBootstrapCollection implements IteratorAggregate +{ + /** + * @var list + */ + private array $extensionBootstraps; + + /** + * @param list $extensionBootstraps + */ + public static function fromArray(array $extensionBootstraps): self + { + return new self(...$extensionBootstraps); + } + + private function __construct(ExtensionBootstrap ...$extensionBootstraps) + { + $this->extensionBootstraps = $extensionBootstraps; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->extensionBootstraps; + } + + public function getIterator(): ExtensionBootstrapCollectionIterator + { + return new ExtensionBootstrapCollectionIterator($this); + } +} diff --git a/src/TextUI/Configuration/Value/ExtensionBootstrapCollectionIterator.php b/src/TextUI/Configuration/Value/ExtensionBootstrapCollectionIterator.php new file mode 100644 index 00000000000..0b5c20ba15c --- /dev/null +++ b/src/TextUI/Configuration/Value/ExtensionBootstrapCollectionIterator.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function count; +use Iterator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class ExtensionBootstrapCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $extensionBootstraps; + private int $position = 0; + + public function __construct(ExtensionBootstrapCollection $extensionBootstraps) + { + $this->extensionBootstraps = $extensionBootstraps->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->extensionBootstraps); + } + + public function key(): int + { + return $this->position; + } + + public function current(): ExtensionBootstrap + { + return $this->extensionBootstraps[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/src/TextUI/Configuration/Value/File.php b/src/TextUI/Configuration/Value/File.php new file mode 100644 index 00000000000..85900f47f6c --- /dev/null +++ b/src/TextUI/Configuration/Value/File.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class File +{ + /** + * @var non-empty-string + */ + private string $path; + + /** + * @param non-empty-string $path + */ + public function __construct(string $path) + { + $this->path = $path; + } + + /** + * @return non-empty-string + */ + public function path(): string + { + return $this->path; + } +} diff --git a/src/TextUI/Configuration/Value/FileCollection.php b/src/TextUI/Configuration/Value/FileCollection.php new file mode 100644 index 00000000000..61522a5eb6d --- /dev/null +++ b/src/TextUI/Configuration/Value/FileCollection.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + * + * @template-implements IteratorAggregate + */ +final readonly class FileCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $files; + + /** + * @param list $files + */ + public static function fromArray(array $files): self + { + return new self(...$files); + } + + private function __construct(File ...$files) + { + $this->files = $files; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->files; + } + + public function count(): int + { + return count($this->files); + } + + public function notEmpty(): bool + { + return $this->files !== []; + } + + public function getIterator(): FileCollectionIterator + { + return new FileCollectionIterator($this); + } +} diff --git a/src/TextUI/Configuration/Value/FileCollectionIterator.php b/src/TextUI/Configuration/Value/FileCollectionIterator.php new file mode 100644 index 00000000000..91ec8e27638 --- /dev/null +++ b/src/TextUI/Configuration/Value/FileCollectionIterator.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function count; +use Iterator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class FileCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $files; + private int $position = 0; + + public function __construct(FileCollection $files) + { + $this->files = $files->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->files); + } + + public function key(): int + { + return $this->position; + } + + public function current(): File + { + return $this->files[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/src/TextUI/Configuration/Value/FilterDirectory.php b/src/TextUI/Configuration/Value/FilterDirectory.php new file mode 100644 index 00000000000..52dcd1b1323 --- /dev/null +++ b/src/TextUI/Configuration/Value/FilterDirectory.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class FilterDirectory +{ + /** + * @var non-empty-string + */ + private string $path; + private string $prefix; + private string $suffix; + + /** + * @param non-empty-string $path + */ + public function __construct(string $path, string $prefix, string $suffix) + { + $this->path = $path; + $this->prefix = $prefix; + $this->suffix = $suffix; + } + + /** + * @return non-empty-string + */ + public function path(): string + { + return $this->path; + } + + public function prefix(): string + { + return $this->prefix; + } + + public function suffix(): string + { + return $this->suffix; + } +} diff --git a/src/TextUI/Configuration/Value/FilterDirectoryCollection.php b/src/TextUI/Configuration/Value/FilterDirectoryCollection.php new file mode 100644 index 00000000000..147f0618f0d --- /dev/null +++ b/src/TextUI/Configuration/Value/FilterDirectoryCollection.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + * + * @template-implements IteratorAggregate + */ +final readonly class FilterDirectoryCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $directories; + + /** + * @param list $directories + */ + public static function fromArray(array $directories): self + { + return new self(...$directories); + } + + private function __construct(FilterDirectory ...$directories) + { + $this->directories = $directories; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->directories; + } + + public function count(): int + { + return count($this->directories); + } + + public function notEmpty(): bool + { + return $this->directories !== []; + } + + public function getIterator(): FilterDirectoryCollectionIterator + { + return new FilterDirectoryCollectionIterator($this); + } +} diff --git a/src/TextUI/Configuration/Value/FilterDirectoryCollectionIterator.php b/src/TextUI/Configuration/Value/FilterDirectoryCollectionIterator.php new file mode 100644 index 00000000000..737c752f465 --- /dev/null +++ b/src/TextUI/Configuration/Value/FilterDirectoryCollectionIterator.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function count; +use Iterator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class FilterDirectoryCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $directories; + private int $position = 0; + + public function __construct(FilterDirectoryCollection $directories) + { + $this->directories = $directories->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->directories); + } + + public function key(): int + { + return $this->position; + } + + public function current(): FilterDirectory + { + return $this->directories[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/src/TextUI/Configuration/Value/Group.php b/src/TextUI/Configuration/Value/Group.php new file mode 100644 index 00000000000..cb0bdc8aa3e --- /dev/null +++ b/src/TextUI/Configuration/Value/Group.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Group +{ + private string $name; + + public function __construct(string $name) + { + $this->name = $name; + } + + public function name(): string + { + return $this->name; + } +} diff --git a/src/TextUI/Configuration/Value/GroupCollection.php b/src/TextUI/Configuration/Value/GroupCollection.php new file mode 100644 index 00000000000..8232e1f34fc --- /dev/null +++ b/src/TextUI/Configuration/Value/GroupCollection.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use IteratorAggregate; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + * + * @template-implements IteratorAggregate + */ +final readonly class GroupCollection implements IteratorAggregate +{ + /** + * @var list + */ + private array $groups; + + /** + * @param list $groups + */ + public static function fromArray(array $groups): self + { + return new self(...$groups); + } + + private function __construct(Group ...$groups) + { + $this->groups = $groups; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->groups; + } + + /** + * @return list + */ + public function asArrayOfStrings(): array + { + $result = []; + + foreach ($this->groups as $group) { + $result[] = $group->name(); + } + + return $result; + } + + public function isEmpty(): bool + { + return $this->groups === []; + } + + public function getIterator(): GroupCollectionIterator + { + return new GroupCollectionIterator($this); + } +} diff --git a/src/TextUI/Configuration/Value/GroupCollectionIterator.php b/src/TextUI/Configuration/Value/GroupCollectionIterator.php new file mode 100644 index 00000000000..774808757cb --- /dev/null +++ b/src/TextUI/Configuration/Value/GroupCollectionIterator.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function count; +use Iterator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class GroupCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $groups; + private int $position = 0; + + public function __construct(GroupCollection $groups) + { + $this->groups = $groups->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->groups); + } + + public function key(): int + { + return $this->position; + } + + public function current(): Group + { + return $this->groups[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/src/TextUI/Configuration/Value/IniSetting.php b/src/TextUI/Configuration/Value/IniSetting.php new file mode 100644 index 00000000000..b4d11665551 --- /dev/null +++ b/src/TextUI/Configuration/Value/IniSetting.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class IniSetting +{ + private string $name; + private string $value; + + public function __construct(string $name, string $value) + { + $this->name = $name; + $this->value = $value; + } + + public function name(): string + { + return $this->name; + } + + public function value(): string + { + return $this->value; + } +} diff --git a/src/TextUI/Configuration/Value/IniSettingCollection.php b/src/TextUI/Configuration/Value/IniSettingCollection.php new file mode 100644 index 00000000000..abfd8fd241b --- /dev/null +++ b/src/TextUI/Configuration/Value/IniSettingCollection.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + * + * @template-implements IteratorAggregate + */ +final readonly class IniSettingCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $iniSettings; + + /** + * @param list $iniSettings + */ + public static function fromArray(array $iniSettings): self + { + return new self(...$iniSettings); + } + + private function __construct(IniSetting ...$iniSettings) + { + $this->iniSettings = $iniSettings; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->iniSettings; + } + + public function count(): int + { + return count($this->iniSettings); + } + + public function getIterator(): IniSettingCollectionIterator + { + return new IniSettingCollectionIterator($this); + } +} diff --git a/src/TextUI/Configuration/Value/IniSettingCollectionIterator.php b/src/TextUI/Configuration/Value/IniSettingCollectionIterator.php new file mode 100644 index 00000000000..cb68c3dd1a4 --- /dev/null +++ b/src/TextUI/Configuration/Value/IniSettingCollectionIterator.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function count; +use Iterator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class IniSettingCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $iniSettings; + private int $position = 0; + + public function __construct(IniSettingCollection $iniSettings) + { + $this->iniSettings = $iniSettings->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->iniSettings); + } + + public function key(): int + { + return $this->position; + } + + public function current(): IniSetting + { + return $this->iniSettings[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/src/TextUI/Configuration/Value/Php.php b/src/TextUI/Configuration/Value/Php.php new file mode 100644 index 00000000000..0dc4735dca7 --- /dev/null +++ b/src/TextUI/Configuration/Value/Php.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Php +{ + private DirectoryCollection $includePaths; + private IniSettingCollection $iniSettings; + private ConstantCollection $constants; + private VariableCollection $globalVariables; + private VariableCollection $envVariables; + private VariableCollection $postVariables; + private VariableCollection $getVariables; + private VariableCollection $cookieVariables; + private VariableCollection $serverVariables; + private VariableCollection $filesVariables; + private VariableCollection $requestVariables; + + public function __construct(DirectoryCollection $includePaths, IniSettingCollection $iniSettings, ConstantCollection $constants, VariableCollection $globalVariables, VariableCollection $envVariables, VariableCollection $postVariables, VariableCollection $getVariables, VariableCollection $cookieVariables, VariableCollection $serverVariables, VariableCollection $filesVariables, VariableCollection $requestVariables) + { + $this->includePaths = $includePaths; + $this->iniSettings = $iniSettings; + $this->constants = $constants; + $this->globalVariables = $globalVariables; + $this->envVariables = $envVariables; + $this->postVariables = $postVariables; + $this->getVariables = $getVariables; + $this->cookieVariables = $cookieVariables; + $this->serverVariables = $serverVariables; + $this->filesVariables = $filesVariables; + $this->requestVariables = $requestVariables; + } + + public function includePaths(): DirectoryCollection + { + return $this->includePaths; + } + + public function iniSettings(): IniSettingCollection + { + return $this->iniSettings; + } + + public function constants(): ConstantCollection + { + return $this->constants; + } + + public function globalVariables(): VariableCollection + { + return $this->globalVariables; + } + + public function envVariables(): VariableCollection + { + return $this->envVariables; + } + + public function postVariables(): VariableCollection + { + return $this->postVariables; + } + + public function getVariables(): VariableCollection + { + return $this->getVariables; + } + + public function cookieVariables(): VariableCollection + { + return $this->cookieVariables; + } + + public function serverVariables(): VariableCollection + { + return $this->serverVariables; + } + + public function filesVariables(): VariableCollection + { + return $this->filesVariables; + } + + public function requestVariables(): VariableCollection + { + return $this->requestVariables; + } +} diff --git a/src/TextUI/Configuration/Value/Source.php b/src/TextUI/Configuration/Value/Source.php new file mode 100644 index 00000000000..b6f61eb07d9 --- /dev/null +++ b/src/TextUI/Configuration/Value/Source.php @@ -0,0 +1,195 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Source +{ + /** + * @var non-empty-string + */ + private ?string $baseline; + private bool $ignoreBaseline; + private FilterDirectoryCollection $includeDirectories; + private FileCollection $includeFiles; + private FilterDirectoryCollection $excludeDirectories; + private FileCollection $excludeFiles; + private bool $restrictNotices; + private bool $restrictWarnings; + private bool $ignoreSuppressionOfDeprecations; + private bool $ignoreSuppressionOfPhpDeprecations; + private bool $ignoreSuppressionOfErrors; + private bool $ignoreSuppressionOfNotices; + private bool $ignoreSuppressionOfPhpNotices; + private bool $ignoreSuppressionOfWarnings; + private bool $ignoreSuppressionOfPhpWarnings; + private bool $ignoreSelfDeprecations; + private bool $ignoreDirectDeprecations; + private bool $ignoreIndirectDeprecations; + + /** + * @var array{functions: list, methods: list} + */ + private array $deprecationTriggers; + + /** + * @param non-empty-string $baseline + * @param array{functions: list, methods: list} $deprecationTriggers + */ + public function __construct(?string $baseline, bool $ignoreBaseline, FilterDirectoryCollection $includeDirectories, FileCollection $includeFiles, FilterDirectoryCollection $excludeDirectories, FileCollection $excludeFiles, bool $restrictNotices, bool $restrictWarnings, bool $ignoreSuppressionOfDeprecations, bool $ignoreSuppressionOfPhpDeprecations, bool $ignoreSuppressionOfErrors, bool $ignoreSuppressionOfNotices, bool $ignoreSuppressionOfPhpNotices, bool $ignoreSuppressionOfWarnings, bool $ignoreSuppressionOfPhpWarnings, array $deprecationTriggers, bool $ignoreSelfDeprecations, bool $ignoreDirectDeprecations, bool $ignoreIndirectDeprecations) + { + $this->baseline = $baseline; + $this->ignoreBaseline = $ignoreBaseline; + $this->includeDirectories = $includeDirectories; + $this->includeFiles = $includeFiles; + $this->excludeDirectories = $excludeDirectories; + $this->excludeFiles = $excludeFiles; + $this->restrictNotices = $restrictNotices; + $this->restrictWarnings = $restrictWarnings; + $this->ignoreSuppressionOfDeprecations = $ignoreSuppressionOfDeprecations; + $this->ignoreSuppressionOfPhpDeprecations = $ignoreSuppressionOfPhpDeprecations; + $this->ignoreSuppressionOfErrors = $ignoreSuppressionOfErrors; + $this->ignoreSuppressionOfNotices = $ignoreSuppressionOfNotices; + $this->ignoreSuppressionOfPhpNotices = $ignoreSuppressionOfPhpNotices; + $this->ignoreSuppressionOfWarnings = $ignoreSuppressionOfWarnings; + $this->ignoreSuppressionOfPhpWarnings = $ignoreSuppressionOfPhpWarnings; + $this->deprecationTriggers = $deprecationTriggers; + $this->ignoreSelfDeprecations = $ignoreSelfDeprecations; + $this->ignoreDirectDeprecations = $ignoreDirectDeprecations; + $this->ignoreIndirectDeprecations = $ignoreIndirectDeprecations; + } + + /** + * @phpstan-assert-if-true !null $this->baseline + */ + public function useBaseline(): bool + { + return $this->hasBaseline() && !$this->ignoreBaseline; + } + + /** + * @phpstan-assert-if-true !null $this->baseline + */ + public function hasBaseline(): bool + { + return $this->baseline !== null; + } + + /** + * @throws NoBaselineException + * + * @return non-empty-string + */ + public function baseline(): string + { + if (!$this->hasBaseline()) { + throw new NoBaselineException; + } + + return $this->baseline; + } + + public function includeDirectories(): FilterDirectoryCollection + { + return $this->includeDirectories; + } + + public function includeFiles(): FileCollection + { + return $this->includeFiles; + } + + public function excludeDirectories(): FilterDirectoryCollection + { + return $this->excludeDirectories; + } + + public function excludeFiles(): FileCollection + { + return $this->excludeFiles; + } + + public function notEmpty(): bool + { + return $this->includeDirectories->notEmpty() || $this->includeFiles->notEmpty(); + } + + public function restrictNotices(): bool + { + return $this->restrictNotices; + } + + public function restrictWarnings(): bool + { + return $this->restrictWarnings; + } + + public function ignoreSuppressionOfDeprecations(): bool + { + return $this->ignoreSuppressionOfDeprecations; + } + + public function ignoreSuppressionOfPhpDeprecations(): bool + { + return $this->ignoreSuppressionOfPhpDeprecations; + } + + public function ignoreSuppressionOfErrors(): bool + { + return $this->ignoreSuppressionOfErrors; + } + + public function ignoreSuppressionOfNotices(): bool + { + return $this->ignoreSuppressionOfNotices; + } + + public function ignoreSuppressionOfPhpNotices(): bool + { + return $this->ignoreSuppressionOfPhpNotices; + } + + public function ignoreSuppressionOfWarnings(): bool + { + return $this->ignoreSuppressionOfWarnings; + } + + public function ignoreSuppressionOfPhpWarnings(): bool + { + return $this->ignoreSuppressionOfPhpWarnings; + } + + /** + * @return array{functions: list, methods: list} + */ + public function deprecationTriggers(): array + { + return $this->deprecationTriggers; + } + + public function ignoreSelfDeprecations(): bool + { + return $this->ignoreSelfDeprecations; + } + + public function ignoreDirectDeprecations(): bool + { + return $this->ignoreDirectDeprecations; + } + + public function ignoreIndirectDeprecations(): bool + { + return $this->ignoreIndirectDeprecations; + } +} diff --git a/src/TextUI/Configuration/Value/TestDirectory.php b/src/TextUI/Configuration/Value/TestDirectory.php new file mode 100644 index 00000000000..dfe301a941e --- /dev/null +++ b/src/TextUI/Configuration/Value/TestDirectory.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Util\VersionComparisonOperator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class TestDirectory +{ + /** + * @var non-empty-string + */ + private string $path; + private string $prefix; + private string $suffix; + private string $phpVersion; + private VersionComparisonOperator $phpVersionOperator; + + /** + * @var list + */ + private array $groups; + + /** + * @param non-empty-string $path + * @param list $groups + */ + public function __construct(string $path, string $prefix, string $suffix, string $phpVersion, VersionComparisonOperator $phpVersionOperator, array $groups) + { + $this->path = $path; + $this->prefix = $prefix; + $this->suffix = $suffix; + $this->phpVersion = $phpVersion; + $this->phpVersionOperator = $phpVersionOperator; + $this->groups = $groups; + } + + /** + * @return non-empty-string + */ + public function path(): string + { + return $this->path; + } + + public function prefix(): string + { + return $this->prefix; + } + + public function suffix(): string + { + return $this->suffix; + } + + public function phpVersion(): string + { + return $this->phpVersion; + } + + public function phpVersionOperator(): VersionComparisonOperator + { + return $this->phpVersionOperator; + } + + /** + * @return list + */ + public function groups(): array + { + return $this->groups; + } +} diff --git a/src/TextUI/Configuration/Value/TestDirectoryCollection.php b/src/TextUI/Configuration/Value/TestDirectoryCollection.php new file mode 100644 index 00000000000..ba867273ce2 --- /dev/null +++ b/src/TextUI/Configuration/Value/TestDirectoryCollection.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + * + * @template-implements IteratorAggregate + */ +final readonly class TestDirectoryCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $directories; + + /** + * @param list $directories + */ + public static function fromArray(array $directories): self + { + return new self(...$directories); + } + + private function __construct(TestDirectory ...$directories) + { + $this->directories = $directories; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->directories; + } + + public function count(): int + { + return count($this->directories); + } + + public function getIterator(): TestDirectoryCollectionIterator + { + return new TestDirectoryCollectionIterator($this); + } + + public function isEmpty(): bool + { + return $this->count() === 0; + } +} diff --git a/src/TextUI/Configuration/Value/TestDirectoryCollectionIterator.php b/src/TextUI/Configuration/Value/TestDirectoryCollectionIterator.php new file mode 100644 index 00000000000..fa57410a633 --- /dev/null +++ b/src/TextUI/Configuration/Value/TestDirectoryCollectionIterator.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function count; +use Iterator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class TestDirectoryCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $directories; + private int $position = 0; + + public function __construct(TestDirectoryCollection $directories) + { + $this->directories = $directories->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->directories); + } + + public function key(): int + { + return $this->position; + } + + public function current(): TestDirectory + { + return $this->directories[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/src/TextUI/Configuration/Value/TestFile.php b/src/TextUI/Configuration/Value/TestFile.php new file mode 100644 index 00000000000..e658ff88437 --- /dev/null +++ b/src/TextUI/Configuration/Value/TestFile.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Util\VersionComparisonOperator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class TestFile +{ + /** + * @var non-empty-string + */ + private string $path; + private string $phpVersion; + private VersionComparisonOperator $phpVersionOperator; + + /** + * @var list + */ + private array $groups; + + /** + * @param non-empty-string $path + * @param list $groups + */ + public function __construct(string $path, string $phpVersion, VersionComparisonOperator $phpVersionOperator, array $groups) + { + $this->path = $path; + $this->phpVersion = $phpVersion; + $this->phpVersionOperator = $phpVersionOperator; + $this->groups = $groups; + } + + /** + * @return non-empty-string + */ + public function path(): string + { + return $this->path; + } + + public function phpVersion(): string + { + return $this->phpVersion; + } + + public function phpVersionOperator(): VersionComparisonOperator + { + return $this->phpVersionOperator; + } + + /** + * @return list + */ + public function groups(): array + { + return $this->groups; + } +} diff --git a/src/TextUI/Configuration/Value/TestFileCollection.php b/src/TextUI/Configuration/Value/TestFileCollection.php new file mode 100644 index 00000000000..6d1ae27993c --- /dev/null +++ b/src/TextUI/Configuration/Value/TestFileCollection.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + * + * @template-implements IteratorAggregate + */ +final readonly class TestFileCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $files; + + /** + * @param list $files + */ + public static function fromArray(array $files): self + { + return new self(...$files); + } + + private function __construct(TestFile ...$files) + { + $this->files = $files; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->files; + } + + public function count(): int + { + return count($this->files); + } + + public function getIterator(): TestFileCollectionIterator + { + return new TestFileCollectionIterator($this); + } + + public function isEmpty(): bool + { + return $this->count() === 0; + } +} diff --git a/src/TextUI/Configuration/Value/TestFileCollectionIterator.php b/src/TextUI/Configuration/Value/TestFileCollectionIterator.php new file mode 100644 index 00000000000..ed328e9ec36 --- /dev/null +++ b/src/TextUI/Configuration/Value/TestFileCollectionIterator.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function count; +use Iterator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class TestFileCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $files; + private int $position = 0; + + public function __construct(TestFileCollection $files) + { + $this->files = $files->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->files); + } + + public function key(): int + { + return $this->position; + } + + public function current(): TestFile + { + return $this->files[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/src/TextUI/Configuration/Value/TestSuite.php b/src/TextUI/Configuration/Value/TestSuite.php new file mode 100644 index 00000000000..fdba72e0bad --- /dev/null +++ b/src/TextUI/Configuration/Value/TestSuite.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class TestSuite +{ + /** + * @var non-empty-string + */ + private string $name; + private TestDirectoryCollection $directories; + private TestFileCollection $files; + private FileCollection $exclude; + + /** + * @param non-empty-string $name + */ + public function __construct(string $name, TestDirectoryCollection $directories, TestFileCollection $files, FileCollection $exclude) + { + $this->name = $name; + $this->directories = $directories; + $this->files = $files; + $this->exclude = $exclude; + } + + /** + * @return non-empty-string + */ + public function name(): string + { + return $this->name; + } + + public function directories(): TestDirectoryCollection + { + return $this->directories; + } + + public function files(): TestFileCollection + { + return $this->files; + } + + public function exclude(): FileCollection + { + return $this->exclude; + } +} diff --git a/src/TextUI/Configuration/Value/TestSuiteCollection.php b/src/TextUI/Configuration/Value/TestSuiteCollection.php new file mode 100644 index 00000000000..26c9a645709 --- /dev/null +++ b/src/TextUI/Configuration/Value/TestSuiteCollection.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + * + * @template-implements IteratorAggregate + */ +final readonly class TestSuiteCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $testSuites; + + /** + * @param list $testSuites + */ + public static function fromArray(array $testSuites): self + { + return new self(...$testSuites); + } + + private function __construct(TestSuite ...$testSuites) + { + $this->testSuites = $testSuites; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->testSuites; + } + + public function count(): int + { + return count($this->testSuites); + } + + public function getIterator(): TestSuiteCollectionIterator + { + return new TestSuiteCollectionIterator($this); + } + + public function isEmpty(): bool + { + return $this->count() === 0; + } +} diff --git a/src/TextUI/Configuration/Value/TestSuiteCollectionIterator.php b/src/TextUI/Configuration/Value/TestSuiteCollectionIterator.php new file mode 100644 index 00000000000..d0b0768a48d --- /dev/null +++ b/src/TextUI/Configuration/Value/TestSuiteCollectionIterator.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function count; +use Iterator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class TestSuiteCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $testSuites; + private int $position = 0; + + public function __construct(TestSuiteCollection $testSuites) + { + $this->testSuites = $testSuites->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->testSuites); + } + + public function key(): int + { + return $this->position; + } + + public function current(): TestSuite + { + return $this->testSuites[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/src/TextUI/Configuration/Value/Variable.php b/src/TextUI/Configuration/Value/Variable.php new file mode 100644 index 00000000000..cc0425c8c6d --- /dev/null +++ b/src/TextUI/Configuration/Value/Variable.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Variable +{ + private string $name; + private mixed $value; + private bool $force; + + public function __construct(string $name, mixed $value, bool $force) + { + $this->name = $name; + $this->value = $value; + $this->force = $force; + } + + public function name(): string + { + return $this->name; + } + + public function value(): mixed + { + return $this->value; + } + + public function force(): bool + { + return $this->force; + } +} diff --git a/src/TextUI/Configuration/Value/VariableCollection.php b/src/TextUI/Configuration/Value/VariableCollection.php new file mode 100644 index 00000000000..77fb4cff965 --- /dev/null +++ b/src/TextUI/Configuration/Value/VariableCollection.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + * + * @template-implements IteratorAggregate + */ +final readonly class VariableCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $variables; + + /** + * @param list $variables + */ + public static function fromArray(array $variables): self + { + return new self(...$variables); + } + + private function __construct(Variable ...$variables) + { + $this->variables = $variables; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->variables; + } + + public function count(): int + { + return count($this->variables); + } + + public function getIterator(): VariableCollectionIterator + { + return new VariableCollectionIterator($this); + } +} diff --git a/src/TextUI/Configuration/Value/VariableCollectionIterator.php b/src/TextUI/Configuration/Value/VariableCollectionIterator.php new file mode 100644 index 00000000000..2e32194c17c --- /dev/null +++ b/src/TextUI/Configuration/Value/VariableCollectionIterator.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function count; +use Iterator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class VariableCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $variables; + private int $position = 0; + + public function __construct(VariableCollection $variables) + { + $this->variables = $variables->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->variables); + } + + public function key(): int + { + return $this->position; + } + + public function current(): Variable + { + return $this->variables[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/src/TextUI/Configuration/Xml/CodeCoverage/CodeCoverage.php b/src/TextUI/Configuration/Xml/CodeCoverage/CodeCoverage.php new file mode 100644 index 00000000000..d66f58f1c47 --- /dev/null +++ b/src/TextUI/Configuration/Xml/CodeCoverage/CodeCoverage.php @@ -0,0 +1,255 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage; + +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Clover; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Cobertura; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Crap4j; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Html; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\OpenClover; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Php; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Text; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Xml; +use PHPUnit\TextUI\XmlConfiguration\Exception; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class CodeCoverage +{ + private bool $pathCoverage; + private bool $includeUncoveredFiles; + private bool $ignoreDeprecatedCodeUnits; + private bool $disableCodeCoverageIgnore; + private ?Clover $clover; + private ?Cobertura $cobertura; + private ?Crap4j $crap4j; + private ?Html $html; + private ?OpenClover $openClover; + private ?Php $php; + private ?Text $text; + private ?Xml $xml; + + public function __construct(bool $pathCoverage, bool $includeUncoveredFiles, bool $ignoreDeprecatedCodeUnits, bool $disableCodeCoverageIgnore, ?Clover $clover, ?Cobertura $cobertura, ?Crap4j $crap4j, ?Html $html, ?OpenClover $openClover, ?Php $php, ?Text $text, ?Xml $xml) + { + $this->pathCoverage = $pathCoverage; + $this->includeUncoveredFiles = $includeUncoveredFiles; + $this->ignoreDeprecatedCodeUnits = $ignoreDeprecatedCodeUnits; + $this->disableCodeCoverageIgnore = $disableCodeCoverageIgnore; + $this->clover = $clover; + $this->cobertura = $cobertura; + $this->crap4j = $crap4j; + $this->html = $html; + $this->openClover = $openClover; + $this->php = $php; + $this->text = $text; + $this->xml = $xml; + } + + public function pathCoverage(): bool + { + return $this->pathCoverage; + } + + public function includeUncoveredFiles(): bool + { + return $this->includeUncoveredFiles; + } + + public function ignoreDeprecatedCodeUnits(): bool + { + return $this->ignoreDeprecatedCodeUnits; + } + + public function disableCodeCoverageIgnore(): bool + { + return $this->disableCodeCoverageIgnore; + } + + /** + * @phpstan-assert-if-true !null $this->clover + */ + public function hasClover(): bool + { + return $this->clover !== null; + } + + /** + * @throws Exception + */ + public function clover(): Clover + { + if (!$this->hasClover()) { + throw new Exception( + 'Code Coverage report "Clover XML" has not been configured', + ); + } + + return $this->clover; + } + + /** + * @phpstan-assert-if-true !null $this->cobertura + */ + public function hasCobertura(): bool + { + return $this->cobertura !== null; + } + + /** + * @throws Exception + */ + public function cobertura(): Cobertura + { + if (!$this->hasCobertura()) { + throw new Exception( + 'Code Coverage report "Cobertura XML" has not been configured', + ); + } + + return $this->cobertura; + } + + /** + * @phpstan-assert-if-true !null $this->crap4j + */ + public function hasCrap4j(): bool + { + return $this->crap4j !== null; + } + + /** + * @throws Exception + */ + public function crap4j(): Crap4j + { + if (!$this->hasCrap4j()) { + throw new Exception( + 'Code Coverage report "Crap4J" has not been configured', + ); + } + + return $this->crap4j; + } + + /** + * @phpstan-assert-if-true !null $this->html + */ + public function hasHtml(): bool + { + return $this->html !== null; + } + + /** + * @throws Exception + */ + public function html(): Html + { + if (!$this->hasHtml()) { + throw new Exception( + 'Code Coverage report "HTML" has not been configured', + ); + } + + return $this->html; + } + + /** + * @phpstan-assert-if-true !null $this->openClover + */ + public function hasOpenClover(): bool + { + return $this->openClover !== null; + } + + /** + * @throws Exception + */ + public function openClover(): OpenClover + { + if (!$this->hasOpenClover()) { + throw new Exception( + 'Code Coverage report "OpenClover XML" has not been configured', + ); + } + + return $this->openClover; + } + + /** + * @phpstan-assert-if-true !null $this->php + */ + public function hasPhp(): bool + { + return $this->php !== null; + } + + /** + * @throws Exception + */ + public function php(): Php + { + if (!$this->hasPhp()) { + throw new Exception( + 'Code Coverage report "PHP" has not been configured', + ); + } + + return $this->php; + } + + /** + * @phpstan-assert-if-true !null $this->text + */ + public function hasText(): bool + { + return $this->text !== null; + } + + /** + * @throws Exception + */ + public function text(): Text + { + if (!$this->hasText()) { + throw new Exception( + 'Code Coverage report "Text" has not been configured', + ); + } + + return $this->text; + } + + /** + * @phpstan-assert-if-true !null $this->xml + */ + public function hasXml(): bool + { + return $this->xml !== null; + } + + /** + * @throws Exception + */ + public function xml(): Xml + { + if (!$this->hasXml()) { + throw new Exception( + 'Code Coverage report "XML" has not been configured', + ); + } + + return $this->xml; + } +} diff --git a/src/TextUI/Configuration/Xml/CodeCoverage/Report/Clover.php b/src/TextUI/Configuration/Xml/CodeCoverage/Report/Clover.php new file mode 100644 index 00000000000..cdaf122e9c8 --- /dev/null +++ b/src/TextUI/Configuration/Xml/CodeCoverage/Report/Clover.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\Configuration\File; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Clover +{ + private File $target; + + public function __construct(File $target) + { + $this->target = $target; + } + + public function target(): File + { + return $this->target; + } +} diff --git a/src/TextUI/Configuration/Xml/CodeCoverage/Report/Cobertura.php b/src/TextUI/Configuration/Xml/CodeCoverage/Report/Cobertura.php new file mode 100644 index 00000000000..015dba39407 --- /dev/null +++ b/src/TextUI/Configuration/Xml/CodeCoverage/Report/Cobertura.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\Configuration\File; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Cobertura +{ + private File $target; + + public function __construct(File $target) + { + $this->target = $target; + } + + public function target(): File + { + return $this->target; + } +} diff --git a/src/TextUI/Configuration/Xml/CodeCoverage/Report/Crap4j.php b/src/TextUI/Configuration/Xml/CodeCoverage/Report/Crap4j.php new file mode 100644 index 00000000000..24aa66ddf98 --- /dev/null +++ b/src/TextUI/Configuration/Xml/CodeCoverage/Report/Crap4j.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\Configuration\File; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Crap4j +{ + private File $target; + private int $threshold; + + public function __construct(File $target, int $threshold) + { + $this->target = $target; + $this->threshold = $threshold; + } + + public function target(): File + { + return $this->target; + } + + public function threshold(): int + { + return $this->threshold; + } +} diff --git a/src/TextUI/Configuration/Xml/CodeCoverage/Report/Html.php b/src/TextUI/Configuration/Xml/CodeCoverage/Report/Html.php new file mode 100644 index 00000000000..dde8880f129 --- /dev/null +++ b/src/TextUI/Configuration/Xml/CodeCoverage/Report/Html.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\Configuration\Directory; +use PHPUnit\TextUI\Configuration\NoCustomCssFileException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Html +{ + private Directory $target; + private int $lowUpperBound; + private int $highLowerBound; + private string $colorSuccessLow; + private string $colorSuccessMedium; + private string $colorSuccessHigh; + private string $colorWarning; + private string $colorDanger; + private ?string $customCssFile; + + public function __construct(Directory $target, int $lowUpperBound, int $highLowerBound, string $colorSuccessLow, string $colorSuccessMedium, string $colorSuccessHigh, string $colorWarning, string $colorDanger, ?string $customCssFile) + { + $this->target = $target; + $this->lowUpperBound = $lowUpperBound; + $this->highLowerBound = $highLowerBound; + $this->colorSuccessLow = $colorSuccessLow; + $this->colorSuccessMedium = $colorSuccessMedium; + $this->colorSuccessHigh = $colorSuccessHigh; + $this->colorWarning = $colorWarning; + $this->colorDanger = $colorDanger; + $this->customCssFile = $customCssFile; + } + + public function target(): Directory + { + return $this->target; + } + + public function lowUpperBound(): int + { + return $this->lowUpperBound; + } + + public function highLowerBound(): int + { + return $this->highLowerBound; + } + + public function colorSuccessLow(): string + { + return $this->colorSuccessLow; + } + + public function colorSuccessMedium(): string + { + return $this->colorSuccessMedium; + } + + public function colorSuccessHigh(): string + { + return $this->colorSuccessHigh; + } + + public function colorWarning(): string + { + return $this->colorWarning; + } + + public function colorDanger(): string + { + return $this->colorDanger; + } + + /** + * @phpstan-assert-if-true !null $this->customCssFile + */ + public function hasCustomCssFile(): bool + { + return $this->customCssFile !== null; + } + + /** + * @throws NoCustomCssFileException + */ + public function customCssFile(): string + { + if (!$this->hasCustomCssFile()) { + throw new NoCustomCssFileException; + } + + return $this->customCssFile; + } +} diff --git a/src/TextUI/Configuration/Xml/CodeCoverage/Report/OpenClover.php b/src/TextUI/Configuration/Xml/CodeCoverage/Report/OpenClover.php new file mode 100644 index 00000000000..e20a24d08cb --- /dev/null +++ b/src/TextUI/Configuration/Xml/CodeCoverage/Report/OpenClover.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\Configuration\File; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class OpenClover +{ + private File $target; + + public function __construct(File $target) + { + $this->target = $target; + } + + public function target(): File + { + return $this->target; + } +} diff --git a/src/TextUI/Configuration/Xml/CodeCoverage/Report/Php.php b/src/TextUI/Configuration/Xml/CodeCoverage/Report/Php.php new file mode 100644 index 00000000000..ae022e7a018 --- /dev/null +++ b/src/TextUI/Configuration/Xml/CodeCoverage/Report/Php.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\Configuration\File; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Php +{ + private File $target; + + public function __construct(File $target) + { + $this->target = $target; + } + + public function target(): File + { + return $this->target; + } +} diff --git a/src/TextUI/Configuration/Xml/CodeCoverage/Report/Text.php b/src/TextUI/Configuration/Xml/CodeCoverage/Report/Text.php new file mode 100644 index 00000000000..cf04d9101d5 --- /dev/null +++ b/src/TextUI/Configuration/Xml/CodeCoverage/Report/Text.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\Configuration\File; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Text +{ + private File $target; + private bool $showUncoveredFiles; + private bool $showOnlySummary; + + public function __construct(File $target, bool $showUncoveredFiles, bool $showOnlySummary) + { + $this->target = $target; + $this->showUncoveredFiles = $showUncoveredFiles; + $this->showOnlySummary = $showOnlySummary; + } + + public function target(): File + { + return $this->target; + } + + public function showUncoveredFiles(): bool + { + return $this->showUncoveredFiles; + } + + public function showOnlySummary(): bool + { + return $this->showOnlySummary; + } +} diff --git a/src/TextUI/Configuration/Xml/CodeCoverage/Report/Xml.php b/src/TextUI/Configuration/Xml/CodeCoverage/Report/Xml.php new file mode 100644 index 00000000000..62f99a061a7 --- /dev/null +++ b/src/TextUI/Configuration/Xml/CodeCoverage/Report/Xml.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\Configuration\Directory; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Xml +{ + private Directory $target; + + public function __construct(Directory $target) + { + $this->target = $target; + } + + public function target(): Directory + { + return $this->target; + } +} diff --git a/src/TextUI/Configuration/Xml/Configuration.php b/src/TextUI/Configuration/Xml/Configuration.php new file mode 100644 index 00000000000..378022b0be1 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Configuration.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use PHPUnit\TextUI\Configuration\ExtensionBootstrapCollection; +use PHPUnit\TextUI\Configuration\Php; +use PHPUnit\TextUI\Configuration\Source; +use PHPUnit\TextUI\Configuration\TestSuiteCollection; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\CodeCoverage; +use PHPUnit\TextUI\XmlConfiguration\Logging\Logging; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +abstract readonly class Configuration +{ + private ExtensionBootstrapCollection $extensions; + private Source $source; + private CodeCoverage $codeCoverage; + private Groups $groups; + private Logging $logging; + private Php $php; + private PHPUnit $phpunit; + private TestSuiteCollection $testSuite; + + public function __construct(ExtensionBootstrapCollection $extensions, Source $source, CodeCoverage $codeCoverage, Groups $groups, Logging $logging, Php $php, PHPUnit $phpunit, TestSuiteCollection $testSuite) + { + $this->extensions = $extensions; + $this->source = $source; + $this->codeCoverage = $codeCoverage; + $this->groups = $groups; + $this->logging = $logging; + $this->php = $php; + $this->phpunit = $phpunit; + $this->testSuite = $testSuite; + } + + public function extensions(): ExtensionBootstrapCollection + { + return $this->extensions; + } + + public function source(): Source + { + return $this->source; + } + + public function codeCoverage(): CodeCoverage + { + return $this->codeCoverage; + } + + public function groups(): Groups + { + return $this->groups; + } + + public function logging(): Logging + { + return $this->logging; + } + + public function php(): Php + { + return $this->php; + } + + public function phpunit(): PHPUnit + { + return $this->phpunit; + } + + public function testSuite(): TestSuiteCollection + { + return $this->testSuite; + } + + /** + * @phpstan-assert-if-true DefaultConfiguration $this + */ + public function isDefault(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true LoadedFromFileConfiguration $this + */ + public function wasLoadedFromFile(): bool + { + return false; + } +} diff --git a/src/TextUI/Configuration/Xml/DefaultConfiguration.php b/src/TextUI/Configuration/Xml/DefaultConfiguration.php new file mode 100644 index 00000000000..de16474a8f5 --- /dev/null +++ b/src/TextUI/Configuration/Xml/DefaultConfiguration.php @@ -0,0 +1,172 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use PHPUnit\Runner\TestSuiteSorter; +use PHPUnit\TextUI\Configuration\ConstantCollection; +use PHPUnit\TextUI\Configuration\DirectoryCollection; +use PHPUnit\TextUI\Configuration\ExtensionBootstrapCollection; +use PHPUnit\TextUI\Configuration\FileCollection; +use PHPUnit\TextUI\Configuration\FilterDirectoryCollection as CodeCoverageFilterDirectoryCollection; +use PHPUnit\TextUI\Configuration\GroupCollection; +use PHPUnit\TextUI\Configuration\IniSettingCollection; +use PHPUnit\TextUI\Configuration\Php; +use PHPUnit\TextUI\Configuration\Source; +use PHPUnit\TextUI\Configuration\TestSuiteCollection; +use PHPUnit\TextUI\Configuration\VariableCollection; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\CodeCoverage; +use PHPUnit\TextUI\XmlConfiguration\Logging\Logging; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class DefaultConfiguration extends Configuration +{ + public static function create(): self + { + return new self( + ExtensionBootstrapCollection::fromArray([]), + new Source( + null, + false, + CodeCoverageFilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + CodeCoverageFilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + false, + false, + false, + false, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ), + new CodeCoverage( + false, + true, + false, + false, + null, + null, + null, + null, + null, + null, + null, + null, + ), + new Groups( + GroupCollection::fromArray([]), + GroupCollection::fromArray([]), + ), + new Logging( + null, + null, + null, + null, + null, + ), + new Php( + DirectoryCollection::fromArray([]), + IniSettingCollection::fromArray([]), + ConstantCollection::fromArray([]), + VariableCollection::fromArray([]), + VariableCollection::fromArray([]), + VariableCollection::fromArray([]), + VariableCollection::fromArray([]), + VariableCollection::fromArray([]), + VariableCollection::fromArray([]), + VariableCollection::fromArray([]), + VariableCollection::fromArray([]), + ), + new PHPUnit( + null, + true, + 80, + \PHPUnit\TextUI\Configuration\Configuration::COLOR_DEFAULT, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + null, + [], + false, + false, + false, + false, + false, + true, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + null, + false, + false, + true, + false, + false, + 1, + 1, + 10, + 60, + null, + TestSuiteSorter::ORDER_DEFAULT, + true, + false, + false, + false, + false, + false, + false, + 100, + 10, + ), + TestSuiteCollection::fromArray([]), + ); + } + + public function isDefault(): bool + { + return true; + } +} diff --git a/src/TextUI/Configuration/Xml/Exception.php b/src/TextUI/Configuration/Xml/Exception.php new file mode 100644 index 00000000000..60c3c9acc71 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Exception.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Exception extends RuntimeException implements \PHPUnit\Exception +{ +} diff --git a/src/TextUI/Configuration/Xml/Generator.php b/src/TextUI/Configuration/Xml/Generator.php new file mode 100644 index 00000000000..4fc4ca2e69b --- /dev/null +++ b/src/TextUI/Configuration/Xml/Generator.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function str_replace; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Generator +{ + /** + * @var string + */ + private const string TEMPLATE = <<<'EOT' + + + + + {tests_directory} + + + + + + {src_directory} + + + + +EOT; + + public function generateDefaultConfiguration(string $schemaLocation, string $bootstrapScript, string $testsDirectory, string $srcDirectory, string $cacheDirectory): string + { + return str_replace( + [ + '{schema_location}', + '{bootstrap_script}', + '{tests_directory}', + '{src_directory}', + '{cache_directory}', + ], + [ + $schemaLocation, + $bootstrapScript, + $testsDirectory, + $srcDirectory, + $cacheDirectory, + ], + self::TEMPLATE, + ); + } +} diff --git a/src/TextUI/Configuration/Xml/Groups.php b/src/TextUI/Configuration/Xml/Groups.php new file mode 100644 index 00000000000..1a7cc6b453f --- /dev/null +++ b/src/TextUI/Configuration/Xml/Groups.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use PHPUnit\TextUI\Configuration\GroupCollection; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Groups +{ + private GroupCollection $include; + private GroupCollection $exclude; + + public function __construct(GroupCollection $include, GroupCollection $exclude) + { + $this->include = $include; + $this->exclude = $exclude; + } + + public function hasInclude(): bool + { + return !$this->include->isEmpty(); + } + + public function include(): GroupCollection + { + return $this->include; + } + + public function hasExclude(): bool + { + return !$this->exclude->isEmpty(); + } + + public function exclude(): GroupCollection + { + return $this->exclude; + } +} diff --git a/src/TextUI/Configuration/Xml/LoadedFromFileConfiguration.php b/src/TextUI/Configuration/Xml/LoadedFromFileConfiguration.php new file mode 100644 index 00000000000..e69d137fe2c --- /dev/null +++ b/src/TextUI/Configuration/Xml/LoadedFromFileConfiguration.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use PHPUnit\TextUI\Configuration\ExtensionBootstrapCollection; +use PHPUnit\TextUI\Configuration\Php; +use PHPUnit\TextUI\Configuration\Source; +use PHPUnit\TextUI\Configuration\TestSuiteCollection; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\CodeCoverage; +use PHPUnit\TextUI\XmlConfiguration\Logging\Logging; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class LoadedFromFileConfiguration extends Configuration +{ + /** + * @var non-empty-string + */ + private string $filename; + private ValidationResult $validationResult; + + /** + * @param non-empty-string $filename + */ + public function __construct(string $filename, ValidationResult $validationResult, ExtensionBootstrapCollection $extensions, Source $source, CodeCoverage $codeCoverage, Groups $groups, Logging $logging, Php $php, PHPUnit $phpunit, TestSuiteCollection $testSuite) + { + $this->filename = $filename; + $this->validationResult = $validationResult; + + parent::__construct( + $extensions, + $source, + $codeCoverage, + $groups, + $logging, + $php, + $phpunit, + $testSuite, + ); + } + + /** + * @return non-empty-string + */ + public function filename(): string + { + return $this->filename; + } + + public function hasValidationErrors(): bool + { + return $this->validationResult->hasValidationErrors(); + } + + public function validationErrors(): string + { + return $this->validationResult->asString(); + } + + public function wasLoadedFromFile(): bool + { + return true; + } +} diff --git a/src/TextUI/Configuration/Xml/Loader.php b/src/TextUI/Configuration/Xml/Loader.php new file mode 100644 index 00000000000..886959fd5e5 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Loader.php @@ -0,0 +1,1225 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use const DIRECTORY_SEPARATOR; +use const PHP_EOL; +use const PHP_VERSION; +use function assert; +use function defined; +use function dirname; +use function explode; +use function is_numeric; +use function preg_match; +use function realpath; +use function sprintf; +use function str_contains; +use function str_starts_with; +use function strlen; +use function strtolower; +use function substr; +use function trim; +use DOMDocument; +use DOMElement; +use DOMNode; +use DOMNodeList; +use DOMXPath; +use PHPUnit\Runner\TestSuiteSorter; +use PHPUnit\Runner\Version; +use PHPUnit\TextUI\Configuration\Configuration; +use PHPUnit\TextUI\Configuration\Constant; +use PHPUnit\TextUI\Configuration\ConstantCollection; +use PHPUnit\TextUI\Configuration\Directory; +use PHPUnit\TextUI\Configuration\DirectoryCollection; +use PHPUnit\TextUI\Configuration\ExtensionBootstrap; +use PHPUnit\TextUI\Configuration\ExtensionBootstrapCollection; +use PHPUnit\TextUI\Configuration\File; +use PHPUnit\TextUI\Configuration\FileCollection; +use PHPUnit\TextUI\Configuration\FilterDirectory; +use PHPUnit\TextUI\Configuration\FilterDirectoryCollection; +use PHPUnit\TextUI\Configuration\Group; +use PHPUnit\TextUI\Configuration\GroupCollection; +use PHPUnit\TextUI\Configuration\IniSetting; +use PHPUnit\TextUI\Configuration\IniSettingCollection; +use PHPUnit\TextUI\Configuration\Php; +use PHPUnit\TextUI\Configuration\Source; +use PHPUnit\TextUI\Configuration\TestDirectory; +use PHPUnit\TextUI\Configuration\TestDirectoryCollection; +use PHPUnit\TextUI\Configuration\TestFile; +use PHPUnit\TextUI\Configuration\TestFileCollection; +use PHPUnit\TextUI\Configuration\TestSuite as TestSuiteConfiguration; +use PHPUnit\TextUI\Configuration\TestSuiteCollection; +use PHPUnit\TextUI\Configuration\Variable; +use PHPUnit\TextUI\Configuration\VariableCollection; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\CodeCoverage; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Clover; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Cobertura; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Crap4j; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Html as CodeCoverageHtml; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\OpenClover; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Php as CodeCoveragePhp; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Text as CodeCoverageText; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Xml as CodeCoverageXml; +use PHPUnit\TextUI\XmlConfiguration\Logging\Junit; +use PHPUnit\TextUI\XmlConfiguration\Logging\Logging; +use PHPUnit\TextUI\XmlConfiguration\Logging\Otr; +use PHPUnit\TextUI\XmlConfiguration\Logging\TeamCity; +use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Html as TestDoxHtml; +use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Text as TestDoxText; +use PHPUnit\Util\VersionComparisonOperator; +use PHPUnit\Util\Xml\Loader as XmlLoader; +use PHPUnit\Util\Xml\XmlException; +use SebastianBergmann\CodeCoverage\Report\Html\Colors; +use SebastianBergmann\CodeCoverage\Report\Thresholds; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Loader +{ + /** + * @throws Exception + */ + public function load(string $filename): LoadedFromFileConfiguration + { + try { + $document = (new XmlLoader)->loadFile($filename); + } catch (XmlException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + + $xpath = new DOMXPath($document); + + try { + $xsdFilename = (new SchemaFinder)->find(Version::series()); + } catch (CannotFindSchemaException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + + $configurationFileRealpath = realpath($filename); + + assert($configurationFileRealpath !== false && $configurationFileRealpath !== ''); + + $validationResult = (new Validator)->validate($document, $xsdFilename); + + try { + return new LoadedFromFileConfiguration( + $configurationFileRealpath, + $validationResult, + $this->extensions($xpath), + $this->source($configurationFileRealpath, $xpath), + $this->codeCoverage($configurationFileRealpath, $xpath), + $this->groups($xpath), + $this->logging($configurationFileRealpath, $xpath), + $this->php($configurationFileRealpath, $xpath), + $this->phpunit($configurationFileRealpath, $document, $xpath), + $this->testSuite($configurationFileRealpath, $xpath), + ); + } catch (Throwable $t) { + $message = sprintf( + 'Cannot load XML configuration file %s', + $configurationFileRealpath, + ); + + if ($validationResult->hasValidationErrors()) { + $message .= ' because it has validation errors:' . PHP_EOL . $validationResult->asString(); + } + + throw new Exception($message, previous: $t); + } + } + + private function logging(string $filename, DOMXPath $xpath): Logging + { + $junit = null; + $element = $this->element($xpath, 'logging/junit'); + + if ($element !== null) { + $junit = new Junit( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputFile'), + ), + ), + ); + } + + $otr = null; + $element = $this->element($xpath, 'logging/otr'); + + if ($element !== null) { + $otr = new Otr( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputFile'), + ), + ), + $this->parseBooleanAttribute($element, 'includeGitInformation', false), + ); + } + + $teamCity = null; + $element = $this->element($xpath, 'logging/teamcity'); + + if ($element !== null) { + $teamCity = new TeamCity( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputFile'), + ), + ), + ); + } + + $testDoxHtml = null; + $element = $this->element($xpath, 'logging/testdoxHtml'); + + if ($element !== null) { + $testDoxHtml = new TestDoxHtml( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputFile'), + ), + ), + ); + } + + $testDoxText = null; + $element = $this->element($xpath, 'logging/testdoxText'); + + if ($element !== null) { + $testDoxText = new TestDoxText( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputFile'), + ), + ), + ); + } + + return new Logging( + $junit, + $otr, + $teamCity, + $testDoxHtml, + $testDoxText, + ); + } + + private function extensions(DOMXPath $xpath): ExtensionBootstrapCollection + { + $extensionBootstrappers = []; + + $bootstrapNodes = $xpath->query('extensions/bootstrap'); + + assert($bootstrapNodes instanceof DOMNodeList); + + foreach ($bootstrapNodes as $bootstrap) { + assert($bootstrap instanceof DOMElement); + + $parameters = []; + + $parameterNodes = $xpath->query('parameter', $bootstrap); + + assert($parameterNodes instanceof DOMNodeList); + + foreach ($parameterNodes as $parameter) { + assert($parameter instanceof DOMElement); + + $parameters[$parameter->getAttribute('name')] = $parameter->getAttribute('value'); + } + + $className = $bootstrap->getAttribute('class'); + + assert($className !== ''); + + $extensionBootstrappers[] = new ExtensionBootstrap( + $className, + $parameters, + ); + } + + return ExtensionBootstrapCollection::fromArray($extensionBootstrappers); + } + + /** + * @return non-empty-string + */ + private function toAbsolutePath(string $filename, string $path): string + { + $path = trim($path); + + if (str_starts_with($path, '/')) { + return $path; + } + + // Matches the following on Windows: + // - \\NetworkComputer\Path + // - \\.\D: + // - \\.\c: + // - C:\Windows + // - C:\windows + // - C:/windows + // - c:/windows + if (defined('PHP_WINDOWS_VERSION_BUILD') && + $path !== '' && + ($path[0] === '\\' || (strlen($path) >= 3 && preg_match('#^[A-Z]:[/\\\]#i', substr($path, 0, 3))))) { + return $path; + } + + if (str_contains($path, '://')) { + return $path; + } + + return dirname($filename) . DIRECTORY_SEPARATOR . $path; + } + + private function source(string $filename, DOMXPath $xpath): Source + { + $baseline = null; + $restrictNotices = false; + $restrictWarnings = false; + $ignoreSuppressionOfDeprecations = false; + $ignoreSuppressionOfPhpDeprecations = false; + $ignoreSuppressionOfErrors = false; + $ignoreSuppressionOfNotices = false; + $ignoreSuppressionOfPhpNotices = false; + $ignoreSuppressionOfWarnings = false; + $ignoreSuppressionOfPhpWarnings = false; + $ignoreSelfDeprecations = false; + $ignoreDirectDeprecations = false; + $ignoreIndirectDeprecations = false; + + $element = $this->element($xpath, 'source'); + + if ($element !== null) { + $baseline = $this->parseStringAttribute($element, 'baseline'); + + if ($baseline !== null) { + $baseline = $this->toAbsolutePath($filename, $baseline); + } + + $restrictNotices = $this->parseBooleanAttribute($element, 'restrictNotices', false); + $restrictWarnings = $this->parseBooleanAttribute($element, 'restrictWarnings', false); + $ignoreSuppressionOfDeprecations = $this->parseBooleanAttribute($element, 'ignoreSuppressionOfDeprecations', false); + $ignoreSuppressionOfPhpDeprecations = $this->parseBooleanAttribute($element, 'ignoreSuppressionOfPhpDeprecations', false); + $ignoreSuppressionOfErrors = $this->parseBooleanAttribute($element, 'ignoreSuppressionOfErrors', false); + $ignoreSuppressionOfNotices = $this->parseBooleanAttribute($element, 'ignoreSuppressionOfNotices', false); + $ignoreSuppressionOfPhpNotices = $this->parseBooleanAttribute($element, 'ignoreSuppressionOfPhpNotices', false); + $ignoreSuppressionOfWarnings = $this->parseBooleanAttribute($element, 'ignoreSuppressionOfWarnings', false); + $ignoreSuppressionOfPhpWarnings = $this->parseBooleanAttribute($element, 'ignoreSuppressionOfPhpWarnings', false); + $ignoreSelfDeprecations = $this->parseBooleanAttribute($element, 'ignoreSelfDeprecations', false); + $ignoreDirectDeprecations = $this->parseBooleanAttribute($element, 'ignoreDirectDeprecations', false); + $ignoreIndirectDeprecations = $this->parseBooleanAttribute($element, 'ignoreIndirectDeprecations', false); + } + + $deprecationTriggers = [ + 'functions' => [], + 'methods' => [], + ]; + + $functionNodes = $xpath->query('source/deprecationTrigger/function'); + + assert($functionNodes instanceof DOMNodeList); + + foreach ($functionNodes as $functionNode) { + assert($functionNode instanceof DOMElement); + + $deprecationTriggers['functions'][] = $functionNode->textContent; + } + + $methodNodes = $xpath->query('source/deprecationTrigger/method'); + + assert($methodNodes instanceof DOMNodeList); + + foreach ($methodNodes as $methodNode) { + assert($methodNode instanceof DOMElement); + + $deprecationTriggers['methods'][] = $methodNode->textContent; + } + + return new Source( + $baseline, + false, + $this->readFilterDirectories($filename, $xpath, 'source/include/directory'), + $this->readFilterFiles($filename, $xpath, 'source/include/file'), + $this->readFilterDirectories($filename, $xpath, 'source/exclude/directory'), + $this->readFilterFiles($filename, $xpath, 'source/exclude/file'), + $restrictNotices, + $restrictWarnings, + $ignoreSuppressionOfDeprecations, + $ignoreSuppressionOfPhpDeprecations, + $ignoreSuppressionOfErrors, + $ignoreSuppressionOfNotices, + $ignoreSuppressionOfPhpNotices, + $ignoreSuppressionOfWarnings, + $ignoreSuppressionOfPhpWarnings, + $deprecationTriggers, + $ignoreSelfDeprecations, + $ignoreDirectDeprecations, + $ignoreIndirectDeprecations, + ); + } + + private function codeCoverage(string $filename, DOMXPath $xpath): CodeCoverage + { + $pathCoverage = false; + $includeUncoveredFiles = true; + $ignoreDeprecatedCodeUnits = false; + $disableCodeCoverageIgnore = false; + + $element = $this->element($xpath, 'coverage'); + + if ($element !== null) { + $pathCoverage = $this->parseBooleanAttribute( + $element, + 'pathCoverage', + false, + ); + + $includeUncoveredFiles = $this->parseBooleanAttribute( + $element, + 'includeUncoveredFiles', + true, + ); + + $ignoreDeprecatedCodeUnits = $this->parseBooleanAttribute( + $element, + 'ignoreDeprecatedCodeUnits', + false, + ); + + $disableCodeCoverageIgnore = $this->parseBooleanAttribute( + $element, + 'disableCodeCoverageIgnore', + false, + ); + } + + $clover = null; + $element = $this->element($xpath, 'coverage/report/clover'); + + if ($element !== null) { + $clover = new Clover( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputFile'), + ), + ), + ); + } + + $cobertura = null; + $element = $this->element($xpath, 'coverage/report/cobertura'); + + if ($element !== null) { + $cobertura = new Cobertura( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputFile'), + ), + ), + ); + } + + $crap4j = null; + $element = $this->element($xpath, 'coverage/report/crap4j'); + + if ($element !== null) { + $crap4j = new Crap4j( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputFile'), + ), + ), + $this->parseIntegerAttribute($element, 'threshold', 30), + ); + } + + $html = null; + $element = $this->element($xpath, 'coverage/report/html'); + + if ($element !== null) { + $defaultColors = Colors::default(); + $defaultThresholds = Thresholds::default(); + + $html = new CodeCoverageHtml( + new Directory( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputDirectory'), + ), + ), + $this->parseIntegerAttribute($element, 'lowUpperBound', $defaultThresholds->lowUpperBound()), + $this->parseIntegerAttribute($element, 'highLowerBound', $defaultThresholds->highLowerBound()), + $this->parseStringAttributeWithDefault($element, 'colorSuccessLow', $defaultColors->successLow()), + $this->parseStringAttributeWithDefault($element, 'colorSuccessMedium', $defaultColors->successMedium()), + $this->parseStringAttributeWithDefault($element, 'colorSuccessHigh', $defaultColors->successHigh()), + $this->parseStringAttributeWithDefault($element, 'colorWarning', $defaultColors->warning()), + $this->parseStringAttributeWithDefault($element, 'colorDanger', $defaultColors->danger()), + $this->parseStringAttribute($element, 'customCssFile'), + ); + } + + $openClover = null; + $element = $this->element($xpath, 'coverage/report/openclover'); + + if ($element !== null) { + $openClover = new OpenClover( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputFile'), + ), + ), + ); + } + + $php = null; + $element = $this->element($xpath, 'coverage/report/php'); + + if ($element !== null) { + $php = new CodeCoveragePhp( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputFile'), + ), + ), + ); + } + + $text = null; + $element = $this->element($xpath, 'coverage/report/text'); + + if ($element !== null) { + $text = new CodeCoverageText( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputFile'), + ), + ), + $this->parseBooleanAttribute($element, 'showUncoveredFiles', false), + $this->parseBooleanAttribute($element, 'showOnlySummary', false), + ); + } + + $xml = null; + $element = $this->element($xpath, 'coverage/report/xml'); + + if ($element !== null) { + $xml = new CodeCoverageXml( + new Directory( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputDirectory'), + ), + ), + ); + } + + return new CodeCoverage( + $pathCoverage, + $includeUncoveredFiles, + $ignoreDeprecatedCodeUnits, + $disableCodeCoverageIgnore, + $clover, + $cobertura, + $crap4j, + $html, + $openClover, + $php, + $text, + $xml, + ); + } + + private function booleanFromString(string $value, bool $default): bool + { + if (strtolower($value) === 'false') { + return false; + } + + if (strtolower($value) === 'true') { + return true; + } + + return $default; + } + + private function valueFromString(string $value): bool|string + { + if (strtolower($value) === 'false') { + return false; + } + + if (strtolower($value) === 'true') { + return true; + } + + return $value; + } + + private function readFilterDirectories(string $filename, DOMXPath $xpath, string $query): FilterDirectoryCollection + { + $directories = []; + + $directoryNodes = $xpath->query($query); + + assert($directoryNodes instanceof DOMNodeList); + + foreach ($directoryNodes as $directoryNode) { + assert($directoryNode instanceof DOMElement); + + $directoryPath = $directoryNode->textContent; + + if ($directoryPath === '') { + continue; + } + + $directories[] = new FilterDirectory( + $this->toAbsolutePath($filename, $directoryPath), + $directoryNode->hasAttribute('prefix') ? $directoryNode->getAttribute('prefix') : '', + $directoryNode->hasAttribute('suffix') ? $directoryNode->getAttribute('suffix') : '.php', + ); + } + + return FilterDirectoryCollection::fromArray($directories); + } + + private function readFilterFiles(string $filename, DOMXPath $xpath, string $query): FileCollection + { + $files = []; + + $fileNodes = $xpath->query($query); + + assert($fileNodes instanceof DOMNodeList); + + foreach ($fileNodes as $fileNode) { + assert($fileNode instanceof DOMNode); + + $filePath = $fileNode->textContent; + + if ($filePath !== '') { + $files[] = new File($this->toAbsolutePath($filename, $filePath)); + } + } + + return FileCollection::fromArray($files); + } + + private function groups(DOMXPath $xpath): Groups + { + $include = []; + $exclude = []; + + $groupNodes = $xpath->query('groups/include/group'); + + assert($groupNodes instanceof DOMNodeList); + + foreach ($groupNodes as $groupNode) { + assert($groupNode instanceof DOMNode); + + $include[] = new Group($groupNode->textContent); + } + + $groupNodes = $xpath->query('groups/exclude/group'); + + assert($groupNodes instanceof DOMNodeList); + + foreach ($groupNodes as $groupNode) { + assert($groupNode instanceof DOMNode); + + $exclude[] = new Group($groupNode->textContent); + } + + return new Groups( + GroupCollection::fromArray($include), + GroupCollection::fromArray($exclude), + ); + } + + private function parseBooleanAttribute(DOMElement $element, string $attribute, bool $default): bool + { + if (!$element->hasAttribute($attribute)) { + return $default; + } + + return $this->booleanFromString( + $element->getAttribute($attribute), + false, + ); + } + + private function parseIntegerAttribute(DOMElement $element, string $attribute, int $default): int + { + if (!$element->hasAttribute($attribute)) { + return $default; + } + + return $this->parseInteger( + $element->getAttribute($attribute), + $default, + ); + } + + private function parseStringAttribute(DOMElement $element, string $attribute): ?string + { + if (!$element->hasAttribute($attribute)) { + return null; + } + + return $element->getAttribute($attribute); + } + + private function parseStringAttributeWithDefault(DOMElement $element, string $attribute, string $default): string + { + if (!$element->hasAttribute($attribute)) { + return $default; + } + + return $element->getAttribute($attribute); + } + + private function parseInteger(string $value, int $default): int + { + if (is_numeric($value)) { + return (int) $value; + } + + return $default; + } + + private function php(string $filename, DOMXPath $xpath): Php + { + $includePaths = []; + + $includePathNodes = $xpath->query('php/includePath'); + + assert($includePathNodes instanceof DOMNodeList); + + foreach ($includePathNodes as $includePath) { + assert($includePath instanceof DOMNode); + + $path = $includePath->textContent; + + if ($path !== '') { + $includePaths[] = new Directory($this->toAbsolutePath($filename, $path)); + } + } + + $iniSettings = []; + + $iniNodes = $xpath->query('php/ini'); + + assert($iniNodes instanceof DOMNodeList); + + foreach ($iniNodes as $ini) { + assert($ini instanceof DOMElement); + + $iniSettings[] = new IniSetting( + $ini->getAttribute('name'), + $ini->getAttribute('value'), + ); + } + + $constants = []; + + $constNodes = $xpath->query('php/const'); + + assert($constNodes instanceof DOMNodeList); + + foreach ($constNodes as $constNode) { + assert($constNode instanceof DOMElement); + + $value = $constNode->getAttribute('value'); + + $constants[] = new Constant( + $constNode->getAttribute('name'), + $this->valueFromString($value), + ); + } + + $variables = [ + 'var' => [], + 'env' => [], + 'post' => [], + 'get' => [], + 'cookie' => [], + 'server' => [], + 'files' => [], + 'request' => [], + ]; + + foreach (['var', 'env', 'post', 'get', 'cookie', 'server', 'files', 'request'] as $array) { + $varNodes = $xpath->query('php/' . $array); + + assert($varNodes instanceof DOMNodeList); + + foreach ($varNodes as $var) { + assert($var instanceof DOMElement); + + $name = $var->getAttribute('name'); + $value = $var->getAttribute('value'); + $force = false; + $verbatim = false; + + if ($var->hasAttribute('force')) { + $force = $this->booleanFromString($var->getAttribute('force'), false); + } + + if ($var->hasAttribute('verbatim')) { + $verbatim = $this->booleanFromString($var->getAttribute('verbatim'), false); + } + + if (!$verbatim) { + $value = $this->valueFromString($value); + } + + $variables[$array][] = new Variable($name, $value, $force); + } + } + + return new Php( + DirectoryCollection::fromArray($includePaths), + IniSettingCollection::fromArray($iniSettings), + ConstantCollection::fromArray($constants), + VariableCollection::fromArray($variables['var']), + VariableCollection::fromArray($variables['env']), + VariableCollection::fromArray($variables['post']), + VariableCollection::fromArray($variables['get']), + VariableCollection::fromArray($variables['cookie']), + VariableCollection::fromArray($variables['server']), + VariableCollection::fromArray($variables['files']), + VariableCollection::fromArray($variables['request']), + ); + } + + private function phpunit(string $filename, DOMDocument $document, DOMXPath $xpath): PHPUnit + { + $executionOrder = TestSuiteSorter::ORDER_DEFAULT; + $defectsFirst = false; + $resolveDependencies = $this->parseBooleanAttribute($document->documentElement, 'resolveDependencies', true); + + if ($document->documentElement->hasAttribute('executionOrder')) { + foreach (explode(',', $document->documentElement->getAttribute('executionOrder')) as $order) { + switch ($order) { + case 'default': + $executionOrder = TestSuiteSorter::ORDER_DEFAULT; + $defectsFirst = false; + $resolveDependencies = true; + + break; + + case 'depends': + $resolveDependencies = true; + + break; + + case 'no-depends': + $resolveDependencies = false; + + break; + + case 'defects': + $defectsFirst = true; + + break; + + case 'duration': + $executionOrder = TestSuiteSorter::ORDER_DURATION; + + break; + + case 'random': + $executionOrder = TestSuiteSorter::ORDER_RANDOMIZED; + + break; + + case 'reverse': + $executionOrder = TestSuiteSorter::ORDER_REVERSED; + + break; + + case 'size': + $executionOrder = TestSuiteSorter::ORDER_SIZE; + + break; + } + } + } + + $cacheDirectory = $this->parseStringAttribute($document->documentElement, 'cacheDirectory'); + + if ($cacheDirectory !== null) { + $cacheDirectory = $this->toAbsolutePath($filename, $cacheDirectory); + } + + $bootstrap = $this->parseStringAttribute($document->documentElement, 'bootstrap'); + + if ($bootstrap !== null) { + $bootstrap = $this->toAbsolutePath($filename, $bootstrap); + } + + $extensionsDirectory = $this->parseStringAttribute($document->documentElement, 'extensionsDirectory'); + + if ($extensionsDirectory !== null) { + $extensionsDirectory = $this->toAbsolutePath($filename, $extensionsDirectory); + } + + $backupStaticProperties = false; + + if ($document->documentElement->hasAttribute('backupStaticProperties')) { + $backupStaticProperties = $this->parseBooleanAttribute($document->documentElement, 'backupStaticProperties', false); + } + + $requireCoverageMetadata = false; + + if ($document->documentElement->hasAttribute('requireCoverageMetadata')) { + $requireCoverageMetadata = $this->parseBooleanAttribute($document->documentElement, 'requireCoverageMetadata', false); + } + + $beStrictAboutCoverageMetadata = false; + + if ($document->documentElement->hasAttribute('beStrictAboutCoverageMetadata')) { + $beStrictAboutCoverageMetadata = $this->parseBooleanAttribute($document->documentElement, 'beStrictAboutCoverageMetadata', false); + } + + $shortenArraysForExportThreshold = $this->parseIntegerAttribute($document->documentElement, 'shortenArraysForExportThreshold', 10); + + if ($shortenArraysForExportThreshold < 0) { + $shortenArraysForExportThreshold = 0; + } + + return new PHPUnit( + $cacheDirectory, + $this->parseBooleanAttribute($document->documentElement, 'cacheResult', true), + $this->parseColumns($document), + $this->parseColors($document), + $this->parseBooleanAttribute($document->documentElement, 'stderr', false), + $this->parseBooleanAttribute($document->documentElement, 'displayDetailsOnAllIssues', false), + $this->parseBooleanAttribute($document->documentElement, 'displayDetailsOnIncompleteTests', false), + $this->parseBooleanAttribute($document->documentElement, 'displayDetailsOnSkippedTests', false), + $this->parseBooleanAttribute($document->documentElement, 'displayDetailsOnTestsThatTriggerDeprecations', false), + $this->parseBooleanAttribute($document->documentElement, 'displayDetailsOnPhpunitDeprecations', false), + $this->parseBooleanAttribute($document->documentElement, 'displayDetailsOnPhpunitNotices', false), + $this->parseBooleanAttribute($document->documentElement, 'displayDetailsOnTestsThatTriggerErrors', false), + $this->parseBooleanAttribute($document->documentElement, 'displayDetailsOnTestsThatTriggerNotices', false), + $this->parseBooleanAttribute($document->documentElement, 'displayDetailsOnTestsThatTriggerWarnings', false), + $this->parseBooleanAttribute($document->documentElement, 'reverseDefectList', false), + $requireCoverageMetadata, + $bootstrap, + $this->bootstrapForTestSuite($filename, $xpath), + $this->parseBooleanAttribute($document->documentElement, 'processIsolation', false), + $this->parseBooleanAttribute($document->documentElement, 'failOnAllIssues', false), + $this->parseBooleanAttribute($document->documentElement, 'failOnDeprecation', false), + $this->parseBooleanAttribute($document->documentElement, 'failOnPhpunitDeprecation', false), + $this->parseBooleanAttribute($document->documentElement, 'failOnPhpunitNotice', false), + $this->parseBooleanAttribute($document->documentElement, 'failOnPhpunitWarning', true), + $this->parseBooleanAttribute($document->documentElement, 'failOnEmptyTestSuite', false), + $this->parseBooleanAttribute($document->documentElement, 'failOnIncomplete', false), + $this->parseBooleanAttribute($document->documentElement, 'failOnNotice', false), + $this->parseBooleanAttribute($document->documentElement, 'failOnRisky', false), + $this->parseBooleanAttribute($document->documentElement, 'failOnSkipped', false), + $this->parseBooleanAttribute($document->documentElement, 'failOnWarning', false), + $this->parseBooleanAttribute($document->documentElement, 'stopOnDefect', false), + $this->parseBooleanAttribute($document->documentElement, 'stopOnDeprecation', false), + $this->parseBooleanAttribute($document->documentElement, 'stopOnError', false), + $this->parseBooleanAttribute($document->documentElement, 'stopOnFailure', false), + $this->parseBooleanAttribute($document->documentElement, 'stopOnIncomplete', false), + $this->parseBooleanAttribute($document->documentElement, 'stopOnNotice', false), + $this->parseBooleanAttribute($document->documentElement, 'stopOnRisky', false), + $this->parseBooleanAttribute($document->documentElement, 'stopOnSkipped', false), + $this->parseBooleanAttribute($document->documentElement, 'stopOnWarning', false), + $extensionsDirectory, + $this->parseBooleanAttribute($document->documentElement, 'beStrictAboutChangesToGlobalState', false), + $this->parseBooleanAttribute($document->documentElement, 'beStrictAboutOutputDuringTests', false), + $this->parseBooleanAttribute($document->documentElement, 'beStrictAboutTestsThatDoNotTestAnything', true), + $beStrictAboutCoverageMetadata, + $this->parseBooleanAttribute($document->documentElement, 'enforceTimeLimit', false), + $this->parseIntegerAttribute($document->documentElement, 'defaultTimeLimit', 1), + $this->parseIntegerAttribute($document->documentElement, 'timeoutForSmallTests', 1), + $this->parseIntegerAttribute($document->documentElement, 'timeoutForMediumTests', 10), + $this->parseIntegerAttribute($document->documentElement, 'timeoutForLargeTests', 60), + $this->parseStringAttribute($document->documentElement, 'defaultTestSuite'), + $executionOrder, + $resolveDependencies, + $defectsFirst, + $this->parseBooleanAttribute($document->documentElement, 'backupGlobals', false), + $backupStaticProperties, + $this->parseBooleanAttribute($document->documentElement, 'testdox', false), + $this->parseBooleanAttribute($document->documentElement, 'testdoxSummary', false), + $this->parseBooleanAttribute($document->documentElement, 'controlGarbageCollector', false), + $this->parseIntegerAttribute($document->documentElement, 'numberOfTestsBeforeGarbageCollection', 100), + $shortenArraysForExportThreshold, + ); + } + + private function parseColors(DOMDocument $document): string + { + $colors = Configuration::COLOR_DEFAULT; + + if ($document->documentElement->hasAttribute('colors')) { + /* only allow boolean for compatibility with previous versions + 'always' only allowed from command line */ + if ($this->booleanFromString($document->documentElement->getAttribute('colors'), false)) { + $colors = Configuration::COLOR_AUTO; + } else { + $colors = Configuration::COLOR_NEVER; + } + } + + return $colors; + } + + private function parseColumns(DOMDocument $document): int|string + { + $columns = 80; + + if ($document->documentElement->hasAttribute('columns')) { + $columns = $document->documentElement->getAttribute('columns'); + + if ($columns !== 'max') { + $columns = $this->parseInteger($columns, 80); + } + } + + return $columns; + } + + /** + * @return array + */ + private function bootstrapForTestSuite(string $filename, DOMXPath $xpath): array + { + $bootstrapForTestSuite = []; + + foreach ($this->parseTestSuiteElements($xpath) as $element) { + if (!$element->hasAttribute('bootstrap')) { + continue; + } + + $name = $element->getAttribute('name'); + $bootstrap = $element->getAttribute('bootstrap'); + + assert($name !== ''); + assert($bootstrap !== ''); + + $bootstrapForTestSuite[$name] = $this->toAbsolutePath($filename, $bootstrap); + } + + return $bootstrapForTestSuite; + } + + private function testSuite(string $filename, DOMXPath $xpath): TestSuiteCollection + { + $testSuites = []; + + foreach ($this->parseTestSuiteElements($xpath) as $element) { + $exclude = []; + + foreach ($element->getElementsByTagName('exclude') as $excludeNode) { + $excludeFile = $excludeNode->textContent; + + if ($excludeFile !== '') { + $exclude[] = new File($this->toAbsolutePath($filename, $excludeFile)); + } + } + + $directories = []; + + foreach ($element->getElementsByTagName('directory') as $directoryNode) { + assert($directoryNode instanceof DOMElement); + + $directory = $directoryNode->textContent; + + if ($directory === '') { + continue; + } + + $prefix = ''; + + if ($directoryNode->hasAttribute('prefix')) { + $prefix = $directoryNode->getAttribute('prefix'); + } + + $suffix = 'Test.php'; + + if ($directoryNode->hasAttribute('suffix')) { + $suffix = $directoryNode->getAttribute('suffix'); + } + + $phpVersion = PHP_VERSION; + + if ($directoryNode->hasAttribute('phpVersion')) { + $phpVersion = $directoryNode->getAttribute('phpVersion'); + } + + $phpVersionOperator = new VersionComparisonOperator('>='); + + if ($directoryNode->hasAttribute('phpVersionOperator')) { + $phpVersionOperator = new VersionComparisonOperator($directoryNode->getAttribute('phpVersionOperator')); + } + + $groups = []; + + if ($directoryNode->hasAttribute('groups')) { + foreach (explode(',', $directoryNode->getAttribute('groups')) as $group) { + $group = trim($group); + + if ($group === '') { + continue; + } + + $groups[] = $group; + } + } + + $directories[] = new TestDirectory( + $this->toAbsolutePath($filename, $directory), + $prefix, + $suffix, + $phpVersion, + $phpVersionOperator, + $groups, + ); + } + + $files = []; + + foreach ($element->getElementsByTagName('file') as $fileNode) { + assert($fileNode instanceof DOMElement); + + $file = $fileNode->textContent; + + if ($file === '') { + continue; + } + + $phpVersion = PHP_VERSION; + + if ($fileNode->hasAttribute('phpVersion')) { + $phpVersion = $fileNode->getAttribute('phpVersion'); + } + + $phpVersionOperator = new VersionComparisonOperator('>='); + + if ($fileNode->hasAttribute('phpVersionOperator')) { + $phpVersionOperator = new VersionComparisonOperator($fileNode->getAttribute('phpVersionOperator')); + } + + $groups = []; + + if ($fileNode->hasAttribute('groups')) { + foreach (explode(',', $fileNode->getAttribute('groups')) as $group) { + $group = trim($group); + + if ($group === '') { + continue; + } + + $groups[] = $group; + } + } + + $files[] = new TestFile( + $this->toAbsolutePath($filename, $file), + $phpVersion, + $phpVersionOperator, + $groups, + ); + } + + $name = $element->getAttribute('name'); + + assert($name !== ''); + + $testSuites[] = new TestSuiteConfiguration( + $name, + TestDirectoryCollection::fromArray($directories), + TestFileCollection::fromArray($files), + FileCollection::fromArray($exclude), + ); + } + + return TestSuiteCollection::fromArray($testSuites); + } + + /** + * @return list + */ + private function parseTestSuiteElements(DOMXPath $xpath): array + { + $elements = []; + + $testSuiteNodes = $xpath->query('testsuites/testsuite'); + + assert($testSuiteNodes instanceof DOMNodeList); + + if ($testSuiteNodes->length === 0) { + $testSuiteNodes = $xpath->query('testsuite'); + + assert($testSuiteNodes instanceof DOMNodeList); + } + + if ($testSuiteNodes->length === 1) { + $element = $testSuiteNodes->item(0); + + assert($element instanceof DOMElement); + + $elements[] = $element; + } else { + foreach ($testSuiteNodes as $testSuiteNode) { + assert($testSuiteNode instanceof DOMElement); + + $elements[] = $testSuiteNode; + } + } + + return $elements; + } + + private function element(DOMXPath $xpath, string $element): ?DOMElement + { + $nodes = $xpath->query($element); + + assert($nodes instanceof DOMNodeList); + + if ($nodes->length === 1) { + $node = $nodes->item(0); + + assert($node instanceof DOMElement); + + return $node; + } + + return null; + } +} diff --git a/src/TextUI/Configuration/Xml/Logging/Junit.php b/src/TextUI/Configuration/Xml/Logging/Junit.php new file mode 100644 index 00000000000..cf9878dfd25 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Logging/Junit.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\Logging; + +use PHPUnit\TextUI\Configuration\File; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Junit +{ + private File $target; + + public function __construct(File $target) + { + $this->target = $target; + } + + public function target(): File + { + return $this->target; + } +} diff --git a/src/TextUI/Configuration/Xml/Logging/Logging.php b/src/TextUI/Configuration/Xml/Logging/Logging.php new file mode 100644 index 00000000000..63f4d89fc9c --- /dev/null +++ b/src/TextUI/Configuration/Xml/Logging/Logging.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\Logging; + +use PHPUnit\TextUI\XmlConfiguration\Exception; +use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Html as TestDoxHtml; +use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Text as TestDoxText; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Logging +{ + private ?Junit $junit; + private ?Otr $otr; + private ?TeamCity $teamCity; + private ?TestDoxHtml $testDoxHtml; + private ?TestDoxText $testDoxText; + + public function __construct(?Junit $junit, ?Otr $otr, ?TeamCity $teamCity, ?TestDoxHtml $testDoxHtml, ?TestDoxText $testDoxText) + { + $this->junit = $junit; + $this->otr = $otr; + $this->teamCity = $teamCity; + $this->testDoxHtml = $testDoxHtml; + $this->testDoxText = $testDoxText; + } + + public function hasJunit(): bool + { + return $this->junit !== null; + } + + /** + * @throws Exception + */ + public function junit(): Junit + { + if ($this->junit === null) { + throw new Exception('Logger "JUnit XML" is not configured'); + } + + return $this->junit; + } + + public function hasOtr(): bool + { + return $this->otr !== null; + } + + /** + * @throws Exception + */ + public function otr(): Otr + { + if ($this->otr === null) { + throw new Exception('Logger "Open Test Reporting XML" is not configured'); + } + + return $this->otr; + } + + public function hasTeamCity(): bool + { + return $this->teamCity !== null; + } + + /** + * @throws Exception + */ + public function teamCity(): TeamCity + { + if ($this->teamCity === null) { + throw new Exception('Logger "Team City" is not configured'); + } + + return $this->teamCity; + } + + public function hasTestDoxHtml(): bool + { + return $this->testDoxHtml !== null; + } + + /** + * @throws Exception + */ + public function testDoxHtml(): TestDoxHtml + { + if ($this->testDoxHtml === null) { + throw new Exception('Logger "TestDox HTML" is not configured'); + } + + return $this->testDoxHtml; + } + + public function hasTestDoxText(): bool + { + return $this->testDoxText !== null; + } + + /** + * @throws Exception + */ + public function testDoxText(): TestDoxText + { + if ($this->testDoxText === null) { + throw new Exception('Logger "TestDox Text" is not configured'); + } + + return $this->testDoxText; + } +} diff --git a/src/TextUI/Configuration/Xml/Logging/Otr.php b/src/TextUI/Configuration/Xml/Logging/Otr.php new file mode 100644 index 00000000000..25cfc989862 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Logging/Otr.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\Logging; + +use PHPUnit\TextUI\Configuration\File; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Otr +{ + private File $target; + private bool $includeGitInformation; + + public function __construct(File $target, bool $includeGitInformation) + { + $this->target = $target; + $this->includeGitInformation = $includeGitInformation; + } + + public function target(): File + { + return $this->target; + } + + public function includeGitInformation(): bool + { + return $this->includeGitInformation; + } +} diff --git a/src/TextUI/Configuration/Xml/Logging/TeamCity.php b/src/TextUI/Configuration/Xml/Logging/TeamCity.php new file mode 100644 index 00000000000..daf1ceccfd7 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Logging/TeamCity.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\Logging; + +use PHPUnit\TextUI\Configuration\File; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class TeamCity +{ + private File $target; + + public function __construct(File $target) + { + $this->target = $target; + } + + public function target(): File + { + return $this->target; + } +} diff --git a/src/TextUI/Configuration/Xml/Logging/TestDox/Html.php b/src/TextUI/Configuration/Xml/Logging/TestDox/Html.php new file mode 100644 index 00000000000..60e9d4da342 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Logging/TestDox/Html.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\Logging\TestDox; + +use PHPUnit\TextUI\Configuration\File; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Html +{ + private File $target; + + public function __construct(File $target) + { + $this->target = $target; + } + + public function target(): File + { + return $this->target; + } +} diff --git a/src/TextUI/Configuration/Xml/Logging/TestDox/Text.php b/src/TextUI/Configuration/Xml/Logging/TestDox/Text.php new file mode 100644 index 00000000000..ed436c0a84b --- /dev/null +++ b/src/TextUI/Configuration/Xml/Logging/TestDox/Text.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\Logging\TestDox; + +use PHPUnit\TextUI\Configuration\File; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Text +{ + private File $target; + + public function __construct(File $target) + { + $this->target = $target; + } + + public function target(): File + { + return $this->target; + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/MigrationBuilder.php b/src/TextUI/Configuration/Xml/Migration/MigrationBuilder.php new file mode 100644 index 00000000000..6235c55f3fe --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/MigrationBuilder.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function version_compare; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class MigrationBuilder +{ + /** + * @var non-empty-array> + */ + private const array AVAILABLE_MIGRATIONS = [ + '8.5' => [ + RemoveLogTypes::class, + ], + + '9.2' => [ + RemoveCacheTokensAttribute::class, + IntroduceCoverageElement::class, + MoveAttributesFromRootToCoverage::class, + MoveAttributesFromFilterWhitelistToCoverage::class, + MoveWhitelistIncludesToCoverage::class, + MoveWhitelistExcludesToCoverage::class, + RemoveEmptyFilter::class, + CoverageCloverToReport::class, + CoverageCrap4jToReport::class, + CoverageHtmlToReport::class, + CoveragePhpToReport::class, + CoverageTextToReport::class, + CoverageXmlToReport::class, + ConvertLogTypes::class, + ], + + '9.5' => [ + RemoveListeners::class, + RemoveTestSuiteLoaderAttributes::class, + RemoveCacheResultFileAttribute::class, + RemoveCoverageElementCacheDirectoryAttribute::class, + RemoveCoverageElementProcessUncoveredFilesAttribute::class, + IntroduceCacheDirectoryAttribute::class, + RenameBackupStaticAttributesAttribute::class, + RemoveBeStrictAboutResourceUsageDuringSmallTestsAttribute::class, + RemoveBeStrictAboutTodoAnnotatedTestsAttribute::class, + RemovePrinterAttributes::class, + RemoveVerboseAttribute::class, + RenameForceCoversAnnotationAttribute::class, + RenameBeStrictAboutCoversAnnotationAttribute::class, + RemoveConversionToExceptionsAttributes::class, + RemoveNoInteractionAttribute::class, + RemoveLoggingElements::class, + RemoveTestDoxGroupsElement::class, + ], + + '10.0' => [ + MoveCoverageDirectoriesToSource::class, + ], + + '10.4' => [ + RemoveBeStrictAboutTodoAnnotatedTestsAttribute::class, + ], + + '10.5' => [ + RemoveRegisterMockObjectsFromTestArgumentsRecursivelyAttribute::class, + ], + + '11.0' => [ + ReplaceRestrictDeprecationsWithIgnoreDeprecations::class, + ], + + '11.1' => [ + RemoveCacheResultFileAttribute::class, + RemoveCoverageElementCacheDirectoryAttribute::class, + ], + + '11.2' => [ + RemoveBeStrictAboutTodoAnnotatedTestsAttribute::class, + ], + ]; + + /** + * @return non-empty-list + */ + public function build(string $fromVersion): array + { + $stack = [new UpdateSchemaLocation]; + + foreach (self::AVAILABLE_MIGRATIONS as $version => $migrations) { + if (version_compare($version, $fromVersion, '<')) { + continue; + } + + foreach ($migrations as $migration) { + $stack[] = new $migration; + } + } + + return $stack; + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/MigrationException.php b/src/TextUI/Configuration/Xml/Migration/MigrationException.php new file mode 100644 index 00000000000..bb35aca6817 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/MigrationException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use PHPUnit\Exception; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MigrationException extends RuntimeException implements Exception +{ +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/ConvertLogTypes.php b/src/TextUI/Configuration/Xml/Migration/Migrations/ConvertLogTypes.php new file mode 100644 index 00000000000..81a0e322abc --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/ConvertLogTypes.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ConvertLogTypes implements Migration +{ + public function migrate(DOMDocument $document): void + { + $logging = $document->getElementsByTagName('logging')->item(0); + + if (!$logging instanceof DOMElement) { + return; + } + $types = [ + 'junit' => 'junit', + 'teamcity' => 'teamcity', + 'testdox-html' => 'testdoxHtml', + 'testdox-text' => 'testdoxText', + 'testdox-xml' => 'testdoxXml', + 'plain' => 'text', + ]; + + $logNodes = []; + + foreach ($logging->getElementsByTagName('log') as $logNode) { + if (!isset($types[$logNode->getAttribute('type')])) { + continue; + } + + $logNodes[] = $logNode; + } + + foreach ($logNodes as $oldNode) { + $newLogNode = $document->createElement($types[$oldNode->getAttribute('type')]); + $newLogNode->setAttribute('outputFile', $oldNode->getAttribute('target')); + + $logging->replaceChild($newLogNode, $oldNode); + } + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageCloverToReport.php b/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageCloverToReport.php new file mode 100644 index 00000000000..0dfee46fda2 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageCloverToReport.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class CoverageCloverToReport extends LogToReportMigration +{ + protected function forType(): string + { + return 'coverage-clover'; + } + + protected function toReportFormat(DOMElement $logNode): DOMElement + { + $clover = $logNode->ownerDocument->createElement('clover'); + + $clover->setAttribute('outputFile', $logNode->getAttribute('target')); + + return $clover; + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageCrap4jToReport.php b/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageCrap4jToReport.php new file mode 100644 index 00000000000..f0aac5c7c9b --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageCrap4jToReport.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class CoverageCrap4jToReport extends LogToReportMigration +{ + protected function forType(): string + { + return 'coverage-crap4j'; + } + + protected function toReportFormat(DOMElement $logNode): DOMElement + { + $crap4j = $logNode->ownerDocument->createElement('crap4j'); + $crap4j->setAttribute('outputFile', $logNode->getAttribute('target')); + + $this->migrateAttributes($logNode, $crap4j, ['threshold']); + + return $crap4j; + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageHtmlToReport.php b/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageHtmlToReport.php new file mode 100644 index 00000000000..f6b7982d98b --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageHtmlToReport.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class CoverageHtmlToReport extends LogToReportMigration +{ + protected function forType(): string + { + return 'coverage-html'; + } + + protected function toReportFormat(DOMElement $logNode): DOMElement + { + $html = $logNode->ownerDocument->createElement('html'); + $html->setAttribute('outputDirectory', $logNode->getAttribute('target')); + + $this->migrateAttributes($logNode, $html, ['lowUpperBound', 'highLowerBound']); + + return $html; + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/CoveragePhpToReport.php b/src/TextUI/Configuration/Xml/Migration/Migrations/CoveragePhpToReport.php new file mode 100644 index 00000000000..7e362708b3e --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/CoveragePhpToReport.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class CoveragePhpToReport extends LogToReportMigration +{ + protected function forType(): string + { + return 'coverage-php'; + } + + protected function toReportFormat(DOMElement $logNode): DOMElement + { + $php = $logNode->ownerDocument->createElement('php'); + $php->setAttribute('outputFile', $logNode->getAttribute('target')); + + return $php; + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageTextToReport.php b/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageTextToReport.php new file mode 100644 index 00000000000..d463cef8d45 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageTextToReport.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class CoverageTextToReport extends LogToReportMigration +{ + protected function forType(): string + { + return 'coverage-text'; + } + + protected function toReportFormat(DOMElement $logNode): DOMElement + { + $text = $logNode->ownerDocument->createElement('text'); + $text->setAttribute('outputFile', $logNode->getAttribute('target')); + + $this->migrateAttributes($logNode, $text, ['showUncoveredFiles', 'showOnlySummary']); + + return $text; + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageXmlToReport.php b/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageXmlToReport.php new file mode 100644 index 00000000000..3db89974215 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageXmlToReport.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class CoverageXmlToReport extends LogToReportMigration +{ + protected function forType(): string + { + return 'coverage-xml'; + } + + protected function toReportFormat(DOMElement $logNode): DOMElement + { + $xml = $logNode->ownerDocument->createElement('xml'); + $xml->setAttribute('outputDirectory', $logNode->getAttribute('target')); + + return $xml; + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/IntroduceCacheDirectoryAttribute.php b/src/TextUI/Configuration/Xml/Migration/Migrations/IntroduceCacheDirectoryAttribute.php new file mode 100644 index 00000000000..87624cc6dec --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/IntroduceCacheDirectoryAttribute.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class IntroduceCacheDirectoryAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('cacheDirectory')) { + return; + } + + $root->setAttribute('cacheDirectory', '.phpunit.cache'); + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/IntroduceCoverageElement.php b/src/TextUI/Configuration/Xml/Migration/Migrations/IntroduceCoverageElement.php new file mode 100644 index 00000000000..9334c1f43f4 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/IntroduceCoverageElement.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class IntroduceCoverageElement implements Migration +{ + public function migrate(DOMDocument $document): void + { + $coverage = $document->createElement('coverage'); + + $document->documentElement->insertBefore( + $coverage, + $document->documentElement->firstChild, + ); + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/LogToReportMigration.php b/src/TextUI/Configuration/Xml/Migration/Migrations/LogToReportMigration.php new file mode 100644 index 00000000000..08815cbcb31 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/LogToReportMigration.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use function sprintf; +use DOMDocument; +use DOMElement; +use DOMXPath; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class LogToReportMigration implements Migration +{ + /** + * @throws MigrationException + */ + public function migrate(DOMDocument $document): void + { + $coverage = $document->getElementsByTagName('coverage')->item(0); + + if (!$coverage instanceof DOMElement) { + throw new MigrationException('Unexpected state - No coverage element'); + } + + $logNode = $this->findLogNode($document); + + if ($logNode === null) { + return; + } + + $reportChild = $this->toReportFormat($logNode); + + $report = $coverage->getElementsByTagName('report')->item(0); + + if ($report === null) { + $report = $coverage->appendChild($document->createElement('report')); + } + + $report->appendChild($reportChild); + $logNode->parentNode->removeChild($logNode); + } + + /** + * @param list $attributes + */ + protected function migrateAttributes(DOMElement $src, DOMElement $dest, array $attributes): void + { + foreach ($attributes as $attr) { + if (!$src->hasAttribute($attr)) { + continue; + } + + $dest->setAttribute($attr, $src->getAttribute($attr)); + $src->removeAttribute($attr); + } + } + + abstract protected function forType(): string; + + abstract protected function toReportFormat(DOMElement $logNode): DOMElement; + + private function findLogNode(DOMDocument $document): ?DOMElement + { + $xpath = new DOMXPath($document); + + $logNode = $xpath->query( + sprintf( + '//logging/log[@type="%s"]', + $this->forType(), + ), + ); + + assert($logNode !== false); + + $logNode = $logNode->item(0); + + if (!$logNode instanceof DOMElement) { + return null; + } + + return $logNode; + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/Migration.php b/src/TextUI/Configuration/Xml/Migration/Migrations/Migration.php new file mode 100644 index 00000000000..05359a2d082 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/Migration.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Migration +{ + public function migrate(DOMDocument $document): void; +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/MoveAttributesFromFilterWhitelistToCoverage.php b/src/TextUI/Configuration/Xml/Migration/Migrations/MoveAttributesFromFilterWhitelistToCoverage.php new file mode 100644 index 00000000000..685381698cb --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/MoveAttributesFromFilterWhitelistToCoverage.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class MoveAttributesFromFilterWhitelistToCoverage implements Migration +{ + /** + * @throws MigrationException + */ + public function migrate(DOMDocument $document): void + { + $whitelist = $document->getElementsByTagName('whitelist')->item(0); + + if ($whitelist === null) { + return; + } + + $coverage = $document->getElementsByTagName('coverage')->item(0); + + if (!$coverage instanceof DOMElement) { + throw new MigrationException('Unexpected state - No coverage element'); + } + + $map = [ + 'addUncoveredFilesFromWhitelist' => 'includeUncoveredFiles', + 'processUncoveredFilesFromWhitelist' => 'processUncoveredFiles', + ]; + + foreach ($map as $old => $new) { + if (!$whitelist->hasAttribute($old)) { + continue; + } + + $coverage->setAttribute($new, $whitelist->getAttribute($old)); + $whitelist->removeAttribute($old); + } + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/MoveAttributesFromRootToCoverage.php b/src/TextUI/Configuration/Xml/Migration/Migrations/MoveAttributesFromRootToCoverage.php new file mode 100644 index 00000000000..f0e47b90fe4 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/MoveAttributesFromRootToCoverage.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class MoveAttributesFromRootToCoverage implements Migration +{ + /** + * @throws MigrationException + */ + public function migrate(DOMDocument $document): void + { + $map = [ + 'disableCodeCoverageIgnore' => 'disableCodeCoverageIgnore', + 'ignoreDeprecatedCodeUnitsFromCodeCoverage' => 'ignoreDeprecatedCodeUnits', + ]; + + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + $coverage = $document->getElementsByTagName('coverage')->item(0); + + if (!$coverage instanceof DOMElement) { + throw new MigrationException('Unexpected state - No coverage element'); + } + + foreach ($map as $old => $new) { + if (!$root->hasAttribute($old)) { + continue; + } + + $coverage->setAttribute($new, $root->getAttribute($old)); + $root->removeAttribute($old); + } + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/MoveCoverageDirectoriesToSource.php b/src/TextUI/Configuration/Xml/Migration/Migrations/MoveCoverageDirectoriesToSource.php new file mode 100644 index 00000000000..4dd37ea5158 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/MoveCoverageDirectoriesToSource.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; +use DOMXPath; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class MoveCoverageDirectoriesToSource implements Migration +{ + /** + * @throws MigrationException + */ + public function migrate(DOMDocument $document): void + { + $source = $document->getElementsByTagName('source')->item(0); + + if ($source !== null) { + return; + } + + $coverage = $document->getElementsByTagName('coverage')->item(0); + + if ($coverage === null) { + return; + } + + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + $source = $document->createElement('source'); + $root->appendChild($source); + + $xpath = new DOMXPath($document); + + foreach (['include', 'exclude'] as $element) { + $nodes = $xpath->query('//coverage/' . $element); + + assert($nodes !== false); + + foreach (SnapshotNodeList::fromNodeList($nodes) as $node) { + $source->appendChild($node); + } + } + + if ($coverage->childElementCount !== 0) { + return; + } + + assert($coverage->parentNode !== null); + + $coverage->parentNode->removeChild($coverage); + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/MoveWhitelistExcludesToCoverage.php b/src/TextUI/Configuration/Xml/Migration/Migrations/MoveWhitelistExcludesToCoverage.php new file mode 100644 index 00000000000..09641bb13b7 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/MoveWhitelistExcludesToCoverage.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use function in_array; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class MoveWhitelistExcludesToCoverage implements Migration +{ + /** + * @throws MigrationException + */ + public function migrate(DOMDocument $document): void + { + $whitelist = $document->getElementsByTagName('whitelist')->item(0); + + if ($whitelist === null) { + return; + } + + $excludeNodes = SnapshotNodeList::fromNodeList($whitelist->getElementsByTagName('exclude')); + + if ($excludeNodes->count() === 0) { + return; + } + + $coverage = $document->getElementsByTagName('coverage')->item(0); + + if (!$coverage instanceof DOMElement) { + throw new MigrationException('Unexpected state - No coverage element'); + } + + $targetExclude = $coverage->getElementsByTagName('exclude')->item(0); + + if ($targetExclude === null) { + $targetExclude = $coverage->appendChild( + $document->createElement('exclude'), + ); + } + + foreach ($excludeNodes as $excludeNode) { + assert($excludeNode instanceof DOMElement); + + foreach (SnapshotNodeList::fromNodeList($excludeNode->childNodes) as $child) { + if (!$child instanceof DOMElement || !in_array($child->nodeName, ['directory', 'file'], true)) { + continue; + } + + $targetExclude->appendChild($child); + } + + if ($excludeNode->getElementsByTagName('*')->count() !== 0) { + throw new MigrationException('Dangling child elements in exclude found.'); + } + + $whitelist->removeChild($excludeNode); + } + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/MoveWhitelistIncludesToCoverage.php b/src/TextUI/Configuration/Xml/Migration/Migrations/MoveWhitelistIncludesToCoverage.php new file mode 100644 index 00000000000..9990124963d --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/MoveWhitelistIncludesToCoverage.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class MoveWhitelistIncludesToCoverage implements Migration +{ + /** + * @throws MigrationException + */ + public function migrate(DOMDocument $document): void + { + $whitelist = $document->getElementsByTagName('whitelist')->item(0); + + if ($whitelist === null) { + return; + } + + $coverage = $document->getElementsByTagName('coverage')->item(0); + + if (!$coverage instanceof DOMElement) { + throw new MigrationException('Unexpected state - No coverage element'); + } + + $include = $document->createElement('include'); + $coverage->appendChild($include); + + foreach (SnapshotNodeList::fromNodeList($whitelist->childNodes) as $child) { + if (!$child instanceof DOMElement) { + continue; + } + + if (!($child->nodeName === 'directory' || $child->nodeName === 'file')) { + continue; + } + + $include->appendChild($child); + } + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveBeStrictAboutResourceUsageDuringSmallTestsAttribute.php b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveBeStrictAboutResourceUsageDuringSmallTestsAttribute.php new file mode 100644 index 00000000000..cdb90775cd9 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveBeStrictAboutResourceUsageDuringSmallTestsAttribute.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveBeStrictAboutResourceUsageDuringSmallTestsAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('beStrictAboutResourceUsageDuringSmallTests')) { + $root->removeAttribute('beStrictAboutResourceUsageDuringSmallTests'); + } + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveBeStrictAboutTodoAnnotatedTestsAttribute.php b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveBeStrictAboutTodoAnnotatedTestsAttribute.php new file mode 100644 index 00000000000..1858e67f42b --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveBeStrictAboutTodoAnnotatedTestsAttribute.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveBeStrictAboutTodoAnnotatedTestsAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('beStrictAboutTodoAnnotatedTests')) { + $root->removeAttribute('beStrictAboutTodoAnnotatedTests'); + } + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveCacheResultFileAttribute.php b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveCacheResultFileAttribute.php new file mode 100644 index 00000000000..a5c51c4ebc3 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveCacheResultFileAttribute.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveCacheResultFileAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('cacheResultFile')) { + $root->removeAttribute('cacheResultFile'); + } + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveCacheTokensAttribute.php b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveCacheTokensAttribute.php new file mode 100644 index 00000000000..69bf38a21f3 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveCacheTokensAttribute.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveCacheTokensAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('cacheTokens')) { + $root->removeAttribute('cacheTokens'); + } + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveConversionToExceptionsAttributes.php b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveConversionToExceptionsAttributes.php new file mode 100644 index 00000000000..a908aee381f --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveConversionToExceptionsAttributes.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveConversionToExceptionsAttributes implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('convertDeprecationsToExceptions')) { + $root->removeAttribute('convertDeprecationsToExceptions'); + } + + if ($root->hasAttribute('convertErrorsToExceptions')) { + $root->removeAttribute('convertErrorsToExceptions'); + } + + if ($root->hasAttribute('convertNoticesToExceptions')) { + $root->removeAttribute('convertNoticesToExceptions'); + } + + if ($root->hasAttribute('convertWarningsToExceptions')) { + $root->removeAttribute('convertWarningsToExceptions'); + } + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveCoverageElementCacheDirectoryAttribute.php b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveCoverageElementCacheDirectoryAttribute.php new file mode 100644 index 00000000000..c26d2071c72 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveCoverageElementCacheDirectoryAttribute.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveCoverageElementCacheDirectoryAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $node = $document->getElementsByTagName('coverage')->item(0); + + if (!$node instanceof DOMElement || $node->parentNode === null) { + return; + } + + if ($node->hasAttribute('cacheDirectory')) { + $node->removeAttribute('cacheDirectory'); + } + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveCoverageElementProcessUncoveredFilesAttribute.php b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveCoverageElementProcessUncoveredFilesAttribute.php new file mode 100644 index 00000000000..34768625101 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveCoverageElementProcessUncoveredFilesAttribute.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveCoverageElementProcessUncoveredFilesAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $node = $document->getElementsByTagName('coverage')->item(0); + + if (!$node instanceof DOMElement || $node->parentNode === null) { + return; + } + + if ($node->hasAttribute('processUncoveredFiles')) { + $node->removeAttribute('processUncoveredFiles'); + } + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveEmptyFilter.php b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveEmptyFilter.php new file mode 100644 index 00000000000..a831e205109 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveEmptyFilter.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function sprintf; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveEmptyFilter implements Migration +{ + /** + * @throws MigrationException + */ + public function migrate(DOMDocument $document): void + { + $whitelist = $document->getElementsByTagName('whitelist')->item(0); + + if ($whitelist instanceof DOMElement) { + $this->ensureEmpty($whitelist); + $whitelist->parentNode->removeChild($whitelist); + } + + $filter = $document->getElementsByTagName('filter')->item(0); + + if ($filter instanceof DOMElement) { + $this->ensureEmpty($filter); + $filter->parentNode->removeChild($filter); + } + } + + /** + * @throws MigrationException + */ + private function ensureEmpty(DOMElement $element): void + { + if ($element->attributes->length > 0) { + throw new MigrationException(sprintf('%s element has unexpected attributes', $element->nodeName)); + } + + if ($element->getElementsByTagName('*')->length > 0) { + throw new MigrationException(sprintf('%s element has unexpected children', $element->nodeName)); + } + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveListeners.php b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveListeners.php new file mode 100644 index 00000000000..bf2889965de --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveListeners.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveListeners implements Migration +{ + public function migrate(DOMDocument $document): void + { + $node = $document->getElementsByTagName('listeners')->item(0); + + if (!$node instanceof DOMElement || $node->parentNode === null) { + return; + } + + $node->parentNode->removeChild($node); + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveLogTypes.php b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveLogTypes.php new file mode 100644 index 00000000000..46ee55e925b --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveLogTypes.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveLogTypes implements Migration +{ + public function migrate(DOMDocument $document): void + { + $logging = $document->getElementsByTagName('logging')->item(0); + + if (!$logging instanceof DOMElement) { + return; + } + + foreach (SnapshotNodeList::fromNodeList($logging->getElementsByTagName('log')) as $logNode) { + assert($logNode instanceof DOMElement); + + switch ($logNode->getAttribute('type')) { + case 'json': + case 'tap': + $logging->removeChild($logNode); + } + } + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveLoggingElements.php b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveLoggingElements.php new file mode 100644 index 00000000000..d40ab029cd1 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveLoggingElements.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; +use DOMXPath; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveLoggingElements implements Migration +{ + public function migrate(DOMDocument $document): void + { + $this->removeTestDoxElement($document); + $this->removeTextElement($document); + } + + private function removeTestDoxElement(DOMDocument $document): void + { + $nodes = new DOMXPath($document)->query('logging/testdoxXml'); + + assert($nodes !== false); + + $node = $nodes->item(0); + + if (!$node instanceof DOMElement || $node->parentNode === null) { + return; + } + + $node->parentNode->removeChild($node); + } + + private function removeTextElement(DOMDocument $document): void + { + $nodes = new DOMXPath($document)->query('logging/text'); + + assert($nodes !== false); + + $node = $nodes->item(0); + + if (!$node instanceof DOMElement || $node->parentNode === null) { + return; + } + + $node->parentNode->removeChild($node); + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveNoInteractionAttribute.php b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveNoInteractionAttribute.php new file mode 100644 index 00000000000..897cccd81bd --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveNoInteractionAttribute.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveNoInteractionAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('noInteraction')) { + $root->removeAttribute('noInteraction'); + } + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/RemovePrinterAttributes.php b/src/TextUI/Configuration/Xml/Migration/Migrations/RemovePrinterAttributes.php new file mode 100644 index 00000000000..84f4bcf1add --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/RemovePrinterAttributes.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemovePrinterAttributes implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('printerClass')) { + $root->removeAttribute('printerClass'); + } + + if ($root->hasAttribute('printerFile')) { + $root->removeAttribute('printerFile'); + } + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveRegisterMockObjectsFromTestArgumentsRecursivelyAttribute.php b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveRegisterMockObjectsFromTestArgumentsRecursivelyAttribute.php new file mode 100644 index 00000000000..e2ff30514f9 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveRegisterMockObjectsFromTestArgumentsRecursivelyAttribute.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveRegisterMockObjectsFromTestArgumentsRecursivelyAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('registerMockObjectsFromTestArgumentsRecursively')) { + $root->removeAttribute('registerMockObjectsFromTestArgumentsRecursively'); + } + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveTestDoxGroupsElement.php b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveTestDoxGroupsElement.php new file mode 100644 index 00000000000..ea5a69217be --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveTestDoxGroupsElement.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveTestDoxGroupsElement implements Migration +{ + public function migrate(DOMDocument $document): void + { + $node = $document->getElementsByTagName('testdoxGroups')->item(0); + + if (!$node instanceof DOMElement || $node->parentNode === null) { + return; + } + + $node->parentNode->removeChild($node); + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveTestSuiteLoaderAttributes.php b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveTestSuiteLoaderAttributes.php new file mode 100644 index 00000000000..284dda2ef88 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveTestSuiteLoaderAttributes.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveTestSuiteLoaderAttributes implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('testSuiteLoaderClass')) { + $root->removeAttribute('testSuiteLoaderClass'); + } + + if ($root->hasAttribute('testSuiteLoaderFile')) { + $root->removeAttribute('testSuiteLoaderFile'); + } + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveVerboseAttribute.php b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveVerboseAttribute.php new file mode 100644 index 00000000000..d4aa66087bb --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveVerboseAttribute.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveVerboseAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('verbose')) { + $root->removeAttribute('verbose'); + } + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/RenameBackupStaticAttributesAttribute.php b/src/TextUI/Configuration/Xml/Migration/Migrations/RenameBackupStaticAttributesAttribute.php new file mode 100644 index 00000000000..c2de95ce38c --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/RenameBackupStaticAttributesAttribute.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RenameBackupStaticAttributesAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('backupStaticProperties')) { + return; + } + + if (!$root->hasAttribute('backupStaticAttributes')) { + return; + } + + $root->setAttribute('backupStaticProperties', $root->getAttribute('backupStaticAttributes')); + $root->removeAttribute('backupStaticAttributes'); + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/RenameBeStrictAboutCoversAnnotationAttribute.php b/src/TextUI/Configuration/Xml/Migration/Migrations/RenameBeStrictAboutCoversAnnotationAttribute.php new file mode 100644 index 00000000000..dda890b66cb --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/RenameBeStrictAboutCoversAnnotationAttribute.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RenameBeStrictAboutCoversAnnotationAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('beStrictAboutCoverageMetadata')) { + return; + } + + if (!$root->hasAttribute('beStrictAboutCoversAnnotation')) { + return; + } + + $root->setAttribute('beStrictAboutCoverageMetadata', $root->getAttribute('beStrictAboutCoversAnnotation')); + $root->removeAttribute('beStrictAboutCoversAnnotation'); + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/RenameForceCoversAnnotationAttribute.php b/src/TextUI/Configuration/Xml/Migration/Migrations/RenameForceCoversAnnotationAttribute.php new file mode 100644 index 00000000000..707aff8a368 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/RenameForceCoversAnnotationAttribute.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RenameForceCoversAnnotationAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('requireCoverageMetadata')) { + return; + } + + if (!$root->hasAttribute('forceCoversAnnotation')) { + return; + } + + $root->setAttribute('requireCoverageMetadata', $root->getAttribute('forceCoversAnnotation')); + $root->removeAttribute('forceCoversAnnotation'); + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/ReplaceRestrictDeprecationsWithIgnoreDeprecations.php b/src/TextUI/Configuration/Xml/Migration/Migrations/ReplaceRestrictDeprecationsWithIgnoreDeprecations.php new file mode 100644 index 00000000000..12cb1e7f087 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/ReplaceRestrictDeprecationsWithIgnoreDeprecations.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ReplaceRestrictDeprecationsWithIgnoreDeprecations implements Migration +{ + /** + * @throws MigrationException + */ + public function migrate(DOMDocument $document): void + { + $source = $document->getElementsByTagName('source')->item(0); + + if ($source === null) { + return; + } + + assert($source instanceof DOMElement); + + if (!$source->hasAttribute('restrictDeprecations')) { + return; + } + + $restrictDeprecations = $source->getAttribute('restrictDeprecations') === 'true'; + + $source->removeAttribute('restrictDeprecations'); + + if (!$restrictDeprecations || + $source->hasAttribute('ignoreIndirectDeprecations')) { + return; + } + + $source->setAttribute('ignoreIndirectDeprecations', 'true'); + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrations/UpdateSchemaLocation.php b/src/TextUI/Configuration/Xml/Migration/Migrations/UpdateSchemaLocation.php new file mode 100644 index 00000000000..35c8abe81d2 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrations/UpdateSchemaLocation.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use function str_contains; +use DOMDocument; +use DOMElement; +use PHPUnit\Runner\Version; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class UpdateSchemaLocation implements Migration +{ + private const string NAMESPACE_URI = 'http://www.w3.org/2001/XMLSchema-instance'; + private const string LOCAL_NAME_SCHEMA_LOCATION = 'noNamespaceSchemaLocation'; + + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + $existingSchemaLocation = $root->getAttributeNodeNS(self::NAMESPACE_URI, self::LOCAL_NAME_SCHEMA_LOCATION)->value; + + if (str_contains($existingSchemaLocation, '://') === false) { // If the current schema location is a relative path, don't update it + return; + } + + $root->setAttributeNS( + self::NAMESPACE_URI, + 'xsi:' . self::LOCAL_NAME_SCHEMA_LOCATION, + 'https://schema.phpunit.de/' . Version::series() . '/phpunit.xsd', + ); + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/Migrator.php b/src/TextUI/Configuration/Xml/Migration/Migrator.php new file mode 100644 index 00000000000..4649dec28e4 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/Migrator.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use PHPUnit\Runner\Version; +use PHPUnit\Util\Xml\Loader as XmlLoader; +use PHPUnit\Util\Xml\XmlException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Migrator +{ + /** + * @throws Exception + * @throws MigrationException + * @throws XmlException + */ + public function migrate(string $filename): string + { + $origin = (new SchemaDetector)->detect($filename); + + if (!$origin->detected()) { + throw new Exception('The file does not validate against any known schema'); + } + + if ($origin->version() === Version::series()) { + throw new Exception('The file does not need to be migrated'); + } + + $configurationDocument = (new XmlLoader)->loadFile($filename); + + foreach ((new MigrationBuilder)->build($origin->version()) as $migration) { + $migration->migrate($configurationDocument); + } + + $configurationDocument->formatOutput = true; + $configurationDocument->preserveWhiteSpace = false; + + $xml = $configurationDocument->saveXML(); + + assert($xml !== false); + + return $xml; + } +} diff --git a/src/TextUI/Configuration/Xml/Migration/SnapshotNodeList.php b/src/TextUI/Configuration/Xml/Migration/SnapshotNodeList.php new file mode 100644 index 00000000000..491c24edace --- /dev/null +++ b/src/TextUI/Configuration/Xml/Migration/SnapshotNodeList.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use ArrayIterator; +use Countable; +use DOMNode; +use DOMNodeList; +use IteratorAggregate; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @template-implements IteratorAggregate + */ +final class SnapshotNodeList implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $nodes = []; + + /** + * @param DOMNodeList $list + */ + public static function fromNodeList(DOMNodeList $list): self + { + $snapshot = new self; + + foreach ($list as $node) { + $snapshot->nodes[] = $node; + } + + return $snapshot; + } + + public function count(): int + { + return count($this->nodes); + } + + /** + * @return ArrayIterator + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->nodes); + } +} diff --git a/src/TextUI/Configuration/Xml/PHPUnit.php b/src/TextUI/Configuration/Xml/PHPUnit.php new file mode 100644 index 00000000000..0b384e7399e --- /dev/null +++ b/src/TextUI/Configuration/Xml/PHPUnit.php @@ -0,0 +1,530 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class PHPUnit +{ + private ?string $cacheDirectory; + private bool $cacheResult; + private int|string $columns; + private string $colors; + private bool $stderr; + private bool $displayDetailsOnAllIssues; + private bool $displayDetailsOnIncompleteTests; + private bool $displayDetailsOnSkippedTests; + private bool $displayDetailsOnTestsThatTriggerDeprecations; + private bool $displayDetailsOnPhpunitDeprecations; + private bool $displayDetailsOnPhpunitNotices; + private bool $displayDetailsOnTestsThatTriggerErrors; + private bool $displayDetailsOnTestsThatTriggerNotices; + private bool $displayDetailsOnTestsThatTriggerWarnings; + private bool $reverseDefectList; + private bool $requireCoverageMetadata; + private ?string $bootstrap; + + /** + * @var array + */ + private array $bootstrapForTestSuite; + private bool $processIsolation; + private bool $failOnAllIssues; + private bool $failOnDeprecation; + private bool $failOnPhpunitDeprecation; + private bool $failOnPhpunitNotice; + private bool $failOnPhpunitWarning; + private bool $failOnEmptyTestSuite; + private bool $failOnIncomplete; + private bool $failOnNotice; + private bool $failOnRisky; + private bool $failOnSkipped; + private bool $failOnWarning; + private bool $stopOnDefect; + private bool $stopOnDeprecation; + private bool $stopOnError; + private bool $stopOnFailure; + private bool $stopOnIncomplete; + private bool $stopOnNotice; + private bool $stopOnRisky; + private bool $stopOnSkipped; + private bool $stopOnWarning; + + /** + * @var ?non-empty-string + */ + private ?string $extensionsDirectory; + private bool $beStrictAboutChangesToGlobalState; + private bool $beStrictAboutOutputDuringTests; + private bool $beStrictAboutTestsThatDoNotTestAnything; + private bool $beStrictAboutCoverageMetadata; + private bool $enforceTimeLimit; + private int $defaultTimeLimit; + private int $timeoutForSmallTests; + private int $timeoutForMediumTests; + private int $timeoutForLargeTests; + private ?string $defaultTestSuite; + private int $executionOrder; + private bool $resolveDependencies; + private bool $defectsFirst; + private bool $backupGlobals; + private bool $backupStaticProperties; + private bool $testdoxPrinter; + private bool $testdoxPrinterSummary; + private bool $controlGarbageCollector; + private int $numberOfTestsBeforeGarbageCollection; + + /** + * @var non-negative-int + */ + private int $shortenArraysForExportThreshold; + + /** + * @param array $bootstrapForTestSuite + * @param ?non-empty-string $extensionsDirectory + * @param non-negative-int $shortenArraysForExportThreshold + */ + public function __construct(?string $cacheDirectory, bool $cacheResult, int|string $columns, string $colors, bool $stderr, bool $displayDetailsOnAllIssues, bool $displayDetailsOnIncompleteTests, bool $displayDetailsOnSkippedTests, bool $displayDetailsOnTestsThatTriggerDeprecations, bool $displayDetailsOnPhpunitDeprecations, bool $displayDetailsOnPhpunitNotices, bool $displayDetailsOnTestsThatTriggerErrors, bool $displayDetailsOnTestsThatTriggerNotices, bool $displayDetailsOnTestsThatTriggerWarnings, bool $reverseDefectList, bool $requireCoverageMetadata, ?string $bootstrap, array $bootstrapForTestSuite, bool $processIsolation, bool $failOnAllIssues, bool $failOnDeprecation, bool $failOnPhpunitDeprecation, bool $failOnPhpunitNotice, bool $failOnPhpunitWarning, bool $failOnEmptyTestSuite, bool $failOnIncomplete, bool $failOnNotice, bool $failOnRisky, bool $failOnSkipped, bool $failOnWarning, bool $stopOnDefect, bool $stopOnDeprecation, bool $stopOnError, bool $stopOnFailure, bool $stopOnIncomplete, bool $stopOnNotice, bool $stopOnRisky, bool $stopOnSkipped, bool $stopOnWarning, ?string $extensionsDirectory, bool $beStrictAboutChangesToGlobalState, bool $beStrictAboutOutputDuringTests, bool $beStrictAboutTestsThatDoNotTestAnything, bool $beStrictAboutCoverageMetadata, bool $enforceTimeLimit, int $defaultTimeLimit, int $timeoutForSmallTests, int $timeoutForMediumTests, int $timeoutForLargeTests, ?string $defaultTestSuite, int $executionOrder, bool $resolveDependencies, bool $defectsFirst, bool $backupGlobals, bool $backupStaticProperties, bool $testdoxPrinter, bool $testdoxPrinterSummary, bool $controlGarbageCollector, int $numberOfTestsBeforeGarbageCollection, int $shortenArraysForExportThreshold) + { + $this->cacheDirectory = $cacheDirectory; + $this->cacheResult = $cacheResult; + $this->columns = $columns; + $this->colors = $colors; + $this->stderr = $stderr; + $this->displayDetailsOnAllIssues = $displayDetailsOnAllIssues; + $this->displayDetailsOnIncompleteTests = $displayDetailsOnIncompleteTests; + $this->displayDetailsOnSkippedTests = $displayDetailsOnSkippedTests; + $this->displayDetailsOnTestsThatTriggerDeprecations = $displayDetailsOnTestsThatTriggerDeprecations; + $this->displayDetailsOnPhpunitDeprecations = $displayDetailsOnPhpunitDeprecations; + $this->displayDetailsOnPhpunitNotices = $displayDetailsOnPhpunitNotices; + $this->displayDetailsOnTestsThatTriggerErrors = $displayDetailsOnTestsThatTriggerErrors; + $this->displayDetailsOnTestsThatTriggerNotices = $displayDetailsOnTestsThatTriggerNotices; + $this->displayDetailsOnTestsThatTriggerWarnings = $displayDetailsOnTestsThatTriggerWarnings; + $this->reverseDefectList = $reverseDefectList; + $this->requireCoverageMetadata = $requireCoverageMetadata; + $this->bootstrap = $bootstrap; + $this->bootstrapForTestSuite = $bootstrapForTestSuite; + $this->processIsolation = $processIsolation; + $this->failOnAllIssues = $failOnAllIssues; + $this->failOnDeprecation = $failOnDeprecation; + $this->failOnPhpunitDeprecation = $failOnPhpunitDeprecation; + $this->failOnPhpunitNotice = $failOnPhpunitNotice; + $this->failOnPhpunitWarning = $failOnPhpunitWarning; + $this->failOnEmptyTestSuite = $failOnEmptyTestSuite; + $this->failOnIncomplete = $failOnIncomplete; + $this->failOnNotice = $failOnNotice; + $this->failOnRisky = $failOnRisky; + $this->failOnSkipped = $failOnSkipped; + $this->failOnWarning = $failOnWarning; + $this->stopOnDefect = $stopOnDefect; + $this->stopOnDeprecation = $stopOnDeprecation; + $this->stopOnError = $stopOnError; + $this->stopOnFailure = $stopOnFailure; + $this->stopOnIncomplete = $stopOnIncomplete; + $this->stopOnNotice = $stopOnNotice; + $this->stopOnRisky = $stopOnRisky; + $this->stopOnSkipped = $stopOnSkipped; + $this->stopOnWarning = $stopOnWarning; + $this->extensionsDirectory = $extensionsDirectory; + $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; + $this->beStrictAboutOutputDuringTests = $beStrictAboutOutputDuringTests; + $this->beStrictAboutTestsThatDoNotTestAnything = $beStrictAboutTestsThatDoNotTestAnything; + $this->beStrictAboutCoverageMetadata = $beStrictAboutCoverageMetadata; + $this->enforceTimeLimit = $enforceTimeLimit; + $this->defaultTimeLimit = $defaultTimeLimit; + $this->timeoutForSmallTests = $timeoutForSmallTests; + $this->timeoutForMediumTests = $timeoutForMediumTests; + $this->timeoutForLargeTests = $timeoutForLargeTests; + $this->defaultTestSuite = $defaultTestSuite; + $this->executionOrder = $executionOrder; + $this->resolveDependencies = $resolveDependencies; + $this->defectsFirst = $defectsFirst; + $this->backupGlobals = $backupGlobals; + $this->backupStaticProperties = $backupStaticProperties; + $this->testdoxPrinter = $testdoxPrinter; + $this->testdoxPrinterSummary = $testdoxPrinterSummary; + $this->controlGarbageCollector = $controlGarbageCollector; + $this->numberOfTestsBeforeGarbageCollection = $numberOfTestsBeforeGarbageCollection; + $this->shortenArraysForExportThreshold = $shortenArraysForExportThreshold; + } + + /** + * @phpstan-assert-if-true !null $this->cacheDirectory + */ + public function hasCacheDirectory(): bool + { + return $this->cacheDirectory !== null; + } + + /** + * @throws Exception + */ + public function cacheDirectory(): string + { + if (!$this->hasCacheDirectory()) { + throw new Exception('Cache directory is not configured'); + } + + return $this->cacheDirectory; + } + + public function cacheResult(): bool + { + return $this->cacheResult; + } + + public function columns(): int|string + { + return $this->columns; + } + + public function colors(): string + { + return $this->colors; + } + + public function stderr(): bool + { + return $this->stderr; + } + + public function displayDetailsOnAllIssues(): bool + { + return $this->displayDetailsOnAllIssues; + } + + public function displayDetailsOnIncompleteTests(): bool + { + return $this->displayDetailsOnIncompleteTests; + } + + public function displayDetailsOnSkippedTests(): bool + { + return $this->displayDetailsOnSkippedTests; + } + + public function displayDetailsOnTestsThatTriggerDeprecations(): bool + { + return $this->displayDetailsOnTestsThatTriggerDeprecations; + } + + public function displayDetailsOnPhpunitDeprecations(): bool + { + return $this->displayDetailsOnPhpunitDeprecations; + } + + public function displayDetailsOnPhpunitNotices(): bool + { + return $this->displayDetailsOnPhpunitNotices; + } + + public function displayDetailsOnTestsThatTriggerErrors(): bool + { + return $this->displayDetailsOnTestsThatTriggerErrors; + } + + public function displayDetailsOnTestsThatTriggerNotices(): bool + { + return $this->displayDetailsOnTestsThatTriggerNotices; + } + + public function displayDetailsOnTestsThatTriggerWarnings(): bool + { + return $this->displayDetailsOnTestsThatTriggerWarnings; + } + + public function reverseDefectList(): bool + { + return $this->reverseDefectList; + } + + public function requireCoverageMetadata(): bool + { + return $this->requireCoverageMetadata; + } + + /** + * @phpstan-assert-if-true !null $this->bootstrap + */ + public function hasBootstrap(): bool + { + return $this->bootstrap !== null; + } + + /** + * @throws Exception + */ + public function bootstrap(): string + { + if (!$this->hasBootstrap()) { + throw new Exception('Bootstrap script is not configured'); + } + + return $this->bootstrap; + } + + /** + * @return array + */ + public function bootstrapForTestSuite(): array + { + return $this->bootstrapForTestSuite; + } + + public function processIsolation(): bool + { + return $this->processIsolation; + } + + public function failOnAllIssues(): bool + { + return $this->failOnAllIssues; + } + + public function failOnDeprecation(): bool + { + return $this->failOnDeprecation; + } + + public function failOnPhpunitDeprecation(): bool + { + return $this->failOnPhpunitDeprecation; + } + + public function failOnPhpunitNotice(): bool + { + return $this->failOnPhpunitNotice; + } + + public function failOnPhpunitWarning(): bool + { + return $this->failOnPhpunitWarning; + } + + public function failOnEmptyTestSuite(): bool + { + return $this->failOnEmptyTestSuite; + } + + public function failOnIncomplete(): bool + { + return $this->failOnIncomplete; + } + + public function failOnNotice(): bool + { + return $this->failOnNotice; + } + + public function failOnRisky(): bool + { + return $this->failOnRisky; + } + + public function failOnSkipped(): bool + { + return $this->failOnSkipped; + } + + public function failOnWarning(): bool + { + return $this->failOnWarning; + } + + public function stopOnDefect(): bool + { + return $this->stopOnDefect; + } + + public function stopOnDeprecation(): bool + { + return $this->stopOnDeprecation; + } + + public function stopOnError(): bool + { + return $this->stopOnError; + } + + public function stopOnFailure(): bool + { + return $this->stopOnFailure; + } + + public function stopOnIncomplete(): bool + { + return $this->stopOnIncomplete; + } + + public function stopOnNotice(): bool + { + return $this->stopOnNotice; + } + + public function stopOnRisky(): bool + { + return $this->stopOnRisky; + } + + public function stopOnSkipped(): bool + { + return $this->stopOnSkipped; + } + + public function stopOnWarning(): bool + { + return $this->stopOnWarning; + } + + /** + * @phpstan-assert-if-true !null $this->extensionsDirectory + */ + public function hasExtensionsDirectory(): bool + { + return $this->extensionsDirectory !== null; + } + + /** + * @throws Exception + * + * @return non-empty-string + */ + public function extensionsDirectory(): string + { + if (!$this->hasExtensionsDirectory()) { + throw new Exception('Extensions directory is not configured'); + } + + return $this->extensionsDirectory; + } + + public function beStrictAboutChangesToGlobalState(): bool + { + return $this->beStrictAboutChangesToGlobalState; + } + + public function beStrictAboutOutputDuringTests(): bool + { + return $this->beStrictAboutOutputDuringTests; + } + + public function beStrictAboutTestsThatDoNotTestAnything(): bool + { + return $this->beStrictAboutTestsThatDoNotTestAnything; + } + + public function beStrictAboutCoverageMetadata(): bool + { + return $this->beStrictAboutCoverageMetadata; + } + + public function enforceTimeLimit(): bool + { + return $this->enforceTimeLimit; + } + + public function defaultTimeLimit(): int + { + return $this->defaultTimeLimit; + } + + public function timeoutForSmallTests(): int + { + return $this->timeoutForSmallTests; + } + + public function timeoutForMediumTests(): int + { + return $this->timeoutForMediumTests; + } + + public function timeoutForLargeTests(): int + { + return $this->timeoutForLargeTests; + } + + /** + * @phpstan-assert-if-true !null $this->defaultTestSuite + */ + public function hasDefaultTestSuite(): bool + { + return $this->defaultTestSuite !== null; + } + + /** + * @throws Exception + */ + public function defaultTestSuite(): string + { + if (!$this->hasDefaultTestSuite()) { + throw new Exception('Default test suite is not configured'); + } + + return $this->defaultTestSuite; + } + + public function executionOrder(): int + { + return $this->executionOrder; + } + + public function resolveDependencies(): bool + { + return $this->resolveDependencies; + } + + public function defectsFirst(): bool + { + return $this->defectsFirst; + } + + public function backupGlobals(): bool + { + return $this->backupGlobals; + } + + public function backupStaticProperties(): bool + { + return $this->backupStaticProperties; + } + + public function testdoxPrinter(): bool + { + return $this->testdoxPrinter; + } + + public function testdoxPrinterSummary(): bool + { + return $this->testdoxPrinterSummary; + } + + public function controlGarbageCollector(): bool + { + return $this->controlGarbageCollector; + } + + public function numberOfTestsBeforeGarbageCollection(): int + { + return $this->numberOfTestsBeforeGarbageCollection; + } + + /** + * @return non-negative-int + */ + public function shortenArraysForExportThreshold(): int + { + return $this->shortenArraysForExportThreshold; + } +} diff --git a/src/TextUI/Configuration/Xml/SchemaDetector/FailedSchemaDetectionResult.php b/src/TextUI/Configuration/Xml/SchemaDetector/FailedSchemaDetectionResult.php new file mode 100644 index 00000000000..5bd282c8c3b --- /dev/null +++ b/src/TextUI/Configuration/Xml/SchemaDetector/FailedSchemaDetectionResult.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class FailedSchemaDetectionResult extends SchemaDetectionResult +{ +} diff --git a/src/TextUI/Configuration/Xml/SchemaDetector/SchemaDetectionResult.php b/src/TextUI/Configuration/Xml/SchemaDetector/SchemaDetectionResult.php new file mode 100644 index 00000000000..aa855b0cb06 --- /dev/null +++ b/src/TextUI/Configuration/Xml/SchemaDetector/SchemaDetectionResult.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use PHPUnit\Util\Xml\XmlException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +abstract readonly class SchemaDetectionResult +{ + /** + * @phpstan-assert-if-true SuccessfulSchemaDetectionResult $this + */ + public function detected(): bool + { + return false; + } + + /** + * @throws XmlException + */ + public function version(): string + { + throw new XmlException('No supported schema was detected'); + } +} diff --git a/src/TextUI/Configuration/Xml/SchemaDetector/SchemaDetector.php b/src/TextUI/Configuration/Xml/SchemaDetector/SchemaDetector.php new file mode 100644 index 00000000000..5f55f5f2016 --- /dev/null +++ b/src/TextUI/Configuration/Xml/SchemaDetector/SchemaDetector.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use PHPUnit\Util\Xml\Loader; +use PHPUnit\Util\Xml\XmlException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class SchemaDetector +{ + /** + * @throws XmlException + */ + public function detect(string $filename): SchemaDetectionResult + { + $document = (new Loader)->loadFile($filename); + + $schemaFinder = new SchemaFinder; + + foreach ($schemaFinder->available() as $candidate) { + $schema = (new SchemaFinder)->find($candidate); + + if (!(new Validator)->validate($document, $schema)->hasValidationErrors()) { + return new SuccessfulSchemaDetectionResult($candidate); + } + } + + return new FailedSchemaDetectionResult; + } +} diff --git a/src/TextUI/Configuration/Xml/SchemaDetector/SuccessfulSchemaDetectionResult.php b/src/TextUI/Configuration/Xml/SchemaDetector/SuccessfulSchemaDetectionResult.php new file mode 100644 index 00000000000..72a64c193f4 --- /dev/null +++ b/src/TextUI/Configuration/Xml/SchemaDetector/SuccessfulSchemaDetectionResult.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class SuccessfulSchemaDetectionResult extends SchemaDetectionResult +{ + /** + * @var non-empty-string + */ + private string $version; + + /** + * @param non-empty-string $version + */ + public function __construct(string $version) + { + $this->version = $version; + } + + public function detected(): bool + { + return true; + } + + /** + * @throws void + * + * @return non-empty-string + */ + public function version(): string + { + return $this->version; + } +} diff --git a/src/TextUI/Configuration/Xml/SchemaFinder.php b/src/TextUI/Configuration/Xml/SchemaFinder.php new file mode 100644 index 00000000000..39d25cfadf5 --- /dev/null +++ b/src/TextUI/Configuration/Xml/SchemaFinder.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use function defined; +use function is_file; +use function rsort; +use function sprintf; +use DirectoryIterator; +use PHPUnit\Runner\Version; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class SchemaFinder +{ + /** + * @return non-empty-list + */ + public function available(): array + { + $result = [Version::series()]; + + foreach ((new DirectoryIterator($this->path() . 'schema')) as $file) { + if ($file->isDot()) { + continue; + } + + $version = $file->getBasename('.xsd'); + + assert($version !== ''); + + $result[] = $version; + } + + rsort($result); + + return $result; + } + + /** + * @throws CannotFindSchemaException + */ + public function find(string $version): string + { + if ($version === Version::series()) { + $filename = $this->path() . 'phpunit.xsd'; + } else { + $filename = $this->path() . 'schema/' . $version . '.xsd'; + } + + if (!is_file($filename)) { + throw new CannotFindSchemaException( + sprintf( + 'Schema for PHPUnit %s is not available', + $version, + ), + ); + } + + return $filename; + } + + private function path(): string + { + if (defined('__PHPUNIT_PHAR_ROOT__')) { + return __PHPUNIT_PHAR_ROOT__ . '/'; + } + + return __DIR__ . '/../../../../'; + } +} diff --git a/src/TextUI/Configuration/Xml/TestSuiteMapper.php b/src/TextUI/Configuration/Xml/TestSuiteMapper.php new file mode 100644 index 00000000000..66bd611c1c6 --- /dev/null +++ b/src/TextUI/Configuration/Xml/TestSuiteMapper.php @@ -0,0 +1,150 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use const PHP_VERSION; +use function in_array; +use function is_dir; +use function is_file; +use function sprintf; +use function str_contains; +use function version_compare; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Framework\Exception as FrameworkException; +use PHPUnit\Framework\TestSuite as TestSuiteObject; +use PHPUnit\TextUI\Configuration\TestSuiteCollection; +use PHPUnit\TextUI\RuntimeException; +use PHPUnit\TextUI\TestDirectoryNotFoundException; +use PHPUnit\TextUI\TestFileNotFoundException; +use SebastianBergmann\FileIterator\Facade; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteMapper +{ + /** + * @param non-empty-string $xmlConfigurationFile + * @param list $includeTestSuites + * @param list $excludeTestSuites + * + * @throws RuntimeException + * @throws TestDirectoryNotFoundException + * @throws TestFileNotFoundException + */ + public function map(string $xmlConfigurationFile, TestSuiteCollection $configuredTestSuites, array $includeTestSuites, array $excludeTestSuites): TestSuiteObject + { + try { + $result = TestSuiteObject::empty($xmlConfigurationFile); + $processed = []; + + foreach ($configuredTestSuites as $configuredTestSuite) { + if ($includeTestSuites !== [] && !in_array($configuredTestSuite->name(), $includeTestSuites, true)) { + continue; + } + + if ($excludeTestSuites !== [] && in_array($configuredTestSuite->name(), $excludeTestSuites, true)) { + continue; + } + + $testSuiteName = $configuredTestSuite->name(); + $exclude = []; + + foreach ($configuredTestSuite->exclude()->asArray() as $file) { + $exclude[] = $file->path(); + } + + $testSuite = TestSuiteObject::empty($configuredTestSuite->name()); + $empty = true; + + foreach ($configuredTestSuite->directories() as $directory) { + if (!str_contains($directory->path(), '*') && !is_dir($directory->path())) { + throw new TestDirectoryNotFoundException($directory->path()); + } + + if (!version_compare(PHP_VERSION, $directory->phpVersion(), $directory->phpVersionOperator()->asString())) { + continue; + } + + $files = (new Facade)->getFilesAsArray( + $directory->path(), + $directory->suffix(), + $directory->prefix(), + $exclude, + ); + + $groups = $directory->groups(); + + foreach ($files as $file) { + if (isset($processed[$file])) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Cannot add file %s to test suite "%s" as it was already added to test suite "%s"', + $file, + $testSuiteName, + $processed[$file], + ), + ); + + continue; + } + + $processed[$file] = $testSuiteName; + $empty = false; + + $testSuite->addTestFile($file, $groups); + } + } + + foreach ($configuredTestSuite->files() as $file) { + if (!is_file($file->path())) { + throw new TestFileNotFoundException($file->path()); + } + + if (!version_compare(PHP_VERSION, $file->phpVersion(), $file->phpVersionOperator()->asString())) { + continue; + } + + if (isset($processed[$file->path()])) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Cannot add file %s to test suite "%s" as it was already added to test suite "%s"', + $file->path(), + $testSuiteName, + $processed[$file->path()], + ), + ); + + continue; + } + + $processed[$file->path()] = $testSuiteName; + $empty = false; + + $testSuite->addTestFile($file->path(), $file->groups()); + } + + if (!$empty) { + $result->addTest($testSuite); + } + } + + return $result; + } catch (FrameworkException $e) { + throw new RuntimeException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + } +} diff --git a/src/TextUI/Configuration/Xml/Validator/ValidationResult.php b/src/TextUI/Configuration/Xml/Validator/ValidationResult.php new file mode 100644 index 00000000000..95fe473d686 --- /dev/null +++ b/src/TextUI/Configuration/Xml/Validator/ValidationResult.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use const PHP_EOL; +use function sprintf; +use function trim; +use LibXMLError; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class ValidationResult +{ + /** + * @var array> + */ + private array $validationErrors; + + /** + * @param array $errors + */ + public static function fromArray(array $errors): self + { + $validationErrors = []; + + foreach ($errors as $error) { + if (!isset($validationErrors[$error->line])) { + $validationErrors[$error->line] = []; + } + + $validationErrors[$error->line][] = trim($error->message); + } + + return new self($validationErrors); + } + + /** + * @param array> $validationErrors + */ + private function __construct(array $validationErrors) + { + $this->validationErrors = $validationErrors; + } + + public function hasValidationErrors(): bool + { + return $this->validationErrors !== []; + } + + public function asString(): string + { + $buffer = ''; + + foreach ($this->validationErrors as $line => $validationErrorsOnLine) { + $buffer .= sprintf(PHP_EOL . ' Line %d:' . PHP_EOL, $line); + + foreach ($validationErrorsOnLine as $validationError) { + $buffer .= sprintf(' - %s' . PHP_EOL, $validationError); + } + } + + return $buffer; + } +} diff --git a/src/TextUI/Configuration/Xml/Validator/Validator.php b/src/TextUI/Configuration/Xml/Validator/Validator.php new file mode 100644 index 00000000000..cc3a93dd29d --- /dev/null +++ b/src/TextUI/Configuration/Xml/Validator/Validator.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use function file_get_contents; +use function libxml_clear_errors; +use function libxml_get_errors; +use function libxml_use_internal_errors; +use DOMDocument; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Validator +{ + public function validate(DOMDocument $document, string $xsdFilename): ValidationResult + { + $buffer = file_get_contents($xsdFilename); + + assert($buffer !== false); + + $originalErrorHandling = libxml_use_internal_errors(true); + + $document->schemaValidateSource($buffer); + + $errors = libxml_get_errors(); + libxml_clear_errors(); + libxml_use_internal_errors($originalErrorHandling); + + return ValidationResult::fromArray($errors); + } +} diff --git a/src/TextUI/DefaultResultPrinter.php b/src/TextUI/DefaultResultPrinter.php deleted file mode 100644 index e26f7f6a8d0..00000000000 --- a/src/TextUI/DefaultResultPrinter.php +++ /dev/null @@ -1,591 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI; - -use const PHP_EOL; -use function array_map; -use function array_reverse; -use function count; -use function floor; -use function implode; -use function in_array; -use function is_int; -use function max; -use function preg_split; -use function sprintf; -use function str_pad; -use function str_repeat; -use function strlen; -use function vsprintf; -use PHPUnit\Framework\AssertionFailedError; -use PHPUnit\Framework\Exception; -use PHPUnit\Framework\InvalidArgumentException; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestFailure; -use PHPUnit\Framework\TestResult; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Framework\Warning; -use PHPUnit\Runner\PhptTestCase; -use PHPUnit\Util\Color; -use PHPUnit\Util\Printer; -use SebastianBergmann\Environment\Console; -use SebastianBergmann\Timer\ResourceUsageFormatter; -use SebastianBergmann\Timer\Timer; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -class DefaultResultPrinter extends Printer implements ResultPrinter -{ - public const EVENT_TEST_START = 0; - - public const EVENT_TEST_END = 1; - - public const EVENT_TESTSUITE_START = 2; - - public const EVENT_TESTSUITE_END = 3; - - public const COLOR_NEVER = 'never'; - - public const COLOR_AUTO = 'auto'; - - public const COLOR_ALWAYS = 'always'; - - public const COLOR_DEFAULT = self::COLOR_NEVER; - - private const AVAILABLE_COLORS = [self::COLOR_NEVER, self::COLOR_AUTO, self::COLOR_ALWAYS]; - - /** - * @var int - */ - protected $column = 0; - - /** - * @var int - */ - protected $maxColumn; - - /** - * @var bool - */ - protected $lastTestFailed = false; - - /** - * @var int - */ - protected $numAssertions = 0; - - /** - * @var int - */ - protected $numTests = -1; - - /** - * @var int - */ - protected $numTestsRun = 0; - - /** - * @var int - */ - protected $numTestsWidth; - - /** - * @var bool - */ - protected $colors = false; - - /** - * @var bool - */ - protected $debug = false; - - /** - * @var bool - */ - protected $verbose = false; - - /** - * @var int - */ - private $numberOfColumns; - - /** - * @var bool - */ - private $reverse; - - /** - * @var bool - */ - private $defectListPrinted = false; - - /** - * @var Timer - */ - private $timer; - - /** - * Constructor. - * - * @param null|resource|string $out - * @param int|string $numberOfColumns - * - * @throws Exception - */ - public function __construct($out = null, bool $verbose = false, string $colors = self::COLOR_DEFAULT, bool $debug = false, $numberOfColumns = 80, bool $reverse = false) - { - parent::__construct($out); - - if (!in_array($colors, self::AVAILABLE_COLORS, true)) { - throw InvalidArgumentException::create( - 3, - vsprintf('value from "%s", "%s" or "%s"', self::AVAILABLE_COLORS) - ); - } - - if (!is_int($numberOfColumns) && $numberOfColumns !== 'max') { - throw InvalidArgumentException::create(5, 'integer or "max"'); - } - - $console = new Console; - $maxNumberOfColumns = $console->getNumberOfColumns(); - - if ($numberOfColumns === 'max' || ($numberOfColumns !== 80 && $numberOfColumns > $maxNumberOfColumns)) { - $numberOfColumns = $maxNumberOfColumns; - } - - $this->numberOfColumns = $numberOfColumns; - $this->verbose = $verbose; - $this->debug = $debug; - $this->reverse = $reverse; - - if ($colors === self::COLOR_AUTO && $console->hasColorSupport()) { - $this->colors = true; - } else { - $this->colors = (self::COLOR_ALWAYS === $colors); - } - - $this->timer = new Timer; - - $this->timer->start(); - } - - public function printResult(TestResult $result): void - { - $this->printHeader($result); - $this->printErrors($result); - $this->printWarnings($result); - $this->printFailures($result); - $this->printRisky($result); - - if ($this->verbose) { - $this->printIncompletes($result); - $this->printSkipped($result); - } - - $this->printFooter($result); - } - - /** - * An error occurred. - */ - public function addError(Test $test, Throwable $t, float $time): void - { - $this->writeProgressWithColor('fg-red, bold', 'E'); - $this->lastTestFailed = true; - } - - /** - * A failure occurred. - */ - public function addFailure(Test $test, AssertionFailedError $e, float $time): void - { - $this->writeProgressWithColor('bg-red, fg-white', 'F'); - $this->lastTestFailed = true; - } - - /** - * A warning occurred. - */ - public function addWarning(Test $test, Warning $e, float $time): void - { - $this->writeProgressWithColor('fg-yellow, bold', 'W'); - $this->lastTestFailed = true; - } - - /** - * Incomplete test. - */ - public function addIncompleteTest(Test $test, Throwable $t, float $time): void - { - $this->writeProgressWithColor('fg-yellow, bold', 'I'); - $this->lastTestFailed = true; - } - - /** - * Risky test. - */ - public function addRiskyTest(Test $test, Throwable $t, float $time): void - { - $this->writeProgressWithColor('fg-yellow, bold', 'R'); - $this->lastTestFailed = true; - } - - /** - * Skipped test. - */ - public function addSkippedTest(Test $test, Throwable $t, float $time): void - { - $this->writeProgressWithColor('fg-cyan, bold', 'S'); - $this->lastTestFailed = true; - } - - /** - * A testsuite started. - */ - public function startTestSuite(TestSuite $suite): void - { - if ($this->numTests == -1) { - $this->numTests = count($suite); - $this->numTestsWidth = strlen((string) $this->numTests); - $this->maxColumn = $this->numberOfColumns - strlen(' / (XXX%)') - (2 * $this->numTestsWidth); - } - } - - /** - * A testsuite ended. - */ - public function endTestSuite(TestSuite $suite): void - { - } - - /** - * A test started. - */ - public function startTest(Test $test): void - { - if ($this->debug) { - $this->write( - sprintf( - "Test '%s' started\n", - \PHPUnit\Util\Test::describeAsString($test) - ) - ); - } - } - - /** - * A test ended. - */ - public function endTest(Test $test, float $time): void - { - if ($this->debug) { - $this->write( - sprintf( - "Test '%s' ended\n", - \PHPUnit\Util\Test::describeAsString($test) - ) - ); - } - - if (!$this->lastTestFailed) { - $this->writeProgress('.'); - } - - if ($test instanceof TestCase) { - $this->numAssertions += $test->getNumAssertions(); - } elseif ($test instanceof PhptTestCase) { - $this->numAssertions++; - } - - $this->lastTestFailed = false; - - if ($test instanceof TestCase && !$test->hasExpectationOnOutput()) { - $this->write($test->getActualOutput()); - } - } - - protected function printDefects(array $defects, string $type): void - { - $count = count($defects); - - if ($count == 0) { - return; - } - - if ($this->defectListPrinted) { - $this->write("\n--\n\n"); - } - - $this->write( - sprintf( - "There %s %d %s%s:\n", - ($count == 1) ? 'was' : 'were', - $count, - $type, - ($count == 1) ? '' : 's' - ) - ); - - $i = 1; - - if ($this->reverse) { - $defects = array_reverse($defects); - } - - foreach ($defects as $defect) { - $this->printDefect($defect, $i++); - } - - $this->defectListPrinted = true; - } - - protected function printDefect(TestFailure $defect, int $count): void - { - $this->printDefectHeader($defect, $count); - $this->printDefectTrace($defect); - } - - protected function printDefectHeader(TestFailure $defect, int $count): void - { - $this->write( - sprintf( - "\n%d) %s\n", - $count, - $defect->getTestName() - ) - ); - } - - protected function printDefectTrace(TestFailure $defect): void - { - $e = $defect->thrownException(); - $this->write((string) $e); - - while ($e = $e->getPrevious()) { - $this->write("\nCaused by\n" . $e); - } - } - - protected function printErrors(TestResult $result): void - { - $this->printDefects($result->errors(), 'error'); - } - - protected function printFailures(TestResult $result): void - { - $this->printDefects($result->failures(), 'failure'); - } - - protected function printWarnings(TestResult $result): void - { - $this->printDefects($result->warnings(), 'warning'); - } - - protected function printIncompletes(TestResult $result): void - { - $this->printDefects($result->notImplemented(), 'incomplete test'); - } - - protected function printRisky(TestResult $result): void - { - $this->printDefects($result->risky(), 'risky test'); - } - - protected function printSkipped(TestResult $result): void - { - $this->printDefects($result->skipped(), 'skipped test'); - } - - protected function printHeader(TestResult $result): void - { - if (count($result) > 0) { - $this->write(PHP_EOL . PHP_EOL . (new ResourceUsageFormatter)->resourceUsage($this->timer->stop()) . PHP_EOL . PHP_EOL); - } - } - - protected function printFooter(TestResult $result): void - { - if (count($result) === 0) { - $this->writeWithColor( - 'fg-black, bg-yellow', - 'No tests executed!' - ); - - return; - } - - if ($result->wasSuccessfulAndNoTestIsRiskyOrSkippedOrIncomplete()) { - $this->writeWithColor( - 'fg-black, bg-green', - sprintf( - 'OK (%d test%s, %d assertion%s)', - count($result), - (count($result) === 1) ? '' : 's', - $this->numAssertions, - ($this->numAssertions === 1) ? '' : 's' - ) - ); - - return; - } - - $color = 'fg-black, bg-yellow'; - - if ($result->wasSuccessful()) { - if ($this->verbose || !$result->allHarmless()) { - $this->write("\n"); - } - - $this->writeWithColor( - $color, - 'OK, but incomplete, skipped, or risky tests!' - ); - } else { - $this->write("\n"); - - if ($result->errorCount()) { - $color = 'fg-white, bg-red'; - - $this->writeWithColor( - $color, - 'ERRORS!' - ); - } elseif ($result->failureCount()) { - $color = 'fg-white, bg-red'; - - $this->writeWithColor( - $color, - 'FAILURES!' - ); - } elseif ($result->warningCount()) { - $color = 'fg-black, bg-yellow'; - - $this->writeWithColor( - $color, - 'WARNINGS!' - ); - } - } - - $this->writeCountString(count($result), 'Tests', $color, true); - $this->writeCountString($this->numAssertions, 'Assertions', $color, true); - $this->writeCountString($result->errorCount(), 'Errors', $color); - $this->writeCountString($result->failureCount(), 'Failures', $color); - $this->writeCountString($result->warningCount(), 'Warnings', $color); - $this->writeCountString($result->skippedCount(), 'Skipped', $color); - $this->writeCountString($result->notImplementedCount(), 'Incomplete', $color); - $this->writeCountString($result->riskyCount(), 'Risky', $color); - $this->writeWithColor($color, '.'); - } - - protected function writeProgress(string $progress): void - { - if ($this->debug) { - return; - } - - $this->write($progress); - $this->column++; - $this->numTestsRun++; - - if ($this->column == $this->maxColumn || $this->numTestsRun == $this->numTests) { - if ($this->numTestsRun == $this->numTests) { - $this->write(str_repeat(' ', $this->maxColumn - $this->column)); - } - - $this->write( - sprintf( - ' %' . $this->numTestsWidth . 'd / %' . - $this->numTestsWidth . 'd (%3s%%)', - $this->numTestsRun, - $this->numTests, - floor(($this->numTestsRun / $this->numTests) * 100) - ) - ); - - if ($this->column == $this->maxColumn) { - $this->writeNewLine(); - } - } - } - - protected function writeNewLine(): void - { - $this->column = 0; - $this->write("\n"); - } - - /** - * Formats a buffer with a specified ANSI color sequence if colors are - * enabled. - */ - protected function colorizeTextBox(string $color, string $buffer): string - { - if (!$this->colors) { - return $buffer; - } - - $lines = preg_split('/\r\n|\r|\n/', $buffer); - $padding = max(array_map('\strlen', $lines)); - - $styledLines = []; - - foreach ($lines as $line) { - $styledLines[] = Color::colorize($color, str_pad($line, $padding)); - } - - return implode(PHP_EOL, $styledLines); - } - - /** - * Writes a buffer out with a color sequence if colors are enabled. - */ - protected function writeWithColor(string $color, string $buffer, bool $lf = true): void - { - $this->write($this->colorizeTextBox($color, $buffer)); - - if ($lf) { - $this->write(PHP_EOL); - } - } - - /** - * Writes progress with a color sequence if colors are enabled. - */ - protected function writeProgressWithColor(string $color, string $buffer): void - { - $buffer = $this->colorizeTextBox($color, $buffer); - $this->writeProgress($buffer); - } - - private function writeCountString(int $count, string $name, string $color, bool $always = false): void - { - static $first = true; - - if ($always || $count > 0) { - $this->writeWithColor( - $color, - sprintf( - '%s%s: %d', - !$first ? ', ' : '', - $name, - $count - ), - false - ); - - $first = false; - } - } -} diff --git a/src/TextUI/Exception.php b/src/TextUI/Exception.php deleted file mode 100644 index 7c261e58e7f..00000000000 --- a/src/TextUI/Exception.php +++ /dev/null @@ -1,19 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI; - -use RuntimeException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Exception extends RuntimeException implements \PHPUnit\Exception -{ -} diff --git a/src/TextUI/Exception/CannotOpenSocketException.php b/src/TextUI/Exception/CannotOpenSocketException.php new file mode 100644 index 00000000000..519d1378568 --- /dev/null +++ b/src/TextUI/Exception/CannotOpenSocketException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CannotOpenSocketException extends RuntimeException implements Exception +{ + public function __construct(string $hostname, int $port) + { + parent::__construct( + sprintf( + 'Cannot open socket %s:%d', + $hostname, + $port, + ), + ); + } +} diff --git a/src/TextUI/Exception/Exception.php b/src/TextUI/Exception/Exception.php new file mode 100644 index 00000000000..6b370ca0760 --- /dev/null +++ b/src/TextUI/Exception/Exception.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface Exception extends Throwable +{ +} diff --git a/src/TextUI/Exception/InvalidSocketException.php b/src/TextUI/Exception/InvalidSocketException.php new file mode 100644 index 00000000000..441afd2a1ed --- /dev/null +++ b/src/TextUI/Exception/InvalidSocketException.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidSocketException extends RuntimeException implements Exception +{ + public function __construct(string $socket) + { + parent::__construct( + sprintf( + '"%s" does not match "socket://hostname:port" format', + $socket, + ), + ); + } +} diff --git a/src/TextUI/Exception/RuntimeException.php b/src/TextUI/Exception/RuntimeException.php new file mode 100644 index 00000000000..875a0487c8c --- /dev/null +++ b/src/TextUI/Exception/RuntimeException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class RuntimeException extends \RuntimeException implements Exception +{ +} diff --git a/src/TextUI/Exception/TestDirectoryNotFoundException.php b/src/TextUI/Exception/TestDirectoryNotFoundException.php new file mode 100644 index 00000000000..9b35390cdab --- /dev/null +++ b/src/TextUI/Exception/TestDirectoryNotFoundException.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestDirectoryNotFoundException extends RuntimeException implements Exception +{ + public function __construct(string $path) + { + parent::__construct( + sprintf( + 'Test directory "%s" not found', + $path, + ), + ); + } +} diff --git a/src/TextUI/Exception/TestFileNotFoundException.php b/src/TextUI/Exception/TestFileNotFoundException.php new file mode 100644 index 00000000000..46c9df80671 --- /dev/null +++ b/src/TextUI/Exception/TestFileNotFoundException.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestFileNotFoundException extends RuntimeException implements Exception +{ + public function __construct(string $path) + { + parent::__construct( + sprintf( + 'Test file "%s" not found', + $path, + ), + ); + } +} diff --git a/src/TextUI/Help.php b/src/TextUI/Help.php index db0561c19b2..8a4f802abdf 100644 --- a/src/TextUI/Help.php +++ b/src/TextUI/Help.php @@ -11,6 +11,7 @@ use const PHP_EOL; use function count; +use function defined; use function explode; use function max; use function preg_replace_callback; @@ -22,137 +23,16 @@ use SebastianBergmann\Environment\Console; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class Help { - private const LEFT_MARGIN = ' '; - - private const HELP_TEXT = [ - 'Usage' => [ - ['text' => 'phpunit [options] UnitTest.php'], - ['text' => 'phpunit [options] '], - ], - 'Code Coverage Options' => [ - ['arg' => '--coverage-clover ', 'desc' => 'Generate code coverage report in Clover XML format'], - ['arg' => '--coverage-crap4j ', 'desc' => 'Generate code coverage report in Crap4J XML format'], - ['arg' => '--coverage-html ', 'desc' => 'Generate code coverage report in HTML format'], - ['arg' => '--coverage-php ', 'desc' => 'Export PHP_CodeCoverage object to file'], - ['arg' => '--coverage-text=', 'desc' => 'Generate code coverage report in text format [default: standard output]'], - ['arg' => '--coverage-xml ', 'desc' => 'Generate code coverage report in PHPUnit XML format'], - ['arg' => '--coverage-filter ', 'desc' => 'Include in code coverage analysis'], - ['arg' => '--disable-coverage-ignore', 'desc' => 'Disable annotations for ignoring code coverage'], - ['arg' => '--no-coverage', 'desc' => 'Ignore code coverage configuration'], - ], - - 'Logging Options' => [ - ['arg' => '--log-junit ', 'desc' => 'Log test execution in JUnit XML format to file'], - ['arg' => '--log-teamcity ', 'desc' => 'Log test execution in TeamCity format to file'], - ['arg' => '--testdox-html ', 'desc' => 'Write agile documentation in HTML format to file'], - ['arg' => '--testdox-text ', 'desc' => 'Write agile documentation in Text format to file'], - ['arg' => '--testdox-xml ', 'desc' => 'Write agile documentation in XML format to file'], - ['arg' => '--reverse-list', 'desc' => 'Print defects in reverse order'], - ], - - 'Test Selection Options' => [ - ['arg' => '--filter ', 'desc' => 'Filter which tests to run'], - ['arg' => '--testsuite ', 'desc' => 'Filter which testsuite to run'], - ['arg' => '--group ', 'desc' => 'Only runs tests from the specified group(s)'], - ['arg' => '--exclude-group ', 'desc' => 'Exclude tests from the specified group(s)'], - ['arg' => '--list-groups', 'desc' => 'List available test groups'], - ['arg' => '--list-suites', 'desc' => 'List available test suites'], - ['arg' => '--list-tests', 'desc' => 'List available tests'], - ['arg' => '--list-tests-xml ', 'desc' => 'List available tests in XML format'], - ['arg' => '--test-suffix ', 'desc' => 'Only search for test in files with specified suffix(es). Default: Test.php,.phpt'], - ], - - 'Test Execution Options' => [ - ['arg' => '--dont-report-useless-tests', 'desc' => 'Do not report tests that do not test anything'], - ['arg' => '--strict-coverage', 'desc' => 'Be strict about @covers annotation usage'], - ['arg' => '--strict-global-state', 'desc' => 'Be strict about changes to global state'], - ['arg' => '--disallow-test-output', 'desc' => 'Be strict about output during tests'], - ['arg' => '--disallow-resource-usage', 'desc' => 'Be strict about resource usage during small tests'], - ['arg' => '--enforce-time-limit', 'desc' => 'Enforce time limit based on test size'], - ['arg' => '--default-time-limit=', 'desc' => 'Timeout in seconds for tests without @small, @medium or @large'], - ['arg' => '--disallow-todo-tests', 'desc' => 'Disallow @todo-annotated tests'], - ['spacer' => ''], - - ['arg' => '--process-isolation', 'desc' => 'Run each test in a separate PHP process'], - ['arg' => '--globals-backup', 'desc' => 'Backup and restore $GLOBALS for each test'], - ['arg' => '--static-backup', 'desc' => 'Backup and restore static attributes for each test'], - ['spacer' => ''], - - ['arg' => '--colors=', 'desc' => 'Use colors in output ("never", "auto" or "always")'], - ['arg' => '--columns ', 'desc' => 'Number of columns to use for progress output'], - ['arg' => '--columns max', 'desc' => 'Use maximum number of columns for progress output'], - ['arg' => '--stderr', 'desc' => 'Write to STDERR instead of STDOUT'], - ['arg' => '--stop-on-defect', 'desc' => 'Stop execution upon first not-passed test'], - ['arg' => '--stop-on-error', 'desc' => 'Stop execution upon first error'], - ['arg' => '--stop-on-failure', 'desc' => 'Stop execution upon first error or failure'], - ['arg' => '--stop-on-warning', 'desc' => 'Stop execution upon first warning'], - ['arg' => '--stop-on-risky', 'desc' => 'Stop execution upon first risky test'], - ['arg' => '--stop-on-skipped', 'desc' => 'Stop execution upon first skipped test'], - ['arg' => '--stop-on-incomplete', 'desc' => 'Stop execution upon first incomplete test'], - ['arg' => '--fail-on-incomplete', 'desc' => 'Treat incomplete tests as failures'], - ['arg' => '--fail-on-risky', 'desc' => 'Treat risky tests as failures'], - ['arg' => '--fail-on-skipped', 'desc' => 'Treat skipped tests as failures'], - ['arg' => '--fail-on-warning', 'desc' => 'Treat tests with warnings as failures'], - ['arg' => '-v|--verbose', 'desc' => 'Output more verbose information'], - ['arg' => '--debug', 'desc' => 'Display debugging information'], - ['spacer' => ''], - - ['arg' => '--repeat ', 'desc' => 'Runs the test(s) repeatedly'], - ['arg' => '--teamcity', 'desc' => 'Report test execution progress in TeamCity format'], - ['arg' => '--testdox', 'desc' => 'Report test execution progress in TestDox format'], - ['arg' => '--testdox-group', 'desc' => 'Only include tests from the specified group(s)'], - ['arg' => '--testdox-exclude-group', 'desc' => 'Exclude tests from the specified group(s)'], - ['arg' => '--no-interaction', 'desc' => 'Disable TestDox progress animation'], - ['arg' => '--printer ', 'desc' => 'TestListener implementation to use'], - ['spacer' => ''], - - ['arg' => '--order-by=', 'desc' => 'Run tests in order: default|defects|duration|no-depends|random|reverse|size'], - ['arg' => '--random-order-seed=', 'desc' => 'Use a specific random seed for random order'], - ['arg' => '--cache-result', 'desc' => 'Write test results to cache file'], - ['arg' => '--do-not-cache-result', 'desc' => 'Do not write test results to cache file'], - ], - - 'Configuration Options' => [ - ['arg' => '--prepend ', 'desc' => 'A PHP script that is included as early as possible'], - ['arg' => '--bootstrap ', 'desc' => 'A PHP script that is included before the tests run'], - ['arg' => '-c|--configuration ', 'desc' => 'Read configuration from XML file'], - ['arg' => '--no-configuration', 'desc' => 'Ignore default configuration file (phpunit.xml)'], - ['arg' => '--no-logging', 'desc' => 'Ignore logging configuration'], - ['arg' => '--extensions ', 'desc' => 'A comma separated list of PHPUnit extensions to load'], - ['arg' => '--no-extensions', 'desc' => 'Do not load PHPUnit extensions'], - ['arg' => '--include-path ', 'desc' => 'Prepend PHP\'s include_path with given path(s)'], - ['arg' => '-d ', 'desc' => 'Sets a php.ini value'], - ['arg' => '--generate-configuration', 'desc' => 'Generate configuration file with suggested settings'], - ['arg' => '--cache-result-file=', 'desc' => 'Specify result cache path and filename'], - ], - - 'Miscellaneous Options' => [ - ['arg' => '-h|--help', 'desc' => 'Prints this usage information'], - ['arg' => '--version', 'desc' => 'Prints the version and exits'], - ['arg' => '--atleast-version ', 'desc' => 'Checks that version is greater than min and exits'], - ['arg' => '--check-version', 'desc' => 'Check whether PHPUnit is the latest version'], - ], - - ]; - - /** - * @var int Number of columns required to write the longest option name to the console - */ - private $maxArgLength = 0; - - /** - * @var int Number of columns left for the description field after padding and option - */ - private $maxDescLength; - - /** - * @var bool Use color highlights for sections, options and parameters - */ - private $hasColor = false; + private const string LEFT_MARGIN = ' '; + private int $lengthOfLongestOptionName = 0; + private readonly int $columnsAvailableForDescription; + private bool $hasColor; public function __construct(?int $width = null, ?bool $withColor = null) { @@ -166,91 +46,286 @@ public function __construct(?int $width = null, ?bool $withColor = null) $this->hasColor = $withColor; } - foreach (self::HELP_TEXT as $options) { + foreach ($this->elements() as $options) { foreach ($options as $option) { if (isset($option['arg'])) { - $this->maxArgLength = max($this->maxArgLength, isset($option['arg']) ? strlen($option['arg']) : 0); + $this->lengthOfLongestOptionName = max($this->lengthOfLongestOptionName, strlen($option['arg'])); } } } - $this->maxDescLength = $width - $this->maxArgLength - 4; + $this->columnsAvailableForDescription = $width - $this->lengthOfLongestOptionName - 4; } - /** - * Write the help file to the CLI, adapting width and colors to the console. - */ - public function writeToConsole(): void + public function generate(): string { if ($this->hasColor) { - $this->writeWithColor(); - } else { - $this->writePlaintext(); + return $this->writeWithColor(); } + + return $this->writeWithoutColor(); } - private function writePlaintext(): void + private function writeWithoutColor(): string { - foreach (self::HELP_TEXT as $section => $options) { - print "{$section}:" . PHP_EOL; + $buffer = ''; + + foreach ($this->elements() as $section => $options) { + $buffer .= "{$section}:" . PHP_EOL; if ($section !== 'Usage') { - print PHP_EOL; + $buffer .= PHP_EOL; } foreach ($options as $option) { if (isset($option['spacer'])) { - print PHP_EOL; + $buffer .= PHP_EOL; } if (isset($option['text'])) { - print self::LEFT_MARGIN . $option['text'] . PHP_EOL; + $buffer .= self::LEFT_MARGIN . $option['text'] . PHP_EOL; } if (isset($option['arg'])) { - $arg = str_pad($option['arg'], $this->maxArgLength); - print self::LEFT_MARGIN . $arg . ' ' . $option['desc'] . PHP_EOL; + $arg = str_pad($option['arg'], $this->lengthOfLongestOptionName); + + $buffer .= self::LEFT_MARGIN . $arg . ' ' . $option['desc'] . PHP_EOL; } } - print PHP_EOL; + $buffer .= PHP_EOL; } + + return $buffer; } - private function writeWithColor(): void + private function writeWithColor(): string { - foreach (self::HELP_TEXT as $section => $options) { - print Color::colorize('fg-yellow', "{$section}:") . PHP_EOL; + $buffer = ''; + + foreach ($this->elements() as $section => $options) { + $buffer .= Color::colorize('fg-yellow', "{$section}:") . PHP_EOL; + + if ($section !== 'Usage') { + $buffer .= PHP_EOL; + } foreach ($options as $option) { if (isset($option['spacer'])) { - print PHP_EOL; + $buffer .= PHP_EOL; } if (isset($option['text'])) { - print self::LEFT_MARGIN . $option['text'] . PHP_EOL; + $buffer .= self::LEFT_MARGIN . $option['text'] . PHP_EOL; } if (isset($option['arg'])) { - $arg = Color::colorize('fg-green', str_pad($option['arg'], $this->maxArgLength)); + $arg = Color::colorize('fg-green', str_pad($option['arg'], $this->lengthOfLongestOptionName)); $arg = preg_replace_callback( '/(<[^>]+>)/', - static function ($matches) { - return Color::colorize('fg-cyan', $matches[0]); - }, - $arg + static fn (array $matches) => Color::colorize('fg-cyan', $matches[0]), + $arg, ); - $desc = explode(PHP_EOL, wordwrap($option['desc'], $this->maxDescLength, PHP_EOL)); - print self::LEFT_MARGIN . $arg . ' ' . $desc[0] . PHP_EOL; + $desc = explode(PHP_EOL, wordwrap($option['desc'], $this->columnsAvailableForDescription, PHP_EOL)); + + $buffer .= self::LEFT_MARGIN . $arg . ' ' . $desc[0] . PHP_EOL; for ($i = 1; $i < count($desc); $i++) { - print str_repeat(' ', $this->maxArgLength + 3) . $desc[$i] . PHP_EOL; + $buffer .= str_repeat(' ', $this->lengthOfLongestOptionName + 3) . $desc[$i] . PHP_EOL; } } } - print PHP_EOL; + $buffer .= PHP_EOL; + } + + return $buffer; + } + + /** + * @return array> + */ + private function elements(): array + { + $elements = [ + 'Usage' => [ + ['text' => 'phpunit [options] ...'], + ], + + 'Configuration' => [ + ['arg' => '--bootstrap ', 'desc' => 'A PHP script that is included before the tests run'], + ['arg' => '-c|--configuration ', 'desc' => 'Read configuration from XML file'], + ['arg' => '--no-configuration', 'desc' => 'Ignore default configuration file (phpunit.xml)'], + ['arg' => '--extension ', 'desc' => 'Register test runner extension with bootstrap '], + ['arg' => '--no-extensions', 'desc' => 'Do not register test runner extensions'], + ['arg' => '--include-path ', 'desc' => 'Prepend PHP\'s include_path with given path(s)'], + ['arg' => '-d ', 'desc' => 'Sets a php.ini value'], + ['arg' => '--cache-directory ', 'desc' => 'Specify cache directory'], + ['arg' => '--generate-configuration', 'desc' => 'Generate configuration file with suggested settings'], + ['arg' => '--migrate-configuration', 'desc' => 'Migrate configuration file to current format'], + ['arg' => '--generate-baseline ', 'desc' => 'Generate baseline for issues'], + ['arg' => '--use-baseline ', 'desc' => 'Use baseline to ignore issues'], + ['arg' => '--ignore-baseline', 'desc' => 'Do not use baseline to ignore issues'], + ], + + 'Selection' => [ + ['arg' => '--all', 'desc' => 'Ignore test selection from XML configuration file'], + ['arg' => '--list-suites', 'desc' => 'List available test suites'], + ['arg' => '--testsuite ', 'desc' => 'Only run tests from the specified test suite(s)'], + ['arg' => '--exclude-testsuite ', 'desc' => 'Exclude tests from the specified test suite(s)'], + ['arg' => '--list-groups', 'desc' => 'List available test groups'], + ['arg' => '--group ', 'desc' => 'Only run tests from the specified group(s)'], + ['arg' => '--exclude-group ', 'desc' => 'Exclude tests from the specified group(s)'], + ['arg' => '--covers ', 'desc' => 'Only run tests that intend to cover '], + ['arg' => '--uses ', 'desc' => 'Only run tests that intend to use '], + ['arg' => '--requires-php-extension ', 'desc' => 'Only run tests that require PHP extension '], + ['arg' => '--list-test-files', 'desc' => 'List available test files'], + ['arg' => '--list-tests', 'desc' => 'List available tests'], + ['arg' => '--list-tests-xml ', 'desc' => 'List available tests in XML format'], + ['arg' => '--filter ', 'desc' => 'Filter which tests to run'], + ['arg' => '--exclude-filter ', 'desc' => 'Exclude tests for the specified filter pattern'], + ['arg' => '--test-suffix ', 'desc' => 'Only search for test in files with specified suffix(es). Default: Test.php,.phpt'], + ], + + 'Execution' => [ + ['arg' => '--process-isolation', 'desc' => 'Run each test in a separate PHP process'], + ['arg' => '--globals-backup', 'desc' => 'Backup and restore $GLOBALS for each test'], + ['arg' => '--static-backup', 'desc' => 'Backup and restore static properties for each test'], + ['spacer' => ''], + + ['arg' => '--strict-coverage', 'desc' => 'Be strict about code coverage metadata'], + ['arg' => '--strict-global-state', 'desc' => 'Be strict about changes to global state'], + ['arg' => '--disallow-test-output', 'desc' => 'Be strict about output during tests'], + ['arg' => '--enforce-time-limit', 'desc' => 'Enforce time limit based on test size'], + ['arg' => '--default-time-limit ', 'desc' => 'Timeout in seconds for tests that have no declared size'], + ['arg' => '--do-not-report-useless-tests', 'desc' => 'Do not report tests that do not test anything'], + ['spacer' => ''], + + ['arg' => '--stop-on-defect', 'desc' => 'Stop after first error, failure, warning, or risky test'], + ['arg' => '--stop-on-error', 'desc' => 'Stop after first error'], + ['arg' => '--stop-on-failure', 'desc' => 'Stop after first failure'], + ['arg' => '--stop-on-warning', 'desc' => 'Stop after first warning'], + ['arg' => '--stop-on-risky', 'desc' => 'Stop after first risky test'], + ['arg' => '--stop-on-deprecation', 'desc' => 'Stop after first test that triggered a deprecation'], + ['arg' => '--stop-on-notice', 'desc' => 'Stop after first test that triggered a notice'], + ['arg' => '--stop-on-skipped', 'desc' => 'Stop after first skipped test'], + ['arg' => '--stop-on-incomplete', 'desc' => 'Stop after first incomplete test'], + ['spacer' => ''], + + ['arg' => '--fail-on-empty-test-suite', 'desc' => 'Signal failure using shell exit code when no tests were run'], + ['arg' => '--fail-on-warning', 'desc' => 'Signal failure using shell exit code when a warning was triggered'], + ['arg' => '--fail-on-risky', 'desc' => 'Signal failure using shell exit code when a test was considered risky'], + ['arg' => '--fail-on-deprecation', 'desc' => 'Signal failure using shell exit code when a deprecation was triggered'], + ['arg' => '--fail-on-phpunit-deprecation', 'desc' => 'Signal failure using shell exit code when a PHPUnit deprecation was triggered'], + ['arg' => '--fail-on-phpunit-notice', 'desc' => 'Signal failure using shell exit code when a PHPUnit notice was triggered'], + ['arg' => '--fail-on-phpunit-warning', 'desc' => 'Signal failure using shell exit code when a PHPUnit warning was triggered'], + ['arg' => '--fail-on-notice', 'desc' => 'Signal failure using shell exit code when a notice was triggered'], + ['arg' => '--fail-on-skipped', 'desc' => 'Signal failure using shell exit code when a test was skipped'], + ['arg' => '--fail-on-incomplete', 'desc' => 'Signal failure using shell exit code when a test was marked incomplete'], + ['arg' => '--fail-on-all-issues', 'desc' => 'Signal failure using shell exit code when an issue is triggered'], + ['spacer' => ''], + + ['arg' => '--do-not-fail-on-empty-test-suite', 'desc' => 'Do not signal failure using shell exit code when no tests were run'], + ['arg' => '--do-not-fail-on-warning', 'desc' => 'Do not signal failure using shell exit code when a warning was triggered'], + ['arg' => '--do-not-fail-on-risky', 'desc' => 'Do not signal failure using shell exit code when a test was considered risky'], + ['arg' => '--do-not-fail-on-deprecation', 'desc' => 'Do not signal failure using shell exit code when a deprecation was triggered'], + ['arg' => '--do-not-fail-on-phpunit-deprecation', 'desc' => 'Do not signal failure using shell exit code when a PHPUnit deprecation was triggered'], + ['arg' => '--do-not-fail-on-phpunit-notice', 'desc' => 'Do not signal failure using shell exit code when a PHPUnit notice was triggered'], + ['arg' => '--do-not-fail-on-phpunit-warning', 'desc' => 'Do not signal failure using shell exit code when a PHPUnit warning was triggered'], + ['arg' => '--do-not-fail-on-notice', 'desc' => 'Do not signal failure using shell exit code when a notice was triggered'], + ['arg' => '--do-not-fail-on-skipped', 'desc' => 'Do not signal failure using shell exit code when a test was skipped'], + ['arg' => '--do-not-fail-on-incomplete', 'desc' => 'Do not signal failure using shell exit code when a test was marked incomplete'], + ['spacer' => ''], + + ['arg' => '--cache-result', 'desc' => 'Write test results to cache file'], + ['arg' => '--do-not-cache-result', 'desc' => 'Do not write test results to cache file'], + ['spacer' => ''], + + ['arg' => '--order-by ', 'desc' => 'Run tests in order: default|defects|depends|duration|no-depends|random|reverse|size'], + ['arg' => '--random-order-seed ', 'desc' => 'Use the specified random seed when running tests in random order'], + ], + + 'Reporting' => [ + ['arg' => '--colors ', 'desc' => 'Use colors in output ("never", "auto" or "always")'], + ['arg' => '--columns ', 'desc' => 'Number of columns to use for progress output'], + ['arg' => '--columns max', 'desc' => 'Use maximum number of columns for progress output'], + ['arg' => '--stderr', 'desc' => 'Write to STDERR instead of STDOUT'], + ['spacer' => ''], + + ['arg' => '--no-progress', 'desc' => 'Disable output of test execution progress'], + ['arg' => '--no-results', 'desc' => 'Disable output of test results'], + ['arg' => '--no-output', 'desc' => 'Disable all output'], + ['spacer' => ''], + + ['arg' => '--display-incomplete', 'desc' => 'Display details for incomplete tests'], + ['arg' => '--display-skipped', 'desc' => 'Display details for skipped tests'], + ['arg' => '--display-deprecations', 'desc' => 'Display details for deprecations triggered by tests'], + ['arg' => '--display-phpunit-deprecations', 'desc' => 'Display details for PHPUnit deprecations'], + ['arg' => '--display-phpunit-notices', 'desc' => 'Display details for PHPUnit notices'], + ['arg' => '--display-errors', 'desc' => 'Display details for errors triggered by tests'], + ['arg' => '--display-notices', 'desc' => 'Display details for notices triggered by tests'], + ['arg' => '--display-warnings', 'desc' => 'Display details for warnings triggered by tests'], + ['arg' => '--display-all-issues', 'desc' => 'Display details for all issues that are triggered'], + ['arg' => '--reverse-list', 'desc' => 'Print defects in reverse order'], + ['spacer' => ''], + + ['arg' => '--teamcity', 'desc' => 'Replace default progress and result output with TeamCity format'], + ['arg' => '--testdox', 'desc' => 'Replace default result output with TestDox format'], + ['arg' => '--testdox-summary', 'desc' => 'Repeat TestDox output for tests with errors, failures, or issues'], + ['spacer' => ''], + + ['arg' => '--debug', 'desc' => 'Replace default progress and result output with debugging information'], + ['arg' => '--with-telemetry', 'desc' => 'Include telemetry information in debugging information output'], + ], + + 'Logging' => [ + ['arg' => '--log-junit ', 'desc' => 'Write test results in JUnit XML format to file'], + ['arg' => '--log-otr ', 'desc' => 'Write test results in Open Test Reporting XML format to file'], + ['arg' => '--include-git-information', 'desc' => 'Include Git information in Open Test Reporting XML logfile'], + ['arg' => '--log-teamcity ', 'desc' => 'Write test results in TeamCity format to file'], + ['arg' => '--testdox-html ', 'desc' => 'Write test results in TestDox format (HTML) to file'], + ['arg' => '--testdox-text ', 'desc' => 'Write test results in TestDox format (plain text) to file'], + ['arg' => '--log-events-text ', 'desc' => 'Stream events as plain text to file'], + ['arg' => '--log-events-verbose-text ', 'desc' => 'Stream events as plain text with extended information to file'], + ['arg' => '--no-logging', 'desc' => 'Ignore logging configured in the XML configuration file'], + ], + + 'Code Coverage' => [ + ['arg' => '--coverage-clover ', 'desc' => 'Write code coverage report in Clover XML format to file'], + ['arg' => '--coverage-openclover ', 'desc' => 'Write code coverage report in OpenClover XML format to file'], + ['arg' => '--coverage-cobertura ', 'desc' => 'Write code coverage report in Cobertura XML format to file'], + ['arg' => '--coverage-crap4j ', 'desc' => 'Write code coverage report in Crap4J XML format to file'], + ['arg' => '--coverage-html ', 'desc' => 'Write code coverage report in HTML format to directory'], + ['arg' => '--coverage-php ', 'desc' => 'Write serialized code coverage data to file'], + ['arg' => '--coverage-text=', 'desc' => 'Write code coverage report in text format to file [default: standard output]'], + ['arg' => '--only-summary-for-coverage-text', 'desc' => 'Option for code coverage report in text format: only show summary'], + ['arg' => '--show-uncovered-for-coverage-text', 'desc' => 'Option for code coverage report in text format: show uncovered files'], + ['arg' => '--coverage-xml ', 'desc' => 'Write code coverage report in XML format to directory'], + ['arg' => '--warm-coverage-cache', 'desc' => 'Warm static analysis cache'], + ['arg' => '--coverage-filter ', 'desc' => 'Include in code coverage reporting'], + ['arg' => '--path-coverage', 'desc' => 'Report path coverage in addition to line coverage'], + ['arg' => '--disable-coverage-ignore', 'desc' => 'Disable metadata for ignoring code coverage'], + ['arg' => '--no-coverage', 'desc' => 'Ignore code coverage reporting configured in the XML configuration file'], + ], + ]; + + if (defined('__PHPUNIT_PHAR__')) { + $elements['PHAR'] = [ + ['arg' => '--manifest', 'desc' => 'Print Software Bill of Materials (SBOM) in plain-text format'], + ['arg' => '--sbom', 'desc' => 'Print Software Bill of Materials (SBOM) in CycloneDX XML format'], + ['arg' => '--composer-lock', 'desc' => 'Print composer.lock file used to build the PHAR'], + ]; } + + $elements['Miscellaneous'] = [ + ['arg' => '-h|--help', 'desc' => 'Prints this usage information'], + ['arg' => '--version', 'desc' => 'Prints the version and exits'], + ['arg' => '--atleast-version ', 'desc' => 'Checks that version is greater than and exits'], + ['arg' => '--check-version', 'desc' => 'Checks whether PHPUnit is the latest version and exits'], + ['arg' => '--check-php-configuration', 'desc' => 'Checks whether PHP configuration follows best practices'], + ]; + + return $elements; } } diff --git a/src/TextUI/Output/Default/ProgressPrinter/ProgressPrinter.php b/src/TextUI/Output/Default/ProgressPrinter/ProgressPrinter.php new file mode 100644 index 00000000000..de8daf9bd7a --- /dev/null +++ b/src/TextUI/Output/Default/ProgressPrinter/ProgressPrinter.php @@ -0,0 +1,424 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use function floor; +use function sprintf; +use function str_repeat; +use function strlen; +use PHPUnit\Event\Facade; +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\ErrorTriggered; +use PHPUnit\Event\Test\NoticeTriggered; +use PHPUnit\Event\Test\PhpDeprecationTriggered; +use PHPUnit\Event\Test\PhpNoticeTriggered; +use PHPUnit\Event\Test\PhpunitWarningTriggered; +use PHPUnit\Event\Test\PhpWarningTriggered; +use PHPUnit\Event\Test\WarningTriggered; +use PHPUnit\Event\TestRunner\ChildProcessErrored; +use PHPUnit\Event\TestRunner\ExecutionStarted; +use PHPUnit\Framework\TestStatus\TestStatus; +use PHPUnit\TextUI\Configuration\Source; +use PHPUnit\TextUI\Configuration\SourceFilter; +use PHPUnit\TextUI\Output\Printer; +use PHPUnit\Util\Color; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ProgressPrinter +{ + private readonly Printer $printer; + private readonly bool $colors; + private readonly int $numberOfColumns; + private readonly Source $source; + private int $column = 0; + private int $numberOfTests = 0; + private int $numberOfTestsWidth = 0; + private int $maxColumn = 0; + private int $numberOfTestsRun = 0; + private ?TestStatus $status = null; + private bool $prepared = false; + private bool $childProcessErrored = false; + + public function __construct(Printer $printer, Facade $facade, bool $colors, int $numberOfColumns, Source $source) + { + $this->printer = $printer; + $this->colors = $colors; + $this->numberOfColumns = $numberOfColumns; + $this->source = $source; + + $this->registerSubscribers($facade); + } + + public function testRunnerExecutionStarted(ExecutionStarted $event): void + { + $this->numberOfTestsRun = 0; + $this->numberOfTests = $event->testSuite()->count(); + $this->numberOfTestsWidth = strlen((string) $this->numberOfTests); + $this->column = 0; + $this->maxColumn = $this->numberOfColumns - strlen(' / (XXX%)') - (2 * $this->numberOfTestsWidth); + } + + public function beforeTestClassMethodErrored(): void + { + $this->printProgressForError(); + $this->updateTestStatus(TestStatus::error()); + } + + public function testPrepared(): void + { + $this->prepared = true; + } + + public function testSkipped(): void + { + if (!$this->prepared) { + $this->printProgressForSkipped(); + } else { + $this->updateTestStatus(TestStatus::skipped()); + } + } + + public function testMarkedIncomplete(): void + { + $this->updateTestStatus(TestStatus::incomplete()); + } + + public function testTriggeredNotice(NoticeTriggered $event): void + { + if ($event->ignoredByBaseline()) { + return; + } + + if ($this->source->restrictNotices() && + !SourceFilter::instance()->includes($event->file())) { + return; + } + + if (!$this->source->ignoreSuppressionOfNotices() && $event->wasSuppressed()) { + return; + } + + $this->updateTestStatus(TestStatus::notice()); + } + + public function testTriggeredPhpNotice(PhpNoticeTriggered $event): void + { + if ($event->ignoredByBaseline()) { + return; + } + + if ($this->source->restrictNotices() && + !SourceFilter::instance()->includes($event->file())) { + return; + } + + if (!$this->source->ignoreSuppressionOfPhpNotices() && $event->wasSuppressed()) { + return; + } + + $this->updateTestStatus(TestStatus::notice()); + } + + public function testTriggeredDeprecation(DeprecationTriggered $event): void + { + if ($event->ignoredByBaseline() || $event->ignoredByTest()) { + return; + } + + if ($this->source->ignoreSelfDeprecations() && + ($event->trigger()->isTest() || $event->trigger()->isSelf())) { + return; + } + + if ($this->source->ignoreDirectDeprecations() && $event->trigger()->isDirect()) { + return; + } + + if ($this->source->ignoreIndirectDeprecations() && $event->trigger()->isIndirect()) { + return; + } + + if (!$this->source->ignoreSuppressionOfDeprecations() && $event->wasSuppressed()) { + return; + } + + $this->updateTestStatus(TestStatus::deprecation()); + } + + public function testTriggeredPhpDeprecation(PhpDeprecationTriggered $event): void + { + if ($event->ignoredByBaseline() || $event->ignoredByTest()) { + return; + } + + if ($this->source->ignoreSelfDeprecations() && + ($event->trigger()->isTest() || $event->trigger()->isSelf())) { + return; + } + + if ($this->source->ignoreDirectDeprecations() && $event->trigger()->isDirect()) { + return; + } + + if ($this->source->ignoreIndirectDeprecations() && $event->trigger()->isIndirect()) { + return; + } + + if (!$this->source->ignoreSuppressionOfPhpDeprecations() && $event->wasSuppressed()) { + return; + } + + $this->updateTestStatus(TestStatus::deprecation()); + } + + public function testTriggeredPhpunitDeprecation(): void + { + $this->updateTestStatus(TestStatus::deprecation()); + } + + public function testTriggeredPhpunitNotice(): void + { + $this->updateTestStatus(TestStatus::notice()); + } + + public function testConsideredRisky(): void + { + $this->updateTestStatus(TestStatus::risky()); + } + + public function testTriggeredWarning(WarningTriggered $event): void + { + if ($event->ignoredByBaseline()) { + return; + } + + if ($this->source->restrictWarnings() && + !SourceFilter::instance()->includes($event->file())) { + return; + } + + if (!$this->source->ignoreSuppressionOfWarnings() && $event->wasSuppressed()) { + return; + } + + $this->updateTestStatus(TestStatus::warning()); + } + + public function testTriggeredPhpWarning(PhpWarningTriggered $event): void + { + if ($event->ignoredByBaseline()) { + return; + } + + if ($this->source->restrictWarnings() && + !SourceFilter::instance()->includes($event->file())) { + return; + } + + if (!$this->source->ignoreSuppressionOfPhpWarnings() && $event->wasSuppressed()) { + return; + } + + $this->updateTestStatus(TestStatus::warning()); + } + + public function testTriggeredPhpunitWarning(PhpunitWarningTriggered $event): void + { + if ($event->ignoredByTest()) { + return; + } + + $this->updateTestStatus(TestStatus::warning()); + } + + public function testTriggeredError(ErrorTriggered $event): void + { + if (!$this->source->ignoreSuppressionOfErrors() && $event->wasSuppressed()) { + return; + } + + $this->updateTestStatus(TestStatus::error()); + } + + public function testFailed(): void + { + $this->updateTestStatus(TestStatus::failure()); + } + + public function testErrored(Errored $event): void + { + if ($this->childProcessErrored) { + $this->updateTestStatus(TestStatus::error()); + + return; + } + + if (!$this->prepared) { + $this->printProgressForError(); + } else { + $this->updateTestStatus(TestStatus::error()); + } + } + + public function testFinished(): void + { + if ($this->status === null) { + $this->printProgressForSuccess(); + } elseif ($this->status->isSkipped()) { + $this->printProgressForSkipped(); + } elseif ($this->status->isIncomplete()) { + $this->printProgressForIncomplete(); + } elseif ($this->status->isRisky()) { + $this->printProgressForRisky(); + } elseif ($this->status->isNotice()) { + $this->printProgressForNotice(); + } elseif ($this->status->isDeprecation()) { + $this->printProgressForDeprecation(); + } elseif ($this->status->isWarning()) { + $this->printProgressForWarning(); + } elseif ($this->status->isFailure()) { + $this->printProgressForFailure(); + } else { + $this->printProgressForError(); + } + + $this->status = null; + $this->prepared = false; + $this->childProcessErrored = false; + } + + public function childProcessErrored(ChildProcessErrored $event): void + { + $this->childProcessErrored = true; + } + + private function registerSubscribers(Facade $facade): void + { + $facade->registerSubscribers( + new BeforeTestClassMethodErroredSubscriber($this), + new TestConsideredRiskySubscriber($this), + new TestErroredSubscriber($this), + new TestFailedSubscriber($this), + new TestFinishedSubscriber($this), + new TestMarkedIncompleteSubscriber($this), + new TestPreparedSubscriber($this), + new TestRunnerExecutionStartedSubscriber($this), + new TestSkippedSubscriber($this), + new TestTriggeredDeprecationSubscriber($this), + new TestTriggeredNoticeSubscriber($this), + new TestTriggeredPhpDeprecationSubscriber($this), + new TestTriggeredPhpNoticeSubscriber($this), + new TestTriggeredPhpunitDeprecationSubscriber($this), + new TestTriggeredPhpunitNoticeSubscriber($this), + new TestTriggeredPhpunitWarningSubscriber($this), + new TestTriggeredPhpWarningSubscriber($this), + new TestTriggeredWarningSubscriber($this), + new ChildProcessErroredSubscriber($this), + ); + } + + private function updateTestStatus(TestStatus $status): void + { + if ($this->status !== null && + $this->status->isMoreImportantThan($status)) { + return; + } + + $this->status = $status; + } + + private function printProgressForSuccess(): void + { + $this->printProgress('.'); + } + + private function printProgressForSkipped(): void + { + $this->printProgressWithColor('fg-cyan, bold', 'S'); + } + + private function printProgressForIncomplete(): void + { + $this->printProgressWithColor('fg-yellow, bold', 'I'); + } + + private function printProgressForNotice(): void + { + $this->printProgressWithColor('fg-yellow, bold', 'N'); + } + + private function printProgressForDeprecation(): void + { + $this->printProgressWithColor('fg-yellow, bold', 'D'); + } + + private function printProgressForRisky(): void + { + $this->printProgressWithColor('fg-yellow, bold', 'R'); + } + + private function printProgressForWarning(): void + { + $this->printProgressWithColor('fg-yellow, bold', 'W'); + } + + private function printProgressForFailure(): void + { + $this->printProgressWithColor('bg-red, fg-white', 'F'); + } + + private function printProgressForError(): void + { + $this->printProgressWithColor('fg-red, bold', 'E'); + } + + private function printProgressWithColor(string $color, string $progress): void + { + if ($this->colors) { + $progress = Color::colorizeTextBox($color, $progress); + } + + $this->printProgress($progress); + } + + private function printProgress(string $progress): void + { + $this->printer->print($progress); + + $this->column++; + $this->numberOfTestsRun++; + + if ($this->column === $this->maxColumn || $this->numberOfTestsRun === $this->numberOfTests) { + if ($this->numberOfTestsRun === $this->numberOfTests) { + $this->printer->print(str_repeat(' ', $this->maxColumn - $this->column)); + } + + $this->printer->print( + sprintf( + ' %' . $this->numberOfTestsWidth . 'd / %' . + $this->numberOfTestsWidth . 'd (%3s%%)', + $this->numberOfTestsRun, + $this->numberOfTests, + floor(($this->numberOfTestsRun / $this->numberOfTests) * 100), + ), + ); + + if ($this->column === $this->maxColumn) { + $this->column = 0; + $this->printer->print("\n"); + } + } + } +} diff --git a/src/TextUI/Output/Default/ProgressPrinter/Subscriber/BeforeTestClassMethodErroredSubscriber.php b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/BeforeTestClassMethodErroredSubscriber.php new file mode 100644 index 00000000000..2984cdda8b7 --- /dev/null +++ b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/BeforeTestClassMethodErroredSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; +use PHPUnit\Event\Test\BeforeFirstTestMethodErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeTestClassMethodErroredSubscriber extends Subscriber implements BeforeFirstTestMethodErroredSubscriber +{ + public function notify(BeforeFirstTestMethodErrored $event): void + { + $this->printer()->beforeTestClassMethodErrored(); + } +} diff --git a/src/TextUI/Output/Default/ProgressPrinter/Subscriber/ChildProcessErroredSubscriber.php b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/ChildProcessErroredSubscriber.php new file mode 100644 index 00000000000..643694076e8 --- /dev/null +++ b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/ChildProcessErroredSubscriber.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\TestRunner\ChildProcessErrored; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ChildProcessErroredSubscriber extends Subscriber implements \PHPUnit\Event\TestRunner\ChildProcessErroredSubscriber +{ + public function notify(ChildProcessErrored $event): void + { + $this->printer()->childProcessErrored($event); + } +} diff --git a/src/TextUI/Output/Default/ProgressPrinter/Subscriber/Subscriber.php b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/Subscriber.php new file mode 100644 index 00000000000..32515ee343c --- /dev/null +++ b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/Subscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Subscriber +{ + private ProgressPrinter $printer; + + public function __construct(ProgressPrinter $printer) + { + $this->printer = $printer; + } + + protected function printer(): ProgressPrinter + { + return $this->printer; + } +} diff --git a/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestConsideredRiskySubscriber.php b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestConsideredRiskySubscriber.php new file mode 100644 index 00000000000..e5b57c6d3d6 --- /dev/null +++ b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestConsideredRiskySubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\ConsideredRisky; +use PHPUnit\Event\Test\ConsideredRiskySubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestConsideredRiskySubscriber extends Subscriber implements ConsideredRiskySubscriber +{ + public function notify(ConsideredRisky $event): void + { + $this->printer()->testConsideredRisky(); + } +} diff --git a/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestErroredSubscriber.php b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestErroredSubscriber.php new file mode 100644 index 00000000000..33340755261 --- /dev/null +++ b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestErroredSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\ErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestErroredSubscriber extends Subscriber implements ErroredSubscriber +{ + public function notify(Errored $event): void + { + $this->printer()->testErrored($event); + } +} diff --git a/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestFailedSubscriber.php b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestFailedSubscriber.php new file mode 100644 index 00000000000..9109d1b312b --- /dev/null +++ b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestFailedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\FailedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFailedSubscriber extends Subscriber implements FailedSubscriber +{ + public function notify(Failed $event): void + { + $this->printer()->testFailed(); + } +} diff --git a/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestFinishedSubscriber.php b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestFinishedSubscriber.php new file mode 100644 index 00000000000..e4b4ca5ee87 --- /dev/null +++ b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestFinishedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + public function notify(Finished $event): void + { + $this->printer()->testFinished(); + } +} diff --git a/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestMarkedIncompleteSubscriber.php b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestMarkedIncompleteSubscriber.php new file mode 100644 index 00000000000..8b445088064 --- /dev/null +++ b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestMarkedIncompleteSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\MarkedIncompleteSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestMarkedIncompleteSubscriber extends Subscriber implements MarkedIncompleteSubscriber +{ + public function notify(MarkedIncomplete $event): void + { + $this->printer()->testMarkedIncomplete(); + } +} diff --git a/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestPreparedSubscriber.php b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestPreparedSubscriber.php new file mode 100644 index 00000000000..d99f2fa4190 --- /dev/null +++ b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestPreparedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\PreparedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPreparedSubscriber extends Subscriber implements PreparedSubscriber +{ + public function notify(Prepared $event): void + { + $this->printer()->testPrepared(); + } +} diff --git a/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestRunnerExecutionStartedSubscriber.php b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestRunnerExecutionStartedSubscriber.php new file mode 100644 index 00000000000..78e104f6d46 --- /dev/null +++ b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestRunnerExecutionStartedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\TestRunner\ExecutionStarted; +use PHPUnit\Event\TestRunner\ExecutionStartedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestRunnerExecutionStartedSubscriber extends Subscriber implements ExecutionStartedSubscriber +{ + public function notify(ExecutionStarted $event): void + { + $this->printer()->testRunnerExecutionStarted($event); + } +} diff --git a/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestSkippedSubscriber.php b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestSkippedSubscriber.php new file mode 100644 index 00000000000..a2f4e25569b --- /dev/null +++ b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestSkippedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\Skipped; +use PHPUnit\Event\Test\SkippedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSkippedSubscriber extends Subscriber implements SkippedSubscriber +{ + public function notify(Skipped $event): void + { + $this->printer()->testSkipped(); + } +} diff --git a/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredDeprecationSubscriber.php b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredDeprecationSubscriber.php new file mode 100644 index 00000000000..16a4ccf96ec --- /dev/null +++ b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredDeprecationSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\Test\DeprecationTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredDeprecationSubscriber extends Subscriber implements DeprecationTriggeredSubscriber +{ + public function notify(DeprecationTriggered $event): void + { + $this->printer()->testTriggeredDeprecation($event); + } +} diff --git a/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredErrorSubscriber.php b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredErrorSubscriber.php new file mode 100644 index 00000000000..1f89911b45e --- /dev/null +++ b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredErrorSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\ErrorTriggered; +use PHPUnit\Event\Test\ErrorTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredErrorSubscriber extends Subscriber implements ErrorTriggeredSubscriber +{ + public function notify(ErrorTriggered $event): void + { + $this->printer()->testTriggeredError($event); + } +} diff --git a/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredNoticeSubscriber.php b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredNoticeSubscriber.php new file mode 100644 index 00000000000..0639f027272 --- /dev/null +++ b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredNoticeSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\NoticeTriggered; +use PHPUnit\Event\Test\NoticeTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredNoticeSubscriber extends Subscriber implements NoticeTriggeredSubscriber +{ + public function notify(NoticeTriggered $event): void + { + $this->printer()->testTriggeredNotice($event); + } +} diff --git a/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpDeprecationSubscriber.php b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpDeprecationSubscriber.php new file mode 100644 index 00000000000..550250c2100 --- /dev/null +++ b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpDeprecationSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\PhpDeprecationTriggered; +use PHPUnit\Event\Test\PhpDeprecationTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpDeprecationSubscriber extends Subscriber implements PhpDeprecationTriggeredSubscriber +{ + public function notify(PhpDeprecationTriggered $event): void + { + $this->printer()->testTriggeredPhpDeprecation($event); + } +} diff --git a/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpNoticeSubscriber.php b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpNoticeSubscriber.php new file mode 100644 index 00000000000..299b898c076 --- /dev/null +++ b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpNoticeSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\PhpNoticeTriggered; +use PHPUnit\Event\Test\PhpNoticeTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpNoticeSubscriber extends Subscriber implements PhpNoticeTriggeredSubscriber +{ + public function notify(PhpNoticeTriggered $event): void + { + $this->printer()->testTriggeredPhpNotice($event); + } +} diff --git a/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpWarningSubscriber.php b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpWarningSubscriber.php new file mode 100644 index 00000000000..a4ff81c2276 --- /dev/null +++ b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpWarningSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\PhpWarningTriggered; +use PHPUnit\Event\Test\PhpWarningTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpWarningSubscriber extends Subscriber implements PhpWarningTriggeredSubscriber +{ + public function notify(PhpWarningTriggered $event): void + { + $this->printer()->testTriggeredPhpWarning($event); + } +} diff --git a/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpunitDeprecationSubscriber.php b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpunitDeprecationSubscriber.php new file mode 100644 index 00000000000..62311a0123a --- /dev/null +++ b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpunitDeprecationSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\PhpunitDeprecationTriggered; +use PHPUnit\Event\Test\PhpunitDeprecationTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpunitDeprecationSubscriber extends Subscriber implements PhpunitDeprecationTriggeredSubscriber +{ + public function notify(PhpunitDeprecationTriggered $event): void + { + $this->printer()->testTriggeredPhpunitDeprecation(); + } +} diff --git a/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpunitNoticeSubscriber.php b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpunitNoticeSubscriber.php new file mode 100644 index 00000000000..6b0e48cfd1b --- /dev/null +++ b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpunitNoticeSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\PhpunitNoticeTriggered; +use PHPUnit\Event\Test\PhpunitNoticeTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpunitNoticeSubscriber extends Subscriber implements PhpunitNoticeTriggeredSubscriber +{ + public function notify(PhpunitNoticeTriggered $event): void + { + $this->printer()->testTriggeredPhpunitNotice(); + } +} diff --git a/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpunitWarningSubscriber.php b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpunitWarningSubscriber.php new file mode 100644 index 00000000000..7d0aed1ff87 --- /dev/null +++ b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpunitWarningSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\PhpunitWarningTriggered; +use PHPUnit\Event\Test\PhpunitWarningTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpunitWarningSubscriber extends Subscriber implements PhpunitWarningTriggeredSubscriber +{ + public function notify(PhpunitWarningTriggered $event): void + { + $this->printer()->testTriggeredPhpunitWarning($event); + } +} diff --git a/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredWarningSubscriber.php b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredWarningSubscriber.php new file mode 100644 index 00000000000..620458422dd --- /dev/null +++ b/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredWarningSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\WarningTriggered; +use PHPUnit\Event\Test\WarningTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredWarningSubscriber extends Subscriber implements WarningTriggeredSubscriber +{ + public function notify(WarningTriggered $event): void + { + $this->printer()->testTriggeredWarning($event); + } +} diff --git a/src/TextUI/Output/Default/ResultPrinter.php b/src/TextUI/Output/Default/ResultPrinter.php new file mode 100644 index 00000000000..555382db45f --- /dev/null +++ b/src/TextUI/Output/Default/ResultPrinter.php @@ -0,0 +1,696 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default; + +use const PHP_EOL; +use function array_keys; +use function array_merge; +use function array_reverse; +use function array_unique; +use function assert; +use function count; +use function explode; +use function ksort; +use function range; +use function sprintf; +use function str_starts_with; +use function strlen; +use function substr; +use function trim; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\Test\AfterLastTestMethodErrored; +use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; +use PHPUnit\Event\Test\ConsideredRisky; +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\Test\ErrorTriggered; +use PHPUnit\Event\Test\NoticeTriggered; +use PHPUnit\Event\Test\PhpDeprecationTriggered; +use PHPUnit\Event\Test\PhpNoticeTriggered; +use PHPUnit\Event\Test\PhpunitDeprecationTriggered; +use PHPUnit\Event\Test\PhpunitErrorTriggered; +use PHPUnit\Event\Test\PhpunitNoticeTriggered; +use PHPUnit\Event\Test\PhpunitWarningTriggered; +use PHPUnit\Event\Test\PhpWarningTriggered; +use PHPUnit\Event\Test\WarningTriggered; +use PHPUnit\TestRunner\TestResult\Issues\Issue; +use PHPUnit\TestRunner\TestResult\TestResult; +use PHPUnit\TextUI\Output\Printer; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ResultPrinter +{ + private readonly Printer $printer; + private readonly bool $displayPhpunitDeprecations; + private readonly bool $displayPhpunitErrors; + private readonly bool $displayPhpunitNotices; + private readonly bool $displayPhpunitWarnings; + private readonly bool $displayTestsWithErrors; + private readonly bool $displayTestsWithFailedAssertions; + private readonly bool $displayRiskyTests; + private readonly bool $displayDetailsOnIncompleteTests; + private readonly bool $displayDetailsOnSkippedTests; + private readonly bool $displayDetailsOnTestsThatTriggerDeprecations; + private readonly bool $displayDetailsOnTestsThatTriggerErrors; + private readonly bool $displayDetailsOnTestsThatTriggerNotices; + private readonly bool $displayDetailsOnTestsThatTriggerWarnings; + private readonly bool $displayDefectsInReverseOrder; + private bool $listPrinted = false; + + public function __construct(Printer $printer, bool $displayPhpunitDeprecations, bool $displayPhpunitErrors, bool $displayPhpunitNotices, bool $displayPhpunitWarnings, bool $displayTestsWithErrors, bool $displayTestsWithFailedAssertions, bool $displayRiskyTests, bool $displayDetailsOnIncompleteTests, bool $displayDetailsOnSkippedTests, bool $displayDetailsOnTestsThatTriggerDeprecations, bool $displayDetailsOnTestsThatTriggerErrors, bool $displayDetailsOnTestsThatTriggerNotices, bool $displayDetailsOnTestsThatTriggerWarnings, bool $displayDefectsInReverseOrder) + { + $this->printer = $printer; + $this->displayPhpunitDeprecations = $displayPhpunitDeprecations; + $this->displayPhpunitErrors = $displayPhpunitErrors; + $this->displayPhpunitNotices = $displayPhpunitNotices; + $this->displayPhpunitWarnings = $displayPhpunitWarnings; + $this->displayTestsWithErrors = $displayTestsWithErrors; + $this->displayTestsWithFailedAssertions = $displayTestsWithFailedAssertions; + $this->displayRiskyTests = $displayRiskyTests; + $this->displayDetailsOnIncompleteTests = $displayDetailsOnIncompleteTests; + $this->displayDetailsOnSkippedTests = $displayDetailsOnSkippedTests; + $this->displayDetailsOnTestsThatTriggerDeprecations = $displayDetailsOnTestsThatTriggerDeprecations; + $this->displayDetailsOnTestsThatTriggerErrors = $displayDetailsOnTestsThatTriggerErrors; + $this->displayDetailsOnTestsThatTriggerNotices = $displayDetailsOnTestsThatTriggerNotices; + $this->displayDetailsOnTestsThatTriggerWarnings = $displayDetailsOnTestsThatTriggerWarnings; + $this->displayDefectsInReverseOrder = $displayDefectsInReverseOrder; + } + + public function print(TestResult $result, bool $stackTraceForDeprecations = false): void + { + if ($this->displayPhpunitErrors) { + $this->printPhpunitErrors($result); + } + + if ($this->displayPhpunitWarnings) { + $this->printTestRunnerWarnings($result); + } + + if ($this->displayPhpunitDeprecations) { + $this->printTestRunnerDeprecations($result); + } + + if ($this->displayPhpunitNotices) { + $this->printTestRunnerNotices($result); + } + + if ($this->displayTestsWithErrors) { + $this->printTestsWithErrors($result); + } + + if ($this->displayTestsWithFailedAssertions) { + $this->printTestsWithFailedAssertions($result); + } + + if ($this->displayPhpunitWarnings) { + $this->printDetailsOnTestsThatTriggeredPhpunitWarnings($result); + } + + if ($this->displayPhpunitDeprecations) { + $this->printDetailsOnTestsThatTriggeredPhpunitDeprecations($result); + } + + if ($this->displayRiskyTests) { + $this->printRiskyTests($result); + } + + if ($this->displayPhpunitNotices) { + $this->printDetailsOnTestsThatTriggeredPhpunitNotices($result); + } + + if ($this->displayDetailsOnIncompleteTests) { + $this->printIncompleteTests($result); + } + + if ($this->displayDetailsOnSkippedTests) { + $this->printSkippedTestSuites($result); + $this->printSkippedTests($result); + } + + if ($this->displayDetailsOnTestsThatTriggerErrors) { + $this->printIssueList('error', $result->errors()); + } + + if ($this->displayDetailsOnTestsThatTriggerWarnings) { + $this->printIssueList('PHP warning', $result->phpWarnings()); + $this->printIssueList('warning', $result->warnings()); + } + + if ($this->displayDetailsOnTestsThatTriggerNotices) { + $this->printIssueList('PHP notice', $result->phpNotices()); + $this->printIssueList('notice', $result->notices()); + } + + if ($this->displayDetailsOnTestsThatTriggerDeprecations) { + $this->printIssueList('PHP deprecation', $result->phpDeprecations()); + $this->printIssueList('deprecation', $result->deprecations(), $stackTraceForDeprecations); + } + } + + private function printPhpunitErrors(TestResult $result): void + { + if (!$result->hasTestTriggeredPhpunitErrorEvents()) { + return; + } + + $elements = $this->mapTestsWithIssuesEventsToElements($result->testTriggeredPhpunitErrorEvents()); + + $this->printListHeaderWithNumber($elements['numberOfTestsWithIssues'], 'PHPUnit error'); + $this->printList($elements['elements']); + } + + private function printDetailsOnTestsThatTriggeredPhpunitDeprecations(TestResult $result): void + { + if (!$result->hasTestTriggeredPhpunitDeprecationEvents()) { + return; + } + + $elements = $this->mapTestsWithIssuesEventsToElements($result->testTriggeredPhpunitDeprecationEvents()); + + $this->printListHeaderWithNumberOfTestsAndNumberOfIssues( + $elements['numberOfTestsWithIssues'], + $elements['numberOfIssues'], + 'PHPUnit deprecation', + ); + + $this->printList($elements['elements']); + } + + private function printDetailsOnTestsThatTriggeredPhpunitNotices(TestResult $result): void + { + if (!$result->hasTestTriggeredPhpunitNoticeEvents()) { + return; + } + + $elements = $this->mapTestsWithIssuesEventsToElements($result->testTriggeredPhpunitNoticeEvents()); + + $this->printListHeaderWithNumberOfTestsAndNumberOfIssues( + $elements['numberOfTestsWithIssues'], + $elements['numberOfIssues'], + 'PHPUnit notice', + ); + + $this->printList($elements['elements']); + } + + private function printTestRunnerNotices(TestResult $result): void + { + if (!$result->hasTestRunnerTriggeredNoticeEvents()) { + return; + } + + $elements = []; + $messages = []; + + foreach ($result->testRunnerTriggeredNoticeEvents() as $event) { + if (isset($messages[$event->message()])) { + continue; + } + + $elements[] = [ + 'title' => $event->message(), + 'body' => '', + ]; + + $messages[$event->message()] = true; + } + + $this->printListHeaderWithNumber(count($elements), 'PHPUnit test runner notice'); + $this->printList($elements); + } + + private function printTestRunnerWarnings(TestResult $result): void + { + if (!$result->hasTestRunnerTriggeredWarningEvents()) { + return; + } + + $elements = []; + $messages = []; + + foreach ($result->testRunnerTriggeredWarningEvents() as $event) { + if (isset($messages[$event->message()])) { + continue; + } + + $elements[] = [ + 'title' => $event->message(), + 'body' => '', + ]; + + $messages[$event->message()] = true; + } + + $this->printListHeaderWithNumber(count($elements), 'PHPUnit test runner warning'); + $this->printList($elements); + } + + private function printTestRunnerDeprecations(TestResult $result): void + { + if (!$result->hasTestRunnerTriggeredDeprecationEvents()) { + return; + } + + $elements = []; + + foreach ($result->testRunnerTriggeredDeprecationEvents() as $event) { + $elements[] = [ + 'title' => $event->message(), + 'body' => '', + ]; + } + + $this->printListHeaderWithNumber(count($elements), 'PHPUnit test runner deprecation'); + $this->printList($elements); + } + + private function printDetailsOnTestsThatTriggeredPhpunitWarnings(TestResult $result): void + { + if (!$result->hasTestTriggeredPhpunitWarningEvents()) { + return; + } + + $elements = $this->mapTestsWithIssuesEventsToElements($result->testTriggeredPhpunitWarningEvents()); + + $this->printListHeaderWithNumberOfTestsAndNumberOfIssues( + $elements['numberOfTestsWithIssues'], + $elements['numberOfIssues'], + 'PHPUnit warning', + ); + + $this->printList($elements['elements']); + } + + private function printTestsWithErrors(TestResult $result): void + { + if (!$result->hasTestErroredEvents()) { + return; + } + + $elements = []; + + foreach ($result->testErroredEvents() as $event) { + if ($event instanceof AfterLastTestMethodErrored || $event instanceof BeforeFirstTestMethodErrored) { + $title = $event->testClassName(); + } else { + $title = $this->name($event->test()); + } + + $elements[] = [ + 'title' => $title, + 'body' => $event->throwable()->asString(), + ]; + } + + $this->printListHeaderWithNumber(count($elements), 'error'); + $this->printList($elements); + } + + private function printTestsWithFailedAssertions(TestResult $result): void + { + if (!$result->hasTestFailedEvents()) { + return; + } + + $elements = []; + + foreach ($result->testFailedEvents() as $event) { + $body = $event->throwable()->asString(); + + if (str_starts_with($body, 'AssertionError: ')) { + $body = substr($body, strlen('AssertionError: ')); + } + + $elements[] = [ + 'title' => $this->name($event->test()), + 'body' => $body, + ]; + } + + $this->printListHeaderWithNumber(count($elements), 'failure'); + $this->printList($elements); + } + + private function printRiskyTests(TestResult $result): void + { + if (!$result->hasTestConsideredRiskyEvents()) { + return; + } + + $elements = $this->mapTestsWithIssuesEventsToElements($result->testConsideredRiskyEvents()); + + $this->printListHeaderWithNumber($elements['numberOfTestsWithIssues'], 'risky test'); + $this->printList($elements['elements']); + } + + private function printIncompleteTests(TestResult $result): void + { + if (!$result->hasTestMarkedIncompleteEvents()) { + return; + } + + $elements = []; + + foreach ($result->testMarkedIncompleteEvents() as $event) { + $elements[] = [ + 'title' => $this->name($event->test()), + 'body' => $event->throwable()->asString(), + ]; + } + + $this->printListHeaderWithNumber(count($elements), 'incomplete test'); + $this->printList($elements); + } + + private function printSkippedTestSuites(TestResult $result): void + { + if (!$result->hasTestSuiteSkippedEvents()) { + return; + } + + $elements = []; + + foreach ($result->testSuiteSkippedEvents() as $event) { + $elements[] = [ + 'title' => $event->testSuite()->name(), + 'body' => $event->message(), + ]; + } + + $this->printListHeaderWithNumber(count($elements), 'skipped test suite'); + $this->printList($elements); + } + + private function printSkippedTests(TestResult $result): void + { + if (!$result->hasTestSkippedEvents()) { + return; + } + + $elements = []; + + foreach ($result->testSkippedEvents() as $event) { + $elements[] = [ + 'title' => $this->name($event->test()), + 'body' => $event->message(), + ]; + } + + $this->printListHeaderWithNumber(count($elements), 'skipped test'); + $this->printList($elements); + } + + /** + * @param non-empty-string $type + * @param list $issues + */ + private function printIssueList(string $type, array $issues, bool $stackTrace = false): void + { + if ($issues === []) { + return; + } + + $numberOfUniqueIssues = count($issues); + $triggeringTests = []; + + foreach ($issues as $issue) { + $triggeringTests = array_merge($triggeringTests, array_keys($issue->triggeringTests())); + } + + $numberOfTests = count(array_unique($triggeringTests)); + unset($triggeringTests); + + $this->printListHeader( + sprintf( + '%d test%s triggered %d %s%s:' . PHP_EOL . PHP_EOL, + $numberOfTests, + $numberOfTests !== 1 ? 's' : '', + $numberOfUniqueIssues, + $type, + $numberOfUniqueIssues !== 1 ? 's' : '', + ), + ); + + $i = 1; + + foreach ($issues as $issue) { + $title = sprintf( + '%s:%d', + $issue->file(), + $issue->line(), + ); + + $body = trim($issue->description()) . PHP_EOL . PHP_EOL; + + if ($stackTrace && $issue->hasStackTrace()) { + $body .= trim($issue->stackTrace()) . PHP_EOL . PHP_EOL; + } + + if (!$issue->triggeredInTest()) { + $body .= 'Triggered by:'; + + $triggeringTests = $issue->triggeringTests(); + + ksort($triggeringTests); + + foreach ($triggeringTests as $triggeringTest) { + $body .= PHP_EOL . PHP_EOL . '* ' . $triggeringTest['test']->id(); + + if ($triggeringTest['count'] > 1) { + $body .= sprintf( + ' (%d times)', + $triggeringTest['count'], + ); + } + + if ($triggeringTest['test']->isTestMethod()) { + $body .= PHP_EOL . ' ' . $triggeringTest['test']->file() . ':' . $triggeringTest['test']->line(); + } + } + } + + $this->printIssueListElement($i++, $title, $body); + + $this->printer->print(PHP_EOL); + } + } + + private function printListHeaderWithNumberOfTestsAndNumberOfIssues(int $numberOfTestsWithIssues, int $numberOfIssues, string $type): void + { + $this->printListHeader( + sprintf( + "%d test%s triggered %d %s%s:\n\n", + $numberOfTestsWithIssues, + $numberOfTestsWithIssues !== 1 ? 's' : '', + $numberOfIssues, + $type, + $numberOfIssues !== 1 ? 's' : '', + ), + ); + } + + private function printListHeaderWithNumber(int $number, string $type): void + { + $this->printListHeader( + sprintf( + "There %s %d %s%s:\n\n", + ($number === 1) ? 'was' : 'were', + $number, + $type, + ($number === 1) ? '' : 's', + ), + ); + } + + private function printListHeader(string $header): void + { + if ($this->listPrinted) { + $this->printer->print("--\n\n"); + } + + $this->listPrinted = true; + + $this->printer->print($header); + } + + /** + * @param list $elements + */ + private function printList(array $elements): void + { + $i = 1; + + if ($this->displayDefectsInReverseOrder) { + $elements = array_reverse($elements); + } + + foreach ($elements as $element) { + $this->printListElement($i++, $element['title'], $element['body']); + } + + $this->printer->print("\n"); + } + + private function printListElement(int $number, string $title, string $body): void + { + $body = trim($body); + + $this->printer->print( + sprintf( + "%s%d) %s\n%s%s", + $number > 1 ? "\n" : '', + $number, + $title, + $body, + $body !== '' ? "\n" : '', + ), + ); + } + + private function printIssueListElement(int $number, string $title, string $body): void + { + $body = trim($body); + + $this->printer->print( + sprintf( + "%d) %s\n%s%s", + $number, + $title, + $body, + $body !== '' ? "\n" : '', + ), + ); + } + + private function name(Test $test): string + { + if ($test->isTestMethod()) { + assert($test instanceof TestMethod); + + if (!$test->testData()->hasDataFromDataProvider()) { + return $test->nameWithClass(); + } + + return $test->className() . '::' . $test->methodName() . $test->testData()->dataFromDataProvider()->dataAsStringForResultOutput(); + } + + return $test->name(); + } + + /** + * @param array> $events + * + * @return array{numberOfTestsWithIssues: int, numberOfIssues: int, elements: list} + */ + private function mapTestsWithIssuesEventsToElements(array $events): array + { + $elements = []; + $issues = 0; + + foreach ($events as $reasons) { + $test = $reasons[0]->test(); + $testLocation = $this->testLocation($test); + $title = $this->name($test); + $body = ''; + $first = true; + $single = count($reasons) === 1; + + foreach ($reasons as $reason) { + if ($first) { + $first = false; + } else { + $body .= PHP_EOL; + } + + $body .= $this->reasonMessage($reason, $single); + $body .= $this->reasonLocation($reason, $single); + + $issues++; + } + + if ($testLocation !== '') { + $body .= $testLocation; + } + + $elements[] = [ + 'title' => $title, + 'body' => $body, + ]; + } + + return [ + 'numberOfTestsWithIssues' => count($events), + 'numberOfIssues' => $issues, + 'elements' => $elements, + ]; + } + + private function testLocation(Test $test): string + { + if (!$test->isTestMethod()) { + return ''; + } + + assert($test instanceof TestMethod); + + return sprintf( + '%s%s:%d%s', + PHP_EOL, + $test->file(), + $test->line(), + PHP_EOL, + ); + } + + private function reasonMessage(ConsideredRisky|DeprecationTriggered|ErrorTriggered|NoticeTriggered|PhpDeprecationTriggered|PhpNoticeTriggered|PhpunitDeprecationTriggered|PhpunitErrorTriggered|PhpunitNoticeTriggered|PhpunitWarningTriggered|PhpWarningTriggered|WarningTriggered $reason, bool $single): string + { + $message = trim($reason->message()); + + if ($single) { + return $message . PHP_EOL; + } + + $lines = explode(PHP_EOL, $message); + $buffer = '* ' . $lines[0] . PHP_EOL; + + if (count($lines) > 1) { + foreach (range(1, count($lines) - 1) as $line) { + $buffer .= ' ' . $lines[$line] . PHP_EOL; + } + } + + return $buffer; + } + + private function reasonLocation(ConsideredRisky|DeprecationTriggered|ErrorTriggered|NoticeTriggered|PhpDeprecationTriggered|PhpNoticeTriggered|PhpunitDeprecationTriggered|PhpunitErrorTriggered|PhpunitNoticeTriggered|PhpunitWarningTriggered|PhpWarningTriggered|WarningTriggered $reason, bool $single): string + { + if (!$reason instanceof DeprecationTriggered && + !$reason instanceof PhpDeprecationTriggered && + !$reason instanceof ErrorTriggered && + !$reason instanceof NoticeTriggered && + !$reason instanceof PhpNoticeTriggered && + !$reason instanceof WarningTriggered && + !$reason instanceof PhpWarningTriggered) { + return ''; + } + + return sprintf( + '%s%s:%d%s', + $single ? '' : ' ', + $reason->file(), + $reason->line(), + PHP_EOL, + ); + } +} diff --git a/src/TextUI/Output/Default/UnexpectedOutputPrinter.php b/src/TextUI/Output/Default/UnexpectedOutputPrinter.php new file mode 100644 index 00000000000..b51d51060fc --- /dev/null +++ b/src/TextUI/Output/Default/UnexpectedOutputPrinter.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default; + +use PHPUnit\Event\Facade; +use PHPUnit\Event\Test\PrintedUnexpectedOutput; +use PHPUnit\Event\Test\PrintedUnexpectedOutputSubscriber; +use PHPUnit\TextUI\Output\Printer; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class UnexpectedOutputPrinter implements PrintedUnexpectedOutputSubscriber +{ + private Printer $printer; + + public function __construct(Printer $printer, Facade $facade) + { + $this->printer = $printer; + + $facade->registerSubscriber($this); + } + + public function notify(PrintedUnexpectedOutput $event): void + { + $this->printer->print($event->output()); + } +} diff --git a/src/TextUI/Output/Facade.php b/src/TextUI/Output/Facade.php new file mode 100644 index 00000000000..2a50017b0b7 --- /dev/null +++ b/src/TextUI/Output/Facade.php @@ -0,0 +1,275 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output; + +use const PHP_EOL; +use function assert; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Logging\TeamCity\TeamCityLogger; +use PHPUnit\Logging\TestDox\TestResultCollection; +use PHPUnit\Runner\DirectoryDoesNotExistException; +use PHPUnit\TestRunner\TestResult\TestResult; +use PHPUnit\TextUI\CannotOpenSocketException; +use PHPUnit\TextUI\Configuration\Configuration; +use PHPUnit\TextUI\InvalidSocketException; +use PHPUnit\TextUI\Output\Default\ProgressPrinter\ProgressPrinter as DefaultProgressPrinter; +use PHPUnit\TextUI\Output\Default\ResultPrinter as DefaultResultPrinter; +use PHPUnit\TextUI\Output\Default\UnexpectedOutputPrinter; +use PHPUnit\TextUI\Output\TestDox\ResultPrinter as TestDoxResultPrinter; +use SebastianBergmann\Timer\Duration; +use SebastianBergmann\Timer\ResourceUsageFormatter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Facade +{ + private static ?Printer $printer = null; + private static ?DefaultResultPrinter $defaultResultPrinter = null; + private static ?TestDoxResultPrinter $testDoxResultPrinter = null; + private static ?SummaryPrinter $summaryPrinter = null; + private static bool $defaultProgressPrinter = false; + + public static function init(Configuration $configuration, bool $extensionReplacesProgressOutput, bool $extensionReplacesResultOutput): Printer + { + self::createPrinter($configuration); + + assert(self::$printer !== null); + + if ($configuration->debug()) { + return self::$printer; + } + + self::createUnexpectedOutputPrinter(); + + if (!$extensionReplacesProgressOutput) { + self::createProgressPrinter($configuration); + } + + if (!$extensionReplacesResultOutput) { + self::createResultPrinter($configuration); + self::createSummaryPrinter($configuration); + } + + if ($configuration->outputIsTeamCity()) { + new TeamCityLogger( + DefaultPrinter::standardOutput(), + EventFacade::instance(), + ); + } + + return self::$printer; + } + + /** + * @param ?array $testDoxResult + */ + public static function printResult(TestResult $result, ?array $testDoxResult, Duration $duration, bool $stackTraceForDeprecations): void + { + assert(self::$printer !== null); + + if ($result->numberOfTestsRun() > 0) { + if (self::$defaultProgressPrinter) { + self::$printer->print(PHP_EOL . PHP_EOL); + } + + self::$printer->print((new ResourceUsageFormatter)->resourceUsage($duration) . PHP_EOL . PHP_EOL); + } + + if (self::$testDoxResultPrinter !== null && $testDoxResult !== null) { + self::$testDoxResultPrinter->print($result, $testDoxResult); + } + + if (self::$defaultResultPrinter !== null) { + self::$defaultResultPrinter->print($result, $stackTraceForDeprecations); + } + + if (self::$summaryPrinter !== null) { + self::$summaryPrinter->print($result); + } + } + + /** + * @throws CannotOpenSocketException + * @throws DirectoryDoesNotExistException + * @throws InvalidSocketException + */ + public static function printerFor(string $target): Printer + { + if ($target === 'php://stdout') { + if (!self::$printer instanceof NullPrinter) { + return self::$printer; + } + + return DefaultPrinter::standardOutput(); + } + + return DefaultPrinter::from($target); + } + + private static function createPrinter(Configuration $configuration): void + { + $printerNeeded = false; + + if ($configuration->debug()) { + $printerNeeded = true; + } + + if ($configuration->outputIsTeamCity()) { + $printerNeeded = true; + } + + if ($configuration->outputIsTestDox()) { + $printerNeeded = true; + } + + if (!$configuration->noOutput() && !$configuration->noProgress()) { + $printerNeeded = true; + } + + if (!$configuration->noOutput() && !$configuration->noResults()) { + $printerNeeded = true; + } + + if ($printerNeeded) { + if ($configuration->outputToStandardErrorStream()) { + self::$printer = DefaultPrinter::standardError(); + + return; + } + + self::$printer = DefaultPrinter::standardOutput(); + + return; + } + + self::$printer = new NullPrinter; + } + + private static function createProgressPrinter(Configuration $configuration): void + { + assert(self::$printer !== null); + + if (!self::useDefaultProgressPrinter($configuration)) { + return; + } + + new DefaultProgressPrinter( + self::$printer, + EventFacade::instance(), + $configuration->colors(), + $configuration->columns(), + $configuration->source(), + ); + + self::$defaultProgressPrinter = true; + } + + private static function useDefaultProgressPrinter(Configuration $configuration): bool + { + if ($configuration->noOutput()) { + return false; + } + + if ($configuration->noProgress()) { + return false; + } + + if ($configuration->outputIsTeamCity()) { + return false; + } + + return true; + } + + private static function createResultPrinter(Configuration $configuration): void + { + assert(self::$printer !== null); + + if ($configuration->outputIsTestDox()) { + self::$defaultResultPrinter = new DefaultResultPrinter( + self::$printer, + $configuration->displayDetailsOnPhpunitDeprecations() || $configuration->displayDetailsOnAllIssues(), + true, + $configuration->displayDetailsOnPhpunitNotices() || $configuration->displayDetailsOnAllIssues(), + true, + false, + false, + true, + false, + false, + $configuration->displayDetailsOnTestsThatTriggerDeprecations() || $configuration->displayDetailsOnAllIssues(), + $configuration->displayDetailsOnTestsThatTriggerErrors() || $configuration->displayDetailsOnAllIssues(), + $configuration->displayDetailsOnTestsThatTriggerNotices() || $configuration->displayDetailsOnAllIssues(), + $configuration->displayDetailsOnTestsThatTriggerWarnings() || $configuration->displayDetailsOnAllIssues(), + $configuration->reverseDefectList(), + ); + } + + if ($configuration->outputIsTestDox()) { + self::$testDoxResultPrinter = new TestDoxResultPrinter( + self::$printer, + $configuration->colors(), + $configuration->columns(), + $configuration->testDoxOutputWithSummary(), + ); + } + + if ($configuration->noOutput() || $configuration->noResults()) { + return; + } + + if (self::$defaultResultPrinter !== null) { + return; + } + + self::$defaultResultPrinter = new DefaultResultPrinter( + self::$printer, + $configuration->displayDetailsOnPhpunitDeprecations() || $configuration->displayDetailsOnAllIssues(), + true, + $configuration->displayDetailsOnPhpunitNotices() || $configuration->displayDetailsOnAllIssues(), + true, + true, + true, + true, + $configuration->displayDetailsOnIncompleteTests() || $configuration->displayDetailsOnAllIssues(), + $configuration->displayDetailsOnSkippedTests() || $configuration->displayDetailsOnAllIssues(), + $configuration->displayDetailsOnTestsThatTriggerDeprecations() || $configuration->displayDetailsOnAllIssues(), + $configuration->displayDetailsOnTestsThatTriggerErrors() || $configuration->displayDetailsOnAllIssues(), + $configuration->displayDetailsOnTestsThatTriggerNotices() || $configuration->displayDetailsOnAllIssues(), + $configuration->displayDetailsOnTestsThatTriggerWarnings() || $configuration->displayDetailsOnAllIssues(), + $configuration->reverseDefectList(), + ); + } + + private static function createSummaryPrinter(Configuration $configuration): void + { + assert(self::$printer !== null); + + if (($configuration->noOutput() || $configuration->noResults()) && + !($configuration->outputIsTeamCity() || $configuration->outputIsTestDox())) { + return; + } + + self::$summaryPrinter = new SummaryPrinter( + self::$printer, + $configuration->colors(), + ); + } + + private static function createUnexpectedOutputPrinter(): void + { + assert(self::$printer !== null); + + new UnexpectedOutputPrinter(self::$printer, EventFacade::instance()); + } +} diff --git a/src/TextUI/Output/Printer/DefaultPrinter.php b/src/TextUI/Output/Printer/DefaultPrinter.php new file mode 100644 index 00000000000..382f4815403 --- /dev/null +++ b/src/TextUI/Output/Printer/DefaultPrinter.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output; + +use function assert; +use function count; +use function dirname; +use function explode; +use function fclose; +use function fopen; +use function fsockopen; +use function fwrite; +use function str_replace; +use function str_starts_with; +use PHPUnit\Runner\DirectoryDoesNotExistException; +use PHPUnit\TextUI\CannotOpenSocketException; +use PHPUnit\TextUI\InvalidSocketException; +use PHPUnit\Util\Filesystem; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class DefaultPrinter implements Printer +{ + /** + * @var closed-resource|resource + */ + private $stream; + private readonly bool $isPhpStream; + private bool $isOpen; + + /** + * @throws CannotOpenSocketException + * @throws DirectoryDoesNotExistException + * @throws InvalidSocketException + */ + public static function from(string $out): self + { + return new self($out); + } + + /** + * @throws CannotOpenSocketException + * @throws DirectoryDoesNotExistException + * @throws InvalidSocketException + */ + public static function standardOutput(): self + { + return new self('php://stdout'); + } + + /** + * @throws CannotOpenSocketException + * @throws DirectoryDoesNotExistException + * @throws InvalidSocketException + */ + public static function standardError(): self + { + return new self('php://stderr'); + } + + /** + * @throws CannotOpenSocketException + * @throws DirectoryDoesNotExistException + * @throws InvalidSocketException + */ + private function __construct(string $out) + { + $this->isPhpStream = str_starts_with($out, 'php://'); + + if (str_starts_with($out, 'socket://')) { + $tmp = explode(':', str_replace('socket://', '', $out)); + + if (count($tmp) !== 2) { + throw new InvalidSocketException($out); + } + + $stream = @fsockopen($tmp[0], (int) $tmp[1]); + + if ($stream === false) { + throw new CannotOpenSocketException($tmp[0], (int) $tmp[1]); + } + + $this->stream = $stream; + $this->isOpen = true; + + return; + } + + if (!$this->isPhpStream && !Filesystem::createDirectory(dirname($out))) { + throw new DirectoryDoesNotExistException(dirname($out)); + } + + $stream = fopen($out, 'wb'); + + assert($stream !== false); + + $this->stream = $stream; + $this->isOpen = true; + } + + public function print(string $buffer): void + { + assert($this->isOpen); + + fwrite($this->stream, $buffer); + } + + public function flush(): void + { + if ($this->isOpen && $this->isPhpStream) { + fclose($this->stream); + + $this->isOpen = false; + } + } +} diff --git a/src/TextUI/Output/Printer/NullPrinter.php b/src/TextUI/Output/Printer/NullPrinter.php new file mode 100644 index 00000000000..5e6b7ddf225 --- /dev/null +++ b/src/TextUI/Output/Printer/NullPrinter.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class NullPrinter implements Printer +{ + public function print(string $buffer): void + { + } + + public function flush(): void + { + } +} diff --git a/src/TextUI/Output/Printer/Printer.php b/src/TextUI/Output/Printer/Printer.php new file mode 100644 index 00000000000..c9b0fb97069 --- /dev/null +++ b/src/TextUI/Output/Printer/Printer.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface Printer +{ + public function print(string $buffer): void; + + public function flush(): void; +} diff --git a/src/TextUI/Output/SummaryPrinter.php b/src/TextUI/Output/SummaryPrinter.php new file mode 100644 index 00000000000..246fbf3ae1e --- /dev/null +++ b/src/TextUI/Output/SummaryPrinter.php @@ -0,0 +1,161 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\TestRunner\TestResult\TestResult; +use PHPUnit\Util\Color; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SummaryPrinter +{ + private readonly Printer $printer; + private readonly bool $colors; + private bool $countPrinted = false; + + public function __construct(Printer $printer, bool $colors) + { + $this->printer = $printer; + $this->colors = $colors; + } + + public function print(TestResult $result): void + { + if ($result->numberOfTestsRun() === 0) { + $this->printWithColor( + 'fg-black, bg-yellow', + 'No tests executed!', + ); + + return; + } + + if ($result->wasSuccessful() && + !$result->hasIssues() && + !$result->hasTestSuiteSkippedEvents() && + !$result->hasTestSkippedEvents()) { + $this->printWithColor( + 'fg-black, bg-green', + sprintf( + 'OK (%d test%s, %d assertion%s)', + $result->numberOfTestsRun(), + $result->numberOfTestsRun() === 1 ? '' : 's', + $result->numberOfAssertions(), + $result->numberOfAssertions() === 1 ? '' : 's', + ), + ); + + $this->printNumberOfIssuesIgnoredByBaseline($result); + + return; + } + + if ($result->wasSuccessful()) { + if ($result->hasIssues()) { + $color = 'fg-black, bg-yellow'; + + $this->printWithColor( + $color, + 'OK, but there were issues!', + ); + } else { + $color = 'fg-black, bg-green'; + + $this->printWithColor( + $color, + 'OK, but some tests were skipped!', + ); + } + } else { + $color = 'fg-white, bg-red'; + + if ($result->hasTestErroredEvents() || $result->hasTestTriggeredPhpunitErrorEvents()) { + $this->printWithColor( + 'fg-white, bg-red', + 'ERRORS!', + ); + } else { + $this->printWithColor( + 'fg-white, bg-red', + 'FAILURES!', + ); + } + } + + $this->printCountString($result->numberOfTestsRun(), 'Tests', $color, true); + $this->printCountString($result->numberOfAssertions(), 'Assertions', $color, true); + $this->printCountString($result->numberOfErrors(), 'Errors', $color); + $this->printCountString($result->numberOfTestFailedEvents(), 'Failures', $color); + $this->printCountString($result->numberOfPhpunitWarnings(), 'PHPUnit Warnings', $color); + $this->printCountString($result->numberOfWarnings(), 'Warnings', $color); + $this->printCountString($result->numberOfPhpOrUserDeprecations(), 'Deprecations', $color); + $this->printCountString($result->numberOfPhpunitDeprecations(), 'PHPUnit Deprecations', $color); + $this->printCountString($result->numberOfPhpunitNotices(), 'PHPUnit Notices', $color); + $this->printCountString($result->numberOfNotices(), 'Notices', $color); + $this->printCountString($result->numberOfTestSuiteSkippedEvents() + $result->numberOfTestSkippedEvents(), 'Skipped', $color); + $this->printCountString($result->numberOfTestMarkedIncompleteEvents(), 'Incomplete', $color); + $this->printCountString($result->numberOfTestsWithTestConsideredRiskyEvents(), 'Risky', $color); + $this->printWithColor($color, '.'); + + $this->printNumberOfIssuesIgnoredByBaseline($result); + } + + private function printCountString(int $count, string $name, string $color, bool $always = false): void + { + if ($always || $count > 0) { + $this->printWithColor( + $color, + sprintf( + '%s%s: %d', + $this->countPrinted ? ', ' : '', + $name, + $count, + ), + false, + ); + + $this->countPrinted = true; + } + } + + private function printWithColor(string $color, string $buffer, bool $lf = true): void + { + if ($this->colors) { + $buffer = Color::colorizeTextBox($color, $buffer); + } + + $this->printer->print($buffer); + + if ($lf) { + $this->printer->print(PHP_EOL); + } + } + + private function printNumberOfIssuesIgnoredByBaseline(TestResult $result): void + { + if ($result->hasIssuesIgnoredByBaseline()) { + $this->printer->print( + sprintf( + '%s%d issue%s %s ignored by baseline.%s', + PHP_EOL, + $result->numberOfIssuesIgnoredByBaseline(), + $result->numberOfIssuesIgnoredByBaseline() > 1 ? 's' : '', + $result->numberOfIssuesIgnoredByBaseline() > 1 ? 'were' : 'was', + PHP_EOL, + ), + ); + } + } +} diff --git a/src/TextUI/Output/TestDox/ResultPrinter.php b/src/TextUI/Output/TestDox/ResultPrinter.php new file mode 100644 index 00000000000..27ae3cede9e --- /dev/null +++ b/src/TextUI/Output/TestDox/ResultPrinter.php @@ -0,0 +1,502 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\TestDox; + +use const PHP_EOL; +use function array_map; +use function explode; +use function implode; +use function preg_match; +use function preg_split; +use function rtrim; +use function sprintf; +use function str_starts_with; +use function trim; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Test\AfterLastTestMethodErrored; +use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; +use PHPUnit\Framework\TestStatus\TestStatus; +use PHPUnit\Logging\TestDox\TestResult as TestDoxTestResult; +use PHPUnit\Logging\TestDox\TestResultCollection; +use PHPUnit\TestRunner\TestResult\TestResult; +use PHPUnit\TextUI\Output\Printer; +use PHPUnit\Util\Color; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ResultPrinter +{ + private Printer $printer; + private bool $colors; + private int $columns; + private bool $printSummary; + + public function __construct(Printer $printer, bool $colors, int $columns, bool $printSummary) + { + $this->printer = $printer; + $this->colors = $colors; + $this->columns = $columns; + $this->printSummary = $printSummary; + } + + /** + * @param array $tests + */ + public function print(TestResult $result, array $tests): void + { + $this->doPrint($tests, false); + + if ($this->printSummary) { + $this->printer->print('Summary of tests with errors, failures, or issues:' . PHP_EOL . PHP_EOL); + + $this->doPrint($tests, true); + } + + $beforeFirstTestMethodErrored = []; + $afterLastTestMethodErrored = []; + + foreach ($result->testErroredEvents() as $error) { + if ($error instanceof BeforeFirstTestMethodErrored) { + $beforeFirstTestMethodErrored[$error->calledMethod()->className() . '::' . $error->calledMethod()->methodName()] = $error; + } + + if ($error instanceof AfterLastTestMethodErrored) { + $afterLastTestMethodErrored[$error->calledMethod()->className() . '::' . $error->calledMethod()->methodName()] = $error; + } + } + + $this->printBeforeClassOrAfterClassErrors( + 'before-first-test', + $beforeFirstTestMethodErrored, + ); + + $this->printBeforeClassOrAfterClassErrors( + 'after-last-test', + $afterLastTestMethodErrored, + ); + } + + /** + * @param array $tests + */ + private function doPrint(array $tests, bool $onlySummary): void + { + foreach ($tests as $prettifiedClassName => $_tests) { + $print = true; + + if ($onlySummary) { + $found = false; + + foreach ($_tests as $test) { + if ($test->status()->isSuccess()) { + continue; + } + + $found = true; + + break; + } + + if (!$found) { + $print = false; + } + } + + if (!$print) { + continue; + } + + $this->printPrettifiedClassName($prettifiedClassName); + + foreach ($_tests as $test) { + if ($onlySummary && $test->status()->isSuccess()) { + continue; + } + + $this->printTestResult($test); + } + + $this->printer->print(PHP_EOL); + } + } + + private function printPrettifiedClassName(string $prettifiedClassName): void + { + $buffer = $prettifiedClassName; + + if ($this->colors) { + $buffer = Color::colorizeTextBox('underlined', $buffer); + } + + $this->printer->print($buffer . PHP_EOL); + } + + private function printTestResult(TestDoxTestResult $test): void + { + $this->printTestResultHeader($test); + $this->printTestResultBody($test); + } + + private function printTestResultHeader(TestDoxTestResult $test): void + { + $buffer = ' ' . $this->symbolFor($test->status()) . ' '; + + if ($this->colors) { + $this->printer->print( + Color::colorizeTextBox( + $this->colorFor($test->status()), + $buffer, + ), + ); + } else { + $this->printer->print($buffer); + } + + $this->printer->print($test->test()->testDox()->prettifiedMethodName($this->colors) . PHP_EOL); + } + + private function printTestResultBody(TestDoxTestResult $test): void + { + if ($test->status()->isSuccess()) { + return; + } + + if (!$test->hasThrowable()) { + return; + } + + $this->printTestResultBodyStart($test); + $this->printThrowable($test->status(), $test->throwable()); + $this->printTestResultBodyEnd($test); + } + + private function printTestResultBodyStart(TestDoxTestResult $test): void + { + $this->printer->print( + $this->prefixLines( + $this->prefixFor('start', $test->status()), + '', + ), + ); + + $this->printer->print(PHP_EOL); + } + + private function printTestResultBodyEnd(TestDoxTestResult $test): void + { + $this->printer->print(PHP_EOL); + + $this->printer->print( + $this->prefixLines( + $this->prefixFor('last', $test->status()), + '', + ), + ); + + $this->printer->print(PHP_EOL); + } + + private function printThrowable(TestStatus $status, Throwable $throwable): void + { + $message = trim($throwable->description()); + $stackTrace = $this->formatStackTrace($throwable->stackTrace()); + $diff = ''; + + if ($message !== '' && $this->colors) { + ['message' => $message, 'diff' => $diff] = $this->colorizeMessageAndDiff( + $message, + $this->messageColorFor($status), + ); + } + + if ($message !== '') { + $this->printer->print( + $this->prefixLines( + $this->prefixFor('message', $status), + $message, + ), + ); + + $this->printer->print(PHP_EOL); + } + + if ($diff !== '') { + $this->printer->print( + $this->prefixLines( + $this->prefixFor('diff', $status), + $diff, + ), + ); + + $this->printer->print(PHP_EOL); + } + + if ($stackTrace !== '') { + if ($message !== '' || $diff !== '') { + $tracePrefix = $this->prefixFor('default', $status); + } else { + $tracePrefix = $this->prefixFor('trace', $status); + } + + $this->printer->print( + $this->prefixLines($tracePrefix, PHP_EOL . $stackTrace), + ); + } + + if ($throwable->hasPrevious()) { + $this->printer->print(PHP_EOL); + + $this->printer->print( + $this->prefixLines( + $this->prefixFor('default', $status), + ' ', + ), + ); + + $this->printer->print(PHP_EOL); + + $this->printer->print( + $this->prefixLines( + $this->prefixFor('default', $status), + 'Caused by:', + ), + ); + + $this->printer->print(PHP_EOL); + + $this->printThrowable($status, $throwable->previous()); + } + } + + /** + * @return array{message: string, diff: string} + */ + private function colorizeMessageAndDiff(string $buffer, string $style): array + { + $lines = []; + + if ($buffer !== '') { + $lines = array_map('\rtrim', explode(PHP_EOL, $buffer)); + } + + $message = []; + $diff = []; + $insideDiff = false; + + foreach ($lines as $line) { + if ($line === '--- Expected') { + $insideDiff = true; + } + + if (!$insideDiff) { + $message[] = $line; + } else { + if (str_starts_with($line, '-')) { + $line = Color::colorize('fg-red', Color::visualizeWhitespace($line, true)); + } elseif (str_starts_with($line, '+')) { + $line = Color::colorize('fg-green', Color::visualizeWhitespace($line, true)); + } elseif ($line === '@@ @@') { + $line = Color::colorize('fg-cyan', $line); + } + + $diff[] = $line; + } + } + + $message = implode(PHP_EOL, $message); + $diff = implode(PHP_EOL, $diff); + + if ($message !== '') { + // Testdox output has a left-margin of 5; keep right-margin to prevent terminal scrolling + $message = Color::colorizeTextBox($style, $message, $this->columns - 7); + } + + return [ + 'message' => $message, + 'diff' => $diff, + ]; + } + + private function formatStackTrace(string $stackTrace): string + { + if (!$this->colors) { + return rtrim($stackTrace); + } + + $lines = []; + $previousPath = ''; + + foreach (explode(PHP_EOL, $stackTrace) as $line) { + if (preg_match('/^(.*):(\d+)$/', $line, $matches) > 0) { + $lines[] = Color::colorizePath($matches[1], $previousPath) . Color::dim(':') . Color::colorize('fg-blue', $matches[2]) . "\n"; + $previousPath = $matches[1]; + + continue; + } + + $lines[] = $line; + $previousPath = ''; + } + + return rtrim(implode('', $lines)); + } + + private function prefixLines(string $prefix, string $message): string + { + $lines = preg_split('/\r\n|\r|\n/', $message); + + if ($lines === false) { + $lines = []; + } + + return implode( + PHP_EOL, + array_map( + static fn (string $line) => ' ' . $prefix . ($line !== '' ? ' ' . $line : ''), + $lines, + ), + ); + } + + /** + * @param 'default'|'diff'|'last'|'message'|'start'|'trace' $type + */ + private function prefixFor(string $type, TestStatus $status): string + { + if (!$this->colors) { + return '│'; + } + + return Color::colorize( + $this->colorFor($status), + match ($type) { + 'default' => '│', + 'start' => '┐', + 'message' => '├', + 'diff' => '┊', + 'trace' => '╵', + 'last' => '┴', + }, + ); + } + + private function colorFor(TestStatus $status): string + { + if ($status->isSuccess()) { + return 'fg-green'; + } + + if ($status->isError()) { + return 'fg-yellow'; + } + + if ($status->isFailure()) { + return 'fg-red'; + } + + if ($status->isSkipped()) { + return 'fg-cyan'; + } + + if ($status->isIncomplete() || $status->isDeprecation() || $status->isNotice() || $status->isRisky() || $status->isWarning()) { + return 'fg-yellow'; + } + + return 'fg-blue'; + } + + private function messageColorFor(TestStatus $status): string + { + if ($status->isSuccess()) { + return ''; + } + + if ($status->isError()) { + return 'bg-yellow,fg-black'; + } + + if ($status->isFailure()) { + return 'bg-red,fg-white'; + } + + if ($status->isSkipped()) { + return 'fg-cyan'; + } + + if ($status->isIncomplete() || $status->isDeprecation() || $status->isNotice() || $status->isRisky() || $status->isWarning()) { + return 'fg-yellow'; + } + + return 'fg-white,bg-blue'; + } + + private function symbolFor(TestStatus $status): string + { + if ($status->isSuccess()) { + return '✔'; + } + + if ($status->isError() || $status->isFailure()) { + return '✘'; + } + + if ($status->isSkipped()) { + return '↩'; + } + + if ($status->isDeprecation() || $status->isNotice() || $status->isRisky() || $status->isWarning()) { + return '⚠'; + } + + if ($status->isIncomplete()) { + return '∅'; + } + + return '?'; + } + + /** + * @param 'after-last-test'|'before-first-test' $type + * @param array $errors + */ + private function printBeforeClassOrAfterClassErrors(string $type, array $errors): void + { + if ($errors === []) { + return; + } + + $this->printer->print( + sprintf( + 'These %s methods errored:' . PHP_EOL . PHP_EOL, + $type, + ), + ); + + $index = 0; + + foreach ($errors as $method => $error) { + $this->printer->print( + sprintf( + '%d) %s' . PHP_EOL, + ++$index, + $method, + ), + ); + + $this->printer->print(trim($error->throwable()->description()) . PHP_EOL . PHP_EOL); + $this->printer->print($this->formatStackTrace($error->throwable()->stackTrace()) . PHP_EOL); + } + + $this->printer->print(PHP_EOL); + } +} diff --git a/src/TextUI/ResultPrinter.php b/src/TextUI/ResultPrinter.php deleted file mode 100644 index 1d3aac9a632..00000000000 --- a/src/TextUI/ResultPrinter.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI; - -use PHPUnit\Framework\TestListener; -use PHPUnit\Framework\TestResult; - -interface ResultPrinter extends TestListener -{ - public function printResult(TestResult $result): void; - - public function write(string $buffer): void; -} diff --git a/src/TextUI/ShellExitCodeCalculator.php b/src/TextUI/ShellExitCodeCalculator.php new file mode 100644 index 00000000000..cc95d33c161 --- /dev/null +++ b/src/TextUI/ShellExitCodeCalculator.php @@ -0,0 +1,184 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +use PHPUnit\TestRunner\TestResult\TestResult; +use PHPUnit\TextUI\Configuration\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ShellExitCodeCalculator +{ + private const int SUCCESS_EXIT = 0; + private const int FAILURE_EXIT = 1; + private const int EXCEPTION_EXIT = 2; + + public function calculate(Configuration $configuration, TestResult $result): int + { + $failOnDeprecation = false; + $failOnPhpunitDeprecation = false; + $failOnPhpunitNotice = false; + $failOnPhpunitWarning = false; + $failOnEmptyTestSuite = false; + $failOnIncomplete = false; + $failOnNotice = false; + $failOnRisky = false; + $failOnSkipped = false; + $failOnWarning = false; + + if ($configuration->failOnAllIssues()) { + $failOnDeprecation = true; + $failOnPhpunitDeprecation = true; + $failOnPhpunitNotice = true; + $failOnPhpunitWarning = true; + $failOnEmptyTestSuite = true; + $failOnIncomplete = true; + $failOnNotice = true; + $failOnRisky = true; + $failOnSkipped = true; + $failOnWarning = true; + } + + if ($configuration->failOnDeprecation()) { + $failOnDeprecation = true; + } + + if ($configuration->doNotFailOnDeprecation()) { + $failOnDeprecation = false; + } + + if ($configuration->failOnPhpunitDeprecation()) { + $failOnPhpunitDeprecation = true; + } + + if ($configuration->doNotFailOnPhpunitDeprecation()) { + $failOnPhpunitDeprecation = false; + } + + if ($configuration->failOnPhpunitNotice()) { + $failOnPhpunitNotice = true; + } + + if ($configuration->doNotFailOnPhpunitNotice()) { + $failOnPhpunitNotice = false; + } + + if ($configuration->failOnPhpunitWarning()) { + $failOnPhpunitWarning = true; + } + + if ($configuration->doNotFailOnPhpunitWarning()) { + $failOnPhpunitWarning = false; + } + + if ($configuration->failOnEmptyTestSuite()) { + $failOnEmptyTestSuite = true; + } + + if ($configuration->doNotFailOnEmptyTestSuite()) { + $failOnEmptyTestSuite = false; + } + + if ($configuration->failOnIncomplete()) { + $failOnIncomplete = true; + } + + if ($configuration->doNotFailOnIncomplete()) { + $failOnIncomplete = false; + } + + if ($configuration->failOnNotice()) { + $failOnNotice = true; + } + + if ($configuration->doNotFailOnNotice()) { + $failOnNotice = false; + } + + if ($configuration->failOnRisky()) { + $failOnRisky = true; + } + + if ($configuration->doNotFailOnRisky()) { + $failOnRisky = false; + } + + if ($configuration->failOnSkipped()) { + $failOnSkipped = true; + } + + if ($configuration->doNotFailOnSkipped()) { + $failOnSkipped = false; + } + + if ($configuration->failOnWarning()) { + $failOnWarning = true; + } + + if ($configuration->doNotFailOnWarning()) { + $failOnWarning = false; + } + + $returnCode = self::FAILURE_EXIT; + + if ($result->wasSuccessful()) { + $returnCode = self::SUCCESS_EXIT; + } + + if ($failOnEmptyTestSuite && !$result->hasTests()) { + $returnCode = self::FAILURE_EXIT; + } + + if ($failOnDeprecation && $result->hasPhpOrUserDeprecations()) { + $returnCode = self::FAILURE_EXIT; + } + + if ($failOnPhpunitDeprecation && $result->hasPhpunitDeprecations()) { + $returnCode = self::FAILURE_EXIT; + } + + if ($failOnPhpunitNotice && $result->hasPhpunitNotices()) { + $returnCode = self::FAILURE_EXIT; + } + + if ($failOnPhpunitWarning && $result->hasPhpunitWarnings()) { + $returnCode = self::FAILURE_EXIT; + } + + if ($failOnIncomplete && $result->hasIncompleteTests()) { + $returnCode = self::FAILURE_EXIT; + } + + if ($failOnNotice && $result->hasNotices()) { + $returnCode = self::FAILURE_EXIT; + } + + if ($failOnRisky && $result->hasRiskyTests()) { + $returnCode = self::FAILURE_EXIT; + } + + if ($failOnSkipped && $result->hasSkippedTests()) { + $returnCode = self::FAILURE_EXIT; + } + + if ($failOnWarning && $result->hasWarnings()) { + $returnCode = self::FAILURE_EXIT; + } + + if ($result->hasErrors()) { + $returnCode = self::EXCEPTION_EXIT; + } + + return $returnCode; + } +} diff --git a/src/TextUI/TestRunner.php b/src/TextUI/TestRunner.php index f0448043161..6e6107b5f2f 100644 --- a/src/TextUI/TestRunner.php +++ b/src/TextUI/TestRunner.php @@ -9,1232 +9,68 @@ */ namespace PHPUnit\TextUI; -use const PHP_EOL; -use const PHP_SAPI; -use const PHP_VERSION; -use function array_diff; -use function assert; -use function class_exists; -use function count; -use function dirname; -use function file_put_contents; -use function htmlspecialchars; -use function is_array; -use function is_int; -use function is_string; use function mt_srand; -use function range; -use function realpath; -use function sprintf; -use function time; -use PHPUnit\Framework\Exception; -use PHPUnit\Framework\TestResult; +use PHPUnit\Event; use PHPUnit\Framework\TestSuite; -use PHPUnit\Runner\AfterLastTestHook; -use PHPUnit\Runner\BaseTestRunner; -use PHPUnit\Runner\BeforeFirstTestHook; -use PHPUnit\Runner\DefaultTestResultCache; -use PHPUnit\Runner\Filter\ExcludeGroupFilterIterator; -use PHPUnit\Runner\Filter\Factory; -use PHPUnit\Runner\Filter\IncludeGroupFilterIterator; -use PHPUnit\Runner\Filter\NameFilterIterator; -use PHPUnit\Runner\Hook; -use PHPUnit\Runner\NullTestResultCache; -use PHPUnit\Runner\ResultCacheExtension; -use PHPUnit\Runner\StandardTestSuiteLoader; -use PHPUnit\Runner\TestHook; -use PHPUnit\Runner\TestListenerAdapter; -use PHPUnit\Runner\TestSuiteLoader; +use PHPUnit\Runner\ResultCache\ResultCache; use PHPUnit\Runner\TestSuiteSorter; -use PHPUnit\Runner\Version; -use PHPUnit\TextUI\XmlConfiguration\Configuration; -use PHPUnit\TextUI\XmlConfiguration\ExtensionHandler; -use PHPUnit\TextUI\XmlConfiguration\Loader; -use PHPUnit\TextUI\XmlConfiguration\PhpHandler; -use PHPUnit\Util\Filesystem; -use PHPUnit\Util\Log\JUnit; -use PHPUnit\Util\Log\TeamCity; -use PHPUnit\Util\Printer; -use PHPUnit\Util\TestDox\CliTestDoxPrinter; -use PHPUnit\Util\TestDox\HtmlResultPrinter; -use PHPUnit\Util\TestDox\TextResultPrinter; -use PHPUnit\Util\TestDox\XmlResultPrinter; -use PHPUnit\Util\XdebugFilterScriptGenerator; -use ReflectionClass; -use ReflectionException; -use SebastianBergmann\CodeCoverage\CodeCoverage; -use SebastianBergmann\CodeCoverage\Driver\Driver; -use SebastianBergmann\CodeCoverage\Exception as CodeCoverageException; -use SebastianBergmann\CodeCoverage\Filter as CodeCoverageFilter; -use SebastianBergmann\CodeCoverage\Report\Clover as CloverReport; -use SebastianBergmann\CodeCoverage\Report\Crap4j as Crap4jReport; -use SebastianBergmann\CodeCoverage\Report\Html\Facade as HtmlReport; -use SebastianBergmann\CodeCoverage\Report\PHP as PhpReport; -use SebastianBergmann\CodeCoverage\Report\Text as TextReport; -use SebastianBergmann\CodeCoverage\Report\Xml\Facade as XmlReport; -use SebastianBergmann\Comparator\Comparator; -use SebastianBergmann\Environment\Runtime; -use SebastianBergmann\Invoker\Invoker; -use SebastianBergmann\Timer\Timer; +use PHPUnit\TextUI\Configuration\Configuration; +use Throwable; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class TestRunner extends BaseTestRunner +final class TestRunner { - public const SUCCESS_EXIT = 0; - - public const FAILURE_EXIT = 1; - - public const EXCEPTION_EXIT = 2; - - /** - * @var bool - */ - private static $versionStringPrinted = false; - - /** - * @var CodeCoverageFilter - */ - private $codeCoverageFilter; - - /** - * @var TestSuiteLoader - */ - private $loader; - - /** - * @var ResultPrinter - */ - private $printer; - - /** - * @var bool - */ - private $messagePrinted = false; - - /** - * @var Hook[] - */ - private $extensions = []; - - /** - * @var Timer - */ - private $timer; - - public function __construct(TestSuiteLoader $loader = null, CodeCoverageFilter $filter = null) - { - if ($filter === null) { - $filter = new CodeCoverageFilter; - } - - $this->codeCoverageFilter = $filter; - $this->loader = $loader; - $this->timer = new Timer; - } - - /** - * @throws \PHPUnit\Runner\Exception - * @throws Exception - */ - public function run(TestSuite $suite, array $arguments = [], array $warnings = [], bool $exit = true): TestResult - { - if (isset($arguments['configuration'])) { - $GLOBALS['__PHPUNIT_CONFIGURATION_FILE'] = $arguments['configuration']; - } - - $this->handleConfiguration($arguments); - - if (is_int($arguments['columns']) && $arguments['columns'] < 16) { - $arguments['columns'] = 16; - $tooFewColumnsRequested = true; - } - - if (isset($arguments['bootstrap'])) { - $GLOBALS['__PHPUNIT_BOOTSTRAP'] = $arguments['bootstrap']; - } - - if ($arguments['backupGlobals'] === true) { - $suite->setBackupGlobals(true); - } - - if ($arguments['backupStaticAttributes'] === true) { - $suite->setBackupStaticAttributes(true); - } - - if ($arguments['beStrictAboutChangesToGlobalState'] === true) { - $suite->setBeStrictAboutChangesToGlobalState(true); - } - - if ($arguments['executionOrder'] === TestSuiteSorter::ORDER_RANDOMIZED) { - mt_srand($arguments['randomOrderSeed']); - } - - if ($arguments['cacheResult']) { - if (!isset($arguments['cacheResultFile'])) { - if (isset($arguments['configuration'])) { - assert($arguments['configuration'] instanceof Configuration); - - $cacheLocation = $arguments['configuration']->filename(); - } else { - $cacheLocation = $_SERVER['PHP_SELF']; - } - - $arguments['cacheResultFile'] = null; - - $cacheResultFile = realpath($cacheLocation); - - if ($cacheResultFile !== false) { - $arguments['cacheResultFile'] = dirname($cacheResultFile); - } - } - - $cache = new DefaultTestResultCache($arguments['cacheResultFile']); - - $this->addExtension(new ResultCacheExtension($cache)); - } - - if ($arguments['executionOrder'] !== TestSuiteSorter::ORDER_DEFAULT || $arguments['executionOrderDefects'] !== TestSuiteSorter::ORDER_DEFAULT || $arguments['resolveDependencies']) { - $cache = $cache ?? new NullTestResultCache; - - $cache->load(); - - $sorter = new TestSuiteSorter($cache); - - $sorter->reorderTestsInSuite($suite, $arguments['executionOrder'], $arguments['resolveDependencies'], $arguments['executionOrderDefects']); - $originalExecutionOrder = $sorter->getOriginalExecutionOrder(); - - unset($sorter); - } - - if (is_int($arguments['repeat']) && $arguments['repeat'] > 0) { - $_suite = new TestSuite; - - /* @noinspection PhpUnusedLocalVariableInspection */ - foreach (range(1, $arguments['repeat']) as $step) { - $_suite->addTest($suite); - } - - $suite = $_suite; - - unset($_suite); - } - - $result = $this->createTestResult(); - - $listener = new TestListenerAdapter; - $listenerNeeded = false; - - foreach ($this->extensions as $extension) { - if ($extension instanceof TestHook) { - $listener->add($extension); - - $listenerNeeded = true; - } - } - - if ($listenerNeeded) { - $result->addListener($listener); - } - - unset($listener, $listenerNeeded); - - if (!$arguments['convertDeprecationsToExceptions']) { - $result->convertDeprecationsToExceptions(false); - } - - if (!$arguments['convertErrorsToExceptions']) { - $result->convertErrorsToExceptions(false); - } - - if (!$arguments['convertNoticesToExceptions']) { - $result->convertNoticesToExceptions(false); - } - - if (!$arguments['convertWarningsToExceptions']) { - $result->convertWarningsToExceptions(false); - } - - if ($arguments['stopOnError']) { - $result->stopOnError(true); - } - - if ($arguments['stopOnFailure']) { - $result->stopOnFailure(true); - } - - if ($arguments['stopOnWarning']) { - $result->stopOnWarning(true); - } - - if ($arguments['stopOnIncomplete']) { - $result->stopOnIncomplete(true); - } - - if ($arguments['stopOnRisky']) { - $result->stopOnRisky(true); - } - - if ($arguments['stopOnSkipped']) { - $result->stopOnSkipped(true); - } - - if ($arguments['stopOnDefect']) { - $result->stopOnDefect(true); - } - - if ($arguments['registerMockObjectsFromTestArgumentsRecursively']) { - $result->setRegisterMockObjectsFromTestArgumentsRecursively(true); - } - - if ($this->printer === null) { - if (isset($arguments['printer'])) { - if ($arguments['printer'] instanceof ResultPrinter) { - $this->printer = $arguments['printer']; - } elseif (is_string($arguments['printer']) && class_exists($arguments['printer'], false)) { - try { - $reflector = new ReflectionClass($arguments['printer']); - - if ($reflector->implementsInterface(ResultPrinter::class)) { - $this->printer = $this->createPrinter($arguments['printer'], $arguments); - } - - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - } - } else { - $this->printer = $this->createPrinter(DefaultResultPrinter::class, $arguments); - } - } - - if (isset($originalExecutionOrder) && $this->printer instanceof CliTestDoxPrinter) { - assert($this->printer instanceof CliTestDoxPrinter); - - $this->printer->setOriginalExecutionOrder($originalExecutionOrder); - $this->printer->setShowProgressAnimation(!$arguments['noInteraction']); - } - - $this->printer->write( - Version::getVersionString() . "\n" - ); - - self::$versionStringPrinted = true; - - foreach ($arguments['listeners'] as $listener) { - $result->addListener($listener); - } - - $result->addListener($this->printer); - - $coverageFilterFromConfigurationFile = false; - $coverageFilterFromOption = false; - $codeCoverageReports = 0; - - if (!isset($arguments['noLogging'])) { - if (isset($arguments['testdoxHTMLFile'])) { - $result->addListener( - new HtmlResultPrinter( - $arguments['testdoxHTMLFile'], - $arguments['testdoxGroups'], - $arguments['testdoxExcludeGroups'] - ) - ); - } - - if (isset($arguments['testdoxTextFile'])) { - $result->addListener( - new TextResultPrinter( - $arguments['testdoxTextFile'], - $arguments['testdoxGroups'], - $arguments['testdoxExcludeGroups'] - ) - ); - } - - if (isset($arguments['testdoxXMLFile'])) { - $result->addListener( - new XmlResultPrinter( - $arguments['testdoxXMLFile'] - ) - ); - } - - if (isset($arguments['teamcityLogfile'])) { - $result->addListener( - new TeamCity($arguments['teamcityLogfile']) - ); - } - - if (isset($arguments['junitLogfile'])) { - $result->addListener( - new JUnit( - $arguments['junitLogfile'], - $arguments['reportUselessTests'] - ) - ); - } - - if (isset($arguments['coverageClover'])) { - $codeCoverageReports++; - } - - if (isset($arguments['coverageCrap4J'])) { - $codeCoverageReports++; - } - - if (isset($arguments['coverageHtml'])) { - $codeCoverageReports++; - } - - if (isset($arguments['coveragePHP'])) { - $codeCoverageReports++; - } - - if (isset($arguments['coverageText'])) { - $codeCoverageReports++; - } - - if (isset($arguments['coverageXml'])) { - $codeCoverageReports++; - } - } - - if (isset($arguments['noCoverage'])) { - $codeCoverageReports = 0; - } - - if ($codeCoverageReports > 0 || isset($arguments['xdebugFilterFile'])) { - if (isset($arguments['coverageFilter'])) { - if (!is_array($arguments['coverageFilter'])) { - $coverageFilterDirectories = [$arguments['coverageFilter']]; - } else { - $coverageFilterDirectories = $arguments['coverageFilter']; - } - - foreach ($coverageFilterDirectories as $coverageFilterDirectory) { - $this->codeCoverageFilter->includeDirectory($coverageFilterDirectory); - } - - $coverageFilterFromOption = true; - } - - if (isset($arguments['configuration'])) { - assert($arguments['configuration'] instanceof Configuration); - - $codeCoverageConfiguration = $arguments['configuration']->codeCoverage(); - - if ($codeCoverageConfiguration->hasNonEmptyListOfFilesToBeIncludedInCodeCoverageReport()) { - $coverageFilterFromConfigurationFile = true; - - foreach ($codeCoverageConfiguration->directories() as $directory) { - $this->codeCoverageFilter->includeDirectory( - $directory->path(), - $directory->suffix(), - $directory->prefix() - ); - } - - foreach ($codeCoverageConfiguration->files() as $file) { - $this->codeCoverageFilter->includeFile($file->path()); - } - - foreach ($codeCoverageConfiguration->excludeDirectories() as $directory) { - $this->codeCoverageFilter->excludeDirectory( - $directory->path(), - $directory->suffix(), - $directory->prefix() - ); - } - - foreach ($codeCoverageConfiguration->excludeFiles() as $file) { - $this->codeCoverageFilter->excludeFile($file->path()); - } - } - } - } - - if ($codeCoverageReports > 0) { - try { - if (isset($codeCoverageConfiguration) && $codeCoverageConfiguration->pathCoverage()) { - $codeCoverageDriver = Driver::forLineAndPathCoverage($this->codeCoverageFilter); - } else { - $codeCoverageDriver = Driver::forLineCoverage($this->codeCoverageFilter); - } - - $codeCoverage = new CodeCoverage( - $codeCoverageDriver, - $this->codeCoverageFilter - ); - - $codeCoverage->excludeSubclassesOfThisClassFromUnintentionallyCoveredCodeCheck(Comparator::class); - - if ($arguments['strictCoverage']) { - $codeCoverage->enableCheckForUnintentionallyCoveredCode(); - } - - if (isset($arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'])) { - if ($arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage']) { - $codeCoverage->ignoreDeprecatedCode(); - } else { - $codeCoverage->doNotIgnoreDeprecatedCode(); - } - } - - if (isset($arguments['disableCodeCoverageIgnore'])) { - if ($arguments['disableCodeCoverageIgnore']) { - $codeCoverage->disableAnnotationsForIgnoringCode(); - } else { - $codeCoverage->enableAnnotationsForIgnoringCode(); - } - } - - if (isset($arguments['configuration'])) { - assert($arguments['configuration'] instanceof Configuration); - - $codeCoverageConfiguration = $arguments['configuration']->codeCoverage(); - - if ($codeCoverageConfiguration->hasNonEmptyListOfFilesToBeIncludedInCodeCoverageReport()) { - if ($codeCoverageConfiguration->includeUncoveredFiles()) { - $codeCoverage->includeUncoveredFiles(); - } else { - $codeCoverage->excludeUncoveredFiles(); - } - - if ($codeCoverageConfiguration->processUncoveredFiles()) { - $codeCoverage->processUncoveredFiles(); - } else { - $codeCoverage->doNotProcessUncoveredFiles(); - } - } - } - - if ($this->codeCoverageFilter->isEmpty()) { - if (!$coverageFilterFromConfigurationFile && !$coverageFilterFromOption) { - $warnings[] = 'No filter is configured, code coverage will not be processed'; - } else { - $warnings[] = 'Incorrect filter configuration, code coverage will not be processed'; - } - - $codeCoverageReports = 0; - - unset($codeCoverage); - } - } catch (CodeCoverageException $e) { - $warnings[] = $e->getMessage(); - - $codeCoverageReports = 0; - } - } - - if ($arguments['verbose']) { - if (PHP_SAPI === 'phpdbg') { - $this->writeMessage('Runtime', 'PHPDBG ' . PHP_VERSION); - } else { - $runtime = 'PHP ' . PHP_VERSION; - - if (isset($codeCoverageDriver)) { - $runtime .= ' with ' . $codeCoverageDriver->nameAndVersion(); - } - - $this->writeMessage('Runtime', $runtime); - } - - if (isset($arguments['configuration'])) { - assert($arguments['configuration'] instanceof Configuration); - - $this->writeMessage( - 'Configuration', - $arguments['configuration']->filename() - ); - } - - foreach ($arguments['loadedExtensions'] as $extension) { - $this->writeMessage( - 'Extension', - $extension - ); - } - - foreach ($arguments['notLoadedExtensions'] as $extension) { - $this->writeMessage( - 'Extension', - $extension - ); - } - } - - if ($arguments['executionOrder'] === TestSuiteSorter::ORDER_RANDOMIZED) { - $this->writeMessage( - 'Random Seed', - (string) $arguments['randomOrderSeed'] - ); - } - - if (isset($tooFewColumnsRequested)) { - $warnings[] = 'Less than 16 columns requested, number of columns set to 16'; - } - - if ((new Runtime)->discardsComments()) { - $warnings[] = 'opcache.save_comments=0 set; annotations will not work'; - } - - if (isset($arguments['conflictBetweenPrinterClassAndTestdox'])) { - $warnings[] = 'Directives printerClass and testdox are mutually exclusive'; - } - - foreach ($warnings as $warning) { - $this->writeMessage('Warning', $warning); - } - - if (isset($arguments['configuration'])) { - assert($arguments['configuration'] instanceof Configuration); - - if ($arguments['configuration']->hasValidationErrors()) { - $this->write( - "\n Warning - The configuration file did not pass validation!\n The following problems have been detected:\n" - ); - - foreach ($arguments['configuration']->validationErrors() as $line => $errors) { - $this->write(sprintf("\n Line %d:\n", $line)); - - foreach ($errors as $msg) { - $this->write(sprintf(" - %s\n", $msg)); - } - } - - $this->write("\n Test results may not be as expected.\n\n"); - } - } - - if (isset($arguments['xdebugFilterFile'], $codeCoverageConfiguration)) { - $this->write(PHP_EOL . 'Please note that --dump-xdebug-filter and --prepend are deprecated and will be removed in PHPUnit 10.' . PHP_EOL); - - $script = (new XdebugFilterScriptGenerator)->generate($codeCoverageConfiguration); - - if ($arguments['xdebugFilterFile'] !== 'php://stdout' && $arguments['xdebugFilterFile'] !== 'php://stderr' && !Filesystem::createDirectory(dirname($arguments['xdebugFilterFile']))) { - $this->write(sprintf('Cannot write Xdebug filter script to %s ' . PHP_EOL, $arguments['xdebugFilterFile'])); - - exit(self::EXCEPTION_EXIT); - } - - file_put_contents($arguments['xdebugFilterFile'], $script); - - $this->write(sprintf('Wrote Xdebug filter script to %s ' . PHP_EOL . PHP_EOL, $arguments['xdebugFilterFile'])); - - exit(self::SUCCESS_EXIT); - } - - $this->printer->write("\n"); - - if (isset($codeCoverage)) { - $result->setCodeCoverage($codeCoverage); - - if ($codeCoverageReports > 1 && isset($arguments['cacheTokens'])) { - if ($arguments['cacheTokens']) { - $codeCoverage->enableTokenCaching(); - } else { - $codeCoverage->disableTokenCaching(); - } - } - } - - $result->beStrictAboutTestsThatDoNotTestAnything($arguments['reportUselessTests']); - $result->beStrictAboutOutputDuringTests($arguments['disallowTestOutput']); - $result->beStrictAboutTodoAnnotatedTests($arguments['disallowTodoAnnotatedTests']); - $result->beStrictAboutResourceUsageDuringSmallTests($arguments['beStrictAboutResourceUsageDuringSmallTests']); - - if ($arguments['enforceTimeLimit'] === true && !(new Invoker)->canInvokeWithTimeout()) { - $this->writeMessage('Error', 'PHP extension pcntl is required for enforcing time limits'); - } - - $result->enforceTimeLimit($arguments['enforceTimeLimit']); - $result->setDefaultTimeLimit($arguments['defaultTimeLimit']); - $result->setTimeoutForSmallTests($arguments['timeoutForSmallTests']); - $result->setTimeoutForMediumTests($arguments['timeoutForMediumTests']); - $result->setTimeoutForLargeTests($arguments['timeoutForLargeTests']); - - if (isset($arguments['forceCoversAnnotation']) && $arguments['forceCoversAnnotation'] === true) { - $result->forceCoversAnnotation(); - } - - $this->processSuiteFilters($suite, $arguments); - $suite->setRunTestInSeparateProcess($arguments['processIsolation']); - - foreach ($this->extensions as $extension) { - if ($extension instanceof BeforeFirstTestHook) { - $extension->executeBeforeFirstTest(); - } - } - - $testSuiteWarningsPrinted = false; - - foreach ($suite->warnings() as $warning) { - $this->writeMessage('Warning', $warning); - - $testSuiteWarningsPrinted = true; - } - - if ($testSuiteWarningsPrinted) { - $this->write(PHP_EOL); - } - - $suite->run($result); - - foreach ($this->extensions as $extension) { - if ($extension instanceof AfterLastTestHook) { - $extension->executeAfterLastTest(); - } - } - - $result->flushListeners(); - $this->printer->printResult($result); - - if (isset($codeCoverage)) { - if (isset($arguments['coverageClover'])) { - $this->codeCoverageGenerationStart('Clover XML'); - - try { - $writer = new CloverReport; - $writer->process($codeCoverage, $arguments['coverageClover']); - - $this->codeCoverageGenerationSucceeded(); - - unset($writer); - } catch (CodeCoverageException $e) { - $this->codeCoverageGenerationFailed($e); - } - } - - if (isset($arguments['coverageCrap4J'])) { - $this->codeCoverageGenerationStart('Crap4J XML'); - - try { - $writer = new Crap4jReport($arguments['crap4jThreshold']); - $writer->process($codeCoverage, $arguments['coverageCrap4J']); - - $this->codeCoverageGenerationSucceeded(); - - unset($writer); - } catch (CodeCoverageException $e) { - $this->codeCoverageGenerationFailed($e); - } - } - - if (isset($arguments['coverageHtml'])) { - $this->codeCoverageGenerationStart('HTML'); - - try { - $writer = new HtmlReport( - $arguments['reportLowUpperBound'], - $arguments['reportHighLowerBound'], - sprintf( - ' and PHPUnit %s', - Version::id() - ) - ); - - $writer->process($codeCoverage, $arguments['coverageHtml']); - - $this->codeCoverageGenerationSucceeded(); - - unset($writer); - } catch (CodeCoverageException $e) { - $this->codeCoverageGenerationFailed($e); - } - } - - if (isset($arguments['coveragePHP'])) { - $this->codeCoverageGenerationStart('PHP'); - - try { - $writer = new PhpReport; - $writer->process($codeCoverage, $arguments['coveragePHP']); - - $this->codeCoverageGenerationSucceeded(); - - unset($writer); - } catch (CodeCoverageException $e) { - $this->codeCoverageGenerationFailed($e); - } - } - - if (isset($arguments['coverageText'])) { - if ($arguments['coverageText'] === 'php://stdout') { - $outputStream = $this->printer; - $colors = $arguments['colors'] && $arguments['colors'] !== DefaultResultPrinter::COLOR_NEVER; - } else { - $outputStream = new Printer($arguments['coverageText']); - $colors = false; - } - - $processor = new TextReport( - $arguments['reportLowUpperBound'], - $arguments['reportHighLowerBound'], - $arguments['coverageTextShowUncoveredFiles'], - $arguments['coverageTextShowOnlySummary'] - ); - - $outputStream->write( - $processor->process($codeCoverage, $colors) - ); - } - - if (isset($arguments['coverageXml'])) { - $this->codeCoverageGenerationStart('PHPUnit XML'); - - try { - $writer = new XmlReport(Version::id()); - $writer->process($codeCoverage, $arguments['coverageXml']); - - $this->codeCoverageGenerationSucceeded(); - - unset($writer); - } catch (CodeCoverageException $e) { - $this->codeCoverageGenerationFailed($e); - } - } - } - - if ($exit) { - if (isset($arguments['failOnEmptyTestSuite']) && $arguments['failOnEmptyTestSuite'] === true && count($result) === 0) { - exit(self::FAILURE_EXIT); - } - - if ($result->wasSuccessfulIgnoringWarnings()) { - if ($arguments['failOnRisky'] && !$result->allHarmless()) { - exit(self::FAILURE_EXIT); - } - - if ($arguments['failOnWarning'] && $result->warningCount() > 0) { - exit(self::FAILURE_EXIT); - } - - if ($arguments['failOnIncomplete'] && $result->notImplementedCount() > 0) { - exit(self::FAILURE_EXIT); - } - - if ($arguments['failOnSkipped'] && $result->skippedCount() > 0) { - exit(self::FAILURE_EXIT); - } - - exit(self::SUCCESS_EXIT); - } - - if ($result->errorCount() > 0) { - exit(self::EXCEPTION_EXIT); - } - - if ($result->failureCount() > 0) { - exit(self::FAILURE_EXIT); - } - } - - return $result; - } - /** - * Returns the loader to be used. + * @throws RuntimeException */ - public function getLoader(): TestSuiteLoader + public function run(Configuration $configuration, ResultCache $resultCache, TestSuite $suite): void { - if ($this->loader === null) { - $this->loader = new StandardTestSuiteLoader; - } - - return $this->loader; - } - - public function addExtension(Hook $extension): void - { - $this->extensions[] = $extension; - } - - /** - * Override to define how to handle a failed loading of - * a test suite. - */ - protected function runFailed(string $message): void - { - $this->write($message . PHP_EOL); - - exit(self::FAILURE_EXIT); - } - - private function createTestResult(): TestResult - { - return new TestResult; - } + try { + Event\Facade::emitter()->testRunnerStarted(); - private function write(string $buffer): void - { - if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') { - $buffer = htmlspecialchars($buffer); - } - - if ($this->printer !== null) { - $this->printer->write($buffer); - } else { - print $buffer; - } - } - - /** - * @throws Exception - */ - private function handleConfiguration(array &$arguments): void - { - if (isset($arguments['configuration']) && - !$arguments['configuration'] instanceof Configuration) { - $arguments['configuration'] = (new Loader)->load($arguments['configuration']); - } - - $arguments['debug'] = $arguments['debug'] ?? false; - $arguments['filter'] = $arguments['filter'] ?? false; - $arguments['listeners'] = $arguments['listeners'] ?? []; - - if (isset($arguments['configuration'])) { - (new PhpHandler)->handle($arguments['configuration']->php()); - - $codeCoverageConfiguration = $arguments['configuration']->codeCoverage(); - - if (!isset($arguments['coverageClover']) && $codeCoverageConfiguration->hasClover()) { - $arguments['coverageClover'] = $codeCoverageConfiguration->clover()->target()->path(); - } - - if (!isset($arguments['coverageCrap4J']) && $codeCoverageConfiguration->hasCrap4j()) { - $arguments['coverageCrap4J'] = $codeCoverageConfiguration->crap4j()->target()->path(); - - if (!isset($arguments['crap4jThreshold'])) { - $arguments['crap4jThreshold'] = $codeCoverageConfiguration->crap4j()->threshold(); - } + if ($configuration->executionOrder() === TestSuiteSorter::ORDER_RANDOMIZED) { + mt_srand($configuration->randomOrderSeed()); } - if (!isset($arguments['coverageHtml']) && $codeCoverageConfiguration->hasHtml()) { - $arguments['coverageHtml'] = $codeCoverageConfiguration->html()->target()->path(); - - if (!isset($arguments['reportLowUpperBound'])) { - $arguments['reportLowUpperBound'] = $codeCoverageConfiguration->html()->lowUpperBound(); - } - - if (!isset($arguments['reportHighLowerBound'])) { - $arguments['reportHighLowerBound'] = $codeCoverageConfiguration->html()->highLowerBound(); - } - } - - if (!isset($arguments['coveragePHP']) && $codeCoverageConfiguration->hasPhp()) { - $arguments['coveragePHP'] = $codeCoverageConfiguration->php()->target()->path(); - } - - if (!isset($arguments['coverageText']) && $codeCoverageConfiguration->hasText()) { - $arguments['coverageText'] = $codeCoverageConfiguration->text()->target()->path(); - $arguments['coverageTextShowUncoveredFiles'] = $codeCoverageConfiguration->text()->showUncoveredFiles(); - $arguments['coverageTextShowOnlySummary'] = $codeCoverageConfiguration->text()->showOnlySummary(); - } - - if (!isset($arguments['coverageXml']) && $codeCoverageConfiguration->hasXml()) { - $arguments['coverageXml'] = $codeCoverageConfiguration->xml()->target()->path(); - } + if ($configuration->executionOrder() !== TestSuiteSorter::ORDER_DEFAULT || + $configuration->executionOrderDefects() !== TestSuiteSorter::ORDER_DEFAULT || + $configuration->resolveDependencies()) { + $resultCache->load(); - $phpunitConfiguration = $arguments['configuration']->phpunit(); - - $arguments['backupGlobals'] = $arguments['backupGlobals'] ?? $phpunitConfiguration->backupGlobals(); - $arguments['backupStaticAttributes'] = $arguments['backupStaticAttributes'] ?? $phpunitConfiguration->backupStaticAttributes(); - $arguments['beStrictAboutChangesToGlobalState'] = $arguments['beStrictAboutChangesToGlobalState'] ?? $phpunitConfiguration->beStrictAboutChangesToGlobalState(); - $arguments['cacheResult'] = $arguments['cacheResult'] ?? $phpunitConfiguration->cacheResult(); - $arguments['cacheTokens'] = $arguments['cacheTokens'] ?? $codeCoverageConfiguration->cacheTokens(); - $arguments['colors'] = $arguments['colors'] ?? $phpunitConfiguration->colors(); - $arguments['convertDeprecationsToExceptions'] = $arguments['convertDeprecationsToExceptions'] ?? $phpunitConfiguration->convertDeprecationsToExceptions(); - $arguments['convertErrorsToExceptions'] = $arguments['convertErrorsToExceptions'] ?? $phpunitConfiguration->convertErrorsToExceptions(); - $arguments['convertNoticesToExceptions'] = $arguments['convertNoticesToExceptions'] ?? $phpunitConfiguration->convertNoticesToExceptions(); - $arguments['convertWarningsToExceptions'] = $arguments['convertWarningsToExceptions'] ?? $phpunitConfiguration->convertWarningsToExceptions(); - $arguments['processIsolation'] = $arguments['processIsolation'] ?? $phpunitConfiguration->processIsolation(); - $arguments['stopOnDefect'] = $arguments['stopOnDefect'] ?? $phpunitConfiguration->stopOnDefect(); - $arguments['stopOnError'] = $arguments['stopOnError'] ?? $phpunitConfiguration->stopOnError(); - $arguments['stopOnFailure'] = $arguments['stopOnFailure'] ?? $phpunitConfiguration->stopOnFailure(); - $arguments['stopOnWarning'] = $arguments['stopOnWarning'] ?? $phpunitConfiguration->stopOnWarning(); - $arguments['stopOnIncomplete'] = $arguments['stopOnIncomplete'] ?? $phpunitConfiguration->stopOnIncomplete(); - $arguments['stopOnRisky'] = $arguments['stopOnRisky'] ?? $phpunitConfiguration->stopOnRisky(); - $arguments['stopOnSkipped'] = $arguments['stopOnSkipped'] ?? $phpunitConfiguration->stopOnSkipped(); - $arguments['failOnEmptyTestSuite'] = $arguments['failOnEmptyTestSuite'] ?? $phpunitConfiguration->failOnEmptyTestSuite(); - $arguments['failOnIncomplete'] = $arguments['failOnIncomplete'] ?? $phpunitConfiguration->failOnIncomplete(); - $arguments['failOnRisky'] = $arguments['failOnRisky'] ?? $phpunitConfiguration->failOnRisky(); - $arguments['failOnSkipped'] = $arguments['failOnSkipped'] ?? $phpunitConfiguration->failOnSkipped(); - $arguments['failOnWarning'] = $arguments['failOnWarning'] ?? $phpunitConfiguration->failOnWarning(); - $arguments['enforceTimeLimit'] = $arguments['enforceTimeLimit'] ?? $phpunitConfiguration->enforceTimeLimit(); - $arguments['defaultTimeLimit'] = $arguments['defaultTimeLimit'] ?? $phpunitConfiguration->defaultTimeLimit(); - $arguments['timeoutForSmallTests'] = $arguments['timeoutForSmallTests'] ?? $phpunitConfiguration->timeoutForSmallTests(); - $arguments['timeoutForMediumTests'] = $arguments['timeoutForMediumTests'] ?? $phpunitConfiguration->timeoutForMediumTests(); - $arguments['timeoutForLargeTests'] = $arguments['timeoutForLargeTests'] ?? $phpunitConfiguration->timeoutForLargeTests(); - $arguments['reportUselessTests'] = $arguments['reportUselessTests'] ?? $phpunitConfiguration->beStrictAboutTestsThatDoNotTestAnything(); - $arguments['strictCoverage'] = $arguments['strictCoverage'] ?? $phpunitConfiguration->beStrictAboutCoversAnnotation(); - $arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'] = $arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'] ?? $codeCoverageConfiguration->ignoreDeprecatedCodeUnits(); - $arguments['disallowTestOutput'] = $arguments['disallowTestOutput'] ?? $phpunitConfiguration->beStrictAboutOutputDuringTests(); - $arguments['disallowTodoAnnotatedTests'] = $arguments['disallowTodoAnnotatedTests'] ?? $phpunitConfiguration->beStrictAboutTodoAnnotatedTests(); - $arguments['beStrictAboutResourceUsageDuringSmallTests'] = $arguments['beStrictAboutResourceUsageDuringSmallTests'] ?? $phpunitConfiguration->beStrictAboutResourceUsageDuringSmallTests(); - $arguments['verbose'] = $arguments['verbose'] ?? $phpunitConfiguration->verbose(); - $arguments['reverseDefectList'] = $arguments['reverseDefectList'] ?? $phpunitConfiguration->reverseDefectList(); - $arguments['forceCoversAnnotation'] = $arguments['forceCoversAnnotation'] ?? $phpunitConfiguration->forceCoversAnnotation(); - $arguments['disableCodeCoverageIgnore'] = $arguments['disableCodeCoverageIgnore'] ?? $codeCoverageConfiguration->disableCodeCoverageIgnore(); - $arguments['registerMockObjectsFromTestArgumentsRecursively'] = $arguments['registerMockObjectsFromTestArgumentsRecursively'] ?? $phpunitConfiguration->registerMockObjectsFromTestArgumentsRecursively(); - $arguments['noInteraction'] = $arguments['noInteraction'] ?? $phpunitConfiguration->noInteraction(); - $arguments['executionOrder'] = $arguments['executionOrder'] ?? $phpunitConfiguration->executionOrder(); - $arguments['resolveDependencies'] = $arguments['resolveDependencies'] ?? $phpunitConfiguration->resolveDependencies(); - - if (!isset($arguments['bootstrap']) && $phpunitConfiguration->hasBootstrap()) { - $arguments['bootstrap'] = $phpunitConfiguration->bootstrap(); - } - - if (!isset($arguments['cacheResultFile']) && $phpunitConfiguration->hasCacheResultFile()) { - $arguments['cacheResultFile'] = $phpunitConfiguration->cacheResultFile(); - } - - if (!isset($arguments['executionOrderDefects'])) { - $arguments['executionOrderDefects'] = $phpunitConfiguration->defectsFirst() ? TestSuiteSorter::ORDER_DEFECTS_FIRST : TestSuiteSorter::ORDER_DEFAULT; - } - - if ($phpunitConfiguration->conflictBetweenPrinterClassAndTestdox()) { - $arguments['conflictBetweenPrinterClassAndTestdox'] = true; - } - - $groupCliArgs = []; - - if (!empty($arguments['groups'])) { - $groupCliArgs = $arguments['groups']; - } - - $groupConfiguration = $arguments['configuration']->groups(); - - if (!isset($arguments['groups']) && $groupConfiguration->hasInclude()) { - $arguments['groups'] = $groupConfiguration->include()->asArrayOfStrings(); - } - - if (!isset($arguments['excludeGroups']) && $groupConfiguration->hasExclude()) { - $arguments['excludeGroups'] = array_diff($groupConfiguration->exclude()->asArrayOfStrings(), $groupCliArgs); - } - - $extensionHandler = new ExtensionHandler; - - foreach ($arguments['configuration']->extensions() as $extension) { - $this->addExtension($extensionHandler->createHookInstance($extension)); - } - - foreach ($arguments['configuration']->listeners() as $listener) { - $arguments['listeners'][] = $extensionHandler->createTestListenerInstance($listener); - } - - unset($extensionHandler); - - foreach ($arguments['unavailableExtensions'] as $extension) { - $arguments['warnings'][] = sprintf( - 'Extension "%s" is not available', - $extension + new TestSuiteSorter($resultCache)->reorderTestsInSuite( + $suite, + $configuration->executionOrder(), + $configuration->resolveDependencies(), + $configuration->executionOrderDefects(), ); - } - $loggingConfiguration = $arguments['configuration']->logging(); - - if ($loggingConfiguration->hasText()) { - $arguments['listeners'][] = new DefaultResultPrinter( - $loggingConfiguration->text()->target()->path(), - true + Event\Facade::emitter()->testSuiteSorted( + $configuration->executionOrder(), + $configuration->executionOrderDefects(), + $configuration->resolveDependencies(), ); } - if (!isset($arguments['teamcityLogfile']) && $loggingConfiguration->hasTeamCity()) { - $arguments['teamcityLogfile'] = $loggingConfiguration->teamCity()->target()->path(); - } - - if (!isset($arguments['junitLogfile']) && $loggingConfiguration->hasJunit()) { - $arguments['junitLogfile'] = $loggingConfiguration->junit()->target()->path(); - } - - if (!isset($arguments['testdoxHTMLFile']) && $loggingConfiguration->hasTestDoxHtml()) { - $arguments['testdoxHTMLFile'] = $loggingConfiguration->testDoxHtml()->target()->path(); - } - - if (!isset($arguments['testdoxTextFile']) && $loggingConfiguration->hasTestDoxText()) { - $arguments['testdoxTextFile'] = $loggingConfiguration->testDoxText()->target()->path(); - } - - if (!isset($arguments['testdoxXMLFile']) && $loggingConfiguration->hasTestDoxXml()) { - $arguments['testdoxXMLFile'] = $loggingConfiguration->testDoxXml()->target()->path(); - } - - $testdoxGroupConfiguration = $arguments['configuration']->testdoxGroups(); - - if (!isset($arguments['testdoxGroups']) && $testdoxGroupConfiguration->hasInclude()) { - $arguments['testdoxGroups'] = $testdoxGroupConfiguration->include()->asArrayOfStrings(); - } - - if (!isset($arguments['testdoxExcludeGroups']) && $testdoxGroupConfiguration->hasExclude()) { - $arguments['testdoxExcludeGroups'] = $testdoxGroupConfiguration->exclude()->asArrayOfStrings(); - } - } - - $extensionHandler = new ExtensionHandler; - - foreach ($arguments['extensions'] as $extension) { - $this->addExtension($extensionHandler->createHookInstance($extension)); - } - - unset($extensionHandler); - - $arguments['backupGlobals'] = $arguments['backupGlobals'] ?? null; - $arguments['backupStaticAttributes'] = $arguments['backupStaticAttributes'] ?? null; - $arguments['beStrictAboutChangesToGlobalState'] = $arguments['beStrictAboutChangesToGlobalState'] ?? null; - $arguments['beStrictAboutResourceUsageDuringSmallTests'] = $arguments['beStrictAboutResourceUsageDuringSmallTests'] ?? false; - $arguments['cacheResult'] = $arguments['cacheResult'] ?? true; - $arguments['cacheTokens'] = $arguments['cacheTokens'] ?? false; - $arguments['colors'] = $arguments['colors'] ?? DefaultResultPrinter::COLOR_DEFAULT; - $arguments['columns'] = $arguments['columns'] ?? 80; - $arguments['convertDeprecationsToExceptions'] = $arguments['convertDeprecationsToExceptions'] ?? true; - $arguments['convertErrorsToExceptions'] = $arguments['convertErrorsToExceptions'] ?? true; - $arguments['convertNoticesToExceptions'] = $arguments['convertNoticesToExceptions'] ?? true; - $arguments['convertWarningsToExceptions'] = $arguments['convertWarningsToExceptions'] ?? true; - $arguments['crap4jThreshold'] = $arguments['crap4jThreshold'] ?? 30; - $arguments['disallowTestOutput'] = $arguments['disallowTestOutput'] ?? false; - $arguments['disallowTodoAnnotatedTests'] = $arguments['disallowTodoAnnotatedTests'] ?? false; - $arguments['defaultTimeLimit'] = $arguments['defaultTimeLimit'] ?? 0; - $arguments['enforceTimeLimit'] = $arguments['enforceTimeLimit'] ?? false; - $arguments['excludeGroups'] = $arguments['excludeGroups'] ?? []; - $arguments['executionOrder'] = $arguments['executionOrder'] ?? TestSuiteSorter::ORDER_DEFAULT; - $arguments['executionOrderDefects'] = $arguments['executionOrderDefects'] ?? TestSuiteSorter::ORDER_DEFAULT; - $arguments['failOnIncomplete'] = $arguments['failOnIncomplete'] ?? false; - $arguments['failOnRisky'] = $arguments['failOnRisky'] ?? false; - $arguments['failOnSkipped'] = $arguments['failOnSkipped'] ?? false; - $arguments['failOnWarning'] = $arguments['failOnWarning'] ?? false; - $arguments['groups'] = $arguments['groups'] ?? []; - $arguments['noInteraction'] = $arguments['noInteraction'] ?? false; - $arguments['processIsolation'] = $arguments['processIsolation'] ?? false; - $arguments['randomOrderSeed'] = $arguments['randomOrderSeed'] ?? time(); - $arguments['registerMockObjectsFromTestArgumentsRecursively'] = $arguments['registerMockObjectsFromTestArgumentsRecursively'] ?? false; - $arguments['repeat'] = $arguments['repeat'] ?? false; - $arguments['reportHighLowerBound'] = $arguments['reportHighLowerBound'] ?? 90; - $arguments['reportLowUpperBound'] = $arguments['reportLowUpperBound'] ?? 50; - $arguments['reportUselessTests'] = $arguments['reportUselessTests'] ?? true; - $arguments['reverseList'] = $arguments['reverseList'] ?? false; - $arguments['resolveDependencies'] = $arguments['resolveDependencies'] ?? true; - $arguments['stopOnError'] = $arguments['stopOnError'] ?? false; - $arguments['stopOnFailure'] = $arguments['stopOnFailure'] ?? false; - $arguments['stopOnIncomplete'] = $arguments['stopOnIncomplete'] ?? false; - $arguments['stopOnRisky'] = $arguments['stopOnRisky'] ?? false; - $arguments['stopOnSkipped'] = $arguments['stopOnSkipped'] ?? false; - $arguments['stopOnWarning'] = $arguments['stopOnWarning'] ?? false; - $arguments['stopOnDefect'] = $arguments['stopOnDefect'] ?? false; - $arguments['strictCoverage'] = $arguments['strictCoverage'] ?? false; - $arguments['testdoxExcludeGroups'] = $arguments['testdoxExcludeGroups'] ?? []; - $arguments['testdoxGroups'] = $arguments['testdoxGroups'] ?? []; - $arguments['timeoutForLargeTests'] = $arguments['timeoutForLargeTests'] ?? 60; - $arguments['timeoutForMediumTests'] = $arguments['timeoutForMediumTests'] ?? 10; - $arguments['timeoutForSmallTests'] = $arguments['timeoutForSmallTests'] ?? 1; - $arguments['verbose'] = $arguments['verbose'] ?? false; - } - - private function processSuiteFilters(TestSuite $suite, array $arguments): void - { - if (!$arguments['filter'] && - empty($arguments['groups']) && - empty($arguments['excludeGroups'])) { - return; - } - - $filterFactory = new Factory; + (new TestSuiteFilterProcessor)->process($configuration, $suite); - if (!empty($arguments['excludeGroups'])) { - $filterFactory->addFilter( - new ReflectionClass(ExcludeGroupFilterIterator::class), - $arguments['excludeGroups'] + Event\Facade::emitter()->testRunnerExecutionStarted( + Event\TestSuite\TestSuiteBuilder::from($suite), ); - } - if (!empty($arguments['groups'])) { - $filterFactory->addFilter( - new ReflectionClass(IncludeGroupFilterIterator::class), - $arguments['groups'] - ); - } + $suite->run(); - if ($arguments['filter']) { - $filterFactory->addFilter( - new ReflectionClass(NameFilterIterator::class), - $arguments['filter'] + Event\Facade::emitter()->testRunnerExecutionFinished(); + Event\Facade::emitter()->testRunnerFinished(); + } catch (Throwable $t) { + throw new RuntimeException( + $t->getMessage(), + (int) $t->getCode(), + $t, ); } - - $suite->injectFilter($filterFactory); - } - - private function writeMessage(string $type, string $message): void - { - if (!$this->messagePrinted) { - $this->write("\n"); - } - - $this->write( - sprintf( - "%-15s%s\n", - $type . ':', - $message - ) - ); - - $this->messagePrinted = true; - } - - private function createPrinter(string $class, array $arguments): ResultPrinter - { - $object = new $class( - (isset($arguments['stderr']) && $arguments['stderr'] === true) ? 'php://stderr' : null, - $arguments['verbose'], - $arguments['colors'], - $arguments['debug'], - $arguments['columns'], - $arguments['reverseList'] - ); - - assert($object instanceof ResultPrinter); - - return $object; - } - - private function codeCoverageGenerationStart(string $format): void - { - $this->printer->write( - sprintf( - "\nGenerating code coverage report in %s format ... ", - $format - ) - ); - - $this->timer->start(); - } - - private function codeCoverageGenerationSucceeded(): void - { - $this->printer->write( - sprintf( - "done [%s]\n", - $this->timer->stop()->asString() - ) - ); - } - - private function codeCoverageGenerationFailed(\Exception $e): void - { - $this->printer->write( - sprintf( - "failed [%s]\n%s\n", - $this->timer->stop()->asString(), - $e->getMessage() - ) - ); } } diff --git a/src/TextUI/TestSuiteFilterProcessor.php b/src/TextUI/TestSuiteFilterProcessor.php new file mode 100644 index 00000000000..c0e4beb1936 --- /dev/null +++ b/src/TextUI/TestSuiteFilterProcessor.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +use function array_map; +use PHPUnit\Event; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Runner\Filter\Factory; +use PHPUnit\TextUI\Configuration\Configuration; +use PHPUnit\TextUI\Configuration\FilterNotConfiguredException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteFilterProcessor +{ + /** + * @throws Event\RuntimeException + * @throws FilterNotConfiguredException + */ + public function process(Configuration $configuration, TestSuite $suite): void + { + $factory = new Factory; + + if (!$configuration->hasFilter() && + !$configuration->hasGroups() && + !$configuration->hasExcludeGroups() && + !$configuration->hasExcludeFilter() && + !$configuration->hasTestsCovering() && + !$configuration->hasTestsUsing() && + !$configuration->hasTestsRequiringPhpExtension()) { + return; + } + + if ($configuration->hasExcludeGroups()) { + $factory->addExcludeGroupFilter( + $configuration->excludeGroups(), + ); + } + + if ($configuration->hasGroups()) { + $factory->addIncludeGroupFilter( + $configuration->groups(), + ); + } + + if ($configuration->hasTestsCovering()) { + $factory->addIncludeGroupFilter( + array_map( + static fn (string $name): string => '__phpunit_covers_' . $name, + $configuration->testsCovering(), + ), + ); + } + + if ($configuration->hasTestsUsing()) { + $factory->addIncludeGroupFilter( + array_map( + static fn (string $name): string => '__phpunit_uses_' . $name, + $configuration->testsUsing(), + ), + ); + } + + if ($configuration->hasTestsRequiringPhpExtension()) { + $factory->addIncludeGroupFilter( + array_map( + static fn (string $name): string => '__phpunit_requires_php_extension' . $name, + $configuration->testsRequiringPhpExtension(), + ), + ); + } + + if ($configuration->hasExcludeFilter()) { + $factory->addExcludeNameFilter( + $configuration->excludeFilter(), + ); + } + + if ($configuration->hasFilter()) { + $factory->addIncludeNameFilter( + $configuration->filter(), + ); + } + + $suite->injectFilter($factory); + + Event\Facade::emitter()->testSuiteFiltered( + Event\TestSuite\TestSuiteBuilder::from($suite), + ); + } +} diff --git a/src/TextUI/XmlConfiguration/CodeCoverage/CodeCoverage.php b/src/TextUI/XmlConfiguration/CodeCoverage/CodeCoverage.php deleted file mode 100644 index 4acff287261..00000000000 --- a/src/TextUI/XmlConfiguration/CodeCoverage/CodeCoverage.php +++ /dev/null @@ -1,297 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage; - -use function count; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\DirectoryCollection; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Clover; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Crap4j; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Html; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Php; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Text; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Xml; -use PHPUnit\TextUI\XmlConfiguration\Exception; -use PHPUnit\TextUI\XmlConfiguration\FileCollection; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class CodeCoverage -{ - /** - * @var DirectoryCollection - */ - private $directories; - - /** - * @var FileCollection - */ - private $files; - - /** - * @var DirectoryCollection - */ - private $excludeDirectories; - - /** - * @var FileCollection - */ - private $excludeFiles; - - /** - * @var bool - */ - private $pathCoverage; - - /** - * @var bool - */ - private $includeUncoveredFiles; - - /** - * @var bool - */ - private $processUncoveredFiles; - - /** - * @var bool - */ - private $cacheTokens; - - /** - * @var bool - */ - private $ignoreDeprecatedCodeUnits; - - /** - * @var bool - */ - private $disableCodeCoverageIgnore; - - /** - * @var ?Clover - */ - private $clover; - - /** - * @var ?Crap4j - */ - private $crap4j; - - /** - * @var ?Html - */ - private $html; - - /** - * @var ?Php - */ - private $php; - - /** - * @var ?Text - */ - private $text; - - /** - * @var ?Xml - */ - private $xml; - - public function __construct(DirectoryCollection $directories, FileCollection $files, DirectoryCollection $excludeDirectories, FileCollection $excludeFiles, bool $pathCoverage, bool $includeUncoveredFiles, bool $processUncoveredFiles, bool $cacheTokens, bool $ignoreDeprecatedCodeUnits, bool $disableCodeCoverageIgnore, ?Clover $clover, ?Crap4j $crap4j, ?Html $html, ?Php $php, ?Text $text, ?Xml $xml) - { - $this->directories = $directories; - $this->files = $files; - $this->excludeDirectories = $excludeDirectories; - $this->excludeFiles = $excludeFiles; - $this->pathCoverage = $pathCoverage; - $this->includeUncoveredFiles = $includeUncoveredFiles; - $this->processUncoveredFiles = $processUncoveredFiles; - $this->cacheTokens = $cacheTokens; - $this->ignoreDeprecatedCodeUnits = $ignoreDeprecatedCodeUnits; - $this->disableCodeCoverageIgnore = $disableCodeCoverageIgnore; - $this->clover = $clover; - $this->crap4j = $crap4j; - $this->html = $html; - $this->php = $php; - $this->text = $text; - $this->xml = $xml; - } - - public function hasNonEmptyListOfFilesToBeIncludedInCodeCoverageReport(): bool - { - return count($this->directories) > 0 || count($this->files) > 0; - } - - public function directories(): DirectoryCollection - { - return $this->directories; - } - - public function files(): FileCollection - { - return $this->files; - } - - public function excludeDirectories(): DirectoryCollection - { - return $this->excludeDirectories; - } - - public function excludeFiles(): FileCollection - { - return $this->excludeFiles; - } - - public function pathCoverage(): bool - { - return $this->pathCoverage; - } - - public function includeUncoveredFiles(): bool - { - return $this->includeUncoveredFiles; - } - - public function cacheTokens(): bool - { - return $this->cacheTokens; - } - - public function ignoreDeprecatedCodeUnits(): bool - { - return $this->ignoreDeprecatedCodeUnits; - } - - public function disableCodeCoverageIgnore(): bool - { - return $this->disableCodeCoverageIgnore; - } - - public function processUncoveredFiles(): bool - { - return $this->processUncoveredFiles; - } - - /** - * @psalm-assert-if-true !null $this->clover - */ - public function hasClover(): bool - { - return $this->clover !== null; - } - - public function clover(): Clover - { - if (!$this->hasClover()) { - throw new Exception( - 'Code Coverage report "Clover XML" has not been configured' - ); - } - - return $this->clover; - } - - /** - * @psalm-assert-if-true !null $this->crap4j - */ - public function hasCrap4j(): bool - { - return $this->crap4j !== null; - } - - public function crap4j(): Crap4j - { - if (!$this->hasCrap4j()) { - throw new Exception( - 'Code Coverage report "Crap4J" has not been configured' - ); - } - - return $this->crap4j; - } - - /** - * @psalm-assert-if-true !null $this->html - */ - public function hasHtml(): bool - { - return $this->html !== null; - } - - public function html(): Html - { - if (!$this->hasHtml()) { - throw new Exception( - 'Code Coverage report "HTML" has not been configured' - ); - } - - return $this->html; - } - - /** - * @psalm-assert-if-true !null $this->php - */ - public function hasPhp(): bool - { - return $this->php !== null; - } - - public function php(): Php - { - if (!$this->hasPhp()) { - throw new Exception( - 'Code Coverage report "PHP" has not been configured' - ); - } - - return $this->php; - } - - /** - * @psalm-assert-if-true !null $this->text - */ - public function hasText(): bool - { - return $this->text !== null; - } - - public function text(): Text - { - if (!$this->hasText()) { - throw new Exception( - 'Code Coverage report "Text" has not been configured' - ); - } - - return $this->text; - } - - /** - * @psalm-assert-if-true !null $this->xml - */ - public function hasXml(): bool - { - return $this->xml !== null; - } - - public function xml(): Xml - { - if (!$this->hasXml()) { - throw new Exception( - 'Code Coverage report "XML" has not been configured' - ); - } - - return $this->xml; - } -} diff --git a/src/TextUI/XmlConfiguration/CodeCoverage/Filter/Directory.php b/src/TextUI/XmlConfiguration/CodeCoverage/Filter/Directory.php deleted file mode 100644 index 3bf99c39dbd..00000000000 --- a/src/TextUI/XmlConfiguration/CodeCoverage/Filter/Directory.php +++ /dev/null @@ -1,65 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Directory -{ - /** - * @var string - */ - private $path; - - /** - * @var string - */ - private $prefix; - - /** - * @var string - */ - private $suffix; - - /** - * @var string - */ - private $group; - - public function __construct(string $path, string $prefix, string $suffix, string $group) - { - $this->path = $path; - $this->prefix = $prefix; - $this->suffix = $suffix; - $this->group = $group; - } - - public function path(): string - { - return $this->path; - } - - public function prefix(): string - { - return $this->prefix; - } - - public function suffix(): string - { - return $this->suffix; - } - - public function group(): string - { - return $this->group; - } -} diff --git a/src/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollection.php b/src/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollection.php deleted file mode 100644 index 803ccda20af..00000000000 --- a/src/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollection.php +++ /dev/null @@ -1,57 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter; - -use function count; -use Countable; -use IteratorAggregate; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class DirectoryCollection implements Countable, IteratorAggregate -{ - /** - * @var Directory[] - */ - private $directories; - - /** - * @param Directory[] $directories - */ - public static function fromArray(array $directories): self - { - return new self(...$directories); - } - - private function __construct(Directory ...$directories) - { - $this->directories = $directories; - } - - /** - * @return Directory[] - */ - public function asArray(): array - { - return $this->directories; - } - - public function count(): int - { - return count($this->directories); - } - - public function getIterator(): DirectoryCollectionIterator - { - return new DirectoryCollectionIterator($this); - } -} diff --git a/src/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollectionIterator.php b/src/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollectionIterator.php deleted file mode 100644 index c59a3ba998d..00000000000 --- a/src/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollectionIterator.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter; - -use function count; -use function iterator_count; -use Countable; -use Iterator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class DirectoryCollectionIterator implements Countable, Iterator -{ - /** - * @var Directory[] - */ - private $directories; - - /** - * @var int - */ - private $position; - - public function __construct(DirectoryCollection $directories) - { - $this->directories = $directories->asArray(); - } - - public function count(): int - { - return iterator_count($this); - } - - public function rewind(): void - { - $this->position = 0; - } - - public function valid(): bool - { - return $this->position < count($this->directories); - } - - public function key(): int - { - return $this->position; - } - - public function current(): Directory - { - return $this->directories[$this->position]; - } - - public function next(): void - { - $this->position++; - } -} diff --git a/src/TextUI/XmlConfiguration/CodeCoverage/Report/Clover.php b/src/TextUI/XmlConfiguration/CodeCoverage/Report/Clover.php deleted file mode 100644 index e7ff407bee5..00000000000 --- a/src/TextUI/XmlConfiguration/CodeCoverage/Report/Clover.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; - -use PHPUnit\TextUI\XmlConfiguration\File; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Clover -{ - /** - * @var File - */ - private $target; - - public function __construct(File $target) - { - $this->target = $target; - } - - public function target(): File - { - return $this->target; - } -} diff --git a/src/TextUI/XmlConfiguration/CodeCoverage/Report/Crap4j.php b/src/TextUI/XmlConfiguration/CodeCoverage/Report/Crap4j.php deleted file mode 100644 index fd4d42912f0..00000000000 --- a/src/TextUI/XmlConfiguration/CodeCoverage/Report/Crap4j.php +++ /dev/null @@ -1,45 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; - -use PHPUnit\TextUI\XmlConfiguration\File; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Crap4j -{ - /** - * @var File - */ - private $target; - - /** - * @var int - */ - private $threshold; - - public function __construct(File $target, int $threshold) - { - $this->target = $target; - $this->threshold = $threshold; - } - - public function target(): File - { - return $this->target; - } - - public function threshold(): int - { - return $this->threshold; - } -} diff --git a/src/TextUI/XmlConfiguration/CodeCoverage/Report/Html.php b/src/TextUI/XmlConfiguration/CodeCoverage/Report/Html.php deleted file mode 100644 index 7084ffe5e29..00000000000 --- a/src/TextUI/XmlConfiguration/CodeCoverage/Report/Html.php +++ /dev/null @@ -1,56 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; - -use PHPUnit\TextUI\XmlConfiguration\Directory; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Html -{ - /** - * @var Directory - */ - private $target; - - /** - * @var int - */ - private $lowUpperBound; - - /** - * @var int - */ - private $highLowerBound; - - public function __construct(Directory $target, int $lowUpperBound, int $highLowerBound) - { - $this->target = $target; - $this->lowUpperBound = $lowUpperBound; - $this->highLowerBound = $highLowerBound; - } - - public function target(): Directory - { - return $this->target; - } - - public function lowUpperBound(): int - { - return $this->lowUpperBound; - } - - public function highLowerBound(): int - { - return $this->highLowerBound; - } -} diff --git a/src/TextUI/XmlConfiguration/CodeCoverage/Report/Php.php b/src/TextUI/XmlConfiguration/CodeCoverage/Report/Php.php deleted file mode 100644 index d86b66216a2..00000000000 --- a/src/TextUI/XmlConfiguration/CodeCoverage/Report/Php.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; - -use PHPUnit\TextUI\XmlConfiguration\File; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Php -{ - /** - * @var File - */ - private $target; - - public function __construct(File $target) - { - $this->target = $target; - } - - public function target(): File - { - return $this->target; - } -} diff --git a/src/TextUI/XmlConfiguration/CodeCoverage/Report/Text.php b/src/TextUI/XmlConfiguration/CodeCoverage/Report/Text.php deleted file mode 100644 index b7e9f3da3e7..00000000000 --- a/src/TextUI/XmlConfiguration/CodeCoverage/Report/Text.php +++ /dev/null @@ -1,56 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; - -use PHPUnit\TextUI\XmlConfiguration\File; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Text -{ - /** - * @var File - */ - private $target; - - /** - * @var bool - */ - private $showUncoveredFiles; - - /** - * @var bool - */ - private $showOnlySummary; - - public function __construct(File $target, bool $showUncoveredFiles, bool $showOnlySummary) - { - $this->target = $target; - $this->showUncoveredFiles = $showUncoveredFiles; - $this->showOnlySummary = $showOnlySummary; - } - - public function target(): File - { - return $this->target; - } - - public function showUncoveredFiles(): bool - { - return $this->showUncoveredFiles; - } - - public function showOnlySummary(): bool - { - return $this->showOnlySummary; - } -} diff --git a/src/TextUI/XmlConfiguration/CodeCoverage/Report/Xml.php b/src/TextUI/XmlConfiguration/CodeCoverage/Report/Xml.php deleted file mode 100644 index 977685c4697..00000000000 --- a/src/TextUI/XmlConfiguration/CodeCoverage/Report/Xml.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; - -use PHPUnit\TextUI\XmlConfiguration\Directory; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Xml -{ - /** - * @var Directory - */ - private $target; - - public function __construct(Directory $target) - { - $this->target = $target; - } - - public function target(): Directory - { - return $this->target; - } -} diff --git a/src/TextUI/XmlConfiguration/Configuration.php b/src/TextUI/XmlConfiguration/Configuration.php deleted file mode 100644 index f944e7b8ff5..00000000000 --- a/src/TextUI/XmlConfiguration/Configuration.php +++ /dev/null @@ -1,155 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\CodeCoverage; -use PHPUnit\TextUI\XmlConfiguration\Logging\Logging; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Configuration -{ - /** - * @var string - */ - private $filename; - - /** - * @var array - * @psalm-var array> - */ - private $validationErrors = []; - - /** - * @var ExtensionCollection - */ - private $extensions; - - /** - * @var CodeCoverage - */ - private $codeCoverage; - - /** - * @var Groups - */ - private $groups; - - /** - * @var Groups - */ - private $testdoxGroups; - - /** - * @var ExtensionCollection - */ - private $listeners; - - /** - * @var Logging - */ - private $logging; - - /** - * @var Php - */ - private $php; - - /** - * @var PHPUnit - */ - private $phpunit; - - /** - * @var TestSuiteCollection - */ - private $testSuite; - - /** - * @psalm-param array> $validationErrors - */ - public function __construct(string $filename, array $validationErrors, ExtensionCollection $extensions, CodeCoverage $codeCoverage, Groups $groups, Groups $testdoxGroups, ExtensionCollection $listeners, Logging $logging, Php $php, PHPUnit $phpunit, TestSuiteCollection $testSuite) - { - $this->filename = $filename; - $this->validationErrors = $validationErrors; - $this->extensions = $extensions; - $this->codeCoverage = $codeCoverage; - $this->groups = $groups; - $this->testdoxGroups = $testdoxGroups; - $this->listeners = $listeners; - $this->logging = $logging; - $this->php = $php; - $this->phpunit = $phpunit; - $this->testSuite = $testSuite; - } - - public function filename(): string - { - return $this->filename; - } - - public function hasValidationErrors(): bool - { - return count($this->validationErrors) > 0; - } - - public function validationErrors(): array - { - return $this->validationErrors; - } - - public function extensions(): ExtensionCollection - { - return $this->extensions; - } - - public function codeCoverage(): CodeCoverage - { - return $this->codeCoverage; - } - - public function groups(): Groups - { - return $this->groups; - } - - public function testdoxGroups(): Groups - { - return $this->testdoxGroups; - } - - public function listeners(): ExtensionCollection - { - return $this->listeners; - } - - public function logging(): Logging - { - return $this->logging; - } - - public function php(): Php - { - return $this->php; - } - - public function phpunit(): PHPUnit - { - return $this->phpunit; - } - - public function testSuite(): TestSuiteCollection - { - return $this->testSuite; - } -} diff --git a/src/TextUI/XmlConfiguration/Exception.php b/src/TextUI/XmlConfiguration/Exception.php deleted file mode 100644 index 162b37e8814..00000000000 --- a/src/TextUI/XmlConfiguration/Exception.php +++ /dev/null @@ -1,19 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use RuntimeException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Exception extends RuntimeException implements \PHPUnit\Exception -{ -} diff --git a/src/TextUI/XmlConfiguration/Filesystem/Directory.php b/src/TextUI/XmlConfiguration/Filesystem/Directory.php deleted file mode 100644 index 1629603b704..00000000000 --- a/src/TextUI/XmlConfiguration/Filesystem/Directory.php +++ /dev/null @@ -1,32 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Directory -{ - /** - * @var string - */ - private $path; - - public function __construct(string $path) - { - $this->path = $path; - } - - public function path(): string - { - return $this->path; - } -} diff --git a/src/TextUI/XmlConfiguration/Filesystem/DirectoryCollection.php b/src/TextUI/XmlConfiguration/Filesystem/DirectoryCollection.php deleted file mode 100644 index c8ae596416a..00000000000 --- a/src/TextUI/XmlConfiguration/Filesystem/DirectoryCollection.php +++ /dev/null @@ -1,62 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use Countable; -use IteratorAggregate; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class DirectoryCollection implements Countable, IteratorAggregate -{ - /** - * @var Directory[] - */ - private $directories; - - /** - * @param Directory[] $directories - */ - public static function fromArray(array $directories): self - { - return new self(...$directories); - } - - private function __construct(Directory ...$directories) - { - $this->directories = $directories; - } - - /** - * @return Directory[] - */ - public function asArray(): array - { - return $this->directories; - } - - public function count(): int - { - return count($this->directories); - } - - public function getIterator(): DirectoryCollectionIterator - { - return new DirectoryCollectionIterator($this); - } - - public function isEmpty(): bool - { - return $this->count() === 0; - } -} diff --git a/src/TextUI/XmlConfiguration/Filesystem/DirectoryCollectionIterator.php b/src/TextUI/XmlConfiguration/Filesystem/DirectoryCollectionIterator.php deleted file mode 100644 index 7f354eea5bd..00000000000 --- a/src/TextUI/XmlConfiguration/Filesystem/DirectoryCollectionIterator.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use function iterator_count; -use Countable; -use Iterator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class DirectoryCollectionIterator implements Countable, Iterator -{ - /** - * @var Directory[] - */ - private $directories; - - /** - * @var int - */ - private $position; - - public function __construct(DirectoryCollection $directories) - { - $this->directories = $directories->asArray(); - } - - public function count(): int - { - return iterator_count($this); - } - - public function rewind(): void - { - $this->position = 0; - } - - public function valid(): bool - { - return $this->position < count($this->directories); - } - - public function key(): int - { - return $this->position; - } - - public function current(): Directory - { - return $this->directories[$this->position]; - } - - public function next(): void - { - $this->position++; - } -} diff --git a/src/TextUI/XmlConfiguration/Filesystem/File.php b/src/TextUI/XmlConfiguration/Filesystem/File.php deleted file mode 100644 index 0af5000d1bf..00000000000 --- a/src/TextUI/XmlConfiguration/Filesystem/File.php +++ /dev/null @@ -1,32 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class File -{ - /** - * @var string - */ - private $path; - - public function __construct(string $path) - { - $this->path = $path; - } - - public function path(): string - { - return $this->path; - } -} diff --git a/src/TextUI/XmlConfiguration/Filesystem/FileCollection.php b/src/TextUI/XmlConfiguration/Filesystem/FileCollection.php deleted file mode 100644 index bfc1e33e4c7..00000000000 --- a/src/TextUI/XmlConfiguration/Filesystem/FileCollection.php +++ /dev/null @@ -1,62 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use Countable; -use IteratorAggregate; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class FileCollection implements Countable, IteratorAggregate -{ - /** - * @var File[] - */ - private $files; - - /** - * @param File[] $files - */ - public static function fromArray(array $files): self - { - return new self(...$files); - } - - private function __construct(File ...$files) - { - $this->files = $files; - } - - /** - * @return File[] - */ - public function asArray(): array - { - return $this->files; - } - - public function count(): int - { - return count($this->files); - } - - public function getIterator(): FileCollectionIterator - { - return new FileCollectionIterator($this); - } - - public function isEmpty(): bool - { - return $this->count() === 0; - } -} diff --git a/src/TextUI/XmlConfiguration/Filesystem/FileCollectionIterator.php b/src/TextUI/XmlConfiguration/Filesystem/FileCollectionIterator.php deleted file mode 100644 index d9bab1f8ba1..00000000000 --- a/src/TextUI/XmlConfiguration/Filesystem/FileCollectionIterator.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use function iterator_count; -use Countable; -use Iterator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class FileCollectionIterator implements Countable, Iterator -{ - /** - * @var File[] - */ - private $files; - - /** - * @var int - */ - private $position; - - public function __construct(FileCollection $files) - { - $this->files = $files->asArray(); - } - - public function count(): int - { - return iterator_count($this); - } - - public function rewind(): void - { - $this->position = 0; - } - - public function valid(): bool - { - return $this->position < count($this->files); - } - - public function key(): int - { - return $this->position; - } - - public function current(): File - { - return $this->files[$this->position]; - } - - public function next(): void - { - $this->position++; - } -} diff --git a/src/TextUI/XmlConfiguration/Generator.php b/src/TextUI/XmlConfiguration/Generator.php deleted file mode 100644 index 491dfa5f399..00000000000 --- a/src/TextUI/XmlConfiguration/Generator.php +++ /dev/null @@ -1,68 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function str_replace; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Generator -{ - /** - * @var string - */ - private const TEMPLATE = <<<'EOT' - - - - - {tests_directory} - - - - - - {src_directory} - - - - -EOT; - - public function generateDefaultConfiguration(string $phpunitVersion, string $bootstrapScript, string $testsDirectory, string $srcDirectory): string - { - return str_replace( - [ - '{phpunit_version}', - '{bootstrap_script}', - '{tests_directory}', - '{src_directory}', - ], - [ - $phpunitVersion, - $bootstrapScript, - $testsDirectory, - $srcDirectory, - ], - self::TEMPLATE - ); - } -} diff --git a/src/TextUI/XmlConfiguration/Group/Group.php b/src/TextUI/XmlConfiguration/Group/Group.php deleted file mode 100644 index e59844053f0..00000000000 --- a/src/TextUI/XmlConfiguration/Group/Group.php +++ /dev/null @@ -1,32 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Group -{ - /** - * @var string - */ - private $name; - - public function __construct(string $name) - { - $this->name = $name; - } - - public function name(): string - { - return $this->name; - } -} diff --git a/src/TextUI/XmlConfiguration/Group/GroupCollection.php b/src/TextUI/XmlConfiguration/Group/GroupCollection.php deleted file mode 100644 index 2ad9fef6835..00000000000 --- a/src/TextUI/XmlConfiguration/Group/GroupCollection.php +++ /dev/null @@ -1,69 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use IteratorAggregate; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class GroupCollection implements IteratorAggregate -{ - /** - * @var Group[] - */ - private $groups; - - /** - * @param Group[] $groups - */ - public static function fromArray(array $groups): self - { - return new self(...$groups); - } - - private function __construct(Group ...$groups) - { - $this->groups = $groups; - } - - /** - * @return Group[] - */ - public function asArray(): array - { - return $this->groups; - } - - /** - * @return string[] - */ - public function asArrayOfStrings(): array - { - $result = []; - - foreach ($this->groups as $group) { - $result[] = $group->name(); - } - - return $result; - } - - public function isEmpty(): bool - { - return empty($this->groups); - } - - public function getIterator(): GroupCollectionIterator - { - return new GroupCollectionIterator($this); - } -} diff --git a/src/TextUI/XmlConfiguration/Group/GroupCollectionIterator.php b/src/TextUI/XmlConfiguration/Group/GroupCollectionIterator.php deleted file mode 100644 index 0755fdac4e3..00000000000 --- a/src/TextUI/XmlConfiguration/Group/GroupCollectionIterator.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use function iterator_count; -use Countable; -use Iterator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class GroupCollectionIterator implements Countable, Iterator -{ - /** - * @var Group[] - */ - private $groups; - - /** - * @var int - */ - private $position; - - public function __construct(GroupCollection $groups) - { - $this->groups = $groups->asArray(); - } - - public function count(): int - { - return iterator_count($this); - } - - public function rewind(): void - { - $this->position = 0; - } - - public function valid(): bool - { - return $this->position < count($this->groups); - } - - public function key(): int - { - return $this->position; - } - - public function current(): Group - { - return $this->groups[$this->position]; - } - - public function next(): void - { - $this->position++; - } -} diff --git a/src/TextUI/XmlConfiguration/Group/Groups.php b/src/TextUI/XmlConfiguration/Group/Groups.php deleted file mode 100644 index 9004fe43bf4..00000000000 --- a/src/TextUI/XmlConfiguration/Group/Groups.php +++ /dev/null @@ -1,53 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Groups -{ - /** - * @var GroupCollection - */ - private $include; - - /** - * @var GroupCollection - */ - private $exclude; - - public function __construct(GroupCollection $include, GroupCollection $exclude) - { - $this->include = $include; - $this->exclude = $exclude; - } - - public function hasInclude(): bool - { - return !$this->include->isEmpty(); - } - - public function include(): GroupCollection - { - return $this->include; - } - - public function hasExclude(): bool - { - return !$this->exclude->isEmpty(); - } - - public function exclude(): GroupCollection - { - return $this->exclude; - } -} diff --git a/src/TextUI/XmlConfiguration/Loader.php b/src/TextUI/XmlConfiguration/Loader.php deleted file mode 100644 index ccefd39ee7a..00000000000 --- a/src/TextUI/XmlConfiguration/Loader.php +++ /dev/null @@ -1,1249 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use const DIRECTORY_SEPARATOR; -use const PHP_VERSION; -use function assert; -use function defined; -use function dirname; -use function explode; -use function file_exists; -use function is_numeric; -use function libxml_clear_errors; -use function libxml_get_errors; -use function libxml_use_internal_errors; -use function preg_match; -use function stream_resolve_include_path; -use function strlen; -use function strpos; -use function strtolower; -use function substr; -use function trim; -use DOMDocument; -use DOMElement; -use DOMNodeList; -use DOMXPath; -use PHPUnit\Runner\TestSuiteSorter; -use PHPUnit\TextUI\DefaultResultPrinter; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\CodeCoverage; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\Directory as FilterDirectory; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\DirectoryCollection as FilterDirectoryCollection; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Clover; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Crap4j; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Html as CodeCoverageHtml; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Php as CodeCoveragePhp; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Text as CodeCoverageText; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Xml as CodeCoverageXml; -use PHPUnit\TextUI\XmlConfiguration\Logging\Junit; -use PHPUnit\TextUI\XmlConfiguration\Logging\Logging; -use PHPUnit\TextUI\XmlConfiguration\Logging\TeamCity; -use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Html as TestDoxHtml; -use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Text as TestDoxText; -use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Xml as TestDoxXml; -use PHPUnit\TextUI\XmlConfiguration\Logging\Text; -use PHPUnit\TextUI\XmlConfiguration\TestSuite as TestSuiteConfiguration; -use PHPUnit\Util\TestDox\CliTestDoxPrinter; -use PHPUnit\Util\VersionComparisonOperator; -use PHPUnit\Util\Xml; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Loader -{ - public function load(string $filename): Configuration - { - $document = Xml::loadFile($filename, false, true, true); - $xpath = new DOMXPath($document); - - return new Configuration( - $filename, - $this->validate($document), - $this->extensions($filename, $xpath), - $this->codeCoverage($filename, $xpath, $document), - $this->groups($xpath), - $this->testdoxGroups($xpath), - $this->listeners($filename, $xpath), - $this->logging($filename, $xpath), - $this->php($filename, $xpath), - $this->phpunit($filename, $document), - $this->testSuite($filename, $xpath) - ); - } - - public function logging(string $filename, DOMXPath $xpath): Logging - { - if ($xpath->query('logging/log')->length !== 0) { - return $this->legacyLogging($filename, $xpath); - } - - $junit = null; - $element = $this->element($xpath, 'logging/junit'); - - if ($element) { - $junit = new Junit( - new File( - $this->toAbsolutePath( - $filename, - (string) $this->getStringAttribute($element, 'outputFile') - ) - ) - ); - } - - $text = null; - $element = $this->element($xpath, 'logging/text'); - - if ($element) { - $text = new Text( - new File( - $this->toAbsolutePath( - $filename, - (string) $this->getStringAttribute($element, 'outputFile') - ) - ) - ); - } - - $teamCity = null; - $element = $this->element($xpath, 'logging/teamcity'); - - if ($element) { - $teamCity = new TeamCity( - new File( - $this->toAbsolutePath( - $filename, - (string) $this->getStringAttribute($element, 'outputFile') - ) - ) - ); - } - - $testDoxHtml = null; - $element = $this->element($xpath, 'logging/testdoxHtml'); - - if ($element) { - $testDoxHtml = new TestDoxHtml( - new File( - $this->toAbsolutePath( - $filename, - (string) $this->getStringAttribute($element, 'outputFile') - ) - ) - ); - } - - $testDoxText = null; - $element = $this->element($xpath, 'logging/testdoxText'); - - if ($element) { - $testDoxText = new TestDoxText( - new File( - $this->toAbsolutePath( - $filename, - (string) $this->getStringAttribute($element, 'outputFile') - ) - ) - ); - } - - $testDoxXml = null; - $element = $this->element($xpath, 'logging/testdoxXml'); - - if ($element) { - $testDoxXml = new TestDoxXml( - new File( - $this->toAbsolutePath( - $filename, - (string) $this->getStringAttribute($element, 'outputFile') - ) - ) - ); - } - - return new Logging( - $junit, - $text, - $teamCity, - $testDoxHtml, - $testDoxText, - $testDoxXml - ); - } - - public function legacyLogging(string $filename, DOMXPath $xpath): Logging - { - $junit = null; - $teamCity = null; - $testDoxHtml = null; - $testDoxText = null; - $testDoxXml = null; - $text = null; - - foreach ($xpath->query('logging/log') as $log) { - assert($log instanceof DOMElement); - - $type = (string) $log->getAttribute('type'); - $target = (string) $log->getAttribute('target'); - - if (!$target) { - continue; - } - - $target = $this->toAbsolutePath($filename, $target); - - switch ($type) { - case 'plain': - $text = new Text( - new File($target) - ); - - break; - - case 'junit': - $junit = new Junit( - new File($target) - ); - - break; - - case 'teamcity': - $teamCity = new TeamCity( - new File($target) - ); - - break; - - case 'testdox-html': - $testDoxHtml = new TestDoxHtml( - new File($target) - ); - - break; - - case 'testdox-text': - $testDoxText = new TestDoxText( - new File($target) - ); - - break; - - case 'testdox-xml': - $testDoxXml = new TestDoxXml( - new File($target) - ); - - break; - } - } - - return new Logging( - $junit, - $text, - $teamCity, - $testDoxHtml, - $testDoxText, - $testDoxXml - ); - } - - /** - * @psalm-return array> - */ - private function validate(DOMDocument $document): array - { - $original = libxml_use_internal_errors(true); - $xsdFilename = __DIR__ . '/../../../phpunit.xsd'; - - if (defined('__PHPUNIT_PHAR_ROOT__')) { - $xsdFilename = __PHPUNIT_PHAR_ROOT__ . '/phpunit.xsd'; - } - - $document->schemaValidate($xsdFilename); - $tmp = libxml_get_errors(); - libxml_clear_errors(); - libxml_use_internal_errors($original); - - $errors = []; - - foreach ($tmp as $error) { - if (!isset($errors[$error->line])) { - $errors[$error->line] = []; - } - - $errors[$error->line][] = trim($error->message); - } - - return $errors; - } - - private function extensions(string $filename, DOMXPath $xpath): ExtensionCollection - { - $extensions = []; - - foreach ($xpath->query('extensions/extension') as $extension) { - assert($extension instanceof DOMElement); - - $extensions[] = $this->getElementConfigurationParameters($filename, $extension); - } - - return ExtensionCollection::fromArray($extensions); - } - - private function getElementConfigurationParameters(string $filename, DOMElement $element): Extension - { - /** @psalm-var class-string $class */ - $class = (string) $element->getAttribute('class'); - $file = ''; - $arguments = $this->getConfigurationArguments($filename, $element->childNodes); - - if ($element->getAttribute('file')) { - $file = $this->toAbsolutePath( - $filename, - (string) $element->getAttribute('file'), - true - ); - } - - return new Extension($class, $file, $arguments); - } - - private function toAbsolutePath(string $filename, string $path, bool $useIncludePath = false): string - { - $path = trim($path); - - if (strpos($path, '/') === 0) { - return $path; - } - - // Matches the following on Windows: - // - \\NetworkComputer\Path - // - \\.\D: - // - \\.\c: - // - C:\Windows - // - C:\windows - // - C:/windows - // - c:/windows - if (defined('PHP_WINDOWS_VERSION_BUILD') && - ($path[0] === '\\' || (strlen($path) >= 3 && preg_match('#^[A-Z]\:[/\\\]#i', substr($path, 0, 3))))) { - return $path; - } - - if (strpos($path, '://') !== false) { - return $path; - } - - $file = dirname($filename) . DIRECTORY_SEPARATOR . $path; - - if ($useIncludePath && !file_exists($file)) { - $includePathFile = stream_resolve_include_path($path); - - if ($includePathFile) { - $file = $includePathFile; - } - } - - return $file; - } - - private function getConfigurationArguments(string $filename, DOMNodeList $nodes): array - { - $arguments = []; - - if ($nodes->length === 0) { - return $arguments; - } - - foreach ($nodes as $node) { - if (!$node instanceof DOMElement) { - continue; - } - - if ($node->tagName !== 'arguments') { - continue; - } - - foreach ($node->childNodes as $argument) { - if (!$argument instanceof DOMElement) { - continue; - } - - if ($argument->tagName === 'file' || $argument->tagName === 'directory') { - $arguments[] = $this->toAbsolutePath($filename, (string) $argument->textContent); - } else { - $arguments[] = Xml::xmlToVariable($argument); - } - } - } - - return $arguments; - } - - private function codeCoverage(string $filename, DOMXPath $xpath, DOMDocument $document): CodeCoverage - { - if ($xpath->query('filter/whitelist')->length !== 0) { - return $this->legacyCodeCoverage($filename, $xpath, $document); - } - - $pathCoverage = false; - $includeUncoveredFiles = true; - $processUncoveredFiles = false; - $cacheTokens = false; - $ignoreDeprecatedCodeUnits = false; - $disableCodeCoverageIgnore = false; - - $element = $this->element($xpath, 'coverage'); - - if ($element) { - $pathCoverage = $this->getBooleanAttribute( - $element, - 'pathCoverage', - false - ); - - $includeUncoveredFiles = $this->getBooleanAttribute( - $element, - 'includeUncoveredFiles', - false - ); - - $processUncoveredFiles = $this->getBooleanAttribute( - $element, - 'processUncoveredFiles', - false - ); - - $cacheTokens = $this->getBooleanAttribute( - $element, - 'cacheTokens', - false - ); - - $ignoreDeprecatedCodeUnits = $this->getBooleanAttribute( - $element, - 'ignoreDeprecatedCodeUnits', - false - ); - - $disableCodeCoverageIgnore = $this->getBooleanAttribute( - $element, - 'disableCodeCoverageIgnore', - false - ); - } - - $clover = null; - $element = $this->element($xpath, 'coverage/report/clover'); - - if ($element) { - $clover = new Clover( - new File( - $this->toAbsolutePath( - $filename, - (string) $this->getStringAttribute($element, 'outputFile') - ) - ) - ); - } - - $crap4j = null; - $element = $this->element($xpath, 'coverage/report/crap4j'); - - if ($element) { - $crap4j = new Crap4j( - new File( - $this->toAbsolutePath( - $filename, - (string) $this->getStringAttribute($element, 'outputFile') - ) - ), - $this->getIntegerAttribute($element, 'threshold', 30) - ); - } - - $html = null; - $element = $this->element($xpath, 'coverage/report/html'); - - if ($element) { - $html = new CodeCoverageHtml( - new Directory( - $this->toAbsolutePath( - $filename, - (string) $this->getStringAttribute($element, 'outputDirectory') - ) - ), - $this->getIntegerAttribute($element, 'lowUpperBound', 50), - $this->getIntegerAttribute($element, 'highLowerBound', 90) - ); - } - - $php = null; - $element = $this->element($xpath, 'coverage/report/php'); - - if ($element) { - $php = new CodeCoveragePhp( - new File( - $this->toAbsolutePath( - $filename, - (string) $this->getStringAttribute($element, 'outputFile') - ) - ) - ); - } - - $text = null; - $element = $this->element($xpath, 'coverage/report/text'); - - if ($element) { - $text = new CodeCoverageText( - new File( - $this->toAbsolutePath( - $filename, - (string) $this->getStringAttribute($element, 'outputFile') - ) - ), - $this->getBooleanAttribute($element, 'showUncoveredFiles', false), - $this->getBooleanAttribute($element, 'showOnlySummary', false) - ); - } - - $xml = null; - $element = $this->element($xpath, 'coverage/report/xml'); - - if ($element) { - $xml = new CodeCoverageXml( - new Directory( - $this->toAbsolutePath( - $filename, - (string) $this->getStringAttribute($element, 'outputDirectory') - ) - ) - ); - } - - return new CodeCoverage( - $this->readFilterDirectories($filename, $xpath, 'coverage/include/directory'), - $this->readFilterFiles($filename, $xpath, 'coverage/include/file'), - $this->readFilterDirectories($filename, $xpath, 'coverage/exclude/directory'), - $this->readFilterFiles($filename, $xpath, 'coverage/exclude/file'), - $pathCoverage, - $includeUncoveredFiles, - $processUncoveredFiles, - $cacheTokens, - $ignoreDeprecatedCodeUnits, - $disableCodeCoverageIgnore, - $clover, - $crap4j, - $html, - $php, - $text, - $xml - ); - } - - /** - * @deprecated - */ - private function legacyCodeCoverage(string $filename, DOMXPath $xpath, DOMDocument $document): CodeCoverage - { - $cacheTokens = $this->getBooleanAttribute( - $document->documentElement, - 'cacheTokens', - false - ); - - $ignoreDeprecatedCodeUnits = $this->getBooleanAttribute( - $document->documentElement, - 'ignoreDeprecatedCodeUnitsFromCodeCoverage', - false - ); - - $disableCodeCoverageIgnore = $this->getBooleanAttribute( - $document->documentElement, - 'disableCodeCoverageIgnore', - false - ); - - $includeUncoveredFiles = true; - $processUncoveredFiles = false; - - $element = $this->element($xpath, 'filter/whitelist'); - - if ($element) { - if ($element->hasAttribute('addUncoveredFilesFromWhitelist')) { - $includeUncoveredFiles = (bool) $this->getBoolean( - (string) $element->getAttribute('addUncoveredFilesFromWhitelist'), - true - ); - } - - if ($element->hasAttribute('processUncoveredFilesFromWhitelist')) { - $processUncoveredFiles = (bool) $this->getBoolean( - (string) $element->getAttribute('processUncoveredFilesFromWhitelist'), - false - ); - } - } - - $clover = null; - $crap4j = null; - $html = null; - $php = null; - $text = null; - $xml = null; - - foreach ($xpath->query('logging/log') as $log) { - assert($log instanceof DOMElement); - - $type = (string) $log->getAttribute('type'); - $target = (string) $log->getAttribute('target'); - - if (!$target) { - continue; - } - - $target = $this->toAbsolutePath($filename, $target); - - switch ($type) { - case 'coverage-clover': - $clover = new Clover( - new File($target) - ); - - break; - - case 'coverage-crap4j': - $crap4j = new Crap4j( - new File($target), - $this->getIntegerAttribute($log, 'threshold', 30) - ); - - break; - - case 'coverage-html': - $html = new CodeCoverageHtml( - new Directory($target), - $this->getIntegerAttribute($log, 'lowUpperBound', 50), - $this->getIntegerAttribute($log, 'highLowerBound', 90) - ); - - break; - - case 'coverage-php': - $php = new CodeCoveragePhp( - new File($target) - ); - - break; - - case 'coverage-text': - $text = new CodeCoverageText( - new File($target), - $this->getBooleanAttribute($log, 'showUncoveredFiles', false), - $this->getBooleanAttribute($log, 'showOnlySummary', false) - ); - - break; - - case 'coverage-xml': - $xml = new CodeCoverageXml( - new Directory($target) - ); - - break; - } - } - - return new CodeCoverage( - $this->readFilterDirectories($filename, $xpath, 'filter/whitelist/directory'), - $this->readFilterFiles($filename, $xpath, 'filter/whitelist/file'), - $this->readFilterDirectories($filename, $xpath, 'filter/whitelist/exclude/directory'), - $this->readFilterFiles($filename, $xpath, 'filter/whitelist/exclude/file'), - false, - $includeUncoveredFiles, - $processUncoveredFiles, - $cacheTokens, - $ignoreDeprecatedCodeUnits, - $disableCodeCoverageIgnore, - $clover, - $crap4j, - $html, - $php, - $text, - $xml - ); - } - - /** - * If $value is 'false' or 'true', this returns the value that $value represents. - * Otherwise, returns $default, which may be a string in rare cases. - * - * @see \PHPUnit\TextUI\XmlConfigurationTest::testPHPConfigurationIsReadCorrectly - * - * @param bool|string $default - * - * @return bool|string - */ - private function getBoolean(string $value, $default) - { - if (strtolower($value) === 'false') { - return false; - } - - if (strtolower($value) === 'true') { - return true; - } - - return $default; - } - - private function readFilterDirectories(string $filename, DOMXPath $xpath, string $query): FilterDirectoryCollection - { - $directories = []; - - foreach ($xpath->query($query) as $directoryNode) { - assert($directoryNode instanceof DOMElement); - - $directoryPath = (string) $directoryNode->textContent; - - if (!$directoryPath) { - continue; - } - - $directories[] = new FilterDirectory( - $this->toAbsolutePath($filename, $directoryPath), - $directoryNode->hasAttribute('prefix') ? (string) $directoryNode->getAttribute('prefix') : '', - $directoryNode->hasAttribute('suffix') ? (string) $directoryNode->getAttribute('suffix') : '.php', - $directoryNode->hasAttribute('group') ? (string) $directoryNode->getAttribute('group') : 'DEFAULT' - ); - } - - return FilterDirectoryCollection::fromArray($directories); - } - - private function readFilterFiles(string $filename, DOMXPath $xpath, string $query): FileCollection - { - $files = []; - - foreach ($xpath->query($query) as $file) { - $filePath = (string) $file->textContent; - - if ($filePath) { - $files[] = new File($this->toAbsolutePath($filename, $filePath)); - } - } - - return FileCollection::fromArray($files); - } - - private function groups(DOMXPath $xpath): Groups - { - return $this->parseGroupConfiguration($xpath, 'groups'); - } - - private function testdoxGroups(DOMXPath $xpath): Groups - { - return $this->parseGroupConfiguration($xpath, 'testdoxGroups'); - } - - private function parseGroupConfiguration(DOMXPath $xpath, string $root): Groups - { - $include = []; - $exclude = []; - - foreach ($xpath->query($root . '/include/group') as $group) { - $include[] = new Group((string) $group->textContent); - } - - foreach ($xpath->query($root . '/exclude/group') as $group) { - $exclude[] = new Group((string) $group->textContent); - } - - return new Groups( - GroupCollection::fromArray($include), - GroupCollection::fromArray($exclude) - ); - } - - private function listeners(string $filename, DOMXPath $xpath): ExtensionCollection - { - $listeners = []; - - foreach ($xpath->query('listeners/listener') as $listener) { - assert($listener instanceof DOMElement); - - $listeners[] = $this->getElementConfigurationParameters($filename, $listener); - } - - return ExtensionCollection::fromArray($listeners); - } - - private function getBooleanAttribute(DOMElement $element, string $attribute, bool $default): bool - { - if (!$element->hasAttribute($attribute)) { - return $default; - } - - return (bool) $this->getBoolean( - (string) $element->getAttribute($attribute), - false - ); - } - - private function getIntegerAttribute(DOMElement $element, string $attribute, int $default): int - { - if (!$element->hasAttribute($attribute)) { - return $default; - } - - return $this->getInteger( - (string) $element->getAttribute($attribute), - $default - ); - } - - private function getStringAttribute(DOMElement $element, string $attribute): ?string - { - if (!$element->hasAttribute($attribute)) { - return null; - } - - return (string) $element->getAttribute($attribute); - } - - private function getInteger(string $value, int $default): int - { - if (is_numeric($value)) { - return (int) $value; - } - - return $default; - } - - private function php(string $filename, DOMXPath $xpath): Php - { - $includePaths = []; - - foreach ($xpath->query('php/includePath') as $includePath) { - $path = (string) $includePath->textContent; - - if ($path) { - $includePaths[] = new Directory($this->toAbsolutePath($filename, $path)); - } - } - - $iniSettings = []; - - foreach ($xpath->query('php/ini') as $ini) { - assert($ini instanceof DOMElement); - - $iniSettings[] = new IniSetting( - (string) $ini->getAttribute('name'), - (string) $ini->getAttribute('value') - ); - } - - $constants = []; - - foreach ($xpath->query('php/const') as $const) { - assert($const instanceof DOMElement); - - $value = (string) $const->getAttribute('value'); - - $constants[] = new Constant( - (string) $const->getAttribute('name'), - $this->getBoolean($value, $value) - ); - } - - $variables = [ - 'var' => [], - 'env' => [], - 'post' => [], - 'get' => [], - 'cookie' => [], - 'server' => [], - 'files' => [], - 'request' => [], - ]; - - foreach (['var', 'env', 'post', 'get', 'cookie', 'server', 'files', 'request'] as $array) { - foreach ($xpath->query('php/' . $array) as $var) { - assert($var instanceof DOMElement); - - $name = (string) $var->getAttribute('name'); - $value = (string) $var->getAttribute('value'); - $force = false; - $verbatim = false; - - if ($var->hasAttribute('force')) { - $force = (bool) $this->getBoolean($var->getAttribute('force'), false); - } - - if ($var->hasAttribute('verbatim')) { - $verbatim = $this->getBoolean($var->getAttribute('verbatim'), false); - } - - if (!$verbatim) { - $value = $this->getBoolean($value, $value); - } - - $variables[$array][] = new Variable($name, $value, $force); - } - } - - return new Php( - DirectoryCollection::fromArray($includePaths), - IniSettingCollection::fromArray($iniSettings), - ConstantCollection::fromArray($constants), - VariableCollection::fromArray($variables['var']), - VariableCollection::fromArray($variables['env']), - VariableCollection::fromArray($variables['post']), - VariableCollection::fromArray($variables['get']), - VariableCollection::fromArray($variables['cookie']), - VariableCollection::fromArray($variables['server']), - VariableCollection::fromArray($variables['files']), - VariableCollection::fromArray($variables['request']), - ); - } - - private function phpunit(string $filename, DOMDocument $document): PHPUnit - { - $executionOrder = TestSuiteSorter::ORDER_DEFAULT; - $defectsFirst = false; - $resolveDependencies = $this->getBooleanAttribute($document->documentElement, 'resolveDependencies', true); - - if ($document->documentElement->hasAttribute('executionOrder')) { - foreach (explode(',', $document->documentElement->getAttribute('executionOrder')) as $order) { - switch ($order) { - case 'default': - $executionOrder = TestSuiteSorter::ORDER_DEFAULT; - $defectsFirst = false; - $resolveDependencies = true; - - break; - - case 'depends': - $resolveDependencies = true; - - break; - - case 'no-depends': - $resolveDependencies = false; - - break; - - case 'defects': - $defectsFirst = true; - - break; - - case 'duration': - $executionOrder = TestSuiteSorter::ORDER_DURATION; - - break; - - case 'random': - $executionOrder = TestSuiteSorter::ORDER_RANDOMIZED; - - break; - - case 'reverse': - $executionOrder = TestSuiteSorter::ORDER_REVERSED; - - break; - - case 'size': - $executionOrder = TestSuiteSorter::ORDER_SIZE; - - break; - } - } - } - - $printerClass = $this->getStringAttribute($document->documentElement, 'printerClass'); - $testdox = $this->getBooleanAttribute($document->documentElement, 'testdox', false); - $conflictBetweenPrinterClassAndTestdox = false; - - if ($testdox) { - if ($printerClass !== null) { - $conflictBetweenPrinterClassAndTestdox = true; - } - - $printerClass = CliTestDoxPrinter::class; - } - - $cacheResultFile = $this->getStringAttribute($document->documentElement, 'cacheResultFile'); - - if ($cacheResultFile !== null) { - $cacheResultFile = $this->toAbsolutePath($filename, $cacheResultFile); - } - - $bootstrap = $this->getStringAttribute($document->documentElement, 'bootstrap'); - - if ($bootstrap !== null) { - $bootstrap = $this->toAbsolutePath($filename, $bootstrap); - } - - $extensionsDirectory = $this->getStringAttribute($document->documentElement, 'extensionsDirectory'); - - if ($extensionsDirectory !== null) { - $extensionsDirectory = $this->toAbsolutePath($filename, $extensionsDirectory); - } - - $testSuiteLoaderFile = $this->getStringAttribute($document->documentElement, 'testSuiteLoaderFile'); - - if ($testSuiteLoaderFile !== null) { - $testSuiteLoaderFile = $this->toAbsolutePath($filename, $testSuiteLoaderFile); - } - - $printerFile = $this->getStringAttribute($document->documentElement, 'printerFile'); - - if ($printerFile !== null) { - $printerFile = $this->toAbsolutePath($filename, $printerFile); - } - - return new PHPUnit( - $this->getBooleanAttribute($document->documentElement, 'cacheResult', false), - $cacheResultFile, - $this->getColumns($document), - $this->getColors($document), - $this->getBooleanAttribute($document->documentElement, 'stderr', false), - $this->getBooleanAttribute($document->documentElement, 'noInteraction', false), - $this->getBooleanAttribute($document->documentElement, 'verbose', false), - $this->getBooleanAttribute($document->documentElement, 'reverseDefectList', false), - $this->getBooleanAttribute($document->documentElement, 'convertDeprecationsToExceptions', true), - $this->getBooleanAttribute($document->documentElement, 'convertErrorsToExceptions', true), - $this->getBooleanAttribute($document->documentElement, 'convertNoticesToExceptions', true), - $this->getBooleanAttribute($document->documentElement, 'convertWarningsToExceptions', true), - $this->getBooleanAttribute($document->documentElement, 'forceCoversAnnotation', false), - $bootstrap, - $this->getBooleanAttribute($document->documentElement, 'processIsolation', false), - $this->getBooleanAttribute($document->documentElement, 'failOnEmptyTestSuite', false), - $this->getBooleanAttribute($document->documentElement, 'failOnIncomplete', false), - $this->getBooleanAttribute($document->documentElement, 'failOnRisky', false), - $this->getBooleanAttribute($document->documentElement, 'failOnSkipped', false), - $this->getBooleanAttribute($document->documentElement, 'failOnWarning', false), - $this->getBooleanAttribute($document->documentElement, 'stopOnDefect', false), - $this->getBooleanAttribute($document->documentElement, 'stopOnError', false), - $this->getBooleanAttribute($document->documentElement, 'stopOnFailure', false), - $this->getBooleanAttribute($document->documentElement, 'stopOnWarning', false), - $this->getBooleanAttribute($document->documentElement, 'stopOnIncomplete', false), - $this->getBooleanAttribute($document->documentElement, 'stopOnRisky', false), - $this->getBooleanAttribute($document->documentElement, 'stopOnSkipped', false), - $extensionsDirectory, - $this->getStringAttribute($document->documentElement, 'testSuiteLoaderClass'), - $testSuiteLoaderFile, - $printerClass, - $printerFile, - $this->getBooleanAttribute($document->documentElement, 'beStrictAboutChangesToGlobalState', false), - $this->getBooleanAttribute($document->documentElement, 'beStrictAboutOutputDuringTests', false), - $this->getBooleanAttribute($document->documentElement, 'beStrictAboutResourceUsageDuringSmallTests', false), - $this->getBooleanAttribute($document->documentElement, 'beStrictAboutTestsThatDoNotTestAnything', true), - $this->getBooleanAttribute($document->documentElement, 'beStrictAboutTodoAnnotatedTests', false), - $this->getBooleanAttribute($document->documentElement, 'beStrictAboutCoversAnnotation', false), - $this->getBooleanAttribute($document->documentElement, 'enforceTimeLimit', false), - $this->getIntegerAttribute($document->documentElement, 'defaultTimeLimit', 1), - $this->getIntegerAttribute($document->documentElement, 'timeoutForSmallTests', 1), - $this->getIntegerAttribute($document->documentElement, 'timeoutForMediumTests', 10), - $this->getIntegerAttribute($document->documentElement, 'timeoutForLargeTests', 60), - $this->getStringAttribute($document->documentElement, 'defaultTestSuite'), - $executionOrder, - $resolveDependencies, - $defectsFirst, - $this->getBooleanAttribute($document->documentElement, 'backupGlobals', false), - $this->getBooleanAttribute($document->documentElement, 'backupStaticAttributes', false), - $this->getBooleanAttribute($document->documentElement, 'registerMockObjectsFromTestArgumentsRecursively', false), - $conflictBetweenPrinterClassAndTestdox - ); - } - - private function getColors(DOMDocument $document): string - { - $colors = DefaultResultPrinter::COLOR_DEFAULT; - - if ($document->documentElement->hasAttribute('colors')) { - /* only allow boolean for compatibility with previous versions - 'always' only allowed from command line */ - if ($this->getBoolean($document->documentElement->getAttribute('colors'), false)) { - $colors = DefaultResultPrinter::COLOR_AUTO; - } else { - $colors = DefaultResultPrinter::COLOR_NEVER; - } - } - - return $colors; - } - - /** - * @return int|string - */ - private function getColumns(DOMDocument $document) - { - $columns = 80; - - if ($document->documentElement->hasAttribute('columns')) { - $columns = (string) $document->documentElement->getAttribute('columns'); - - if ($columns !== 'max') { - $columns = $this->getInteger($columns, 80); - } - } - - return $columns; - } - - private function testSuite(string $filename, DOMXPath $xpath): TestSuiteCollection - { - $testSuites = []; - - foreach ($this->getTestSuiteElements($xpath) as $element) { - $exclude = []; - - foreach ($element->getElementsByTagName('exclude') as $excludeNode) { - $excludeFile = (string) $excludeNode->textContent; - - if ($excludeFile) { - $exclude[] = new File($this->toAbsolutePath($filename, $excludeFile)); - } - } - - $directories = []; - - foreach ($element->getElementsByTagName('directory') as $directoryNode) { - assert($directoryNode instanceof DOMElement); - - $directory = (string) $directoryNode->textContent; - - if (empty($directory)) { - continue; - } - - $prefix = ''; - - if ($directoryNode->hasAttribute('prefix')) { - $prefix = (string) $directoryNode->getAttribute('prefix'); - } - - $suffix = 'Test.php'; - - if ($directoryNode->hasAttribute('suffix')) { - $suffix = (string) $directoryNode->getAttribute('suffix'); - } - - $phpVersion = PHP_VERSION; - - if ($directoryNode->hasAttribute('phpVersion')) { - $phpVersion = (string) $directoryNode->getAttribute('phpVersion'); - } - - $phpVersionOperator = new VersionComparisonOperator('>='); - - if ($directoryNode->hasAttribute('phpVersionOperator')) { - $phpVersionOperator = new VersionComparisonOperator((string) $directoryNode->getAttribute('phpVersionOperator')); - } - - $directories[] = new TestDirectory( - $this->toAbsolutePath($filename, $directory), - $prefix, - $suffix, - $phpVersion, - $phpVersionOperator - ); - } - - $files = []; - - foreach ($element->getElementsByTagName('file') as $fileNode) { - assert($fileNode instanceof DOMElement); - - $file = (string) $fileNode->textContent; - - if (empty($file)) { - continue; - } - - $phpVersion = PHP_VERSION; - - if ($fileNode->hasAttribute('phpVersion')) { - $phpVersion = (string) $fileNode->getAttribute('phpVersion'); - } - - $phpVersionOperator = new VersionComparisonOperator('>='); - - if ($fileNode->hasAttribute('phpVersionOperator')) { - $phpVersionOperator = new VersionComparisonOperator((string) $fileNode->getAttribute('phpVersionOperator')); - } - - $files[] = new TestFile( - $this->toAbsolutePath($filename, $file), - $phpVersion, - $phpVersionOperator - ); - } - - $testSuites[] = new TestSuiteConfiguration( - (string) $element->getAttribute('name'), - TestDirectoryCollection::fromArray($directories), - TestFileCollection::fromArray($files), - FileCollection::fromArray($exclude) - ); - } - - return TestSuiteCollection::fromArray($testSuites); - } - - /** - * @return DOMElement[] - */ - private function getTestSuiteElements(DOMXPath $xpath): array - { - /** @var DOMElement[] $elements */ - $elements = []; - - $testSuiteNodes = $xpath->query('testsuites/testsuite'); - - if ($testSuiteNodes->length === 0) { - $testSuiteNodes = $xpath->query('testsuite'); - } - - if ($testSuiteNodes->length === 1) { - $element = $testSuiteNodes->item(0); - - assert($element instanceof DOMElement); - - $elements[] = $element; - } else { - foreach ($testSuiteNodes as $testSuiteNode) { - assert($testSuiteNode instanceof DOMElement); - - $elements[] = $testSuiteNode; - } - } - - return $elements; - } - - private function element(DOMXPath $xpath, string $element): ?DOMElement - { - $nodes = $xpath->query($element); - - if ($nodes->length === 1) { - $node = $nodes->item(0); - - assert($node instanceof DOMElement); - - return $node; - } - - return null; - } -} diff --git a/src/TextUI/XmlConfiguration/Logging/Junit.php b/src/TextUI/XmlConfiguration/Logging/Junit.php deleted file mode 100644 index efde962d936..00000000000 --- a/src/TextUI/XmlConfiguration/Logging/Junit.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\Logging; - -use PHPUnit\TextUI\XmlConfiguration\File; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Junit -{ - /** - * @var File - */ - private $target; - - public function __construct(File $target) - { - $this->target = $target; - } - - public function target(): File - { - return $this->target; - } -} diff --git a/src/TextUI/XmlConfiguration/Logging/Logging.php b/src/TextUI/XmlConfiguration/Logging/Logging.php deleted file mode 100644 index cdceced5007..00000000000 --- a/src/TextUI/XmlConfiguration/Logging/Logging.php +++ /dev/null @@ -1,146 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\Logging; - -use PHPUnit\TextUI\XmlConfiguration\Exception; -use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Html as TestDoxHtml; -use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Text as TestDoxText; -use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Xml as TestDoxXml; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Logging -{ - /** - * @var ?Junit - */ - private $junit; - - /** - * @var ?Text - */ - private $text; - - /** - * @var ?TeamCity - */ - private $teamCity; - - /** - * @var ?TestDoxHtml - */ - private $testDoxHtml; - - /** - * @var ?TestDoxText - */ - private $testDoxText; - - /** - * @var ?TestDoxXml - */ - private $testDoxXml; - - public function __construct(?Junit $junit, ?Text $text, ?TeamCity $teamCity, ?TestDoxHtml $testDoxHtml, ?TestDoxText $testDoxText, ?TestDoxXml $testDoxXml) - { - $this->junit = $junit; - $this->text = $text; - $this->teamCity = $teamCity; - $this->testDoxHtml = $testDoxHtml; - $this->testDoxText = $testDoxText; - $this->testDoxXml = $testDoxXml; - } - - public function hasJunit(): bool - { - return $this->junit !== null; - } - - public function junit(): Junit - { - if ($this->junit === null) { - throw new Exception('Logger "JUnit XML" is not configured'); - } - - return $this->junit; - } - - public function hasText(): bool - { - return $this->text !== null; - } - - public function text(): Text - { - if ($this->text === null) { - throw new Exception('Logger "Text" is not configured'); - } - - return $this->text; - } - - public function hasTeamCity(): bool - { - return $this->teamCity !== null; - } - - public function teamCity(): TeamCity - { - if ($this->teamCity === null) { - throw new Exception('Logger "Team City" is not configured'); - } - - return $this->teamCity; - } - - public function hasTestDoxHtml(): bool - { - return $this->testDoxHtml !== null; - } - - public function testDoxHtml(): TestDoxHtml - { - if ($this->testDoxHtml === null) { - throw new Exception('Logger "TestDox HTML" is not configured'); - } - - return $this->testDoxHtml; - } - - public function hasTestDoxText(): bool - { - return $this->testDoxText !== null; - } - - public function testDoxText(): TestDoxText - { - if ($this->testDoxText === null) { - throw new Exception('Logger "TestDox Text" is not configured'); - } - - return $this->testDoxText; - } - - public function hasTestDoxXml(): bool - { - return $this->testDoxXml !== null; - } - - public function testDoxXml(): TestDoxXml - { - if ($this->testDoxXml === null) { - throw new Exception('Logger "TestDox XML" is not configured'); - } - - return $this->testDoxXml; - } -} diff --git a/src/TextUI/XmlConfiguration/Logging/TeamCity.php b/src/TextUI/XmlConfiguration/Logging/TeamCity.php deleted file mode 100644 index 03b2b56a9cc..00000000000 --- a/src/TextUI/XmlConfiguration/Logging/TeamCity.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\Logging; - -use PHPUnit\TextUI\XmlConfiguration\File; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class TeamCity -{ - /** - * @var File - */ - private $target; - - public function __construct(File $target) - { - $this->target = $target; - } - - public function target(): File - { - return $this->target; - } -} diff --git a/src/TextUI/XmlConfiguration/Logging/TestDox/Html.php b/src/TextUI/XmlConfiguration/Logging/TestDox/Html.php deleted file mode 100644 index 310040b2e55..00000000000 --- a/src/TextUI/XmlConfiguration/Logging/TestDox/Html.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\Logging\TestDox; - -use PHPUnit\TextUI\XmlConfiguration\File; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Html -{ - /** - * @var File - */ - private $target; - - public function __construct(File $target) - { - $this->target = $target; - } - - public function target(): File - { - return $this->target; - } -} diff --git a/src/TextUI/XmlConfiguration/Logging/TestDox/Text.php b/src/TextUI/XmlConfiguration/Logging/TestDox/Text.php deleted file mode 100644 index 59d37e9b230..00000000000 --- a/src/TextUI/XmlConfiguration/Logging/TestDox/Text.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\Logging\TestDox; - -use PHPUnit\TextUI\XmlConfiguration\File; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Text -{ - /** - * @var File - */ - private $target; - - public function __construct(File $target) - { - $this->target = $target; - } - - public function target(): File - { - return $this->target; - } -} diff --git a/src/TextUI/XmlConfiguration/Logging/TestDox/Xml.php b/src/TextUI/XmlConfiguration/Logging/TestDox/Xml.php deleted file mode 100644 index b8c1576c169..00000000000 --- a/src/TextUI/XmlConfiguration/Logging/TestDox/Xml.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\Logging\TestDox; - -use PHPUnit\TextUI\XmlConfiguration\File; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Xml -{ - /** - * @var File - */ - private $target; - - public function __construct(File $target) - { - $this->target = $target; - } - - public function target(): File - { - return $this->target; - } -} diff --git a/src/TextUI/XmlConfiguration/Logging/Text.php b/src/TextUI/XmlConfiguration/Logging/Text.php deleted file mode 100644 index 2769ec96726..00000000000 --- a/src/TextUI/XmlConfiguration/Logging/Text.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\Logging; - -use PHPUnit\TextUI\XmlConfiguration\File; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Text -{ - /** - * @var File - */ - private $target; - - public function __construct(File $target) - { - $this->target = $target; - } - - public function target(): File - { - return $this->target; - } -} diff --git a/src/TextUI/XmlConfiguration/PHP/Constant.php b/src/TextUI/XmlConfiguration/PHP/Constant.php deleted file mode 100644 index e9b28b9e89d..00000000000 --- a/src/TextUI/XmlConfiguration/PHP/Constant.php +++ /dev/null @@ -1,43 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Constant -{ - /** - * @var string - */ - private $name; - - /** - * @var mixed - */ - private $value; - - public function __construct(string $name, $value) - { - $this->name = $name; - $this->value = $value; - } - - public function name(): string - { - return $this->name; - } - - public function value() - { - return $this->value; - } -} diff --git a/src/TextUI/XmlConfiguration/PHP/ConstantCollection.php b/src/TextUI/XmlConfiguration/PHP/ConstantCollection.php deleted file mode 100644 index 51c14715fc2..00000000000 --- a/src/TextUI/XmlConfiguration/PHP/ConstantCollection.php +++ /dev/null @@ -1,57 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use Countable; -use IteratorAggregate; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class ConstantCollection implements Countable, IteratorAggregate -{ - /** - * @var Constant[] - */ - private $constants; - - /** - * @param Constant[] $constants - */ - public static function fromArray(array $constants): self - { - return new self(...$constants); - } - - private function __construct(Constant ...$constants) - { - $this->constants = $constants; - } - - /** - * @return Constant[] - */ - public function asArray(): array - { - return $this->constants; - } - - public function count(): int - { - return count($this->constants); - } - - public function getIterator(): ConstantCollectionIterator - { - return new ConstantCollectionIterator($this); - } -} diff --git a/src/TextUI/XmlConfiguration/PHP/ConstantCollectionIterator.php b/src/TextUI/XmlConfiguration/PHP/ConstantCollectionIterator.php deleted file mode 100644 index c1c8d834bae..00000000000 --- a/src/TextUI/XmlConfiguration/PHP/ConstantCollectionIterator.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use function iterator_count; -use Countable; -use Iterator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ConstantCollectionIterator implements Countable, Iterator -{ - /** - * @var Constant[] - */ - private $constants; - - /** - * @var int - */ - private $position; - - public function __construct(ConstantCollection $constants) - { - $this->constants = $constants->asArray(); - } - - public function count(): int - { - return iterator_count($this); - } - - public function rewind(): void - { - $this->position = 0; - } - - public function valid(): bool - { - return $this->position < count($this->constants); - } - - public function key(): int - { - return $this->position; - } - - public function current(): Constant - { - return $this->constants[$this->position]; - } - - public function next(): void - { - $this->position++; - } -} diff --git a/src/TextUI/XmlConfiguration/PHP/IniSetting.php b/src/TextUI/XmlConfiguration/PHP/IniSetting.php deleted file mode 100644 index 58cf735b091..00000000000 --- a/src/TextUI/XmlConfiguration/PHP/IniSetting.php +++ /dev/null @@ -1,43 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class IniSetting -{ - /** - * @var string - */ - private $name; - - /** - * @var string - */ - private $value; - - public function __construct(string $name, string $value) - { - $this->name = $name; - $this->value = $value; - } - - public function name(): string - { - return $this->name; - } - - public function value(): string - { - return $this->value; - } -} diff --git a/src/TextUI/XmlConfiguration/PHP/IniSettingCollection.php b/src/TextUI/XmlConfiguration/PHP/IniSettingCollection.php deleted file mode 100644 index 216d85aeced..00000000000 --- a/src/TextUI/XmlConfiguration/PHP/IniSettingCollection.php +++ /dev/null @@ -1,57 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use Countable; -use IteratorAggregate; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class IniSettingCollection implements Countable, IteratorAggregate -{ - /** - * @var IniSetting[] - */ - private $iniSettings; - - /** - * @param IniSetting[] $iniSettings - */ - public static function fromArray(array $iniSettings): self - { - return new self(...$iniSettings); - } - - private function __construct(IniSetting ...$iniSettings) - { - $this->iniSettings = $iniSettings; - } - - /** - * @return IniSetting[] - */ - public function asArray(): array - { - return $this->iniSettings; - } - - public function count(): int - { - return count($this->iniSettings); - } - - public function getIterator(): IniSettingCollectionIterator - { - return new IniSettingCollectionIterator($this); - } -} diff --git a/src/TextUI/XmlConfiguration/PHP/IniSettingCollectionIterator.php b/src/TextUI/XmlConfiguration/PHP/IniSettingCollectionIterator.php deleted file mode 100644 index f31225e8f68..00000000000 --- a/src/TextUI/XmlConfiguration/PHP/IniSettingCollectionIterator.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use function iterator_count; -use Countable; -use Iterator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class IniSettingCollectionIterator implements Countable, Iterator -{ - /** - * @var IniSetting[] - */ - private $iniSettings; - - /** - * @var int - */ - private $position; - - public function __construct(IniSettingCollection $iniSettings) - { - $this->iniSettings = $iniSettings->asArray(); - } - - public function count(): int - { - return iterator_count($this); - } - - public function rewind(): void - { - $this->position = 0; - } - - public function valid(): bool - { - return $this->position < count($this->iniSettings); - } - - public function key(): int - { - return $this->position; - } - - public function current(): IniSetting - { - return $this->iniSettings[$this->position]; - } - - public function next(): void - { - $this->position++; - } -} diff --git a/src/TextUI/XmlConfiguration/PHP/Php.php b/src/TextUI/XmlConfiguration/PHP/Php.php deleted file mode 100644 index 26897e3593f..00000000000 --- a/src/TextUI/XmlConfiguration/PHP/Php.php +++ /dev/null @@ -1,142 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Php -{ - /** - * @var DirectoryCollection - */ - private $includePaths; - - /** - * @var IniSettingCollection - */ - private $iniSettings; - - /** - * @var ConstantCollection - */ - private $constants; - - /** - * @var VariableCollection - */ - private $globalVariables; - - /** - * @var VariableCollection - */ - private $envVariables; - - /** - * @var VariableCollection - */ - private $postVariables; - - /** - * @var VariableCollection - */ - private $getVariables; - - /** - * @var VariableCollection - */ - private $cookieVariables; - - /** - * @var VariableCollection - */ - private $serverVariables; - - /** - * @var VariableCollection - */ - private $filesVariables; - - /** - * @var VariableCollection - */ - private $requestVariables; - - public function __construct(DirectoryCollection $includePaths, IniSettingCollection $iniSettings, ConstantCollection $constants, VariableCollection $globalVariables, VariableCollection $envVariables, VariableCollection $postVariables, VariableCollection $getVariables, VariableCollection $cookieVariables, VariableCollection $serverVariables, VariableCollection $filesVariables, VariableCollection $requestVariables) - { - $this->includePaths = $includePaths; - $this->iniSettings = $iniSettings; - $this->constants = $constants; - $this->globalVariables = $globalVariables; - $this->envVariables = $envVariables; - $this->postVariables = $postVariables; - $this->getVariables = $getVariables; - $this->cookieVariables = $cookieVariables; - $this->serverVariables = $serverVariables; - $this->filesVariables = $filesVariables; - $this->requestVariables = $requestVariables; - } - - public function includePaths(): DirectoryCollection - { - return $this->includePaths; - } - - public function iniSettings(): IniSettingCollection - { - return $this->iniSettings; - } - - public function constants(): ConstantCollection - { - return $this->constants; - } - - public function globalVariables(): VariableCollection - { - return $this->globalVariables; - } - - public function envVariables(): VariableCollection - { - return $this->envVariables; - } - - public function postVariables(): VariableCollection - { - return $this->postVariables; - } - - public function getVariables(): VariableCollection - { - return $this->getVariables; - } - - public function cookieVariables(): VariableCollection - { - return $this->cookieVariables; - } - - public function serverVariables(): VariableCollection - { - return $this->serverVariables; - } - - public function filesVariables(): VariableCollection - { - return $this->filesVariables; - } - - public function requestVariables(): VariableCollection - { - return $this->requestVariables; - } -} diff --git a/src/TextUI/XmlConfiguration/PHP/Variable.php b/src/TextUI/XmlConfiguration/PHP/Variable.php deleted file mode 100644 index c2684e28f63..00000000000 --- a/src/TextUI/XmlConfiguration/PHP/Variable.php +++ /dev/null @@ -1,54 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Variable -{ - /** - * @var string - */ - private $name; - - /** - * @var mixed - */ - private $value; - - /** - * @var bool - */ - private $force; - - public function __construct(string $name, $value, bool $force) - { - $this->name = $name; - $this->value = $value; - $this->force = $force; - } - - public function name(): string - { - return $this->name; - } - - public function value() - { - return $this->value; - } - - public function force(): bool - { - return $this->force; - } -} diff --git a/src/TextUI/XmlConfiguration/PHP/VariableCollection.php b/src/TextUI/XmlConfiguration/PHP/VariableCollection.php deleted file mode 100644 index e84992168f6..00000000000 --- a/src/TextUI/XmlConfiguration/PHP/VariableCollection.php +++ /dev/null @@ -1,57 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use Countable; -use IteratorAggregate; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class VariableCollection implements Countable, IteratorAggregate -{ - /** - * @var Variable[] - */ - private $variables; - - /** - * @param Variable[] $variables - */ - public static function fromArray(array $variables): self - { - return new self(...$variables); - } - - private function __construct(Variable ...$variables) - { - $this->variables = $variables; - } - - /** - * @return Variable[] - */ - public function asArray(): array - { - return $this->variables; - } - - public function count(): int - { - return count($this->variables); - } - - public function getIterator(): VariableCollectionIterator - { - return new VariableCollectionIterator($this); - } -} diff --git a/src/TextUI/XmlConfiguration/PHP/VariableCollectionIterator.php b/src/TextUI/XmlConfiguration/PHP/VariableCollectionIterator.php deleted file mode 100644 index 3d594c1ee58..00000000000 --- a/src/TextUI/XmlConfiguration/PHP/VariableCollectionIterator.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use function iterator_count; -use Countable; -use Iterator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class VariableCollectionIterator implements Countable, Iterator -{ - /** - * @var Variable[] - */ - private $variables; - - /** - * @var int - */ - private $position; - - public function __construct(VariableCollection $variables) - { - $this->variables = $variables->asArray(); - } - - public function count(): int - { - return iterator_count($this); - } - - public function rewind(): void - { - $this->position = 0; - } - - public function valid(): bool - { - return $this->position < count($this->variables); - } - - public function key(): int - { - return $this->position; - } - - public function current(): Variable - { - return $this->variables[$this->position]; - } - - public function next(): void - { - $this->position++; - } -} diff --git a/src/TextUI/XmlConfiguration/PHPUnit/Extension.php b/src/TextUI/XmlConfiguration/PHPUnit/Extension.php deleted file mode 100644 index 77c5b2d0d64..00000000000 --- a/src/TextUI/XmlConfiguration/PHPUnit/Extension.php +++ /dev/null @@ -1,71 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class Extension -{ - /** - * @var string - * @psalm-var class-string - */ - private $className; - - /** - * @var string - */ - private $sourceFile; - - /** - * @var array - */ - private $arguments; - - /** - * @psalm-param class-string $className - */ - public function __construct(string $className, string $sourceFile, array $arguments) - { - $this->className = $className; - $this->sourceFile = $sourceFile; - $this->arguments = $arguments; - } - - /** - * @psalm-return class-string - */ - public function className(): string - { - return $this->className; - } - - public function hasSourceFile(): bool - { - return $this->sourceFile !== ''; - } - - public function sourceFile(): string - { - return $this->sourceFile; - } - - public function hasArguments(): bool - { - return !empty($this->arguments); - } - - public function arguments(): array - { - return $this->arguments; - } -} diff --git a/src/TextUI/XmlConfiguration/PHPUnit/ExtensionCollection.php b/src/TextUI/XmlConfiguration/PHPUnit/ExtensionCollection.php deleted file mode 100644 index 45be2eeebc7..00000000000 --- a/src/TextUI/XmlConfiguration/PHPUnit/ExtensionCollection.php +++ /dev/null @@ -1,50 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use IteratorAggregate; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class ExtensionCollection implements IteratorAggregate -{ - /** - * @var Extension[] - */ - private $extensions; - - /** - * @param Extension[] $extensions - */ - public static function fromArray(array $extensions): self - { - return new self(...$extensions); - } - - private function __construct(Extension ...$extensions) - { - $this->extensions = $extensions; - } - - /** - * @return Extension[] - */ - public function asArray(): array - { - return $this->extensions; - } - - public function getIterator(): ExtensionCollectionIterator - { - return new ExtensionCollectionIterator($this); - } -} diff --git a/src/TextUI/XmlConfiguration/PHPUnit/ExtensionCollectionIterator.php b/src/TextUI/XmlConfiguration/PHPUnit/ExtensionCollectionIterator.php deleted file mode 100644 index 4bd54be4943..00000000000 --- a/src/TextUI/XmlConfiguration/PHPUnit/ExtensionCollectionIterator.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use function iterator_count; -use Countable; -use Iterator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ExtensionCollectionIterator implements Countable, Iterator -{ - /** - * @var Extension[] - */ - private $extensions; - - /** - * @var int - */ - private $position; - - public function __construct(ExtensionCollection $extensions) - { - $this->extensions = $extensions->asArray(); - } - - public function count(): int - { - return iterator_count($this); - } - - public function rewind(): void - { - $this->position = 0; - } - - public function valid(): bool - { - return $this->position < count($this->extensions); - } - - public function key(): int - { - return $this->position; - } - - public function current(): Extension - { - return $this->extensions[$this->position]; - } - - public function next(): void - { - $this->position++; - } -} diff --git a/src/TextUI/XmlConfiguration/PHPUnit/ExtensionHandler.php b/src/TextUI/XmlConfiguration/PHPUnit/ExtensionHandler.php deleted file mode 100644 index e1b00d3ff23..00000000000 --- a/src/TextUI/XmlConfiguration/PHPUnit/ExtensionHandler.php +++ /dev/null @@ -1,101 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function class_exists; -use function sprintf; -use PHPUnit\Framework\Exception; -use PHPUnit\Framework\TestListener; -use PHPUnit\Runner\Hook; -use ReflectionClass; -use ReflectionException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ExtensionHandler -{ - public function createHookInstance(Extension $extension): Hook - { - $object = $this->createInstance($extension); - - if (!$object instanceof Hook) { - throw new Exception( - sprintf( - 'Class "%s" does not implement a PHPUnit\Runner\Hook interface', - $extension->className() - ) - ); - } - - return $object; - } - - public function createTestListenerInstance(Extension $extension): TestListener - { - $object = $this->createInstance($extension); - - if (!$object instanceof TestListener) { - throw new Exception( - sprintf( - 'Class "%s" does not implement the PHPUnit\Framework\TestListener interface', - $extension->className() - ) - ); - } - - return $object; - } - - private function createInstance(Extension $extension): object - { - $this->ensureClassExists($extension); - - try { - $reflector = new ReflectionClass($extension->className()); - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - - if (!$extension->hasArguments()) { - return $reflector->newInstance(); - } - - return $reflector->newInstanceArgs($extension->arguments()); - } - - /** - * @throws Exception - */ - private function ensureClassExists(Extension $extension): void - { - if (class_exists($extension->className(), false)) { - return; - } - - if ($extension->hasSourceFile()) { - /** @noinspection PhpIncludeInspection */ - require_once $extension->sourceFile(); - } - - if (!class_exists($extension->className())) { - throw new Exception( - sprintf( - 'Class "%s" does not exist', - $extension->className() - ) - ); - } - } -} diff --git a/src/TextUI/XmlConfiguration/PHPUnit/PHPUnit.php b/src/TextUI/XmlConfiguration/PHPUnit/PHPUnit.php deleted file mode 100644 index 9efe1dd2929..00000000000 --- a/src/TextUI/XmlConfiguration/PHPUnit/PHPUnit.php +++ /dev/null @@ -1,692 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class PHPUnit -{ - /** - * @var bool - */ - private $cacheResult; - - /** - * @var ?string - */ - private $cacheResultFile; - - /** - * @var int|string - */ - private $columns; - - /** - * @var string - */ - private $colors; - - /** - * @var bool - */ - private $stderr; - - /** - * @var bool - */ - private $noInteraction; - - /** - * @var bool - */ - private $verbose; - - /** - * @var bool - */ - private $reverseDefectList; - - /** - * @var bool - */ - private $convertDeprecationsToExceptions; - - /** - * @var bool - */ - private $convertErrorsToExceptions; - - /** - * @var bool - */ - private $convertNoticesToExceptions; - - /** - * @var bool - */ - private $convertWarningsToExceptions; - - /** - * @var bool - */ - private $forceCoversAnnotation; - - /** - * @var ?string - */ - private $bootstrap; - - /** - * @var bool - */ - private $processIsolation; - - /** - * @var bool - */ - private $failOnEmptyTestSuite; - - /** - * @var bool - */ - private $failOnIncomplete; - - /** - * @var bool - */ - private $failOnRisky; - - /** - * @var bool - */ - private $failOnSkipped; - - /** - * @var bool - */ - private $failOnWarning; - - /** - * @var bool - */ - private $stopOnDefect; - - /** - * @var bool - */ - private $stopOnError; - - /** - * @var bool - */ - private $stopOnFailure; - - /** - * @var bool - */ - private $stopOnWarning; - - /** - * @var bool - */ - private $stopOnIncomplete; - - /** - * @var bool - */ - private $stopOnRisky; - - /** - * @var bool - */ - private $stopOnSkipped; - - /** - * @var ?string - */ - private $extensionsDirectory; - - /** - * @var ?string - * - * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 - */ - private $testSuiteLoaderClass; - - /** - * @var ?string - * - * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 - */ - private $testSuiteLoaderFile; - - /** - * @var ?string - */ - private $printerClass; - - /** - * @var ?string - */ - private $printerFile; - - /** - * @var bool - */ - private $beStrictAboutChangesToGlobalState; - - /** - * @var bool - */ - private $beStrictAboutOutputDuringTests; - - /** - * @var bool - */ - private $beStrictAboutResourceUsageDuringSmallTests; - - /** - * @var bool - */ - private $beStrictAboutTestsThatDoNotTestAnything; - - /** - * @var bool - */ - private $beStrictAboutTodoAnnotatedTests; - - /** - * @var bool - */ - private $beStrictAboutCoversAnnotation; - - /** - * @var bool - */ - private $enforceTimeLimit; - - /** - * @var int - */ - private $defaultTimeLimit; - - /** - * @var int - */ - private $timeoutForSmallTests; - - /** - * @var int - */ - private $timeoutForMediumTests; - - /** - * @var int - */ - private $timeoutForLargeTests; - - /** - * @var ?string - */ - private $defaultTestSuite; - - /** - * @var int - */ - private $executionOrder; - - /** - * @var bool - */ - private $resolveDependencies; - - /** - * @var bool - */ - private $defectsFirst; - - /** - * @var bool - */ - private $backupGlobals; - - /** - * @var bool - */ - private $backupStaticAttributes; - - /** - * @var bool - */ - private $registerMockObjectsFromTestArgumentsRecursively; - - /** - * @var bool - */ - private $conflictBetweenPrinterClassAndTestdox; - - public function __construct(bool $cacheResult, ?string $cacheResultFile, $columns, string $colors, bool $stderr, bool $noInteraction, bool $verbose, bool $reverseDefectList, bool $convertDeprecationsToExceptions, bool $convertErrorsToExceptions, bool $convertNoticesToExceptions, bool $convertWarningsToExceptions, bool $forceCoversAnnotation, ?string $bootstrap, bool $processIsolation, bool $failOnEmptyTestSuite, bool $failOnIncomplete, bool $failOnRisky, bool $failOnSkipped, bool $failOnWarning, bool $stopOnDefect, bool $stopOnError, bool $stopOnFailure, bool $stopOnWarning, bool $stopOnIncomplete, bool $stopOnRisky, bool $stopOnSkipped, ?string $extensionsDirectory, ?string $testSuiteLoaderClass, ?string $testSuiteLoaderFile, ?string $printerClass, ?string $printerFile, bool $beStrictAboutChangesToGlobalState, bool $beStrictAboutOutputDuringTests, bool $beStrictAboutResourceUsageDuringSmallTests, bool $beStrictAboutTestsThatDoNotTestAnything, bool $beStrictAboutTodoAnnotatedTests, bool $beStrictAboutCoversAnnotation, bool $enforceTimeLimit, int $defaultTimeLimit, int $timeoutForSmallTests, int $timeoutForMediumTests, int $timeoutForLargeTests, ?string $defaultTestSuite, int $executionOrder, bool $resolveDependencies, bool $defectsFirst, bool $backupGlobals, bool $backupStaticAttributes, bool $registerMockObjectsFromTestArgumentsRecursively, bool $conflictBetweenPrinterClassAndTestdox) - { - $this->cacheResult = $cacheResult; - $this->cacheResultFile = $cacheResultFile; - $this->columns = $columns; - $this->colors = $colors; - $this->stderr = $stderr; - $this->noInteraction = $noInteraction; - $this->verbose = $verbose; - $this->reverseDefectList = $reverseDefectList; - $this->convertDeprecationsToExceptions = $convertDeprecationsToExceptions; - $this->convertErrorsToExceptions = $convertErrorsToExceptions; - $this->convertNoticesToExceptions = $convertNoticesToExceptions; - $this->convertWarningsToExceptions = $convertWarningsToExceptions; - $this->forceCoversAnnotation = $forceCoversAnnotation; - $this->bootstrap = $bootstrap; - $this->processIsolation = $processIsolation; - $this->failOnEmptyTestSuite = $failOnEmptyTestSuite; - $this->failOnIncomplete = $failOnIncomplete; - $this->failOnRisky = $failOnRisky; - $this->failOnSkipped = $failOnSkipped; - $this->failOnWarning = $failOnWarning; - $this->stopOnDefect = $stopOnDefect; - $this->stopOnError = $stopOnError; - $this->stopOnFailure = $stopOnFailure; - $this->stopOnWarning = $stopOnWarning; - $this->stopOnIncomplete = $stopOnIncomplete; - $this->stopOnRisky = $stopOnRisky; - $this->stopOnSkipped = $stopOnSkipped; - $this->extensionsDirectory = $extensionsDirectory; - $this->testSuiteLoaderClass = $testSuiteLoaderClass; - $this->testSuiteLoaderFile = $testSuiteLoaderFile; - $this->printerClass = $printerClass; - $this->printerFile = $printerFile; - $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; - $this->beStrictAboutOutputDuringTests = $beStrictAboutOutputDuringTests; - $this->beStrictAboutResourceUsageDuringSmallTests = $beStrictAboutResourceUsageDuringSmallTests; - $this->beStrictAboutTestsThatDoNotTestAnything = $beStrictAboutTestsThatDoNotTestAnything; - $this->beStrictAboutTodoAnnotatedTests = $beStrictAboutTodoAnnotatedTests; - $this->beStrictAboutCoversAnnotation = $beStrictAboutCoversAnnotation; - $this->enforceTimeLimit = $enforceTimeLimit; - $this->defaultTimeLimit = $defaultTimeLimit; - $this->timeoutForSmallTests = $timeoutForSmallTests; - $this->timeoutForMediumTests = $timeoutForMediumTests; - $this->timeoutForLargeTests = $timeoutForLargeTests; - $this->defaultTestSuite = $defaultTestSuite; - $this->executionOrder = $executionOrder; - $this->resolveDependencies = $resolveDependencies; - $this->defectsFirst = $defectsFirst; - $this->backupGlobals = $backupGlobals; - $this->backupStaticAttributes = $backupStaticAttributes; - $this->registerMockObjectsFromTestArgumentsRecursively = $registerMockObjectsFromTestArgumentsRecursively; - $this->conflictBetweenPrinterClassAndTestdox = $conflictBetweenPrinterClassAndTestdox; - } - - public function cacheResult(): bool - { - return $this->cacheResult; - } - - public function hasCacheResultFile(): bool - { - return $this->cacheResultFile !== null; - } - - /** - * @throws Exception - */ - public function cacheResultFile(): string - { - if (!$this->hasCacheResultFile()) { - throw new Exception('Cache result file is not configured'); - } - - return (string) $this->cacheResultFile; - } - - public function columns() - { - return $this->columns; - } - - public function colors(): string - { - return $this->colors; - } - - public function stderr(): bool - { - return $this->stderr; - } - - public function noInteraction(): bool - { - return $this->noInteraction; - } - - public function verbose(): bool - { - return $this->verbose; - } - - public function reverseDefectList(): bool - { - return $this->reverseDefectList; - } - - public function convertDeprecationsToExceptions(): bool - { - return $this->convertDeprecationsToExceptions; - } - - public function convertErrorsToExceptions(): bool - { - return $this->convertErrorsToExceptions; - } - - public function convertNoticesToExceptions(): bool - { - return $this->convertNoticesToExceptions; - } - - public function convertWarningsToExceptions(): bool - { - return $this->convertWarningsToExceptions; - } - - public function forceCoversAnnotation(): bool - { - return $this->forceCoversAnnotation; - } - - public function hasBootstrap(): bool - { - return $this->bootstrap !== null; - } - - /** - * @throws Exception - */ - public function bootstrap(): string - { - if (!$this->hasBootstrap()) { - throw new Exception('Bootstrap script is not configured'); - } - - return (string) $this->bootstrap; - } - - public function processIsolation(): bool - { - return $this->processIsolation; - } - - public function failOnEmptyTestSuite(): bool - { - return $this->failOnEmptyTestSuite; - } - - public function failOnIncomplete(): bool - { - return $this->failOnIncomplete; - } - - public function failOnRisky(): bool - { - return $this->failOnRisky; - } - - public function failOnSkipped(): bool - { - return $this->failOnSkipped; - } - - public function failOnWarning(): bool - { - return $this->failOnWarning; - } - - public function stopOnDefect(): bool - { - return $this->stopOnDefect; - } - - public function stopOnError(): bool - { - return $this->stopOnError; - } - - public function stopOnFailure(): bool - { - return $this->stopOnFailure; - } - - public function stopOnWarning(): bool - { - return $this->stopOnWarning; - } - - public function stopOnIncomplete(): bool - { - return $this->stopOnIncomplete; - } - - public function stopOnRisky(): bool - { - return $this->stopOnRisky; - } - - public function stopOnSkipped(): bool - { - return $this->stopOnSkipped; - } - - public function hasExtensionsDirectory(): bool - { - return $this->extensionsDirectory !== null; - } - - /** - * @throws Exception - */ - public function extensionsDirectory(): string - { - if (!$this->hasExtensionsDirectory()) { - throw new Exception('Extensions directory is not configured'); - } - - return (string) $this->extensionsDirectory; - } - - /** - * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 - */ - public function hasTestSuiteLoaderClass(): bool - { - return $this->testSuiteLoaderClass !== null; - } - - /** - * @throws Exception - * - * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 - */ - public function testSuiteLoaderClass(): string - { - if (!$this->hasTestSuiteLoaderClass()) { - throw new Exception('TestSuiteLoader class is not configured'); - } - - return (string) $this->testSuiteLoaderClass; - } - - /** - * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 - */ - public function hasTestSuiteLoaderFile(): bool - { - return $this->testSuiteLoaderFile !== null; - } - - /** - * @throws Exception - * - * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 - */ - public function testSuiteLoaderFile(): string - { - if (!$this->hasTestSuiteLoaderFile()) { - throw new Exception('TestSuiteLoader sourcecode file is not configured'); - } - - return (string) $this->testSuiteLoaderFile; - } - - public function hasPrinterClass(): bool - { - return $this->printerClass !== null; - } - - /** - * @throws Exception - */ - public function printerClass(): string - { - if (!$this->hasPrinterClass()) { - throw new Exception('ResultPrinter class is not configured'); - } - - return (string) $this->printerClass; - } - - public function hasPrinterFile(): bool - { - return $this->printerFile !== null; - } - - /** - * @throws Exception - */ - public function printerFile(): string - { - if (!$this->hasPrinterFile()) { - throw new Exception('ResultPrinter sourcecode file is not configured'); - } - - return (string) $this->printerFile; - } - - public function beStrictAboutChangesToGlobalState(): bool - { - return $this->beStrictAboutChangesToGlobalState; - } - - public function beStrictAboutOutputDuringTests(): bool - { - return $this->beStrictAboutOutputDuringTests; - } - - public function beStrictAboutResourceUsageDuringSmallTests(): bool - { - return $this->beStrictAboutResourceUsageDuringSmallTests; - } - - public function beStrictAboutTestsThatDoNotTestAnything(): bool - { - return $this->beStrictAboutTestsThatDoNotTestAnything; - } - - public function beStrictAboutTodoAnnotatedTests(): bool - { - return $this->beStrictAboutTodoAnnotatedTests; - } - - public function beStrictAboutCoversAnnotation(): bool - { - return $this->beStrictAboutCoversAnnotation; - } - - public function enforceTimeLimit(): bool - { - return $this->enforceTimeLimit; - } - - public function defaultTimeLimit(): int - { - return $this->defaultTimeLimit; - } - - public function timeoutForSmallTests(): int - { - return $this->timeoutForSmallTests; - } - - public function timeoutForMediumTests(): int - { - return $this->timeoutForMediumTests; - } - - public function timeoutForLargeTests(): int - { - return $this->timeoutForLargeTests; - } - - public function hasDefaultTestSuite(): bool - { - return $this->defaultTestSuite !== null; - } - - /** - * @throws Exception - */ - public function defaultTestSuite(): string - { - if (!$this->hasDefaultTestSuite()) { - throw new Exception('Default test suite is not configured'); - } - - return (string) $this->defaultTestSuite; - } - - public function executionOrder(): int - { - return $this->executionOrder; - } - - public function resolveDependencies(): bool - { - return $this->resolveDependencies; - } - - public function defectsFirst(): bool - { - return $this->defectsFirst; - } - - public function backupGlobals(): bool - { - return $this->backupGlobals; - } - - public function backupStaticAttributes(): bool - { - return $this->backupStaticAttributes; - } - - public function registerMockObjectsFromTestArgumentsRecursively(): bool - { - return $this->registerMockObjectsFromTestArgumentsRecursively; - } - - public function conflictBetweenPrinterClassAndTestdox(): bool - { - return $this->conflictBetweenPrinterClassAndTestdox; - } -} diff --git a/src/TextUI/XmlConfiguration/TestSuite/TestDirectory.php b/src/TextUI/XmlConfiguration/TestSuite/TestDirectory.php deleted file mode 100644 index 263d02ea946..00000000000 --- a/src/TextUI/XmlConfiguration/TestSuite/TestDirectory.php +++ /dev/null @@ -1,78 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use PHPUnit\Util\VersionComparisonOperator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class TestDirectory -{ - /** - * @var string - */ - private $path; - - /** - * @var string - */ - private $prefix; - - /** - * @var string - */ - private $suffix; - - /** - * @var string - */ - private $phpVersion; - - /** - * @var VersionComparisonOperator - */ - private $phpVersionOperator; - - public function __construct(string $path, string $prefix, string $suffix, string $phpVersion, VersionComparisonOperator $phpVersionOperator) - { - $this->path = $path; - $this->prefix = $prefix; - $this->suffix = $suffix; - $this->phpVersion = $phpVersion; - $this->phpVersionOperator = $phpVersionOperator; - } - - public function path(): string - { - return $this->path; - } - - public function prefix(): string - { - return $this->prefix; - } - - public function suffix(): string - { - return $this->suffix; - } - - public function phpVersion(): string - { - return $this->phpVersion; - } - - public function phpVersionOperator(): VersionComparisonOperator - { - return $this->phpVersionOperator; - } -} diff --git a/src/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollection.php b/src/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollection.php deleted file mode 100644 index 10111af0598..00000000000 --- a/src/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollection.php +++ /dev/null @@ -1,62 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use Countable; -use IteratorAggregate; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class TestDirectoryCollection implements Countable, IteratorAggregate -{ - /** - * @var TestDirectory[] - */ - private $directories; - - /** - * @param TestDirectory[] $directories - */ - public static function fromArray(array $directories): self - { - return new self(...$directories); - } - - private function __construct(TestDirectory ...$directories) - { - $this->directories = $directories; - } - - /** - * @return TestDirectory[] - */ - public function asArray(): array - { - return $this->directories; - } - - public function count(): int - { - return count($this->directories); - } - - public function getIterator(): TestDirectoryCollectionIterator - { - return new TestDirectoryCollectionIterator($this); - } - - public function isEmpty(): bool - { - return $this->count() === 0; - } -} diff --git a/src/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollectionIterator.php b/src/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollectionIterator.php deleted file mode 100644 index 11a48a2476d..00000000000 --- a/src/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollectionIterator.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use function iterator_count; -use Countable; -use Iterator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class TestDirectoryCollectionIterator implements Countable, Iterator -{ - /** - * @var TestDirectory[] - */ - private $directories; - - /** - * @var int - */ - private $position; - - public function __construct(TestDirectoryCollection $directories) - { - $this->directories = $directories->asArray(); - } - - public function count(): int - { - return iterator_count($this); - } - - public function rewind(): void - { - $this->position = 0; - } - - public function valid(): bool - { - return $this->position < count($this->directories); - } - - public function key(): int - { - return $this->position; - } - - public function current(): TestDirectory - { - return $this->directories[$this->position]; - } - - public function next(): void - { - $this->position++; - } -} diff --git a/src/TextUI/XmlConfiguration/TestSuite/TestFile.php b/src/TextUI/XmlConfiguration/TestSuite/TestFile.php deleted file mode 100644 index 2e69450c1ba..00000000000 --- a/src/TextUI/XmlConfiguration/TestSuite/TestFile.php +++ /dev/null @@ -1,56 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use PHPUnit\Util\VersionComparisonOperator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class TestFile -{ - /** - * @var string - */ - private $path; - - /** - * @var string - */ - private $phpVersion; - - /** - * @var VersionComparisonOperator - */ - private $phpVersionOperator; - - public function __construct(string $path, string $phpVersion, VersionComparisonOperator $phpVersionOperator) - { - $this->path = $path; - $this->phpVersion = $phpVersion; - $this->phpVersionOperator = $phpVersionOperator; - } - - public function path(): string - { - return $this->path; - } - - public function phpVersion(): string - { - return $this->phpVersion; - } - - public function phpVersionOperator(): VersionComparisonOperator - { - return $this->phpVersionOperator; - } -} diff --git a/src/TextUI/XmlConfiguration/TestSuite/TestFileCollection.php b/src/TextUI/XmlConfiguration/TestSuite/TestFileCollection.php deleted file mode 100644 index 791ddf7180a..00000000000 --- a/src/TextUI/XmlConfiguration/TestSuite/TestFileCollection.php +++ /dev/null @@ -1,62 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use Countable; -use IteratorAggregate; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class TestFileCollection implements Countable, IteratorAggregate -{ - /** - * @var TestFile[] - */ - private $files; - - /** - * @param TestFile[] $files - */ - public static function fromArray(array $files): self - { - return new self(...$files); - } - - private function __construct(TestFile ...$files) - { - $this->files = $files; - } - - /** - * @return TestFile[] - */ - public function asArray(): array - { - return $this->files; - } - - public function count(): int - { - return count($this->files); - } - - public function getIterator(): TestFileCollectionIterator - { - return new TestFileCollectionIterator($this); - } - - public function isEmpty(): bool - { - return $this->count() === 0; - } -} diff --git a/src/TextUI/XmlConfiguration/TestSuite/TestFileCollectionIterator.php b/src/TextUI/XmlConfiguration/TestSuite/TestFileCollectionIterator.php deleted file mode 100644 index b44c368948f..00000000000 --- a/src/TextUI/XmlConfiguration/TestSuite/TestFileCollectionIterator.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use function iterator_count; -use Countable; -use Iterator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class TestFileCollectionIterator implements Countable, Iterator -{ - /** - * @var TestFile[] - */ - private $files; - - /** - * @var int - */ - private $position; - - public function __construct(TestFileCollection $files) - { - $this->files = $files->asArray(); - } - - public function count(): int - { - return iterator_count($this); - } - - public function rewind(): void - { - $this->position = 0; - } - - public function valid(): bool - { - return $this->position < count($this->files); - } - - public function key(): int - { - return $this->position; - } - - public function current(): TestFile - { - return $this->files[$this->position]; - } - - public function next(): void - { - $this->position++; - } -} diff --git a/src/TextUI/XmlConfiguration/TestSuite/TestSuite.php b/src/TextUI/XmlConfiguration/TestSuite/TestSuite.php deleted file mode 100644 index 4a60982157a..00000000000 --- a/src/TextUI/XmlConfiguration/TestSuite/TestSuite.php +++ /dev/null @@ -1,65 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class TestSuite -{ - /** - * @var string - */ - private $name; - - /** - * @var TestDirectoryCollection - */ - private $directories; - - /** - * @var TestFileCollection - */ - private $files; - - /** - * @var FileCollection - */ - private $exclude; - - public function __construct(string $name, TestDirectoryCollection $directories, TestFileCollection $files, FileCollection $exclude) - { - $this->name = $name; - $this->directories = $directories; - $this->files = $files; - $this->exclude = $exclude; - } - - public function name(): string - { - return $this->name; - } - - public function directories(): TestDirectoryCollection - { - return $this->directories; - } - - public function files(): TestFileCollection - { - return $this->files; - } - - public function exclude(): FileCollection - { - return $this->exclude; - } -} diff --git a/src/TextUI/XmlConfiguration/TestSuite/TestSuiteCollection.php b/src/TextUI/XmlConfiguration/TestSuite/TestSuiteCollection.php deleted file mode 100644 index 124609d74e3..00000000000 --- a/src/TextUI/XmlConfiguration/TestSuite/TestSuiteCollection.php +++ /dev/null @@ -1,62 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use Countable; -use IteratorAggregate; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable - */ -final class TestSuiteCollection implements Countable, IteratorAggregate -{ - /** - * @var TestSuite[] - */ - private $testSuites; - - /** - * @param TestSuite[] $testSuites - */ - public static function fromArray(array $testSuites): self - { - return new self(...$testSuites); - } - - private function __construct(TestSuite ...$testSuites) - { - $this->testSuites = $testSuites; - } - - /** - * @return TestSuite[] - */ - public function asArray(): array - { - return $this->testSuites; - } - - public function count(): int - { - return count($this->testSuites); - } - - public function getIterator(): TestSuiteCollectionIterator - { - return new TestSuiteCollectionIterator($this); - } - - public function isEmpty(): bool - { - return $this->count() === 0; - } -} diff --git a/src/TextUI/XmlConfiguration/TestSuite/TestSuiteCollectionIterator.php b/src/TextUI/XmlConfiguration/TestSuite/TestSuiteCollectionIterator.php deleted file mode 100644 index 33b0f849573..00000000000 --- a/src/TextUI/XmlConfiguration/TestSuite/TestSuiteCollectionIterator.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use function iterator_count; -use Countable; -use Iterator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class TestSuiteCollectionIterator implements Countable, Iterator -{ - /** - * @var TestSuite[] - */ - private $testSuites; - - /** - * @var int - */ - private $position; - - public function __construct(TestSuiteCollection $testSuites) - { - $this->testSuites = $testSuites->asArray(); - } - - public function count(): int - { - return iterator_count($this); - } - - public function rewind(): void - { - $this->position = 0; - } - - public function valid(): bool - { - return $this->position < count($this->testSuites); - } - - public function key(): int - { - return $this->position; - } - - public function current(): TestSuite - { - return $this->testSuites[$this->position]; - } - - public function next(): void - { - $this->position++; - } -} diff --git a/src/TextUI/XmlConfiguration/TestSuite/TestSuiteMapper.php b/src/TextUI/XmlConfiguration/TestSuite/TestSuiteMapper.php deleted file mode 100644 index 2e7cb7608cb..00000000000 --- a/src/TextUI/XmlConfiguration/TestSuite/TestSuiteMapper.php +++ /dev/null @@ -1,77 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use const PHP_VERSION; -use function explode; -use function in_array; -use function version_compare; -use PHPUnit\Framework\TestSuite as TestSuiteObject; -use SebastianBergmann\FileIterator\Facade; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class TestSuiteMapper -{ - public function map(TestSuiteCollection $configuration, string $filter): TestSuiteObject - { - $filterAsArray = $filter ? explode(',', $filter) : []; - $result = new TestSuiteObject; - - foreach ($configuration as $testSuiteConfiguration) { - if (!empty($filterAsArray) && !in_array($testSuiteConfiguration->name(), $filterAsArray, true)) { - continue; - } - - $testSuite = new TestSuiteObject($testSuiteConfiguration->name()); - $testSuiteEmpty = true; - - foreach ($testSuiteConfiguration->directories() as $directory) { - if (!version_compare(PHP_VERSION, $directory->phpVersion(), $directory->phpVersionOperator()->asString())) { - continue; - } - - $exclude = []; - - foreach ($testSuiteConfiguration->exclude()->asArray() as $file) { - $exclude[] = $file->path(); - } - - $testSuite->addTestFiles( - (new Facade)->getFilesAsArray( - $directory->path(), - $directory->suffix(), - $directory->prefix(), - $exclude - ) - ); - - $testSuiteEmpty = false; - } - - foreach ($testSuiteConfiguration->files() as $file) { - if (!version_compare(PHP_VERSION, $file->phpVersion(), $file->phpVersionOperator()->asString())) { - continue; - } - - $testSuite->addTestFile($file->path()); - - $testSuiteEmpty = false; - } - - if (!$testSuiteEmpty) { - $result->addTest($testSuite); - } - } - - return $result; - } -} diff --git a/src/Util/Annotation/DocBlock.php b/src/Util/Annotation/DocBlock.php deleted file mode 100644 index 8c9c74c132a..00000000000 --- a/src/Util/Annotation/DocBlock.php +++ /dev/null @@ -1,552 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\Annotation; - -use const JSON_ERROR_NONE; -use const PREG_OFFSET_CAPTURE; -use function array_filter; -use function array_key_exists; -use function array_map; -use function array_merge; -use function array_pop; -use function array_slice; -use function array_values; -use function constant; -use function count; -use function explode; -use function file; -use function implode; -use function is_array; -use function is_int; -use function json_decode; -use function json_last_error; -use function json_last_error_msg; -use function preg_match; -use function preg_match_all; -use function preg_replace; -use function preg_split; -use function realpath; -use function rtrim; -use function sprintf; -use function str_replace; -use function strlen; -use function strpos; -use function strtolower; -use function substr; -use function trim; -use PharIo\Version\VersionConstraintParser; -use PHPUnit\Framework\InvalidDataProviderException; -use PHPUnit\Framework\SkippedTestError; -use PHPUnit\Framework\Warning; -use PHPUnit\Util\Exception; -use PHPUnit\Util\InvalidDataSetException; -use ReflectionClass; -use ReflectionException; -use ReflectionFunctionAbstract; -use ReflectionMethod; -use Reflector; -use Traversable; - -/** - * This is an abstraction around a PHPUnit-specific docBlock, - * allowing us to ask meaningful questions about a specific - * reflection symbol. - * - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class DocBlock -{ - /** - * @todo This constant should be private (it's public because of TestTest::testGetProvidedDataRegEx) - */ - public const REGEX_DATA_PROVIDER = '/@dataProvider\s+([a-zA-Z0-9._:-\\\\x7f-\xff]+)/'; - - private const REGEX_REQUIRES_VERSION = '/@requires\s+(?PPHP(?:Unit)?)\s+(?P[<>=!]{0,2})\s*(?P[\d\.-]+(dev|(RC|alpha|beta)[\d\.])?)[ \t]*\r?$/m'; - - private const REGEX_REQUIRES_VERSION_CONSTRAINT = '/@requires\s+(?PPHP(?:Unit)?)\s+(?P[\d\t \-.|~^]+)[ \t]*\r?$/m'; - - private const REGEX_REQUIRES_OS = '/@requires\s+(?POS(?:FAMILY)?)\s+(?P.+?)[ \t]*\r?$/m'; - - private const REGEX_REQUIRES_SETTING = '/@requires\s+(?Psetting)\s+(?P([^ ]+?))\s*(?P[\w\.-]+[\w\.]?)?[ \t]*\r?$/m'; - - private const REGEX_REQUIRES = '/@requires\s+(?Pfunction|extension)\s+(?P([^\s<>=!]+))\s*(?P[<>=!]{0,2})\s*(?P[\d\.-]+[\d\.]?)?[ \t]*\r?$/m'; - - private const REGEX_TEST_WITH = '/@testWith\s+/'; - - /** @var string */ - private $docComment; - - /** @var bool */ - private $isMethod; - - /** @var array> pre-parsed annotations indexed by name and occurrence index */ - private $symbolAnnotations; - - /** - * @var null|array - * - * @psalm-var null|(array{ - * __OFFSET: array&array{__FILE: string}, - * setting?: array, - * extension_versions?: array - * }&array< - * string, - * string|array{version: string, operator: string}|array{constraint: string}|array - * >) - */ - private $parsedRequirements; - - /** @var int */ - private $startLine; - - /** @var int */ - private $endLine; - - /** @var string */ - private $fileName; - - /** @var string */ - private $name; - - /** - * @var string - * - * @psalm-var class-string - */ - private $className; - - public static function ofClass(ReflectionClass $class): self - { - $className = $class->getName(); - - return new self( - (string) $class->getDocComment(), - false, - self::extractAnnotationsFromReflector($class), - $class->getStartLine(), - $class->getEndLine(), - $class->getFileName(), - $className, - $className - ); - } - - /** - * @psalm-param class-string $classNameInHierarchy - */ - public static function ofMethod(ReflectionMethod $method, string $classNameInHierarchy): self - { - return new self( - (string) $method->getDocComment(), - true, - self::extractAnnotationsFromReflector($method), - $method->getStartLine(), - $method->getEndLine(), - $method->getFileName(), - $method->getName(), - $classNameInHierarchy - ); - } - - /** - * Note: we do not preserve an instance of the reflection object, since it cannot be safely (de-)serialized. - * - * @param array> $symbolAnnotations - * - * @psalm-param class-string $className - */ - private function __construct(string $docComment, bool $isMethod, array $symbolAnnotations, int $startLine, int $endLine, string $fileName, string $name, string $className) - { - $this->docComment = $docComment; - $this->isMethod = $isMethod; - $this->symbolAnnotations = $symbolAnnotations; - $this->startLine = $startLine; - $this->endLine = $endLine; - $this->fileName = $fileName; - $this->name = $name; - $this->className = $className; - } - - /** - * @psalm-return array{ - * __OFFSET: array&array{__FILE: string}, - * setting?: array, - * extension_versions?: array - * }&array< - * string, - * string|array{version: string, operator: string}|array{constraint: string}|array - * > - * - * @throws Warning if the requirements version constraint is not well-formed - */ - public function requirements(): array - { - if ($this->parsedRequirements !== null) { - return $this->parsedRequirements; - } - - $offset = $this->startLine; - $requires = []; - $recordedSettings = []; - $extensionVersions = []; - $recordedOffsets = [ - '__FILE' => realpath($this->fileName), - ]; - - // Split docblock into lines and rewind offset to start of docblock - $lines = preg_split('/\r\n|\r|\n/', $this->docComment); - $offset -= count($lines); - - foreach ($lines as $line) { - if (preg_match(self::REGEX_REQUIRES_OS, $line, $matches)) { - $requires[$matches['name']] = $matches['value']; - $recordedOffsets[$matches['name']] = $offset; - } - - if (preg_match(self::REGEX_REQUIRES_VERSION, $line, $matches)) { - $requires[$matches['name']] = [ - 'version' => $matches['version'], - 'operator' => $matches['operator'], - ]; - $recordedOffsets[$matches['name']] = $offset; - } - - if (preg_match(self::REGEX_REQUIRES_VERSION_CONSTRAINT, $line, $matches)) { - if (!empty($requires[$matches['name']])) { - $offset++; - - continue; - } - - try { - $versionConstraintParser = new VersionConstraintParser; - - $requires[$matches['name'] . '_constraint'] = [ - 'constraint' => $versionConstraintParser->parse(trim($matches['constraint'])), - ]; - $recordedOffsets[$matches['name'] . '_constraint'] = $offset; - } catch (\PharIo\Version\Exception $e) { - /* @TODO this catch is currently not valid, see https://github.com/phar-io/version/issues/16 */ - throw new Warning($e->getMessage(), $e->getCode(), $e); - } - } - - if (preg_match(self::REGEX_REQUIRES_SETTING, $line, $matches)) { - $recordedSettings[$matches['setting']] = $matches['value']; - $recordedOffsets['__SETTING_' . $matches['setting']] = $offset; - } - - if (preg_match(self::REGEX_REQUIRES, $line, $matches)) { - $name = $matches['name'] . 's'; - - if (!isset($requires[$name])) { - $requires[$name] = []; - } - - $requires[$name][] = $matches['value']; - $recordedOffsets[$matches['name'] . '_' . $matches['value']] = $offset; - - if ($name === 'extensions' && !empty($matches['version'])) { - $extensionVersions[$matches['value']] = [ - 'version' => $matches['version'], - 'operator' => $matches['operator'], - ]; - } - } - - $offset++; - } - - return $this->parsedRequirements = array_merge( - $requires, - ['__OFFSET' => $recordedOffsets], - array_filter([ - 'setting' => $recordedSettings, - 'extension_versions' => $extensionVersions, - ]) - ); - } - - /** - * Returns the provided data for a method. - * - * @throws Exception - */ - public function getProvidedData(): ?array - { - /** @noinspection SuspiciousBinaryOperationInspection */ - $data = $this->getDataFromDataProviderAnnotation($this->docComment) ?? $this->getDataFromTestWithAnnotation($this->docComment); - - if ($data === null) { - return null; - } - - if ($data === []) { - throw new SkippedTestError; - } - - foreach ($data as $key => $value) { - if (!is_array($value)) { - throw new InvalidDataSetException( - sprintf( - 'Data set %s is invalid.', - is_int($key) ? '#' . $key : '"' . $key . '"' - ) - ); - } - } - - return $data; - } - - /** - * @psalm-return array - */ - public function getInlineAnnotations(): array - { - $code = file($this->fileName); - $lineNumber = $this->startLine; - $startLine = $this->startLine - 1; - $endLine = $this->endLine - 1; - $codeLines = array_slice($code, $startLine, $endLine - $startLine + 1); - $annotations = []; - - foreach ($codeLines as $line) { - if (preg_match('#/\*\*?\s*@(?P[A-Za-z_-]+)(?:[ \t]+(?P.*?))?[ \t]*\r?\*/$#m', $line, $matches)) { - $annotations[strtolower($matches['name'])] = [ - 'line' => $lineNumber, - 'value' => $matches['value'], - ]; - } - - $lineNumber++; - } - - return $annotations; - } - - public function symbolAnnotations(): array - { - return $this->symbolAnnotations; - } - - public function isHookToBeExecutedBeforeClass(): bool - { - return $this->isMethod - && false !== strpos($this->docComment, '@beforeClass'); - } - - public function isHookToBeExecutedAfterClass(): bool - { - return $this->isMethod - && false !== strpos($this->docComment, '@afterClass'); - } - - public function isToBeExecutedBeforeTest(): bool - { - return 1 === preg_match('/@before\b/', $this->docComment); - } - - public function isToBeExecutedAfterTest(): bool - { - return 1 === preg_match('/@after\b/', $this->docComment); - } - - public function isToBeExecutedAsPreCondition(): bool - { - return 1 === preg_match('/@preCondition\b/', $this->docComment); - } - - public function isToBeExecutedAsPostCondition(): bool - { - return 1 === preg_match('/@postCondition\b/', $this->docComment); - } - - private function getDataFromDataProviderAnnotation(string $docComment): ?array - { - $methodName = null; - $className = $this->className; - - if ($this->isMethod) { - $methodName = $this->name; - } - - if (!preg_match_all(self::REGEX_DATA_PROVIDER, $docComment, $matches)) { - return null; - } - - $result = []; - - foreach ($matches[1] as $match) { - $dataProviderMethodNameNamespace = explode('\\', $match); - $leaf = explode('::', array_pop($dataProviderMethodNameNamespace)); - $dataProviderMethodName = array_pop($leaf); - - if (empty($dataProviderMethodNameNamespace)) { - $dataProviderMethodNameNamespace = ''; - } else { - $dataProviderMethodNameNamespace = implode('\\', $dataProviderMethodNameNamespace) . '\\'; - } - - if (empty($leaf)) { - $dataProviderClassName = $className; - } else { - /** @psalm-var class-string $dataProviderClassName */ - $dataProviderClassName = $dataProviderMethodNameNamespace . array_pop($leaf); - } - - try { - $dataProviderClass = new ReflectionClass($dataProviderClassName); - - $dataProviderMethod = $dataProviderClass->getMethod( - $dataProviderMethodName - ); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - // @codeCoverageIgnoreEnd - } - - if ($dataProviderMethod->isStatic()) { - $object = null; - } else { - $object = $dataProviderClass->newInstance(); - } - - if ($dataProviderMethod->getNumberOfParameters() === 0) { - $data = $dataProviderMethod->invoke($object); - } else { - $data = $dataProviderMethod->invoke($object, $methodName); - } - - if ($data instanceof Traversable) { - $origData = $data; - $data = []; - - foreach ($origData as $key => $value) { - if (is_int($key)) { - $data[] = $value; - } elseif (array_key_exists($key, $data)) { - throw new InvalidDataProviderException( - sprintf( - 'The key "%s" has already been defined in the data provider "%s".', - $key, - $match - ) - ); - } else { - $data[$key] = $value; - } - } - } - - if (is_array($data)) { - $result = array_merge($result, $data); - } - } - - return $result; - } - - /** - * @throws Exception - */ - private function getDataFromTestWithAnnotation(string $docComment): ?array - { - $docComment = $this->cleanUpMultiLineAnnotation($docComment); - - if (!preg_match(self::REGEX_TEST_WITH, $docComment, $matches, PREG_OFFSET_CAPTURE)) { - return null; - } - - $offset = strlen($matches[0][0]) + $matches[0][1]; - $annotationContent = substr($docComment, $offset); - $data = []; - - foreach (explode("\n", $annotationContent) as $candidateRow) { - $candidateRow = trim($candidateRow); - - if ($candidateRow[0] !== '[') { - break; - } - - $dataSet = json_decode($candidateRow, true); - - if (json_last_error() !== JSON_ERROR_NONE) { - throw new Exception( - 'The data set for the @testWith annotation cannot be parsed: ' . json_last_error_msg() - ); - } - - $data[] = $dataSet; - } - - if (!$data) { - throw new Exception('The data set for the @testWith annotation cannot be parsed.'); - } - - return $data; - } - - private function cleanUpMultiLineAnnotation(string $docComment): string - { - //removing initial ' * ' for docComment - $docComment = str_replace("\r\n", "\n", $docComment); - $docComment = preg_replace('/' . '\n' . '\s*' . '\*' . '\s?' . '/', "\n", $docComment); - $docComment = (string) substr($docComment, 0, -1); - - return rtrim($docComment, "\n"); - } - - /** @return array> */ - private static function parseDocBlock(string $docBlock): array - { - // Strip away the docblock header and footer to ease parsing of one line annotations - $docBlock = (string) substr($docBlock, 3, -2); - $annotations = []; - - if (preg_match_all('/@(?P[A-Za-z_-]+)(?:[ \t]+(?P.*?))?[ \t]*\r?$/m', $docBlock, $matches)) { - $numMatches = count($matches[0]); - - for ($i = 0; $i < $numMatches; $i++) { - $annotations[$matches['name'][$i]][] = (string) $matches['value'][$i]; - } - } - - return $annotations; - } - - /** @param ReflectionClass|ReflectionFunctionAbstract $reflector */ - private static function extractAnnotationsFromReflector(Reflector $reflector): array - { - $annotations = []; - - if ($reflector instanceof ReflectionClass) { - $annotations = array_merge( - $annotations, - ...array_map( - function (ReflectionClass $trait): array { - return self::parseDocBlock((string) $trait->getDocComment()); - }, - array_values($reflector->getTraits()) - ) - ); - } - - return array_merge( - $annotations, - self::parseDocBlock((string) $reflector->getDocComment()) - ); - } -} diff --git a/src/Util/Annotation/Registry.php b/src/Util/Annotation/Registry.php deleted file mode 100644 index 8df14cfc0ce..00000000000 --- a/src/Util/Annotation/Registry.php +++ /dev/null @@ -1,93 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\Annotation; - -use function array_key_exists; -use PHPUnit\Util\Exception; -use ReflectionClass; -use ReflectionException; -use ReflectionMethod; - -/** - * Reflection information, and therefore DocBlock information, is static within - * a single PHP process. It is therefore okay to use a Singleton registry here. - * - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Registry -{ - /** @var null|self */ - private static $instance; - - /** @var array indexed by class name */ - private $classDocBlocks = []; - - /** @var array> indexed by class name and method name */ - private $methodDocBlocks = []; - - public static function getInstance(): self - { - return self::$instance ?? self::$instance = new self; - } - - private function __construct() - { - } - - /** - * @throws Exception - * @psalm-param class-string $class - */ - public function forClassName(string $class): DocBlock - { - if (array_key_exists($class, $this->classDocBlocks)) { - return $this->classDocBlocks[$class]; - } - - try { - $reflection = new ReflectionClass($class); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - return $this->classDocBlocks[$class] = DocBlock::ofClass($reflection); - } - - /** - * @throws Exception - * @psalm-param class-string $classInHierarchy - */ - public function forMethod(string $classInHierarchy, string $method): DocBlock - { - if (isset($this->methodDocBlocks[$classInHierarchy][$method])) { - return $this->methodDocBlocks[$classInHierarchy][$method]; - } - - try { - $reflection = new ReflectionMethod($classInHierarchy, $method); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - return $this->methodDocBlocks[$classInHierarchy][$method] = DocBlock::ofMethod($reflection, $classInHierarchy); - } -} diff --git a/src/Util/Blacklist.php b/src/Util/Blacklist.php deleted file mode 100644 index ad91561e24b..00000000000 --- a/src/Util/Blacklist.php +++ /dev/null @@ -1,39 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -/** - * @deprecated Use ExcludeList instead - */ -final class Blacklist -{ - public static function addDirectory(string $directory): void - { - ExcludeList::addDirectory($directory); - } - - /** - * @throws Exception - * - * @return string[] - */ - public function getBlacklistedDirectories(): array - { - return (new ExcludeList)->getExcludedDirectories(); - } - - /** - * @throws Exception - */ - public function isBlacklisted(string $file): bool - { - return (new ExcludeList)->isExcluded($file); - } -} diff --git a/src/Util/Color.php b/src/Util/Color.php index a756953b66e..c225e37b73e 100644 --- a/src/Util/Color.php +++ b/src/Util/Color.php @@ -10,36 +10,41 @@ namespace PHPUnit\Util; use const DIRECTORY_SEPARATOR; -use function array_keys; +use const PHP_EOL; use function array_map; -use function array_values; +use function array_walk; use function count; use function explode; use function implode; +use function max; use function min; use function preg_replace; use function preg_replace_callback; +use function preg_split; use function sprintf; +use function str_pad; use function strtr; use function trim; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class Color { /** - * @var array + * @var non-empty-array */ - private const WHITESPACE_MAP = [ + private const array WHITESPACE_MAP = [ ' ' => '·', "\t" => '⇥', ]; /** - * @var array + * @var non-empty-array */ - private const WHITESPACE_EOL_MAP = [ + private const array WHITESPACE_EOL_MAP = [ ' ' => '·', "\t" => '⇥', "\n" => '↵', @@ -47,9 +52,9 @@ final class Color ]; /** - * @var array + * @var non-empty-array */ - private static $ansiCodes = [ + private const array ANSI_CODES = [ 'reset' => '0', 'bold' => '1', 'dim' => '2', @@ -85,29 +90,46 @@ public static function colorize(string $color, string $buffer): string $styles = []; foreach ($codes as $code) { - if (isset(self::$ansiCodes[$code])) { - $styles[] = self::$ansiCodes[$code] ?? ''; + if (isset(self::ANSI_CODES[$code])) { + $styles[] = self::ANSI_CODES[$code]; } } - if (empty($styles)) { + if ($styles === []) { return $buffer; } return self::optimizeColor(sprintf("\x1b[%sm", implode(';', $styles)) . $buffer . "\x1b[0m"); } - public static function colorizePath(string $path, ?string $prevPath = null, bool $colorizeFilename = false): string + public static function colorizeTextBox(string $color, string $buffer, ?int $columns = null): string { - if ($prevPath === null) { - $prevPath = ''; + $lines = preg_split('/\r\n|\r|\n/', $buffer); + $maxBoxWidth = max(array_map('\strlen', $lines)); + + if ($columns !== null) { + $maxBoxWidth = min($maxBoxWidth, $columns); } - $path = explode(DIRECTORY_SEPARATOR, $path); - $prevPath = explode(DIRECTORY_SEPARATOR, $prevPath); + array_walk($lines, static function (string &$line) use ($color, $maxBoxWidth): void + { + $line = self::colorize($color, str_pad($line, $maxBoxWidth)); + }); - for ($i = 0; $i < min(count($path), count($prevPath)); $i++) { - if ($path[$i] == $prevPath[$i]) { + return implode(PHP_EOL, $lines); + } + + public static function colorizePath(string $path, ?string $previousPath = null, bool $colorizeFilename = false): string + { + if ($previousPath === null) { + $previousPath = ''; + } + + $path = explode(DIRECTORY_SEPARATOR, $path); + $previousPath = explode(DIRECTORY_SEPARATOR, $previousPath); + + for ($i = 0; $i < min(count($path), count($previousPath)); $i++) { + if ($path[$i] === $previousPath[$i]) { $path[$i] = self::dim($path[$i]); } } @@ -115,11 +137,9 @@ public static function colorizePath(string $path, ?string $prevPath = null, bool if ($colorizeFilename) { $last = count($path) - 1; $path[$last] = preg_replace_callback( - '/([\-_\.]+|phpt$)/', - static function ($matches) { - return self::dim($matches[0]); - }, - $path[$last] + '/([\-_.]+|phpt$)/', + static fn (array $matches) => self::dim($matches[0]), + $path[$last], ); } @@ -139,19 +159,27 @@ public static function visualizeWhitespace(string $buffer, bool $visualizeEOL = { $replaceMap = $visualizeEOL ? self::WHITESPACE_EOL_MAP : self::WHITESPACE_MAP; - return preg_replace_callback('/\s+/', static function ($matches) use ($replaceMap) { - return self::dim(strtr($matches[0], $replaceMap)); - }, $buffer); + return preg_replace_callback( + '/\s+/', + static fn (array $matches) => self::dim(strtr($matches[0], $replaceMap)), + $buffer, + ); } private static function optimizeColor(string $buffer): string { - $patterns = [ - "/\e\\[22m\e\\[2m/" => '', - "/\e\\[([^m]*)m\e\\[([1-9][0-9;]*)m/" => "\e[$1;$2m", - "/(\e\\[[^m]*m)+(\e\\[0m)/" => '$2', - ]; - - return preg_replace(array_keys($patterns), array_values($patterns), $buffer); + return preg_replace( + [ + "/\e\\[22m\e\\[2m/", + "/\e\\[([^m]*)m\e\\[([1-9][0-9;]*)m/", + "/(\e\\[[^m]*m)+(\e\\[0m)/", + ], + [ + '', + "\e[$1;$2m", + '$2', + ], + $buffer, + ); } } diff --git a/src/Util/ErrorHandler.php b/src/Util/ErrorHandler.php deleted file mode 100644 index 61dbbbc1377..00000000000 --- a/src/Util/ErrorHandler.php +++ /dev/null @@ -1,155 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use const E_DEPRECATED; -use const E_NOTICE; -use const E_STRICT; -use const E_USER_DEPRECATED; -use const E_USER_NOTICE; -use const E_USER_WARNING; -use const E_WARNING; -use function error_reporting; -use function restore_error_handler; -use function set_error_handler; -use PHPUnit\Framework\Error\Deprecated; -use PHPUnit\Framework\Error\Error; -use PHPUnit\Framework\Error\Notice; -use PHPUnit\Framework\Error\Warning; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ErrorHandler -{ - /** - * @var bool - */ - private $convertDeprecationsToExceptions; - - /** - * @var bool - */ - private $convertErrorsToExceptions; - - /** - * @var bool - */ - private $convertNoticesToExceptions; - - /** - * @var bool - */ - private $convertWarningsToExceptions; - - /** - * @var bool - */ - private $registered = false; - - public static function invokeIgnoringWarnings(callable $callable) - { - set_error_handler( - static function ($errorNumber, $errorString) { - if ($errorNumber === E_WARNING) { - return; - } - - return false; - } - ); - - $result = $callable(); - - restore_error_handler(); - - return $result; - } - - public function __construct(bool $convertDeprecationsToExceptions, bool $convertErrorsToExceptions, bool $convertNoticesToExceptions, bool $convertWarningsToExceptions) - { - $this->convertDeprecationsToExceptions = $convertDeprecationsToExceptions; - $this->convertErrorsToExceptions = $convertErrorsToExceptions; - $this->convertNoticesToExceptions = $convertNoticesToExceptions; - $this->convertWarningsToExceptions = $convertWarningsToExceptions; - } - - public function __invoke(int $errorNumber, string $errorString, string $errorFile, int $errorLine): bool - { - /* - * Do not raise an exception when the error suppression operator (@) was used. - * - * @see https://github.com/sebastianbergmann/phpunit/issues/3739 - */ - if (!($errorNumber & error_reporting())) { - return false; - } - - switch ($errorNumber) { - case E_NOTICE: - case E_USER_NOTICE: - case E_STRICT: - if (!$this->convertNoticesToExceptions) { - return false; - } - - throw new Notice($errorString, $errorNumber, $errorFile, $errorLine); - - case E_WARNING: - case E_USER_WARNING: - if (!$this->convertWarningsToExceptions) { - return false; - } - - throw new Warning($errorString, $errorNumber, $errorFile, $errorLine); - - case E_DEPRECATED: - case E_USER_DEPRECATED: - if (!$this->convertDeprecationsToExceptions) { - return false; - } - - throw new Deprecated($errorString, $errorNumber, $errorFile, $errorLine); - - default: - if (!$this->convertErrorsToExceptions) { - return false; - } - - throw new Error($errorString, $errorNumber, $errorFile, $errorLine); - } - } - - public function register(): void - { - if ($this->registered) { - return; - } - - $oldErrorHandler = set_error_handler($this); - - if ($oldErrorHandler !== null) { - restore_error_handler(); - - return; - } - - $this->registered = true; - } - - public function unregister(): void - { - if (!$this->registered) { - return; - } - - restore_error_handler(); - } -} diff --git a/src/Util/Exception.php b/src/Util/Exception.php deleted file mode 100644 index 6bcb3d140a8..00000000000 --- a/src/Util/Exception.php +++ /dev/null @@ -1,19 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use RuntimeException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Exception extends RuntimeException implements \PHPUnit\Exception -{ -} diff --git a/src/Util/Exception/Exception.php b/src/Util/Exception/Exception.php new file mode 100644 index 00000000000..58f42db77f3 --- /dev/null +++ b/src/Util/Exception/Exception.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface Exception extends Throwable +{ +} diff --git a/src/Util/Exception/InvalidDirectoryException.php b/src/Util/Exception/InvalidDirectoryException.php new file mode 100644 index 00000000000..623af2ded5f --- /dev/null +++ b/src/Util/Exception/InvalidDirectoryException.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidDirectoryException extends RuntimeException implements Exception +{ + public function __construct(string $directory) + { + parent::__construct( + sprintf( + '"%s" is not a directory', + $directory, + ), + ); + } +} diff --git a/src/Util/Exception/InvalidJsonException.php b/src/Util/Exception/InvalidJsonException.php new file mode 100644 index 00000000000..224f7115c59 --- /dev/null +++ b/src/Util/Exception/InvalidJsonException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidJsonException extends RuntimeException implements Exception +{ +} diff --git a/src/Util/Exception/InvalidVersionOperatorException.php b/src/Util/Exception/InvalidVersionOperatorException.php new file mode 100644 index 00000000000..bc2fe9a0ed5 --- /dev/null +++ b/src/Util/Exception/InvalidVersionOperatorException.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidVersionOperatorException extends RuntimeException implements Exception +{ + public function __construct(string $operator) + { + parent::__construct( + sprintf( + '"%s" is not a valid version_compare() operator', + $operator, + ), + ); + } +} diff --git a/src/Util/Exception/PhpProcessException.php b/src/Util/Exception/PhpProcessException.php new file mode 100644 index 00000000000..05069ef05ec --- /dev/null +++ b/src/Util/Exception/PhpProcessException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\PHP; + +use PHPUnit\Util\Exception; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class PhpProcessException extends RuntimeException implements Exception +{ +} diff --git a/src/Util/Exception/XmlException.php b/src/Util/Exception/XmlException.php new file mode 100644 index 00000000000..127e1ecaffe --- /dev/null +++ b/src/Util/Exception/XmlException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Xml; + +use PHPUnit\Util\Exception; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class XmlException extends RuntimeException implements Exception +{ +} diff --git a/src/Util/ExcludeList.php b/src/Util/ExcludeList.php index 18d6066a70f..1e3edd4e42d 100644 --- a/src/Util/ExcludeList.php +++ b/src/Util/ExcludeList.php @@ -9,81 +9,68 @@ */ namespace PHPUnit\Util; -use const DIRECTORY_SEPARATOR; +use const PHP_OS_FAMILY; +use function array_any; +use function assert; use function class_exists; use function defined; use function dirname; use function is_dir; use function realpath; -use function sprintf; -use function strpos; +use function str_starts_with; use function sys_get_temp_dir; use Composer\Autoload\ClassLoader; use DeepCopy\DeepCopy; -use Doctrine\Instantiator\Instantiator; use PharIo\Manifest\Manifest; use PharIo\Version\Version as PharIoVersion; -use PHP_Token; -use phpDocumentor\Reflection\DocBlock; -use phpDocumentor\Reflection\Project; -use phpDocumentor\Reflection\Type; +use PhpParser\Parser; use PHPUnit\Framework\TestCase; -use Prophecy\Prophet; use ReflectionClass; -use ReflectionException; +use SebastianBergmann\CliParser\Parser as CliParser; use SebastianBergmann\CodeCoverage\CodeCoverage; -use SebastianBergmann\CodeUnit\CodeUnit; -use SebastianBergmann\CodeUnitReverseLookup\Wizard; use SebastianBergmann\Comparator\Comparator; +use SebastianBergmann\Complexity\Calculator; use SebastianBergmann\Diff\Diff; use SebastianBergmann\Environment\Runtime; use SebastianBergmann\Exporter\Exporter; use SebastianBergmann\FileIterator\Facade as FileIteratorFacade; use SebastianBergmann\GlobalState\Snapshot; use SebastianBergmann\Invoker\Invoker; +use SebastianBergmann\LinesOfCode\Counter; use SebastianBergmann\ObjectEnumerator\Enumerator; +use SebastianBergmann\ObjectReflector\ObjectReflector; use SebastianBergmann\RecursionContext\Context; -use SebastianBergmann\ResourceOperations\ResourceOperations; use SebastianBergmann\Template\Template; use SebastianBergmann\Timer\Timer; use SebastianBergmann\Type\TypeName; use SebastianBergmann\Version; +use staabm\SideEffectsDetector\SideEffectsDetector; use TheSeer\Tokenizer\Tokenizer; -use Webmozart\Assert\Assert; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ final class ExcludeList { /** - * @var array + * @var non-empty-array */ - private const EXCLUDED_CLASS_NAMES = [ + private const array EXCLUDED_CLASS_NAMES = [ // composer ClassLoader::class => 1, - // doctrine/instantiator - Instantiator::class => 1, - // myclabs/deepcopy DeepCopy::class => 1, + // nikic/php-parser + Parser::class => 1, + // phar-io/manifest Manifest::class => 1, // phar-io/version PharIoVersion::class => 1, - // phpdocumentor/reflection-common - Project::class => 1, - - // phpdocumentor/reflection-docblock - DocBlock::class => 1, - - // phpdocumentor/type-resolver - Type::class => 1, - - // phpspec/prophecy - Prophet::class => 1, - // phpunit/phpunit TestCase::class => 2, @@ -102,18 +89,15 @@ final class ExcludeList // phpunit/php-timer Timer::class => 1, - // phpunit/php-token-stream - PHP_Token::class => 1, - - // sebastian/code-unit - CodeUnit::class => 1, - - // sebastian/code-unit-reverse-lookup - Wizard::class => 1, + // sebastian/cli-parser + CliParser::class => 1, // sebastian/comparator Comparator::class => 1, + // sebastian/complexity + Calculator::class => 1, + // sebastian/diff Diff::class => 1, @@ -126,117 +110,121 @@ final class ExcludeList // sebastian/global-state Snapshot::class => 1, + // sebastian/lines-of-code + Counter::class => 1, + // sebastian/object-enumerator Enumerator::class => 1, + // sebastian/object-reflector + ObjectReflector::class => 1, + // sebastian/recursion-context Context::class => 1, - // sebastian/resource-operations - ResourceOperations::class => 1, - // sebastian/type TypeName::class => 1, // sebastian/version Version::class => 1, + // staabm/side-effects-detector + SideEffectsDetector::class => 1, + // theseer/tokenizer Tokenizer::class => 1, - - // webmozart/assert - Assert::class => 1, ]; /** - * @var string[] + * @var list */ - private static $directories; + private static array $directories = []; + private static bool $initialized = false; + private readonly bool $enabled; + /** + * @param non-empty-string $directory + * + * @throws InvalidDirectoryException + */ public static function addDirectory(string $directory): void { if (!is_dir($directory)) { - throw new Exception( - sprintf( - '"%s" is not a directory', - $directory - ) - ); + throw new InvalidDirectoryException($directory); + } + + $directory = realpath($directory); + + assert($directory !== false); + + self::$directories[] = $directory; + } + + public function __construct(?bool $enabled = null) + { + if ($enabled === null) { + $enabled = !defined('PHPUNIT_TESTSUITE'); } - self::$directories[] = realpath($directory); + $this->enabled = $enabled; } /** - * @throws Exception - * - * @return string[] + * @return list */ public function getExcludedDirectories(): array { - $this->initialize(); + self::initialize(); return self::$directories; } - /** - * @throws Exception - */ public function isExcluded(string $file): bool { - if (defined('PHPUNIT_TESTSUITE')) { + if (!$this->enabled) { return false; } - $this->initialize(); - - foreach (self::$directories as $directory) { - if (strpos($file, $directory) === 0) { - return true; - } - } + self::initialize(); - return false; + return array_any( + self::$directories, + static fn (string $directory) => str_starts_with($file, $directory), + ); } - /** - * @throws Exception - */ - private function initialize(): void + private static function initialize(): void { - if (self::$directories === null) { - self::$directories = []; - - foreach (self::EXCLUDED_CLASS_NAMES as $className => $parent) { - if (!class_exists($className)) { - continue; - } - - try { - $directory = (new ReflectionClass($className))->getFileName(); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - for ($i = 0; $i < $parent; $i++) { - $directory = dirname($directory); - } - - self::$directories[] = $directory; + if (self::$initialized) { + return; + } + + foreach (self::EXCLUDED_CLASS_NAMES as $className => $parent) { + if (!class_exists($className)) { + continue; } - // Hide process isolation workaround on Windows. - if (DIRECTORY_SEPARATOR === '\\') { - // tempnam() prefix is limited to first 3 chars. - // @see https://php.net/manual/en/function.tempnam.php - self::$directories[] = sys_get_temp_dir() . '\\PHP'; + $directory = new ReflectionClass($className)->getFileName(); + + for ($i = 0; $i < $parent; $i++) { + $directory = dirname($directory); } + + self::$directories[] = $directory; } + + /** + * Hide process isolation workaround on Windows: + * tempnam() prefix is limited to first 3 characters. + * + * @see https://php.net/manual/en/function.tempnam.php + */ + if (PHP_OS_FAMILY === 'Windows') { + // @codeCoverageIgnoreStart + self::$directories[] = sys_get_temp_dir() . '\\PHP'; + // @codeCoverageIgnoreEnd + } + + self::$initialized = true; } } diff --git a/src/Util/Exporter.php b/src/Util/Exporter.php new file mode 100644 index 00000000000..182499e9da0 --- /dev/null +++ b/src/Util/Exporter.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use PHPUnit\TextUI\Configuration\Registry as ConfigurationRegistry; +use SebastianBergmann\Exporter\Exporter as OriginalExporter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class Exporter +{ + private static ?OriginalExporter $exporter = null; + + public static function export(mixed $value): string + { + return self::exporter()->export($value); + } + + /** + * @param array $data + */ + public static function shortenedRecursiveExport(array $data): string + { + return self::exporter()->shortenedRecursiveExport($data); + } + + public static function shortenedExport(mixed $value): string + { + return self::exporter()->shortenedExport($value); + } + + private static function exporter(): OriginalExporter + { + if (self::$exporter !== null) { + return self::$exporter; + } + + self::$exporter = new OriginalExporter( + ConfigurationRegistry::get()->shortenArraysForExportThreshold(), + ); + + return self::$exporter; + } +} diff --git a/src/Util/FileLoader.php b/src/Util/FileLoader.php deleted file mode 100644 index ba02454576d..00000000000 --- a/src/Util/FileLoader.php +++ /dev/null @@ -1,81 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use const DIRECTORY_SEPARATOR; -use function array_diff; -use function array_keys; -use function fopen; -use function get_defined_vars; -use function sprintf; -use function stream_resolve_include_path; -use PHPUnit\Framework\Exception; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class FileLoader -{ - /** - * Checks if a PHP sourcecode file is readable. The sourcecode file is loaded through the load() method. - * - * As a fallback, PHP looks in the directory of the file executing the stream_resolve_include_path function. - * We do not want to load the Test.php file here, so skip it if it found that. - * PHP prioritizes the include_path setting, so if the current directory is in there, it will first look in the - * current working directory. - * - * @throws Exception - */ - public static function checkAndLoad(string $filename): string - { - $includePathFilename = stream_resolve_include_path($filename); - - $localFile = __DIR__ . DIRECTORY_SEPARATOR . $filename; - - if (!$includePathFilename || - $includePathFilename === $localFile || - !self::isReadable($includePathFilename) - ) { - throw new Exception( - sprintf('Cannot open file "%s".' . "\n", $filename) - ); - } - - self::load($includePathFilename); - - return $includePathFilename; - } - - /** - * Loads a PHP sourcefile. - */ - public static function load(string $filename): void - { - $oldVariableNames = array_keys(get_defined_vars()); - - include_once $filename; - - $newVariables = get_defined_vars(); - - foreach (array_diff(array_keys($newVariables), $oldVariableNames) as $variableName) { - if ($variableName !== 'oldVariableNames') { - $GLOBALS[$variableName] = $newVariables[$variableName]; - } - } - } - - /** - * @see https://github.com/sebastianbergmann/phpunit/pull/2751 - */ - private static function isReadable(string $filename): bool - { - return @fopen($filename, 'r') !== false; - } -} diff --git a/src/Util/Filesystem.php b/src/Util/Filesystem.php index 35b2690b12e..3da54042379 100644 --- a/src/Util/Filesystem.php +++ b/src/Util/Filesystem.php @@ -10,32 +10,42 @@ namespace PHPUnit\Util; use const DIRECTORY_SEPARATOR; +use function basename; +use function dirname; use function is_dir; use function mkdir; -use function str_replace; +use function realpath; +use function str_starts_with; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class Filesystem +final readonly class Filesystem { + public static function createDirectory(string $directory): bool + { + return !(!is_dir($directory) && !@mkdir($directory, 0o777, true) && !is_dir($directory)); + } + /** - * Maps class names to source file names. + * @param non-empty-string $path * - * - PEAR CS: Foo_Bar_Baz -> Foo/Bar/Baz.php - * - Namespace: Foo\Bar\Baz -> Foo/Bar/Baz.php + * @return false|non-empty-string */ - public static function classNameToFilename(string $className): string + public static function resolveStreamOrFile(string $path): false|string { - return str_replace( - ['_', '\\'], - DIRECTORY_SEPARATOR, - $className - ) . '.php'; - } + if (str_starts_with($path, 'php://') || str_starts_with($path, 'socket://')) { + return $path; + } - public static function createDirectory(string $directory): bool - { - return !(!is_dir($directory) && !@mkdir($directory, 0777, true) && !is_dir($directory)); + $directory = dirname($path); + + if (is_dir($directory)) { + return realpath($directory) . DIRECTORY_SEPARATOR . basename($path); + } + + return false; } } diff --git a/src/Util/Filter.php b/src/Util/Filter.php index 9212a3ddc16..1abb0063651 100644 --- a/src/Util/Filter.php +++ b/src/Util/Filter.php @@ -9,110 +9,125 @@ */ namespace PHPUnit\Util; +use function array_any; use function array_unshift; use function defined; use function in_array; +use function is_array; use function is_file; use function realpath; use function sprintf; -use function strpos; +use function str_starts_with; use PHPUnit\Framework\Exception; -use PHPUnit\Framework\SyntheticError; +use PHPUnit\Framework\PhptAssertionFailedError; use Throwable; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class Filter +final readonly class Filter { /** * @throws Exception */ - public static function getFilteredStacktrace(Throwable $t): string + public static function stackTraceFromThrowableAsString(Throwable $t, bool $unwrap = true): string { - $filteredStacktrace = ''; - - if ($t instanceof SyntheticError) { - $eTrace = $t->getSyntheticTrace(); - $eFile = $t->getSyntheticFile(); - $eLine = $t->getSyntheticLine(); + if ($t instanceof PhptAssertionFailedError) { + $stackTrace = $t->syntheticTrace(); + $file = $t->syntheticFile(); + $line = $t->syntheticLine(); } elseif ($t instanceof Exception) { - $eTrace = $t->getSerializableTrace(); - $eFile = $t->getFile(); - $eLine = $t->getLine(); + $stackTrace = $t->getSerializableTrace(); + $file = $t->getFile(); + $line = $t->getLine(); } else { - if ($t->getPrevious()) { + if ($unwrap && $t->getPrevious() !== null) { $t = $t->getPrevious(); } - $eTrace = $t->getTrace(); - $eFile = $t->getFile(); - $eLine = $t->getLine(); + $stackTrace = $t->getTrace(); + $file = $t->getFile(); + $line = $t->getLine(); } - if (!self::frameExists($eTrace, $eFile, $eLine)) { + if (!self::frameExists($stackTrace, $file, $line)) { array_unshift( - $eTrace, - ['file' => $eFile, 'line' => $eLine] + $stackTrace, + ['file' => $file, 'line' => $line], ); } + return self::stackTraceAsString($stackTrace); + } + + /** + * @param list $frames + */ + private static function stackTraceAsString(array $frames): string + { + $buffer = ''; $prefix = defined('__PHPUNIT_PHAR_ROOT__') ? __PHPUNIT_PHAR_ROOT__ : false; $excludeList = new ExcludeList; - foreach ($eTrace as $frame) { + foreach ($frames as $frame) { if (self::shouldPrintFrame($frame, $prefix, $excludeList)) { - $filteredStacktrace .= sprintf( + $buffer .= sprintf( "%s:%s\n", $frame['file'], - $frame['line'] ?? '?' + $frame['line'] ?? '?', ); } } - return $filteredStacktrace; + return $buffer; } /** - * @param false|string $prefix + * @param array{file?: non-empty-string} $frame */ - private static function shouldPrintFrame(array $frame, $prefix, ExcludeList $excludeList): bool + private static function shouldPrintFrame(array $frame, false|string $prefix, ExcludeList $excludeList): bool { if (!isset($frame['file'])) { return false; } $file = $frame['file']; - $fileIsNotPrefixed = $prefix === false || strpos($file, $prefix) !== 0; + $fileIsNotPrefixed = $prefix === false || !str_starts_with($file, $prefix); // @see https://github.com/sebastianbergmann/phpunit/issues/4033 if (isset($GLOBALS['_SERVER']['SCRIPT_NAME'])) { $script = realpath($GLOBALS['_SERVER']['SCRIPT_NAME']); } else { + // @codeCoverageIgnoreStart $script = ''; + // @codeCoverageIgnoreEnd } - return is_file($file) && + return $fileIsNotPrefixed && + $file !== $script && self::fileIsExcluded($file, $excludeList) && - $fileIsNotPrefixed && - $file !== $script; + is_file($file); } private static function fileIsExcluded(string $file, ExcludeList $excludeList): bool { - return (empty($GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST']) || + return (!isset($GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST']) || + !is_array($GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST']) || + $GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST'] === [] || !in_array($file, $GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST'], true)) && !$excludeList->isExcluded($file); } + /** + * @param list $trace + */ private static function frameExists(array $trace, string $file, int $line): bool { - foreach ($trace as $frame) { - if (isset($frame['file'], $frame['line']) && $frame['file'] === $file && $frame['line'] === $line) { - return true; - } - } - - return false; + return array_any( + $trace, + static fn (array $frame) => isset($frame['file'], $frame['line']) && $frame['file'] === $file && $frame['line'] === $line, + ); } } diff --git a/src/Util/Getopt.php b/src/Util/Getopt.php deleted file mode 100644 index 3c9a23eaadd..00000000000 --- a/src/Util/Getopt.php +++ /dev/null @@ -1,196 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use function array_map; -use function array_merge; -use function array_shift; -use function array_slice; -use function count; -use function current; -use function explode; -use function key; -use function next; -use function preg_replace; -use function reset; -use function sort; -use function strlen; -use function strpos; -use function strstr; -use function substr; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Getopt -{ - /** - * @throws Exception - */ - public static function parse(array $args, string $short_options, array $long_options = null): array - { - if (empty($args)) { - return [[], []]; - } - - $opts = []; - $non_opts = []; - - if ($long_options) { - sort($long_options); - } - - if (isset($args[0][0]) && $args[0][0] !== '-') { - array_shift($args); - } - - reset($args); - - $args = array_map('trim', $args); - - /* @noinspection ComparisonOperandsOrderInspection */ - while (false !== $arg = current($args)) { - $i = key($args); - next($args); - - if ($arg === '') { - continue; - } - - if ($arg === '--') { - $non_opts = array_merge($non_opts, array_slice($args, $i + 1)); - - break; - } - - if ($arg[0] !== '-' || (strlen($arg) > 1 && $arg[1] === '-' && !$long_options)) { - $non_opts[] = $args[$i]; - - continue; - } - - if (strlen($arg) > 1 && $arg[1] === '-') { - self::parseLongOption( - substr($arg, 2), - $long_options, - $opts, - $args - ); - } else { - self::parseShortOption( - substr($arg, 1), - $short_options, - $opts, - $args - ); - } - } - - return [$opts, $non_opts]; - } - - /** - * @throws Exception - */ - private static function parseShortOption(string $arg, string $short_options, array &$opts, array &$args): void - { - $argLen = strlen($arg); - - for ($i = 0; $i < $argLen; $i++) { - $opt = $arg[$i]; - $opt_arg = null; - - if ($arg[$i] === ':' || ($spec = strstr($short_options, $opt)) === false) { - throw new Exception( - "unrecognized option -- {$opt}" - ); - } - - if (strlen($spec) > 1 && $spec[1] === ':') { - if ($i + 1 < $argLen) { - $opts[] = [$opt, substr($arg, $i + 1)]; - - break; - } - - if (!(strlen($spec) > 2 && $spec[2] === ':')) { - /* @noinspection ComparisonOperandsOrderInspection */ - if (false === $opt_arg = current($args)) { - throw new Exception( - "option requires an argument -- {$opt}" - ); - } - - next($args); - } - } - - $opts[] = [$opt, $opt_arg]; - } - } - - /** - * @throws Exception - */ - private static function parseLongOption(string $arg, array $long_options, array &$opts, array &$args): void - { - $count = count($long_options); - $list = explode('=', $arg); - $opt = $list[0]; - $opt_arg = null; - - if (count($list) > 1) { - $opt_arg = $list[1]; - } - - $opt_len = strlen($opt); - - foreach ($long_options as $i => $long_opt) { - $opt_start = substr($long_opt, 0, $opt_len); - - if ($opt_start !== $opt) { - continue; - } - - $opt_rest = substr($long_opt, $opt_len); - - if ($opt_rest !== '' && $i + 1 < $count && $opt[0] !== '=' && strpos($long_options[$i + 1], $opt) === 0) { - throw new Exception( - "option --{$opt} is ambiguous" - ); - } - - if (substr($long_opt, -1) === '=') { - /* @noinspection StrlenInEmptyStringCheckContextInspection */ - if (substr($long_opt, -2) !== '==' && !strlen((string) $opt_arg)) { - /* @noinspection ComparisonOperandsOrderInspection */ - if (false === $opt_arg = current($args)) { - throw new Exception( - "option --{$opt} requires an argument" - ); - } - - next($args); - } - } elseif ($opt_arg) { - throw new Exception( - "option --{$opt} doesn't allow an argument" - ); - } - - $full_option = '--' . preg_replace('/={1,2}$/', '', $long_opt); - $opts[] = [$full_option, $opt_arg]; - - return; - } - - throw new Exception("unrecognized option --{$opt}"); - } -} diff --git a/src/Util/GlobalState.php b/src/Util/GlobalState.php index 625d2d843c6..9f05089b8cc 100644 --- a/src/Util/GlobalState.php +++ b/src/Util/GlobalState.php @@ -9,8 +9,11 @@ */ namespace PHPUnit\Util; -use function array_keys; -use function count; +use const PHP_MAJOR_VERSION; +use const PHP_MINOR_VERSION; +use function array_reverse; +use function array_shift; +use function assert; use function defined; use function get_defined_constants; use function get_included_files; @@ -22,19 +25,23 @@ use function preg_match; use function serialize; use function sprintf; -use function strpos; +use function str_ends_with; +use function str_starts_with; +use function strtr; use function var_export; use Closure; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class GlobalState +final readonly class GlobalState { /** - * @var string[] + * @var non-empty-list */ - private const SUPER_GLOBAL_ARRAYS = [ + private const array SUPER_GLOBAL_ARRAYS = [ '_ENV', '_POST', '_GET', @@ -44,6 +51,103 @@ final class GlobalState '_REQUEST', ]; + /** + * @var non-empty-array> + */ + private const array DEPRECATED_INI_SETTINGS = [ + '7.3' => [ + 'iconv.input_encoding' => true, + 'iconv.output_encoding' => true, + 'iconv.internal_encoding' => true, + 'mbstring.func_overload' => true, + 'mbstring.http_input' => true, + 'mbstring.http_output' => true, + 'mbstring.internal_encoding' => true, + 'string.strip_tags' => true, + ], + + '7.4' => [ + 'iconv.input_encoding' => true, + 'iconv.output_encoding' => true, + 'iconv.internal_encoding' => true, + 'mbstring.func_overload' => true, + 'mbstring.http_input' => true, + 'mbstring.http_output' => true, + 'mbstring.internal_encoding' => true, + 'pdo_odbc.db2_instance_name' => true, + 'string.strip_tags' => true, + ], + + '8.0' => [ + 'iconv.input_encoding' => true, + 'iconv.output_encoding' => true, + 'iconv.internal_encoding' => true, + 'mbstring.http_input' => true, + 'mbstring.http_output' => true, + 'mbstring.internal_encoding' => true, + ], + + '8.1' => [ + 'auto_detect_line_endings' => true, + 'filter.default' => true, + 'iconv.input_encoding' => true, + 'iconv.output_encoding' => true, + 'iconv.internal_encoding' => true, + 'mbstring.http_input' => true, + 'mbstring.http_output' => true, + 'mbstring.internal_encoding' => true, + 'oci8.old_oci_close_semantics' => true, + ], + + '8.2' => [ + 'auto_detect_line_endings' => true, + 'filter.default' => true, + 'iconv.input_encoding' => true, + 'iconv.output_encoding' => true, + 'iconv.internal_encoding' => true, + 'mbstring.http_input' => true, + 'mbstring.http_output' => true, + 'mbstring.internal_encoding' => true, + 'oci8.old_oci_close_semantics' => true, + ], + + '8.3' => [ + 'auto_detect_line_endings' => true, + 'filter.default' => true, + 'iconv.input_encoding' => true, + 'iconv.output_encoding' => true, + 'iconv.internal_encoding' => true, + 'mbstring.http_input' => true, + 'mbstring.http_output' => true, + 'mbstring.internal_encoding' => true, + 'oci8.old_oci_close_semantics' => true, + ], + + '8.4' => [ + 'auto_detect_line_endings' => true, + 'filter.default' => true, + 'iconv.input_encoding' => true, + 'iconv.output_encoding' => true, + 'iconv.internal_encoding' => true, + 'mbstring.http_input' => true, + 'mbstring.http_output' => true, + 'mbstring.internal_encoding' => true, + 'oci8.old_oci_close_semantics' => true, + ], + + '8.5' => [ + 'auto_detect_line_endings' => true, + 'filter.default' => true, + 'iconv.input_encoding' => true, + 'iconv.output_encoding' => true, + 'iconv.internal_encoding' => true, + 'mbstring.http_input' => true, + 'mbstring.http_output' => true, + 'mbstring.internal_encoding' => true, + 'oci8.old_oci_close_semantics' => true, + ], + ]; + /** * @throws Exception */ @@ -53,7 +157,7 @@ public static function getIncludedFilesAsString(): string } /** - * @param string[] $files + * @param list $files * * @throws Exception */ @@ -64,23 +168,35 @@ public static function processIncludedFilesAsString(array $files): string $result = ''; if (defined('__PHPUNIT_PHAR__')) { + // @codeCoverageIgnoreStart $prefix = 'phar://' . __PHPUNIT_PHAR__ . '/'; + // @codeCoverageIgnoreEnd } - for ($i = count($files) - 1; $i > 0; $i--) { - $file = $files[$i]; + // Do not process bootstrap script + array_shift($files); - if (!empty($GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST']) && + // If bootstrap script was a Composer bin proxy, skip the second entry as well + if (str_ends_with(strtr($files[0], '\\', '/'), '/phpunit/phpunit/phpunit')) { + // @codeCoverageIgnoreStart + array_shift($files); + // @codeCoverageIgnoreEnd + } + + foreach (array_reverse($files) as $file) { + if (isset($GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST']) && + is_array($GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST']) && + $GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST'] !== [] && in_array($file, $GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST'], true)) { continue; } - if ($prefix !== false && strpos($file, $prefix) === 0) { + if ($prefix !== false && str_starts_with($file, $prefix)) { continue; } // Skip virtual file system protocols - if (preg_match('/^(vfs|phpvfs[a-z0-9]+):/', $file)) { + if (preg_match('/^(vfs|phpvfs[a-z0-9]+):/', $file) > 0) { continue; } @@ -96,11 +212,19 @@ public static function getIniSettingsAsString(): string { $result = ''; - foreach (ini_get_all(null, false) as $key => $value) { + $iniSettings = ini_get_all(null, false); + + assert($iniSettings !== false); + + foreach ($iniSettings as $key => $value) { + if (self::isIniSettingDeprecated($key)) { + continue; + } + $result .= sprintf( '@ini_set(%s, %s);' . "\n", self::exportVariable($key), - self::exportVariable((string) $value) + self::exportVariable((string) $value), ); } @@ -118,7 +242,7 @@ public static function getConstantsAsString(): string 'if (!defined(\'%s\')) define(\'%s\', %s);' . "\n", $name, $name, - self::exportVariable($value) + self::exportVariable($value), ); } } @@ -132,8 +256,8 @@ public static function getGlobalsAsString(): string foreach (self::SUPER_GLOBAL_ARRAYS as $superGlobalArray) { if (isset($GLOBALS[$superGlobalArray]) && is_array($GLOBALS[$superGlobalArray])) { - foreach (array_keys($GLOBALS[$superGlobalArray]) as $key) { - if ($GLOBALS[$superGlobalArray][$key] instanceof Closure) { + foreach ($GLOBALS[$superGlobalArray] as $key => $value) { + if ($value instanceof Closure) { continue; } @@ -141,7 +265,7 @@ public static function getGlobalsAsString(): string '$GLOBALS[\'%s\'][\'%s\'] = %s;' . "\n", $superGlobalArray, $key, - self::exportVariable($GLOBALS[$superGlobalArray][$key]) + self::exportVariable($GLOBALS[$superGlobalArray][$key]), ); } } @@ -150,12 +274,12 @@ public static function getGlobalsAsString(): string $excludeList = self::SUPER_GLOBAL_ARRAYS; $excludeList[] = 'GLOBALS'; - foreach (array_keys($GLOBALS) as $key) { - if (!$GLOBALS[$key] instanceof Closure && !in_array($key, $excludeList, true)) { + foreach ($GLOBALS as $key => $value) { + if (!$value instanceof Closure && !in_array($key, $excludeList, true)) { $result .= sprintf( '$GLOBALS[\'%s\'] = %s;' . "\n", $key, - self::exportVariable($GLOBALS[$key]) + self::exportVariable($value), ); } } @@ -163,7 +287,7 @@ public static function getGlobalsAsString(): string return $result; } - private static function exportVariable($variable): string + private static function exportVariable(mixed $variable): string { if (is_scalar($variable) || $variable === null || (is_array($variable) && self::arrayOnlyContainsScalars($variable))) { @@ -173,6 +297,9 @@ private static function exportVariable($variable): string return 'unserialize(' . var_export(serialize($variable), true) . ')'; } + /** + * @param array $array + */ private static function arrayOnlyContainsScalars(array $array): bool { $result = true; @@ -191,4 +318,9 @@ private static function arrayOnlyContainsScalars(array $array): bool return $result; } + + private static function isIniSettingDeprecated(string $iniSetting): bool + { + return isset(self::DEPRECATED_INI_SETTINGS[PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION][$iniSetting]); + } } diff --git a/src/Util/Http/Downloader.php b/src/Util/Http/Downloader.php new file mode 100644 index 00000000000..a9af2e38fbb --- /dev/null +++ b/src/Util/Http/Downloader.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Http; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Downloader +{ + /** + * @param non-empty-string $url + */ + public function download(string $url): false|string; +} diff --git a/src/Util/Http/PhpDownloader.php b/src/Util/Http/PhpDownloader.php new file mode 100644 index 00000000000..5969c042609 --- /dev/null +++ b/src/Util/Http/PhpDownloader.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Http; + +use function file_get_contents; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @codeCoverageIgnore + */ +final class PhpDownloader implements Downloader +{ + /** + * @param non-empty-string $url + */ + public function download(string $url): false|string + { + return file_get_contents($url); + } +} diff --git a/src/Util/InvalidDataSetException.php b/src/Util/InvalidDataSetException.php deleted file mode 100644 index 3493d113aae..00000000000 --- a/src/Util/InvalidDataSetException.php +++ /dev/null @@ -1,19 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use RuntimeException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class InvalidDataSetException extends RuntimeException implements \PHPUnit\Exception -{ -} diff --git a/src/Util/Json.php b/src/Util/Json.php index 752c1fd600b..cbe959accd1 100644 --- a/src/Util/Json.php +++ b/src/Util/Json.php @@ -9,54 +9,56 @@ */ namespace PHPUnit\Util; +use const JSON_ERROR_NONE; use const JSON_PRETTY_PRINT; use const JSON_UNESCAPED_SLASHES; use const JSON_UNESCAPED_UNICODE; -use function count; -use function is_array; +use const SORT_STRING; +use function assert; use function is_object; +use function is_scalar; use function json_decode; use function json_encode; use function json_last_error; use function ksort; -use PHPUnit\Framework\Exception; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class Json +final readonly class Json { /** - * Prettify json string. - * - * @throws \PHPUnit\Framework\Exception + * @throws InvalidJsonException */ public static function prettify(string $json): string { $decodedJson = json_decode($json, false); - if (json_last_error()) { - throw new Exception( - 'Cannot prettify invalid json' - ); + if (json_last_error() !== JSON_ERROR_NONE) { + throw new InvalidJsonException; } - return json_encode($decodedJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + $result = json_encode($decodedJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + + assert($result !== false); + + return $result; } /** - * To allow comparison of JSON strings, first process them into a consistent - * format so that they can be compared as strings. + * Element 0 is true and element 1 is null when JSON decoding did not work. + * * Element 0 is false and element 1 has the decoded value when JSON decoding did work. + * * This is used to avoid ambiguity with JSON strings consisting entirely of 'null' or 'false'. * - * @return array ($error, $canonicalized_json) The $error parameter is used - * to indicate an error decoding the json. This is used to avoid ambiguity - * with JSON strings consisting entirely of 'null' or 'false'. + * @return array{0: false, 1: mixed}|array{0: true, 1: null} */ public static function canonicalize(string $json): array { $decodedJson = json_decode($json); - if (json_last_error()) { + if (json_last_error() !== JSON_ERROR_NONE) { return [true, null]; } @@ -73,26 +75,32 @@ public static function canonicalize(string $json): array * Sort all array keys to ensure both the expected and actual values have * their keys in the same order. */ - private static function recursiveSort(&$json): void + private static function recursiveSort(mixed &$json): void { - if (!is_array($json)) { - // If the object is not empty, change it to an associative array - // so we can sort the keys (and we will still re-encode it - // correctly, since PHP encodes associative arrays as JSON objects.) - // But EMPTY objects MUST remain empty objects. (Otherwise we will - // re-encode it as a JSON array rather than a JSON object.) - // See #2919. - if (is_object($json) && count((array) $json) > 0) { - $json = (array) $json; - } else { - return; - } + if ($json === null || $json === [] || is_scalar($json)) { + return; } - ksort($json); + $isObject = is_object($json); - foreach ($json as $key => &$value) { + if ($isObject) { + // Objects need to be sorted during canonicalization to ensure + // correct comparsion since JSON objects are unordered. It must be + // kept as an object so that the value correctly stays as a JSON + // object instead of potentially being converted to an array. This + // approach ensures that numeric string JSON keys are preserved and + // don't risk being flattened due to PHP's array semantics. + // See #2919, #4584, #4674 + $json = (array) $json; + ksort($json, SORT_STRING); + } + + foreach ($json as &$value) { self::recursiveSort($value); } + + if ($isObject) { + $json = (object) $json; + } } } diff --git a/src/Util/Log/JUnit.php b/src/Util/Log/JUnit.php deleted file mode 100644 index edc39a95dbe..00000000000 --- a/src/Util/Log/JUnit.php +++ /dev/null @@ -1,432 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\Log; - -use function class_exists; -use function get_class; -use function method_exists; -use function sprintf; -use function str_replace; -use DOMDocument; -use DOMElement; -use PHPUnit\Framework\AssertionFailedError; -use PHPUnit\Framework\ExceptionWrapper; -use PHPUnit\Framework\SelfDescribing; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestFailure; -use PHPUnit\Framework\TestListener; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Framework\Warning; -use PHPUnit\Util\Exception; -use PHPUnit\Util\Filter; -use PHPUnit\Util\Printer; -use PHPUnit\Util\Xml; -use ReflectionClass; -use ReflectionException; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class JUnit extends Printer implements TestListener -{ - /** - * @var DOMDocument - */ - private $document; - - /** - * @var DOMElement - */ - private $root; - - /** - * @var bool - */ - private $reportRiskyTests = false; - - /** - * @var DOMElement[] - */ - private $testSuites = []; - - /** - * @var int[] - */ - private $testSuiteTests = [0]; - - /** - * @var int[] - */ - private $testSuiteAssertions = [0]; - - /** - * @var int[] - */ - private $testSuiteErrors = [0]; - - /** - * @var int[] - */ - private $testSuiteWarnings = [0]; - - /** - * @var int[] - */ - private $testSuiteFailures = [0]; - - /** - * @var int[] - */ - private $testSuiteSkipped = [0]; - - /** - * @var int[] - */ - private $testSuiteTimes = [0]; - - /** - * @var int - */ - private $testSuiteLevel = 0; - - /** - * @var DOMElement - */ - private $currentTestCase; - - /** - * @param null|mixed $out - */ - public function __construct($out = null, bool $reportRiskyTests = false) - { - $this->document = new DOMDocument('1.0', 'UTF-8'); - $this->document->formatOutput = true; - - $this->root = $this->document->createElement('testsuites'); - $this->document->appendChild($this->root); - - parent::__construct($out); - - $this->reportRiskyTests = $reportRiskyTests; - } - - /** - * Flush buffer and close output. - */ - public function flush(): void - { - $this->write($this->getXML()); - - parent::flush(); - } - - /** - * An error occurred. - */ - public function addError(Test $test, Throwable $t, float $time): void - { - $this->doAddFault($test, $t, 'error'); - $this->testSuiteErrors[$this->testSuiteLevel]++; - } - - /** - * A warning occurred. - */ - public function addWarning(Test $test, Warning $e, float $time): void - { - $this->doAddFault($test, $e, 'warning'); - $this->testSuiteWarnings[$this->testSuiteLevel]++; - } - - /** - * A failure occurred. - */ - public function addFailure(Test $test, AssertionFailedError $e, float $time): void - { - $this->doAddFault($test, $e, 'failure'); - $this->testSuiteFailures[$this->testSuiteLevel]++; - } - - /** - * Incomplete test. - */ - public function addIncompleteTest(Test $test, Throwable $t, float $time): void - { - $this->doAddSkipped(); - } - - /** - * Risky test. - */ - public function addRiskyTest(Test $test, Throwable $t, float $time): void - { - if (!$this->reportRiskyTests || $this->currentTestCase === null) { - return; - } - - $error = $this->document->createElement( - 'error', - Xml::prepareString( - "Risky Test\n" . - Filter::getFilteredStacktrace($t) - ) - ); - - $error->setAttribute('type', get_class($t)); - - $this->currentTestCase->appendChild($error); - - $this->testSuiteErrors[$this->testSuiteLevel]++; - } - - /** - * Skipped test. - */ - public function addSkippedTest(Test $test, Throwable $t, float $time): void - { - $this->doAddSkipped(); - } - - /** - * A testsuite started. - */ - public function startTestSuite(TestSuite $suite): void - { - $testSuite = $this->document->createElement('testsuite'); - $testSuite->setAttribute('name', $suite->getName()); - - if (class_exists($suite->getName(), false)) { - try { - $class = new ReflectionClass($suite->getName()); - - $testSuite->setAttribute('file', $class->getFileName()); - } catch (ReflectionException $e) { - } - } - - if ($this->testSuiteLevel > 0) { - $this->testSuites[$this->testSuiteLevel]->appendChild($testSuite); - } else { - $this->root->appendChild($testSuite); - } - - $this->testSuiteLevel++; - $this->testSuites[$this->testSuiteLevel] = $testSuite; - $this->testSuiteTests[$this->testSuiteLevel] = 0; - $this->testSuiteAssertions[$this->testSuiteLevel] = 0; - $this->testSuiteErrors[$this->testSuiteLevel] = 0; - $this->testSuiteWarnings[$this->testSuiteLevel] = 0; - $this->testSuiteFailures[$this->testSuiteLevel] = 0; - $this->testSuiteSkipped[$this->testSuiteLevel] = 0; - $this->testSuiteTimes[$this->testSuiteLevel] = 0; - } - - /** - * A testsuite ended. - */ - public function endTestSuite(TestSuite $suite): void - { - $this->testSuites[$this->testSuiteLevel]->setAttribute( - 'tests', - (string) $this->testSuiteTests[$this->testSuiteLevel] - ); - - $this->testSuites[$this->testSuiteLevel]->setAttribute( - 'assertions', - (string) $this->testSuiteAssertions[$this->testSuiteLevel] - ); - - $this->testSuites[$this->testSuiteLevel]->setAttribute( - 'errors', - (string) $this->testSuiteErrors[$this->testSuiteLevel] - ); - - $this->testSuites[$this->testSuiteLevel]->setAttribute( - 'warnings', - (string) $this->testSuiteWarnings[$this->testSuiteLevel] - ); - - $this->testSuites[$this->testSuiteLevel]->setAttribute( - 'failures', - (string) $this->testSuiteFailures[$this->testSuiteLevel] - ); - - $this->testSuites[$this->testSuiteLevel]->setAttribute( - 'skipped', - (string) $this->testSuiteSkipped[$this->testSuiteLevel] - ); - - $this->testSuites[$this->testSuiteLevel]->setAttribute( - 'time', - sprintf('%F', $this->testSuiteTimes[$this->testSuiteLevel]) - ); - - if ($this->testSuiteLevel > 1) { - $this->testSuiteTests[$this->testSuiteLevel - 1] += $this->testSuiteTests[$this->testSuiteLevel]; - $this->testSuiteAssertions[$this->testSuiteLevel - 1] += $this->testSuiteAssertions[$this->testSuiteLevel]; - $this->testSuiteErrors[$this->testSuiteLevel - 1] += $this->testSuiteErrors[$this->testSuiteLevel]; - $this->testSuiteWarnings[$this->testSuiteLevel - 1] += $this->testSuiteWarnings[$this->testSuiteLevel]; - $this->testSuiteFailures[$this->testSuiteLevel - 1] += $this->testSuiteFailures[$this->testSuiteLevel]; - $this->testSuiteSkipped[$this->testSuiteLevel - 1] += $this->testSuiteSkipped[$this->testSuiteLevel]; - $this->testSuiteTimes[$this->testSuiteLevel - 1] += $this->testSuiteTimes[$this->testSuiteLevel]; - } - - $this->testSuiteLevel--; - } - - /** - * A test started. - */ - public function startTest(Test $test): void - { - $usesDataprovider = false; - - if (method_exists($test, 'usesDataProvider')) { - $usesDataprovider = $test->usesDataProvider(); - } - - $testCase = $this->document->createElement('testcase'); - $testCase->setAttribute('name', $test->getName()); - - try { - $class = new ReflectionClass($test); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - $methodName = $test->getName(!$usesDataprovider); - - if ($class->hasMethod($methodName)) { - try { - $method = $class->getMethod($methodName); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - $testCase->setAttribute('class', $class->getName()); - $testCase->setAttribute('classname', str_replace('\\', '.', $class->getName())); - $testCase->setAttribute('file', $class->getFileName()); - $testCase->setAttribute('line', (string) $method->getStartLine()); - } - - $this->currentTestCase = $testCase; - } - - /** - * A test ended. - */ - public function endTest(Test $test, float $time): void - { - $numAssertions = 0; - - if (method_exists($test, 'getNumAssertions')) { - $numAssertions = $test->getNumAssertions(); - } - - $this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions; - - $this->currentTestCase->setAttribute( - 'assertions', - (string) $numAssertions - ); - - $this->currentTestCase->setAttribute( - 'time', - sprintf('%F', $time) - ); - - $this->testSuites[$this->testSuiteLevel]->appendChild( - $this->currentTestCase - ); - - $this->testSuiteTests[$this->testSuiteLevel]++; - $this->testSuiteTimes[$this->testSuiteLevel] += $time; - - $testOutput = ''; - - if (method_exists($test, 'hasOutput') && method_exists($test, 'getActualOutput')) { - $testOutput = $test->hasOutput() ? $test->getActualOutput() : ''; - } - - if (!empty($testOutput)) { - $systemOut = $this->document->createElement( - 'system-out', - Xml::prepareString($testOutput) - ); - - $this->currentTestCase->appendChild($systemOut); - } - - $this->currentTestCase = null; - } - - /** - * Returns the XML as a string. - */ - public function getXML(): string - { - return $this->document->saveXML(); - } - - private function doAddFault(Test $test, Throwable $t, string $type): void - { - if ($this->currentTestCase === null) { - return; - } - - if ($test instanceof SelfDescribing) { - $buffer = $test->toString() . "\n"; - } else { - $buffer = ''; - } - - $buffer .= TestFailure::exceptionToString($t) . "\n" . - Filter::getFilteredStacktrace($t); - - $fault = $this->document->createElement( - $type, - Xml::prepareString($buffer) - ); - - if ($t instanceof ExceptionWrapper) { - $fault->setAttribute('type', $t->getClassName()); - } else { - $fault->setAttribute('type', get_class($t)); - } - - $this->currentTestCase->appendChild($fault); - } - - private function doAddSkipped(): void - { - if ($this->currentTestCase === null) { - return; - } - - $skipped = $this->document->createElement('skipped'); - - $this->currentTestCase->appendChild($skipped); - - $this->testSuiteSkipped[$this->testSuiteLevel]++; - } -} diff --git a/src/Util/Log/TeamCity.php b/src/Util/Log/TeamCity.php deleted file mode 100644 index 5d4e73f6e04..00000000000 --- a/src/Util/Log/TeamCity.php +++ /dev/null @@ -1,391 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\Log; - -use function class_exists; -use function count; -use function explode; -use function get_class; -use function getmypid; -use function ini_get; -use function is_bool; -use function is_scalar; -use function method_exists; -use function print_r; -use function round; -use function str_replace; -use function stripos; -use PHPUnit\Framework\AssertionFailedError; -use PHPUnit\Framework\ExceptionWrapper; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestFailure; -use PHPUnit\Framework\TestResult; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Framework\Warning; -use PHPUnit\TextUI\DefaultResultPrinter; -use PHPUnit\Util\Exception; -use PHPUnit\Util\Filter; -use ReflectionClass; -use ReflectionException; -use SebastianBergmann\Comparator\ComparisonFailure; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class TeamCity extends DefaultResultPrinter -{ - /** - * @var bool - */ - private $isSummaryTestCountPrinted = false; - - /** - * @var string - */ - private $startedTestName; - - /** - * @var false|int - */ - private $flowId; - - public function printResult(TestResult $result): void - { - $this->printHeader($result); - $this->printFooter($result); - } - - /** - * An error occurred. - */ - public function addError(Test $test, Throwable $t, float $time): void - { - $this->printEvent( - 'testFailed', - [ - 'name' => $test->getName(), - 'message' => self::getMessage($t), - 'details' => self::getDetails($t), - 'duration' => self::toMilliseconds($time), - ] - ); - } - - /** - * A warning occurred. - */ - public function addWarning(Test $test, Warning $e, float $time): void - { - $this->printEvent( - 'testFailed', - [ - 'name' => $test->getName(), - 'message' => self::getMessage($e), - 'details' => self::getDetails($e), - 'duration' => self::toMilliseconds($time), - ] - ); - } - - /** - * A failure occurred. - */ - public function addFailure(Test $test, AssertionFailedError $e, float $time): void - { - $parameters = [ - 'name' => $test->getName(), - 'message' => self::getMessage($e), - 'details' => self::getDetails($e), - 'duration' => self::toMilliseconds($time), - ]; - - if ($e instanceof ExpectationFailedException) { - $comparisonFailure = $e->getComparisonFailure(); - - if ($comparisonFailure instanceof ComparisonFailure) { - $expectedString = $comparisonFailure->getExpectedAsString(); - - if ($expectedString === null || empty($expectedString)) { - $expectedString = self::getPrimitiveValueAsString($comparisonFailure->getExpected()); - } - - $actualString = $comparisonFailure->getActualAsString(); - - if ($actualString === null || empty($actualString)) { - $actualString = self::getPrimitiveValueAsString($comparisonFailure->getActual()); - } - - if ($actualString !== null && $expectedString !== null) { - $parameters['type'] = 'comparisonFailure'; - $parameters['actual'] = $actualString; - $parameters['expected'] = $expectedString; - } - } - } - - $this->printEvent('testFailed', $parameters); - } - - /** - * Incomplete test. - */ - public function addIncompleteTest(Test $test, Throwable $t, float $time): void - { - $this->printIgnoredTest($test->getName(), $t, $time); - } - - /** - * Risky test. - */ - public function addRiskyTest(Test $test, Throwable $t, float $time): void - { - $this->addError($test, $t, $time); - } - - /** - * Skipped test. - */ - public function addSkippedTest(Test $test, Throwable $t, float $time): void - { - $testName = $test->getName(); - - if ($this->startedTestName !== $testName) { - $this->startTest($test); - $this->printIgnoredTest($testName, $t, $time); - $this->endTest($test, $time); - } else { - $this->printIgnoredTest($testName, $t, $time); - } - } - - public function printIgnoredTest(string $testName, Throwable $t, float $time): void - { - $this->printEvent( - 'testIgnored', - [ - 'name' => $testName, - 'message' => self::getMessage($t), - 'details' => self::getDetails($t), - 'duration' => self::toMilliseconds($time), - ] - ); - } - - /** - * A testsuite started. - */ - public function startTestSuite(TestSuite $suite): void - { - if (stripos(ini_get('disable_functions'), 'getmypid') === false) { - $this->flowId = getmypid(); - } else { - $this->flowId = false; - } - - if (!$this->isSummaryTestCountPrinted) { - $this->isSummaryTestCountPrinted = true; - - $this->printEvent( - 'testCount', - ['count' => count($suite)] - ); - } - - $suiteName = $suite->getName(); - - if (empty($suiteName)) { - return; - } - - $parameters = ['name' => $suiteName]; - - if (class_exists($suiteName, false)) { - $fileName = self::getFileName($suiteName); - $parameters['locationHint'] = "php_qn://{$fileName}::\\{$suiteName}"; - } else { - $split = explode('::', $suiteName); - - if (count($split) === 2 && class_exists($split[0]) && method_exists($split[0], $split[1])) { - $fileName = self::getFileName($split[0]); - $parameters['locationHint'] = "php_qn://{$fileName}::\\{$suiteName}"; - $parameters['name'] = $split[1]; - } - } - - $this->printEvent('testSuiteStarted', $parameters); - } - - /** - * A testsuite ended. - */ - public function endTestSuite(TestSuite $suite): void - { - $suiteName = $suite->getName(); - - if (empty($suiteName)) { - return; - } - - $parameters = ['name' => $suiteName]; - - if (!class_exists($suiteName, false)) { - $split = explode('::', $suiteName); - - if (count($split) === 2 && class_exists($split[0]) && method_exists($split[0], $split[1])) { - $parameters['name'] = $split[1]; - } - } - - $this->printEvent('testSuiteFinished', $parameters); - } - - /** - * A test started. - */ - public function startTest(Test $test): void - { - $testName = $test->getName(); - $this->startedTestName = $testName; - $params = ['name' => $testName]; - - if ($test instanceof TestCase) { - $className = get_class($test); - $fileName = self::getFileName($className); - $params['locationHint'] = "php_qn://{$fileName}::\\{$className}::{$testName}"; - } - - $this->printEvent('testStarted', $params); - } - - /** - * A test ended. - */ - public function endTest(Test $test, float $time): void - { - parent::endTest($test, $time); - - $this->printEvent( - 'testFinished', - [ - 'name' => $test->getName(), - 'duration' => self::toMilliseconds($time), - ] - ); - } - - protected function writeProgress(string $progress): void - { - } - - private function printEvent(string $eventName, array $params = []): void - { - $this->write("\n##teamcity[{$eventName}"); - - if ($this->flowId) { - $params['flowId'] = $this->flowId; - } - - foreach ($params as $key => $value) { - $escapedValue = self::escapeValue((string) $value); - $this->write(" {$key}='{$escapedValue}'"); - } - - $this->write("]\n"); - } - - private static function getMessage(Throwable $t): string - { - $message = ''; - - if ($t instanceof ExceptionWrapper) { - if ($t->getClassName() !== '') { - $message .= $t->getClassName(); - } - - if ($message !== '' && $t->getMessage() !== '') { - $message .= ' : '; - } - } - - return $message . $t->getMessage(); - } - - private static function getDetails(Throwable $t): string - { - $stackTrace = Filter::getFilteredStacktrace($t); - $previous = $t instanceof ExceptionWrapper ? $t->getPreviousWrapped() : $t->getPrevious(); - - while ($previous) { - $stackTrace .= "\nCaused by\n" . - TestFailure::exceptionToString($previous) . "\n" . - Filter::getFilteredStacktrace($previous); - - $previous = $previous instanceof ExceptionWrapper ? - $previous->getPreviousWrapped() : $previous->getPrevious(); - } - - return ' ' . str_replace("\n", "\n ", $stackTrace); - } - - private static function getPrimitiveValueAsString($value): ?string - { - if ($value === null) { - return 'null'; - } - - if (is_bool($value)) { - return $value ? 'true' : 'false'; - } - - if (is_scalar($value)) { - return print_r($value, true); - } - - return null; - } - - private static function escapeValue(string $text): string - { - return str_replace( - ['|', "'", "\n", "\r", ']', '['], - ['||', "|'", '|n', '|r', '|]', '|['], - $text - ); - } - - /** - * @param string $className - */ - private static function getFileName($className): string - { - try { - return (new ReflectionClass($className))->getFileName(); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - } - - /** - * @param float $time microseconds - */ - private static function toMilliseconds(float $time): int - { - return (int) round($time * 1000); - } -} diff --git a/src/Util/PHP/AbstractPhpProcess.php b/src/Util/PHP/AbstractPhpProcess.php deleted file mode 100644 index f2c158980f1..00000000000 --- a/src/Util/PHP/AbstractPhpProcess.php +++ /dev/null @@ -1,415 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\PHP; - -use const DIRECTORY_SEPARATOR; -use const PHP_SAPI; -use function array_keys; -use function array_merge; -use function assert; -use function escapeshellarg; -use function ini_get_all; -use function restore_error_handler; -use function set_error_handler; -use function sprintf; -use function str_replace; -use function strpos; -use function strrpos; -use function substr; -use function trim; -use function unserialize; -use __PHP_Incomplete_Class; -use ErrorException; -use PHPUnit\Framework\AssertionFailedError; -use PHPUnit\Framework\Exception; -use PHPUnit\Framework\SyntheticError; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestFailure; -use PHPUnit\Framework\TestResult; -use SebastianBergmann\Environment\Runtime; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -abstract class AbstractPhpProcess -{ - /** - * @var Runtime - */ - protected $runtime; - - /** - * @var bool - */ - protected $stderrRedirection = false; - - /** - * @var string - */ - protected $stdin = ''; - - /** - * @var string - */ - protected $args = ''; - - /** - * @var array - */ - protected $env = []; - - /** - * @var int - */ - protected $timeout = 0; - - public static function factory(): self - { - if (DIRECTORY_SEPARATOR === '\\') { - return new WindowsPhpProcess; - } - - return new DefaultPhpProcess; - } - - public function __construct() - { - $this->runtime = new Runtime; - } - - /** - * Defines if should use STDERR redirection or not. - * - * Then $stderrRedirection is TRUE, STDERR is redirected to STDOUT. - */ - public function setUseStderrRedirection(bool $stderrRedirection): void - { - $this->stderrRedirection = $stderrRedirection; - } - - /** - * Returns TRUE if uses STDERR redirection or FALSE if not. - */ - public function useStderrRedirection(): bool - { - return $this->stderrRedirection; - } - - /** - * Sets the input string to be sent via STDIN. - */ - public function setStdin(string $stdin): void - { - $this->stdin = $stdin; - } - - /** - * Returns the input string to be sent via STDIN. - */ - public function getStdin(): string - { - return $this->stdin; - } - - /** - * Sets the string of arguments to pass to the php job. - */ - public function setArgs(string $args): void - { - $this->args = $args; - } - - /** - * Returns the string of arguments to pass to the php job. - */ - public function getArgs(): string - { - return $this->args; - } - - /** - * Sets the array of environment variables to start the child process with. - * - * @param array $env - */ - public function setEnv(array $env): void - { - $this->env = $env; - } - - /** - * Returns the array of environment variables to start the child process with. - */ - public function getEnv(): array - { - return $this->env; - } - - /** - * Sets the amount of seconds to wait before timing out. - */ - public function setTimeout(int $timeout): void - { - $this->timeout = $timeout; - } - - /** - * Returns the amount of seconds to wait before timing out. - */ - public function getTimeout(): int - { - return $this->timeout; - } - - /** - * Runs a single test in a separate PHP process. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function runTestJob(string $job, Test $test, TestResult $result): void - { - $result->startTest($test); - - $_result = $this->runJob($job); - - $this->processChildResult( - $test, - $result, - $_result['stdout'], - $_result['stderr'] - ); - } - - /** - * Returns the command based into the configurations. - */ - public function getCommand(array $settings, string $file = null): string - { - $command = $this->runtime->getBinary(); - - if ($this->runtime->hasPCOV()) { - $settings = array_merge( - $settings, - $this->runtime->getCurrentSettings( - array_keys(ini_get_all('pcov')) - ) - ); - } elseif ($this->runtime->hasXdebug()) { - $settings = array_merge( - $settings, - $this->runtime->getCurrentSettings( - array_keys(ini_get_all('xdebug')) - ) - ); - } - - $command .= $this->settingsToParameters($settings); - - if (PHP_SAPI === 'phpdbg') { - $command .= ' -qrr'; - - if (!$file) { - $command .= 's='; - } - } - - if ($file) { - $command .= ' ' . escapeshellarg($file); - } - - if ($this->args) { - if (!$file) { - $command .= ' --'; - } - $command .= ' ' . $this->args; - } - - if ($this->stderrRedirection) { - $command .= ' 2>&1'; - } - - return $command; - } - - /** - * Runs a single job (PHP code) using a separate PHP process. - */ - abstract public function runJob(string $job, array $settings = []): array; - - protected function settingsToParameters(array $settings): string - { - $buffer = ''; - - foreach ($settings as $setting) { - $buffer .= ' -d ' . escapeshellarg($setting); - } - - return $buffer; - } - - /** - * Processes the TestResult object from an isolated process. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - private function processChildResult(Test $test, TestResult $result, string $stdout, string $stderr): void - { - $time = 0; - - if (!empty($stderr)) { - $result->addError( - $test, - new Exception(trim($stderr)), - $time - ); - } else { - set_error_handler( - /** - * @throws ErrorException - */ - static function ($errno, $errstr, $errfile, $errline): void { - throw new ErrorException($errstr, $errno, $errno, $errfile, $errline); - } - ); - - try { - if (strpos($stdout, "#!/usr/bin/env php\n") === 0) { - $stdout = substr($stdout, 19); - } - - $childResult = unserialize(str_replace("#!/usr/bin/env php\n", '', $stdout)); - restore_error_handler(); - - if ($childResult === false) { - $result->addFailure( - $test, - new AssertionFailedError('Test was run in child process and ended unexpectedly'), - $time - ); - } - } catch (ErrorException $e) { - restore_error_handler(); - $childResult = false; - - $result->addError( - $test, - new Exception(trim($stdout), 0, $e), - $time - ); - } - - if ($childResult !== false) { - if (!empty($childResult['output'])) { - $output = $childResult['output']; - } - - /* @var TestCase $test */ - - $test->setResult($childResult['testResult']); - $test->addToAssertionCount($childResult['numAssertions']); - - $childResult = $childResult['result']; - assert($childResult instanceof TestResult); - - if ($result->getCollectCodeCoverageInformation()) { - $result->getCodeCoverage()->merge( - $childResult->getCodeCoverage() - ); - } - - $time = $childResult->time(); - $notImplemented = $childResult->notImplemented(); - $risky = $childResult->risky(); - $skipped = $childResult->skipped(); - $errors = $childResult->errors(); - $warnings = $childResult->warnings(); - $failures = $childResult->failures(); - - if (!empty($notImplemented)) { - $result->addError( - $test, - $this->getException($notImplemented[0]), - $time - ); - } elseif (!empty($risky)) { - $result->addError( - $test, - $this->getException($risky[0]), - $time - ); - } elseif (!empty($skipped)) { - $result->addError( - $test, - $this->getException($skipped[0]), - $time - ); - } elseif (!empty($errors)) { - $result->addError( - $test, - $this->getException($errors[0]), - $time - ); - } elseif (!empty($warnings)) { - $result->addWarning( - $test, - $this->getException($warnings[0]), - $time - ); - } elseif (!empty($failures)) { - $result->addFailure( - $test, - $this->getException($failures[0]), - $time - ); - } - } - } - - $result->endTest($test, $time); - - if (!empty($output)) { - print $output; - } - } - - /** - * Gets the thrown exception from a PHPUnit\Framework\TestFailure. - * - * @see https://github.com/sebastianbergmann/phpunit/issues/74 - */ - private function getException(TestFailure $error): Exception - { - $exception = $error->thrownException(); - - if ($exception instanceof __PHP_Incomplete_Class) { - $exceptionArray = []; - - foreach ((array) $exception as $key => $value) { - $key = substr($key, strrpos($key, "\0") + 1); - $exceptionArray[$key] = $value; - } - - $exception = new SyntheticError( - sprintf( - '%s: %s', - $exceptionArray['_PHP_Incomplete_Class_Name'], - $exceptionArray['message'] - ), - $exceptionArray['code'], - $exceptionArray['file'], - $exceptionArray['line'], - $exceptionArray['trace'] - ); - } - - return $exception; - } -} diff --git a/src/Util/PHP/DefaultJobRunner.php b/src/Util/PHP/DefaultJobRunner.php new file mode 100644 index 00000000000..7d2d419b324 --- /dev/null +++ b/src/Util/PHP/DefaultJobRunner.php @@ -0,0 +1,251 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\PHP; + +use const PHP_BINARY; +use const PHP_SAPI; +use function array_keys; +use function array_merge; +use function array_values; +use function assert; +use function fclose; +use function file_put_contents; +use function function_exists; +use function fwrite; +use function ini_get_all; +use function is_array; +use function is_resource; +use function proc_close; +use function proc_open; +use function stream_get_contents; +use function sys_get_temp_dir; +use function tempnam; +use function trim; +use function unlink; +use function xdebug_is_debugger_active; +use PHPUnit\Event\Facade; +use PHPUnit\Runner\CodeCoverage; +use SebastianBergmann\Environment\Runtime; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DefaultJobRunner extends JobRunner +{ + /** + * @throws PhpProcessException + */ + public function run(Job $job): Result + { + $temporaryFile = null; + + if ($job->hasInput()) { + $temporaryFile = tempnam(sys_get_temp_dir(), 'phpunit_'); + + if ($temporaryFile === false || + file_put_contents($temporaryFile, $job->code()) === false) { + // @codeCoverageIgnoreStart + throw new PhpProcessException( + 'Unable to write temporary file', + ); + // @codeCoverageIgnoreEnd + } + + $job = new Job( + $job->input(), + $job->phpSettings(), + $job->environmentVariables(), + $job->arguments(), + null, + $job->redirectErrors(), + $job->requiresXdebug(), + ); + } + + assert($temporaryFile !== ''); + + return $this->runProcess($job, $temporaryFile); + } + + /** + * @param ?non-empty-string $temporaryFile + * + * @throws PhpProcessException + */ + private function runProcess(Job $job, ?string $temporaryFile): Result + { + $environmentVariables = null; + + if ($job->hasEnvironmentVariables()) { + /** @phpstan-ignore nullCoalesce.variable */ + $environmentVariables = $_SERVER ?? []; + + unset($environmentVariables['argv'], $environmentVariables['argc']); + + $environmentVariables = array_merge($environmentVariables, $job->environmentVariables()); + + foreach ($environmentVariables as $key => $value) { + if (is_array($value)) { + unset($environmentVariables[$key]); + } + } + + unset($key, $value); + } + + $pipeSpec = [ + 0 => ['pipe', 'r'], + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ]; + + if ($job->redirectErrors()) { + $pipeSpec[2] = ['redirect', 1]; + } + + $process = proc_open( + $this->buildCommand($job, $temporaryFile), + $pipeSpec, + $pipes, + null, + $environmentVariables, + ); + + if (!is_resource($process)) { + // @codeCoverageIgnoreStart + throw new PhpProcessException( + 'Unable to spawn worker process', + ); + // @codeCoverageIgnoreEnd + } + + Facade::emitter()->childProcessStarted(); + + fwrite($pipes[0], $job->code()); + fclose($pipes[0]); + + $stdout = ''; + $stderr = ''; + + if (isset($pipes[1])) { + $stdout = stream_get_contents($pipes[1]); + + fclose($pipes[1]); + } + + if (isset($pipes[2])) { + $stderr = stream_get_contents($pipes[2]); + + fclose($pipes[2]); + } + + proc_close($process); + + if ($temporaryFile !== null) { + unlink($temporaryFile); + } + + assert($stdout !== false); + assert($stderr !== false); + + return new Result($stdout, $stderr); + } + + /** + * @return non-empty-list + */ + private function buildCommand(Job $job, ?string $file): array + { + $runtime = new Runtime; + $command = [PHP_BINARY]; + $phpSettings = $job->phpSettings(); + + if ($runtime->hasPCOV()) { + $pcovSettings = ini_get_all('pcov'); + + assert($pcovSettings !== false); + + $phpSettings = array_merge( + $phpSettings, + $runtime->getCurrentSettings( + array_keys($pcovSettings), + ), + ); + } elseif ($runtime->hasXdebug()) { + assert(function_exists('xdebug_is_debugger_active')); + + $xdebugSettings = ini_get_all('xdebug'); + + assert($xdebugSettings !== false); + + $phpSettings = array_merge( + $phpSettings, + $runtime->getCurrentSettings( + array_keys($xdebugSettings), + ), + ); + + if ( + !CodeCoverage::instance()->isActive() && + xdebug_is_debugger_active() === false && + !$job->requiresXdebug() + ) { + // disable xdebug to speedup test execution + $phpSettings['xdebug.mode'] = 'xdebug.mode=off'; + } + } + + $command = array_merge($command, $this->settingsToParameters(array_values($phpSettings))); + + if (PHP_SAPI === 'phpdbg') { + $command[] = '-qrr'; + + if ($file === null) { + $command[] = 's='; + } + } + + if ($file !== null) { + $command[] = '-f'; + $command[] = $file; + } + + if ($job->hasArguments()) { + if ($file === null) { + $command[] = '--'; + } + + foreach ($job->arguments() as $argument) { + $command[] = trim($argument); + } + } + + return $command; + } + + /** + * @param list $settings + * + * @return list + */ + private function settingsToParameters(array $settings): array + { + $buffer = []; + + foreach ($settings as $setting) { + $buffer[] = '-d'; + $buffer[] = $setting; + } + + return $buffer; + } +} diff --git a/src/Util/PHP/DefaultPhpProcess.php b/src/Util/PHP/DefaultPhpProcess.php deleted file mode 100644 index c4dc11146a8..00000000000 --- a/src/Util/PHP/DefaultPhpProcess.php +++ /dev/null @@ -1,236 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\PHP; - -use function array_merge; -use function fclose; -use function file_put_contents; -use function fread; -use function fwrite; -use function is_array; -use function is_resource; -use function proc_close; -use function proc_open; -use function proc_terminate; -use function rewind; -use function sprintf; -use function stream_get_contents; -use function stream_select; -use function sys_get_temp_dir; -use function tempnam; -use function unlink; -use PHPUnit\Framework\Exception; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -class DefaultPhpProcess extends AbstractPhpProcess -{ - /** - * @var string - */ - protected $tempFile; - - /** - * Runs a single job (PHP code) using a separate PHP process. - * - * @throws Exception - */ - public function runJob(string $job, array $settings = []): array - { - if ($this->stdin || $this->useTemporaryFile()) { - if (!($this->tempFile = tempnam(sys_get_temp_dir(), 'PHPUnit')) || - file_put_contents($this->tempFile, $job) === false) { - throw new Exception( - 'Unable to write temporary file' - ); - } - - $job = $this->stdin; - } - - return $this->runProcess($job, $settings); - } - - /** - * Returns an array of file handles to be used in place of pipes. - */ - protected function getHandles(): array - { - return []; - } - - /** - * Handles creating the child process and returning the STDOUT and STDERR. - * - * @throws Exception - */ - protected function runProcess(string $job, array $settings): array - { - $handles = $this->getHandles(); - - $env = null; - - if ($this->env) { - $env = $_SERVER ?? []; - unset($env['argv'], $env['argc']); - $env = array_merge($env, $this->env); - - foreach ($env as $envKey => $envVar) { - if (is_array($envVar)) { - unset($env[$envKey]); - } - } - } - - $pipeSpec = [ - 0 => $handles[0] ?? ['pipe', 'r'], - 1 => $handles[1] ?? ['pipe', 'w'], - 2 => $handles[2] ?? ['pipe', 'w'], - ]; - - $process = proc_open( - $this->getCommand($settings, $this->tempFile), - $pipeSpec, - $pipes, - null, - $env - ); - - if (!is_resource($process)) { - throw new Exception( - 'Unable to spawn worker process' - ); - } - - if ($job) { - $this->process($pipes[0], $job); - } - - fclose($pipes[0]); - - $stderr = $stdout = ''; - - if ($this->timeout) { - unset($pipes[0]); - - while (true) { - $r = $pipes; - $w = null; - $e = null; - - $n = @stream_select($r, $w, $e, $this->timeout); - - if ($n === false) { - break; - } - - if ($n === 0) { - proc_terminate($process, 9); - - throw new Exception( - sprintf( - 'Job execution aborted after %d seconds', - $this->timeout - ) - ); - } - - if ($n > 0) { - foreach ($r as $pipe) { - $pipeOffset = 0; - - foreach ($pipes as $i => $origPipe) { - if ($pipe === $origPipe) { - $pipeOffset = $i; - - break; - } - } - - if (!$pipeOffset) { - break; - } - - $line = fread($pipe, 8192); - - if ($line === '' || $line === false) { - fclose($pipes[$pipeOffset]); - - unset($pipes[$pipeOffset]); - } elseif ($pipeOffset === 1) { - $stdout .= $line; - } else { - $stderr .= $line; - } - } - - if (empty($pipes)) { - break; - } - } - } - } else { - if (isset($pipes[1])) { - $stdout = stream_get_contents($pipes[1]); - - fclose($pipes[1]); - } - - if (isset($pipes[2])) { - $stderr = stream_get_contents($pipes[2]); - - fclose($pipes[2]); - } - } - - if (isset($handles[1])) { - rewind($handles[1]); - - $stdout = stream_get_contents($handles[1]); - - fclose($handles[1]); - } - - if (isset($handles[2])) { - rewind($handles[2]); - - $stderr = stream_get_contents($handles[2]); - - fclose($handles[2]); - } - - proc_close($process); - - $this->cleanup(); - - return ['stdout' => $stdout, 'stderr' => $stderr]; - } - - /** - * @param resource $pipe - */ - protected function process($pipe, string $job): void - { - fwrite($pipe, $job); - } - - protected function cleanup(): void - { - if ($this->tempFile) { - unlink($this->tempFile); - } - } - - protected function useTemporaryFile(): bool - { - return false; - } -} diff --git a/src/Util/PHP/Job.php b/src/Util/PHP/Job.php new file mode 100644 index 00000000000..172d3f59356 --- /dev/null +++ b/src/Util/PHP/Job.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\PHP; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Job +{ + /** + * @var non-empty-string + */ + private string $code; + + /** + * @var list + */ + private array $phpSettings; + + /** + * @var array + */ + private array $environmentVariables; + + /** + * @var list + */ + private array $arguments; + + /** + * @var ?non-empty-string + */ + private ?string $input; + private bool $redirectErrors; + private bool $requiresXdebug; + + /** + * @param non-empty-string $code + * @param list $phpSettings + * @param array $environmentVariables + * @param list $arguments + * @param ?non-empty-string $input + */ + public function __construct(string $code, array $phpSettings = [], array $environmentVariables = [], array $arguments = [], ?string $input = null, bool $redirectErrors = false, bool $requiresXdebug = false) + { + $this->code = $code; + $this->phpSettings = $phpSettings; + $this->environmentVariables = $environmentVariables; + $this->arguments = $arguments; + $this->input = $input; + $this->redirectErrors = $redirectErrors; + $this->requiresXdebug = $requiresXdebug; + } + + /** + * @return non-empty-string + */ + public function code(): string + { + return $this->code; + } + + /** + * @return list + */ + public function phpSettings(): array + { + return $this->phpSettings; + } + + /** + * @phpstan-assert-if-true !empty $this->environmentVariables + */ + public function hasEnvironmentVariables(): bool + { + return $this->environmentVariables !== []; + } + + /** + * @return array + */ + public function environmentVariables(): array + { + return $this->environmentVariables; + } + + /** + * @phpstan-assert-if-true !empty $this->arguments + */ + public function hasArguments(): bool + { + return $this->arguments !== []; + } + + /** + * @return list + */ + public function arguments(): array + { + return $this->arguments; + } + + /** + * @phpstan-assert-if-true !empty $this->input + */ + public function hasInput(): bool + { + return $this->input !== null; + } + + /** + * @throws PhpProcessException + * + * @return non-empty-string + */ + public function input(): string + { + if ($this->input === null) { + throw new PhpProcessException('No input specified'); + } + + return $this->input; + } + + public function redirectErrors(): bool + { + return $this->redirectErrors; + } + + public function requiresXdebug(): bool + { + return $this->requiresXdebug; + } +} diff --git a/src/Util/PHP/JobRunner.php b/src/Util/PHP/JobRunner.php new file mode 100644 index 00000000000..6805a0c44df --- /dev/null +++ b/src/Util/PHP/JobRunner.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\PHP; + +use function assert; +use function file_get_contents; +use function is_file; +use function unlink; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Framework\ChildProcessResultProcessor; +use PHPUnit\Framework\Test; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class JobRunner +{ + private ChildProcessResultProcessor $processor; + + public function __construct(ChildProcessResultProcessor $processor) + { + $this->processor = $processor; + } + + /** + * @param non-empty-string $processResultFile + */ + final public function runTestJob(Job $job, string $processResultFile, Test $test): void + { + $result = $this->run($job); + + $processResult = ''; + + if (is_file($processResultFile)) { + $processResult = file_get_contents($processResultFile); + + assert($processResult !== false); + + @unlink($processResultFile); + } + + $this->processor->process( + $test, + $processResult, + $result->stderr(), + ); + + EventFacade::emitter()->childProcessFinished($result->stdout(), $result->stderr()); + } + + abstract public function run(Job $job): Result; +} diff --git a/src/Util/PHP/JobRunnerRegistry.php b/src/Util/PHP/JobRunnerRegistry.php new file mode 100644 index 00000000000..94e22a4e454 --- /dev/null +++ b/src/Util/PHP/JobRunnerRegistry.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\PHP; + +use PHPUnit\Event\Facade; +use PHPUnit\Framework\ChildProcessResultProcessor; +use PHPUnit\Framework\Test; +use PHPUnit\Runner\CodeCoverage; +use PHPUnit\TestRunner\TestResult\PassedTests; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class JobRunnerRegistry +{ + private static ?JobRunner $runner = null; + + public static function run(Job $job): Result + { + return self::runner()->run($job); + } + + /** + * @param non-empty-string $processResultFile + */ + public static function runTestJob(Job $job, string $processResultFile, Test $test): void + { + self::runner()->runTestJob($job, $processResultFile, $test); + } + + public static function set(JobRunner $runner): void + { + self::$runner = $runner; + } + + private static function runner(): JobRunner + { + if (self::$runner === null) { + self::$runner = new DefaultJobRunner( + new ChildProcessResultProcessor( + Facade::instance(), + Facade::emitter(), + PassedTests::instance(), + CodeCoverage::instance(), + ), + ); + } + + return self::$runner; + } +} diff --git a/src/Util/PHP/Result.php b/src/Util/PHP/Result.php new file mode 100644 index 00000000000..ed05822601b --- /dev/null +++ b/src/Util/PHP/Result.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\PHP; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Result +{ + private string $stdout; + private string $stderr; + + public function __construct(string $stdout, string $stderr) + { + $this->stdout = $stdout; + $this->stderr = $stderr; + } + + public function stdout(): string + { + return $this->stdout; + } + + public function stderr(): string + { + return $this->stderr; + } +} diff --git a/src/Util/PHP/Template/PhptTestCase.tpl b/src/Util/PHP/Template/PhptTestCase.tpl deleted file mode 100644 index 21e67fb2a68..00000000000 --- a/src/Util/PHP/Template/PhptTestCase.tpl +++ /dev/null @@ -1,53 +0,0 @@ -start(__FILE__); -} - -register_shutdown_function( - function() use ($coverage) { - $output = null; - - if ($coverage) { - $output = $coverage->stop(); - } - - file_put_contents('{coverageFile}', serialize($output)); - } -); - -ob_end_clean(); - -require '{job}'; diff --git a/src/Util/PHP/Template/TestCaseClass.tpl b/src/Util/PHP/Template/TestCaseClass.tpl deleted file mode 100644 index 3e4c64205a1..00000000000 --- a/src/Util/PHP/Template/TestCaseClass.tpl +++ /dev/null @@ -1,115 +0,0 @@ -setCodeCoverage( - new CodeCoverage( - Driver::{driverMethod}($filter), - $filter - ) - ); - } - - $result->beStrictAboutTestsThatDoNotTestAnything({isStrictAboutTestsThatDoNotTestAnything}); - $result->beStrictAboutOutputDuringTests({isStrictAboutOutputDuringTests}); - $result->enforceTimeLimit({enforcesTimeLimit}); - $result->beStrictAboutTodoAnnotatedTests({isStrictAboutTodoAnnotatedTests}); - $result->beStrictAboutResourceUsageDuringSmallTests({isStrictAboutResourceUsageDuringSmallTests}); - - $test = new {className}('{name}', unserialize('{data}'), '{dataName}'); - $test->setDependencyInput(unserialize('{dependencyInput}')); - $test->setInIsolation(TRUE); - - ob_end_clean(); - $test->run($result); - $output = ''; - if (!$test->hasExpectationOnOutput()) { - $output = $test->getActualOutput(); - } - - ini_set('xdebug.scream', '0'); - @rewind(STDOUT); /* @ as not every STDOUT target stream is rewindable */ - if ($stdout = stream_get_contents(STDOUT)) { - $output = $stdout . $output; - $streamMetaData = stream_get_meta_data(STDOUT); - if (!empty($streamMetaData['stream_type']) && 'STDIO' === $streamMetaData['stream_type']) { - @ftruncate(STDOUT, 0); - @rewind(STDOUT); - } - } - - print serialize( - [ - 'testResult' => $test->getResult(), - 'numAssertions' => $test->getNumAssertions(), - 'result' => $result, - 'output' => $output - ] - ); -} - -$configurationFilePath = '{configurationFilePath}'; - -if ('' !== $configurationFilePath) { - $configuration = (new Loader)->load($configurationFilePath); - - (new PhpHandler)->handle($configuration->php()); - - unset($configuration); -} - -function __phpunit_error_handler($errno, $errstr, $errfile, $errline) -{ - return true; -} - -set_error_handler('__phpunit_error_handler'); - -{constants} -{included_files} -{globals} - -restore_error_handler(); - -if (isset($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { - require_once $GLOBALS['__PHPUNIT_BOOTSTRAP']; - unset($GLOBALS['__PHPUNIT_BOOTSTRAP']); -} - -__phpunit_run_isolated_test(); diff --git a/src/Util/PHP/Template/TestCaseMethod.tpl b/src/Util/PHP/Template/TestCaseMethod.tpl deleted file mode 100644 index 396a526dc38..00000000000 --- a/src/Util/PHP/Template/TestCaseMethod.tpl +++ /dev/null @@ -1,118 +0,0 @@ -setCodeCoverage( - new CodeCoverage( - Driver::{driverMethod}($filter), - $filter - ) - ); - } - - $result->beStrictAboutTestsThatDoNotTestAnything({isStrictAboutTestsThatDoNotTestAnything}); - $result->beStrictAboutOutputDuringTests({isStrictAboutOutputDuringTests}); - $result->enforceTimeLimit({enforcesTimeLimit}); - $result->beStrictAboutTodoAnnotatedTests({isStrictAboutTodoAnnotatedTests}); - $result->beStrictAboutResourceUsageDuringSmallTests({isStrictAboutResourceUsageDuringSmallTests}); - - $test = new {className}('{methodName}', unserialize('{data}'), '{dataName}'); - \assert($test instanceof TestCase); - - $test->setDependencyInput(unserialize('{dependencyInput}')); - $test->setInIsolation(true); - - ob_end_clean(); - $test->run($result); - $output = ''; - if (!$test->hasExpectationOnOutput()) { - $output = $test->getActualOutput(); - } - - ini_set('xdebug.scream', '0'); - @rewind(STDOUT); /* @ as not every STDOUT target stream is rewindable */ - if ($stdout = stream_get_contents(STDOUT)) { - $output = $stdout . $output; - $streamMetaData = stream_get_meta_data(STDOUT); - if (!empty($streamMetaData['stream_type']) && 'STDIO' === $streamMetaData['stream_type']) { - @ftruncate(STDOUT, 0); - @rewind(STDOUT); - } - } - - print serialize( - [ - 'testResult' => $test->getResult(), - 'numAssertions' => $test->getNumAssertions(), - 'result' => $result, - 'output' => $output - ] - ); -} - -$configurationFilePath = '{configurationFilePath}'; - -if ('' !== $configurationFilePath) { - $configuration = (new Loader)->load($configurationFilePath); - - (new PhpHandler)->handle($configuration->php()); - - unset($configuration); -} - -function __phpunit_error_handler($errno, $errstr, $errfile, $errline) -{ - return true; -} - -set_error_handler('__phpunit_error_handler'); - -{constants} -{included_files} -{globals} - -restore_error_handler(); - -if (isset($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { - require_once $GLOBALS['__PHPUNIT_BOOTSTRAP']; - unset($GLOBALS['__PHPUNIT_BOOTSTRAP']); -} - -__phpunit_run_isolated_test(); diff --git a/src/Util/PHP/WindowsPhpProcess.php b/src/Util/PHP/WindowsPhpProcess.php deleted file mode 100644 index 9ef9255567f..00000000000 --- a/src/Util/PHP/WindowsPhpProcess.php +++ /dev/null @@ -1,52 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\PHP; - -use const PHP_MAJOR_VERSION; -use function tmpfile; -use PHPUnit\Framework\Exception; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @see https://bugs.php.net/bug.php?id=51800 - */ -final class WindowsPhpProcess extends DefaultPhpProcess -{ - public function getCommand(array $settings, string $file = null): string - { - if (PHP_MAJOR_VERSION < 8) { - return '"' . parent::getCommand($settings, $file) . '"'; - } - - return parent::getCommand($settings, $file); - } - - /** - * @throws Exception - */ - protected function getHandles(): array - { - if (false === $stdout_handle = tmpfile()) { - throw new Exception( - 'A temporary file could not be created; verify that your TEMP environment variable is writable' - ); - } - - return [ - 1 => $stdout_handle, - ]; - } - - protected function useTemporaryFile(): bool - { - return true; - } -} diff --git a/src/Util/Printer.php b/src/Util/Printer.php deleted file mode 100644 index 77b5745ad9d..00000000000 --- a/src/Util/Printer.php +++ /dev/null @@ -1,116 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use const ENT_COMPAT; -use const ENT_SUBSTITUTE; -use const PHP_SAPI; -use function assert; -use function count; -use function dirname; -use function explode; -use function fclose; -use function fopen; -use function fsockopen; -use function fwrite; -use function htmlspecialchars; -use function is_resource; -use function is_string; -use function sprintf; -use function str_replace; -use function strncmp; -use function strpos; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -class Printer -{ - /** - * @psalm-var closed-resource|resource - */ - private $stream; - - /** - * @var bool - */ - private $isPhpStream; - - /** - * @param null|resource|string $out - * - * @throws Exception - */ - public function __construct($out = null) - { - if (is_resource($out)) { - $this->stream = $out; - - return; - } - - if (!is_string($out)) { - return; - } - - if (strpos($out, 'socket://') === 0) { - $tmp = explode(':', str_replace('socket://', '', $out)); - - if (count($tmp) !== 2) { - throw new Exception( - sprintf( - '"%s" does not match "socket://hostname:port" format', - $out - ) - ); - } - - $this->stream = fsockopen($tmp[0], (int) $tmp[1]); - - return; - } - - if (strpos($out, 'php://') === false && !Filesystem::createDirectory(dirname($out))) { - throw new Exception( - sprintf( - 'Directory "%s" was not created', - dirname($out) - ) - ); - } - - $this->stream = fopen($out, 'wb'); - $this->isPhpStream = strncmp($out, 'php://', 6) !== 0; - } - - public function write(string $buffer): void - { - if ($this->stream) { - assert(is_resource($this->stream)); - - fwrite($this->stream, $buffer); - } else { - if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') { - $buffer = htmlspecialchars($buffer, ENT_COMPAT | ENT_SUBSTITUTE); - } - - print $buffer; - } - } - - public function flush(): void - { - if ($this->stream && $this->isPhpStream) { - assert(is_resource($this->stream)); - - fclose($this->stream); - } - } -} diff --git a/src/Util/Reflection.php b/src/Util/Reflection.php new file mode 100644 index 00000000000..b61c19b9b46 --- /dev/null +++ b/src/Util/Reflection.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use function array_keys; +use function array_merge; +use function array_reverse; +use function assert; +use PHPUnit\Framework\Assert; +use PHPUnit\Framework\TestCase; +use ReflectionClass; +use ReflectionException; +use ReflectionMethod; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Reflection +{ + /** + * @param class-string $className + * @param non-empty-string $methodName + * + * @return array{file: non-empty-string, line: non-negative-int} + */ + public static function sourceLocationFor(string $className, string $methodName): array + { + try { + $reflector = new ReflectionMethod($className, $methodName); + + $file = $reflector->getFileName(); + $line = $reflector->getStartLine(); + } catch (ReflectionException) { + $file = 'unknown'; + $line = 0; + } + + assert($file !== false && $file !== ''); + assert($line !== false && $line >= 0); + + return [ + 'file' => $file, + 'line' => $line, + ]; + } + + /** + * @param ReflectionClass $class + * + * @return list + */ + public static function publicMethodsDeclaredDirectlyInTestClass(ReflectionClass $class): array + { + return self::filterAndSortMethods($class, ReflectionMethod::IS_PUBLIC, true); + } + + /** + * @param ReflectionClass $class + * + * @return list + */ + public static function methodsDeclaredDirectlyInTestClass(ReflectionClass $class): array + { + return self::filterAndSortMethods($class, null, false); + } + + /** + * @param ReflectionClass $class + * + * @return list + */ + private static function filterAndSortMethods(ReflectionClass $class, ?int $filter, bool $sortHighestToLowest): array + { + $methodsByClass = []; + + foreach ($class->getMethods($filter) as $method) { + $declaringClassName = $method->getDeclaringClass()->getName(); + + if ($declaringClassName === TestCase::class) { + continue; + } + + if ($declaringClassName === Assert::class) { + continue; + } + + if (!isset($methodsByClass[$declaringClassName])) { + $methodsByClass[$declaringClassName] = []; + } + + $methodsByClass[$declaringClassName][] = $method; + } + + $classNames = array_keys($methodsByClass); + + if ($sortHighestToLowest) { + $classNames = array_reverse($classNames); + } + + $methods = []; + + foreach ($classNames as $className) { + $methods = array_merge($methods, $methodsByClass[$className]); + } + + return $methods; + } +} diff --git a/src/Util/RegularExpression.php b/src/Util/RegularExpression.php deleted file mode 100644 index 167b9215c2c..00000000000 --- a/src/Util/RegularExpression.php +++ /dev/null @@ -1,30 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use function preg_match; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class RegularExpression -{ - /** - * @return false|int - */ - public static function safeMatch(string $pattern, string $subject) - { - return ErrorHandler::invokeIgnoringWarnings( - static function () use ($pattern, $subject) { - return preg_match($pattern, $subject); - } - ); - } -} diff --git a/src/Util/Test.php b/src/Util/Test.php index b3de7279724..bcc2f371bbc 100644 --- a/src/Util/Test.php +++ b/src/Util/Test.php @@ -9,748 +9,51 @@ */ namespace PHPUnit\Util; -use const PHP_OS; -use const PHP_VERSION; -use function addcslashes; -use function array_flip; -use function array_key_exists; -use function array_merge; -use function array_unique; -use function array_unshift; -use function class_exists; -use function count; -use function explode; -use function extension_loaded; -use function function_exists; -use function get_class; -use function ini_get; -use function interface_exists; -use function is_array; -use function is_int; -use function method_exists; -use function phpversion; -use function preg_match; -use function preg_replace; -use function sprintf; -use function strncmp; -use function strpos; -use function version_compare; -use PHPUnit\Framework\Assert; -use PHPUnit\Framework\CodeCoverageException; -use PHPUnit\Framework\ExecutionOrderDependency; -use PHPUnit\Framework\InvalidCoversTargetException; -use PHPUnit\Framework\SelfDescribing; +use const DEBUG_BACKTRACE_IGNORE_ARGS; +use const DEBUG_BACKTRACE_PROVIDE_OBJECT; +use function debug_backtrace; +use function str_starts_with; +use PHPUnit\Event\Code\NoTestCaseObjectOnCallStackException; use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\Warning; -use PHPUnit\Runner\Version; -use PHPUnit\Util\Annotation\Registry; -use ReflectionClass; -use ReflectionException; +use PHPUnit\Metadata\Parser\Registry; use ReflectionMethod; -use SebastianBergmann\CodeUnit\CodeUnitCollection; -use SebastianBergmann\CodeUnit\InvalidCodeUnitException; -use SebastianBergmann\CodeUnit\Mapper; -use SebastianBergmann\Environment\OperatingSystem; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class Test +final readonly class Test { /** - * @var int - */ - public const UNKNOWN = -1; - - /** - * @var int - */ - public const SMALL = 0; - - /** - * @var int - */ - public const MEDIUM = 1; - - /** - * @var int - */ - public const LARGE = 2; - - /** - * @var array - */ - private static $hookMethods = []; - - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public static function describe(\PHPUnit\Framework\Test $test): array - { - if ($test instanceof TestCase) { - return [get_class($test), $test->getName()]; - } - - if ($test instanceof SelfDescribing) { - return ['', $test->toString()]; - } - - return ['', get_class($test)]; - } - - public static function describeAsString(\PHPUnit\Framework\Test $test): string - { - if ($test instanceof SelfDescribing) { - return $test->toString(); - } - - return get_class($test); - } - - /** - * @throws CodeCoverageException - * - * @return array|bool - * @psalm-param class-string $className + * @throws NoTestCaseObjectOnCallStackException */ - public static function getLinesToBeCovered(string $className, string $methodName) + public static function currentTestCase(): TestCase { - $annotations = self::parseTestMethodAnnotations( - $className, - $methodName - ); - - if (!self::shouldCoversAnnotationBeUsed($annotations)) { - return false; - } - - return self::getLinesToBeCoveredOrUsed($className, $methodName, 'covers'); - } - - /** - * Returns lines of code specified with the @uses annotation. - * - * @throws CodeCoverageException - * @psalm-param class-string $className - */ - public static function getLinesToBeUsed(string $className, string $methodName): array - { - return self::getLinesToBeCoveredOrUsed($className, $methodName, 'uses'); - } - - public static function requiresCodeCoverageDataCollection(TestCase $test): bool - { - $annotations = $test->getAnnotations(); - - // If there is no @covers annotation but a @coversNothing annotation on - // the test method then code coverage data does not need to be collected - if (isset($annotations['method']['coversNothing'])) { - return false; - } - - // If there is at least one @covers annotation then - // code coverage data needs to be collected - if (isset($annotations['method']['covers'])) { - return true; - } - - // If there is no @covers annotation but a @coversNothing annotation - // then code coverage data does not need to be collected - if (isset($annotations['class']['coversNothing'])) { - return false; - } - - // If there is no @coversNothing annotation then - // code coverage data may be collected - return true; - } - - /** - * @throws Exception - * @psalm-param class-string $className - */ - public static function getRequirements(string $className, string $methodName): array - { - return self::mergeArraysRecursively( - Registry::getInstance()->forClassName($className)->requirements(), - Registry::getInstance()->forMethod($className, $methodName)->requirements() - ); - } - - /** - * Returns the missing requirements for a test. - * - * @throws Exception - * @throws Warning - * @psalm-param class-string $className - */ - public static function getMissingRequirements(string $className, string $methodName): array - { - $required = self::getRequirements($className, $methodName); - $missing = []; - $hint = null; - - if (!empty($required['PHP'])) { - $operator = new VersionComparisonOperator(empty($required['PHP']['operator']) ? '>=' : $required['PHP']['operator']); - - if (!version_compare(PHP_VERSION, $required['PHP']['version'], $operator->asString())) { - $missing[] = sprintf('PHP %s %s is required.', $operator->asString(), $required['PHP']['version']); - $hint = 'PHP'; - } - } elseif (!empty($required['PHP_constraint'])) { - $version = new \PharIo\Version\Version(self::sanitizeVersionNumber(PHP_VERSION)); - - if (!$required['PHP_constraint']['constraint']->complies($version)) { - $missing[] = sprintf( - 'PHP version does not match the required constraint %s.', - $required['PHP_constraint']['constraint']->asString() - ); - - $hint = 'PHP_constraint'; - } - } - - if (!empty($required['PHPUnit'])) { - $phpunitVersion = Version::id(); - - $operator = new VersionComparisonOperator(empty($required['PHPUnit']['operator']) ? '>=' : $required['PHPUnit']['operator']); - - if (!version_compare($phpunitVersion, $required['PHPUnit']['version'], $operator->asString())) { - $missing[] = sprintf('PHPUnit %s %s is required.', $operator->asString(), $required['PHPUnit']['version']); - $hint = $hint ?? 'PHPUnit'; - } - } elseif (!empty($required['PHPUnit_constraint'])) { - $phpunitVersion = new \PharIo\Version\Version(self::sanitizeVersionNumber(Version::id())); - - if (!$required['PHPUnit_constraint']['constraint']->complies($phpunitVersion)) { - $missing[] = sprintf( - 'PHPUnit version does not match the required constraint %s.', - $required['PHPUnit_constraint']['constraint']->asString() - ); - - $hint = $hint ?? 'PHPUnit_constraint'; - } - } - - if (!empty($required['OSFAMILY']) && $required['OSFAMILY'] !== (new OperatingSystem)->getFamily()) { - $missing[] = sprintf('Operating system %s is required.', $required['OSFAMILY']); - $hint = $hint ?? 'OSFAMILY'; - } - - if (!empty($required['OS'])) { - $requiredOsPattern = sprintf('/%s/i', addcslashes($required['OS'], '/')); - - if (!preg_match($requiredOsPattern, PHP_OS)) { - $missing[] = sprintf('Operating system matching %s is required.', $requiredOsPattern); - $hint = $hint ?? 'OS'; - } - } - - if (!empty($required['functions'])) { - foreach ($required['functions'] as $function) { - $pieces = explode('::', $function); - - if (count($pieces) === 2 && class_exists($pieces[0]) && method_exists($pieces[0], $pieces[1])) { - continue; - } - - if (function_exists($function)) { - continue; - } - - $missing[] = sprintf('Function %s is required.', $function); - $hint = $hint ?? 'function_' . $function; - } - } - - if (!empty($required['setting'])) { - foreach ($required['setting'] as $setting => $value) { - if (ini_get($setting) !== $value) { - $missing[] = sprintf('Setting "%s" must be "%s".', $setting, $value); - $hint = $hint ?? '__SETTING_' . $setting; - } - } - } - - if (!empty($required['extensions'])) { - foreach ($required['extensions'] as $extension) { - if (isset($required['extension_versions'][$extension])) { - continue; - } - - if (!extension_loaded($extension)) { - $missing[] = sprintf('Extension %s is required.', $extension); - $hint = $hint ?? 'extension_' . $extension; - } - } - } - - if (!empty($required['extension_versions'])) { - foreach ($required['extension_versions'] as $extension => $req) { - $actualVersion = phpversion($extension); - - $operator = new VersionComparisonOperator(empty($req['operator']) ? '>=' : $req['operator']); - - if ($actualVersion === false || !version_compare($actualVersion, $req['version'], $operator->asString())) { - $missing[] = sprintf('Extension %s %s %s is required.', $extension, $operator->asString(), $req['version']); - $hint = $hint ?? 'extension_' . $extension; - } - } - } - - if ($hint && isset($required['__OFFSET'])) { - array_unshift($missing, '__OFFSET_FILE=' . $required['__OFFSET']['__FILE']); - array_unshift($missing, '__OFFSET_LINE=' . ($required['__OFFSET'][$hint] ?? 1)); - } - - return $missing; - } - - /** - * Returns the provided data for a method. - * - * @throws Exception - * @psalm-param class-string $className - */ - public static function getProvidedData(string $className, string $methodName): ?array - { - return Registry::getInstance()->forMethod($className, $methodName)->getProvidedData(); - } - - /** - * @psalm-param class-string $className - */ - public static function parseTestMethodAnnotations(string $className, ?string $methodName = ''): array - { - $registry = Registry::getInstance(); - - if ($methodName !== null) { - try { - return [ - 'method' => $registry->forMethod($className, $methodName)->symbolAnnotations(), - 'class' => $registry->forClassName($className)->symbolAnnotations(), - ]; - } catch (Exception $methodNotFound) { - // ignored + foreach (debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS) as $frame) { + if (isset($frame['object']) && $frame['object'] instanceof TestCase) { + return $frame['object']; } } - return [ - 'method' => null, - 'class' => $registry->forClassName($className)->symbolAnnotations(), - ]; - } - - /** - * @psalm-param class-string $className - */ - public static function getInlineAnnotations(string $className, string $methodName): array - { - return Registry::getInstance()->forMethod($className, $methodName)->getInlineAnnotations(); - } - - /** @psalm-param class-string $className */ - public static function getBackupSettings(string $className, string $methodName): array - { - return [ - 'backupGlobals' => self::getBooleanAnnotationSetting( - $className, - $methodName, - 'backupGlobals' - ), - 'backupStaticAttributes' => self::getBooleanAnnotationSetting( - $className, - $methodName, - 'backupStaticAttributes' - ), - ]; - } - - /** - * @psalm-param class-string $className - * - * @return ExecutionOrderDependency[] - */ - public static function getDependencies(string $className, string $methodName): array - { - $annotations = self::parseTestMethodAnnotations( - $className, - $methodName - ); - - $dependsAnnotations = $annotations['class']['depends'] ?? []; - - if (isset($annotations['method']['depends'])) { - $dependsAnnotations = array_merge( - $dependsAnnotations, - $annotations['method']['depends'] - ); - } - - // Normalize dependency name to className::methodName - $dependencies = []; - - foreach ($dependsAnnotations as $value) { - $dependencies[] = ExecutionOrderDependency::createFromDependsAnnotation($className, $value); - } - - return array_unique($dependencies); - } - - /** @psalm-param class-string $className */ - public static function getGroups(string $className, ?string $methodName = ''): array - { - $annotations = self::parseTestMethodAnnotations( - $className, - $methodName - ); - - $groups = []; - - if (isset($annotations['method']['author'])) { - $groups[] = $annotations['method']['author']; - } elseif (isset($annotations['class']['author'])) { - $groups[] = $annotations['class']['author']; - } - - if (isset($annotations['class']['group'])) { - $groups[] = $annotations['class']['group']; - } - - if (isset($annotations['method']['group'])) { - $groups[] = $annotations['method']['group']; - } - - if (isset($annotations['class']['ticket'])) { - $groups[] = $annotations['class']['ticket']; - } - - if (isset($annotations['method']['ticket'])) { - $groups[] = $annotations['method']['ticket']; - } - - foreach (['method', 'class'] as $element) { - foreach (['small', 'medium', 'large'] as $size) { - if (isset($annotations[$element][$size])) { - $groups[] = [$size]; - - break 2; - } - } - } - - return array_unique(array_merge([], ...$groups)); - } - - /** @psalm-param class-string $className */ - public static function getSize(string $className, ?string $methodName): int - { - $groups = array_flip(self::getGroups($className, $methodName)); - - if (isset($groups['large'])) { - return self::LARGE; - } - - if (isset($groups['medium'])) { - return self::MEDIUM; - } - - if (isset($groups['small'])) { - return self::SMALL; - } - - return self::UNKNOWN; - } - - /** @psalm-param class-string $className */ - public static function getProcessIsolationSettings(string $className, string $methodName): bool - { - $annotations = self::parseTestMethodAnnotations( - $className, - $methodName - ); - - return isset($annotations['class']['runTestsInSeparateProcesses']) || isset($annotations['method']['runInSeparateProcess']); - } - - /** @psalm-param class-string $className */ - public static function getClassProcessIsolationSettings(string $className, string $methodName): bool - { - $annotations = self::parseTestMethodAnnotations( - $className, - $methodName - ); - - return isset($annotations['class']['runClassInSeparateProcess']); - } - - /** @psalm-param class-string $className */ - public static function getPreserveGlobalStateSettings(string $className, string $methodName): ?bool - { - return self::getBooleanAnnotationSetting( - $className, - $methodName, - 'preserveGlobalState' - ); - } - - /** @psalm-param class-string $className */ - public static function getHookMethods(string $className): array - { - if (!class_exists($className, false)) { - return self::emptyHookMethodsArray(); - } - - if (!isset(self::$hookMethods[$className])) { - self::$hookMethods[$className] = self::emptyHookMethodsArray(); - - try { - foreach ((new ReflectionClass($className))->getMethods() as $method) { - if ($method->getDeclaringClass()->getName() === Assert::class) { - continue; - } - - if ($method->getDeclaringClass()->getName() === TestCase::class) { - continue; - } - - $docBlock = Registry::getInstance()->forMethod($className, $method->getName()); - - if ($method->isStatic()) { - if ($docBlock->isHookToBeExecutedBeforeClass()) { - array_unshift( - self::$hookMethods[$className]['beforeClass'], - $method->getName() - ); - } - - if ($docBlock->isHookToBeExecutedAfterClass()) { - self::$hookMethods[$className]['afterClass'][] = $method->getName(); - } - } - - if ($docBlock->isToBeExecutedBeforeTest()) { - array_unshift( - self::$hookMethods[$className]['before'], - $method->getName() - ); - } - - if ($docBlock->isToBeExecutedAsPreCondition()) { - array_unshift( - self::$hookMethods[$className]['preCondition'], - $method->getName() - ); - } - - if ($docBlock->isToBeExecutedAsPostCondition()) { - self::$hookMethods[$className]['postCondition'][] = $method->getName(); - } - - if ($docBlock->isToBeExecutedAfterTest()) { - self::$hookMethods[$className]['after'][] = $method->getName(); - } - } - } catch (ReflectionException $e) { - } - } - - return self::$hookMethods[$className]; + throw new NoTestCaseObjectOnCallStackException; } public static function isTestMethod(ReflectionMethod $method): bool { - if (strpos($method->getName(), 'test') === 0) { - return true; - } - - return array_key_exists( - 'test', - Registry::getInstance()->forMethod( - $method->getDeclaringClass()->getName(), - $method->getName() - ) - ->symbolAnnotations() - ); - } - - /** - * @throws CodeCoverageException - * @psalm-param class-string $className - */ - private static function getLinesToBeCoveredOrUsed(string $className, string $methodName, string $mode): array - { - $annotations = self::parseTestMethodAnnotations( - $className, - $methodName - ); - - $classShortcut = null; - - if (!empty($annotations['class'][$mode . 'DefaultClass'])) { - if (count($annotations['class'][$mode . 'DefaultClass']) > 1) { - throw new CodeCoverageException( - sprintf( - 'More than one @%sClass annotation in class or interface "%s".', - $mode, - $className - ) - ); - } - - $classShortcut = $annotations['class'][$mode . 'DefaultClass'][0]; - } - - $list = $annotations['class'][$mode] ?? []; - - if (isset($annotations['method'][$mode])) { - $list = array_merge($list, $annotations['method'][$mode]); - } - - $codeUnits = CodeUnitCollection::fromArray([]); - $mapper = new Mapper; - - foreach (array_unique($list) as $element) { - if ($classShortcut && strncmp($element, '::', 2) === 0) { - $element = $classShortcut . $element; - } - - $element = preg_replace('/[\s()]+$/', '', $element); - $element = explode(' ', $element); - $element = $element[0]; - - if ($mode === 'covers' && interface_exists($element)) { - throw new InvalidCoversTargetException( - sprintf( - 'Trying to @cover interface "%s".', - $element - ) - ); - } - - try { - $codeUnits = $codeUnits->mergeWith($mapper->stringToCodeUnits($element)); - } catch (InvalidCodeUnitException $e) { - throw new InvalidCoversTargetException( - sprintf( - '"@%s %s" is invalid', - $mode, - $element - ), - (int) $e->getCode(), - $e - ); - } - } - - return $mapper->codeUnitsToSourceLines($codeUnits); - } - - private static function emptyHookMethodsArray(): array - { - return [ - 'beforeClass' => ['setUpBeforeClass'], - 'before' => ['setUp'], - 'preCondition' => ['assertPreConditions'], - 'postCondition' => ['assertPostConditions'], - 'after' => ['tearDown'], - 'afterClass' => ['tearDownAfterClass'], - ]; - } - - /** @psalm-param class-string $className */ - private static function getBooleanAnnotationSetting(string $className, ?string $methodName, string $settingName): ?bool - { - $annotations = self::parseTestMethodAnnotations( - $className, - $methodName - ); - - if (isset($annotations['method'][$settingName])) { - if ($annotations['method'][$settingName][0] === 'enabled') { - return true; - } - - if ($annotations['method'][$settingName][0] === 'disabled') { - return false; - } - } - - if (isset($annotations['class'][$settingName])) { - if ($annotations['class'][$settingName][0] === 'enabled') { - return true; - } - - if ($annotations['class'][$settingName][0] === 'disabled') { - return false; - } - } - - return null; - } - - /** - * Trims any extensions from version string that follows after - * the .[.] format. - */ - private static function sanitizeVersionNumber(string $version) - { - return preg_replace( - '/^(\d+\.\d+(?:.\d+)?).*$/', - '$1', - $version - ); - } - - private static function shouldCoversAnnotationBeUsed(array $annotations): bool - { - if (isset($annotations['method']['coversNothing'])) { + if (!$method->isPublic()) { return false; } - if (isset($annotations['method']['covers'])) { + if (str_starts_with($method->getName(), 'test')) { return true; } - if (isset($annotations['class']['coversNothing'])) { - return false; - } - - return true; - } - - /** - * Merge two arrays together. - * - * If an integer key exists in both arrays and preserveNumericKeys is false, the value - * from the second array will be appended to the first array. If both values are arrays, they - * are merged together, else the value of the second array overwrites the one of the first array. - * - * This implementation is copied from https://github.com/zendframework/zend-stdlib/blob/76b653c5e99b40eccf5966e3122c90615134ae46/src/ArrayUtils.php - * - * Zend Framework (http://framework.zend.com/) - * - * @link http://github.com/zendframework/zf2 for the canonical source repository - * - * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) - * @license http://framework.zend.com/license/new-bsd New BSD License - */ - private static function mergeArraysRecursively(array $a, array $b): array - { - foreach ($b as $key => $value) { - if (array_key_exists($key, $a)) { - if (is_int($key)) { - $a[] = $value; - } elseif (is_array($value) && is_array($a[$key])) { - $a[$key] = self::mergeArraysRecursively($a[$key], $value); - } else { - $a[$key] = $value; - } - } else { - $a[$key] = $value; - } - } + $metadata = Registry::parser()->forMethod( + $method->getDeclaringClass()->getName(), + $method->getName(), + ); - return $a; + return $metadata->isTest()->isNotEmpty(); } } diff --git a/src/Util/TestDox/CliTestDoxPrinter.php b/src/Util/TestDox/CliTestDoxPrinter.php deleted file mode 100644 index 7c0fc11743e..00000000000 --- a/src/Util/TestDox/CliTestDoxPrinter.php +++ /dev/null @@ -1,380 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\TestDox; - -use const PHP_EOL; -use function array_map; -use function ceil; -use function count; -use function explode; -use function get_class; -use function implode; -use function preg_match; -use function sprintf; -use function strlen; -use function strpos; -use function trim; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestResult; -use PHPUnit\Runner\BaseTestRunner; -use PHPUnit\Runner\PhptTestCase; -use PHPUnit\Util\Color; -use SebastianBergmann\Timer\ResourceUsageFormatter; -use SebastianBergmann\Timer\Timer; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -class CliTestDoxPrinter extends TestDoxPrinter -{ - /** - * The default Testdox left margin for messages is a vertical line. - */ - private const PREFIX_SIMPLE = [ - 'default' => '│', - 'start' => '│', - 'message' => '│', - 'diff' => '│', - 'trace' => '│', - 'last' => '│', - ]; - - /** - * Colored Testdox use box-drawing for a more textured map of the message. - */ - private const PREFIX_DECORATED = [ - 'default' => '│', - 'start' => '┐', - 'message' => '├', - 'diff' => '┊', - 'trace' => '╵', - 'last' => '┴', - ]; - - private const SPINNER_ICONS = [ - " \e[36m◐\e[0m running tests", - " \e[36m◓\e[0m running tests", - " \e[36m◑\e[0m running tests", - " \e[36m◒\e[0m running tests", - ]; - - private const STATUS_STYLES = [ - BaseTestRunner::STATUS_PASSED => [ - 'symbol' => '✔', - 'color' => 'fg-green', - ], - BaseTestRunner::STATUS_ERROR => [ - 'symbol' => '✘', - 'color' => 'fg-yellow', - 'message' => 'bg-yellow,fg-black', - ], - BaseTestRunner::STATUS_FAILURE => [ - 'symbol' => '✘', - 'color' => 'fg-red', - 'message' => 'bg-red,fg-white', - ], - BaseTestRunner::STATUS_SKIPPED => [ - 'symbol' => '↩', - 'color' => 'fg-cyan', - 'message' => 'fg-cyan', - ], - BaseTestRunner::STATUS_RISKY => [ - 'symbol' => '☢', - 'color' => 'fg-yellow', - 'message' => 'fg-yellow', - ], - BaseTestRunner::STATUS_INCOMPLETE => [ - 'symbol' => '∅', - 'color' => 'fg-yellow', - 'message' => 'fg-yellow', - ], - BaseTestRunner::STATUS_WARNING => [ - 'symbol' => '⚠', - 'color' => 'fg-yellow', - 'message' => 'fg-yellow', - ], - BaseTestRunner::STATUS_UNKNOWN => [ - 'symbol' => '?', - 'color' => 'fg-blue', - 'message' => 'fg-white,bg-blue', - ], - ]; - - /** - * @var int[] - */ - private $nonSuccessfulTestResults = []; - - /** - * @var Timer - */ - private $timer; - - /** - * @param null|resource|string $out - * @param int|string $numberOfColumns - * - * @throws \PHPUnit\Framework\Exception - */ - public function __construct($out = null, bool $verbose = false, string $colors = self::COLOR_DEFAULT, bool $debug = false, $numberOfColumns = 80, bool $reverse = false) - { - parent::__construct($out, $verbose, $colors, $debug, $numberOfColumns, $reverse); - - $this->timer = new Timer; - - $this->timer->start(); - } - - public function printResult(TestResult $result): void - { - $this->printHeader($result); - - $this->printNonSuccessfulTestsSummary($result->count()); - - $this->printFooter($result); - } - - protected function printHeader(TestResult $result): void - { - $this->write("\n" . (new ResourceUsageFormatter)->resourceUsage($this->timer->stop()) . "\n\n"); - } - - protected function formatClassName(Test $test): string - { - if ($test instanceof TestCase) { - return $this->prettifier->prettifyTestClass(get_class($test)); - } - - return get_class($test); - } - - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - protected function registerTestResult(Test $test, ?Throwable $t, int $status, float $time, bool $verbose): void - { - if ($status !== BaseTestRunner::STATUS_PASSED) { - $this->nonSuccessfulTestResults[] = $this->testIndex; - } - - parent::registerTestResult($test, $t, $status, $time, $verbose); - } - - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - protected function formatTestName(Test $test): string - { - if ($test instanceof TestCase) { - return $this->prettifier->prettifyTestCase($test); - } - - return parent::formatTestName($test); - } - - protected function writeTestResult(array $prevResult, array $result): void - { - // spacer line for new suite headers and after verbose messages - if ($prevResult['testName'] !== '' && - (!empty($prevResult['message']) || $prevResult['className'] !== $result['className'])) { - $this->write(PHP_EOL); - } - - // suite header - if ($prevResult['className'] !== $result['className']) { - $this->write($this->colorizeTextBox('underlined', $result['className']) . PHP_EOL); - } - - // test result line - if ($this->colors && $result['className'] === PhptTestCase::class) { - $testName = Color::colorizePath($result['testName'], $prevResult['testName'], true); - } else { - $testName = $result['testMethod']; - } - - $style = self::STATUS_STYLES[$result['status']]; - $line = sprintf( - ' %s %s%s' . PHP_EOL, - $this->colorizeTextBox($style['color'], $style['symbol']), - $testName, - $this->verbose ? ' ' . $this->formatRuntime($result['time'], $style['color']) : '' - ); - - $this->write($line); - - // additional information when verbose - $this->write($result['message']); - } - - protected function formatThrowable(Throwable $t, ?int $status = null): string - { - return trim(\PHPUnit\Framework\TestFailure::exceptionToString($t)); - } - - protected function colorizeMessageAndDiff(string $style, string $buffer): array - { - $lines = $buffer ? array_map('\rtrim', explode(PHP_EOL, $buffer)) : []; - $message = []; - $diff = []; - $insideDiff = false; - - foreach ($lines as $line) { - if ($line === '--- Expected') { - $insideDiff = true; - } - - if (!$insideDiff) { - $message[] = $line; - } else { - if (strpos($line, '-') === 0) { - $line = Color::colorize('fg-red', Color::visualizeWhitespace($line, true)); - } elseif (strpos($line, '+') === 0) { - $line = Color::colorize('fg-green', Color::visualizeWhitespace($line, true)); - } elseif ($line === '@@ @@') { - $line = Color::colorize('fg-cyan', $line); - } - $diff[] = $line; - } - } - $diff = implode(PHP_EOL, $diff); - - if (!empty($message)) { - $message = $this->colorizeTextBox($style, implode(PHP_EOL, $message)); - } - - return [$message, $diff]; - } - - protected function formatStacktrace(Throwable $t): string - { - $trace = \PHPUnit\Util\Filter::getFilteredStacktrace($t); - - if (!$this->colors) { - return $trace; - } - - $lines = []; - $prevPath = ''; - - foreach (explode(PHP_EOL, $trace) as $line) { - if (preg_match('/^(.*):(\d+)$/', $line, $matches)) { - $lines[] = Color::colorizePath($matches[1], $prevPath) . - Color::dim(':') . - Color::colorize('fg-blue', $matches[2]) . - "\n"; - $prevPath = $matches[1]; - } else { - $lines[] = $line; - $prevPath = ''; - } - } - - return implode('', $lines); - } - - protected function formatTestResultMessage(Throwable $t, array $result, ?string $prefix = null): string - { - $message = $this->formatThrowable($t, $result['status']); - $diff = ''; - - if (!($this->verbose || $result['verbose'])) { - return ''; - } - - if ($message && $this->colors) { - $style = self::STATUS_STYLES[$result['status']]['message'] ?? ''; - [$message, $diff] = $this->colorizeMessageAndDiff($style, $message); - } - - if ($prefix === null || !$this->colors) { - $prefix = self::PREFIX_SIMPLE; - } - - if ($this->colors) { - $color = self::STATUS_STYLES[$result['status']]['color'] ?? ''; - $prefix = array_map(static function ($p) use ($color) { - return Color::colorize($color, $p); - }, self::PREFIX_DECORATED); - } - - $trace = $this->formatStacktrace($t); - $out = $this->prefixLines($prefix['start'], PHP_EOL) . PHP_EOL; - - if ($message) { - $out .= $this->prefixLines($prefix['message'], $message . PHP_EOL) . PHP_EOL; - } - - if ($diff) { - $out .= $this->prefixLines($prefix['diff'], $diff . PHP_EOL) . PHP_EOL; - } - - if ($trace) { - if ($message || $diff) { - $out .= $this->prefixLines($prefix['default'], PHP_EOL) . PHP_EOL; - } - $out .= $this->prefixLines($prefix['trace'], $trace . PHP_EOL) . PHP_EOL; - } - $out .= $this->prefixLines($prefix['last'], PHP_EOL) . PHP_EOL; - - return $out; - } - - protected function drawSpinner(): void - { - if ($this->colors) { - $id = $this->spinState % count(self::SPINNER_ICONS); - $this->write(self::SPINNER_ICONS[$id]); - } - } - - protected function undrawSpinner(): void - { - if ($this->colors) { - $id = $this->spinState % count(self::SPINNER_ICONS); - $this->write("\e[1K\e[" . strlen(self::SPINNER_ICONS[$id]) . 'D'); - } - } - - private function formatRuntime(float $time, string $color = ''): string - { - if (!$this->colors) { - return sprintf('[%.2f ms]', $time * 1000); - } - - if ($time > 1) { - $color = 'fg-magenta'; - } - - return Color::colorize($color, ' ' . (int) ceil($time * 1000) . ' ' . Color::dim('ms')); - } - - private function printNonSuccessfulTestsSummary(int $numberOfExecutedTests): void - { - if (empty($this->nonSuccessfulTestResults)) { - return; - } - - if ((count($this->nonSuccessfulTestResults) / $numberOfExecutedTests) >= 0.7) { - return; - } - - $this->write("Summary of non-successful tests:\n\n"); - - $prevResult = $this->getEmptyTestResult(); - - foreach ($this->nonSuccessfulTestResults as $testIndex) { - $result = $this->testResults[$testIndex]; - $this->writeTestResult($prevResult, $result); - $prevResult = $result; - } - } -} diff --git a/src/Util/TestDox/HtmlResultPrinter.php b/src/Util/TestDox/HtmlResultPrinter.php deleted file mode 100644 index d2bcbe527c9..00000000000 --- a/src/Util/TestDox/HtmlResultPrinter.php +++ /dev/null @@ -1,138 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\TestDox; - -use function sprintf; -use PHPUnit\Framework\TestResult; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class HtmlResultPrinter extends ResultPrinter -{ - /** - * @var string - */ - private const PAGE_HEADER = <<<'EOT' - - - - - Test Documentation - - - -EOT; - - /** - * @var string - */ - private const CLASS_HEADER = <<<'EOT' - -

    %s

    -
      - -EOT; - - /** - * @var string - */ - private const CLASS_FOOTER = <<<'EOT' -
    -EOT; - - /** - * @var string - */ - private const PAGE_FOOTER = <<<'EOT' - - - -EOT; - - public function printResult(TestResult $result): void - { - } - - /** - * Handler for 'start run' event. - */ - protected function startRun(): void - { - $this->write(self::PAGE_HEADER); - } - - /** - * Handler for 'start class' event. - */ - protected function startClass(string $name): void - { - $this->write( - sprintf( - self::CLASS_HEADER, - $name, - $this->currentTestClassPrettified - ) - ); - } - - /** - * Handler for 'on test' event. - */ - protected function onTest(string $name, bool $success = true): void - { - $this->write( - sprintf( - "
  • %s %s
  • \n", - $success ? '#555753' : '#ef2929', - $success ? '✓' : '❌', - $name - ) - ); - } - - /** - * Handler for 'end class' event. - */ - protected function endClass(string $name): void - { - $this->write(self::CLASS_FOOTER); - } - - /** - * Handler for 'end run' event. - */ - protected function endRun(): void - { - $this->write(self::PAGE_FOOTER); - } -} diff --git a/src/Util/TestDox/NamePrettifier.php b/src/Util/TestDox/NamePrettifier.php deleted file mode 100644 index 6347c832459..00000000000 --- a/src/Util/TestDox/NamePrettifier.php +++ /dev/null @@ -1,324 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\TestDox; - -use function array_key_exists; -use function array_keys; -use function array_map; -use function array_pop; -use function array_values; -use function explode; -use function get_class; -use function gettype; -use function implode; -use function in_array; -use function is_bool; -use function is_float; -use function is_int; -use function is_numeric; -use function is_object; -use function is_scalar; -use function is_string; -use function mb_strtolower; -use function ord; -use function preg_quote; -use function preg_replace; -use function range; -use function sprintf; -use function str_replace; -use function strlen; -use function strpos; -use function strtolower; -use function strtoupper; -use function substr; -use function trim; -use PHPUnit\Framework\TestCase; -use PHPUnit\Util\Color; -use PHPUnit\Util\Exception as UtilException; -use PHPUnit\Util\Test; -use ReflectionException; -use ReflectionMethod; -use ReflectionObject; -use SebastianBergmann\Exporter\Exporter; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class NamePrettifier -{ - /** - * @var string[] - */ - private $strings = []; - - /** - * @var bool - */ - private $useColor; - - public function __construct(bool $useColor = false) - { - $this->useColor = $useColor; - } - - /** - * Prettifies the name of a test class. - * - * @psalm-param class-string $className - */ - public function prettifyTestClass(string $className): string - { - try { - $annotations = Test::parseTestMethodAnnotations($className); - - if (isset($annotations['class']['testdox'][0])) { - return $annotations['class']['testdox'][0]; - } - } catch (UtilException $e) { - // ignore, determine className by parsing the provided name - } - - $parts = explode('\\', $className); - $className = array_pop($parts); - - if (substr($className, -1 * strlen('Test')) === 'Test') { - $className = substr($className, 0, strlen($className) - strlen('Test')); - } - - if (strpos($className, 'Tests') === 0) { - $className = substr($className, strlen('Tests')); - } elseif (strpos($className, 'Test') === 0) { - $className = substr($className, strlen('Test')); - } - - if (empty($className)) { - $className = 'UnnamedTests'; - } - - if (!empty($parts)) { - $parts[] = $className; - $fullyQualifiedName = implode('\\', $parts); - } else { - $fullyQualifiedName = $className; - } - - $result = ''; - $wasLowerCase = false; - - foreach (range(0, strlen($className) - 1) as $i) { - $isLowerCase = mb_strtolower($className[$i], 'UTF-8') === $className[$i]; - - if ($wasLowerCase && !$isLowerCase) { - $result .= ' '; - } - - $result .= $className[$i]; - - if ($isLowerCase) { - $wasLowerCase = true; - } else { - $wasLowerCase = false; - } - } - - if ($fullyQualifiedName !== $className) { - return $result . ' (' . $fullyQualifiedName . ')'; - } - - return $result; - } - - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function prettifyTestCase(TestCase $test): string - { - $annotations = $test->getAnnotations(); - $annotationWithPlaceholders = false; - - $callback = static function (string $variable): string { - return sprintf('/%s(?=\b)/', preg_quote($variable, '/')); - }; - - if (isset($annotations['method']['testdox'][0])) { - $result = $annotations['method']['testdox'][0]; - - if (strpos($result, '$') !== false) { - $annotation = $annotations['method']['testdox'][0]; - $providedData = $this->mapTestMethodParameterNamesToProvidedDataValues($test); - $variables = array_map($callback, array_keys($providedData)); - - $result = trim(preg_replace($variables, $providedData, $annotation)); - - $annotationWithPlaceholders = true; - } - } else { - $result = $this->prettifyTestMethod($test->getName(false)); - } - - if (!$annotationWithPlaceholders && $test->usesDataProvider()) { - $result .= $this->prettifyDataSet($test); - } - - return $result; - } - - public function prettifyDataSet(TestCase $test): string - { - if (!$this->useColor) { - return $test->getDataSetAsString(false); - } - - if (is_int($test->dataName())) { - $data = Color::dim(' with data set ') . Color::colorize('fg-cyan', (string) $test->dataName()); - } else { - $data = Color::dim(' with ') . Color::colorize('fg-cyan', Color::visualizeWhitespace((string) $test->dataName())); - } - - return $data; - } - - /** - * Prettifies the name of a test method. - */ - public function prettifyTestMethod(string $name): string - { - $buffer = ''; - - if ($name === '') { - return $buffer; - } - - $string = (string) preg_replace('#\d+$#', '', $name, -1, $count); - - if (in_array($string, $this->strings, true)) { - $name = $string; - } elseif ($count === 0) { - $this->strings[] = $string; - } - - if (strpos($name, 'test_') === 0) { - $name = substr($name, 5); - } elseif (strpos($name, 'test') === 0) { - $name = substr($name, 4); - } - - if ($name === '') { - return $buffer; - } - - $name[0] = strtoupper($name[0]); - - if (strpos($name, '_') !== false) { - return trim(str_replace('_', ' ', $name)); - } - - $wasNumeric = false; - - foreach (range(0, strlen($name) - 1) as $i) { - if ($i > 0 && ord($name[$i]) >= 65 && ord($name[$i]) <= 90) { - $buffer .= ' ' . strtolower($name[$i]); - } else { - $isNumeric = is_numeric($name[$i]); - - if (!$wasNumeric && $isNumeric) { - $buffer .= ' '; - $wasNumeric = true; - } - - if ($wasNumeric && !$isNumeric) { - $wasNumeric = false; - } - - $buffer .= $name[$i]; - } - } - - return $buffer; - } - - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - private function mapTestMethodParameterNamesToProvidedDataValues(TestCase $test): array - { - try { - $reflector = new ReflectionMethod(get_class($test), $test->getName(false)); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new UtilException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - $providedData = []; - $providedDataValues = array_values($test->getProvidedData()); - $i = 0; - - $providedData['$_dataName'] = $test->dataName(); - - foreach ($reflector->getParameters() as $parameter) { - if (!array_key_exists($i, $providedDataValues) && $parameter->isDefaultValueAvailable()) { - try { - $providedDataValues[$i] = $parameter->getDefaultValue(); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new UtilException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - } - - $value = $providedDataValues[$i++] ?? null; - - if (is_object($value)) { - $reflector = new ReflectionObject($value); - - if ($reflector->hasMethod('__toString')) { - $value = (string) $value; - } else { - $value = get_class($value); - } - } - - if (!is_scalar($value)) { - $value = gettype($value); - } - - if (is_bool($value) || is_int($value) || is_float($value)) { - $value = (new Exporter)->export($value); - } - - if (is_string($value) && $value === '') { - if ($this->useColor) { - $value = Color::colorize('dim,underlined', 'empty'); - } else { - $value = "''"; - } - } - - $providedData['$' . $parameter->getName()] = $value; - } - - if ($this->useColor) { - $providedData = array_map(static function ($value) { - return Color::colorize('fg-cyan', Color::visualizeWhitespace((string) $value, true)); - }, $providedData); - } - - return $providedData; - } -} diff --git a/src/Util/TestDox/ResultPrinter.php b/src/Util/TestDox/ResultPrinter.php deleted file mode 100644 index feaee82d3a3..00000000000 --- a/src/Util/TestDox/ResultPrinter.php +++ /dev/null @@ -1,342 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\TestDox; - -use function get_class; -use function in_array; -use PHPUnit\Framework\AssertionFailedError; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Framework\Warning; -use PHPUnit\Framework\WarningTestCase; -use PHPUnit\Runner\BaseTestRunner; -use PHPUnit\TextUI\ResultPrinter as ResultPrinterInterface; -use PHPUnit\Util\Printer; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -abstract class ResultPrinter extends Printer implements ResultPrinterInterface -{ - /** - * @var NamePrettifier - */ - protected $prettifier; - - /** - * @var string - */ - protected $testClass = ''; - - /** - * @var int - */ - protected $testStatus; - - /** - * @var array - */ - protected $tests = []; - - /** - * @var int - */ - protected $successful = 0; - - /** - * @var int - */ - protected $warned = 0; - - /** - * @var int - */ - protected $failed = 0; - - /** - * @var int - */ - protected $risky = 0; - - /** - * @var int - */ - protected $skipped = 0; - - /** - * @var int - */ - protected $incomplete = 0; - - /** - * @var null|string - */ - protected $currentTestClassPrettified; - - /** - * @var null|string - */ - protected $currentTestMethodPrettified; - - /** - * @var array - */ - private $groups; - - /** - * @var array - */ - private $excludeGroups; - - /** - * @param resource $out - * - * @throws \PHPUnit\Framework\Exception - */ - public function __construct($out = null, array $groups = [], array $excludeGroups = []) - { - parent::__construct($out); - - $this->groups = $groups; - $this->excludeGroups = $excludeGroups; - - $this->prettifier = new NamePrettifier; - $this->startRun(); - } - - /** - * Flush buffer and close output. - */ - public function flush(): void - { - $this->doEndClass(); - $this->endRun(); - - parent::flush(); - } - - /** - * An error occurred. - */ - public function addError(Test $test, Throwable $t, float $time): void - { - if (!$this->isOfInterest($test)) { - return; - } - - $this->testStatus = BaseTestRunner::STATUS_ERROR; - $this->failed++; - } - - /** - * A warning occurred. - */ - public function addWarning(Test $test, Warning $e, float $time): void - { - if (!$this->isOfInterest($test)) { - return; - } - - $this->testStatus = BaseTestRunner::STATUS_WARNING; - $this->warned++; - } - - /** - * A failure occurred. - */ - public function addFailure(Test $test, AssertionFailedError $e, float $time): void - { - if (!$this->isOfInterest($test)) { - return; - } - - $this->testStatus = BaseTestRunner::STATUS_FAILURE; - $this->failed++; - } - - /** - * Incomplete test. - */ - public function addIncompleteTest(Test $test, Throwable $t, float $time): void - { - if (!$this->isOfInterest($test)) { - return; - } - - $this->testStatus = BaseTestRunner::STATUS_INCOMPLETE; - $this->incomplete++; - } - - /** - * Risky test. - */ - public function addRiskyTest(Test $test, Throwable $t, float $time): void - { - if (!$this->isOfInterest($test)) { - return; - } - - $this->testStatus = BaseTestRunner::STATUS_RISKY; - $this->risky++; - } - - /** - * Skipped test. - */ - public function addSkippedTest(Test $test, Throwable $t, float $time): void - { - if (!$this->isOfInterest($test)) { - return; - } - - $this->testStatus = BaseTestRunner::STATUS_SKIPPED; - $this->skipped++; - } - - /** - * A testsuite started. - */ - public function startTestSuite(TestSuite $suite): void - { - } - - /** - * A testsuite ended. - */ - public function endTestSuite(TestSuite $suite): void - { - } - - /** - * A test started. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function startTest(Test $test): void - { - if (!$this->isOfInterest($test)) { - return; - } - - $class = get_class($test); - - if ($this->testClass !== $class) { - if ($this->testClass !== '') { - $this->doEndClass(); - } - - $this->currentTestClassPrettified = $this->prettifier->prettifyTestClass($class); - $this->testClass = $class; - $this->tests = []; - - $this->startClass($class); - } - - if ($test instanceof TestCase) { - $this->currentTestMethodPrettified = $this->prettifier->prettifyTestCase($test); - } - - $this->testStatus = BaseTestRunner::STATUS_PASSED; - } - - /** - * A test ended. - */ - public function endTest(Test $test, float $time): void - { - if (!$this->isOfInterest($test)) { - return; - } - - $this->tests[] = [$this->currentTestMethodPrettified, $this->testStatus]; - - $this->currentTestClassPrettified = null; - $this->currentTestMethodPrettified = null; - } - - protected function doEndClass(): void - { - foreach ($this->tests as $test) { - $this->onTest($test[0], $test[1] === BaseTestRunner::STATUS_PASSED); - } - - $this->endClass($this->testClass); - } - - /** - * Handler for 'start run' event. - */ - protected function startRun(): void - { - } - - /** - * Handler for 'start class' event. - */ - protected function startClass(string $name): void - { - } - - /** - * Handler for 'on test' event. - */ - protected function onTest(string $name, bool $success = true): void - { - } - - /** - * Handler for 'end class' event. - */ - protected function endClass(string $name): void - { - } - - /** - * Handler for 'end run' event. - */ - protected function endRun(): void - { - } - - private function isOfInterest(Test $test): bool - { - if (!$test instanceof TestCase) { - return false; - } - - if ($test instanceof WarningTestCase) { - return false; - } - - if (!empty($this->groups)) { - foreach ($test->getGroups() as $group) { - if (in_array($group, $this->groups, true)) { - return true; - } - } - - return false; - } - - if (!empty($this->excludeGroups)) { - foreach ($test->getGroups() as $group) { - if (in_array($group, $this->excludeGroups, true)) { - return false; - } - } - - return true; - } - - return true; - } -} diff --git a/src/Util/TestDox/TestDoxPrinter.php b/src/Util/TestDox/TestDoxPrinter.php deleted file mode 100644 index 32c90f0b1bf..00000000000 --- a/src/Util/TestDox/TestDoxPrinter.php +++ /dev/null @@ -1,385 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\TestDox; - -use const PHP_EOL; -use function array_map; -use function get_class; -use function implode; -use function preg_split; -use function trim; -use PHPUnit\Framework\AssertionFailedError; -use PHPUnit\Framework\Reorderable; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestResult; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Framework\Warning; -use PHPUnit\Runner\BaseTestRunner; -use PHPUnit\Runner\PhptTestCase; -use PHPUnit\TextUI\DefaultResultPrinter; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -class TestDoxPrinter extends DefaultResultPrinter -{ - /** - * @var NamePrettifier - */ - protected $prettifier; - - /** - * @var int The number of test results received from the TestRunner - */ - protected $testIndex = 0; - - /** - * @var int The number of test results already sent to the output - */ - protected $testFlushIndex = 0; - - /** - * @var array Buffer for test results - */ - protected $testResults = []; - - /** - * @var array Lookup table for testname to testResults[index] - */ - protected $testNameResultIndex = []; - - /** - * @var bool - */ - protected $enableOutputBuffer = false; - - /** - * @var array array - */ - protected $originalExecutionOrder = []; - - /** - * @var int - */ - protected $spinState = 0; - - /** - * @var bool - */ - protected $showProgress = true; - - /** - * @param null|resource|string $out - * @param int|string $numberOfColumns - * - * @throws \PHPUnit\Framework\Exception - */ - public function __construct($out = null, bool $verbose = false, string $colors = self::COLOR_DEFAULT, bool $debug = false, $numberOfColumns = 80, bool $reverse = false) - { - parent::__construct($out, $verbose, $colors, $debug, $numberOfColumns, $reverse); - - $this->prettifier = new NamePrettifier($this->colors); - } - - public function setOriginalExecutionOrder(array $order): void - { - $this->originalExecutionOrder = $order; - $this->enableOutputBuffer = !empty($order); - } - - public function setShowProgressAnimation(bool $showProgress): void - { - $this->showProgress = $showProgress; - } - - public function printResult(TestResult $result): void - { - } - - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function endTest(Test $test, float $time): void - { - if (!$test instanceof TestCase && !$test instanceof PhptTestCase && !$test instanceof TestSuite) { - return; - } - - if ($this->testHasPassed()) { - $this->registerTestResult($test, null, BaseTestRunner::STATUS_PASSED, $time, false); - } - - if ($test instanceof TestCase || $test instanceof PhptTestCase) { - $this->testIndex++; - } - - parent::endTest($test, $time); - } - - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function addError(Test $test, Throwable $t, float $time): void - { - $this->registerTestResult($test, $t, BaseTestRunner::STATUS_ERROR, $time, true); - } - - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function addWarning(Test $test, Warning $e, float $time): void - { - $this->registerTestResult($test, $e, BaseTestRunner::STATUS_WARNING, $time, true); - } - - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function addFailure(Test $test, AssertionFailedError $e, float $time): void - { - $this->registerTestResult($test, $e, BaseTestRunner::STATUS_FAILURE, $time, true); - } - - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function addIncompleteTest(Test $test, Throwable $t, float $time): void - { - $this->registerTestResult($test, $t, BaseTestRunner::STATUS_INCOMPLETE, $time, false); - } - - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function addRiskyTest(Test $test, Throwable $t, float $time): void - { - $this->registerTestResult($test, $t, BaseTestRunner::STATUS_RISKY, $time, false); - } - - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function addSkippedTest(Test $test, Throwable $t, float $time): void - { - $this->registerTestResult($test, $t, BaseTestRunner::STATUS_SKIPPED, $time, false); - } - - public function writeProgress(string $progress): void - { - $this->flushOutputBuffer(); - } - - public function flush(): void - { - $this->flushOutputBuffer(true); - } - - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - protected function registerTestResult(Test $test, ?Throwable $t, int $status, float $time, bool $verbose): void - { - $testName = $test instanceof Reorderable ? $test->sortId() : $test->getName(); - - $result = [ - 'className' => $this->formatClassName($test), - 'testName' => $testName, - 'testMethod' => $this->formatTestName($test), - 'message' => '', - 'status' => $status, - 'time' => $time, - 'verbose' => $verbose, - ]; - - if ($t !== null) { - $result['message'] = $this->formatTestResultMessage($t, $result); - } - - $this->testResults[$this->testIndex] = $result; - $this->testNameResultIndex[$testName] = $this->testIndex; - } - - protected function formatTestName(Test $test): string - { - return method_exists($test, 'getName') ? $test->getName() : ''; - } - - protected function formatClassName(Test $test): string - { - return get_class($test); - } - - protected function testHasPassed(): bool - { - if (!isset($this->testResults[$this->testIndex]['status'])) { - return true; - } - - if ($this->testResults[$this->testIndex]['status'] === BaseTestRunner::STATUS_PASSED) { - return true; - } - - return false; - } - - protected function flushOutputBuffer(bool $forceFlush = false): void - { - if ($this->testFlushIndex === $this->testIndex) { - return; - } - - if ($this->testFlushIndex > 0) { - if ($this->enableOutputBuffer) { - $prevResult = $this->getTestResultByName($this->originalExecutionOrder[$this->testFlushIndex - 1]); - } else { - $prevResult = $this->testResults[$this->testFlushIndex - 1]; - } - } else { - $prevResult = $this->getEmptyTestResult(); - } - - if (!$this->enableOutputBuffer) { - $this->writeTestResult($prevResult, $this->testResults[$this->testFlushIndex++]); - } else { - do { - $flushed = false; - - if (!$forceFlush && isset($this->originalExecutionOrder[$this->testFlushIndex])) { - $result = $this->getTestResultByName($this->originalExecutionOrder[$this->testFlushIndex]); - } else { - // This test(name) cannot found in original execution order, - // flush result to output stream right away - $result = $this->testResults[$this->testFlushIndex]; - } - - if (!empty($result)) { - $this->hideSpinner(); - $this->writeTestResult($prevResult, $result); - $this->testFlushIndex++; - $prevResult = $result; - $flushed = true; - } else { - $this->showSpinner(); - } - } while ($flushed && $this->testFlushIndex < $this->testIndex); - } - } - - protected function showSpinner(): void - { - if (!$this->showProgress) { - return; - } - - if ($this->spinState) { - $this->undrawSpinner(); - } - - $this->spinState++; - $this->drawSpinner(); - } - - protected function hideSpinner(): void - { - if (!$this->showProgress) { - return; - } - - if ($this->spinState) { - $this->undrawSpinner(); - } - - $this->spinState = 0; - } - - protected function drawSpinner(): void - { - // optional for CLI printers: show the user a 'buffering output' spinner - } - - protected function undrawSpinner(): void - { - // remove the spinner from the current line - } - - protected function writeTestResult(array $prevResult, array $result): void - { - } - - protected function getEmptyTestResult(): array - { - return [ - 'className' => '', - 'testName' => '', - 'message' => '', - 'failed' => '', - 'verbose' => '', - ]; - } - - protected function getTestResultByName(?string $testName): array - { - if (isset($this->testNameResultIndex[$testName])) { - return $this->testResults[$this->testNameResultIndex[$testName]]; - } - - return []; - } - - protected function formatThrowable(Throwable $t, ?int $status = null): string - { - $message = trim(\PHPUnit\Framework\TestFailure::exceptionToString($t)); - - if ($message) { - $message .= PHP_EOL . PHP_EOL . $this->formatStacktrace($t); - } else { - $message = $this->formatStacktrace($t); - } - - return $message; - } - - protected function formatStacktrace(Throwable $t): string - { - return \PHPUnit\Util\Filter::getFilteredStacktrace($t); - } - - protected function formatTestResultMessage(Throwable $t, array $result, string $prefix = '│'): string - { - $message = $this->formatThrowable($t, $result['status']); - - if ($message === '') { - return ''; - } - - if (!($this->verbose || $result['verbose'])) { - return ''; - } - - return $this->prefixLines($prefix, $message); - } - - protected function prefixLines(string $prefix, string $message): string - { - $message = trim($message); - - return implode( - PHP_EOL, - array_map( - static function (string $text) use ($prefix) { - return ' ' . $prefix . ($text ? ' ' . $text : ''); - }, - preg_split('/\r\n|\r|\n/', $message) - ) - ); - } -} diff --git a/src/Util/TestDox/TextResultPrinter.php b/src/Util/TestDox/TextResultPrinter.php deleted file mode 100644 index 8a1893e55ca..00000000000 --- a/src/Util/TestDox/TextResultPrinter.php +++ /dev/null @@ -1,52 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\TestDox; - -use PHPUnit\Framework\TestResult; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class TextResultPrinter extends ResultPrinter -{ - public function printResult(TestResult $result): void - { - } - - /** - * Handler for 'start class' event. - */ - protected function startClass(string $name): void - { - $this->write($this->currentTestClassPrettified . "\n"); - } - - /** - * Handler for 'on test' event. - */ - protected function onTest(string $name, bool $success = true): void - { - if ($success) { - $this->write(' [x] '); - } else { - $this->write(' [ ] '); - } - - $this->write($name . "\n"); - } - - /** - * Handler for 'end class' event. - */ - protected function endClass(string $name): void - { - $this->write("\n"); - } -} diff --git a/src/Util/TestDox/XmlResultPrinter.php b/src/Util/TestDox/XmlResultPrinter.php deleted file mode 100644 index 7a8d7d76920..00000000000 --- a/src/Util/TestDox/XmlResultPrinter.php +++ /dev/null @@ -1,254 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\TestDox; - -use function array_filter; -use function get_class; -use function implode; -use DOMDocument; -use DOMElement; -use PHPUnit\Framework\AssertionFailedError; -use PHPUnit\Framework\Exception; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestListener; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Framework\Warning; -use PHPUnit\Util\Printer; -use ReflectionClass; -use ReflectionException; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class XmlResultPrinter extends Printer implements TestListener -{ - /** - * @var DOMDocument - */ - private $document; - - /** - * @var DOMElement - */ - private $root; - - /** - * @var NamePrettifier - */ - private $prettifier; - - /** - * @var null|Throwable - */ - private $exception; - - /** - * @param resource|string $out - * - * @throws Exception - */ - public function __construct($out = null) - { - $this->document = new DOMDocument('1.0', 'UTF-8'); - $this->document->formatOutput = true; - - $this->root = $this->document->createElement('tests'); - $this->document->appendChild($this->root); - - $this->prettifier = new NamePrettifier; - - parent::__construct($out); - } - - /** - * Flush buffer and close output. - */ - public function flush(): void - { - $this->write($this->document->saveXML()); - - parent::flush(); - } - - /** - * An error occurred. - */ - public function addError(Test $test, Throwable $t, float $time): void - { - $this->exception = $t; - } - - /** - * A warning occurred. - */ - public function addWarning(Test $test, Warning $e, float $time): void - { - } - - /** - * A failure occurred. - */ - public function addFailure(Test $test, AssertionFailedError $e, float $time): void - { - $this->exception = $e; - } - - /** - * Incomplete test. - */ - public function addIncompleteTest(Test $test, Throwable $t, float $time): void - { - } - - /** - * Risky test. - */ - public function addRiskyTest(Test $test, Throwable $t, float $time): void - { - } - - /** - * Skipped test. - */ - public function addSkippedTest(Test $test, Throwable $t, float $time): void - { - } - - /** - * A test suite started. - */ - public function startTestSuite(TestSuite $suite): void - { - } - - /** - * A test suite ended. - */ - public function endTestSuite(TestSuite $suite): void - { - } - - /** - * A test started. - */ - public function startTest(Test $test): void - { - $this->exception = null; - } - - /** - * A test ended. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function endTest(Test $test, float $time): void - { - if (!$test instanceof TestCase) { - return; - } - - $groups = array_filter( - $test->getGroups(), - static function ($group) { - return !($group === 'small' || $group === 'medium' || $group === 'large'); - } - ); - - $testNode = $this->document->createElement('test'); - - $testNode->setAttribute('className', get_class($test)); - $testNode->setAttribute('methodName', $test->getName()); - $testNode->setAttribute('prettifiedClassName', $this->prettifier->prettifyTestClass(get_class($test))); - $testNode->setAttribute('prettifiedMethodName', $this->prettifier->prettifyTestCase($test)); - $testNode->setAttribute('status', (string) $test->getStatus()); - $testNode->setAttribute('time', (string) $time); - $testNode->setAttribute('size', (string) $test->getSize()); - $testNode->setAttribute('groups', implode(',', $groups)); - - foreach ($groups as $group) { - $groupNode = $this->document->createElement('group'); - - $groupNode->setAttribute('name', $group); - - $testNode->appendChild($groupNode); - } - - $annotations = $test->getAnnotations(); - - foreach (['class', 'method'] as $type) { - foreach ($annotations[$type] as $annotation => $values) { - if ($annotation !== 'covers' && $annotation !== 'uses') { - continue; - } - - foreach ($values as $value) { - $coversNode = $this->document->createElement($annotation); - - $coversNode->setAttribute('target', $value); - - $testNode->appendChild($coversNode); - } - } - } - - foreach ($test->doubledTypes() as $doubledType) { - $testDoubleNode = $this->document->createElement('testDouble'); - - $testDoubleNode->setAttribute('type', $doubledType); - - $testNode->appendChild($testDoubleNode); - } - - $inlineAnnotations = \PHPUnit\Util\Test::getInlineAnnotations(get_class($test), $test->getName(false)); - - if (isset($inlineAnnotations['given'], $inlineAnnotations['when'], $inlineAnnotations['then'])) { - $testNode->setAttribute('given', $inlineAnnotations['given']['value']); - $testNode->setAttribute('givenStartLine', (string) $inlineAnnotations['given']['line']); - $testNode->setAttribute('when', $inlineAnnotations['when']['value']); - $testNode->setAttribute('whenStartLine', (string) $inlineAnnotations['when']['line']); - $testNode->setAttribute('then', $inlineAnnotations['then']['value']); - $testNode->setAttribute('thenStartLine', (string) $inlineAnnotations['then']['line']); - } - - if ($this->exception !== null) { - if ($this->exception instanceof Exception) { - $steps = $this->exception->getSerializableTrace(); - } else { - $steps = $this->exception->getTrace(); - } - - try { - $file = (new ReflectionClass($test))->getFileName(); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - - foreach ($steps as $step) { - if (isset($step['file']) && $step['file'] === $file) { - $testNode->setAttribute('exceptionLine', (string) $step['line']); - - break; - } - } - - $testNode->setAttribute('exceptionMessage', $this->exception->getMessage()); - } - - $this->root->appendChild($testNode); - } -} diff --git a/src/Util/TextTestListRenderer.php b/src/Util/TextTestListRenderer.php deleted file mode 100644 index 67168a67f31..00000000000 --- a/src/Util/TextTestListRenderer.php +++ /dev/null @@ -1,54 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use const PHP_EOL; -use function get_class; -use function sprintf; -use function str_replace; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Runner\PhptTestCase; -use RecursiveIteratorIterator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class TextTestListRenderer -{ - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function render(TestSuite $suite): string - { - $buffer = 'Available test(s):' . PHP_EOL; - - foreach (new RecursiveIteratorIterator($suite->getIterator()) as $test) { - if ($test instanceof TestCase) { - $name = sprintf( - '%s::%s', - get_class($test), - str_replace(' with data set ', '', $test->getName()) - ); - } elseif ($test instanceof PhptTestCase) { - $name = $test->getName(); - } else { - continue; - } - - $buffer .= sprintf( - ' - %s' . PHP_EOL, - $name - ); - } - - return $buffer; - } -} diff --git a/src/Util/ThrowableToStringMapper.php b/src/Util/ThrowableToStringMapper.php new file mode 100644 index 00000000000..0fcf3695b83 --- /dev/null +++ b/src/Util/ThrowableToStringMapper.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use function trim; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\PhptAssertionFailedError; +use PHPUnit\Framework\SelfDescribing; +use PHPUnit\Runner\ErrorException; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ThrowableToStringMapper +{ + public static function map(Throwable $t): string + { + if ($t instanceof ErrorException) { + return $t->getMessage(); + } + + if ($t instanceof SelfDescribing) { + $buffer = $t->toString(); + + if ($t instanceof ExpectationFailedException && $t->getComparisonFailure() !== null) { + $buffer .= $t->getComparisonFailure()->getDiff(); + } + + if ($t instanceof PhptAssertionFailedError) { + $buffer .= $t->diff(); + } + + if ($buffer !== '') { + $buffer = trim($buffer) . "\n"; + } + + return $buffer; + } + + return $t::class . ': ' . $t->getMessage() . "\n"; + } +} diff --git a/src/Util/Type.php b/src/Util/Type.php deleted file mode 100644 index 01a6b1931e9..00000000000 --- a/src/Util/Type.php +++ /dev/null @@ -1,52 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Type -{ - public static function isType(string $type): bool - { - switch ($type) { - case 'numeric': - case 'integer': - case 'int': - case 'iterable': - case 'float': - case 'string': - case 'boolean': - case 'bool': - case 'null': - case 'array': - case 'object': - case 'resource': - case 'scalar': - return true; - - default: - return false; - } - } - - public static function isCloneable(object $object): bool - { - try { - $clone = clone $object; - } catch (Throwable $t) { - return false; - } - - return $clone instanceof $object; - } -} diff --git a/src/Util/VersionComparisonOperator.php b/src/Util/VersionComparisonOperator.php index ab65dbf359a..9dcba3c32a3 100644 --- a/src/Util/VersionComparisonOperator.php +++ b/src/Util/VersionComparisonOperator.php @@ -10,19 +10,24 @@ namespace PHPUnit\Util; use function in_array; -use function sprintf; /** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * @psalm-immutable + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable */ -final class VersionComparisonOperator +final readonly class VersionComparisonOperator { /** - * @psalm-var '<'|'lt'|'<='|'le'|'>'|'gt'|'>='|'ge'|'=='|'='|'eq'|'!='|'<>'|'ne' + * @var '!='|'<'|'<='|'<>'|'='|'=='|'>'|'>='|'eq'|'ge'|'gt'|'le'|'lt'|'ne' */ - private $operator; + private string $operator; + /** + * @param '!='|'<'|'<='|'<>'|'='|'=='|'>'|'>='|'eq'|'ge'|'gt'|'le'|'lt'|'ne' $operator + * + * @throws InvalidVersionOperatorException + */ public function __construct(string $operator) { $this->ensureOperatorIsValid($operator); @@ -31,7 +36,7 @@ public function __construct(string $operator) } /** - * @return '<'|'lt'|'<='|'le'|'>'|'gt'|'>='|'ge'|'=='|'='|'eq'|'!='|'<>'|'ne' + * @return '!='|'<'|'<='|'<>'|'='|'=='|'>'|'>='|'eq'|'ge'|'gt'|'le'|'lt'|'ne' */ public function asString(): string { @@ -39,19 +44,14 @@ public function asString(): string } /** - * @throws Exception + * @param '!='|'<'|'<='|'<>'|'='|'=='|'>'|'>='|'eq'|'ge'|'gt'|'le'|'lt'|'ne' $operator * - * @psalm-assert '<'|'lt'|'<='|'le'|'>'|'gt'|'>='|'ge'|'=='|'='|'eq'|'!='|'<>'|'ne' $operator + * @throws InvalidVersionOperatorException */ private function ensureOperatorIsValid(string $operator): void { if (!in_array($operator, ['<', 'lt', '<=', 'le', '>', 'gt', '>=', 'ge', '==', '=', 'eq', '!=', '<>', 'ne'], true)) { - throw new Exception( - sprintf( - '"%s" is not a valid version_compare() operator', - $operator - ) - ); + throw new InvalidVersionOperatorException($operator); } } } diff --git a/src/Util/XdebugFilterScriptGenerator.php b/src/Util/XdebugFilterScriptGenerator.php deleted file mode 100644 index 4a57528cc59..00000000000 --- a/src/Util/XdebugFilterScriptGenerator.php +++ /dev/null @@ -1,80 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use const DIRECTORY_SEPARATOR; -use function addslashes; -use function array_map; -use function implode; -use function is_string; -use function realpath; -use function sprintf; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\CodeCoverage as FilterConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @deprecated - */ -final class XdebugFilterScriptGenerator -{ - public function generate(FilterConfiguration $filter): string - { - $files = array_map( - static function ($item) { - return sprintf( - " '%s'", - $item - ); - }, - $this->getItems($filter) - ); - - $files = implode(",\n", $files); - - return <<directories() as $directory) { - $path = realpath($directory->path()); - - if (is_string($path)) { - $files[] = sprintf( - addslashes('%s' . DIRECTORY_SEPARATOR), - $path - ); - } - } - - foreach ($filter->files() as $file) { - $files[] = $file->path(); - } - - return $files; - } -} diff --git a/src/Util/Xml.php b/src/Util/Xml.php deleted file mode 100644 index 1bf6a6146a9..00000000000 --- a/src/Util/Xml.php +++ /dev/null @@ -1,314 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use const ENT_QUOTES; -use function assert; -use function chdir; -use function class_exists; -use function dirname; -use function error_reporting; -use function file_get_contents; -use function getcwd; -use function gettype; -use function htmlspecialchars; -use function is_string; -use function libxml_get_errors; -use function libxml_use_internal_errors; -use function mb_convert_encoding; -use function ord; -use function preg_replace; -use function settype; -use function sprintf; -use function strlen; -use DOMCharacterData; -use DOMDocument; -use DOMElement; -use DOMNode; -use DOMText; -use PHPUnit\Framework\Exception; -use ReflectionClass; -use ReflectionException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Xml -{ - public static function import(DOMElement $element): DOMElement - { - return (new DOMDocument)->importNode($element, true); - } - - /** - * Load an $actual document into a DOMDocument. This is called - * from the selector assertions. - * - * If $actual is already a DOMDocument, it is returned with - * no changes. Otherwise, $actual is loaded into a new DOMDocument - * as either HTML or XML, depending on the value of $isHtml. If $isHtml is - * false and $xinclude is true, xinclude is performed on the loaded - * DOMDocument. - * - * Note: prior to PHPUnit 3.3.0, this method loaded a file and - * not a string as it currently does. To load a file into a - * DOMDocument, use loadFile() instead. - * - * @param DOMDocument|string $actual - * - * @throws Exception - */ - public static function load($actual, bool $isHtml = false, string $filename = '', bool $xinclude = false, bool $strict = false): DOMDocument - { - if ($actual instanceof DOMDocument) { - return $actual; - } - - if (!is_string($actual)) { - throw new Exception('Could not load XML from ' . gettype($actual)); - } - - if ($actual === '') { - throw new Exception('Could not load XML from empty string'); - } - - // Required for XInclude on Windows. - if ($xinclude) { - $cwd = getcwd(); - @chdir(dirname($filename)); - } - - $document = new DOMDocument; - $document->preserveWhiteSpace = false; - - $internal = libxml_use_internal_errors(true); - $message = ''; - $reporting = error_reporting(0); - - if ($filename !== '') { - // Required for XInclude - $document->documentURI = $filename; - } - - if ($isHtml) { - $loaded = $document->loadHTML($actual); - } else { - $loaded = $document->loadXML($actual); - } - - if (!$isHtml && $xinclude) { - $document->xinclude(); - } - - foreach (libxml_get_errors() as $error) { - $message .= "\n" . $error->message; - } - - libxml_use_internal_errors($internal); - error_reporting($reporting); - - if (isset($cwd)) { - @chdir($cwd); - } - - if ($loaded === false || ($strict && $message !== '')) { - if ($filename !== '') { - throw new Exception( - sprintf( - 'Could not load "%s".%s', - $filename, - $message !== '' ? "\n" . $message : '' - ) - ); - } - - if ($message === '') { - $message = 'Could not load XML for unknown reason'; - } - - throw new Exception($message); - } - - return $document; - } - - /** - * Loads an XML (or HTML) file into a DOMDocument object. - * - * @throws Exception - */ - public static function loadFile(string $filename, bool $isHtml = false, bool $xinclude = false, bool $strict = false): DOMDocument - { - $reporting = error_reporting(0); - $contents = file_get_contents($filename); - - error_reporting($reporting); - - if ($contents === false) { - throw new Exception( - sprintf( - 'Could not read "%s".', - $filename - ) - ); - } - - return self::load($contents, $isHtml, $filename, $xinclude, $strict); - } - - public static function removeCharacterDataNodes(DOMNode $node): void - { - if ($node->hasChildNodes()) { - for ($i = $node->childNodes->length - 1; $i >= 0; $i--) { - if (($child = $node->childNodes->item($i)) instanceof DOMCharacterData) { - $node->removeChild($child); - } - } - } - } - - /** - * Escapes a string for the use in XML documents. - * - * Any Unicode character is allowed, excluding the surrogate blocks, FFFE, - * and FFFF (not even as character reference). - * - * @see https://www.w3.org/TR/xml/#charsets - */ - public static function prepareString(string $string): string - { - return preg_replace( - '/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]/', - '', - htmlspecialchars( - self::convertToUtf8($string), - ENT_QUOTES - ) - ); - } - - /** - * "Convert" a DOMElement object into a PHP variable. - */ - public static function xmlToVariable(DOMElement $element) - { - $variable = null; - - switch ($element->tagName) { - case 'array': - $variable = []; - - foreach ($element->childNodes as $entry) { - if (!$entry instanceof DOMElement || $entry->tagName !== 'element') { - continue; - } - $item = $entry->childNodes->item(0); - - if ($item instanceof DOMText) { - $item = $entry->childNodes->item(1); - } - - $value = self::xmlToVariable($item); - - if ($entry->hasAttribute('key')) { - $variable[(string) $entry->getAttribute('key')] = $value; - } else { - $variable[] = $value; - } - } - - break; - - case 'object': - $className = $element->getAttribute('class'); - - if ($element->hasChildNodes()) { - $arguments = $element->childNodes->item(0)->childNodes; - $constructorArgs = []; - - foreach ($arguments as $argument) { - if ($argument instanceof DOMElement) { - $constructorArgs[] = self::xmlToVariable($argument); - } - } - - try { - assert(class_exists($className)); - - $variable = (new ReflectionClass($className))->newInstanceArgs($constructorArgs); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - } else { - $variable = new $className; - } - - break; - - case 'boolean': - $variable = $element->textContent === 'true'; - - break; - - case 'integer': - case 'double': - case 'string': - $variable = $element->textContent; - - settype($variable, $element->tagName); - - break; - } - - return $variable; - } - - private static function convertToUtf8(string $string): string - { - if (!self::isUtf8($string)) { - $string = mb_convert_encoding($string, 'UTF-8'); - } - - return $string; - } - - private static function isUtf8(string $string): bool - { - $length = strlen($string); - - for ($i = 0; $i < $length; $i++) { - if (ord($string[$i]) < 0x80) { - $n = 0; - } elseif ((ord($string[$i]) & 0xE0) === 0xC0) { - $n = 1; - } elseif ((ord($string[$i]) & 0xF0) === 0xE0) { - $n = 2; - } elseif ((ord($string[$i]) & 0xF0) === 0xF0) { - $n = 3; - } else { - return false; - } - - for ($j = 0; $j < $n; $j++) { - if ((++$i === $length) || ((ord($string[$i]) & 0xC0) !== 0x80)) { - return false; - } - } - } - - return true; - } -} diff --git a/src/Util/Xml/Loader.php b/src/Util/Xml/Loader.php new file mode 100644 index 00000000000..1289c253fc6 --- /dev/null +++ b/src/Util/Xml/Loader.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Xml; + +use function error_reporting; +use function file_get_contents; +use function libxml_get_errors; +use function libxml_use_internal_errors; +use function sprintf; +use function trim; +use DOMDocument; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Loader +{ + /** + * @throws XmlException + */ + public function loadFile(string $filename): DOMDocument + { + $reporting = error_reporting(0); + $contents = file_get_contents($filename); + + error_reporting($reporting); + + if ($contents === false) { + throw new XmlException( + sprintf( + 'Could not read XML from file "%s"', + $filename, + ), + ); + } + + if (trim($contents) === '') { + throw new XmlException( + sprintf( + 'Could not parse XML from empty file "%s"', + $filename, + ), + ); + } + + return $this->load($contents); + } + + /** + * @throws XmlException + */ + public function load(string $actual): DOMDocument + { + if ($actual === '') { + throw new XmlException('Could not parse XML from empty string'); + } + + $document = new DOMDocument; + $document->preserveWhiteSpace = false; + + $internal = libxml_use_internal_errors(true); + $message = ''; + $reporting = error_reporting(0); + $loaded = $document->loadXML($actual); + + foreach (libxml_get_errors() as $error) { + $message .= "\n" . $error->message; + } + + libxml_use_internal_errors($internal); + error_reporting($reporting); + + if ($loaded === false) { + if ($message === '') { + $message = 'Could not load XML for unknown reason'; + } + + throw new XmlException($message); + } + + return $document; + } +} diff --git a/src/Util/Xml/Xml.php b/src/Util/Xml/Xml.php new file mode 100644 index 00000000000..5e30bb43b70 --- /dev/null +++ b/src/Util/Xml/Xml.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use const ENT_QUOTES; +use function htmlspecialchars; +use function mb_convert_encoding; +use function ord; +use function preg_replace; +use function strlen; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Xml +{ + /** + * Escapes a string for the use in XML documents. + * + * Any Unicode character is allowed, excluding the surrogate blocks, FFFE, + * and FFFF (not even as character reference). + * + * @see https://www.w3.org/TR/xml/#charsets + */ + public static function prepareString(string $string): string + { + return preg_replace( + '/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]/', + '', + htmlspecialchars( + self::convertToUtf8($string), + ENT_QUOTES, + ), + ); + } + + private static function convertToUtf8(string $string): string + { + if (!self::isUtf8($string)) { + $string = mb_convert_encoding($string, 'UTF-8'); + } + + return $string; + } + + private static function isUtf8(string $string): bool + { + $length = strlen($string); + + for ($i = 0; $i < $length; $i++) { + if (ord($string[$i]) < 0x80) { + $n = 0; + } elseif ((ord($string[$i]) & 0xE0) === 0xC0) { + $n = 1; + } elseif ((ord($string[$i]) & 0xF0) === 0xE0) { + $n = 2; + } elseif ((ord($string[$i]) & 0xF0) === 0xF0) { + $n = 3; + } else { + return false; + } + + for ($j = 0; $j < $n; $j++) { + if ((++$i === $length) || ((ord($string[$i]) & 0xC0) !== 0x80)) { + return false; + } + } + } + + return true; + } +} diff --git a/src/Util/XmlTestListRenderer.php b/src/Util/XmlTestListRenderer.php deleted file mode 100644 index d92e1fe2623..00000000000 --- a/src/Util/XmlTestListRenderer.php +++ /dev/null @@ -1,90 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use function get_class; -use function implode; -use function str_replace; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Runner\PhptTestCase; -use RecursiveIteratorIterator; -use XMLWriter; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class XmlTestListRenderer -{ - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function render(TestSuite $suite): string - { - $writer = new XMLWriter; - - $writer->openMemory(); - $writer->setIndent(true); - $writer->startDocument(); - $writer->startElement('tests'); - - $currentTestCase = null; - - foreach (new RecursiveIteratorIterator($suite->getIterator()) as $test) { - if ($test instanceof TestCase) { - if (get_class($test) !== $currentTestCase) { - if ($currentTestCase !== null) { - $writer->endElement(); - } - - $writer->startElement('testCaseClass'); - $writer->writeAttribute('name', get_class($test)); - - $currentTestCase = get_class($test); - } - - $writer->startElement('testCaseMethod'); - $writer->writeAttribute('name', $test->getName(false)); - $writer->writeAttribute('groups', implode(',', $test->getGroups())); - - if (!empty($test->getDataSetAsString(false))) { - $writer->writeAttribute( - 'dataSet', - str_replace( - ' with data set ', - '', - $test->getDataSetAsString(false) - ) - ); - } - - $writer->endElement(); - } elseif ($test instanceof PhptTestCase) { - if ($currentTestCase !== null) { - $writer->endElement(); - - $currentTestCase = null; - } - - $writer->startElement('phptFile'); - $writer->writeAttribute('path', $test->getName()); - $writer->endElement(); - } - } - - if ($currentTestCase !== null) { - $writer->endElement(); - } - - $writer->endElement(); - - return $writer->outputMemory(); - } -} diff --git a/tests/README.md b/tests/README.md index e725516872e..1f2f51c6c78 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,59 +1,17 @@ -# PHPUnit self-tests +# PHPUnit's Own Test Suite -This document contains the notes of projects contributors about testing the PHPUnit core and its integration with the most important dependencies. +## Test Suite Structure -## Quick start +This is the top-level directory structure of the `tests` directory: -There are two main ways to self-test PHPUnit. The first is to test _everything_ using the default configuration. Here's how to do that with pretty colors in a human-readable format: +* `tests/unit` holds tests that are "regular" PHPUnit tests (implemented using `PHPUnit\Framework\TestCase`) +* `tests/end-to-end` holds tests in the [PHPT](https://qa.php.net/phpt_details.php) format +* `tests/end-to-end/phar` holds PHAR-specific tests that are not part of the regular `end-to-end` tests +* `tests/_files` holds test fixture that is used by tests in `tests/unit` and/or `tests/end-to-end` -``` -cd /path/to/phpunit -./phpunit --testdox --colors=always --verbose -``` +## Running the Test Suite -If you want to do a very quick check health-check of most basic use cases you can use the `basic` test collection: - -``` -./phpunit --testdox --colors=always --verbose -c tests/basic/configuration.basic.xml -``` - -The `basic` suite of tests puts the core system through its paces and covers most of the basic use cases of PHPUnit including happy flows and common exceptions. - -## Running the PHPUnit self-tests in PhpStorm - -First, please configure PhpStorm in the settings: - -Languages & Frameworks > PHP > Test Frameworks: -- PHPUnit library: - - [x] Path to phpunit.phar: `phpunit` (relative to your local copy of PHPUnit) -- Test Runner: - - [x] Default configuration file: `phpunit.xml` (relative to your local copy of PHPUnit) - -Note: These configuration steps are current as of PhpStorm 2019.2. -In later versions, some settings might have been renamed or -been moved around. - -Now you can execute all tests or only some tests as needed. -Please note that there are some subfolders within the `tests/` -folder that must not be included in the test scope. This breaks -running tests by folders for the end-to-end tests and the complete -test suite. - -- To run all tests, please select "Defined in the configuration file" - as test scope. -- To run the unit tests, please select the `tests/unit/` folder - as test scope. -- You can also select individual files or folders from the unit or - end-to-end tests as test scope. - -## Structure of the self-test collection - -Note: this section will change often while `tests/` is being refactored. - -- `configuration.xml`: the global configuration file which defines the internal `unit` and `end-to-end` tests suites -- `tests/` - - `_files`: specialized helper files; input/output samples - - `basic/`: fast tests covering all basics - - `configuration.basic.xml`: configuration file tailored for the `basic` suite - - `end-to-end/`: run PHPUnit as a separate process and observe its behaviour via console messages and the filesystem - - `unit/`: unit tests for individual smallest components and integration tests of common use cases +* `./phpunit` will run all tests from `tests/unit` and `tests/end-to-end` (except the PHAR-specific tests) +* `./phpunit --testsuite unit` will run all tests from `tests/unit` +* `./phpunit --testsuite end-to-end` will run all tests from `tests/end-to-end` (except the PHAR-specific tests) +* `ant phar-snapshot run-phar-specific-tests` will build a PHAR and run the PHAR-specific tests diff --git a/tests/_files/3194.php b/tests/_files/3194.php index 7c012b48589..5240a977503 100644 --- a/tests/_files/3194.php +++ b/tests/_files/3194.php @@ -9,6 +9,7 @@ */ namespace PHPUnit\TestFixture; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; trait T3194 @@ -29,9 +30,7 @@ public function doSomething(): bool } } -/** - * @covers \PHPUnit\TestFixture\C3194 - */ +#[CoversClass(C3194::class)] final class Test3194 extends TestCase { public function testOne(): void diff --git a/tests/_files/3530.wsdl b/tests/_files/3530.wsdl deleted file mode 100644 index b94a1e0a061..00000000000 --- a/tests/_files/3530.wsdl +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/_files/AbstractMockTestClass.php b/tests/_files/AbstractMockTestClass.php deleted file mode 100644 index 0e7455928b8..00000000000 --- a/tests/_files/AbstractMockTestClass.php +++ /dev/null @@ -1,22 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture\MockObject; - -use PHPUnit\TestFixture\MockTestInterface; - -abstract class AbstractMockTestClass implements MockTestInterface -{ - abstract public function doSomething(); - - public function returnAnything() - { - return 1; - } -} diff --git a/tests/_files/AbstractVariousIterableDataProviderTest.php b/tests/_files/AbstractVariousIterableDataProviderTest.php index 486ee02f01e..7dad830f6fe 100644 --- a/tests/_files/AbstractVariousIterableDataProviderTest.php +++ b/tests/_files/AbstractVariousIterableDataProviderTest.php @@ -9,15 +9,11 @@ */ namespace PHPUnit\TestFixture; +use PHPUnit\Framework\Attributes\DataProvider; + abstract class AbstractVariousIterableDataProviderTest { - abstract public function asArrayProvider(); - - abstract public function asIteratorProvider(); - - abstract public function asTraversableProvider(); - - public function asArrayProviderInParent() + public static function asArrayProviderInParent() { return [ ['J'], @@ -26,7 +22,7 @@ public function asArrayProviderInParent() ]; } - public function asIteratorProviderInParent() + public static function asIteratorProviderInParent() { yield ['M']; @@ -35,7 +31,7 @@ public function asIteratorProviderInParent() yield ['O']; } - public function asTraversableProviderInParent() + public static function asTraversableProviderInParent() { return new WrapperIteratorAggregate([ ['P'], @@ -44,21 +40,23 @@ public function asTraversableProviderInParent() ]); } - /** - * @dataProvider asArrayProvider - * @dataProvider asIteratorProvider - * @dataProvider asTraversableProvider - */ - public function testAbstract(): void + abstract public static function asArrayProvider(); + + abstract public static function asIteratorProvider(); + + abstract public static function asTraversableProvider(); + + #[DataProvider('asArrayProvider')] + #[DataProvider('asIteratorProvider')] + #[DataProvider('asTraversableProvider')] + public function testAbstract(string $x): void { } - /** - * @dataProvider asArrayProviderInParent - * @dataProvider asIteratorProviderInParent - * @dataProvider asTraversableProviderInParent - */ - public function testInParent(): void + #[DataProvider('asArrayProviderInParent')] + #[DataProvider('asIteratorProviderInParent')] + #[DataProvider('asTraversableProviderInParent')] + public function testInParent(string $x): void { } } diff --git a/tests/_files/AlternativeSuffixTest.test.php b/tests/_files/AlternativeSuffixTest.test.php new file mode 100644 index 00000000000..6abb44ce1c6 --- /dev/null +++ b/tests/_files/AlternativeSuffixTest.test.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\TestCase; + +final class AlternativeSuffixTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } + + public function testTwo(): void + { + $this->assertTrue(true); + } + + public function testThree(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/_files/AnInterfaceWithReturnType.php b/tests/_files/AnInterfaceWithReturnType.php deleted file mode 100644 index d605f442cce..00000000000 --- a/tests/_files/AnInterfaceWithReturnType.php +++ /dev/null @@ -1,15 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -interface AnInterfaceWithReturnType -{ - public function returnAnArray(): array; -} diff --git a/tests/_files/AnotherDummySubscriber.php b/tests/_files/AnotherDummySubscriber.php new file mode 100644 index 00000000000..a3010f45763 --- /dev/null +++ b/tests/_files/AnotherDummySubscriber.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Event\Subscriber; + +interface AnotherDummySubscriber extends Subscriber +{ + public function notify(DummyEvent $event): void; +} diff --git a/tests/_files/AssertionExample.php b/tests/_files/AssertionExample.php index 56ae6d47cc0..c9c322a39d8 100644 --- a/tests/_files/AssertionExample.php +++ b/tests/_files/AssertionExample.php @@ -11,7 +11,7 @@ use function assert; -class AssertionExample +final class AssertionExample { public function doSomething(): void { diff --git a/tests/_files/AssertionExampleTest.php b/tests/_files/AssertionExampleTest.php index 11feee03bfd..625ab79ab4f 100644 --- a/tests/_files/AssertionExampleTest.php +++ b/tests/_files/AssertionExampleTest.php @@ -7,10 +7,11 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + use PHPUnit\Framework\TestCase; -use PHPUnit\TestFixture\AssertionExample; -class AssertionExampleTest extends TestCase +final class AssertionExampleTest extends TestCase { public function testOne(): void { diff --git a/tests/_files/Author.php b/tests/_files/Author.php index 193fee5865f..f2403b1aa9d 100644 --- a/tests/_files/Author.php +++ b/tests/_files/Author.php @@ -9,11 +9,10 @@ */ namespace PHPUnit\TestFixture; -class Author +final class Author { // the order of properties is important for testing the cycle! public $books = []; - private $name = ''; public function __construct($name) diff --git a/tests/_files/BackedEnumeration.php b/tests/_files/BackedEnumeration.php new file mode 100644 index 00000000000..b9356b99512 --- /dev/null +++ b/tests/_files/BackedEnumeration.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +enum BackedEnumeration: string +{ + case Test = 'test'; +} diff --git a/tests/_files/BankAccount.php b/tests/_files/BankAccount.php index 6a161e2dbae..7921321e6b4 100644 --- a/tests/_files/BankAccount.php +++ b/tests/_files/BankAccount.php @@ -9,14 +9,14 @@ */ namespace PHPUnit\TestFixture; -class BankAccount +final class BankAccount { /** * The bank account's balance. * * @var float */ - protected $balance = 0; + private $balance = 0; /** * Returns the bank account's balance. @@ -63,7 +63,7 @@ public function withdrawMoney($balance) * * @throws BankAccountException */ - protected function setBalance($balance): void + private function setBalance($balance): void { if ($balance >= 0) { $this->balance = $balance; diff --git a/tests/_files/BankAccountException.php b/tests/_files/BankAccountException.php index c51dc2f108f..316c61a2510 100644 --- a/tests/_files/BankAccountException.php +++ b/tests/_files/BankAccountException.php @@ -11,6 +11,6 @@ use RuntimeException; -class BankAccountException extends RuntimeException +final class BankAccountException extends RuntimeException { } diff --git a/tests/_files/BankAccountTest.php b/tests/_files/BankAccountTest.php index 35f54fdfd32..aec77f7961f 100644 --- a/tests/_files/BankAccountTest.php +++ b/tests/_files/BankAccountTest.php @@ -9,22 +9,16 @@ */ namespace PHPUnit\TestFixture; +use PHPUnit\Framework\Attributes\CoversFunction; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; -class BankAccountTest extends TestCase +#[CoversFunction(BankAccount::class)] +final class BankAccountTest extends TestCase { - protected $ba; - - protected function setUp(): void - { - $this->ba = new BankAccount; - } - - /** - * @covers BankAccount::getBalance - * @group balanceIsInitiallyZero - * @group specification - */ + #[Group('balanceIsInitiallyZero')] + #[Group('specification')] + #[Group('1234')] public function testBalanceIsInitiallyZero(): void { /* @Given a fresh bank account */ @@ -37,17 +31,16 @@ public function testBalanceIsInitiallyZero(): void $this->assertEquals(0, $balance); } - /** - * @covers BankAccount::withdrawMoney - * @group balanceCannotBecomeNegative - * @group specification - */ + #[Group('balanceCannotBecomeNegative')] + #[Group('specification')] public function testBalanceCannotBecomeNegative(): void { + $ba = new BankAccount; + try { - $this->ba->withdrawMoney(1); - } catch (BankAccountException $e) { - $this->assertEquals(0, $this->ba->getBalance()); + $ba->withdrawMoney(1); + } catch (BankAccountException) { + $this->assertEquals(0, $ba->getBalance()); return; } @@ -55,38 +48,20 @@ public function testBalanceCannotBecomeNegative(): void $this->fail(); } - /** - * @covers BankAccount::depositMoney - * @group balanceCannotBecomeNegative - * @group specification - */ + #[Group('balanceCannotBecomeNegative')] + #[Group('specification')] public function testBalanceCannotBecomeNegative2(): void { + $ba = new BankAccount; + try { - $this->ba->depositMoney(-1); - } catch (BankAccountException $e) { - $this->assertEquals(0, $this->ba->getBalance()); + $ba->depositMoney(-1); + } catch (BankAccountException) { + $this->assertEquals(0, $ba->getBalance()); return; } $this->fail(); } - - /* - * @covers BankAccount::getBalance - * @covers BankAccount::depositMoney - * @covers BankAccount::withdrawMoney - * @group balanceCannotBecomeNegative - */ - /* - public function testDepositingAndWithdrawingMoneyWorks() - { - $this->assertEquals(0, $this->ba->getBalance()); - $this->ba->depositMoney(1); - $this->assertEquals(1, $this->ba->getBalance()); - $this->ba->withdrawMoney(1); - $this->assertEquals(0, $this->ba->getBalance()); - } - */ } diff --git a/tests/_files/BankAccountTest.test.php b/tests/_files/BankAccountTest.test.php deleted file mode 100644 index 8e19d320403..00000000000 --- a/tests/_files/BankAccountTest.test.php +++ /dev/null @@ -1,88 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -/** - * Tests for the BankAccount class. - */ -class BankAccountWithCustomExtensionTest extends TestCase -{ - protected $ba; - - protected function setUp(): void - { - $this->ba = new BankAccount; - } - - /** - * @covers BankAccount::getBalance - * @group balanceIsInitiallyZero - * @group specification - */ - public function testBalanceIsInitiallyZero(): void - { - $this->assertEquals(0, $this->ba->getBalance()); - } - - /** - * @covers BankAccount::withdrawMoney - * @group balanceCannotBecomeNegative - * @group specification - */ - public function testBalanceCannotBecomeNegative(): void - { - try { - $this->ba->withdrawMoney(1); - } catch (BankAccountException $e) { - $this->assertEquals(0, $this->ba->getBalance()); - - return; - } - - $this->fail(); - } - - /** - * @covers BankAccount::depositMoney - * @group balanceCannotBecomeNegative - * @group specification - */ - public function testBalanceCannotBecomeNegative2(): void - { - try { - $this->ba->depositMoney(-1); - } catch (BankAccountException $e) { - $this->assertEquals(0, $this->ba->getBalance()); - - return; - } - - $this->fail(); - } - - /* - * @covers BankAccount::getBalance - * @covers BankAccount::depositMoney - * @covers BankAccount::withdrawMoney - * @group balanceCannotBecomeNegative - */ - /* - public function testDepositingAndWithdrawingMoneyWorks() - { - $this->assertEquals(0, $this->ba->getBalance()); - $this->ba->depositMoney(1); - $this->assertEquals(1, $this->ba->getBalance()); - $this->ba->withdrawMoney(1); - $this->assertEquals(0, $this->ba->getBalance()); - } - */ -} diff --git a/tests/_files/BankAccountTest2.php b/tests/_files/BankAccountTest2.php deleted file mode 100644 index 4d878cd47dd..00000000000 --- a/tests/_files/BankAccountTest2.php +++ /dev/null @@ -1,57 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -class BankAccountTest extends TestCase -{ - private $ba; - - protected function setUp(): void - { - $this->ba = new BankAccount; - } - - public function testBalanceIsInitiallyZero(): void - { - $ba = new BankAccount; - - $balance = $ba->getBalance(); - - $this->assertEquals(0, $balance); - } - - public function testBalanceCannotBecomeNegative(): void - { - try { - $this->ba->withdrawMoney(1); - } catch (BankAccountException $e) { - $this->assertEquals(0, $this->ba->getBalance()); - - return; - } - - $this->fail(); - } - - public function testBalanceCannotBecomeNegative2(): void - { - try { - $this->ba->depositMoney(-1); - } catch (BankAccountException $e) { - $this->assertEquals(0, $this->ba->getBalance()); - - return; - } - - $this->fail(); - } -} diff --git a/tests/_files/BeforeAndAfterTest.php b/tests/_files/BeforeAndAfterTest.php deleted file mode 100644 index 4fa69554706..00000000000 --- a/tests/_files/BeforeAndAfterTest.php +++ /dev/null @@ -1,49 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -class BeforeAndAfterTest extends TestCase -{ - public static $beforeWasRun; - - public static $afterWasRun; - - public static function resetProperties(): void - { - self::$beforeWasRun = 0; - self::$afterWasRun = 0; - } - - /** - * @before - */ - public function initialSetup(): void - { - self::$beforeWasRun++; - } - - /** - * @after - */ - public function finalTeardown(): void - { - self::$afterWasRun++; - } - - public function test1(): void - { - } - - public function test2(): void - { - } -} diff --git a/tests/_files/BeforeClassAndAfterClassTest.php b/tests/_files/BeforeClassAndAfterClassTest.php deleted file mode 100644 index 7cc0ee30784..00000000000 --- a/tests/_files/BeforeClassAndAfterClassTest.php +++ /dev/null @@ -1,49 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -class BeforeClassAndAfterClassTest extends TestCase -{ - public static $beforeClassWasRun = 0; - - public static $afterClassWasRun = 0; - - public static function resetProperties(): void - { - self::$beforeClassWasRun = 0; - self::$afterClassWasRun = 0; - } - - /** - * @beforeClass - */ - public static function initialClassSetup(): void - { - self::$beforeClassWasRun++; - } - - /** - * @afterClass - */ - public static function finalClassTeardown(): void - { - self::$afterClassWasRun++; - } - - public function test1(): void - { - } - - public function test2(): void - { - } -} diff --git a/tests/_files/BeforeClassWithOnlyDataProviderTest.php b/tests/_files/BeforeClassWithOnlyDataProviderTest.php deleted file mode 100644 index 6e59af78e70..00000000000 --- a/tests/_files/BeforeClassWithOnlyDataProviderTest.php +++ /dev/null @@ -1,50 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -class BeforeClassWithOnlyDataProviderTest extends \PHPUnit\Framework\TestCase -{ - public static $setUpBeforeClassWasCalled; - - public static $beforeClassWasCalled; - - public static function resetProperties(): void - { - self::$setUpBeforeClassWasCalled = false; - self::$beforeClassWasCalled = false; - } - - /** - * @beforeClass - */ - public static function someAnnotatedSetupMethod(): void - { - self::$beforeClassWasCalled = true; - } - - public static function setUpBeforeClass(): void - { - self::$setUpBeforeClassWasCalled = true; - } - - public function dummyProvider() - { - return [[1]]; - } - - /** - * @dataProvider dummyProvider - * delete annotation to fail test case - */ - public function testDummy(): void - { - $this->assertFalse(false); - } -} diff --git a/tests/_files/Book.php b/tests/_files/Book.php index a1d779640da..57d8a382ded 100644 --- a/tests/_files/Book.php +++ b/tests/_files/Book.php @@ -9,7 +9,7 @@ */ namespace PHPUnit\TestFixture; -class Book +final class Book { // the order of properties is important for testing the cycle! public $author; diff --git a/tests/_files/BooleanConstraint.php b/tests/_files/BooleanConstraint.php deleted file mode 100644 index 683cd0005be..00000000000 --- a/tests/_files/BooleanConstraint.php +++ /dev/null @@ -1,43 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\Constraint\Constraint; - -final class BooleanConstraint extends Constraint -{ - /** - * @var bool - */ - private $matches; - - public static function fromBool(bool $matches): self - { - $instance = new self; - - $instance->matches = $matches; - - return $instance; - } - - public function matches($other): bool - { - return $this->matches; - } - - public function toString(): string - { - if ($this->matches) { - return 'true'; - } - - return 'false'; - } -} diff --git a/tests/_files/ChangeCurrentWorkingDirectoryTest.php b/tests/_files/ChangeCurrentWorkingDirectoryTest.php deleted file mode 100644 index 329890eeeb7..00000000000 --- a/tests/_files/ChangeCurrentWorkingDirectoryTest.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use function chdir; -use PHPUnit\Framework\TestCase; - -class ChangeCurrentWorkingDirectoryTest extends TestCase -{ - public function testSomethingThatChangesTheCwd(): void - { - chdir('../'); - - $this->assertTrue(true); - } -} diff --git a/tests/_files/ClassThatImplementsSerializable.php b/tests/_files/ClassThatImplementsSerializable.php deleted file mode 100644 index 6f92e44b4bc..00000000000 --- a/tests/_files/ClassThatImplementsSerializable.php +++ /dev/null @@ -1,29 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use function get_object_vars; -use function unserialize; -use Serializable; - -class ClassThatImplementsSerializable implements Serializable -{ - public function serialize() - { - return get_object_vars($this); - } - - public function unserialize($serialized): void - { - foreach (unserialize($serialized) as $key => $value) { - $this->{$key} = $value; - } - } -} diff --git a/tests/_files/ClassWithAllPossibleReturnTypes.php b/tests/_files/ClassWithAllPossibleReturnTypes.php deleted file mode 100644 index 71f1b6af5b9..00000000000 --- a/tests/_files/ClassWithAllPossibleReturnTypes.php +++ /dev/null @@ -1,72 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace PHPUnit\TestFixture; -use ArrayIterator; -use Exception; -use Generator; -use stdClass; -use Traversable; - -class ClassWithAllPossibleReturnTypes -{ - public function methodWithNoReturnTypeDeclaration() - { - } - - public function methodWithVoidReturnTypeDeclaration(): void - { - } - - public function methodWithStringReturnTypeDeclaration(): string - { - return 'string'; - } - - public function methodWithFloatReturnTypeDeclaration(): float - { - return 1.0; - } - - public function methodWithIntReturnTypeDeclaration(): int - { - return 1; - } - - public function methodWithBoolReturnTypeDeclaration(): bool - { - return true; - } - - public function methodWithArrayReturnTypeDeclaration(): array - { - return ['string']; - } - - public function methodWithTraversableReturnTypeDeclaration(): Traversable - { - return new ArrayIterator(['string']); - } - - public function methodWithGeneratorReturnTypeDeclaration(): Generator - { - yield 1; - } - - public function methodWithObjectReturnTypeDeclaration(): object - { - return new Exception; - } - - public function methodWithClassReturnTypeDeclaration(): stdClass - { - return new stdClass; - } -} diff --git a/tests/_files/ClassWithNonPublicAttributes.php b/tests/_files/ClassWithNonPublicAttributes.php index 89faff272dc..eeb36548e12 100644 --- a/tests/_files/ClassWithNonPublicAttributes.php +++ b/tests/_files/ClassWithNonPublicAttributes.php @@ -9,27 +9,17 @@ */ namespace PHPUnit\TestFixture; -class ClassWithNonPublicAttributes extends ParentClassWithProtectedAttributes +final class ClassWithNonPublicAttributes extends ParentClassWithProtectedAttributes { - public static $publicStaticAttribute = 'foo'; - + public static $publicStaticAttribute = 'foo'; protected static $protectedStaticAttribute = 'bar'; - - protected static $privateStaticAttribute = 'baz'; - - public $publicAttribute = 'foo'; - - public $foo = 1; - - public $bar = 2; - - public $publicArray = ['foo']; - - protected $protectedAttribute = 'bar'; - - protected $privateAttribute = 'baz'; - - protected $protectedArray = ['bar']; - - protected $privateArray = ['baz']; + protected static $privateStaticAttribute = 'baz'; + public $publicAttribute = 'foo'; + public $foo = 1; + public $bar = 2; + public $publicArray = ['foo']; + protected $protectedAttribute = 'bar'; + protected $privateAttribute = 'baz'; + protected $protectedArray = ['bar']; + protected $privateArray = ['baz']; } diff --git a/tests/_files/ClassWithScalarTypeDeclarations.php b/tests/_files/ClassWithScalarTypeDeclarations.php index d18c85606ad..d334cb5d6fc 100644 --- a/tests/_files/ClassWithScalarTypeDeclarations.php +++ b/tests/_files/ClassWithScalarTypeDeclarations.php @@ -9,7 +9,7 @@ */ namespace PHPUnit\TestFixture; -class ClassWithScalarTypeDeclarations +final class ClassWithScalarTypeDeclarations { public function foo(string $string, int $int): void { diff --git a/tests/_files/ClassWithSelfTypeHint.php b/tests/_files/ClassWithSelfTypeHint.php deleted file mode 100644 index 8a69e07519a..00000000000 --- a/tests/_files/ClassWithSelfTypeHint.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -class ClassWithSelfTypeHint -{ - public function foo(self $foo): void - { - } -} diff --git a/tests/_files/ClassWithStaticMethod.php b/tests/_files/ClassWithStaticMethod.php deleted file mode 100644 index d636e773b27..00000000000 --- a/tests/_files/ClassWithStaticMethod.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -class ClassWithStaticMethod -{ - public static function staticMethod(): void - { - } -} diff --git a/tests/_files/ClassWithToString.php b/tests/_files/ClassWithToString.php index bce508a7a17..4195b190583 100644 --- a/tests/_files/ClassWithToString.php +++ b/tests/_files/ClassWithToString.php @@ -9,9 +9,11 @@ */ namespace PHPUnit\TestFixture; -class ClassWithToString +use Stringable; + +final class ClassWithToString implements Stringable { - public function __toString() + public function __toString(): string { return 'string representation'; } diff --git a/tests/_files/ClassWithUnionReturnTypes.php b/tests/_files/ClassWithUnionReturnTypes.php deleted file mode 100644 index fd94dc0884a..00000000000 --- a/tests/_files/ClassWithUnionReturnTypes.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace PHPUnit\TestFixture; - -class ClassWithUnionReturnTypes -{ - public function returnsBoolOrInt(): bool|int - { - } - - public function returnsBoolOrIntOrNull(): bool|int|null - { - } - - public function returnsMixed(): mixed - { - } -} diff --git a/tests/_files/ClassWithVariadicArgumentMethod.php b/tests/_files/ClassWithVariadicArgumentMethod.php deleted file mode 100644 index 9000926d83c..00000000000 --- a/tests/_files/ClassWithVariadicArgumentMethod.php +++ /dev/null @@ -1,18 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -class ClassWithVariadicArgumentMethod -{ - public function foo(...$args) - { - return $args; - } -} diff --git a/tests/_files/ComparisonFailureTest.php b/tests/_files/ComparisonFailureTest.php new file mode 100644 index 00000000000..02542f3b7a9 --- /dev/null +++ b/tests/_files/ComparisonFailureTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\TestCase; + +final class ComparisonFailureTest extends TestCase +{ + public function testOne(): void + { + $this->assertEquals(true, false); + } +} diff --git a/tests/_files/ConcreteTest.my.php b/tests/_files/ConcreteTest.my.php index 3baed6ada88..87c0b7e4de1 100644 --- a/tests/_files/ConcreteTest.my.php +++ b/tests/_files/ConcreteTest.my.php @@ -9,7 +9,7 @@ */ namespace PHPUnit\TestFixture; -class ConcreteWithMyCustomExtensionTest extends AbstractTest +final class ConcreteTest extends AbstractTest { public function testTwo(): void { diff --git a/tests/_files/ConcreteTest.php b/tests/_files/ConcreteTest.php index 8ee63f5ca73..fa939c2af25 100644 --- a/tests/_files/ConcreteTest.php +++ b/tests/_files/ConcreteTest.php @@ -9,7 +9,7 @@ */ use PHPUnit\TestFixture\AbstractTest; -class ConcreteTest extends AbstractTest +final class ConcreteTest extends AbstractTest { public function testTwo(): void { diff --git a/tests/_files/CountConstraint.php b/tests/_files/CountConstraint.php deleted file mode 100644 index 4b12c7f0c16..00000000000 --- a/tests/_files/CountConstraint.php +++ /dev/null @@ -1,48 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use function sprintf; -use PHPUnit\Framework\Constraint\Constraint; - -final class CountConstraint extends Constraint -{ - /** - * @var int - */ - private $count; - - public static function fromCount(int $count): self - { - $instance = new self; - - $instance->count = $count; - - return $instance; - } - - public function matches($other): bool - { - return true; - } - - public function toString(): string - { - return sprintf( - 'is accepted by %s', - self::class - ); - } - - public function count(): int - { - return $this->count; - } -} diff --git a/tests/_files/CoverageClassExtendedTest.php b/tests/_files/CoverageClassExtendedTest.php deleted file mode 100644 index bca3f541a87..00000000000 --- a/tests/_files/CoverageClassExtendedTest.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use CoveredClass; -use PHPUnit\Framework\TestCase; - -class CoverageClassExtendedTest extends TestCase -{ - /** - * @covers \PHPUnit\TestFixture\CoveredClass - */ - public function testSomething(): void - { - $o = new CoveredClass; - $o->publicMethod(); - } -} diff --git a/tests/_files/CoverageClassNothingTest.php b/tests/_files/CoverageClassNothingTest.php deleted file mode 100644 index 3942046f1a2..00000000000 --- a/tests/_files/CoverageClassNothingTest.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use CoveredClass; -use PHPUnit\Framework\TestCase; - -/** - * @coversNothing - */ -class CoverageClassNothingTest extends TestCase -{ - public function testSomething(): void - { - $o = new CoveredClass; - - $o->publicMethod(); - } -} diff --git a/tests/_files/CoverageClassTest.php b/tests/_files/CoverageClassTest.php deleted file mode 100644 index 5557a48ff95..00000000000 --- a/tests/_files/CoverageClassTest.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use CoveredClass; -use PHPUnit\Framework\TestCase; - -class CoverageClassTest extends TestCase -{ - /** - * @covers \PHPUnit\TestFixture\CoveredClass - */ - public function testSomething(): void - { - $o = new CoveredClass; - - $o->publicMethod(); - } -} diff --git a/tests/_files/CoverageClassWithoutAnnotationsTest.php b/tests/_files/CoverageClassWithoutAnnotationsTest.php deleted file mode 100644 index 719bbb4e11a..00000000000 --- a/tests/_files/CoverageClassWithoutAnnotationsTest.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use CoveredClass; -use PHPUnit\Framework\TestCase; - -class CoverageClassWithoutAnnotationsTest extends TestCase -{ - public function testSomething(): void - { - $o = new CoveredClass; - - $o->publicMethod(); - } -} diff --git a/tests/_files/CoverageCoversOverridesCoversNothingTest.php b/tests/_files/CoverageCoversOverridesCoversNothingTest.php deleted file mode 100644 index 1da6d51a9fc..00000000000 --- a/tests/_files/CoverageCoversOverridesCoversNothingTest.php +++ /dev/null @@ -1,29 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use CoveredClass; -use PHPUnit\Framework\TestCase; - -/** - * @coversNothing - */ -class CoverageCoversOverridesCoversNothingTest extends TestCase -{ - /** - * @covers \PHPUnit\TestFixture\CoveredClass::publicMethod - */ - public function testSomething(): void - { - $o = new CoveredClass; - - $o->publicMethod(); - } -} diff --git a/tests/_files/CoverageFunctionParenthesesTest.php b/tests/_files/CoverageFunctionParenthesesTest.php deleted file mode 100644 index 0c2f659520d..00000000000 --- a/tests/_files/CoverageFunctionParenthesesTest.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -class CoverageFunctionParenthesesTest extends TestCase -{ - /** - * @covers ::globalFunction() - */ - public function testSomething(): void - { - globalFunction(); - } -} diff --git a/tests/_files/CoverageFunctionParenthesesWhitespaceTest.php b/tests/_files/CoverageFunctionParenthesesWhitespaceTest.php deleted file mode 100644 index d6c3e0cbd8d..00000000000 --- a/tests/_files/CoverageFunctionParenthesesWhitespaceTest.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -class CoverageFunctionParenthesesWhitespaceTest extends TestCase -{ - /** - * @covers ::globalFunction ( ) - */ - public function testSomething(): void - { - globalFunction(); - } -} diff --git a/tests/_files/CoverageFunctionTest.php b/tests/_files/CoverageFunctionTest.php index ddf86e81bdc..05ad044baa2 100644 --- a/tests/_files/CoverageFunctionTest.php +++ b/tests/_files/CoverageFunctionTest.php @@ -9,13 +9,14 @@ */ namespace PHPUnit\TestFixture; +use PHPUnit\Framework\Attributes\CoversFunction; +use PHPUnit\Framework\Attributes\UsesFunction; use PHPUnit\Framework\TestCase; -class CoverageFunctionTest extends TestCase +#[CoversFunction('globalFunction')] +#[UsesFunction('globalFunction')] +final class CoverageFunctionTest extends TestCase { - /** - * @covers ::globalFunction - */ public function testSomething(): void { globalFunction(); diff --git a/tests/_files/CoverageMethodNothingCoversMethod.php b/tests/_files/CoverageMethodNothingCoversMethod.php deleted file mode 100644 index 246c5bc295a..00000000000 --- a/tests/_files/CoverageMethodNothingCoversMethod.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use CoveredClass; -use PHPUnit\Framework\TestCase; - -class CoverageMethodNothingCoversMethod extends TestCase -{ - /** - * @covers \PHPUnit\TestFixture\CoveredClass::publicMethod - * @coversNothing - */ - public function testSomething(): void - { - $o = new CoveredClass; - - $o->publicMethod(); - } -} diff --git a/tests/_files/CoverageMethodNothingTest.php b/tests/_files/CoverageMethodNothingTest.php deleted file mode 100644 index 6d5ee22221c..00000000000 --- a/tests/_files/CoverageMethodNothingTest.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use CoveredClass; -use PHPUnit\Framework\TestCase; - -class CoverageMethodNothingTest extends TestCase -{ - /** - * @coversNothing - */ - public function testSomething(): void - { - $o = new CoveredClass; - - $o->publicMethod(); - } -} diff --git a/tests/_files/CoverageMethodOneLineAnnotationTest.php b/tests/_files/CoverageMethodOneLineAnnotationTest.php deleted file mode 100644 index f7ed576a2a9..00000000000 --- a/tests/_files/CoverageMethodOneLineAnnotationTest.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use CoveredClass; -use PHPUnit\Framework\TestCase; - -class CoverageMethodOneLineAnnotationTest extends TestCase -{ - /** @covers \PHPUnit\TestFixture\CoveredClass::publicMethod */ - public function testSomething(): void - { - $o = new CoveredClass; - - $o->publicMethod(); - } -} diff --git a/tests/_files/CoverageMethodParenthesesTest.php b/tests/_files/CoverageMethodParenthesesTest.php deleted file mode 100644 index 02afc124c23..00000000000 --- a/tests/_files/CoverageMethodParenthesesTest.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use CoveredClass; -use PHPUnit\Framework\TestCase; - -class CoverageMethodParenthesesTest extends TestCase -{ - /** - * @covers \PHPUnit\TestFixture\CoveredClass::publicMethod() - */ - public function testSomething(): void - { - $o = new CoveredClass; - - $o->publicMethod(); - } -} diff --git a/tests/_files/CoverageMethodParenthesesWhitespaceTest.php b/tests/_files/CoverageMethodParenthesesWhitespaceTest.php deleted file mode 100644 index b2b83cc279e..00000000000 --- a/tests/_files/CoverageMethodParenthesesWhitespaceTest.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use CoveredClass; -use PHPUnit\Framework\TestCase; - -class CoverageMethodParenthesesWhitespaceTest extends TestCase -{ - /** - * @covers \PHPUnit\TestFixture\CoveredClass::publicMethod ( ) - */ - public function testSomething(): void - { - $o = new CoveredClass; - - $o->publicMethod(); - } -} diff --git a/tests/_files/CoverageMethodTest.php b/tests/_files/CoverageMethodTest.php index 68d24038858..52c0d169776 100644 --- a/tests/_files/CoverageMethodTest.php +++ b/tests/_files/CoverageMethodTest.php @@ -9,14 +9,14 @@ */ namespace PHPUnit\TestFixture; -use CoveredClass; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\UsesMethod; use PHPUnit\Framework\TestCase; -class CoverageMethodTest extends TestCase +#[CoversMethod(CoveredClass::class, 'publicMethod')] +#[UsesMethod(CoveredClass::class, 'publicMethod')] +final class CoverageMethodTest extends TestCase { - /** - * @covers \PHPUnit\TestFixture\CoveredClass::publicMethod - */ public function testSomething(): void { $o = new CoveredClass; diff --git a/tests/_files/CoverageNamespacedFunctionTest.php b/tests/_files/CoverageNamespacedFunctionTest.php deleted file mode 100644 index 3bf9892456a..00000000000 --- a/tests/_files/CoverageNamespacedFunctionTest.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -class CoverageNamespacedFunctionTest extends TestCase -{ - /** - * @covers \PHPUnit\TestFixture\func() - */ - public function testFunc(): void - { - func(); - } -} diff --git a/tests/_files/CoverageNoneTest.php b/tests/_files/CoverageNoneTest.php index d0306b932e9..bbebec59a86 100644 --- a/tests/_files/CoverageNoneTest.php +++ b/tests/_files/CoverageNoneTest.php @@ -9,10 +9,9 @@ */ namespace PHPUnit\TestFixture; -use CoveredClass; use PHPUnit\Framework\TestCase; -class CoverageNoneTest extends TestCase +final class CoverageNoneTest extends TestCase { public function testSomething(): void { diff --git a/tests/_files/CoverageNotPrivateTest.php b/tests/_files/CoverageNotPrivateTest.php deleted file mode 100644 index 90e8ba29019..00000000000 --- a/tests/_files/CoverageNotPrivateTest.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use CoveredClass; -use PHPUnit\Framework\TestCase; - -class CoverageNotPrivateTest extends TestCase -{ - /** - * @covers \PHPUnit\TestFixture\CoveredClass:: - */ - public function testSomething(): void - { - $o = new CoveredClass; - - $o->publicMethod(); - } -} diff --git a/tests/_files/CoverageNotProtectedTest.php b/tests/_files/CoverageNotProtectedTest.php deleted file mode 100644 index 4101cef07b7..00000000000 --- a/tests/_files/CoverageNotProtectedTest.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use CoveredClass; -use PHPUnit\Framework\TestCase; - -class CoverageNotProtectedTest extends TestCase -{ - /** - * @covers \PHPUnit\TestFixture\CoveredClass:: - */ - public function testSomething(): void - { - $o = new CoveredClass; - - $o->publicMethod(); - } -} diff --git a/tests/_files/CoverageNotPublicTest.php b/tests/_files/CoverageNotPublicTest.php deleted file mode 100644 index fb6766447f6..00000000000 --- a/tests/_files/CoverageNotPublicTest.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use CoveredClass; -use PHPUnit\Framework\TestCase; - -class CoverageNotPublicTest extends TestCase -{ - /** - * @covers \PHPUnit\TestFixture\CoveredClass:: - */ - public function testSomething(): void - { - $o = new CoveredClass; - - $o->publicMethod(); - } -} diff --git a/tests/_files/CoveragePrivateTest.php b/tests/_files/CoveragePrivateTest.php deleted file mode 100644 index 2b6fa8e0bf9..00000000000 --- a/tests/_files/CoveragePrivateTest.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use CoveredClass; -use PHPUnit\Framework\TestCase; - -class CoveragePrivateTest extends TestCase -{ - /** - * @covers \PHPUnit\TestFixture\CoveredClass:: - */ - public function testSomething(): void - { - $o = new CoveredClass; - - $o->publicMethod(); - } -} diff --git a/tests/_files/CoverageProtectedTest.php b/tests/_files/CoverageProtectedTest.php deleted file mode 100644 index d0dda4bdb0d..00000000000 --- a/tests/_files/CoverageProtectedTest.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use CoveredClass; -use PHPUnit\Framework\TestCase; - -class CoverageProtectedTest extends TestCase -{ - /** - * @covers \PHPUnit\TestFixture\CoveredClass:: - */ - public function testSomething(): void - { - $o = new CoveredClass; - - $o->publicMethod(); - } -} diff --git a/tests/_files/CoveragePublicTest.php b/tests/_files/CoveragePublicTest.php deleted file mode 100644 index 7b9761de6cb..00000000000 --- a/tests/_files/CoveragePublicTest.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use CoveredClass; -use PHPUnit\Framework\TestCase; - -class CoveragePublicTest extends TestCase -{ - /** - * @covers \PHPUnit\TestFixture\CoveredClass:: - */ - public function testSomething(): void - { - $o = new CoveredClass; - - $o->publicMethod(); - } -} diff --git a/tests/_files/CoverageTraitTest.php b/tests/_files/CoverageTraitTest.php new file mode 100644 index 00000000000..37e26a38135 --- /dev/null +++ b/tests/_files/CoverageTraitTest.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\CoversTrait; +use PHPUnit\Framework\Attributes\UsesTrait; +use PHPUnit\Framework\TestCase; + +#[CoversTrait(CoveredTrait::class)] +#[UsesTrait(CoveredTrait::class)] +final class CoverageTraitTest extends TestCase +{ + public function testSomething(): void + { + } +} diff --git a/tests/_files/CoverageTwoDefaultClassAnnotations.php b/tests/_files/CoverageTwoDefaultClassAnnotations.php deleted file mode 100644 index 2507876c6c4..00000000000 --- a/tests/_files/CoverageTwoDefaultClassAnnotations.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -/** - * @coversDefaultClass \NamespaceOne - * @coversDefaultClass \AnotherDefault\Name\Space\Does\Not\Work - */ -class CoverageTwoDefaultClassAnnotations -{ - /** - * @covers \PHPUnit\TestFixture\CoveredClass:: - */ - public function testSomething(): void - { - $o = new CoveredClass; - - $o->publicMethod(); - } -} diff --git a/tests/_files/CoveredClass.php b/tests/_files/CoveredClass.php index ccd30c00f6a..331a28e401a 100644 --- a/tests/_files/CoveredClass.php +++ b/tests/_files/CoveredClass.php @@ -9,24 +9,7 @@ */ namespace PHPUnit\TestFixture; -class CoveredParentClass -{ - public function publicMethod(): void - { - $this->protectedMethod(); - } - - protected function protectedMethod(): void - { - $this->privateMethod(); - } - - private function privateMethod(): void - { - } -} - -class CoveredClass extends CoveredParentClass +final class CoveredClass extends CoveredParentClass { public function publicMethod(): void { diff --git a/tests/_files/CoveredClassUsingCoveredTrait.php b/tests/_files/CoveredClassUsingCoveredTrait.php new file mode 100644 index 00000000000..151f5b47d2e --- /dev/null +++ b/tests/_files/CoveredClassUsingCoveredTrait.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +final class CoveredClassUsingCoveredTrait +{ + use CoveredTrait; + + public function publicMethod(): void + { + } + + protected function protectedMethod(): void + { + } + + private function privateMethod(): void + { + } +} diff --git a/tests/_files/CoveredClassUsingCoveredTraitTest.php b/tests/_files/CoveredClassUsingCoveredTraitTest.php new file mode 100644 index 00000000000..2ea583bfc75 --- /dev/null +++ b/tests/_files/CoveredClassUsingCoveredTraitTest.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(CoveredClassUsingCoveredTrait::class)] +#[UsesClass(CoveredClassUsingCoveredTrait::class)] +final class CoveredClassUsingCoveredTraitTest extends TestCase +{ + public function testSomething(): void + { + } +} diff --git a/tests/_files/CoveredParentClass.php b/tests/_files/CoveredParentClass.php new file mode 100644 index 00000000000..fce85ca2903 --- /dev/null +++ b/tests/_files/CoveredParentClass.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +class CoveredParentClass +{ + public function publicMethod(): void + { + $this->protectedMethod(); + } + + protected function protectedMethod(): void + { + $this->privateMethod(); + } + + private function privateMethod(): void + { + } +} diff --git a/tests/_files/CoveredTrait.php b/tests/_files/CoveredTrait.php new file mode 100644 index 00000000000..9b9a37d454c --- /dev/null +++ b/tests/_files/CoveredTrait.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +trait CoveredTrait +{ + public function m(int $x, int $y): int + { + return $x + $y; + } +} diff --git a/tests/_files/CoversClassOnClassTest.php b/tests/_files/CoversClassOnClassTest.php new file mode 100644 index 00000000000..62d63175d9f --- /dev/null +++ b/tests/_files/CoversClassOnClassTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(CoveredClass::class)] +#[UsesClass(CoveredClass::class)] +final class CoversClassOnClassTest extends TestCase +{ + public function testSomething(): void + { + $o = new CoveredClass; + + $o->publicMethod(); + } +} diff --git a/tests/_files/CoversNothingOnClassTest.php b/tests/_files/CoversNothingOnClassTest.php new file mode 100644 index 00000000000..9b361f38ada --- /dev/null +++ b/tests/_files/CoversNothingOnClassTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\CoversNothing; +use PHPUnit\Framework\TestCase; + +#[CoversNothing] +final class CoversNothingOnClassTest extends TestCase +{ + public function testSomething(): void + { + $o = new CoveredClass; + + $o->publicMethod(); + } +} diff --git a/tests/_files/CustomPrinter.php b/tests/_files/CustomPrinter.php deleted file mode 100644 index 1d89dabc2af..00000000000 --- a/tests/_files/CustomPrinter.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\TextUI\DefaultResultPrinter; - -/** @noinspection PhpUnused */ -class CustomPrinter extends DefaultResultPrinter -{ -} diff --git a/tests/_files/CwdRestoreTest.php b/tests/_files/CwdRestoreTest.php new file mode 100644 index 00000000000..d8f42809ba6 --- /dev/null +++ b/tests/_files/CwdRestoreTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use function chdir; +use function sys_get_temp_dir; +use PHPUnit\Framework\TestCase; + +final class CwdRestoreTest extends TestCase +{ + public function testChangesCwd(): void + { + chdir(sys_get_temp_dir()); + + $this->assertTrue(true); + } +} diff --git a/tests/_files/DataProviderDependencyResultTest.php b/tests/_files/DataProviderDependencyResultTest.php new file mode 100644 index 00000000000..752778b10dc --- /dev/null +++ b/tests/_files/DataProviderDependencyResultTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Depends; +use PHPUnit\Framework\TestCase; + +final class DataProviderDependencyResultTest extends TestCase +{ + public static function providerMethod(): array + { + return [ + [0, 2], + [1, 1], + ['b' => 2, 'a' => 0], + ]; + } + + #[DataProvider('providerMethod')] + #[Depends('testDependency')] + public function testAdd($a, $b, $c): void + { + $this->assertSame(2, $c); + $this->assertSame($c, $a + $b); + } + + public function testDependency(): int + { + $a = 2; + $this->assertSame(2, $a); + + return $a; + } +} diff --git a/tests/_files/DataProviderDependencyTest.php b/tests/_files/DataProviderDependencyTest.php index 13828310b65..9b14f5d97c7 100644 --- a/tests/_files/DataProviderDependencyTest.php +++ b/tests/_files/DataProviderDependencyTest.php @@ -7,27 +7,29 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -class DataProviderDependencyTest extends PHPUnit\Framework\TestCase +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Depends; +use PHPUnit\Framework\Attributes\Ticket; +use PHPUnit\Framework\TestCase; + +final class DataProviderDependencyTest extends TestCase { - public function testReference(): void + public static function provider(): array { - $this->markTestSkipped('This test should be skipped.'); - $this->assertTrue(true); + self::markTestSkipped('Any test with this data provider should be skipped.'); } - /** - * @see https://github.com/sebastianbergmann/phpunit/issues/1896 - * @depends testReference - * @dataProvider provider - */ - public function testDependency($param): void + public function testReference(): void { + $this->markTestSkipped('This test should be skipped.'); } - public function provider() + #[Depends('testReference')] + #[DataProvider('provider')] + #[Ticket('https://github.com/sebastianbergmann/phpunit/issues/1896')] + public function testDependency($param): void { - $this->markTestSkipped('Any test with this data provider should be skipped.'); - - return []; } } diff --git a/tests/_files/DataProviderDependencyVoidTest.php b/tests/_files/DataProviderDependencyVoidTest.php new file mode 100644 index 00000000000..305b8d82da2 --- /dev/null +++ b/tests/_files/DataProviderDependencyVoidTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Depends; +use PHPUnit\Framework\TestCase; + +final class DataProviderDependencyVoidTest extends TestCase +{ + public static function provider(): iterable + { + return [ + [0, 0], + [1, 'b' => 1], + ['a' => 2, 'b' => 2], + ['b' => 3, 'a' => 3], + ]; + } + + #[DataProvider('provider')] + #[Depends('testDependency')] + public function testEquality($a, $b): void + { + $this->assertSame($a, $b); + } + + public function testDependency(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/_files/DataProviderExecutionOrderTest.php b/tests/_files/DataProviderExecutionOrderTest.php new file mode 100644 index 00000000000..17f6d69ee97 --- /dev/null +++ b/tests/_files/DataProviderExecutionOrderTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class DataProviderExecutionOrderTest extends TestCase +{ + public static function dataProviderAdditions(): array + { + return [ + '1+2=3' => [1, 2, 3], + '2+1=3' => [2, 1, 3], + '1+1=3' => [1, 1, 3], + ]; + } + + public function testFirstTestThatAlwaysWorks(): void + { + $this->assertTrue(true); + } + + #[DataProvider('dataProviderAdditions')] + public function testAddNumbersWithDataProvider(int $a, int $b, int $sum): void + { + $this->assertSame($sum, $a + $b); + } + + public function testTestInTheMiddleThatAlwaysWorks(): void + { + $this->assertTrue(true); + } + + #[DataProvider('dataProviderAdditions')] + public function testAddMoreNumbersWithDataProvider(int $a, int $b, int $sum): void + { + $this->assertSame($sum, $a + $b); + } +} diff --git a/tests/_files/DataProviderFilterTest.php b/tests/_files/DataProviderFilterTest.php index bc3e6840905..1c05d2ecae1 100644 --- a/tests/_files/DataProviderFilterTest.php +++ b/tests/_files/DataProviderFilterTest.php @@ -9,11 +9,12 @@ */ namespace PHPUnit\TestFixture; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; -class DataProviderFilterTest extends TestCase +final class DataProviderFilterTest extends TestCase { - public static function truthProvider() + public static function truthProvider(): array { return [ [true], @@ -23,7 +24,7 @@ public static function truthProvider() ]; } - public static function falseProvider() + public static function falseProvider(): array { return [ 'false test' => [false], @@ -33,17 +34,13 @@ public static function falseProvider() ]; } - /** - * @dataProvider truthProvider - */ + #[DataProvider('truthProvider')] public function testTrue($truth): void { $this->assertTrue($truth); } - /** - * @dataProvider falseProvider - */ + #[DataProvider('falseProvider')] public function testFalse($false): void { $this->assertFalse($false); diff --git a/tests/_files/DataProviderIncompleteTest.php b/tests/_files/DataProviderIncompleteTest.php index 992c006baf0..82df3b64708 100644 --- a/tests/_files/DataProviderIncompleteTest.php +++ b/tests/_files/DataProviderIncompleteTest.php @@ -9,11 +9,12 @@ */ namespace PHPUnit\TestFixture; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; -class DataProviderIncompleteTest extends TestCase +final class DataProviderIncompleteTest extends TestCase { - public static function providerMethod() + public static function providerMethod(): array { return [ [0, 0, 0], @@ -21,29 +22,20 @@ public static function providerMethod() ]; } - /** - * @dataProvider incompleteTestProviderMethod - */ + public static function incompleteTestProviderMethod(): array + { + self::markTestIncomplete('incomplete'); + } + + #[DataProvider('incompleteTestProviderMethod')] public function testIncomplete($a, $b, $c): void { $this->assertTrue(true); } - /** - * @dataProvider providerMethod - */ + #[DataProvider('providerMethod')] public function testAdd($a, $b, $c): void { $this->assertEquals($c, $a + $b); } - - public function incompleteTestProviderMethod() - { - $this->markTestIncomplete('incomplete'); - - return [ - [0, 0, 0], - [0, 1, 1], - ]; - } } diff --git a/tests/_files/DataProviderIssue2833/FirstTest.php b/tests/_files/DataProviderIssue2833/FirstTest.php index 82db2e4e389..165411222e2 100644 --- a/tests/_files/DataProviderIssue2833/FirstTest.php +++ b/tests/_files/DataProviderIssue2833/FirstTest.php @@ -7,24 +7,23 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace Foo\DataProviderIssue2833; +namespace PHPUnit\TestFixture\DataProviderIssue2833; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class FirstTest extends TestCase { - /** - * @dataProvider provide - */ - public function testFirst($x): void - { - $this->assertTrue(true); - } - - public function provide() + public static function provide(): array { SecondTest::DUMMY; return [[true]]; } + + #[DataProvider('provide')] + public function testFirst($x): void + { + $this->assertTrue(true); + } } diff --git a/tests/_files/DataProviderIssue2833/SecondTest.php b/tests/_files/DataProviderIssue2833/SecondTest.php index 57e3cce1823..f0635e5d704 100644 --- a/tests/_files/DataProviderIssue2833/SecondTest.php +++ b/tests/_files/DataProviderIssue2833/SecondTest.php @@ -7,7 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace Foo\DataProviderIssue2833; +namespace PHPUnit\TestFixture\DataProviderIssue2833; use PHPUnit\Framework\TestCase; diff --git a/tests/_files/DataProviderIssue2859/phpunit.xml b/tests/_files/DataProviderIssue2859/phpunit.xml deleted file mode 100644 index 1e47e5123be..00000000000 --- a/tests/_files/DataProviderIssue2859/phpunit.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - ./tests/ - ./tests/*/ - - - diff --git a/tests/_files/DataProviderIssue2859/tests/another/TestWithDataProviderTest.php b/tests/_files/DataProviderIssue2859/tests/another/TestWithDataProviderTest.php deleted file mode 100644 index ebbe0c33f79..00000000000 --- a/tests/_files/DataProviderIssue2859/tests/another/TestWithDataProviderTest.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Foo\DataProviderIssue2859; - -use PHPUnit\Framework\TestCase; - -class TestWithDataProviderTest extends TestCase -{ - /** - * @dataProvider provide - */ - public function testFirst($x): void - { - $this->assertTrue(true); - } - - public function provide() - { - return [[true]]; - } -} diff --git a/tests/_files/DataProviderIssue2922/FirstTest.php b/tests/_files/DataProviderIssue2922/FirstTest.php deleted file mode 100644 index e5bd84e5e42..00000000000 --- a/tests/_files/DataProviderIssue2922/FirstTest.php +++ /dev/null @@ -1,32 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Foo\DataProviderIssue2922; - -use Exception; -use PHPUnit\Framework\TestCase; - -/** - * @group foo - */ -class FirstTest extends TestCase -{ - /** - * @dataProvider provide - */ - public function testFirst($x): void - { - $this->assertTrue(true); - } - - public function provide(): void - { - throw new Exception; - } -} diff --git a/tests/_files/DataProviderIssue2922/SecondTest.php b/tests/_files/DataProviderIssue2922/SecondTest.php deleted file mode 100644 index ccf67c37190..00000000000 --- a/tests/_files/DataProviderIssue2922/SecondTest.php +++ /dev/null @@ -1,21 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Foo\DataProviderIssue2922; - -use PHPUnit\Framework\TestCase; - -// the name of the class cannot match file name - if they match all is fine -class SecondHelloWorldTest extends TestCase -{ - public function testSecond(): void - { - $this->assertTrue(true); - } -} diff --git a/tests/_files/DataProviderMethodHasTestAttributeTest.php b/tests/_files/DataProviderMethodHasTestAttributeTest.php new file mode 100644 index 00000000000..29eb18121fc --- /dev/null +++ b/tests/_files/DataProviderMethodHasTestAttributeTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; + +final class DataProviderMethodHasTestAttributeTest extends TestCase +{ + #[Test] + public static function provider(): array + { + return [[true]]; + } + + #[DataProvider('provider')] + public function testOne(bool $b): void + { + $this->assertTrue($b); + } +} diff --git a/tests/_files/DataProviderMethodNameStartsWithTestTest.php b/tests/_files/DataProviderMethodNameStartsWithTestTest.php new file mode 100644 index 00000000000..922f5036f96 --- /dev/null +++ b/tests/_files/DataProviderMethodNameStartsWithTestTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class DataProviderMethodNameStartsWithTestTest extends TestCase +{ + public static function testProvider(): array + { + return [[true]]; + } + + #[DataProvider('testProvider')] + public function testOne(bool $b): void + { + $this->assertTrue($b); + } +} diff --git a/tests/_files/DataProviderNamedArgumentsTest.php b/tests/_files/DataProviderNamedArgumentsTest.php new file mode 100644 index 00000000000..facc8dded37 --- /dev/null +++ b/tests/_files/DataProviderNamedArgumentsTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class DataProviderNamedArgumentsTest extends TestCase +{ + public static function providerMethod() + { + return [ + ['a' => 1, 'b' => 2, 'c' => 3], + ['c' => 3, 'a' => 2, 'b' => 1], + ]; + } + + #[DataProvider('providerMethod')] + public function testAdd($a, $b, $c): void + { + $this->assertEquals($c, $a + $b); + } +} diff --git a/tests/_files/DataProviderRequiresPhpUnitTest.php b/tests/_files/DataProviderRequiresPhpUnitTest.php new file mode 100644 index 00000000000..03a32bd2a4f --- /dev/null +++ b/tests/_files/DataProviderRequiresPhpUnitTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use Exception; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\RequiresPhpunit; +use PHPUnit\Framework\TestCase; + +final class DataProviderRequiresPhpUnitTest extends TestCase +{ + public static function providerThatThrows(): array + { + throw new Exception('Should have been skipped.'); + } + + public static function validProvider(): array + { + return [[true], [true]]; + } + + public function invalidProvider(): array + { + return [[true], [true]]; + } + + #[RequiresPhpunit('< 10')] + #[DataProvider('invalidProvider')] + public function testWithInvalidDataProvider(bool $param): void + { + $this->assertTrue($param); + } + + #[RequiresPhpunit('>= 10')] + #[DataProvider('validProvider')] + public function testWithValidDataProvider(bool $param): void + { + $this->assertTrue($param); + } + + #[RequiresPhpunit('< 10')] + #[DataProvider('providerThatThrows')] + public function testWithDataProviderThatThrows(): void + { + } + + #[RequiresPhpunit('< 10')] + #[DataProviderExternal(self::class, 'providerThatThrows')] + public function testWithDataProviderExternalThatThrows(): void + { + } +} diff --git a/tests/_files/DataProviderSkippedTest.php b/tests/_files/DataProviderSkippedTest.php index 9eabf9ceb5b..939b100290d 100644 --- a/tests/_files/DataProviderSkippedTest.php +++ b/tests/_files/DataProviderSkippedTest.php @@ -9,9 +9,10 @@ */ namespace PHPUnit\TestFixture; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; -class DataProviderSkippedTest extends TestCase +final class DataProviderSkippedTest extends TestCase { public static function providerMethod() { @@ -21,29 +22,20 @@ public static function providerMethod() ]; } - /** - * @dataProvider skippedTestProviderMethod - */ + public static function skippedTestProviderMethod(): array + { + self::markTestSkipped('skipped'); + } + + #[DataProvider('skippedTestProviderMethod')] public function testSkipped($a, $b, $c): void { $this->assertTrue(true); } - /** - * @dataProvider providerMethod - */ + #[DataProvider('providerMethod')] public function testAdd($a, $b, $c): void { $this->assertEquals($c, $a + $b); } - - public function skippedTestProviderMethod() - { - $this->markTestSkipped('skipped'); - - return [ - [0, 0, 0], - [0, 1, 1], - ]; - } } diff --git a/tests/_files/DataProviderTest.php b/tests/_files/DataProviderTest.php index 71c71974613..7fd152cc0e4 100644 --- a/tests/_files/DataProviderTest.php +++ b/tests/_files/DataProviderTest.php @@ -9,11 +9,12 @@ */ namespace PHPUnit\TestFixture; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; -class DataProviderTest extends TestCase +final class DataProviderTest extends TestCase { - public static function providerMethod() + public static function providerMethod(): array { return [ [0, 0, 0], @@ -23,9 +24,7 @@ public static function providerMethod() ]; } - /** - * @dataProvider providerMethod - */ + #[DataProvider('providerMethod')] public function testAdd($a, $b, $c): void { $this->assertEquals($c, $a + $b); diff --git a/tests/_files/DataProviderTooManyArgumentsNoValidationTest.php b/tests/_files/DataProviderTooManyArgumentsNoValidationTest.php new file mode 100644 index 00000000000..9328b38f983 --- /dev/null +++ b/tests/_files/DataProviderTooManyArgumentsNoValidationTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class DataProviderTooManyArgumentsNoValidationTest extends TestCase +{ + public static function provider(): iterable + { + // correct case, 2nd parameter is not required + yield [true]; + + // correct case + yield [true, true]; + + // incorrect case + yield [true, true, 'Third argument, but test method only has two.']; + } + + #[DataProvider('provider', false)] + public function testMethodHavingTwoParameters(bool $x1, bool $x2 = true): void + { + $this->assertSame($x1, $x2); + } + + #[DataProvider('provider')] + public function testMethodHavingVariadicParameter(bool $x1, ...$rest): void + { + $this->assertTrue($x1); + } +} diff --git a/tests/_files/DataProviderTooManyArgumentsTest.php b/tests/_files/DataProviderTooManyArgumentsTest.php new file mode 100644 index 00000000000..7d94e1e3f02 --- /dev/null +++ b/tests/_files/DataProviderTooManyArgumentsTest.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class DataProviderTooManyArgumentsTest extends TestCase +{ + public static function provider(): iterable + { + // correct case, 2nd parameter is not required + yield [true]; + + // correct case + yield [true, true]; + + // incorrect case + yield [true, true, 'Third argument, but test method only has two.']; + } + + #[DataProvider('provider')] + public function testMethodHavingTwoParameters(bool $x1, bool $x2 = true): void + { + $this->assertSame($x1, $x2); + } + + #[DataProvider('provider')] + public function testMethodHavingVariadicParameter(bool $x1, ...$rest): void + { + $this->assertTrue($x1); + } + + public function testToNotHaveNoTests(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/_files/DataProviderWithStringKeysTest.php b/tests/_files/DataProviderWithStringKeysTest.php new file mode 100644 index 00000000000..4328f243296 --- /dev/null +++ b/tests/_files/DataProviderWithStringKeysTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class DataProviderWithStringKeysTest extends TestCase +{ + public static function providerMethod(): array + { + return [ + '0 + 0 = 0' => [0, 0, 0], + '0 + 1 = 1' => [0, 1, 1], + '1 + 1 = 3' => [1, 1, 3], + '1 + 0 = 1' => [1, 0, 1], + ]; + } + + #[DataProvider('providerMethod')] + public function testAdd($a, $b, $c): void + { + $this->assertEquals($c, $a + $b); + } +} diff --git a/tests/_files/DataproviderExecutionOrderTest.php b/tests/_files/DataproviderExecutionOrderTest.php deleted file mode 100644 index 5ed47a8bfdb..00000000000 --- a/tests/_files/DataproviderExecutionOrderTest.php +++ /dev/null @@ -1,48 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; - -class DataproviderExecutionOrderTest extends TestCase -{ - public function testFirstTestThatAlwaysWorks(): void - { - $this->assertTrue(true); - } - - /** - * @dataProvider dataproviderAdditions - */ - public function testAddNumbersWithADataprovider(int $a, int $b, int $sum): void - { - $this->assertSame($sum, $a + $b); - } - - public function testTestInTheMiddleThatAlwaysWorks(): void - { - $this->assertTrue(true); - } - - /** - * @dataProvider dataproviderAdditions - */ - public function testAddMoreNumbersWithADataprovider(int $a, int $b, int $sum): void - { - $this->assertSame($sum, $a + $b); - } - - public function dataproviderAdditions() - { - return [ - '1+2=3' => [1, 2, 3], - '2+1=3' => [2, 1, 3], - '1+1=3' => [1, 1, 3], - ]; - } -} diff --git a/tests/_files/DataproviderExecutionOrderTest_result_cache.txt b/tests/_files/DataproviderExecutionOrderTest_result_cache.txt index 452a1f85776..7bb5dbcb0b1 100644 --- a/tests/_files/DataproviderExecutionOrderTest_result_cache.txt +++ b/tests/_files/DataproviderExecutionOrderTest_result_cache.txt @@ -1 +1 @@ -C:37:"PHPUnit\Runner\DefaultTestResultCache":2119:{a:2:{s:7:"defects";a:5:{s:60:"tests/end-to-end/regression/GitHub/3396/issue-3396-test.phpt";i:3;s:88:"MultiDependencyExecutionOrderTest::testAddNumbersWithADataprovider with data set "1+1=3"";i:3;s:92:"MultiDependencyExecutionOrderTest::testAddMoreNumbersWithADataprovider with data set "1+1=3"";i:3;s:85:"DataproviderExecutionOrderTest::testAddNumbersWithADataprovider with data set "1+1=3"";i:3;s:89:"DataproviderExecutionOrderTest::testAddMoreNumbersWithADataprovider with data set "1+1=3"";i:3;}s:5:"times";a:17:{s:60:"tests/end-to-end/regression/GitHub/3396/issue-3396-test.phpt";d:0.115;s:63:"MultiDependencyExecutionOrderTest::testFirstTestThatAlwaysWorks";d:0;s:88:"MultiDependencyExecutionOrderTest::testAddNumbersWithADataprovider with data set "1+2=3"";d:0;s:88:"MultiDependencyExecutionOrderTest::testAddNumbersWithADataprovider with data set "2+1=3"";d:0;s:88:"MultiDependencyExecutionOrderTest::testAddNumbersWithADataprovider with data set "1+1=3"";d:0.003;s:69:"MultiDependencyExecutionOrderTest::testTestInTheMiddleThatAlwaysWorks";d:0;s:92:"MultiDependencyExecutionOrderTest::testAddMoreNumbersWithADataprovider with data set "1+2=3"";d:0;s:92:"MultiDependencyExecutionOrderTest::testAddMoreNumbersWithADataprovider with data set "2+1=3"";d:0;s:92:"MultiDependencyExecutionOrderTest::testAddMoreNumbersWithADataprovider with data set "1+1=3"";d:0;s:60:"DataproviderExecutionOrderTest::testFirstTestThatAlwaysWorks";d:0.002;s:85:"DataproviderExecutionOrderTest::testAddNumbersWithADataprovider with data set "1+2=3"";d:0;s:85:"DataproviderExecutionOrderTest::testAddNumbersWithADataprovider with data set "2+1=3"";d:0;s:85:"DataproviderExecutionOrderTest::testAddNumbersWithADataprovider with data set "1+1=3"";d:0.001;s:66:"DataproviderExecutionOrderTest::testTestInTheMiddleThatAlwaysWorks";d:0;s:89:"DataproviderExecutionOrderTest::testAddMoreNumbersWithADataprovider with data set "1+2=3"";d:0;s:89:"DataproviderExecutionOrderTest::testAddMoreNumbersWithADataprovider with data set "2+1=3"";d:0;s:89:"DataproviderExecutionOrderTest::testAddMoreNumbersWithADataprovider with data set "1+1=3"";d:0;}}} +{"version":2,"defects":{"tests\/end-to-end\/regression\/GitHub\/3396\/issue-3396-test.phpt":3,"MultiDependencyExecutionOrderTest::testAddNumbersWithADataprovider with data set \"1+1=3\"":3,"MultiDependencyExecutionOrderTest::testAddMoreNumbersWithADataprovider with data set \"1+1=3\"":3,"DataproviderExecutionOrderTest::testAddNumbersWithADataprovider with data set \"1+1=3\"":3,"DataproviderExecutionOrderTest::testAddMoreNumbersWithADataprovider with data set \"1+1=3\"":3},"times":{"tests\/end-to-end\/regression\/GitHub\/3396\/issue-3396-test.phpt":0.115,"MultiDependencyExecutionOrderTest::testFirstTestThatAlwaysWorks":0,"MultiDependencyExecutionOrderTest::testAddNumbersWithADataprovider with data set \"1+2=3\"":0,"MultiDependencyExecutionOrderTest::testAddNumbersWithADataprovider with data set \"2+1=3\"":0,"MultiDependencyExecutionOrderTest::testAddNumbersWithADataprovider with data set \"1+1=3\"":0.003,"MultiDependencyExecutionOrderTest::testTestInTheMiddleThatAlwaysWorks":0,"MultiDependencyExecutionOrderTest::testAddMoreNumbersWithADataprovider with data set \"1+2=3\"":0,"MultiDependencyExecutionOrderTest::testAddMoreNumbersWithADataprovider with data set \"2+1=3\"":0,"MultiDependencyExecutionOrderTest::testAddMoreNumbersWithADataprovider with data set \"1+1=3\"":0,"DataproviderExecutionOrderTest::testFirstTestThatAlwaysWorks":0.002,"DataproviderExecutionOrderTest::testAddNumbersWithADataprovider with data set \"1+2=3\"":0,"DataproviderExecutionOrderTest::testAddNumbersWithADataprovider with data set \"2+1=3\"":0,"DataproviderExecutionOrderTest::testAddNumbersWithADataprovider with data set \"1+1=3\"":0.001,"DataproviderExecutionOrderTest::testTestInTheMiddleThatAlwaysWorks":0,"DataproviderExecutionOrderTest::testAddMoreNumbersWithADataprovider with data set \"1+2=3\"":0,"DataproviderExecutionOrderTest::testAddMoreNumbersWithADataprovider with data set \"2+1=3\"":0,"DataproviderExecutionOrderTest::testAddMoreNumbersWithADataprovider with data set \"1+1=3\"":0}} \ No newline at end of file diff --git a/tests/_files/DependencyFailureTest.php b/tests/_files/DependencyFailureTest.php deleted file mode 100644 index 37736474b2f..00000000000 --- a/tests/_files/DependencyFailureTest.php +++ /dev/null @@ -1,63 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; - -class DependencyFailureTest extends TestCase -{ - public function testOne(): void - { - $this->fail(); - } - - /** - * @depends testOne - */ - public function testTwo(): void - { - $this->assertTrue(true); - } - - /** - * @depends !clone testTwo - */ - public function testThree(): void - { - $this->assertTrue(true); - } - - /** - * @depends clone testOne - */ - public function testFour(): void - { - $this->assertTrue(true); - } - - /** - * This test has been added to check the printed warnings for the user - * when a dependency simply doesn't exist. - * - * @depends doesNotExist - * - * @see https://github.com/sebastianbergmann/phpunit/issues/3517 - */ - public function testHandlesDependsAnnotationForNonexistentTests(): void - { - $this->assertTrue(true); - } - - /** - * @depends - */ - public function testHandlesDependsAnnotationWithNoMethodSpecified(): void - { - $this->assertTrue(true); - } -} diff --git a/tests/_files/DependencyInputTest.php b/tests/_files/DependencyInputTest.php index dfd090d9b27..9e27f85d2cd 100644 --- a/tests/_files/DependencyInputTest.php +++ b/tests/_files/DependencyInputTest.php @@ -7,9 +7,11 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + use PHPUnit\Framework\TestCase; -class DependencyInputTest extends TestCase +final class DependencyInputTest extends TestCase { public function testDependencyInputAsParameter(string $dependencyInput): void { diff --git a/tests/_files/DependencyOnClassTest.php b/tests/_files/DependencyOnClassTest.php index b4508a0b062..763f9ce1f31 100644 --- a/tests/_files/DependencyOnClassTest.php +++ b/tests/_files/DependencyOnClassTest.php @@ -7,29 +7,20 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\DependsOnClass; use PHPUnit\Framework\TestCase; -class DependencyOnClassTest extends TestCase +final class DependencyOnClassTest extends TestCase { - /** - * Guard support for using annotations to depend on a whole successful TestSuite. - * - * @depends DependencySuccessTest::class - * - * @see https://github.com/sebastianbergmann/phpunit/issues/3519 - */ + #[DependsOnClass(DependencySuccessTest::class)] public function testThatDependsOnASuccessfulClass(): void { $this->assertTrue(true); } - /** - * Guard support for using annotations to depend on a whole failing TestSuite. - * - * @depends DependencyFailureTest::class - * - * @see https://github.com/sebastianbergmann/phpunit/issues/3519 - */ + #[DependsOnClass(DependencyFailureTest::class)] public function testThatDependsOnAFailingClass(): void { $this->assertTrue(true); diff --git a/tests/_files/DependencySuccessTest.php b/tests/_files/DependencySuccessTest.php deleted file mode 100644 index 1f34583a27e..00000000000 --- a/tests/_files/DependencySuccessTest.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; - -class DependencySuccessTest extends TestCase -{ - public function testOne(): void - { - $this->assertTrue(true); - } - - /** - * @depends testOne - */ - public function testTwo(): void - { - $this->assertTrue(true); - } - - /** - * @depends DependencySuccessTest::testTwo - */ - public function testThree(): void - { - $this->assertTrue(true); - } -} diff --git a/tests/_files/DoNoAssertionTestCase.php b/tests/_files/DoNoAssertionTestCase.php index 282793f8bc9..0e6747cf4d7 100644 --- a/tests/_files/DoNoAssertionTestCase.php +++ b/tests/_files/DoNoAssertionTestCase.php @@ -11,7 +11,7 @@ use PHPUnit\Framework\TestCase; -class DoNoAssertionTestCase extends TestCase +final class DoNoAssertionTestCase extends TestCase { public function testNothing(): void { diff --git a/tests/_files/DoesNotPerformAssertionsButPerformingAssertionsTest.php b/tests/_files/DoesNotPerformAssertionsButPerformingAssertionsTest.php index 59d332fe972..226d7423cf4 100644 --- a/tests/_files/DoesNotPerformAssertionsButPerformingAssertionsTest.php +++ b/tests/_files/DoesNotPerformAssertionsButPerformingAssertionsTest.php @@ -9,13 +9,12 @@ */ namespace PHPUnit\TestFixture; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; use PHPUnit\Framework\TestCase; -class DoesNotPerformAssertionsButPerformingAssertionsTest extends TestCase +final class DoesNotPerformAssertionsButPerformingAssertionsTest extends TestCase { - /** - * @doesNotPerformAssertions - */ + #[DoesNotPerformAssertions] public function testFalseAndTrueAreStillFine(): void { $this->assertFalse(false); diff --git a/tests/_files/DoubleTestCase.php b/tests/_files/DoubleTestCase.php deleted file mode 100644 index f239b68cc59..00000000000 --- a/tests/_files/DoubleTestCase.php +++ /dev/null @@ -1,41 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestResult; - -class DoubleTestCase implements Test -{ - protected $testCase; - - public function __construct(TestCase $testCase) - { - $this->testCase = $testCase; - } - - public function count() - { - return 2; - } - - public function run(TestResult $result = null): TestResult - { - $result->startTest($this); - - $this->testCase->runBare(); - $this->testCase->runBare(); - - $result->endTest($this, 0); - - return $result; - } -} diff --git a/tests/_files/DummyBarTest.php b/tests/_files/DummyBarTest.php index d6aa560e3c9..a06b782ff21 100644 --- a/tests/_files/DummyBarTest.php +++ b/tests/_files/DummyBarTest.php @@ -11,7 +11,7 @@ use PHPUnit\Framework\TestCase; -class DummyBarTest extends TestCase +final class DummyBarTest extends TestCase { public function testBarEqualsBar(): void { diff --git a/tests/_files/DummyEvent.php b/tests/_files/DummyEvent.php new file mode 100644 index 00000000000..b7de581c688 --- /dev/null +++ b/tests/_files/DummyEvent.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry\Info; + +final class DummyEvent implements Event +{ + public function telemetryInfo(): Info + { + } + + public function asString(): string + { + } +} diff --git a/tests/_files/DummyException.php b/tests/_files/DummyException.php index b8fd8191bbe..fce0c0565c4 100644 --- a/tests/_files/DummyException.php +++ b/tests/_files/DummyException.php @@ -11,6 +11,6 @@ use Exception; -class DummyException extends Exception +final class DummyException extends Exception { } diff --git a/tests/_files/DummyFooTest.php b/tests/_files/DummyFooTest.php index 10e7e4a8bb0..7ef011680ee 100644 --- a/tests/_files/DummyFooTest.php +++ b/tests/_files/DummyFooTest.php @@ -11,7 +11,7 @@ use PHPUnit\Framework\TestCase; -class DummyFooTest extends TestCase +final class DummyFooTest extends TestCase { public function testFooEqualsFoo(): void { diff --git a/tests/_files/DummySubscriber.php b/tests/_files/DummySubscriber.php new file mode 100644 index 00000000000..a0ac832a7e3 --- /dev/null +++ b/tests/_files/DummySubscriber.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Event\Subscriber; + +interface DummySubscriber extends Subscriber +{ + public function notify(DummyEvent $event): void; +} diff --git a/tests/_files/DuplicateKeyDataProviderTest.php b/tests/_files/DuplicateKeyDataProviderTest.php index a599ea00bce..60568a9c732 100644 --- a/tests/_files/DuplicateKeyDataProviderTest.php +++ b/tests/_files/DuplicateKeyDataProviderTest.php @@ -9,21 +9,21 @@ */ namespace PHPUnit\TestFixture; +use Generator; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; final class DuplicateKeyDataProviderTest extends TestCase { - /** - * @dataProvider dataProvider - */ - public function test($arg): void - { - } - - public function dataProvider() + public static function dataProvider(): Generator { yield 'foo' => ['foo']; yield 'foo' => ['bar']; } + + #[DataProvider('dataProvider')] + public function test($arg): void + { + } } diff --git a/tests/_files/DuplicateKeyDataProvidersTest.php b/tests/_files/DuplicateKeyDataProvidersTest.php new file mode 100644 index 00000000000..a44c5578449 --- /dev/null +++ b/tests/_files/DuplicateKeyDataProvidersTest.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class DuplicateKeyDataProvidersTest extends TestCase +{ + public static function dataProvider1(): iterable + { + return [ + 'bar' => [1], + ]; + } + + public static function dataProvider2(): iterable + { + return [ + 'bar' => [2], + ]; + } + + #[DataProvider('dataProvider1')] + #[DataProvider('dataProvider2')] + public function test($value): void + { + $this->assertSame(2, $value); + } +} diff --git a/tests/_files/EmptyDataProviderTest.php b/tests/_files/EmptyDataProviderTest.php index bb4af0a6249..b7654d38fe6 100644 --- a/tests/_files/EmptyDataProviderTest.php +++ b/tests/_files/EmptyDataProviderTest.php @@ -9,18 +9,17 @@ */ namespace PHPUnit\TestFixture; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; -class EmptyDataProviderTest extends TestCase +final class EmptyDataProviderTest extends TestCase { - public static function providerMethod() + public static function providerMethod(): array { return []; } - /** - * @dataProvider providerMethod - */ + #[DataProvider('providerMethod')] public function testCase(): void { } diff --git a/tests/_files/EmptyTestCaseTest.php b/tests/_files/EmptyTestCaseTest.php index 56b0b144d33..dd4e98980ef 100644 --- a/tests/_files/EmptyTestCaseTest.php +++ b/tests/_files/EmptyTestCaseTest.php @@ -11,6 +11,6 @@ use PHPUnit\Framework\TestCase; -class EmptyTestCaseTest extends TestCase +final class EmptyTestCaseTest extends TestCase { } diff --git a/tests/_files/Enumeration.php b/tests/_files/Enumeration.php new file mode 100644 index 00000000000..0eb077f465c --- /dev/null +++ b/tests/_files/Enumeration.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +enum Enumeration +{ + case Test; +} diff --git a/tests/_files/EnumerationEquals/Example.php b/tests/_files/EnumerationEquals/Example.php new file mode 100644 index 00000000000..0667f81a8c4 --- /dev/null +++ b/tests/_files/EnumerationEquals/Example.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\EnumerationEquals; + +enum Example +{ + case Foo; + case Bar; +} diff --git a/tests/_files/EnumerationEquals/ExampleInt.php b/tests/_files/EnumerationEquals/ExampleInt.php new file mode 100644 index 00000000000..c5468c4b2b8 --- /dev/null +++ b/tests/_files/EnumerationEquals/ExampleInt.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\EnumerationEquals; + +enum ExampleInt: int +{ + case Foo = 0; + case Bar = 1; +} diff --git a/tests/_files/EnumerationEquals/ExampleString.php b/tests/_files/EnumerationEquals/ExampleString.php new file mode 100644 index 00000000000..e40241ccd33 --- /dev/null +++ b/tests/_files/EnumerationEquals/ExampleString.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\EnumerationEquals; + +enum ExampleString: string +{ + case Foo = 'foo'; + case Bar = 'bar'; +} diff --git a/tests/_files/ExampleTrait.php b/tests/_files/ExampleTrait.php deleted file mode 100644 index adf4e98dfb3..00000000000 --- a/tests/_files/ExampleTrait.php +++ /dev/null @@ -1,18 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -trait ExampleTrait -{ - public function ohHai() - { - return __FUNCTION__; - } -} diff --git a/tests/_files/ExceptionInAssertPostConditionsTest.php b/tests/_files/ExceptionInAssertPostConditionsTest.php deleted file mode 100644 index 7160925c1c5..00000000000 --- a/tests/_files/ExceptionInAssertPostConditionsTest.php +++ /dev/null @@ -1,53 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use Exception; -use PHPUnit\Framework\TestCase; - -class ExceptionInAssertPostConditionsTest extends TestCase -{ - public $setUp = false; - - public $assertPreConditions = false; - - public $assertPostConditions = false; - - public $tearDown = false; - - public $testSomething = false; - - protected function setUp(): void - { - $this->setUp = true; - } - - protected function tearDown(): void - { - $this->tearDown = true; - } - - public function testSomething(): void - { - $this->testSomething = true; - } - - protected function assertPreConditions(): void - { - $this->assertPreConditions = true; - } - - protected function assertPostConditions(): void - { - $this->assertPostConditions = true; - - throw new Exception; - } -} diff --git a/tests/_files/ExceptionInAssertPreConditionsTest.php b/tests/_files/ExceptionInAssertPreConditionsTest.php deleted file mode 100644 index e95ca32e8b8..00000000000 --- a/tests/_files/ExceptionInAssertPreConditionsTest.php +++ /dev/null @@ -1,53 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use Exception; -use PHPUnit\Framework\TestCase; - -class ExceptionInAssertPreConditionsTest extends TestCase -{ - public $setUp = false; - - public $assertPreConditions = false; - - public $assertPostConditions = false; - - public $tearDown = false; - - public $testSomething = false; - - protected function setUp(): void - { - $this->setUp = true; - } - - protected function tearDown(): void - { - $this->tearDown = true; - } - - public function testSomething(): void - { - $this->testSomething = true; - } - - protected function assertPreConditions(): void - { - $this->assertPreConditions = true; - - throw new Exception; - } - - protected function assertPostConditions(): void - { - $this->assertPostConditions = true; - } -} diff --git a/tests/_files/ExceptionInMockDestructor.php b/tests/_files/ExceptionInMockDestructor.php new file mode 100644 index 00000000000..6be9ff15e1a --- /dev/null +++ b/tests/_files/ExceptionInMockDestructor.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use Exception; + +class ExceptionInMockDestructor +{ + public function __destruct() + { + throw new Exception('Some exception.'); + } +} diff --git a/tests/_files/ExceptionInMockDestructorTest.php b/tests/_files/ExceptionInMockDestructorTest.php new file mode 100644 index 00000000000..10ca362150c --- /dev/null +++ b/tests/_files/ExceptionInMockDestructorTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\TestCase; + +final class ExceptionInMockDestructorTest extends TestCase +{ + public function testOne(): void + { + $mock = $this->createMock(ExceptionInMockDestructor::class); + + $this->assertInstanceOf(ExceptionInMockDestructor::class, $mock); + } +} diff --git a/tests/_files/ExceptionInSetUpTest.php b/tests/_files/ExceptionInSetUpTest.php deleted file mode 100644 index 64ecaac3b1c..00000000000 --- a/tests/_files/ExceptionInSetUpTest.php +++ /dev/null @@ -1,53 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use Exception; -use PHPUnit\Framework\TestCase; - -class ExceptionInSetUpTest extends TestCase -{ - public $setUp = false; - - public $assertPreConditions = false; - - public $assertPostConditions = false; - - public $tearDown = false; - - public $testSomething = false; - - protected function setUp(): void - { - $this->setUp = true; - - throw new Exception; - } - - protected function tearDown(): void - { - $this->tearDown = true; - } - - public function testSomething(): void - { - $this->testSomething = true; - } - - protected function assertPreConditions(): void - { - $this->assertPreConditions = true; - } - - protected function assertPostConditions(): void - { - $this->assertPostConditions = true; - } -} diff --git a/tests/_files/ExceptionInTearDownAfterClassTest.php b/tests/_files/ExceptionInTearDownAfterClassTest.php deleted file mode 100644 index 9d74c686b35..00000000000 --- a/tests/_files/ExceptionInTearDownAfterClassTest.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use Exception; -use PHPUnit\Framework\TestCase; - -class ExceptionInTearDownAfterClassTest extends TestCase -{ - public static function tearDownAfterClass(): void - { - throw new Exception('throw Exception in tearDownAfterClass()'); - } - - public function testOne(): void - { - $this->assertTrue(true); - } - - public function testTwo(): void - { - $this->assertTrue(true); - } -} diff --git a/tests/_files/ExceptionInTearDownTest.php b/tests/_files/ExceptionInTearDownTest.php deleted file mode 100644 index 5981043079f..00000000000 --- a/tests/_files/ExceptionInTearDownTest.php +++ /dev/null @@ -1,53 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use Exception; -use PHPUnit\Framework\TestCase; - -class ExceptionInTearDownTest extends TestCase -{ - public $setUp = false; - - public $assertPreConditions = false; - - public $assertPostConditions = false; - - public $tearDown = false; - - public $testSomething = false; - - protected function setUp(): void - { - $this->setUp = true; - } - - protected function tearDown(): void - { - $this->tearDown = true; - - throw new Exception('throw Exception in tearDown()'); - } - - public function testSomething(): void - { - $this->testSomething = true; - } - - protected function assertPreConditions(): void - { - $this->assertPreConditions = true; - } - - protected function assertPostConditions(): void - { - $this->assertPostConditions = true; - } -} diff --git a/tests/_files/ExceptionInTest.php b/tests/_files/ExceptionInTest.php deleted file mode 100644 index 92408d4e265..00000000000 --- a/tests/_files/ExceptionInTest.php +++ /dev/null @@ -1,53 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use Exception; -use PHPUnit\Framework\TestCase; - -class ExceptionInTest extends TestCase -{ - public $setUp = false; - - public $assertPreConditions = false; - - public $assertPostConditions = false; - - public $tearDown = false; - - public $testSomething = false; - - protected function setUp(): void - { - $this->setUp = true; - } - - protected function tearDown(): void - { - $this->tearDown = true; - } - - public function testSomething(): void - { - $this->testSomething = true; - - throw new Exception; - } - - protected function assertPreConditions(): void - { - $this->assertPreConditions = true; - } - - protected function assertPostConditions(): void - { - $this->assertPostConditions = true; - } -} diff --git a/tests/_files/ExceptionInTestDetectedInTeardown.php b/tests/_files/ExceptionInTestDetectedInTeardown.php deleted file mode 100644 index a81763f8cf4..00000000000 --- a/tests/_files/ExceptionInTestDetectedInTeardown.php +++ /dev/null @@ -1,32 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use Exception; -use PHPUnit\Framework\TestCase; - -use PHPUnit\Runner\BaseTestRunner; - -class ExceptionInTestDetectedInTeardown extends TestCase -{ - public $exceptionDetected = false; - - protected function tearDown(): void - { - if (BaseTestRunner::STATUS_ERROR == $this->getStatus()) { - $this->exceptionDetected = true; - } - } - - public function testSomething(): void - { - throw new Exception; - } -} diff --git a/tests/_files/ExceptionStackTest.php b/tests/_files/ExceptionStackTest.php index 07218d575a2..fef5e2df644 100644 --- a/tests/_files/ExceptionStackTest.php +++ b/tests/_files/ExceptionStackTest.php @@ -15,7 +15,7 @@ use PHPUnit\Framework\ExpectationFailedException; use PHPUnit\Framework\TestCase; -class ExceptionStackTest extends TestCase +final class ExceptionStackTest extends TestCase { public function testPrintingChildException(): void { diff --git a/tests/_files/ExceptionThrowingIteratorAggregate.php b/tests/_files/ExceptionThrowingIteratorAggregate.php new file mode 100644 index 00000000000..63c21102fc5 --- /dev/null +++ b/tests/_files/ExceptionThrowingIteratorAggregate.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use IteratorAggregate; +use RuntimeException; +use Traversable; + +final class ExceptionThrowingIteratorAggregate implements IteratorAggregate +{ + public function getIterator(): Traversable + { + throw new RuntimeException; + } +} diff --git a/tests/_files/ExceptionWithThrowable.php b/tests/_files/ExceptionWithThrowable.php deleted file mode 100644 index cded0d2df13..00000000000 --- a/tests/_files/ExceptionWithThrowable.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use Throwable; - -interface ExceptionWithThrowable extends Throwable -{ - public function getAdditionalInformation(); -} diff --git a/tests/_files/ExternalProphecyIntegrationTest.php b/tests/_files/ExternalProphecyIntegrationTest.php deleted file mode 100644 index 9a8aeeec17c..00000000000 --- a/tests/_files/ExternalProphecyIntegrationTest.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; - -final class ExternalProphecyIntegrationTest extends TestCase -{ - use ProphecyTrait; - - public function testOne(): void - { - $prophecy = $this->prophesize(\PHPUnit\TestFixture\AnInterface::class); - $prophecy->doSomething()->willReturn('result')->shouldBeCalled(); - - $revelation = $prophecy->reveal(); - - $this->assertSame('result', $revelation->doSomething()); - } -} diff --git a/tests/_files/FaillingDataProviderTest.php b/tests/_files/FaillingDataProviderTest.php new file mode 100644 index 00000000000..2214f4f5985 --- /dev/null +++ b/tests/_files/FaillingDataProviderTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +class FaillingDataProviderTest extends TestCase +{ + public static function provideData(): array + { + return [ + 'good1' => [3, 1, 2], + 'good2' => [5, 2, 3], + 'fail1' => [10, 3, 4], + 'good3' => [10, 5, 5], + 'fail2' => [20, 3, 4], + ]; + } + + public function testOne(): void + { + $this->assertTrue(true); + } + + #[DataProvider('provideData')] + public function testWithProvider($sum, $summand, $summmand2): void + { + $this->assertSame($sum, $summand + $summmand2); + } +} diff --git a/tests/_files/Failure.php b/tests/_files/Failure.php index deea8910b03..80a7284e947 100644 --- a/tests/_files/Failure.php +++ b/tests/_files/Failure.php @@ -11,9 +11,9 @@ use PHPUnit\Framework\TestCase; -class Failure extends TestCase +final class Failure extends TestCase { - protected function runTest(): void + public function testOne(): void { $this->fail(); } diff --git a/tests/_files/FailureTest.php b/tests/_files/FailureTest.php index 828da0a953f..deb2b4f4e98 100644 --- a/tests/_files/FailureTest.php +++ b/tests/_files/FailureTest.php @@ -12,7 +12,7 @@ use PHPUnit\Framework\TestCase; use stdClass; -class FailureTest extends TestCase +final class FailureTest extends TestCase { public function testAssertArrayEqualsArray(): void { diff --git a/tests/_files/FatalTest.php b/tests/_files/FatalTest.php deleted file mode 100644 index be18897d029..00000000000 --- a/tests/_files/FatalTest.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use function extension_loaded; -use function phpversion; -use function version_compare; -use function xdebug_disable; -use PHPUnit\Framework\TestCase; - -class FatalTest extends TestCase -{ - public function testFatalError(): void - { - if (extension_loaded('xdebug') && version_compare(phpversion('xdebug'), '3', '<')) { - xdebug_disable(); - } - - eval('namespace PHPUnit\TestFixture { class FatalTest {} }'); - } -} diff --git a/tests/_files/FileWithIssue.php b/tests/_files/FileWithIssue.php new file mode 100644 index 00000000000..caaa2ce65e7 --- /dev/null +++ b/tests/_files/FileWithIssue.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +final class FileWithIssue +{ +} diff --git a/tests/_files/Foo.php b/tests/_files/Foo.php index 82fcf88ed99..f10ca723838 100644 --- a/tests/_files/Foo.php +++ b/tests/_files/Foo.php @@ -7,7 +7,9 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -class Foo +namespace PHPUnit\TestFixture; + +final class Foo { public function doSomething(Bar $bar) { diff --git a/tests/_files/FunctionCallback.php b/tests/_files/FunctionCallback.php deleted file mode 100644 index 02f78316c53..00000000000 --- a/tests/_files/FunctionCallback.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use function func_get_args; - -class FunctionCallbackWrapper -{ - public static function functionCallback() - { - $args = func_get_args(); - - if ($args == ['foo', 'bar']) { - return 'pass'; - } - } -} diff --git a/tests/_files/Generator.php b/tests/_files/Generator.php new file mode 100644 index 00000000000..45a1bbf49fc --- /dev/null +++ b/tests/_files/Generator.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Generator; + +use Generator; + +function f(): Generator +{ + yield 0; +} diff --git a/tests/_files/Go ogle-Sea.rch.wsdl b/tests/_files/Go ogle-Sea.rch.wsdl deleted file mode 100644 index e448501dd1b..00000000000 --- a/tests/_files/Go ogle-Sea.rch.wsdl +++ /dev/null @@ -1,198 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/_files/GoogleSearch.wsdl b/tests/_files/GoogleSearch.wsdl deleted file mode 100644 index e448501dd1b..00000000000 --- a/tests/_files/GoogleSearch.wsdl +++ /dev/null @@ -1,198 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/_files/IncompleteTest.php b/tests/_files/IncompleteTest.php index 72d1eb8bf45..3668c8fd121 100644 --- a/tests/_files/IncompleteTest.php +++ b/tests/_files/IncompleteTest.php @@ -11,7 +11,7 @@ use PHPUnit\Framework\TestCase; -class IncompleteTest extends TestCase +final class IncompleteTest extends TestCase { public function testIncomplete(): void { diff --git a/tests/_files/Inheritance/InheritanceA.php b/tests/_files/Inheritance/InheritanceA.php index 87f63747482..4de6c644a3e 100644 --- a/tests/_files/Inheritance/InheritanceA.php +++ b/tests/_files/Inheritance/InheritanceA.php @@ -7,7 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -require_once __DIR__ . '/InheritanceB.php'; +namespace PHPUnit\TestFixture; class InheritanceA extends InheritanceB { diff --git a/tests/_files/Inheritance/InheritanceB.php b/tests/_files/Inheritance/InheritanceB.php index edfc55a141f..93a27b86d8c 100644 --- a/tests/_files/Inheritance/InheritanceB.php +++ b/tests/_files/Inheritance/InheritanceB.php @@ -7,6 +7,8 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + use PHPUnit\Framework\TestCase; class InheritanceB extends TestCase diff --git a/tests/_files/InheritedTestCase.php b/tests/_files/InheritedTestCase.php index 3afc714074a..5c379378e0e 100644 --- a/tests/_files/InheritedTestCase.php +++ b/tests/_files/InheritedTestCase.php @@ -9,7 +9,7 @@ */ namespace PHPUnit\TestFixture; -class InheritedTestCase extends OneTestCase +final class InheritedTestCase extends OneTestCase { public function test2(): void { diff --git a/tests/_files/IniTest.php b/tests/_files/IniTest.php index b19854e8128..2400d0571c8 100644 --- a/tests/_files/IniTest.php +++ b/tests/_files/IniTest.php @@ -10,10 +10,12 @@ namespace PHPUnit\TestFixture; use function ini_get; +use PHPUnit\Framework\Attributes\PreserveGlobalState; use PHPUnit\Framework\TestCase; -class IniTest extends TestCase +final class IniTest extends TestCase { + #[PreserveGlobalState(true)] public function testIni(): void { $this->assertEquals('application/x-test', ini_get('default_mimetype')); diff --git a/tests/_files/InterfaceAsTargetWithAttributeTest.php b/tests/_files/InterfaceAsTargetWithAttributeTest.php new file mode 100644 index 00000000000..98d4eb45b7e --- /dev/null +++ b/tests/_files/InterfaceAsTargetWithAttributeTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; +use Throwable; + +#[CoversClass(Throwable::class)] +#[UsesClass(Throwable::class)] +final class InterfaceAsTargetWithAttributeTest extends TestCase +{ + public function testOne(): void + { + } +} diff --git a/tests/_files/InterfaceWithSemiReservedMethodName.php b/tests/_files/InterfaceWithSemiReservedMethodName.php deleted file mode 100644 index d90fd9a3741..00000000000 --- a/tests/_files/InterfaceWithSemiReservedMethodName.php +++ /dev/null @@ -1,15 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -interface InterfaceWithSemiReservedMethodName -{ - public function unset(); -} diff --git a/tests/_files/InterfaceWithStaticMethod.php b/tests/_files/InterfaceWithStaticMethod.php deleted file mode 100644 index 36eb1b56336..00000000000 --- a/tests/_files/InterfaceWithStaticMethod.php +++ /dev/null @@ -1,15 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -interface InterfaceWithStaticMethod -{ - public static function staticMethod(); -} diff --git a/tests/_files/InternalProphecyIntegrationTest.php b/tests/_files/InternalProphecyIntegrationTest.php deleted file mode 100644 index 882d1f70878..00000000000 --- a/tests/_files/InternalProphecyIntegrationTest.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -final class InternalProphecyIntegrationTest extends TestCase -{ - public function testOne(): void - { - $prophecy = $this->prophesize(\PHPUnit\TestFixture\AnInterface::class); - $prophecy->doSomething()->willReturn('result')->shouldBeCalled(); - - $revelation = $prophecy->reveal(); - - $this->assertSame('result', $revelation->doSomething()); - } -} diff --git a/tests/_files/InvalidClassTargetWithAttributeTest.php b/tests/_files/InvalidClassTargetWithAttributeTest.php new file mode 100644 index 00000000000..067dfcd4ba4 --- /dev/null +++ b/tests/_files/InvalidClassTargetWithAttributeTest.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass('InvalidClass')] +#[UsesClass('InvalidClass')] +final class InvalidClassTargetWithAttributeTest extends TestCase +{ + public function testOne(): void + { + } +} diff --git a/tests/_files/InvalidFunctionTargetTest.php b/tests/_files/InvalidFunctionTargetTest.php new file mode 100644 index 00000000000..47943d19ada --- /dev/null +++ b/tests/_files/InvalidFunctionTargetTest.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\CoversFunction; +use PHPUnit\Framework\Attributes\UsesFunction; +use PHPUnit\Framework\TestCase; + +#[CoversFunction('invalid_function')] +#[UsesFunction('invalid_function')] +final class InvalidFunctionTargetTest extends TestCase +{ + public function testOne(): void + { + } +} diff --git a/tests/_files/InvokableConstraintAndPipeOperatorTest.php b/tests/_files/InvokableConstraintAndPipeOperatorTest.php new file mode 100644 index 00000000000..6dbab8e00a1 --- /dev/null +++ b/tests/_files/InvokableConstraintAndPipeOperatorTest.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\TestCase; + +final class InvokableConstraintAndPipeOperatorTest extends TestCase +{ + public function testOne(): void + { + $actual = 'string'; + + $actual |> $this->isString() + |> $this->stringContains('string'); + } +} diff --git a/tests/_files/IsolationTest.php b/tests/_files/IsolationTest.php index 113f5443729..3a0a76cf640 100644 --- a/tests/_files/IsolationTest.php +++ b/tests/_files/IsolationTest.php @@ -11,7 +11,7 @@ use PHPUnit\Framework\TestCase; -class IsolationTest extends TestCase +final class IsolationTest extends TestCase { public function testIsInIsolationReturnsFalse(): void { diff --git a/tests/_files/LargeGroupAttributesTest.php b/tests/_files/LargeGroupAttributesTest.php new file mode 100644 index 00000000000..f8f2a316270 --- /dev/null +++ b/tests/_files/LargeGroupAttributesTest.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\Large; +use PHPUnit\Framework\TestCase; + +#[Large] +final class LargeGroupAttributesTest extends TestCase +{ + public function testOne(): void + { + } +} diff --git a/tests/_files/MediumGroupAttributesTest.php b/tests/_files/MediumGroupAttributesTest.php new file mode 100644 index 00000000000..49cdd19a274 --- /dev/null +++ b/tests/_files/MediumGroupAttributesTest.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\TestCase; + +#[Medium] +final class MediumGroupAttributesTest extends TestCase +{ + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/src/Example.php b/tests/_files/Metadata/Attribute/src/Example.php new file mode 100644 index 00000000000..5e380bbd6a7 --- /dev/null +++ b/tests/_files/Metadata/Attribute/src/Example.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +final class Example +{ + public function method(): void + { + } +} + +function f(): void +{ +} diff --git a/tests/_files/Metadata/Attribute/src/ExampleTrait.php b/tests/_files/Metadata/Attribute/src/ExampleTrait.php new file mode 100644 index 00000000000..8b8d743a1a3 --- /dev/null +++ b/tests/_files/Metadata/Attribute/src/ExampleTrait.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +trait ExampleTrait +{ +} diff --git a/tests/_files/Metadata/Attribute/tests/AnotherTest.php b/tests/_files/Metadata/Attribute/tests/AnotherTest.php new file mode 100644 index 00000000000..2be4ab1fe23 --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/AnotherTest.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\TestCase; + +final class AnotherTest extends TestCase +{ + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/BackupGlobalsTest.php b/tests/_files/Metadata/Attribute/tests/BackupGlobalsTest.php new file mode 100644 index 00000000000..2405a20b7da --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/BackupGlobalsTest.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\BackupGlobals; +use PHPUnit\Framework\Attributes\ExcludeGlobalVariableFromBackup; +use PHPUnit\Framework\TestCase; + +#[BackupGlobals(true)] +#[ExcludeGlobalVariableFromBackup('foo')] +final class BackupGlobalsTest extends TestCase +{ + #[BackupGlobals(false)] + #[ExcludeGlobalVariableFromBackup('bar')] + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/BackupStaticPropertiesTest.php b/tests/_files/Metadata/Attribute/tests/BackupStaticPropertiesTest.php new file mode 100644 index 00000000000..4f239206805 --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/BackupStaticPropertiesTest.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\BackupStaticProperties; +use PHPUnit\Framework\Attributes\ExcludeStaticPropertyFromBackup; +use PHPUnit\Framework\TestCase; + +#[BackupStaticProperties(true)] +#[ExcludeStaticPropertyFromBackup('className', 'propertyName')] +final class BackupStaticPropertiesTest extends TestCase +{ + #[BackupStaticProperties(false)] + #[ExcludeStaticPropertyFromBackup('anotherClassName', 'propertyName')] + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/CoversNothingTest.php b/tests/_files/Metadata/Attribute/tests/CoversNothingTest.php new file mode 100644 index 00000000000..bd5b02a1e97 --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/CoversNothingTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\CoversNothing; +use PHPUnit\Framework\TestCase; + +#[CoversNothing] +final class CoversNothingTest extends TestCase +{ + #[CoversNothing] + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/CoversTest.php b/tests/_files/Metadata/Attribute/tests/CoversTest.php new file mode 100644 index 00000000000..c8eed04d646 --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/CoversTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversClassesThatExtendClass; +use PHPUnit\Framework\Attributes\CoversClassesThatImplementInterface; +use PHPUnit\Framework\Attributes\CoversFunction; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\CoversNamespace; +use PHPUnit\Framework\Attributes\CoversTrait; +use PHPUnit\Framework\TestCase; + +#[CoversNamespace('PHPUnit\TestFixture\Metadata\Attribute')] +#[CoversClass(Example::class)] +#[CoversClassesThatExtendClass(Example::class)] +#[CoversClassesThatImplementInterface(Example::class)] +#[CoversTrait(ExampleTrait::class)] +#[CoversMethod(Example::class, 'method')] +#[CoversFunction('f')] +final class CoversTest extends TestCase +{ + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/DependencyTest.php b/tests/_files/Metadata/Attribute/tests/DependencyTest.php new file mode 100644 index 00000000000..886a95bf106 --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/DependencyTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\Depends; +use PHPUnit\Framework\Attributes\DependsExternal; +use PHPUnit\Framework\Attributes\DependsExternalUsingDeepClone; +use PHPUnit\Framework\Attributes\DependsExternalUsingShallowClone; +use PHPUnit\Framework\Attributes\DependsOnClass; +use PHPUnit\Framework\Attributes\DependsOnClassUsingDeepClone; +use PHPUnit\Framework\Attributes\DependsOnClassUsingShallowClone; +use PHPUnit\Framework\Attributes\DependsUsingDeepClone; +use PHPUnit\Framework\Attributes\DependsUsingShallowClone; +use PHPUnit\Framework\TestCase; + +final class DependencyTest extends TestCase +{ + #[Depends('testOne')] + public function testOne(): void + { + } + + #[DependsUsingDeepClone('testOne')] + public function testTwo(): void + { + } + + #[DependsUsingShallowClone('testOne')] + public function testThree(): void + { + } + + #[DependsExternal(AnotherTest::class, 'testOne')] + public function testFour(): void + { + } + + #[DependsExternalUsingDeepClone(AnotherTest::class, 'testOne')] + public function testFive(): void + { + } + + #[DependsExternalUsingShallowClone(AnotherTest::class, 'testOne')] + public function testSix(): void + { + } + + #[DependsOnClass(AnotherTest::class)] + public function testSeven(): void + { + } + + #[DependsOnClassUsingDeepClone(AnotherTest::class)] + public function testEight(): void + { + } + + #[DependsOnClassUsingShallowClone(AnotherTest::class)] + public function testNine(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/DisableReturnValueGenerationForTestDoublesTest.php b/tests/_files/Metadata/Attribute/tests/DisableReturnValueGenerationForTestDoublesTest.php new file mode 100644 index 00000000000..6a8537248b3 --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/DisableReturnValueGenerationForTestDoublesTest.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\DisableReturnValueGenerationForTestDoubles; +use PHPUnit\Framework\TestCase; + +#[DisableReturnValueGenerationForTestDoubles] +final class DisableReturnValueGenerationForTestDoublesTest extends TestCase +{ + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/DoesNotPerformAssertionsTest.php b/tests/_files/Metadata/Attribute/tests/DoesNotPerformAssertionsTest.php new file mode 100644 index 00000000000..a8b2dc29f1d --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/DoesNotPerformAssertionsTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; +use PHPUnit\Framework\TestCase; + +#[DoesNotPerformAssertions] +final class DoesNotPerformAssertionsTest extends TestCase +{ + #[DoesNotPerformAssertions] + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/DuplicateSmallAttributeTest.php b/tests/_files/Metadata/Attribute/tests/DuplicateSmallAttributeTest.php new file mode 100644 index 00000000000..218c4bd8fdf --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/DuplicateSmallAttributeTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[Small] +#[Small] +final class DuplicateSmallAttributeTest extends TestCase +{ + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/DuplicateTestAttributeTest.php b/tests/_files/Metadata/Attribute/tests/DuplicateTestAttributeTest.php new file mode 100644 index 00000000000..b5214201fed --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/DuplicateTestAttributeTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; + +final class DuplicateTestAttributeTest extends TestCase +{ + #[Test] + #[Test] + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/GroupTest.php b/tests/_files/Metadata/Attribute/tests/GroupTest.php new file mode 100644 index 00000000000..2bfed5d7dfa --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/GroupTest.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Ticket; +use PHPUnit\Framework\TestCase; + +#[Group('group')] +#[Ticket('ticket')] +final class GroupTest extends TestCase +{ + #[Group('another-group')] + #[Ticket('another-ticket')] + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/IgnoreDeprecationsClassTest.php b/tests/_files/Metadata/Attribute/tests/IgnoreDeprecationsClassTest.php new file mode 100644 index 00000000000..b3dc8367d02 --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/IgnoreDeprecationsClassTest.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\IgnoreDeprecations; +use PHPUnit\Framework\TestCase; + +#[IgnoreDeprecations] +final class IgnoreDeprecationsClassTest extends TestCase +{ + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/IgnoreDeprecationsMethodTest.php b/tests/_files/Metadata/Attribute/tests/IgnoreDeprecationsMethodTest.php new file mode 100644 index 00000000000..4ae80b3c490 --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/IgnoreDeprecationsMethodTest.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\IgnoreDeprecations; +use PHPUnit\Framework\TestCase; + +final class IgnoreDeprecationsMethodTest extends TestCase +{ + #[IgnoreDeprecations] + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/IgnorePhpunitDeprecationsClassTest.php b/tests/_files/Metadata/Attribute/tests/IgnorePhpunitDeprecationsClassTest.php new file mode 100644 index 00000000000..1d1cf684501 --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/IgnorePhpunitDeprecationsClassTest.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\IgnorePhpunitDeprecations; +use PHPUnit\Framework\TestCase; + +#[IgnorePhpunitDeprecations] +final class IgnorePhpunitDeprecationsClassTest extends TestCase +{ + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/IgnorePhpunitDeprecationsMethodTest.php b/tests/_files/Metadata/Attribute/tests/IgnorePhpunitDeprecationsMethodTest.php new file mode 100644 index 00000000000..1726a374883 --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/IgnorePhpunitDeprecationsMethodTest.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\IgnorePhpunitDeprecations; +use PHPUnit\Framework\TestCase; + +final class IgnorePhpunitDeprecationsMethodTest extends TestCase +{ + #[IgnorePhpunitDeprecations] + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/IgnorePhpunitWarningsTest.php b/tests/_files/Metadata/Attribute/tests/IgnorePhpunitWarningsTest.php new file mode 100644 index 00000000000..57580c6b4eb --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/IgnorePhpunitWarningsTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\IgnorePhpunitWarnings; +use PHPUnit\Framework\TestCase; + +final class IgnorePhpunitWarningsTest extends TestCase +{ + #[IgnorePhpunitWarnings] + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/_files/Metadata/Attribute/tests/LargeTest.php b/tests/_files/Metadata/Attribute/tests/LargeTest.php new file mode 100644 index 00000000000..92e87e1bf51 --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/LargeTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\Large; +use PHPUnit\Framework\TestCase; + +#[Large] +final class LargeTest extends TestCase +{ +} diff --git a/tests/_files/Metadata/Attribute/tests/MediumTest.php b/tests/_files/Metadata/Attribute/tests/MediumTest.php new file mode 100644 index 00000000000..2f4de138695 --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/MediumTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\TestCase; + +#[Medium] +final class MediumTest extends TestCase +{ +} diff --git a/tests/_files/Metadata/Attribute/tests/NonPhpunitAttribute.php b/tests/_files/Metadata/Attribute/tests/NonPhpunitAttribute.php new file mode 100644 index 00000000000..ebb62c5e814 --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/NonPhpunitAttribute.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use Attribute; + +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final class NonPhpunitAttribute +{ +} diff --git a/tests/_files/Metadata/Attribute/tests/NonPhpunitAttributeTest.php b/tests/_files/Metadata/Attribute/tests/NonPhpunitAttributeTest.php new file mode 100644 index 00000000000..e4f1eb216cd --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/NonPhpunitAttributeTest.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\TestCase; + +#[NonPhpunitAttribute] +final class NonPhpunitAttributeTest extends TestCase +{ + #[NonPhpunitAttribute] + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/PhpunitAttributeThatDoesNotExistTest.php b/tests/_files/Metadata/Attribute/tests/PhpunitAttributeThatDoesNotExistTest.php new file mode 100644 index 00000000000..067a9df2f2e --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/PhpunitAttributeThatDoesNotExistTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\PhpunitAttributeThatDoesNotExist; +use PHPUnit\Framework\TestCase; + +#[PhpunitAttributeThatDoesNotExist] +final class PhpunitAttributeThatDoesNotExistTest extends TestCase +{ + #[PhpunitAttributeThatDoesNotExist] + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/PreserveGlobalStateTest.php b/tests/_files/Metadata/Attribute/tests/PreserveGlobalStateTest.php new file mode 100644 index 00000000000..e0a284af2e0 --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/PreserveGlobalStateTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\PreserveGlobalState; +use PHPUnit\Framework\TestCase; + +#[PreserveGlobalState(true)] +final class PreserveGlobalStateTest extends TestCase +{ + #[PreserveGlobalState(false)] + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/ProcessIsolationTest.php b/tests/_files/Metadata/Attribute/tests/ProcessIsolationTest.php new file mode 100644 index 00000000000..948e7a038cd --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/ProcessIsolationTest.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\RunInSeparateProcess; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; +use PHPUnit\Framework\TestCase; + +#[RunTestsInSeparateProcesses] +final class ProcessIsolationTest extends TestCase +{ + #[RunInSeparateProcess] + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/RequiresEnvironmentVariableTest.php b/tests/_files/Metadata/Attribute/tests/RequiresEnvironmentVariableTest.php new file mode 100644 index 00000000000..96fefede969 --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/RequiresEnvironmentVariableTest.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\RequiresEnvironmentVariable; +use PHPUnit\Framework\TestCase; + +#[RequiresEnvironmentVariable('foo', 'bar')] +final class RequiresEnvironmentVariableTest extends TestCase +{ + #[RequiresEnvironmentVariable('foo')] + #[RequiresEnvironmentVariable('bar', 'baz')] + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/RequiresFunctionTest.php b/tests/_files/Metadata/Attribute/tests/RequiresFunctionTest.php new file mode 100644 index 00000000000..faf1289b15f --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/RequiresFunctionTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\RequiresFunction; +use PHPUnit\Framework\TestCase; + +#[RequiresFunction('f')] +final class RequiresFunctionTest extends TestCase +{ + #[RequiresFunction('g')] + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/RequiresMethodTest.php b/tests/_files/Metadata/Attribute/tests/RequiresMethodTest.php new file mode 100644 index 00000000000..faea6e8a1db --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/RequiresMethodTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\RequiresMethod; +use PHPUnit\Framework\TestCase; + +#[RequiresMethod('ClassName', 'methodName')] +final class RequiresMethodTest extends TestCase +{ + #[RequiresMethod('AnotherClassName', 'anotherMethodName')] + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/RequiresOperatingSystemFamilyTest.php b/tests/_files/Metadata/Attribute/tests/RequiresOperatingSystemFamilyTest.php new file mode 100644 index 00000000000..24b190879f3 --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/RequiresOperatingSystemFamilyTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\RequiresOperatingSystemFamily; +use PHPUnit\Framework\TestCase; + +#[RequiresOperatingSystemFamily('Linux')] +final class RequiresOperatingSystemFamilyTest extends TestCase +{ + #[RequiresOperatingSystemFamily('Linux')] + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/RequiresOperatingSystemTest.php b/tests/_files/Metadata/Attribute/tests/RequiresOperatingSystemTest.php new file mode 100644 index 00000000000..b62fe6121b3 --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/RequiresOperatingSystemTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\RequiresOperatingSystem; +use PHPUnit\Framework\TestCase; + +#[RequiresOperatingSystem('Linux')] +final class RequiresOperatingSystemTest extends TestCase +{ + #[RequiresOperatingSystem('Linux')] + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/RequiresPhpExtensionTest.php b/tests/_files/Metadata/Attribute/tests/RequiresPhpExtensionTest.php new file mode 100644 index 00000000000..34ba5c39fb3 --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/RequiresPhpExtensionTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\RequiresPhpExtension; +use PHPUnit\Framework\TestCase; + +#[RequiresPhpExtension('foo', '>= 1.0')] +final class RequiresPhpExtensionTest extends TestCase +{ + #[RequiresPhpExtension('bar', '>= 2.0')] + public function testOne(): void + { + } + + #[RequiresPhpExtension('baz', '^1.0')] + public function testTwo(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/RequiresPhpTest.php b/tests/_files/Metadata/Attribute/tests/RequiresPhpTest.php new file mode 100644 index 00000000000..c6bef15dbf1 --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/RequiresPhpTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\RequiresPhp; +use PHPUnit\Framework\TestCase; + +#[RequiresPhp('8.0.0')] +final class RequiresPhpTest extends TestCase +{ + #[RequiresPhp('^8.0')] + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/RequiresPhpunitExtensionTest.php b/tests/_files/Metadata/Attribute/tests/RequiresPhpunitExtensionTest.php new file mode 100644 index 00000000000..360b01996b3 --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/RequiresPhpunitExtensionTest.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\RequiresPhpunitExtension; +use PHPUnit\Framework\TestCase; + +#[RequiresPhpunitExtension(SomeExtension::class)] +final class RequiresPhpunitExtensionTest extends TestCase +{ + #[RequiresPhpunitExtension(SomeExtension::class)] + #[RequiresPhpunitExtension(SomeOtherExtension::class)] + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/RequiresPhpunitTest.php b/tests/_files/Metadata/Attribute/tests/RequiresPhpunitTest.php new file mode 100644 index 00000000000..5570ef5113f --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/RequiresPhpunitTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\RequiresPhpunit; +use PHPUnit\Framework\TestCase; + +#[RequiresPhpunit('>= 10.0.0')] +final class RequiresPhpunitTest extends TestCase +{ + #[RequiresPhpunit('^10.0')] + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/RequiresSettingTest.php b/tests/_files/Metadata/Attribute/tests/RequiresSettingTest.php new file mode 100644 index 00000000000..2bf825649c3 --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/RequiresSettingTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\RequiresSetting; +use PHPUnit\Framework\TestCase; + +#[RequiresSetting('setting', 'value')] +final class RequiresSettingTest extends TestCase +{ + #[RequiresSetting('another-setting', 'another-value')] + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/SmallTest.php b/tests/_files/Metadata/Attribute/tests/SmallTest.php new file mode 100644 index 00000000000..566f67e6eba --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/SmallTest.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\After; +use PHPUnit\Framework\Attributes\AfterClass; +use PHPUnit\Framework\Attributes\Before; +use PHPUnit\Framework\Attributes\BeforeClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\PostCondition; +use PHPUnit\Framework\Attributes\PreCondition; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; + +#[Small] +final class SmallTest extends TestCase +{ + #[BeforeClass] + public function beforeTests(): void + { + } + + #[Before] + public function beforeTest(): void + { + } + + #[PreCondition] + public function preCondition(): void + { + } + + #[Test] + public function one(): void + { + } + + #[DataProvider('provider')] + public function testWithDataProvider(): void + { + } + + #[DataProviderExternal(self::class, 'provider')] + public function testWithDataProviderExternal(): void + { + } + + #[PostCondition] + public function postCondition(): void + { + } + + #[After] + public function afterTest(): void + { + } + + #[AfterClass] + public function afterTests(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/TestDoxTest.php b/tests/_files/Metadata/Attribute/tests/TestDoxTest.php new file mode 100644 index 00000000000..98f5a9b5fa6 --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/TestDoxTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\Attributes\TestDoxFormatter; +use PHPUnit\Framework\Attributes\TestDoxFormatterExternal; +use PHPUnit\Framework\TestCase; + +#[TestDox('text')] +final class TestDoxTest extends TestCase +{ + #[TestDox('text')] + public function testOne(): void + { + } + + #[TestDoxFormatter('methodName')] + public function testTwo(): void + { + } + + #[TestDoxFormatterExternal('ClassName', 'methodName')] + public function testThree(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/TestWithInvalidValueTest.php b/tests/_files/Metadata/Attribute/tests/TestWithInvalidValueTest.php new file mode 100644 index 00000000000..aa3954aa9d9 --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/TestWithInvalidValueTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\TestWithJson; +use PHPUnit\Framework\TestCase; + +final class TestWithInvalidValueTest extends TestCase +{ + #[TestWithJson('false')] + public function testOne($one, $two): void + { + $this->assertTrue(true); + } +} diff --git a/tests/_files/Metadata/Attribute/tests/TestWithTest.php b/tests/_files/Metadata/Attribute/tests/TestWithTest.php new file mode 100644 index 00000000000..5ed7dd01f1b --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/TestWithTest.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\TestWith; +use PHPUnit\Framework\Attributes\TestWithJson; +use PHPUnit\Framework\TestCase; + +final class TestWithTest extends TestCase +{ + #[TestWith([1, 2, 3])] + public function testOne($one, $two, $three): void + { + $this->assertTrue(true); + } + + #[TestWith([1, 2, 3], 'Name1')] + public function testOneWithName($one, $two = null, $three = null): void + { + $this->assertTrue(true); + } + + #[TestWithJson('[1, 2, 3]')] + public function testTwo($one, $two, $three): void + { + $this->assertTrue(true); + } + + #[TestWithJson('[1, 2, 3]', 'Name2')] + public function testTwoWithName($one, $two, $three): void + { + $this->assertTrue(true); + } +} diff --git a/tests/_files/Metadata/Attribute/tests/TestWithTooManyValuesTest.php b/tests/_files/Metadata/Attribute/tests/TestWithTooManyValuesTest.php new file mode 100644 index 00000000000..4fe79a44af3 --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/TestWithTooManyValuesTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\TestWith; +use PHPUnit\Framework\TestCase; + +final class TestWithTooManyValuesTest extends TestCase +{ + #[TestWith([1, 2, 3])] + public function testOne($one, $two): void + { + $this->assertTrue(true); + } +} diff --git a/tests/_files/Metadata/Attribute/tests/UsesTest.php b/tests/_files/Metadata/Attribute/tests/UsesTest.php new file mode 100644 index 00000000000..0333fdb525d --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/UsesTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\Attributes\UsesClassesThatExtendClass; +use PHPUnit\Framework\Attributes\UsesClassesThatImplementInterface; +use PHPUnit\Framework\Attributes\UsesFunction; +use PHPUnit\Framework\Attributes\UsesMethod; +use PHPUnit\Framework\Attributes\UsesNamespace; +use PHPUnit\Framework\Attributes\UsesTrait; +use PHPUnit\Framework\TestCase; + +#[UsesNamespace('PHPUnit\TestFixture\Metadata\Attribute')] +#[UsesClass(Example::class)] +#[UsesClassesThatExtendClass(Example::class)] +#[UsesClassesThatImplementInterface(Example::class)] +#[UsesTrait(ExampleTrait::class)] +#[UsesMethod(Example::class, 'method')] +#[UsesFunction('f')] +final class UsesTest extends TestCase +{ + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/WithEnvironmentVariableTest.php b/tests/_files/Metadata/Attribute/tests/WithEnvironmentVariableTest.php new file mode 100644 index 00000000000..1624ff1419d --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/WithEnvironmentVariableTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\WithEnvironmentVariable; +use PHPUnit\Framework\TestCase; + +#[WithEnvironmentVariable('foo', 'bar')] +final class WithEnvironmentVariableTest extends TestCase +{ + #[WithEnvironmentVariable('foo')] + #[WithEnvironmentVariable('bar', 'baz')] + public function testOne(): void + { + } +} diff --git a/tests/_files/Metadata/Attribute/tests/WithoutErrorHandlerTest.php b/tests/_files/Metadata/Attribute/tests/WithoutErrorHandlerTest.php new file mode 100644 index 00000000000..7381f1a8337 --- /dev/null +++ b/tests/_files/Metadata/Attribute/tests/WithoutErrorHandlerTest.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Metadata\Attribute; + +use PHPUnit\Framework\Attributes\WithoutErrorHandler; +use PHPUnit\Framework\TestCase; + +final class WithoutErrorHandlerTest extends TestCase +{ + #[WithoutErrorHandler] + public function testOne(): void + { + } +} diff --git a/tests/_files/MethodCallback.php b/tests/_files/MethodCallback.php deleted file mode 100644 index c9d0ab93f86..00000000000 --- a/tests/_files/MethodCallback.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use function func_get_args; - -class MethodCallback -{ - public static function staticCallback() - { - $args = func_get_args(); - - if ($args == ['foo', 'bar']) { - return 'pass'; - } - } - - public function nonStaticCallback() - { - $args = func_get_args(); - - if ($args == ['foo', 'bar']) { - return 'pass'; - } - } -} diff --git a/tests/_files/MethodCallbackByReference.php b/tests/_files/MethodCallbackByReference.php deleted file mode 100644 index 25815254de6..00000000000 --- a/tests/_files/MethodCallbackByReference.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -class MethodCallbackByReference -{ - public function bar(&$a, &$b, $c): void - { - Legacy::bar($a, $b, $c); - } - - public function callback(&$a, &$b, $c): void - { - $b = 1; - } -} diff --git a/tests/_files/Mockable.php b/tests/_files/Mockable.php index 43fdb2dd2ef..da9d1568ecc 100644 --- a/tests/_files/Mockable.php +++ b/tests/_files/Mockable.php @@ -12,7 +12,6 @@ class Mockable { public $constructorArgs; - public $cloned; public function __construct($arg1 = null, $arg2 = null) diff --git a/tests/_files/ModifiedConstructorTestCase.php b/tests/_files/ModifiedConstructorTestCase.php deleted file mode 100644 index 659bf6bc85e..00000000000 --- a/tests/_files/ModifiedConstructorTestCase.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -class ModifiedConstructorTestCase extends TestCase -{ - public function __construct(?string $name = null) - { - parent::__construct($name); - } - - public function testCase(): void - { - } -} diff --git a/tests/end-to-end/execution-order/_files/MultiDependencyTest.php b/tests/_files/MultiDependencyTest.php similarity index 78% rename from tests/end-to-end/execution-order/_files/MultiDependencyTest.php rename to tests/_files/MultiDependencyTest.php index 9f9574ee00e..c6b7e207592 100644 --- a/tests/end-to-end/execution-order/_files/MultiDependencyTest.php +++ b/tests/_files/MultiDependencyTest.php @@ -7,6 +7,10 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\Depends; +use PHPUnit\Framework\Attributes\DependsExternal; use PHPUnit\Framework\TestCase; class MultiDependencyTest extends TestCase @@ -25,19 +29,15 @@ public function testTwo() return 'bar'; } - /** - * @depends testOne - * @depends testTwo - */ + #[Depends('testOne')] + #[Depends('testTwo')] public function testThree($a, $b): void { $this->assertEquals('foo', $a); $this->assertEquals('bar', $b); } - /** - * @depends MultiDependencyTest::testThree - */ + #[DependsExternal(self::class, 'testThree')] public function testFour(): void { $this->assertTrue(true); diff --git a/tests/_files/MultipleDataProviderTest.php b/tests/_files/MultipleDataProviderTest.php index 2e66464b608..e0cc9bc688d 100644 --- a/tests/_files/MultipleDataProviderTest.php +++ b/tests/_files/MultipleDataProviderTest.php @@ -9,12 +9,16 @@ */ namespace PHPUnit\TestFixture; +use ArrayIterator; use ArrayObject; +use Generator; +use Iterator; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; -class MultipleDataProviderTest extends TestCase +final class MultipleDataProviderTest extends TestCase { - public static function providerA() + public static function providerA(): array { return [ ['ok', null, null], @@ -23,7 +27,7 @@ public static function providerA() ]; } - public static function providerB() + public static function providerB(): array { return [ [null, 'ok', null], @@ -32,7 +36,7 @@ public static function providerB() ]; } - public static function providerC() + public static function providerC(): array { return [ [null, null, 'ok'], @@ -41,7 +45,7 @@ public static function providerC() ]; } - public static function providerD() + public static function providerD(): Generator { yield ['ok', null, null]; @@ -50,7 +54,7 @@ public static function providerD() yield ['ok', null, null]; } - public static function providerE() + public static function providerE(): Generator { yield [null, 'ok', null]; @@ -59,34 +63,30 @@ public static function providerE() yield [null, 'ok', null]; } - public static function providerF() + public static function providerF(): ArrayIterator|Iterator { $object = new ArrayObject( [ [null, null, 'ok'], [null, null, 'ok'], [null, null, 'ok'], - ] + ], ); return $object->getIterator(); } - /** - * @dataProvider providerA - * @dataProvider providerB - * @dataProvider providerC - */ - public function testOne(): void + #[DataProvider('providerA')] + #[DataProvider('providerB')] + #[DataProvider('providerC')] + public function testOne($one, $two, $three): void { } - /** - * @dataProvider providerD - * @dataProvider providerE - * @dataProvider providerF - */ - public function testTwo(): void + #[DataProvider('providerD')] + #[DataProvider('providerE')] + #[DataProvider('providerF')] + public function testTwo($one, $two, $three): void { } } diff --git a/tests/_files/MyTestListener.php b/tests/_files/MyTestListener.php deleted file mode 100644 index a95b495c2f6..00000000000 --- a/tests/_files/MyTestListener.php +++ /dev/null @@ -1,124 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\AssertionFailedError; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestListener; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Framework\Warning; -use Throwable; - -final class MyTestListener implements TestListener -{ - private $endCount = 0; - - private $errorCount = 0; - - private $failureCount = 0; - - private $warningCount = 0; - - private $notImplementedCount = 0; - - private $riskyCount = 0; - - private $skippedCount = 0; - - private $startCount = 0; - - public function addError(Test $test, Throwable $t, float $time): void - { - $this->errorCount++; - } - - public function addWarning(Test $test, Warning $e, float $time): void - { - $this->warningCount++; - } - - public function addFailure(Test $test, AssertionFailedError $e, float $time): void - { - $this->failureCount++; - } - - public function addIncompleteTest(Test $test, Throwable $t, float $time): void - { - $this->notImplementedCount++; - } - - public function addRiskyTest(Test $test, Throwable $t, float $time): void - { - $this->riskyCount++; - } - - public function addSkippedTest(Test $test, Throwable $t, float $time): void - { - $this->skippedCount++; - } - - public function startTestSuite(TestSuite $suite): void - { - } - - public function endTestSuite(TestSuite $suite): void - { - } - - public function startTest(Test $test): void - { - $this->startCount++; - } - - public function endTest(Test $test, float $time): void - { - $this->endCount++; - } - - public function endCount(): int - { - return $this->endCount; - } - - public function errorCount(): int - { - return $this->errorCount; - } - - public function failureCount(): int - { - return $this->failureCount; - } - - public function warningCount(): int - { - return $this->warningCount; - } - - public function notImplementedCount(): int - { - return $this->notImplementedCount; - } - - public function riskyCount(): int - { - return $this->riskyCount; - } - - public function skippedCount(): int - { - return $this->skippedCount; - } - - public function startCount(): int - { - return $this->startCount; - } -} diff --git a/tests/_files/NamedConstraint.php b/tests/_files/NamedConstraint.php deleted file mode 100644 index 839fe857179..00000000000 --- a/tests/_files/NamedConstraint.php +++ /dev/null @@ -1,39 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\Constraint\Constraint; - -final class NamedConstraint extends Constraint -{ - /** - * @var int - */ - private $name; - - public static function fromName(string $name): self - { - $instance = new self; - - $instance->name = $name; - - return $instance; - } - - public function matches($other): bool - { - return true; - } - - public function toString(): string - { - return $this->name; - } -} diff --git a/tests/_files/NamespaceCoverageClassExtendedTest.php b/tests/_files/NamespaceCoverageClassExtendedTest.php deleted file mode 100644 index 975fed8a5bd..00000000000 --- a/tests/_files/NamespaceCoverageClassExtendedTest.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -class NamespaceCoverageClassExtendedTest extends TestCase -{ - /** - * @covers \PHPUnit\TestFixture\CoveredClass - */ - public function testSomething(): void - { - $o = new CoveredClass; - $o->publicMethod(); - } -} diff --git a/tests/_files/NamespaceCoverageClassTest.php b/tests/_files/NamespaceCoverageClassTest.php deleted file mode 100644 index 15a33ab1ab9..00000000000 --- a/tests/_files/NamespaceCoverageClassTest.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -class NamespaceCoverageClassTest extends TestCase -{ - /** - * @covers \PHPUnit\TestFixture\CoveredClass - */ - public function testSomething(): void - { - $o = new CoveredClass; - $o->publicMethod(); - } -} diff --git a/tests/_files/NamespaceCoverageCoversClassPublicTest.php b/tests/_files/NamespaceCoverageCoversClassPublicTest.php deleted file mode 100644 index c56de6e457f..00000000000 --- a/tests/_files/NamespaceCoverageCoversClassPublicTest.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -/** - * @coversDefaultClass \PHPUnit\TestFixture\CoveredClass - */ -class NamespaceCoverageCoversClassPublicTest extends TestCase -{ - /** - * @covers ::publicMethod - */ - public function testSomething(): void - { - $o = new CoveredClass; - $o->publicMethod(); - } -} diff --git a/tests/_files/NamespaceCoverageCoversClassTest.php b/tests/_files/NamespaceCoverageCoversClassTest.php deleted file mode 100644 index a39fd225154..00000000000 --- a/tests/_files/NamespaceCoverageCoversClassTest.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -/** - * @coversDefaultClass \PHPUnit\TestFixture\CoveredClass - */ -class NamespaceCoverageCoversClassTest extends TestCase -{ - /** - * @covers ::privateMethod - * @covers ::protectedMethod - * @covers ::publicMethod - * @covers \PHPUnit\TestFixture\CoveredParentClass::privateMethod - * @covers \PHPUnit\TestFixture\CoveredParentClass::protectedMethod - * @covers \PHPUnit\TestFixture\CoveredParentClass::publicMethod - */ - public function testSomething(): void - { - $o = new CoveredClass; - - $o->publicMethod(); - } -} diff --git a/tests/_files/NamespaceCoverageMethodTest.php b/tests/_files/NamespaceCoverageMethodTest.php deleted file mode 100644 index 118fbf03861..00000000000 --- a/tests/_files/NamespaceCoverageMethodTest.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use CoveredClass; -use PHPUnit\Framework\TestCase; - -class NamespaceCoverageMethodTest extends TestCase -{ - /** - * @covers \PHPUnit\TestFixture\CoveredClass::publicMethod - */ - public function testSomething(): void - { - $o = new CoveredClass; - - $o->publicMethod(); - } -} diff --git a/tests/_files/NamespaceCoverageNotPrivateTest.php b/tests/_files/NamespaceCoverageNotPrivateTest.php deleted file mode 100644 index e8b1c45ba7e..00000000000 --- a/tests/_files/NamespaceCoverageNotPrivateTest.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -class NamespaceCoverageNotPrivateTest extends TestCase -{ - /** - * @covers PHPUnit\TestFixture\CoveredClass:: - */ - public function testSomething(): void - { - $o = new CoveredClass; - - $o->publicMethod(); - } -} diff --git a/tests/_files/NamespaceCoverageNotProtectedTest.php b/tests/_files/NamespaceCoverageNotProtectedTest.php deleted file mode 100644 index a130cd60271..00000000000 --- a/tests/_files/NamespaceCoverageNotProtectedTest.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -class NamespaceCoverageNotProtectedTest extends TestCase -{ - /** - * @covers PHPUnit\TestFixture\CoveredClass:: - */ - public function testSomething(): void - { - $o = new CoveredClass; - - $o->publicMethod(); - } -} diff --git a/tests/_files/NamespaceCoverageNotPublicTest.php b/tests/_files/NamespaceCoverageNotPublicTest.php deleted file mode 100644 index eac579df9f4..00000000000 --- a/tests/_files/NamespaceCoverageNotPublicTest.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -class NamespaceCoverageNotPublicTest extends TestCase -{ - /** - * @covers PHPUnit\TestFixture\CoveredClass:: - */ - public function testSomething(): void - { - $o = new CoveredClass; - - $o->publicMethod(); - } -} diff --git a/tests/_files/NamespaceCoveragePrivateTest.php b/tests/_files/NamespaceCoveragePrivateTest.php deleted file mode 100644 index ec3179fd9bf..00000000000 --- a/tests/_files/NamespaceCoveragePrivateTest.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -class NamespaceCoveragePrivateTest extends TestCase -{ - /** - * @covers \PHPUnit\TestFixture\CoveredClass:: - */ - public function testSomething(): void - { - $o = new CoveredClass; - - $o->publicMethod(); - } -} diff --git a/tests/_files/NamespaceCoverageProtectedTest.php b/tests/_files/NamespaceCoverageProtectedTest.php deleted file mode 100644 index 10227da44e7..00000000000 --- a/tests/_files/NamespaceCoverageProtectedTest.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -class NamespaceCoverageProtectedTest extends TestCase -{ - /** - * @covers \PHPUnit\TestFixture\CoveredClass:: - */ - public function testSomething(): void - { - $o = new CoveredClass; - - $o->publicMethod(); - } -} diff --git a/tests/_files/NamespaceCoveragePublicTest.php b/tests/_files/NamespaceCoveragePublicTest.php deleted file mode 100644 index dfa291f5839..00000000000 --- a/tests/_files/NamespaceCoveragePublicTest.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -class NamespaceCoveragePublicTest extends TestCase -{ - /** - * @covers \PHPUnit\TestFixture\CoveredClass:: - */ - public function testSomething(): void - { - $o = new CoveredClass; - - $o->publicMethod(); - } -} diff --git a/tests/_files/NoArgTestCaseTest.php b/tests/_files/NoArgTestCaseTest.php index 277376b582c..95d914f4b4d 100644 --- a/tests/_files/NoArgTestCaseTest.php +++ b/tests/_files/NoArgTestCaseTest.php @@ -11,7 +11,7 @@ use PHPUnit\Framework\TestCase; -class NoArgTestCaseTest extends TestCase +final class NoArgTestCaseTest extends TestCase { public function testNothing(): void { diff --git a/tests/_files/NoCoverageAttributesTest.php b/tests/_files/NoCoverageAttributesTest.php new file mode 100644 index 00000000000..422f56e7e80 --- /dev/null +++ b/tests/_files/NoCoverageAttributesTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\TestCase; + +final class NoCoverageAttributesTest extends TestCase +{ + public function testSomething(): void + { + $o = new CoveredClass; + + $o->publicMethod(); + } +} diff --git a/tests/_files/NoGroupsMetadataTest.php b/tests/_files/NoGroupsMetadataTest.php new file mode 100644 index 00000000000..d4b4d44d65e --- /dev/null +++ b/tests/_files/NoGroupsMetadataTest.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\TestCase; + +final class NoGroupsMetadataTest extends TestCase +{ + public function testOne(): void + { + } +} diff --git a/tests/_files/NoTestCase.php b/tests/_files/NoTestCase.php new file mode 100644 index 00000000000..83498f1dcaa --- /dev/null +++ b/tests/_files/NoTestCase.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +final class NoTestCase +{ +} diff --git a/tests/_files/NoTestCases.php b/tests/_files/NoTestCases.php index c434e83ff55..d885f95d373 100644 --- a/tests/_files/NoTestCases.php +++ b/tests/_files/NoTestCases.php @@ -11,7 +11,7 @@ use PHPUnit\Framework\TestCase; -class NoTestCases extends TestCase +final class NoTestCases extends TestCase { public function noTestCase(): void { diff --git a/tests/_files/NotExistingCoveredElementTest.php b/tests/_files/NotExistingCoveredElementTest.php deleted file mode 100644 index 22ede079269..00000000000 --- a/tests/_files/NotExistingCoveredElementTest.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -class NotExistingCoveredElementTest extends TestCase -{ - /** - * @covers NotExistingClass - */ - public function testOne(): void - { - } - - /** - * @covers NotExistingClass::notExistingMethod - */ - public function testTwo(): void - { - } - - /** - * @covers NotExistingClass:: - */ - public function testThree(): void - { - } -} diff --git a/tests/_files/NotPublicTestCase.php b/tests/_files/NotPublicTestCase.php index d24ba8f93f8..651118f9263 100644 --- a/tests/_files/NotPublicTestCase.php +++ b/tests/_files/NotPublicTestCase.php @@ -11,7 +11,7 @@ use PHPUnit\Framework\TestCase; -class NotPublicTestCase extends TestCase +final class NotPublicTestCase extends TestCase { public function testPublic(): void { diff --git a/tests/_files/NotReorderableTest.php b/tests/_files/NotReorderableTest.php deleted file mode 100644 index 180fd174618..00000000000 --- a/tests/_files/NotReorderableTest.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestResult; - -class NotReorderableTest implements Test -{ - public function count() - { - return 1; - } - - public function run(TestResult $result = null): TestResult - { - return new TestResult(); - } - - public function provides(): array - { - return []; - } - - public function requires(): array - { - return []; - } -} diff --git a/tests/_files/NotSelfDescribingTest.php b/tests/_files/NotSelfDescribingTest.php deleted file mode 100644 index 435957dd040..00000000000 --- a/tests/_files/NotSelfDescribingTest.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestResult; - -class NotSelfDescribingTest implements Test -{ - public function log($msg): void - { - print $msg; - } - - public function count(): int - { - return 0; - } - - public function run(TestResult $result = null): TestResult - { - return new TestResult; - } -} diff --git a/tests/_files/NotVoidTestCase.php b/tests/_files/NotVoidTestCase.php deleted file mode 100644 index e246f11085d..00000000000 --- a/tests/_files/NotVoidTestCase.php +++ /dev/null @@ -1,16 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -class NotVoidTestCase extends TestCase -{ -} diff --git a/tests/_files/NothingTest.php b/tests/_files/NothingTest.php index 0e0619de279..3c37b9e3572 100644 --- a/tests/_files/NothingTest.php +++ b/tests/_files/NothingTest.php @@ -11,7 +11,7 @@ use PHPUnit\Framework\TestCase; -class NothingTest extends TestCase +final class NothingTest extends TestCase { public function testNothing(): void { diff --git a/tests/_files/NumericGroupAnnotationTest.php b/tests/_files/NumericGroupAnnotationTest.php deleted file mode 100644 index e813eb94673..00000000000 --- a/tests/_files/NumericGroupAnnotationTest.php +++ /dev/null @@ -1,46 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -/** - * @author Companion Cube - * @ticket t123456 - */ -class NumericGroupAnnotationTest extends \PHPUnit\Framework\TestCase -{ - /** - * @testdox Empty test for @ticket numeric annotation values - * @ticket 3502 - * - * @author C. Lippy - * - * @see https://github.com/sebastianbergmann/phpunit/issues/3502 - */ - public function testTicketAnnotationSupportsNumericValue(): void - { - $this->assertTrue(true); - } - - /** - * @testdox Empty test for @group numeric annotation values - * @group 3502 - * - * @see https://github.com/sebastianbergmann/phpunit/issues/3502 - */ - public function testGroupAnnotationSupportsNumericValue(): void - { - $this->assertTrue(true); - } - - public function testDummyTestThatShouldNotRun(): void - { - $this->doesNotPerformAssertions(); - } -} diff --git a/tests/_files/ObjectEquals/ValueObject.php b/tests/_files/ObjectEquals/ValueObject.php new file mode 100644 index 00000000000..49449eea158 --- /dev/null +++ b/tests/_files/ObjectEquals/ValueObject.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\ObjectEquals; + +final class ValueObject +{ + private $value; + + public function __construct(int $value) + { + $this->value = $value; + } + + public function equals(self $other): bool + { + return $this->asInt() === $other->asInt(); + } + + public function asInt(): int + { + return $this->value; + } +} diff --git a/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodThatAcceptsTooManyArguments.php b/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodThatAcceptsTooManyArguments.php new file mode 100644 index 00000000000..dae003c5255 --- /dev/null +++ b/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodThatAcceptsTooManyArguments.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\ObjectEquals; + +final class ValueObjectWithEqualsMethodThatAcceptsTooManyArguments +{ + private $value; + + public function __construct(int $value) + { + $this->value = $value; + } + + public function equals(self $other, self $another): bool + { + return false; + } + + public function asInt(): int + { + return $this->value; + } +} diff --git a/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodThatDoesNotAcceptArguments.php b/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodThatDoesNotAcceptArguments.php new file mode 100644 index 00000000000..c6e78ebd962 --- /dev/null +++ b/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodThatDoesNotAcceptArguments.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\ObjectEquals; + +final class ValueObjectWithEqualsMethodThatDoesNotAcceptArguments +{ + private $value; + + public function __construct(int $value) + { + $this->value = $value; + } + + public function equals(): bool + { + return false; + } + + public function asInt(): int + { + return $this->value; + } +} diff --git a/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodThatDoesNotDeclareParameterType.php b/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodThatDoesNotDeclareParameterType.php new file mode 100644 index 00000000000..36ce99b3e47 --- /dev/null +++ b/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodThatDoesNotDeclareParameterType.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\ObjectEquals; + +final class ValueObjectWithEqualsMethodThatDoesNotDeclareParameterType +{ + private $value; + + public function __construct(int $value) + { + $this->value = $value; + } + + public function equals($other): bool + { + return $this->asInt() === $other->asInt(); + } + + public function asInt(): int + { + return $this->value; + } +} diff --git a/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodThatHasIncompatibleParameterType.php b/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodThatHasIncompatibleParameterType.php new file mode 100644 index 00000000000..60e00f0265b --- /dev/null +++ b/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodThatHasIncompatibleParameterType.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\ObjectEquals; + +use stdClass; + +final class ValueObjectWithEqualsMethodThatHasIncompatibleParameterType +{ + private $value; + + public function __construct(int $value) + { + $this->value = $value; + } + + public function equals(stdClass $other): bool + { + return false; + } + + public function asInt(): int + { + return $this->value; + } +} diff --git a/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodThatHasUnionParameterType.php b/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodThatHasUnionParameterType.php new file mode 100644 index 00000000000..1b157ee682b --- /dev/null +++ b/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodThatHasUnionParameterType.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\ObjectEquals; + +use stdClass; + +final class ValueObjectWithEqualsMethodThatHasUnionParameterType +{ + private $value; + + public function __construct(int $value) + { + $this->value = $value; + } + + public function equals(self|stdClass $other): bool + { + return false; + } + + public function asInt(): int + { + return $this->value; + } +} diff --git a/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodWithNullableReturnType.php b/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodWithNullableReturnType.php new file mode 100644 index 00000000000..a93dd17bcc4 --- /dev/null +++ b/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodWithNullableReturnType.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\ObjectEquals; + +final class ValueObjectWithEqualsMethodWithNullableReturnType +{ + private $value; + + public function __construct(int $value) + { + $this->value = $value; + } + + public function equals(self $other): ?bool + { + return null; + } + + public function asInt(): int + { + return $this->value; + } +} diff --git a/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodWithUnionReturnType.php b/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodWithUnionReturnType.php new file mode 100644 index 00000000000..e5baff4844f --- /dev/null +++ b/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodWithUnionReturnType.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\ObjectEquals; + +final class ValueObjectWithEqualsMethodWithUnionReturnType +{ + private $value; + + public function __construct(int $value) + { + $this->value = $value; + } + + public function equals(self $other): bool|int + { + return 0; + } + + public function asInt(): int + { + return $this->value; + } +} diff --git a/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodWithVoidReturnType.php b/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodWithVoidReturnType.php new file mode 100644 index 00000000000..ddc11c1fd00 --- /dev/null +++ b/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodWithVoidReturnType.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\ObjectEquals; + +final class ValueObjectWithEqualsMethodWithVoidReturnType +{ + private $value; + + public function __construct(int $value) + { + $this->value = $value; + } + + public function equals(self $other): void + { + } + + public function asInt(): int + { + return $this->value; + } +} diff --git a/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodWithoutReturnType.php b/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodWithoutReturnType.php new file mode 100644 index 00000000000..102c12cc12a --- /dev/null +++ b/tests/_files/ObjectEquals/ValueObjectWithEqualsMethodWithoutReturnType.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\ObjectEquals; + +final class ValueObjectWithEqualsMethodWithoutReturnType +{ + private $value; + + public function __construct(int $value) + { + $this->value = $value; + } + + public function equals(self $other) + { + return $this->asInt() === $other->asInt(); + } + + public function asInt(): int + { + return $this->value; + } +} diff --git a/tests/_files/ObjectEquals/ValueObjectWithoutEqualsMethod.php b/tests/_files/ObjectEquals/ValueObjectWithoutEqualsMethod.php new file mode 100644 index 00000000000..1a8f935b475 --- /dev/null +++ b/tests/_files/ObjectEquals/ValueObjectWithoutEqualsMethod.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\ObjectEquals; + +final class ValueObjectWithoutEqualsMethod +{ + private $value; + + public function __construct(int $value) + { + $this->value = $value; + } + + public function asInt(): int + { + return $this->value; + } +} diff --git a/tests/_files/OneClassPerFile/TwoClassesValid.php b/tests/_files/OneClassPerFile/TwoClassesValid.php index a7d7073e514..cd335d64180 100644 --- a/tests/_files/OneClassPerFile/TwoClassesValid.php +++ b/tests/_files/OneClassPerFile/TwoClassesValid.php @@ -7,6 +7,8 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + use PHPUnit\Framework\TestCase; class TwoClassesValid extends Foobar diff --git a/tests/_files/OneClassPerFile/failing/TwoClassesInvalidTest.php b/tests/_files/OneClassPerFile/failing/TwoClassesInvalidTest.php deleted file mode 100644 index 3cffb67818b..00000000000 --- a/tests/_files/OneClassPerFile/failing/TwoClassesInvalidTest.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; - -class TwoClassesInvalid extends TestCase -{ - public function testSomething(): void - { - $this->assertTrue(true); - } -} - -class TwoClassesInvalid2 extends TestCase -{ - public function testSomething(): void - { - $this->assertTrue(true); - } -} diff --git a/tests/_files/OneClassPerFile/phpunit.xml b/tests/_files/OneClassPerFile/phpunit.xml new file mode 100644 index 00000000000..a9ed416d738 --- /dev/null +++ b/tests/_files/OneClassPerFile/phpunit.xml @@ -0,0 +1,9 @@ + + + + + wrongClassName + + + diff --git a/tests/_files/OneClassPerFile/wrongClassName/WrongClassNameTest.php b/tests/_files/OneClassPerFile/wrongClassName/WrongClassNameTest.php index 7004dd44dc4..c16a8a960d4 100644 --- a/tests/_files/OneClassPerFile/wrongClassName/WrongClassNameTest.php +++ b/tests/_files/OneClassPerFile/wrongClassName/WrongClassNameTest.php @@ -7,6 +7,8 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + use PHPUnit\Framework\TestCase; class WrongClassNameBar extends TestCase diff --git a/tests/_files/OutputTestCase.php b/tests/_files/OutputTestCase.php index e7e0b07e4ed..e58f8084cd1 100644 --- a/tests/_files/OutputTestCase.php +++ b/tests/_files/OutputTestCase.php @@ -11,7 +11,7 @@ use PHPUnit\Framework\TestCase; -class OutputTestCase extends TestCase +final class OutputTestCase extends TestCase { public function testExpectOutputStringFooActualFoo(): void { diff --git a/tests/_files/ParentClassWithPrivateAttributes.php b/tests/_files/ParentClassWithPrivateAttributes.php index fb3ce572ea5..0c823151946 100644 --- a/tests/_files/ParentClassWithPrivateAttributes.php +++ b/tests/_files/ParentClassWithPrivateAttributes.php @@ -12,6 +12,5 @@ class ParentClassWithPrivateAttributes { private static $privateStaticParentAttribute = 'foo'; - - private $privateParentAttribute = 'bar'; + private $privateParentAttribute = 'bar'; } diff --git a/tests/_files/ParentClassWithProtectedAttributes.php b/tests/_files/ParentClassWithProtectedAttributes.php index 65d40ab954a..c1d4fb2dde6 100644 --- a/tests/_files/ParentClassWithProtectedAttributes.php +++ b/tests/_files/ParentClassWithProtectedAttributes.php @@ -12,6 +12,5 @@ class ParentClassWithProtectedAttributes extends ParentClassWithPrivateAttributes { protected static $protectedStaticParentAttribute = 'foo'; - - protected $protectedParentAttribute = 'bar'; + protected $protectedParentAttribute = 'bar'; } diff --git a/tests/_files/ParseTestMethodAnnotationsMock.php b/tests/_files/ParseTestMethodAnnotationsMock.php deleted file mode 100644 index 84fb0c20950..00000000000 --- a/tests/_files/ParseTestMethodAnnotationsMock.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -/** - * @theClassAnnotation - */ -class ParseTestMethodAnnotationsMock -{ - use ParseTestMethodAnnotationsTrait; -} - -/** - * @theTraitAnnotation - */ -trait ParseTestMethodAnnotationsTrait -{ -} diff --git a/tests/_files/PartialMockTestClass.php b/tests/_files/PartialMockTestClass.php deleted file mode 100644 index 879b76644ab..00000000000 --- a/tests/_files/PartialMockTestClass.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -class PartialMockTestClass -{ - public $constructorCalled = false; - - public function __construct() - { - $this->constructorCalled = true; - } - - public function doSomething(): void - { - } - - public function doAnotherThing(): void - { - } -} diff --git a/tests/_files/PreConditionAndPostConditionTest.php b/tests/_files/PreConditionAndPostConditionTest.php deleted file mode 100644 index f7ba0b0c361..00000000000 --- a/tests/_files/PreConditionAndPostConditionTest.php +++ /dev/null @@ -1,45 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -final class PreConditionAndPostConditionTest extends TestCase -{ - public static $preConditionWasVerified; - - public static $postConditionWasVerified; - - public static function resetProperties(): void - { - self::$preConditionWasVerified = 0; - self::$postConditionWasVerified = 0; - } - - /** - * @preCondition - */ - public function verifyPreCondition(): void - { - self::$preConditionWasVerified++; - } - - /** - * @postCondition - */ - public function verifyPostCondition(): void - { - self::$postConditionWasVerified++; - } - - public function testSomething(): void - { - } -} diff --git a/tests/_files/RecordingSubscriber.php b/tests/_files/RecordingSubscriber.php new file mode 100644 index 00000000000..dbb348297db --- /dev/null +++ b/tests/_files/RecordingSubscriber.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use function count; +use function end; +use PHPUnit\Event; + +abstract class RecordingSubscriber +{ + /** + * @var array + */ + private array $events = []; + + final public function recordedEventCount(): int + { + return count($this->events); + } + + final public function lastRecordedEvent(): ?Event\Event + { + if ([] === $this->events) { + return null; + } + + return end($this->events); + } + + final protected function record($event): void + { + $this->events[] = $event; + } +} diff --git a/tests/_files/RequirementsClassBeforeClassHookTest.php b/tests/_files/RequirementsClassBeforeClassHookTest.php deleted file mode 100644 index 6dfd411bb62..00000000000 --- a/tests/_files/RequirementsClassBeforeClassHookTest.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use Exception; -use PHPUnit\Framework\TestCase; - -/** - * @requires extension nonExistingExtension - */ -class RequirementsClassBeforeClassHookTest extends TestCase -{ - public static function setUpBeforeClass(): void - { - throw new Exception(__METHOD__ . ' should not be called because of class requirements.'); - } -} diff --git a/tests/_files/RequirementsClassDocBlockTest.php b/tests/_files/RequirementsClassDocBlockTest.php deleted file mode 100644 index 78f082025c2..00000000000 --- a/tests/_files/RequirementsClassDocBlockTest.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -/** - * @requires PHP 5.3 - * @requires PHPUnit 4.0 - * @requires OS Linux - * @requires function testFuncClass - * @requires extension testExtClass - */ -class RequirementsClassDocBlockTest -{ - /** - * @requires PHP 5.4 - * @requires PHPUnit 3.7 - * @requires OS WINNT - * @requires function testFuncMethod - * @requires extension testExtMethod - */ - public function testMethod(): void - { - } -} diff --git a/tests/_files/RequirementsEnvironmentVariableTest.php b/tests/_files/RequirementsEnvironmentVariableTest.php new file mode 100644 index 00000000000..dac8f0249ce --- /dev/null +++ b/tests/_files/RequirementsEnvironmentVariableTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\RequiresEnvironmentVariable; +use PHPUnit\Framework\TestCase; + +final class RequirementsEnvironmentVariableTest extends TestCase +{ + #[RequiresEnvironmentVariable('FOO', 'bar')] + #[RequiresEnvironmentVariable('BAR')] + #[RequiresEnvironmentVariable('BAZ')] + public function testRequiresEnvironmentVariable(): void + { + } +} diff --git a/tests/_files/RequirementsTest.php b/tests/_files/RequirementsTest.php index ab4c3c5e7e6..ccd722f450a 100644 --- a/tests/_files/RequirementsTest.php +++ b/tests/_files/RequirementsTest.php @@ -9,478 +9,365 @@ */ namespace PHPUnit\TestFixture; +use PHPUnit\Framework\Attributes\RequiresFunction; +use PHPUnit\Framework\Attributes\RequiresMethod; +use PHPUnit\Framework\Attributes\RequiresOperatingSystem; +use PHPUnit\Framework\Attributes\RequiresOperatingSystemFamily; +use PHPUnit\Framework\Attributes\RequiresPhp; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; +use PHPUnit\Framework\Attributes\RequiresPhpunit; +use PHPUnit\Framework\Attributes\RequiresPhpunitExtension; +use PHPUnit\Framework\Attributes\RequiresSetting; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\Attributes\Ticket; use PHPUnit\Framework\TestCase; +use ReflectionMethod; -class RequirementsTest extends TestCase +final class RequirementsTest extends TestCase { public function testOne(): void { } - /** - * @requires PHPUnit 1.0 - */ + #[RequiresPhpunit('>= 1.0')] public function testTwo(): void { } - /** - * @requires PHP 2.0 - */ + #[RequiresPhp('>= 2.0')] public function testThree(): void { } - /** - * @requires PHPUnit 2.0 - * @requires PHP 1.0 - */ + #[RequiresPhpunit('>= 2.0')] + #[RequiresPhp('>= 1.0')] public function testFour(): void { } - /** - * @requires PHP 5.4.0RC6 - */ + #[RequiresPhp('5.4.0RC6')] public function testFive(): void { } - /** - * @requires PHP 5.4.0-alpha1 - */ + #[RequiresPhp('5.4.0-alpha1')] public function testSix(): void { } - /** - * @requires PHP 5.4.0beta2 - */ + #[RequiresPhp('5.4.0beta2')] public function testSeven(): void { } - /** - * @requires PHP 5.4-dev - */ + #[RequiresPhp('5.4-dev')] public function testEight(): void { } - /** - * @requires function testFunc - */ + #[RequiresFunction('testFunc')] public function testNine(): void { } - /** - * @requires function testFunc2 - * - * @see https://github.com/sebastianbergmann/phpunit/issues/3459 - */ + #[RequiresFunction('testFunc2')] + #[Ticket('https://github.com/sebastianbergmann/phpunit/issues/3459')] public function testRequiresFunctionWithDigit(): void { } - /** - * @requires extension testExt - */ + #[RequiresPhpExtension('testExt')] public function testTen(): void { } - /** - * @requires OS SunOS - * @requires OSFAMILY Solaris - */ + #[RequiresOperatingSystem('SunOS')] + #[RequiresOperatingSystemFamily('Solaris')] public function testEleven(): void { } - /** - * @requires PHP 99-dev - * @requires PHPUnit 99-dev - * @requires OS DOESNOTEXIST - * @requires function testFuncOne - * @requires function testFunc2 - * @requires extension testExtOne - * @requires extension testExt2 - * @requires extension testExtThree 2.0 - * @requires setting not_a_setting Off - */ + #[RequiresPhp('99-dev')] + #[RequiresPhpunit('99-dev')] + #[RequiresOperatingSystem('DOESNOTEXIST')] + #[RequiresOperatingSystemFamily('DOESNOTEXIST')] + #[RequiresFunction('testFuncOne')] + #[RequiresFunction('testFunc2')] + #[RequiresMethod('DoesNotExist', 'doesNotExist')] + #[RequiresPhpExtension('testExtOne')] + #[RequiresPhpExtension('testExt2')] + #[RequiresPhpExtension('testExtThree', '>= 2.0')] + #[RequiresSetting('not_a_setting', 'Off')] public function testAllPossibleRequirements(): void { } - /** - * @requires function array_merge - */ + #[RequiresFunction('array_merge')] public function testExistingFunction(): void { } - /** - * @requires function ReflectionMethod::setAccessible - */ + #[RequiresMethod(ReflectionMethod::class, 'setAccessible')] public function testExistingMethod(): void { } - /** - * @requires extension spl - */ + #[RequiresPhpExtension('spl')] public function testExistingExtension(): void { } - /** - * @requires OS .* - */ + #[RequiresOperatingSystem('.*')] public function testExistingOs(): void { } - /** - * @requires PHPUnit 1111111 - */ + #[RequiresPhpunit('>= 1111111')] public function testAlwaysSkip(): void { } - /** - * @requires PHP 9999999 - */ + #[RequiresPhp('>= 9999999')] public function testAlwaysSkip2(): void { } - /** - * @requires OS DOESNOTEXIST - */ + #[RequiresOperatingSystem('DOESNOTEXIST')] public function testAlwaysSkip3(): void { } - /** - * @requires OSFAMILY DOESNOTEXIST - */ + #[RequiresOperatingSystemFamily('DOESNOTEXIST')] public function testAlwaysSkip4(): void { } - /** - * @requires extension spl - * @requires OS .* - */ + #[RequiresPhpExtension('spl')] + #[RequiresOperatingSystem('.*')] public function testSpace(): void { } - /** - * @requires extension testExt 1.8.0 - */ + #[RequiresPhpExtension('testExt', '1.8.0')] public function testSpecificExtensionVersion(): void { } - /** - * @testdox PHP version operator less than - * @requires PHP < 5.4 - */ + #[TestDox('PHP version operator less than')] + #[RequiresPhp('< 5.4')] public function testPHPVersionOperatorLessThan(): void { } - /** - * @testdox PHP version operator less than or equals - * @requires PHP <= 5.4 - */ + #[TestDox('PHP version operator less than or equals')] + #[RequiresPhp('<= 5.4')] public function testPHPVersionOperatorLessThanEquals(): void { } - /** - * @testdox PHP version operator greater than - * @requires PHP > 99 - */ + #[TestDox('PHP version operator greater than')] + #[RequiresPhp('> 99')] public function testPHPVersionOperatorGreaterThan(): void { } - /** - * @testdox PHP version operator greater than or equals - * @requires PHP >= 99 - */ + #[TestDox('PHP version operator greater than or equals')] + #[RequiresPhp('>= 99')] public function testPHPVersionOperatorGreaterThanEquals(): void { } - /** - * @testdox PHP version operator equals - * @requires PHP = 5.4 - */ + #[TestDox('PHP version operator equals')] + #[RequiresPhp('= 5.4')] public function testPHPVersionOperatorEquals(): void { } - /** - * @testdox PHP version operator double equals - * @requires PHP == 5.4 - */ + #[TestDox('PHP version operator double equals')] + #[RequiresPhp('== 5.4')] public function testPHPVersionOperatorDoubleEquals(): void { } - /** - * @testdox PHP version operator bang equals - * @requires PHP != 99 - */ + #[TestDox('PHP version operator bang equals')] + #[RequiresPhp('!= 99')] public function testPHPVersionOperatorBangEquals(): void { } - /** - * @testdox PHP version operator not equals - * @requires PHP <> 99 - */ + #[TestDox('PHP version operator not equals')] + #[RequiresPhp('<> 99')] public function testPHPVersionOperatorNotEquals(): void { } - /** - * @testdox PHP version operator no space - * @requires PHP >=99 - */ + #[TestDox('PHP version operator no space')] + #[RequiresPhp('>=99')] public function testPHPVersionOperatorNoSpace(): void { } - /** - * @testdox PHPUnit version operator less than - * @requires PHPUnit < 1.0 - */ + #[TestDox('PHPUnit version operator less than')] + #[RequiresPhpunit('< 1.0')] public function testPHPUnitVersionOperatorLessThan(): void { } - /** - * @testdox PHPUnit version operator less than equals - * @requires PHPUnit <= 1.0 - */ + #[TestDox('PHPUnit version operator less than equals')] + #[RequiresPhpunit('<= 1.0')] public function testPHPUnitVersionOperatorLessThanEquals(): void { } - /** - * @testdox PHPUnit version operator greater than - * @requires PHPUnit > 99 - */ + #[TestDox('PHPUnit version operator greater than')] + #[RequiresPhpunit('> 99')] public function testPHPUnitVersionOperatorGreaterThan(): void { } - /** - * @testdox PHPUnit version operator greater than or equals - * @requires PHPUnit >= 99 - */ + #[TestDox('PHPUnit version operator greater than or equals')] + #[RequiresPhpunit('>= 99')] public function testPHPUnitVersionOperatorGreaterThanEquals(): void { } - /** - * @testdox PHPUnit version operator equals - * @requires PHPUnit = 1.0 - */ + #[TestDox('PHPUnit version operator equals')] + #[RequiresPhpunit('= 1.0')] public function testPHPUnitVersionOperatorEquals(): void { } - /** - * @testdox PHPUnit version operator double equals - * @requires PHPUnit == 1.0 - */ + #[TestDox('PHPUnit version operator double equals')] + #[RequiresPhpunit('== 1.0')] public function testPHPUnitVersionOperatorDoubleEquals(): void { } - /** - * @testdox PHPUnit version operator bang equals - * @requires PHPUnit != 99 - */ + #[TestDox('PHPUnit version operator bang equals')] + #[RequiresPhpunit('!= 99')] public function testPHPUnitVersionOperatorBangEquals(): void { } - /** - * @testdox PHPUnit version operator not equals - * @requires PHPUnit <> 99 - */ + #[TestDox('PHPUnit version operator not equals')] + #[RequiresPhpunit('<> 99')] public function testPHPUnitVersionOperatorNotEquals(): void { } - /** - * @testdox PHPUnit version operator no space - * @requires PHPUnit >=99 - */ + #[TestDox('PHPUnit version operator no space')] + #[RequiresPhpunit('>=99')] public function testPHPUnitVersionOperatorNoSpace(): void { } - /** - * @requires extension testExtOne < 1.0 - */ + #[RequiresPhpExtension('testExtOne', '< 1.0')] public function testExtensionVersionOperatorLessThan(): void { } - /** - * @requires extension testExtOne <= 1.0 - */ + #[RequiresPhpExtension('testExtOne', '<= 1.0')] public function testExtensionVersionOperatorLessThanEquals(): void { } - /** - * @requires extension testExtOne > 99 - */ + #[RequiresPhpExtension('testExtOne', '> 99')] public function testExtensionVersionOperatorGreaterThan(): void { } - /** - * @requires extension testExtOne >= 99 - */ + #[RequiresPhpExtension('testExtOne', '>= 99')] public function testExtensionVersionOperatorGreaterThanEquals(): void { } - /** - * @requires extension testExtOne = 1.0 - */ + #[RequiresPhpExtension('testExtOne', '= 1.0')] public function testExtensionVersionOperatorEquals(): void { } - /** - * @requires extension testExtOne == 1.0 - */ + #[RequiresPhpExtension('testExtOne', '== 1.0')] public function testExtensionVersionOperatorDoubleEquals(): void { } - /** - * @requires extension testExtOne != 99 - */ + #[RequiresPhpExtension('testExtOne', '!= 99')] public function testExtensionVersionOperatorBangEquals(): void { } - /** - * @requires extension testExtOne <> 99 - */ + #[RequiresPhpExtension('testExtOne', '<> 99')] public function testExtensionVersionOperatorNotEquals(): void { } - /** - * @requires extension testExtOne >=99 - */ + #[RequiresPhpExtension('testExtOne', '>= 99')] public function testExtensionVersionOperatorNoSpace(): void { } - /** - * @requires PHP ~1.0 - * @requires PHPUnit ~2.0 - */ + #[RequiresPhp('~1.0')] + #[RequiresPhpunit('~2.0')] public function testVersionConstraintTildeMajor(): void { } - /** - * @requires PHP ^1.0 - * @requires PHPUnit ^2.0 - */ + #[RequiresPhp('^1.0')] + #[RequiresPhpunit('^2.0')] public function testVersionConstraintCaretMajor(): void { } - /** - * @requires PHP ~3.4.7 - * @requires PHPUnit ~4.7.1 - */ + #[RequiresPhp('~3.4.7')] + #[RequiresPhpunit('~4.7.1')] public function testVersionConstraintTildeMinor(): void { } - /** - * @requires PHP ^7.0.17 - * @requires PHPUnit ^4.7.1 - */ + #[RequiresPhp('^7.0.17')] + #[RequiresPhpunit('^4.7.1')] public function testVersionConstraintCaretMinor(): void { } - /** - * @requires PHP ^5.6 || ^7.0 - * @requires PHPUnit ^5.0 || ^6.0 - */ + #[RequiresPhp('^5.6 || ^7.0')] + #[RequiresPhpunit('^5.0 || ^6.0')] public function testVersionConstraintCaretOr(): void { } - /** - * @requires PHP ~5.6.22 || ~7.0.17 - * @requires PHPUnit ^5.0.5 || ^6.0.6 - */ + #[RequiresPhp('~5.6.22 || ~7.0.17')] + #[RequiresPhpunit('~5.0.5 || ~6.0.6')] public function testVersionConstraintTildeOr(): void { } - /** - * @requires PHP ~5.6.22 || ^7.0 - * @requires PHPUnit ~5.6.22 || ^7.0 - */ + #[RequiresPhp('~5.6.22 || ^7.0')] + #[RequiresPhpunit('~5.6.22 || ^7.0')] public function testVersionConstraintTildeOrCaret(): void { } - /** - * @requires PHP ^5.6 || ~7.0.17 - * @requires PHPUnit ^5.6 || ~7.0.17 - */ + #[RequiresPhp('^5.6 || ~7.0.17')] + #[RequiresPhpunit('^5.6 || ~7.0.17')] public function testVersionConstraintCaretOrTilde(): void { } - /** - * @requires PHP ~5.6.22 || ~7.0.17 - * @requires PHPUnit ~5.6.22 || ~7.0.17 - */ + #[RequiresPhp('~5.6.22 || ~7.0.17')] + #[RequiresPhpunit('~5.6.22 || ~7.0.17')] public function testVersionConstraintRegexpIgnoresWhitespace(): void { } - /** - * @requires PHP ~^12345 - */ - public function testVersionConstraintInvalidPhpConstraint(): void - { - } - - /** - * @requires PHPUnit ~^12345 - */ - public function testVersionConstraintInvalidPhpUnitConstraint(): void + #[RequiresSetting('display_errors', 'On')] + public function testSettingDisplayErrorsOn(): void { } - /** - * @requires setting display_errors On - */ - public function testSettingDisplayErrorsOn(): void + #[RequiresPhpunitExtension(SomeExtension::class)] + #[RequiresPhpunitExtension(SomeOtherExtension::class)] + public function testPHPUnitExtensionRequired(): void { } } diff --git a/tests/_files/RouterTest.php b/tests/_files/RouterTest.php index ce8901545eb..3caffc07ca8 100644 --- a/tests/_files/RouterTest.php +++ b/tests/_files/RouterTest.php @@ -10,20 +10,13 @@ namespace PHPUnit\TestFixture; use FooBarHandler; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; final class RouterTest extends TestCase { - /** - * @dataProvider routesProvider - * @testdox Routes $url to $handler - */ - public function testRoutesRequest(string $url, string $handler): void - { - $this->assertTrue(true); - } - - public function routesProvider() + public static function routesProvider(): array { return [ '/foo/bar' => [ @@ -33,4 +26,11 @@ public function routesProvider() ], ]; } + + #[DataProvider('routesProvider')] + #[TestDox('Routes $url to $handler')] + public function testRoutesRequest(string $url, string $handler): void + { + $this->assertTrue(true); + } } diff --git a/tests/_files/SameClassNames/NamespaceOne/MyTest.php b/tests/_files/SameClassNames/NamespaceOne/MyTest.php new file mode 100644 index 00000000000..53dad2d0c52 --- /dev/null +++ b/tests/_files/SameClassNames/NamespaceOne/MyTest.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\NamespaceOne; + +use PHPUnit\Framework\TestCase; + +class MyTest extends TestCase +{ + public function test1of3(): void + { + } +} diff --git a/tests/_files/SameClassNames/NamespaceTwo/MyTest.php b/tests/_files/SameClassNames/NamespaceTwo/MyTest.php new file mode 100644 index 00000000000..e823c86e660 --- /dev/null +++ b/tests/_files/SameClassNames/NamespaceTwo/MyTest.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\NamespaceTwo; + +use PHPUnit\Framework\TestCase; + +class MyTest extends TestCase +{ + public function test2of3(): void + { + } + + public function test3of3(): void + { + } +} diff --git a/tests/_files/SampleArrayAccess.php b/tests/_files/SampleArrayAccess.php index 86b1246a0c4..39dccb3f7e1 100644 --- a/tests/_files/SampleArrayAccess.php +++ b/tests/_files/SampleArrayAccess.php @@ -11,9 +11,9 @@ use ArrayAccess; -class SampleArrayAccess implements ArrayAccess +final class SampleArrayAccess implements ArrayAccess { - private $container; + private array $container; public function __construct() { @@ -29,7 +29,7 @@ public function offsetSet($offset, $value): void } } - public function offsetExists($offset) + public function offsetExists($offset): bool { return isset($this->container[$offset]); } @@ -39,7 +39,7 @@ public function offsetUnset($offset): void unset($this->container[$offset]); } - public function offsetGet($offset) + public function offsetGet($offset): mixed { return $this->container[$offset] ?? null; } diff --git a/tests/_files/SampleClass.php b/tests/_files/SampleClass.php index 404e4633c61..2c3592190ff 100644 --- a/tests/_files/SampleClass.php +++ b/tests/_files/SampleClass.php @@ -9,12 +9,10 @@ */ namespace PHPUnit\TestFixture; -class SampleClass +final class SampleClass { public $a; - public $b; - public $c; public function __construct($a, $b, $c) diff --git a/tests/_files/SeparateProcessesTest.php b/tests/_files/SeparateProcessesTest.php index db62995ddb8..910a1b6a147 100644 --- a/tests/_files/SeparateProcessesTest.php +++ b/tests/_files/SeparateProcessesTest.php @@ -9,12 +9,11 @@ */ namespace PHPUnit\TestFixture; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; use PHPUnit\Framework\TestCase; -/** - * @runTestsInSeparateProcesses - */ -class SeparateProcessesTest extends TestCase +#[RunTestsInSeparateProcesses] +final class SeparateProcessesTest extends TestCase { public function testFoo(): void { diff --git a/tests/_files/Singleton.php b/tests/_files/Singleton.php deleted file mode 100644 index dca22c25c35..00000000000 --- a/tests/_files/Singleton.php +++ /dev/null @@ -1,32 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -class Singleton -{ - private static $uniqueInstance = null; - - public static function getInstance() - { - if (self::$uniqueInstance === null) { - self::$uniqueInstance = new self; - } - - return self::$uniqueInstance; - } - - protected function __construct() - { - } - - private function __clone() - { - } -} diff --git a/tests/_files/SingletonClass.php b/tests/_files/SingletonClass.php deleted file mode 100644 index 01b543e3e3d..00000000000 --- a/tests/_files/SingletonClass.php +++ /dev/null @@ -1,37 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -class SingletonClass -{ - public static function getInstance(): void - { - } - - protected function __construct() - { - } - - private function __sleep(): array - { - } - - private function __wakeup(): void - { - } - - private function __clone() - { - } - - public function doSomething(): void - { - } -} diff --git a/tests/_files/SmallGroupAnnotationsTest.php b/tests/_files/SmallGroupAnnotationsTest.php new file mode 100644 index 00000000000..ac32ff9b314 --- /dev/null +++ b/tests/_files/SmallGroupAnnotationsTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\Ticket; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(CoveredClass::class)] +#[UsesClass(CoveredClass::class)] +#[Group('the-group')] +#[Ticket('the-ticket')] +#[Small] +final class SmallGroupAnnotationsTest extends TestCase +{ + #[Group('another-group')] + #[Ticket('another-ticket')] + public function testOne(): void + { + } +} diff --git a/tests/_files/SmallGroupAttributesTest.php b/tests/_files/SmallGroupAttributesTest.php new file mode 100644 index 00000000000..99caa59d978 --- /dev/null +++ b/tests/_files/SmallGroupAttributesTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\Ticket; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(CoveredClass::class)] +#[UsesClass(CoveredClass::class)] +#[Group('the-group')] +#[Ticket('the-ticket')] +#[Small] +final class SmallGroupAttributesTest extends TestCase +{ + #[Group('another-group')] + #[Ticket('another-ticket')] + public function testOne(): void + { + } +} diff --git a/tests/_files/SomeClass.php b/tests/_files/SomeClass.php deleted file mode 100644 index 43509ce0006..00000000000 --- a/tests/_files/SomeClass.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -class SomeClass -{ - public function doSomething($a, $b) - { - return 'something'; - } - - public function doSomethingElse($c) - { - return 'something else'; - } -} diff --git a/tests/_files/StopOnErrorTestSuite.php b/tests/_files/StopOnErrorTestSuite.php index 1cfb8473e92..f40e0535b4a 100644 --- a/tests/_files/StopOnErrorTestSuite.php +++ b/tests/_files/StopOnErrorTestSuite.php @@ -12,7 +12,7 @@ use Error; use PHPUnit\Framework\TestCase; -class StopOnErrorTestSuite extends TestCase +final class StopOnErrorTestSuite extends TestCase { public function testIncomplete(): void { diff --git a/tests/_files/StopOnWarningTestSuite.php b/tests/_files/StopOnWarningTestSuite.php deleted file mode 100644 index e052a738ac0..00000000000 --- a/tests/_files/StopOnWarningTestSuite.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestSuite; - -class StopOnWarningTestSuite -{ - public static function suite() - { - $suite = new TestSuite('Test Warnings'); - - $suite->addTestSuite(NoTestCases::class); - $suite->addTestSuite(CoverageClassTest::class); - - return $suite; - } -} diff --git a/tests/_files/StopsOnWarningTest.php b/tests/_files/StopsOnWarningTest.php index 7d66d28265b..e4d6a24077f 100644 --- a/tests/_files/StopsOnWarningTest.php +++ b/tests/_files/StopsOnWarningTest.php @@ -11,7 +11,7 @@ use PHPUnit\Framework\TestCase; -class StopsOnWarningTest extends TestCase +final class StopsOnWarningTest extends TestCase { public function testOne(): void { diff --git a/tests/_files/StringableClass.php b/tests/_files/StringableClass.php deleted file mode 100644 index e164a7b91f9..00000000000 --- a/tests/_files/StringableClass.php +++ /dev/null @@ -1,18 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -class StringableClass -{ - public function __toString() - { - return '12345'; - } -} diff --git a/tests/_files/Struct.php b/tests/_files/Struct.php index b0f45158b48..aa831b919d3 100644 --- a/tests/_files/Struct.php +++ b/tests/_files/Struct.php @@ -9,7 +9,7 @@ */ namespace PHPUnit\TestFixture; -class Struct +final class Struct { public $var; diff --git a/tests/_files/Success.php b/tests/_files/Success.php index e7ebb46e4e1..41e717eceda 100644 --- a/tests/_files/Success.php +++ b/tests/_files/Success.php @@ -11,9 +11,9 @@ use PHPUnit\Framework\TestCase; -class Success extends TestCase +final class Success extends TestCase { - protected function runTest(): void + public function testOne(): void { $this->assertTrue(true); } diff --git a/tests/_files/TemplateMethodsTest.php b/tests/_files/TemplateMethodsTest.php index b7111188bb9..5a55140bcb8 100644 --- a/tests/_files/TemplateMethodsTest.php +++ b/tests/_files/TemplateMethodsTest.php @@ -12,7 +12,7 @@ use PHPUnit\Framework\TestCase; use Throwable; -class TemplateMethodsTest extends TestCase +final class TemplateMethodsTest extends TestCase { public static function setUpBeforeClass(): void { @@ -29,31 +29,31 @@ protected function setUp(): void print __METHOD__ . "\n"; } - protected function tearDown(): void + protected function assertPreConditions(): void { print __METHOD__ . "\n"; } - public function testOne(): void + protected function assertPostConditions(): void { print __METHOD__ . "\n"; - $this->assertTrue(true); } - public function testTwo(): void + protected function tearDown(): void { print __METHOD__ . "\n"; - $this->assertTrue(false); } - protected function assertPreConditions(): void + public function testOne(): void { print __METHOD__ . "\n"; + $this->assertTrue(true); } - protected function assertPostConditions(): void + public function testTwo(): void { print __METHOD__ . "\n"; + $this->assertTrue(false); } protected function onNotSuccessfulTest(Throwable $t): void diff --git a/tests/_files/TestAutoreferenced.php b/tests/_files/TestAutoreferenced.php deleted file mode 100644 index 2ef5d9a803f..00000000000 --- a/tests/_files/TestAutoreferenced.php +++ /dev/null @@ -1,22 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -class TestAutoreferenced extends TestCase -{ - public $myTestData; - - public function testJsonEncodeException($data): void - { - $this->myTestData = $data; - } -} diff --git a/tests/_files/TestCaseTest.php b/tests/_files/TestCaseTest.php new file mode 100644 index 00000000000..adcb7bb84a5 --- /dev/null +++ b/tests/_files/TestCaseTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; + +final class TestCaseTest extends TestCase +{ + public function testOne(): void + { + } + + #[Test] + public function two(): void + { + } + + public function three(): void + { + } + + private function four(): void + { + } +} diff --git a/tests/_files/TestDoxAttributeOnTestClassTest.php b/tests/_files/TestDoxAttributeOnTestClassTest.php new file mode 100644 index 00000000000..c80a893998f --- /dev/null +++ b/tests/_files/TestDoxAttributeOnTestClassTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestDox; + +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[TestDox('Custom Title')] +final class TestDoxAttributeOnTestClassTest extends TestCase +{ +} diff --git a/tests/_files/TestDoxTest.php b/tests/_files/TestDoxTest.php new file mode 100644 index 00000000000..a5ab4a1c471 --- /dev/null +++ b/tests/_files/TestDoxTest.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use DateTimeImmutable; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\Attributes\TestDoxFormatter; +use PHPUnit\Framework\TestCase; + +final class TestDoxTest extends TestCase +{ + public static function formatter(DateTimeImmutable $date): string + { + return 'This is a custom description: ' . $date->format('Y-m-d'); + } + + public function testOne(): void + { + } + + public function testTwo(): void + { + } + + #[TestDox('This is a custom test description')] + public function testThree(): void + { + } + + #[TestDox('This is a custom test description with placeholders $a $b $f $i $s $o $stdClass $enum $backedEnum $n $empty $default')] + public function testFour(array $a, bool $b, float $f, int $i, string $s, object $o, object $stdClass, $enum, $backedEnum, $n, string $empty, string $default = 'default'): void + { + } + + #[TestDoxFormatter('formatter')] + public function testFive(DateTimeImmutable $date): void + { + } +} diff --git a/tests/_files/TestError.php b/tests/_files/TestError.php new file mode 100644 index 00000000000..415a0b69a67 --- /dev/null +++ b/tests/_files/TestError.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use Exception; +use PHPUnit\Framework\TestCase; + +final class TestError extends TestCase +{ + public function testOne(): void + { + throw new Exception; + } +} diff --git a/tests/_files/TestGeneratorMaker.php b/tests/_files/TestGeneratorMaker.php index f6b89818a37..1fbf8a4a361 100644 --- a/tests/_files/TestGeneratorMaker.php +++ b/tests/_files/TestGeneratorMaker.php @@ -9,7 +9,7 @@ */ namespace PHPUnit\TestFixture; -class TestGeneratorMaker +final class TestGeneratorMaker { public function create($array = []) { diff --git a/tests/_files/TestIncomplete.php b/tests/_files/TestIncomplete.php index 5219f9bc0f5..cffe5587fab 100644 --- a/tests/_files/TestIncomplete.php +++ b/tests/_files/TestIncomplete.php @@ -11,9 +11,9 @@ use PHPUnit\Framework\TestCase; -class TestIncomplete extends TestCase +final class TestIncomplete extends TestCase { - protected function runTest(): void + public function testOne(): void { $this->markTestIncomplete('Incomplete test'); } diff --git a/tests/_files/TestIterator.php b/tests/_files/TestIterator.php index 5ee7dfd3a65..f74de3d1cfd 100644 --- a/tests/_files/TestIterator.php +++ b/tests/_files/TestIterator.php @@ -12,13 +12,12 @@ use function count; use Iterator; -class TestIterator implements Iterator +final class TestIterator implements Iterator { - protected $array; + private array $array; + private int $position = 0; - protected $position = 0; - - public function __construct($array = []) + public function __construct(array $array = []) { $this->array = $array; } @@ -28,17 +27,17 @@ public function rewind(): void $this->position = 0; } - public function valid() + public function valid(): bool { return $this->position < count($this->array); } - public function key() + public function key(): int|string { return $this->position; } - public function current() + public function current(): mixed { return $this->array[$this->position]; } diff --git a/tests/_files/TestIterator2.php b/tests/_files/TestIterator2.php index 03300b6ac32..cb225d5995b 100644 --- a/tests/_files/TestIterator2.php +++ b/tests/_files/TestIterator2.php @@ -15,16 +15,16 @@ use function reset; use Iterator; -class TestIterator2 implements Iterator +final class TestIterator2 implements Iterator { - protected $data; + private array $data; public function __construct(array $array) { $this->data = $array; } - public function current() + public function current(): mixed { return current($this->data); } @@ -34,12 +34,12 @@ public function next(): void next($this->data); } - public function key() + public function key(): null|int|string { return key($this->data); } - public function valid() + public function valid(): bool { return key($this->data) !== null; } diff --git a/tests/_files/TestIteratorAggregate.php b/tests/_files/TestIteratorAggregate.php index 0fa858ea475..e13d7ade16d 100644 --- a/tests/_files/TestIteratorAggregate.php +++ b/tests/_files/TestIteratorAggregate.php @@ -12,16 +12,16 @@ use IteratorAggregate; use Traversable; -class TestIteratorAggregate implements IteratorAggregate +final class TestIteratorAggregate implements IteratorAggregate { - private $traversable; + private Traversable $traversable; public function __construct(Traversable $traversable) { $this->traversable = $traversable; } - public function getIterator() + public function getIterator(): Traversable { return $this->traversable; } diff --git a/tests/_files/TestIteratorAggregate2.php b/tests/_files/TestIteratorAggregate2.php index b31bd9dd4c4..9a14ec6cb5c 100644 --- a/tests/_files/TestIteratorAggregate2.php +++ b/tests/_files/TestIteratorAggregate2.php @@ -12,16 +12,16 @@ use IteratorAggregate; use Traversable; -class TestIteratorAggregate2 implements IteratorAggregate +final class TestIteratorAggregate2 implements IteratorAggregate { - private $traversable; + private Traversable $traversable; public function __construct(Traversable $traversable) { $this->traversable = $traversable; } - public function getIterator() + public function getIterator(): Traversable { return $this->traversable; } diff --git a/tests/_files/TestProxyFixture.php b/tests/_files/TestProxyFixture.php deleted file mode 100644 index 9c3f4387cc0..00000000000 --- a/tests/_files/TestProxyFixture.php +++ /dev/null @@ -1,53 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use stdClass; - -class TestProxyFixture -{ - public function returnString() - { - return 'result'; - } - - public function returnTypedString(): string - { - return 'result'; - } - - public function returnObject() - { - $result = new stdClass; - - $result->foo = 'bar'; - - return $result; - } - - public function returnTypedObject(): stdClass - { - $result = new stdClass; - - $result->foo = 'bar'; - - return $result; - } - - public function returnObjectOfFinalClass() - { - return new FinalClass('value'); - } - - public function returnTypedObjectOfFinalClass(): FinalClass - { - return new FinalClass('value'); - } -} diff --git a/tests/_files/TestRisky.php b/tests/_files/TestRisky.php deleted file mode 100644 index e5ecf79775a..00000000000 --- a/tests/_files/TestRisky.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -class TestRisky extends TestCase -{ - protected function runTest(): void - { - $this->markAsRisky(); - } -} diff --git a/tests/_files/TestSkipped.php b/tests/_files/TestSkipped.php deleted file mode 100644 index 5481b99c51f..00000000000 --- a/tests/_files/TestSkipped.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -class TestSkipped extends TestCase -{ - protected function runTest(): void - { - $this->markTestSkipped('Skipped test'); - } -} diff --git a/tests/_files/TestTestError.php b/tests/_files/TestTestError.php deleted file mode 100644 index bfc4f7421af..00000000000 --- a/tests/_files/TestTestError.php +++ /dev/null @@ -1,21 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use Exception; -use PHPUnit\Framework\TestCase; - -class TestError extends TestCase -{ - protected function runTest(): void - { - throw new Exception; - } -} diff --git a/tests/_files/TestWarning.php b/tests/_files/TestWarning.php deleted file mode 100644 index 40553efd233..00000000000 --- a/tests/_files/TestWarning.php +++ /dev/null @@ -1,21 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\Warning; - -class TestWarning extends TestCase -{ - protected function runTest(): void - { - throw new Warning; - } -} diff --git a/tests/_files/TestWithAnnotations.php b/tests/_files/TestWithAnnotations.php deleted file mode 100644 index 700c2b62bc6..00000000000 --- a/tests/_files/TestWithAnnotations.php +++ /dev/null @@ -1,71 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -/** - * @runTestsInSeparateProcesses - * @runClassInSeparateProcess - */ -class TestWithAnnotations extends TestCase -{ - public static function providerMethod() - { - return [[0]]; - } - - /** - * @backupGlobals enabled - */ - public function testThatInteractsWithGlobalVariables(): void - { - } - - /** - * @backupStaticAttributes enabled - */ - public function testThatInteractsWithStaticAttributes(): void - { - } - - /** - * @runInSeparateProcess - * @preserveGlobalState disabled - */ - public function testInSeparateProcess(): void - { - } - - /** - * @backupGlobals enabled - * @dataProvider providerMethod - */ - public function testThatInteractsWithGlobalVariablesWithDataProvider(): void - { - } - - /** - * @backupStaticAttributes enabled - * @dataProvider providerMethod - */ - public function testThatInteractsWithStaticAttributesWithDataProvider(): void - { - } - - /** - * @runInSeparateProcess - * @preserveGlobalState disabled - * @dataProvider providerMethod - */ - public function testInSeparateProcessWithDataProvider(): void - { - } -} diff --git a/tests/_files/TestWithAttributeDataProviderTest.php b/tests/_files/TestWithAttributeDataProviderTest.php new file mode 100644 index 00000000000..64c7fd4c0cf --- /dev/null +++ b/tests/_files/TestWithAttributeDataProviderTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\TestWith; +use PHPUnit\Framework\TestCase; + +final class TestWithAttributeDataProviderTest extends TestCase +{ + #[TestWith(['a', 'b'], 'foo')] + #[TestWith(['c', 'd'], 'bar')] + #[TestWith(['e', 'f'])] + #[TestWith(['g', 'h'])] + public function testWithAttribute($one, $two): void + { + } + + #[TestWith(['a', 'b'], 'foo')] + #[TestWith(['c', 'd'], 'foo')] + public function testWithDuplicateName($one, $two): void + { + } +} diff --git a/tests/_files/TestWithClassLevelIsolationAttributes.php b/tests/_files/TestWithClassLevelIsolationAttributes.php new file mode 100644 index 00000000000..40a6314f12c --- /dev/null +++ b/tests/_files/TestWithClassLevelIsolationAttributes.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestBuilder; + +use PHPUnit\Framework\Attributes\BackupGlobals; +use PHPUnit\Framework\Attributes\BackupStaticProperties; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; +use PHPUnit\Framework\TestCase; + +#[BackupGlobals(true)] +#[BackupStaticProperties(true)] +#[RunTestsInSeparateProcesses] +final class TestWithClassLevelIsolationAttributes extends TestCase +{ + public function testOne(): void + { + } +} diff --git a/tests/_files/TestWithDataProvider.php b/tests/_files/TestWithDataProvider.php new file mode 100644 index 00000000000..f0c2fa7ff23 --- /dev/null +++ b/tests/_files/TestWithDataProvider.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestBuilder; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class TestWithDataProvider extends TestCase +{ + public static function provider(): array + { + return [[0]]; + } + + #[DataProvider('provider')] + public function testOne(int $zero): void + { + } +} diff --git a/tests/_files/TestWithDifferentOutput.php b/tests/_files/TestWithDifferentOutput.php deleted file mode 100644 index 8606cb19fd7..00000000000 --- a/tests/_files/TestWithDifferentOutput.php +++ /dev/null @@ -1,41 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -final class TestWithDifferentOutput extends TestCase -{ - public function testThatDoesNotGenerateOutput(): void - { - $this->assertTrue(true); - } - - public function testThatExpectsOutputRegex(): void - { - $this->expectOutputRegex('.*'); - - print 'Hello!'; - } - - public function testThatExpectsOutputString(): void - { - $this->expectOutputString('Hello!'); - - print 'Hello!'; - } - - public function testThatGeneratesOutput(): void - { - print 'Hello!'; - - $this->assertTrue(true); - } -} diff --git a/tests/_files/TestWithDifferentSizes.php b/tests/_files/TestWithDifferentSizes.php deleted file mode 100644 index 11c65b4b666..00000000000 --- a/tests/_files/TestWithDifferentSizes.php +++ /dev/null @@ -1,72 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -final class TestWithDifferentSizes extends TestCase -{ - public function testWithSizeUnknown(): void - { - $this->assertTrue(true); - } - - /** - * @large - */ - public function testWithSizeLarge(): void - { - $this->assertTrue(true); - } - - /** - * @depends testDataProviderWithSizeMedium - * @medium - */ - public function testWithSizeMedium(): void - { - $this->assertTrue(true); - } - - /** - * @depends testWithSizeLarge - * @small - */ - public function testWithSizeSmall(): void - { - $this->assertTrue(true); - } - - /** - * @dataProvider provider - * @small - */ - public function testDataProviderWithSizeSmall(bool $value): void - { - $this->assertTrue(true); - } - - /** - * @dataProvider provider - * @medium - */ - public function testDataProviderWithSizeMedium(bool $value): void - { - $this->assertTrue(true); - } - - public function provider(): array - { - return [ - [false], - [true], - ]; - } -} diff --git a/tests/_files/TestWithDifferentStatuses.php b/tests/_files/TestWithDifferentStatuses.php deleted file mode 100644 index e20a502ab25..00000000000 --- a/tests/_files/TestWithDifferentStatuses.php +++ /dev/null @@ -1,62 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use Exception; -use PHPUnit\Framework\TestCase; - -final class TestWithDifferentStatuses extends TestCase -{ - public function testThatFails(): void - { - $this->fail(); - } - - public function testThatErrors(): void - { - throw new Exception(); - } - - public function testThatPasses(): void - { - $this->assertTrue(true); - } - - public function testThatIsMarkedAsIncomplete(): void - { - $this->markTestIncomplete(); - } - - public function testThatIsMarkedAsRisky(): void - { - $this->markAsRisky(); - } - - public function testThatIsMarkedAsSkipped(): void - { - $this->markTestSkipped(); - } - - public function testThatAddsAWarning(): void - { - $this->addWarning('Sorry, Dave!'); - } - - public function testWithCreatePartialMockWarning(): void - { - $this->createPartialMock(\PHPUnit\TestFixture\Mockable::class, ['mockableMethod', 'fakeMethod1', 'fakeMethod2']); - } - - public function testWithCreatePartialMockPassesNoWarning(): void - { - $mock = $this->createPartialMock(\PHPUnit\TestFixture\Mockable::class, ['mockableMethod']); - $this->assertNull($mock->mockableMethod()); - } -} diff --git a/tests/_files/TestWithHookMethodsPrioritizedTest.php b/tests/_files/TestWithHookMethodsPrioritizedTest.php new file mode 100644 index 00000000000..96b0afe9c24 --- /dev/null +++ b/tests/_files/TestWithHookMethodsPrioritizedTest.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\After; +use PHPUnit\Framework\Attributes\AfterClass; +use PHPUnit\Framework\Attributes\Before; +use PHPUnit\Framework\Attributes\BeforeClass; +use PHPUnit\Framework\Attributes\PostCondition; +use PHPUnit\Framework\Attributes\PreCondition; +use PHPUnit\Framework\TestCase; + +final class TestWithHookMethodsPrioritizedTest extends TestCase +{ + #[BeforeClass(priority: 1)] + public static function beforeFirstTest(): void + { + } + + #[AfterClass(priority: 6)] + public static function afterLastTest(): void + { + } + + #[Before(priority: 2)] + protected function beforeEachTest(): void + { + } + + #[PreCondition(priority: 3)] + protected function preConditions(): void + { + } + + #[PostCondition(priority: 4)] + protected function postConditions(): void + { + } + + #[After(priority: 5)] + protected function afterEachTest(): void + { + } +} diff --git a/tests/_files/TestWithHookMethodsTest.php b/tests/_files/TestWithHookMethodsTest.php new file mode 100644 index 00000000000..d7f15a8548a --- /dev/null +++ b/tests/_files/TestWithHookMethodsTest.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\After; +use PHPUnit\Framework\Attributes\AfterClass; +use PHPUnit\Framework\Attributes\Before; +use PHPUnit\Framework\Attributes\BeforeClass; +use PHPUnit\Framework\Attributes\PostCondition; +use PHPUnit\Framework\Attributes\PreCondition; +use PHPUnit\Framework\TestCase; + +final class TestWithHookMethodsTest extends TestCase +{ + #[BeforeClass] + public static function beforeFirstTestWithAttribute(): void + { + } + + #[AfterClass] + public static function afterLastTestWithAttribute(): void + { + } + + #[Before] + protected function beforeEachTestWithAttribute(): void + { + } + + #[After] + protected function afterEachTestWithAttribute(): void + { + } + + #[PreCondition] + protected function preConditionsWithAttribute(): void + { + } + + #[PostCondition] + protected function postConditionsWithAttribute(): void + { + } +} diff --git a/tests/_files/TestWithMethodLevelIsolationAttributes.php b/tests/_files/TestWithMethodLevelIsolationAttributes.php new file mode 100644 index 00000000000..2687e906e49 --- /dev/null +++ b/tests/_files/TestWithMethodLevelIsolationAttributes.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestBuilder; + +use PHPUnit\Framework\Attributes\BackupGlobals; +use PHPUnit\Framework\Attributes\BackupStaticProperties; +use PHPUnit\Framework\Attributes\RunInSeparateProcess; +use PHPUnit\Framework\TestCase; + +final class TestWithMethodLevelIsolationAttributes extends TestCase +{ + #[BackupGlobals(true)] + #[BackupStaticProperties(true)] + #[RunInSeparateProcess] + public function testOne(): void + { + } +} diff --git a/tests/_files/TestWithTest.php b/tests/_files/TestWithTest.php deleted file mode 100644 index ba7bd92aaa0..00000000000 --- a/tests/_files/TestWithTest.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -class TestWithTest extends TestCase -{ - public static function providerMethod() - { - return [ - [0, 0, 0], - [0, 1, 1], - [1, 1, 3], - [1, 0, 1], - ]; - } - - /** - * @testWith [0, 0, 0] - * [0, 1, 1] - * [1, 2, 3] - * [20, 22, 42] - */ - public function testAdd($a, $b, $c): void - { - $this->assertEquals($c, $a + $b); - } -} diff --git a/tests/_files/TestWithoutHookMethodsTest.php b/tests/_files/TestWithoutHookMethodsTest.php new file mode 100644 index 00000000000..79cc6b39741 --- /dev/null +++ b/tests/_files/TestWithoutHookMethodsTest.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\TestCase; + +final class TestWithoutHookMethodsTest extends TestCase +{ +} diff --git a/tests/_files/TestWithoutIsolationAttributes.php b/tests/_files/TestWithoutIsolationAttributes.php new file mode 100644 index 00000000000..c6d1498be45 --- /dev/null +++ b/tests/_files/TestWithoutIsolationAttributes.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestBuilder; + +use PHPUnit\Framework\TestCase; + +final class TestWithoutIsolationAttributes extends TestCase +{ + public function testOne(): void + { + } +} diff --git a/tests/_files/TestableCliTestDoxPrinter.php b/tests/_files/TestableCliTestDoxPrinter.php deleted file mode 100644 index 9386ba587b9..00000000000 --- a/tests/_files/TestableCliTestDoxPrinter.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Util\TestDox\CliTestDoxPrinter; - -class TestableCliTestDoxPrinter extends CliTestDoxPrinter -{ - private $buffer; - - public function write(string $text): void - { - $this->buffer .= $text; - } - - public function getBuffer(): string - { - return $this->buffer; - } -} diff --git a/tests/_files/ThrowExceptionTestCase.php b/tests/_files/ThrowExceptionTestCase.php index 6bc1c85953a..0835a0b31e4 100644 --- a/tests/_files/ThrowExceptionTestCase.php +++ b/tests/_files/ThrowExceptionTestCase.php @@ -12,7 +12,7 @@ use PHPUnit\Framework\TestCase; use RuntimeException; -class ThrowExceptionTestCase extends TestCase +final class ThrowExceptionTestCase extends TestCase { public function test(): void { @@ -23,7 +23,7 @@ public function testWithExpectExceptionObject(): void { throw new RuntimeException( 'Cannot compute at this time.', - 9000 + 9000, ); } } diff --git a/tests/_files/ThrowNoExceptionTestCase.php b/tests/_files/ThrowNoExceptionTestCase.php index 953702d59a4..beb5605ba9d 100644 --- a/tests/_files/ThrowNoExceptionTestCase.php +++ b/tests/_files/ThrowNoExceptionTestCase.php @@ -11,7 +11,7 @@ use PHPUnit\Framework\TestCase; -class ThrowNoExceptionTestCase extends TestCase +final class ThrowNoExceptionTestCase extends TestCase { public function test(): void { diff --git a/tests/_files/TraitWithConstructor.php b/tests/_files/TraitWithConstructor.php deleted file mode 100644 index 5505745ea47..00000000000 --- a/tests/_files/TraitWithConstructor.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -trait TraitWithConstructor -{ - private $value; - - public function __construct(string $value) - { - $this->value = $value; - } - - public function value(): string - { - return $this->value; - } -} diff --git a/tests/_files/TraversableMockTestInterface.php b/tests/_files/TraversableMockTestInterface.php deleted file mode 100644 index 685831ff7be..00000000000 --- a/tests/_files/TraversableMockTestInterface.php +++ /dev/null @@ -1,16 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use Traversable; - -interface TraversableMockTestInterface extends Traversable -{ -} diff --git a/tests/_files/VariousDocblockDefinedDataProvider.php b/tests/_files/VariousDocblockDefinedDataProvider.php deleted file mode 100644 index f58db385aef..00000000000 --- a/tests/_files/VariousDocblockDefinedDataProvider.php +++ /dev/null @@ -1,84 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -final class VariousDocblockDefinedDataProvider -{ - /** - * @anotherAnnotation - */ - public function anotherAnnotation(): void - { - } - - /** - * @testWith [1] - */ - public function testWith1(): void - { - } - - /** - * @testWith [1, 2] - * [3, 4] - */ - public function testWith1234(): void - { - } - - /** - * @testWith ["ab"] - * [true] - * [null] - */ - public function testWithABTrueNull(): void - { - } - - /** - * @testWith [1] - * [2] - * @annotation - */ - public function testWith12AndAnotherAnnotation(): void - { - } - - /** - * @testWith [1] - * [2] - * blah blah - */ - public function testWith12AndBlahBlah(): void - { - } - - /** - * @testWith ["\"", "\""] - */ - public function testWithEscapedString(): void - { - } - - /** - * @testWith [s] - */ - public function testWithMalformedValue(): void - { - } - - /** - * @testWith ["valid"] - * [invalid] - */ - public function testWithWellFormedAndMalformedValue(): void - { - } -} diff --git a/tests/_files/VariousIterableDataProviderTest.php b/tests/_files/VariousIterableDataProviderTest.php index cb34e27d22d..cb518069911 100644 --- a/tests/_files/VariousIterableDataProviderTest.php +++ b/tests/_files/VariousIterableDataProviderTest.php @@ -9,9 +9,12 @@ */ namespace PHPUnit\TestFixture; -class VariousIterableDataProviderTest extends AbstractVariousIterableDataProviderTest +use Generator; +use PHPUnit\Framework\Attributes\DataProvider; + +final class VariousIterableDataProviderTest extends AbstractVariousIterableDataProviderTest { - public static function asArrayStaticProvider() + public static function asArrayStaticProvider(): array { return [ ['A'], @@ -20,7 +23,7 @@ public static function asArrayStaticProvider() ]; } - public static function asIteratorStaticProvider() + public static function asIteratorStaticProvider(): Generator { yield ['D']; @@ -29,7 +32,7 @@ public static function asIteratorStaticProvider() yield ['F']; } - public static function asTraversableStaticProvider() + public static function asTraversableStaticProvider(): WrapperIteratorAggregate { return new WrapperIteratorAggregate([ ['G'], @@ -38,16 +41,7 @@ public static function asTraversableStaticProvider() ]); } - /** - * @dataProvider asArrayStaticProvider - * @dataProvider asIteratorStaticProvider - * @dataProvider asTraversableStaticProvider - */ - public function testStatic(): void - { - } - - public function asArrayProvider() + public static function asArrayProvider(): array { return [ ['S'], @@ -56,7 +50,7 @@ public function asArrayProvider() ]; } - public function asIteratorProvider() + public static function asIteratorProvider(): Generator { yield ['V']; @@ -65,7 +59,7 @@ public function asIteratorProvider() yield ['X']; } - public function asTraversableProvider() + public static function asTraversableProvider(): WrapperIteratorAggregate { return new WrapperIteratorAggregate([ ['Y'], @@ -74,21 +68,24 @@ public function asTraversableProvider() ]); } - /** - * @dataProvider asArrayProvider - * @dataProvider asIteratorProvider - * @dataProvider asTraversableProvider - */ - public function testNonStatic(): void + #[DataProvider('asArrayStaticProvider')] + #[DataProvider('asIteratorStaticProvider')] + #[DataProvider('asTraversableStaticProvider')] + public function testStatic(string $x): void + { + } + + #[DataProvider('asArrayProvider')] + #[DataProvider('asIteratorProvider')] + #[DataProvider('asTraversableProvider')] + public function testNonStatic(string $x): void { } - /** - * @dataProvider asArrayProviderInParent - * @dataProvider asIteratorProviderInParent - * @dataProvider asTraversableProviderInParent - */ - public function testFromParent(): void + #[DataProvider('asArrayProviderInParent')] + #[DataProvider('asIteratorProviderInParent')] + #[DataProvider('asTraversableProviderInParent')] + public function testFromParent(string $x): void { } } diff --git a/tests/_files/WasRun.php b/tests/_files/WasRun.php deleted file mode 100644 index ff7f87ae5cd..00000000000 --- a/tests/_files/WasRun.php +++ /dev/null @@ -1,22 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture; - -use PHPUnit\Framework\TestCase; - -class WasRun extends TestCase -{ - public $wasRun = false; - - protected function runTest(): void - { - $this->wasRun = true; - } -} diff --git a/tests/_files/WrapperIteratorAggregate.php b/tests/_files/WrapperIteratorAggregate.php index df781f2a80a..9bb3666bbd3 100644 --- a/tests/_files/WrapperIteratorAggregate.php +++ b/tests/_files/WrapperIteratorAggregate.php @@ -9,25 +9,19 @@ */ namespace PHPUnit\TestFixture; -use function assert; -use function is_array; +use Generator; use IteratorAggregate; -use Traversable; -class WrapperIteratorAggregate implements IteratorAggregate +final class WrapperIteratorAggregate implements IteratorAggregate { - /** - * @var array|Traversable - */ - private $baseCollection; + private iterable $baseCollection; - public function __construct($baseCollection) + public function __construct(iterable $baseCollection) { - assert(is_array($baseCollection) || $baseCollection instanceof Traversable); $this->baseCollection = $baseCollection; } - public function getIterator() + public function getIterator(): Generator { foreach ($this->baseCollection as $k => $v) { yield $k => $v; diff --git a/tests/_files/XmlConfigurationMigration/input-9.2.xml b/tests/_files/XmlConfigurationMigration/input-9.2.xml new file mode 100644 index 00000000000..5ca77be85d2 --- /dev/null +++ b/tests/_files/XmlConfigurationMigration/input-9.2.xml @@ -0,0 +1,38 @@ + + + + + + src + + + src/generated + src/autoload.php + + + src/other + src/some.php + + + + + + + + + + + + + + + + + + + diff --git a/tests/_files/XmlConfigurationMigration/input-9.5.xml b/tests/_files/XmlConfigurationMigration/input-9.5.xml new file mode 100644 index 00000000000..4b65160d283 --- /dev/null +++ b/tests/_files/XmlConfigurationMigration/input-9.5.xml @@ -0,0 +1,69 @@ + + + + + + src + + + + + + + + + Sebastian + + + 22 + April + 19.78 + + + MyTestFile.php + MyRelativePath + true + + + + + + 42 + false + + + + + + + + + + + + bar + + + foo + + + diff --git a/tests/_files/XmlConfigurationMigration/input-issue-5859.xml b/tests/_files/XmlConfigurationMigration/input-issue-5859.xml new file mode 100644 index 00000000000..ee91318f785 --- /dev/null +++ b/tests/_files/XmlConfigurationMigration/input-issue-5859.xml @@ -0,0 +1,5 @@ + + + + diff --git a/tests/_files/XmlConfigurationMigration/input-issue-6087.xml b/tests/_files/XmlConfigurationMigration/input-issue-6087.xml new file mode 100644 index 00000000000..8ba9d4b4814 --- /dev/null +++ b/tests/_files/XmlConfigurationMigration/input-issue-6087.xml @@ -0,0 +1,11 @@ + + + + + tests + + + diff --git a/tests/_files/XmlConfigurationMigration/input-relative-schema-path.xml b/tests/_files/XmlConfigurationMigration/input-relative-schema-path.xml new file mode 100644 index 00000000000..2bd593509cf --- /dev/null +++ b/tests/_files/XmlConfigurationMigration/input-relative-schema-path.xml @@ -0,0 +1,5 @@ + + + + diff --git a/tests/_files/XmlConfigurationMigration/output-9.2.xml b/tests/_files/XmlConfigurationMigration/output-9.2.xml new file mode 100644 index 00000000000..935675cdd20 --- /dev/null +++ b/tests/_files/XmlConfigurationMigration/output-9.2.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + src + + + + src/generated + src/autoload.php + src/other + src/some.php + + + diff --git a/tests/_files/XmlConfigurationMigration/output-9.5.xml b/tests/_files/XmlConfigurationMigration/output-9.5.xml new file mode 100644 index 00000000000..b3f5451fb61 --- /dev/null +++ b/tests/_files/XmlConfigurationMigration/output-9.5.xml @@ -0,0 +1,17 @@ + + + + + + + + src + + + + diff --git a/tests/_files/XmlConfigurationMigration/output-issue-5859.xml b/tests/_files/XmlConfigurationMigration/output-issue-5859.xml new file mode 100644 index 00000000000..d64ecdd3163 --- /dev/null +++ b/tests/_files/XmlConfigurationMigration/output-issue-5859.xml @@ -0,0 +1,5 @@ + + + + diff --git a/tests/_files/XmlConfigurationMigration/output-issue-6087.xml b/tests/_files/XmlConfigurationMigration/output-issue-6087.xml new file mode 100644 index 00000000000..3f026e8929a --- /dev/null +++ b/tests/_files/XmlConfigurationMigration/output-issue-6087.xml @@ -0,0 +1,10 @@ + + + + + tests + + + diff --git a/tests/_files/XmlConfigurationMigration/output-relative-schema-path.xml b/tests/_files/XmlConfigurationMigration/output-relative-schema-path.xml new file mode 100644 index 00000000000..31fa3f9c8a6 --- /dev/null +++ b/tests/_files/XmlConfigurationMigration/output-relative-schema-path.xml @@ -0,0 +1,5 @@ + + + + diff --git a/tests/_files/AbstractTest.php b/tests/_files/abstract/with-test-suffix/AbstractTest.php similarity index 100% rename from tests/_files/AbstractTest.php rename to tests/_files/abstract/with-test-suffix/AbstractTest.php diff --git a/tests/_files/abstract/with-test-suffix/ConcreteTestClassExtendingAbstractTestClassWithTestSuffixTest.php b/tests/_files/abstract/with-test-suffix/ConcreteTestClassExtendingAbstractTestClassWithTestSuffixTest.php new file mode 100644 index 00000000000..f7cf69ad134 --- /dev/null +++ b/tests/_files/abstract/with-test-suffix/ConcreteTestClassExtendingAbstractTestClassWithTestSuffixTest.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +final class ConcreteTestClassExtendingAbstractTestClassWithTestSuffixTest extends AbstractTest +{ +} diff --git a/tests/_files/abstract/without-test-suffix/AbstractTestCase.php b/tests/_files/abstract/without-test-suffix/AbstractTestCase.php new file mode 100644 index 00000000000..24967dfea54 --- /dev/null +++ b/tests/_files/abstract/without-test-suffix/AbstractTestCase.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\TestCase; + +abstract class AbstractTestCase extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/_files/abstract/without-test-suffix/ConcreteTestClassExtendingAbstractTestClassWithoutTestSuffixTest.php b/tests/_files/abstract/without-test-suffix/ConcreteTestClassExtendingAbstractTestClassWithoutTestSuffixTest.php new file mode 100644 index 00000000000..528d679382f --- /dev/null +++ b/tests/_files/abstract/without-test-suffix/ConcreteTestClassExtendingAbstractTestClassWithoutTestSuffixTest.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +final class ConcreteTestClassExtendingAbstractTestClassWithoutTestSuffixTest extends AbstractTestCase +{ +} diff --git a/tests/_files/actualFileFormat.txt b/tests/_files/actualFileFormat.txt new file mode 100644 index 00000000000..ba578e48b18 --- /dev/null +++ b/tests/_files/actualFileFormat.txt @@ -0,0 +1 @@ +BAR diff --git a/tests/_files/bar.txt b/tests/_files/bar.txt new file mode 100644 index 00000000000..351f9261cd1 --- /dev/null +++ b/tests/_files/bar.txt @@ -0,0 +1 @@ +Voulez-vous un café? diff --git a/tests/_files/baseline/FileWithIssues.php b/tests/_files/baseline/FileWithIssues.php new file mode 100644 index 00000000000..13bdbb6af19 --- /dev/null +++ b/tests/_files/baseline/FileWithIssues.php @@ -0,0 +1,11 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +$a = $b; +$b = $c; diff --git a/tests/_files/baseline/expected.xml b/tests/_files/baseline/expected.xml new file mode 100644 index 00000000000..f41e43f5995 --- /dev/null +++ b/tests/_files/baseline/expected.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/tests/_files/configuration-issue-6340.xml b/tests/_files/configuration-issue-6340.xml new file mode 100644 index 00000000000..6d6964b6aa4 --- /dev/null +++ b/tests/_files/configuration-issue-6340.xml @@ -0,0 +1,12 @@ + + + diff --git a/tests/_files/configuration.depends-on-class.xml b/tests/_files/configuration.depends-on-class.xml index 451da96a25f..3eca540590c 100644 --- a/tests/_files/configuration.depends-on-class.xml +++ b/tests/_files/configuration.depends-on-class.xml @@ -1,12 +1,11 @@ - + ../../tests/_files/DependencyOnClassTest.php - ../../tests/_files/DependencyFailureTest.php + ../../tests/_files/dependencies/DependencyFailureTest.php - ../../tests/_files/DependencySuccessTest.php + ../../tests/_files/dependencies/DependencySuccessTest.php diff --git a/tests/_files/configuration.suites.xml b/tests/_files/configuration.suites.xml deleted file mode 100644 index eb9ec3d76e1..00000000000 --- a/tests/_files/configuration.suites.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/tests/_files/configuration.testsuite_no_name.xml b/tests/_files/configuration.testsuite_no_name.xml new file mode 100644 index 00000000000..34b327d3ef6 --- /dev/null +++ b/tests/_files/configuration.testsuite_no_name.xml @@ -0,0 +1,6 @@ + + + + tests + + diff --git a/tests/_files/configuration.xml b/tests/_files/configuration.xml index 889e73299c1..f29010b7b0e 100644 --- a/tests/_files/configuration.xml +++ b/tests/_files/configuration.xml @@ -18,22 +18,21 @@ failOnWarning="false" extensionsDirectory="/tmp" printerClass="PHPUnit\TextUI\DefaultResultPrinter" - testSuiteLoaderClass="PHPUnit\Runner\StandardTestSuiteLoader" defaultTestSuite="My Test Suite" beStrictAboutChangesToGlobalState="false" beStrictAboutOutputDuringTests="false" - beStrictAboutResourceUsageDuringSmallTests="false" beStrictAboutTestsThatDoNotTestAnything="false" - beStrictAboutTodoAnnotatedTests="false" beStrictAboutCoversAnnotation="false" defaultTimeLimit="123" enforceTimeLimit="false" timeoutForSmallTests="1" timeoutForMediumTests="10" timeoutForLargeTests="60" - verbose="false" executionOrder="default" - noInteraction="http://github.com/true"> + controlGarbageCollector="true" + numberOfTestsBeforeGarbageCollection="1000" + shortenArraysForExportThreshold="10" +> /path/to/files @@ -59,56 +58,11 @@ - - - - - - Sebastian - - - 22 - April - 19.78 - - - MyTestFile.php - MyRelativePath - true - - - - - - 42 - false - - - - - - - - - Sebastian - - - 22 - April - 19.78 - - - MyTestFile.php - MyRelativePath - - - - - - 42 - - + + + + diff --git a/tests/_files/configuration_codecoverage.xml b/tests/_files/configuration_codecoverage.xml index 0a7f37a6910..55f1e5653e5 100644 --- a/tests/_files/configuration_codecoverage.xml +++ b/tests/_files/configuration_codecoverage.xml @@ -1,11 +1,7 @@ - - - + + + /path/to/files /path/to/file @@ -19,10 +15,21 @@ /path/to/file + + PHPUnit\TestFixture\DeprecationTrigger\trigger_deprecation + PHPUnit\TestFixture\DeprecationTrigger\DeprecationTrigger::triggerDeprecation + + + + + + diff --git a/tests/_files/configuration_empty.xml b/tests/_files/configuration_empty.xml index 31106ddb9b4..f30eb62cf5f 100644 --- a/tests/_files/configuration_empty.xml +++ b/tests/_files/configuration_empty.xml @@ -1,8 +1,6 @@ - + @@ -19,15 +17,18 @@ + + + + + + + + + + + - - - - - - - - diff --git a/tests/_files/configuration_execution_order_options.xml b/tests/_files/configuration_execution_order_options.xml deleted file mode 100644 index 98e4bbe29e0..00000000000 --- a/tests/_files/configuration_execution_order_options.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - diff --git a/tests/_files/configuration_legacy_codecoverage.xml b/tests/_files/configuration_legacy_codecoverage.xml deleted file mode 100644 index bf6dd886f80..00000000000 --- a/tests/_files/configuration_legacy_codecoverage.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - /path/to/files - /path/to/file - - /path/to/file - - - /path/to/files - /path/to/file - - - - - - - - - - - - - diff --git a/tests/_files/configuration_legacy_logging.xml b/tests/_files/configuration_legacy_logging.xml deleted file mode 100644 index 9495efaeeae..00000000000 --- a/tests/_files/configuration_legacy_logging.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/tests/_files/configuration_logging.xml b/tests/_files/configuration_logging.xml index fcec15f8392..a66e7f22b06 100644 --- a/tests/_files/configuration_logging.xml +++ b/tests/_files/configuration_logging.xml @@ -2,10 +2,9 @@ + - - diff --git a/tests/_files/configuration_testdox.xml b/tests/_files/configuration_testdox.xml index 95f7da6cc55..f01f3ca8ec2 100644 --- a/tests/_files/configuration_testdox.xml +++ b/tests/_files/configuration_testdox.xml @@ -1,2 +1,2 @@ - + diff --git a/tests/_files/configuration_testsuites.xml b/tests/_files/configuration_testsuites.xml index de918896298..b7ce961b927 100644 --- a/tests/_files/configuration_testsuites.xml +++ b/tests/_files/configuration_testsuites.xml @@ -3,12 +3,12 @@ xsi:noNamespaceSchemaLocation="../../phpunit.xsd"> - tests/first + tests/first - tests/second - tests/file.php + tests/second + tests/file.php tests/second/_files diff --git a/tests/_files/configuration_xdebug_filter.xml b/tests/_files/configuration_xdebug_filter.xml deleted file mode 100644 index 2438bf420ef..00000000000 --- a/tests/_files/configuration_xdebug_filter.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - AbstractTest.php - - - Foo.php - - - diff --git a/tests/_files/configuration_xinclude.xml b/tests/_files/configuration_xinclude.xml index 6aedb9e67a0..8e1b12d71e6 100644 --- a/tests/_files/configuration_xinclude.xml +++ b/tests/_files/configuration_xinclude.xml @@ -17,23 +17,18 @@ failOnRisky="false" extensionsDirectory="/tmp" printerClass="PHPUnit\TextUI\DefaultResultPrinter" - testSuiteLoaderClass="PHPUnit\Runner\StandardTestSuiteLoader" defaultTestSuite="My Test Suite" beStrictAboutChangesToGlobalState="false" beStrictAboutOutputDuringTests="false" - beStrictAboutResourceUsageDuringSmallTests="false" beStrictAboutTestsThatDoNotTestAnything="false" - beStrictAboutTodoAnnotatedTests="false" beStrictAboutCoversAnnotation="false" defaultTimeLimit="123" enforceTimeLimit="false" timeoutForSmallTests="1" timeoutForMediumTests="10" timeoutForLargeTests="60" - verbose="false" executionOrder="default" - resolveDependencies="false" - noInteraction="http://github.com/true"> + resolveDependencies="false"> - + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +function delete_directory(string $directory): void +{ + if (\is_file($directory)) { + @\unlink($directory); + + return; + } + + if (!\is_dir($directory)) { + return; + } + + foreach (\glob(\rtrim($directory, '/') . '/*') as $path) { + delete_directory($path); + } + + @\rmdir($directory); +} diff --git a/tests/_files/dependencies/DependencyFailureTest.php b/tests/_files/dependencies/DependencyFailureTest.php new file mode 100644 index 00000000000..06a86e6c2d1 --- /dev/null +++ b/tests/_files/dependencies/DependencyFailureTest.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\Depends; +use PHPUnit\Framework\Attributes\DependsUsingShallowClone; +use PHPUnit\Framework\TestCase; + +class DependencyFailureTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(false); + } + + #[Depends('testOne')] + public function testTwo(): void + { + $this->assertTrue(true); + } + + #[Depends('testTwo')] + public function testThree(): void + { + $this->assertTrue(true); + } + + #[DependsUsingShallowClone('testOne')] + public function testFour(): void + { + $this->assertTrue(true); + } + + #[Depends('doesNotExist')] + public function testHandlesDependencyOnTestMethodThatDoesNotExist(): void + { + $this->assertTrue(true); + } + + #[Depends('')] + public function testHandlesDependencyOnTestMethodWithEmptyName(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/_files/dependencies/DependencySuccessTest.php b/tests/_files/dependencies/DependencySuccessTest.php new file mode 100644 index 00000000000..cc9cd119924 --- /dev/null +++ b/tests/_files/dependencies/DependencySuccessTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\Depends; +use PHPUnit\Framework\Attributes\DependsExternal; +use PHPUnit\Framework\TestCase; + +class DependencySuccessTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } + + #[Depends('testOne')] + public function testTwo(): void + { + $this->assertTrue(true); + } + + #[DependsExternal(self::class, 'testTwo')] + public function testThree(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/_files/deprecation-trigger/DeprecationTrigger.php b/tests/_files/deprecation-trigger/DeprecationTrigger.php new file mode 100644 index 00000000000..d66323d11c6 --- /dev/null +++ b/tests/_files/deprecation-trigger/DeprecationTrigger.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\DeprecationTrigger; + +final class DeprecationTrigger +{ + public function triggerDeprecation(): void + { + } +} diff --git a/tests/_files/deprecation-trigger/trigger_deprecation.php b/tests/_files/deprecation-trigger/trigger_deprecation.php new file mode 100644 index 00000000000..0979dc3976f --- /dev/null +++ b/tests/_files/deprecation-trigger/trigger_deprecation.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\DeprecationTrigger; + +function trigger_deprecation(): void +{ +} diff --git a/tests/_files/empty.xml b/tests/_files/empty.xml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/_files/failure.phpt b/tests/_files/failure.phpt new file mode 100644 index 00000000000..2d76d87f7ab --- /dev/null +++ b/tests/_files/failure.phpt @@ -0,0 +1,7 @@ +--TEST-- +failure +--FILE-- + diff --git a/tests/_files/mock-object/AbstractClass.php b/tests/_files/mock-object/AbstractClass.php new file mode 100644 index 00000000000..b5d8f899e62 --- /dev/null +++ b/tests/_files/mock-object/AbstractClass.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +abstract class AbstractClass +{ + public function doSomething(): bool + { + return $this->doSomethingElse(); + } + + abstract public function doSomethingElse(): bool; +} diff --git a/tests/_files/AnInterface.php b/tests/_files/mock-object/AnInterface.php similarity index 77% rename from tests/_files/AnInterface.php rename to tests/_files/mock-object/AnInterface.php index cef55b94ee2..59d52d323da 100644 --- a/tests/_files/AnInterface.php +++ b/tests/_files/mock-object/AnInterface.php @@ -7,9 +7,9 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\TestFixture; +namespace PHPUnit\TestFixture\MockObject; interface AnInterface { - public function doSomething(); + public function doSomething(): bool; } diff --git a/tests/_files/mock-object/AnInterfaceForIssue5593.php b/tests/_files/mock-object/AnInterfaceForIssue5593.php new file mode 100644 index 00000000000..e084154730c --- /dev/null +++ b/tests/_files/mock-object/AnInterfaceForIssue5593.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +interface AnInterfaceForIssue5593 +{ + public function doSomething(): AnotherInterfaceForIssue5593; +} diff --git a/tests/_files/mock-object/AnotherClassUsingConfigurableMethods.php b/tests/_files/mock-object/AnotherClassUsingConfigurableMethods.php deleted file mode 100644 index 19febbd8dc3..00000000000 --- a/tests/_files/mock-object/AnotherClassUsingConfigurableMethods.php +++ /dev/null @@ -1,22 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture\MockObject; - -use PHPUnit\Framework\MockObject\Api; - -class AnotherClassUsingConfigurableMethods -{ - use Api; - - public static function getConfigurableMethods(): array - { - return static::$__phpunit_configurableMethods; - } -} diff --git a/tests/_files/AnotherInterface.php b/tests/_files/mock-object/AnotherInterface.php similarity index 88% rename from tests/_files/AnotherInterface.php rename to tests/_files/mock-object/AnotherInterface.php index d349e17af0b..4710dd09563 100644 --- a/tests/_files/AnotherInterface.php +++ b/tests/_files/mock-object/AnotherInterface.php @@ -7,7 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\TestFixture; +namespace PHPUnit\TestFixture\MockObject; interface AnotherInterface { diff --git a/tests/_files/mock-object/AnotherInterfaceForIssue5593.php b/tests/_files/mock-object/AnotherInterfaceForIssue5593.php new file mode 100644 index 00000000000..81f695a029f --- /dev/null +++ b/tests/_files/mock-object/AnotherInterfaceForIssue5593.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +interface AnotherInterfaceForIssue5593 +{ + public function doSomethingElse(): static; +} diff --git a/tests/_files/mock-object/AnotherInterfaceThatDoesSomething.php b/tests/_files/mock-object/AnotherInterfaceThatDoesSomething.php new file mode 100644 index 00000000000..ef3f767f70e --- /dev/null +++ b/tests/_files/mock-object/AnotherInterfaceThatDoesSomething.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +interface AnotherInterfaceThatDoesSomething +{ + public function doSomething(); +} diff --git a/tests/_files/mock-object/ChildClass.php b/tests/_files/mock-object/ChildClass.php deleted file mode 100644 index ac981e0ec12..00000000000 --- a/tests/_files/mock-object/ChildClass.php +++ /dev/null @@ -1,14 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture\MockObject; - -class ChildClass extends ParentClass -{ -} diff --git a/tests/_files/mock-object/ClassUsingConfigurableMethods.php b/tests/_files/mock-object/ClassUsingConfigurableMethods.php deleted file mode 100644 index 17ec424eafb..00000000000 --- a/tests/_files/mock-object/ClassUsingConfigurableMethods.php +++ /dev/null @@ -1,22 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture\MockObject; - -use PHPUnit\Framework\MockObject\Api; - -class ClassUsingConfigurableMethods -{ - use Api; - - public static function getConfigurableMethods(): array - { - return static::$__phpunit_configurableMethods; - } -} diff --git a/tests/_files/mock-object/ClassWithImplicitProtocol.php b/tests/_files/mock-object/ClassWithImplicitProtocol.php deleted file mode 100644 index dddd2c1516a..00000000000 --- a/tests/_files/mock-object/ClassWithImplicitProtocol.php +++ /dev/null @@ -1,21 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture\MockObject; - -class ClassWithImplicitProtocol -{ - public function firstCall(): void - { - } - - public function secondCall(): void - { - } -} diff --git a/tests/_files/mock-object/ClassWithoutParentButParentReturnType.php b/tests/_files/mock-object/ClassWithoutParentButParentReturnType.php deleted file mode 100644 index c3aefaa9e6e..00000000000 --- a/tests/_files/mock-object/ClassWithoutParentButParentReturnType.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture\MockObject; - -class ClassWithoutParentButParentReturnType -{ - public function foo(): parent - { - } -} diff --git a/tests/_files/mock-object/Enumeration.php b/tests/_files/mock-object/Enumeration.php new file mode 100644 index 00000000000..3ce989776a4 --- /dev/null +++ b/tests/_files/mock-object/Enumeration.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +enum Enumeration +{ +} diff --git a/tests/_files/mock-object/ExampleTrait.php b/tests/_files/mock-object/ExampleTrait.php new file mode 100644 index 00000000000..36f5f1de0cc --- /dev/null +++ b/tests/_files/mock-object/ExampleTrait.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +trait ExampleTrait +{ + public function ohHai() + { + return __FUNCTION__; + } +} diff --git a/tests/_files/mock-object/ExtendableClass.php b/tests/_files/mock-object/ExtendableClass.php new file mode 100644 index 00000000000..dbe29c1294e --- /dev/null +++ b/tests/_files/mock-object/ExtendableClass.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +class ExtendableClass +{ + public bool $constructorCalled = false; + + public function __construct() + { + $this->constructorCalled = true; + } + + public function __destruct() + { + } + + public function doSomething(): bool + { + return $this->doSomethingElse(); + } + + public function doSomethingElse(): bool + { + return false; + } + + final public function finalMethod(): void + { + } + + private function privateMethod(): void + { + } +} diff --git a/tests/_files/mock-object/ExtendableClassCallingMethodInConstructor.php b/tests/_files/mock-object/ExtendableClassCallingMethodInConstructor.php new file mode 100644 index 00000000000..dc380ddd5f9 --- /dev/null +++ b/tests/_files/mock-object/ExtendableClassCallingMethodInConstructor.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +class ExtendableClassCallingMethodInConstructor +{ + public function __construct() + { + $this->reset(); + } + + public function reset(): void + { + } + + public function second(): void + { + $this->reset(); + } +} diff --git a/tests/_files/mock-object/ExtendableClassCallingMethodInDestructor.php b/tests/_files/mock-object/ExtendableClassCallingMethodInDestructor.php new file mode 100644 index 00000000000..8141c83345b --- /dev/null +++ b/tests/_files/mock-object/ExtendableClassCallingMethodInDestructor.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +class ExtendableClassCallingMethodInDestructor +{ + public function __destruct() + { + $this->doSomethingElse(); + } + + public function doSomething(): static + { + return $this; + } + + public function doSomethingElse(): void + { + } +} diff --git a/tests/_files/mock-object/ExtendableClassWithCloneMethod.php b/tests/_files/mock-object/ExtendableClassWithCloneMethod.php new file mode 100644 index 00000000000..54f4c9a2cf6 --- /dev/null +++ b/tests/_files/mock-object/ExtendableClassWithCloneMethod.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +use Exception; + +class ExtendableClassWithCloneMethod +{ + /** + * @throws Exception + */ + public function __clone(): void + { + throw new Exception(__METHOD__); + } + + public function doSomething(): bool + { + return true; + } +} diff --git a/tests/_files/mock-object/ExtendableClassWithConstructorArguments.php b/tests/_files/mock-object/ExtendableClassWithConstructorArguments.php new file mode 100644 index 00000000000..996151caa75 --- /dev/null +++ b/tests/_files/mock-object/ExtendableClassWithConstructorArguments.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +class ExtendableClassWithConstructorArguments +{ + private string $value; + + public function __construct(string $value) + { + $this->value = $value; + } + + public function value(): string + { + return $this->value; + } +} diff --git a/tests/_files/mock-object/ExtendableClassWithPropertyWithGetHook.php b/tests/_files/mock-object/ExtendableClassWithPropertyWithGetHook.php new file mode 100644 index 00000000000..dc5051f8d07 --- /dev/null +++ b/tests/_files/mock-object/ExtendableClassWithPropertyWithGetHook.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +class ExtendableClassWithPropertyWithGetHook +{ + public string $property { + get { + return 'value'; + } + } +} diff --git a/tests/_files/mock-object/ExtendableClassWithPropertyWithSetHook.php b/tests/_files/mock-object/ExtendableClassWithPropertyWithSetHook.php new file mode 100644 index 00000000000..dd72d204a03 --- /dev/null +++ b/tests/_files/mock-object/ExtendableClassWithPropertyWithSetHook.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +class ExtendableClassWithPropertyWithSetHook +{ + public string $property { + set (string $value) { + $this->property = $value; + } + } +} diff --git a/tests/_files/mock-object/ExtendableReadonlyClass.php b/tests/_files/mock-object/ExtendableReadonlyClass.php new file mode 100644 index 00000000000..1a1673ba3ee --- /dev/null +++ b/tests/_files/mock-object/ExtendableReadonlyClass.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +readonly class ExtendableReadonlyClass +{ + public function __construct(private mixed $value) + { + } + + public function value(): mixed + { + return $this->value; + } +} diff --git a/tests/_files/mock-object/ExtendableReadonlyClassWithCloneMethod.php b/tests/_files/mock-object/ExtendableReadonlyClassWithCloneMethod.php new file mode 100644 index 00000000000..5f8d9701c03 --- /dev/null +++ b/tests/_files/mock-object/ExtendableReadonlyClassWithCloneMethod.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +use Exception; + +readonly class ExtendableReadonlyClassWithCloneMethod +{ + /** + * @throws Exception + */ + public function __clone(): void + { + throw new Exception(__METHOD__); + } + + public function doSomething(): bool + { + return true; + } +} diff --git a/tests/_files/FinalClass.php b/tests/_files/mock-object/FinalClass.php similarity index 91% rename from tests/_files/FinalClass.php rename to tests/_files/mock-object/FinalClass.php index abd2eb27142..c08f5fbdcea 100644 --- a/tests/_files/FinalClass.php +++ b/tests/_files/mock-object/FinalClass.php @@ -7,7 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\TestFixture; +namespace PHPUnit\TestFixture\MockObject; final class FinalClass { diff --git a/tests/_files/mock-object/InterfaceWithImplicitProtocol.php b/tests/_files/mock-object/InterfaceWithImplicitProtocol.php new file mode 100644 index 00000000000..6deb63e19b3 --- /dev/null +++ b/tests/_files/mock-object/InterfaceWithImplicitProtocol.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +interface InterfaceWithImplicitProtocol +{ + public function one(): void; + + public function two(): void; +} diff --git a/tests/_files/mock-object/InterfaceWithMethodThatHasDefaultParameterValues.php b/tests/_files/mock-object/InterfaceWithMethodThatHasDefaultParameterValues.php new file mode 100644 index 00000000000..7b10933e685 --- /dev/null +++ b/tests/_files/mock-object/InterfaceWithMethodThatHasDefaultParameterValues.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +interface InterfaceWithMethodThatHasDefaultParameterValues +{ + public function doSomething(int $a, int $b = 1): int; +} diff --git a/tests/_files/mock-object/InterfaceWithMethodThatReturnsSelf.php b/tests/_files/mock-object/InterfaceWithMethodThatReturnsSelf.php new file mode 100644 index 00000000000..794feed342c --- /dev/null +++ b/tests/_files/mock-object/InterfaceWithMethodThatReturnsSelf.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +interface InterfaceWithMethodThatReturnsSelf +{ + public function doSomething(): self; +} diff --git a/tests/_files/mock-object/InterfaceWithMethodThatReturnsStatic.php b/tests/_files/mock-object/InterfaceWithMethodThatReturnsStatic.php new file mode 100644 index 00000000000..fd806494803 --- /dev/null +++ b/tests/_files/mock-object/InterfaceWithMethodThatReturnsStatic.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +interface InterfaceWithMethodThatReturnsStatic +{ + public function doSomething(): static; +} diff --git a/tests/_files/mock-object/InterfaceWithNeverReturningMethod.php b/tests/_files/mock-object/InterfaceWithNeverReturningMethod.php new file mode 100644 index 00000000000..7d151a486d7 --- /dev/null +++ b/tests/_files/mock-object/InterfaceWithNeverReturningMethod.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +interface InterfaceWithNeverReturningMethod +{ + public function m(): never; +} diff --git a/tests/_files/mock-object/InterfaceWithPropertyWithGetHook.php b/tests/_files/mock-object/InterfaceWithPropertyWithGetHook.php new file mode 100644 index 00000000000..33578e78da7 --- /dev/null +++ b/tests/_files/mock-object/InterfaceWithPropertyWithGetHook.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +interface InterfaceWithPropertyWithGetHook +{ + public string $property { get; } +} diff --git a/tests/_files/mock-object/InterfaceWithPropertyWithSetHook.php b/tests/_files/mock-object/InterfaceWithPropertyWithSetHook.php new file mode 100644 index 00000000000..c455fea1fbe --- /dev/null +++ b/tests/_files/mock-object/InterfaceWithPropertyWithSetHook.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +interface InterfaceWithPropertyWithSetHook +{ + public string $property { set; } +} diff --git a/tests/_files/mock-object/InterfaceWithReturnTypeDeclaration.php b/tests/_files/mock-object/InterfaceWithReturnTypeDeclaration.php new file mode 100644 index 00000000000..b4f474cc3c4 --- /dev/null +++ b/tests/_files/mock-object/InterfaceWithReturnTypeDeclaration.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +interface InterfaceWithReturnTypeDeclaration +{ + public function __toString(): string; + + public function doSomething(): bool; + + public function doSomethingElse(int $x): int; + + public function selfReference(): self; + + public function returnsNullOrString(): ?string; +} diff --git a/tests/_files/mock-object/Issue6174.php b/tests/_files/mock-object/Issue6174.php new file mode 100644 index 00000000000..dd3d9c32484 --- /dev/null +++ b/tests/_files/mock-object/Issue6174.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +interface Issue6174 +{ + public function methodNullDefault(?string $param, ?string $nullDefault = null): string; + + public function methodStringDefault(?string $param, ?string $stringDefault = 'something'): string; +} diff --git a/tests/_files/mock-object/MethodWIthVariadicVariables.php b/tests/_files/mock-object/MethodWIthVariadicVariables.php new file mode 100644 index 00000000000..759a08ca766 --- /dev/null +++ b/tests/_files/mock-object/MethodWIthVariadicVariables.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +class MethodWIthVariadicVariables +{ + public function testVariadic(string $foo, mixed ...$arguments): array + { + return [$foo, ...$arguments]; + } +} diff --git a/tests/_files/mock-object/MockClassGenerated.tpl b/tests/_files/mock-object/MockClassGenerated.tpl deleted file mode 100644 index f50883ac8eb..00000000000 --- a/tests/_files/mock-object/MockClassGenerated.tpl +++ /dev/null @@ -1,13 +0,0 @@ -namespace PHPUnit\TestFixture\MockObject; - -use PHPUnit\Framework\MockObject\Api; - -class MockClassGenerated -{ - use Api; - - public static function getConfigurableMethods(): array - { - return static::$__phpunit_configurableMethods; - } -} diff --git a/tests/_files/mock-object/MockClassWithConfigurableMethods.php b/tests/_files/mock-object/MockClassWithConfigurableMethods.php deleted file mode 100644 index 0c86f12c91e..00000000000 --- a/tests/_files/mock-object/MockClassWithConfigurableMethods.php +++ /dev/null @@ -1,22 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture\MockObject; - -use PHPUnit\Framework\MockObject\Api; - -class MockClassWithConfigurableMethods -{ - use Api; - - public static function getConfigurableMethods(): array - { - return static::$__phpunit_configurableMethods; - } -} diff --git a/tests/_files/mock-object/MockTraitGenerated.tpl b/tests/_files/mock-object/MockTraitGenerated.tpl deleted file mode 100644 index 05092777a61..00000000000 --- a/tests/_files/mock-object/MockTraitGenerated.tpl +++ /dev/null @@ -1,5 +0,0 @@ -namespace PHPUnit\TestFixture\MockObject; - -trait MockTraitGenerated -{ -} diff --git a/tests/_files/mock-object/ParentClass.php b/tests/_files/mock-object/ParentClass.php deleted file mode 100644 index 1168c69eff5..00000000000 --- a/tests/_files/mock-object/ParentClass.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture\MockObject; - -class ParentClass -{ - public function foo(): void - { - } -} diff --git a/tests/_files/mock-object/ReinitializeConfigurableMethods.php b/tests/_files/mock-object/ReinitializeConfigurableMethods.php deleted file mode 100644 index 54fca35ead1..00000000000 --- a/tests/_files/mock-object/ReinitializeConfigurableMethods.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TestFixture\MockObject; - -use PHPUnit\Framework\MockObject\Api; - -class ReinitializeConfigurableMethods -{ - use Api; -} diff --git a/tests/_files/mock-object/TestProxyFixture.php b/tests/_files/mock-object/TestProxyFixture.php new file mode 100644 index 00000000000..d41f16dcece --- /dev/null +++ b/tests/_files/mock-object/TestProxyFixture.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +class TestProxyFixture +{ + public function returnString(): string + { + return 'result'; + } +} diff --git a/tests/_files/mock-object/TraitWithConcreteAndAbstractMethod.php b/tests/_files/mock-object/TraitWithConcreteAndAbstractMethod.php new file mode 100644 index 00000000000..3ae3da5f0c1 --- /dev/null +++ b/tests/_files/mock-object/TraitWithConcreteAndAbstractMethod.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +trait TraitWithConcreteAndAbstractMethod +{ + public function concreteMethod(): bool + { + return $this->abstractMethod(); + } + + abstract public function abstractMethod(): bool; +} diff --git a/tests/_files/mock-object/YetAnotherInterface.php b/tests/_files/mock-object/YetAnotherInterface.php new file mode 100644 index 00000000000..1c6cf009f6c --- /dev/null +++ b/tests/_files/mock-object/YetAnotherInterface.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +interface YetAnotherInterface +{ + public function doSomethingElseEntirely(); +} diff --git a/tests/_files/namespace/someNamespaceA/NamespacedClass.php b/tests/_files/namespace/someNamespaceA/NamespacedClass.php index b035c17b905..6beac955481 100644 --- a/tests/_files/namespace/someNamespaceA/NamespacedClass.php +++ b/tests/_files/namespace/someNamespaceA/NamespacedClass.php @@ -7,7 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace someNamespaceA; +namespace PHPUnit\TestFixture\someNamespaceA; class NamespacedClass { diff --git a/tests/_files/namespace/someNamespaceB/NamespacedClass.php b/tests/_files/namespace/someNamespaceB/NamespacedClass.php index e2eb0f22d0b..420b371510b 100644 --- a/tests/_files/namespace/someNamespaceB/NamespacedClass.php +++ b/tests/_files/namespace/someNamespaceB/NamespacedClass.php @@ -7,7 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace someNamespaceB; +namespace PHPUnit\TestFixture\someNamespaceB; class NamespacedClass { diff --git a/tests/_files/phpt-for-coverage.phpt b/tests/_files/phpt-for-coverage.phpt index b72cf052811..2fb623afa49 100644 --- a/tests/_files/phpt-for-coverage.phpt +++ b/tests/_files/phpt-for-coverage.phpt @@ -2,7 +2,7 @@ PHPT for testing coverage --FILE-- publicMethod(); --EXPECT-- diff --git a/tests/_files/phpt-for-multiwhitelist-coverage.phpt b/tests/_files/phpt-for-multiwhitelist-coverage.phpt index 7dff83ea98f..9ac0763dc6b 100644 --- a/tests/_files/phpt-for-multiwhitelist-coverage.phpt +++ b/tests/_files/phpt-for-multiwhitelist-coverage.phpt @@ -2,7 +2,7 @@ PHPT for testing coverage using multiple whitespace arguments --FILE-- publicMethod(); $anotherCoveredClass = new SampleClass(1, 2, 'a'); diff --git a/tests/_files/phpt/invalid/no-expectation-code.phpt b/tests/_files/phpt/invalid/no-expectation-code.phpt new file mode 100644 index 00000000000..d463ae53e15 --- /dev/null +++ b/tests/_files/phpt/invalid/no-expectation-code.phpt @@ -0,0 +1,4 @@ +--TEST-- +Test +--FILE-- + - - - tests - - - diff --git a/tests/_files/phpunit-example-extension/tests/OneTest.php b/tests/_files/phpunit-example-extension/tests/OneTest.php deleted file mode 100644 index 0cb87dcacf8..00000000000 --- a/tests/_files/phpunit-example-extension/tests/OneTest.php +++ /dev/null @@ -1,21 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\ExampleExtension\TestCaseTrait; -use PHPUnit\Framework\TestCase; - -class OneTest extends TestCase -{ - use TestCaseTrait; - - public function testOne(): void - { - $this->assertTrue(true); - } -} diff --git a/tests/_files/phpunit-example-extension/tools/phpunit.d/phpunit-example-extension-3.0.3.phar b/tests/_files/phpunit-example-extension/tools/phpunit.d/phpunit-example-extension-3.0.3.phar deleted file mode 100644 index d0b70ca64c8..00000000000 Binary files a/tests/_files/phpunit-example-extension/tools/phpunit.d/phpunit-example-extension-3.0.3.phar and /dev/null differ diff --git a/tests/_files/skip-if-requires-code-coverage-driver.php b/tests/_files/skip-if-requires-code-coverage-driver.php new file mode 100644 index 00000000000..86d8a6c289a --- /dev/null +++ b/tests/_files/skip-if-requires-code-coverage-driver.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use function explode; +use function extension_loaded; +use function getenv; +use function in_array; +use function ini_get; +use function phpversion; +use function version_compare; +use function xdebug_info; + +if (extension_loaded('pcov')) { + return; +} + +if (!extension_loaded('xdebug')) { + print 'skip: This test requires a code coverage driver'; + + return; +} + +if (version_compare(phpversion('xdebug'), '3.1', '>=') && in_array('coverage', xdebug_info('mode'), true)) { + return; +} + +$mode = getenv('XDEBUG_MODE'); + +if ($mode === false || $mode === '') { + $mode = ini_get('xdebug.mode'); +} + +if ($mode === false || + !in_array('coverage', explode(',', $mode), true)) { + print 'skip: XDEBUG_MODE=coverage or xdebug.mode=coverage has to be set'; +} diff --git a/tests/_files/source-filter/a/PrefixSuffix.php b/tests/_files/source-filter/a/PrefixSuffix.php new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/_files/source-filter/a/c/.hidden/PrefixSuffix.php b/tests/_files/source-filter/a/c/.hidden/PrefixSuffix.php new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/_files/source-filter/a/c/Prefix.php b/tests/_files/source-filter/a/c/Prefix.php new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/_files/source-filter/a/c/PrefixSuffix.php b/tests/_files/source-filter/a/c/PrefixSuffix.php new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/_files/source-filter/a/c/Suffix.php b/tests/_files/source-filter/a/c/Suffix.php new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/_files/source-filter/a/c/d/Prefix.php b/tests/_files/source-filter/a/c/d/Prefix.php new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/_files/source-filter/a/c/d/PrefixSuffix.php b/tests/_files/source-filter/a/c/d/PrefixSuffix.php new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/_files/source-filter/a/c/d/Suffix.php b/tests/_files/source-filter/a/c/d/Suffix.php new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/_files/source-filter/b/PrefixSuffix.php b/tests/_files/source-filter/b/PrefixSuffix.php new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/_files/source-filter/b/e/PrefixExampleSuffix.php b/tests/_files/source-filter/b/e/PrefixExampleSuffix.php new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/_files/source-filter/b/e/PrefixSuffix.php b/tests/_files/source-filter/b/e/PrefixSuffix.php new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/_files/source-filter/b/e/g/PrefixSuffix.php b/tests/_files/source-filter/b/e/g/PrefixSuffix.php new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/_files/source-filter/b/f/PrefixSuffix.php b/tests/_files/source-filter/b/f/PrefixSuffix.php new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/_files/source-filter/b/f/h/PrefixSuffix.php b/tests/_files/source-filter/b/f/h/PrefixSuffix.php new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/_files/success.phpt b/tests/_files/success.phpt new file mode 100644 index 00000000000..d47ea41521b --- /dev/null +++ b/tests/_files/success.phpt @@ -0,0 +1,7 @@ +--TEST-- +success +--FILE-- + - - - - unit - integration - - - - - - - diff --git a/tests/basic/unit/StatusTest.php b/tests/basic/unit/StatusTest.php deleted file mode 100644 index c1715be0ee1..00000000000 --- a/tests/basic/unit/StatusTest.php +++ /dev/null @@ -1,96 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\SelfTest\Basic; - -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\Warning; -use PHPUnit\TestFixture\AnInterface; -use RuntimeException; - -/** - * @covers Foo - * - * @uses Bar - * - * @testdox Test result status with and without message - */ -class StatusTest extends TestCase -{ - public function testSuccess(): void - { - $this->createMock(AnInterface::class); - - $this->assertTrue(true); - } - - public function testFailure(): void - { - $this->assertTrue(false); - } - - public function testError(): void - { - throw new RuntimeException; - } - - public function testIncomplete(): void - { - $this->markTestIncomplete(); - } - - public function testSkipped(): void - { - $this->markTestSkipped(); - } - - public function testRisky(): void - { - } - - public function testWarning(): void - { - throw new Warning; - } - - public function testSuccessWithMessage(): void - { - $this->assertTrue(true, '"success with custom message"'); - } - - public function testFailureWithMessage(): void - { - $this->assertTrue(false, 'failure with custom message'); - } - - public function testErrorWithMessage(): void - { - throw new RuntimeException('error with custom message'); - } - - public function testIncompleteWithMessage(): void - { - $this->markTestIncomplete('incomplete with custom message'); - } - - public function testSkippedWithMessage(): void - { - $this->markTestSkipped('skipped with custom message'); - } - - public function testRiskyWithMessage(): void - { - // Custom messages not implemented for risky status - } - - public function testWarningWithMessage(): void - { - throw new Warning('warning with custom message'); - } -} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 32786972bd3..c54583850e6 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -7,11 +7,46 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -if (!\defined('TEST_FILES_PATH')) { - \define('TEST_FILES_PATH', __DIR__ . \DIRECTORY_SEPARATOR . '_files' . \DIRECTORY_SEPARATOR); +if (!defined('TEST_FILES_PATH')) { + define('TEST_FILES_PATH', __DIR__ . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR); } -\ini_set('precision', '14'); -\ini_set('serialize_precision', '14'); +$composer = file_exists(__DIR__ . '/../vendor/autoload.php'); +$phar = file_exists(__DIR__ . '/autoload.php'); -require_once __DIR__ . '/../vendor/autoload.php'; +if ($composer && $phar) { + print 'More than one test fixture autoloader is available, exiting.' . PHP_EOL; + + exit(1); +} + +if (!$composer && !$phar) { + print 'No test fixture autoloader was registered, exiting.' . PHP_EOL; + + exit(1); +} + +if ($composer) { + if (!defined('PHPUNIT_COMPOSER_INSTALL')) { + define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__) . '/vendor/autoload.php'); + } + + require_once __DIR__ . '/../vendor/autoload.php'; +} + +if ($phar) { + if (!defined('__PHPUNIT_PHAR__')) { + require_once __DIR__ . '/../build/artifacts/phpunit-snapshot.phar'; + } + + require_once __DIR__ . '/autoload.php'; + + $jsonFile = dirname(__DIR__) . '/composer.json'; + $base = dirname($jsonFile); + + foreach (json_decode(file_get_contents($jsonFile), true)['autoload-dev']['files'] as $file) { + require_once $base . DIRECTORY_SEPARATOR . $file; + } +} + +unset($composer, $phar, $jsonFile, $base, $file); diff --git a/tests/end-to-end/_files/BeforeTestMethodWithAttributeTest.php b/tests/end-to-end/_files/BeforeTestMethodWithAttributeTest.php new file mode 100644 index 00000000000..c975d68a36b --- /dev/null +++ b/tests/end-to-end/_files/BeforeTestMethodWithAttributeTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\DeprecatedAnnotationsTestFixture; + +use PHPUnit\Framework\Attributes\Before; +use PHPUnit\Framework\TestCase; + +final class BeforeTestMethodWithAttributeTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } + + #[Before] + protected function beforeMethod(): void + { + } +} diff --git a/tests/end-to-end/_files/BeforeTestMethodWithPrioritizedAttributeTest.php b/tests/end-to-end/_files/BeforeTestMethodWithPrioritizedAttributeTest.php new file mode 100644 index 00000000000..053ee663bcf --- /dev/null +++ b/tests/end-to-end/_files/BeforeTestMethodWithPrioritizedAttributeTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\DeprecatedAnnotationsTestFixture; + +use PHPUnit\Framework\Attributes\Before; +use PHPUnit\Framework\TestCase; + +final class BeforeTestMethodWithPrioritizedAttributeTest extends TestCase +{ + protected function setUp(): void + { + } + + public function testOne(): void + { + $this->assertTrue(true); + } + + #[Before(priority: 1)] + protected function beforeMethodWithHighPriority(): void + { + } + + #[Before(priority: -1)] + protected function beforeMethodWithLowPriority(): void + { + } +} diff --git a/tests/end-to-end/_files/ExpectingExceptionsTest.php b/tests/end-to-end/_files/ExpectingExceptionsTest.php new file mode 100644 index 00000000000..82550140c81 --- /dev/null +++ b/tests/end-to-end/_files/ExpectingExceptionsTest.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use Exception; +use PHPUnit\Framework\TestCase; + +final class ExpectingExceptionsTest extends TestCase +{ + public function testPassesWhenExpectedExceptionIsThrown(): void + { + $this->expectException(Exception::class); + + throw new Exception; + } + + public function testFailsWhenExpectedExceptionIsNotThrown(): void + { + $this->expectException(Exception::class); + } + + public function testPassesWhenExpectedExceptionIsThrownAndHasMessageThatIsOrContainsExpectedMessage(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('message'); + + throw new Exception('message'); + } + + public function testFailsWhenExpectedExceptionIsThrownAndDoesNotHaveMessageThatIsOrContainsExpectedMessage(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('message'); + + throw new Exception; + } + + public function testPassesWhenExpectedExceptionIsThrownAndHasMessageThatMatchesRegularExpression(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessageMatches('/message/'); + + throw new Exception('message'); + } + + public function testFailsWhenExpectedExceptionIsThrownAndDoesNotHaveMessageThatMatchesRegularExpression(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessageMatches('/message/'); + + throw new Exception; + } + + public function testPassesWhenExpectedExceptionIsThrownAndHasExpectedCode(): void + { + $this->expectException(Exception::class); + $this->expectExceptionCode(1234); + + throw new Exception(code: 1234); + } + + public function testFailsWhenExpectedExceptionIsThrownAndDoesNotHaveExpectedCode(): void + { + $this->expectException(Exception::class); + $this->expectExceptionCode(1234); + + throw new Exception; + } + + public function testPassesWhenExpectedExceptionObjectIsThrown(): void + { + $this->expectExceptionObject(new Exception('message', 1234)); + + throw new Exception('message', 1234); + } + + public function testFailsWhenExpectedExceptionObjectIsNotThrown(): void + { + $this->expectExceptionObject(new Exception('message', 1234)); + } +} diff --git a/tests/end-to-end/_files/Extension.php b/tests/end-to-end/_files/Extension.php deleted file mode 100644 index ab1355977b9..00000000000 --- a/tests/end-to-end/_files/Extension.php +++ /dev/null @@ -1,96 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Test; - -use const PHP_EOL; -use function count; -use function func_get_args; -use PHPUnit\Runner\AfterIncompleteTestHook; -use PHPUnit\Runner\AfterLastTestHook; -use PHPUnit\Runner\AfterRiskyTestHook; -use PHPUnit\Runner\AfterSkippedTestHook; -use PHPUnit\Runner\AfterSuccessfulTestHook; -use PHPUnit\Runner\AfterTestErrorHook; -use PHPUnit\Runner\AfterTestFailureHook; -use PHPUnit\Runner\AfterTestHook; -use PHPUnit\Runner\AfterTestWarningHook; -use PHPUnit\Runner\BeforeFirstTestHook; -use PHPUnit\Runner\BeforeTestHook; - -final class Extension implements AfterIncompleteTestHook, AfterLastTestHook, AfterRiskyTestHook, AfterSkippedTestHook, AfterSuccessfulTestHook, AfterTestErrorHook, AfterTestFailureHook, AfterTestHook, AfterTestWarningHook, BeforeFirstTestHook, BeforeTestHook -{ - private $amountOfInjectedArguments = 0; - - public function __construct() - { - $this->amountOfInjectedArguments = count(func_get_args()); - } - - public function tellAmountOfInjectedArguments(): void - { - print __METHOD__ . ': ' . $this->amountOfInjectedArguments . PHP_EOL; - } - - public function executeBeforeFirstTest(): void - { - $this->tellAmountOfInjectedArguments(); - print __METHOD__ . PHP_EOL; - } - - public function executeBeforeTest(string $test): void - { - print __METHOD__ . ': ' . $test . PHP_EOL; - } - - public function executeAfterTest(string $test, float $time): void - { - print __METHOD__ . ': ' . $test . PHP_EOL; - } - - public function executeAfterSuccessfulTest(string $test, float $time): void - { - print __METHOD__ . ': ' . $test . PHP_EOL; - } - - public function executeAfterIncompleteTest(string $test, string $message, float $time): void - { - print __METHOD__ . ': ' . $test . ': ' . $message . PHP_EOL; - } - - public function executeAfterRiskyTest(string $test, string $message, float $time): void - { - print __METHOD__ . ': ' . $test . ': ' . $message . PHP_EOL; - } - - public function executeAfterSkippedTest(string $test, string $message, float $time): void - { - print __METHOD__ . ': ' . $test . ': ' . $message . PHP_EOL; - } - - public function executeAfterTestError(string $test, string $message, float $time): void - { - print __METHOD__ . ': ' . $test . ': ' . $message . PHP_EOL; - } - - public function executeAfterTestFailure(string $test, string $message, float $time): void - { - print __METHOD__ . ': ' . $test . ': ' . $message . PHP_EOL; - } - - public function executeAfterTestWarning(string $test, string $message, float $time): void - { - print __METHOD__ . ': ' . $test . ': ' . $message . PHP_EOL; - } - - public function executeAfterLastTest(): void - { - print __METHOD__ . PHP_EOL; - } -} diff --git a/tests/end-to-end/_files/HookMethodsOrderTest.php b/tests/end-to-end/_files/HookMethodsOrderTest.php new file mode 100644 index 00000000000..882556c0d6c --- /dev/null +++ b/tests/end-to-end/_files/HookMethodsOrderTest.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\After; +use PHPUnit\Framework\Attributes\Before; + +final class HookMethodsOrderTest extends HookMethodsOrderTestCase +{ + protected function setUp(): void + { + } + + protected function tearDown(): void + { + } + + public function testOne(): void + { + $this->assertTrue(true); + } + + #[Before] + protected function beforeSecond(): void + { + } + + #[Before] + protected function beforeFirst(): void + { + } + + #[Before(priority: 1)] + protected function beforeWithPriority(): void + { + } + + #[After] + protected function afterFirst(): void + { + } + + #[After] + protected function afterSecond(): void + { + } + + #[After(priority: 1)] + protected function afterWithPriority(): void + { + } +} diff --git a/tests/end-to-end/_files/HookMethodsOrderTestCase.php b/tests/end-to-end/_files/HookMethodsOrderTestCase.php new file mode 100644 index 00000000000..39f7554c8bd --- /dev/null +++ b/tests/end-to-end/_files/HookMethodsOrderTestCase.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\After; +use PHPUnit\Framework\Attributes\Before; +use PHPUnit\Framework\TestCase; + +abstract class HookMethodsOrderTestCase extends TestCase +{ + #[Before] + protected function beforeInParent(): void + { + } + + #[Before(priority: 1)] + protected function beforeWithPriorityInParent(): void + { + } + + #[After] + protected function afterInParent(): void + { + } + + #[After(priority: 1)] + protected function afterWithPriorityInParent(): void + { + } +} diff --git a/tests/end-to-end/_files/NullPrinter.php b/tests/end-to-end/_files/NullPrinter.php deleted file mode 100644 index 0608c607b58..00000000000 --- a/tests/end-to-end/_files/NullPrinter.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Test; - -use PHPUnit\Framework\TestListenerDefaultImplementation; -use PHPUnit\Framework\TestResult; -use PHPUnit\TextUI\ResultPrinter; - -final class NullPrinter implements ResultPrinter -{ - use TestListenerDefaultImplementation; - - public function printResult(TestResult $result): void - { - } - - public function write(string $buffer): void - { - } -} diff --git a/tests/end-to-end/_files/OutcomesAndIssuesTest.php b/tests/end-to-end/_files/OutcomesAndIssuesTest.php new file mode 100644 index 00000000000..3def81c2f85 --- /dev/null +++ b/tests/end-to-end/_files/OutcomesAndIssuesTest.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use const E_USER_DEPRECATED; +use const E_USER_NOTICE; +use const E_USER_WARNING; +use function trigger_error; +use Exception; +use PHPUnit\Framework\TestCase; + +final class OutcomesAndIssuesTest extends TestCase +{ + public function testSuccessWithoutIssues(): void + { + $this->assertTrue(true); + } + + public function testSuccessWithRisky(): void + { + } + + public function testSuccessWithDeprecation(): void + { + trigger_error('deprecation message', E_USER_DEPRECATED); + + $this->assertTrue(true); + } + + public function testSuccessWithNotice(): void + { + trigger_error('notice message', E_USER_NOTICE); + + $this->assertTrue(true); + } + + public function testSuccessWithWarning(): void + { + trigger_error('warning message', E_USER_WARNING); + + $this->assertTrue(true); + } + + public function testFailWithDeprecation(): void + { + trigger_error('deprecation message', E_USER_DEPRECATED); + + $this->assertTrue(false); + } + + public function testFailWithNotice(): void + { + trigger_error('notice message', E_USER_NOTICE); + + $this->assertTrue(false); + } + + public function testFailWithWarning(): void + { + trigger_error('warning message', E_USER_WARNING); + + $this->assertTrue(false); + } + + public function testErrorWithDeprecation(): void + { + trigger_error('deprecation message', E_USER_DEPRECATED); + + throw new Exception('exception message'); + } + + public function testErrorWithNotice(): void + { + trigger_error('notice message', E_USER_NOTICE); + + throw new Exception('exception message'); + } + + public function testErrorWithWarning(): void + { + trigger_error('warning message', E_USER_WARNING); + + throw new Exception('exception message'); + } + + public function testIncompleteWithDeprecation(): void + { + trigger_error('deprecation message', E_USER_DEPRECATED); + + $this->markTestIncomplete('incomplete message'); + } + + public function testIncompleteWithNotice(): void + { + trigger_error('notice message', E_USER_NOTICE); + + $this->markTestIncomplete('incomplete message'); + } + + public function testIncompleteWithWarning(): void + { + trigger_error('warning message', E_USER_WARNING); + + $this->markTestIncomplete('incomplete message'); + } + + public function testSkippedWithDeprecation(): void + { + trigger_error('deprecation message', E_USER_DEPRECATED); + + $this->markTestSkipped('skipped message'); + } + + public function testSkippedWithNotice(): void + { + trigger_error('notice message', E_USER_NOTICE); + + $this->markTestSkipped('skipped message'); + } + + public function testSkippedWithWarning(): void + { + trigger_error('warning message', E_USER_WARNING); + + $this->markTestSkipped('skipped message'); + } +} diff --git a/tests/end-to-end/_files/TestDataProviderExternalAndDataProviderTest.php b/tests/end-to-end/_files/TestDataProviderExternalAndDataProviderTest.php new file mode 100644 index 00000000000..74f151f4ec5 --- /dev/null +++ b/tests/end-to-end/_files/TestDataProviderExternalAndDataProviderTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\TestCase; + +final class TestDataProviderExternalAndDataProviderTest extends TestCase +{ + public static function externalProvider(): iterable + { + yield 'foo' => ['bar', 'baz']; + } + + public static function provider(): iterable + { + yield 'foo2' => ['bar', 'baz']; + } + + #[DataProvider('provider')] + #[DataProviderExternal(self::class, 'externalProvider')] + public function testWithDifferentProviderTypes($one, $two): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/TestProcessIsolationWithDataProvider.php b/tests/end-to-end/_files/TestProcessIsolationWithDataProvider.php new file mode 100644 index 00000000000..9369eb9010e --- /dev/null +++ b/tests/end-to-end/_files/TestProcessIsolationWithDataProvider.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\RunInSeparateProcess; +use PHPUnit\Framework\TestCase; + +final class TestProcessIsolationWithDataProvider extends TestCase +{ + public static function greetDataProvider(): iterable + { + yield ['Hello world!']; + } + + #[RunInSeparateProcess] + #[DataProvider('greetDataProvider')] + public function testInIsolationWithProvider(string $expected): void + { + $this->assertSame($expected, 'Hello world!'); + } +} diff --git a/tests/end-to-end/_files/TestWithAndTestWithJsonDataProviderTest.php b/tests/end-to-end/_files/TestWithAndTestWithJsonDataProviderTest.php new file mode 100644 index 00000000000..cc86be436c1 --- /dev/null +++ b/tests/end-to-end/_files/TestWithAndTestWithJsonDataProviderTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\TestWith; +use PHPUnit\Framework\Attributes\TestWithJson; +use PHPUnit\Framework\TestCase; + +final class TestWithAndTestWithJsonDataProviderTest extends TestCase +{ + public static function provider(): iterable + { + yield 'foo' => ['bar', 'baz']; + } + + #[TestWith(['a', 'b'], 'foo')] + #[TestWithJson('[1, 2]')] + public function testWithDifferentProviderTypes($one, $two): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/TestWithAttributeAndDataProviderTest.php b/tests/end-to-end/_files/TestWithAttributeAndDataProviderTest.php new file mode 100644 index 00000000000..3fa138c9516 --- /dev/null +++ b/tests/end-to-end/_files/TestWithAttributeAndDataProviderTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestWith; +use PHPUnit\Framework\TestCase; + +final class TestWithAttributeAndDataProviderTest extends TestCase +{ + public static function provider(): iterable + { + yield 'foo' => ['bar', 'baz']; + } + + #[TestWith(['a', 'b'], 'foo')] + #[DataProvider('provider')] + public function testWithDifferentProviderTypes($one, $two): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/TestWithAttributeAndExternalDataProviderTest.php b/tests/end-to-end/_files/TestWithAttributeAndExternalDataProviderTest.php new file mode 100644 index 00000000000..e9803287adc --- /dev/null +++ b/tests/end-to-end/_files/TestWithAttributeAndExternalDataProviderTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\TestWith; +use PHPUnit\Framework\TestCase; + +final class TestWithAttributeAndExternalDataProviderTest extends TestCase +{ + public static function provider(): iterable + { + yield 'foo' => ['bar', 'baz']; + } + + #[TestWith(['a', 'b'], 'foo')] + #[DataProviderExternal(self::class, 'provider')] + public function testWithDifferentProviderTypes($one, $two): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/TestWithJsonAttributeAndDataProviderTest.php b/tests/end-to-end/_files/TestWithJsonAttributeAndDataProviderTest.php new file mode 100644 index 00000000000..f06754d9bcf --- /dev/null +++ b/tests/end-to-end/_files/TestWithJsonAttributeAndDataProviderTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestWithJson; +use PHPUnit\Framework\TestCase; + +final class TestWithJsonAttributeAndDataProviderTest extends TestCase +{ + public static function provider(): iterable + { + yield 'foo' => ['bar', 'baz']; + } + + #[TestWithJson('[1, 2, 3]')] + #[DataProvider('provider')] + public function testWithDifferentProviderTypes($one, $two): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/TraitTargetedWithCoversClassTest.php b/tests/end-to-end/_files/TraitTargetedWithCoversClassTest.php new file mode 100644 index 00000000000..ec3f79aeff2 --- /dev/null +++ b/tests/end-to-end/_files/TraitTargetedWithCoversClassTest.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\DeprecatedAnnotationsTestFixture; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\CoveredTrait; + +#[CoversClass(CoveredTrait::class)] +final class TraitTargetedWithCoversClassTest extends TestCase +{ + public function testSomething(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/TraitTargetedWithCoversTraitTest.php b/tests/end-to-end/_files/TraitTargetedWithCoversTraitTest.php new file mode 100644 index 00000000000..7e8159c9725 --- /dev/null +++ b/tests/end-to-end/_files/TraitTargetedWithCoversTraitTest.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\DeprecatedAnnotationsTestFixture; + +use PHPUnit\Framework\Attributes\CoversTrait; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\CoveredTrait; + +#[CoversTrait(CoveredTrait::class)] +final class TraitTargetedWithCoversTraitTest extends TestCase +{ + public function testSomething(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/TraitTargetedWithUsesClassTest.php b/tests/end-to-end/_files/TraitTargetedWithUsesClassTest.php new file mode 100644 index 00000000000..b06f015557a --- /dev/null +++ b/tests/end-to-end/_files/TraitTargetedWithUsesClassTest.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\DeprecatedAnnotationsTestFixture; + +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\CoveredTrait; + +#[UsesClass(CoveredTrait::class)] +final class TraitTargetedWithUsesClassTest extends TestCase +{ + public function testSomething(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/TraitTargetedWithUsesTraitTest.php b/tests/end-to-end/_files/TraitTargetedWithUsesTraitTest.php new file mode 100644 index 00000000000..699ccc46d72 --- /dev/null +++ b/tests/end-to-end/_files/TraitTargetedWithUsesTraitTest.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\DeprecatedAnnotationsTestFixture; + +use PHPUnit\Framework\Attributes\UsesTrait; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\CoveredTrait; + +#[UsesTrait(CoveredTrait::class)] +final class TraitTargetedWithUsesTraitTest extends TestCase +{ + public function testSomething(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/UnexpectedOutputTest.php b/tests/end-to-end/_files/UnexpectedOutputTest.php new file mode 100644 index 00000000000..560992ae269 --- /dev/null +++ b/tests/end-to-end/_files/UnexpectedOutputTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use function var_dump; +use PHPUnit\Framework\TestCase; + +final class UnexpectedOutputTest extends TestCase +{ + public function testSomething(): void + { + var_dump(['foo' => 'bar']); + + $this->assertSame('something', 'something'); + } +} diff --git a/tests/end-to-end/_files/WithExitTest.php b/tests/end-to-end/_files/WithExitTest.php new file mode 100644 index 00000000000..c3e6c641c5e --- /dev/null +++ b/tests/end-to-end/_files/WithExitTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\TestCase; + +final class WithExitTest extends TestCase +{ + public function testWithMessage(): void + { + $this->assertTrue(true); + + exit('message'); + } + + public function testWithoutMessage(): void + { + $this->assertTrue(true); + + exit(1); + } +} diff --git a/tests/end-to-end/_files/attribute-based-filtering/phpunit.xml b/tests/end-to-end/_files/attribute-based-filtering/phpunit.xml new file mode 100644 index 00000000000..0025e227829 --- /dev/null +++ b/tests/end-to-end/_files/attribute-based-filtering/phpunit.xml @@ -0,0 +1,10 @@ + + + + + tests + + + diff --git a/tests/end-to-end/_files/attribute-based-filtering/src/Foo.php b/tests/end-to-end/_files/attribute-based-filtering/src/Foo.php new file mode 100644 index 00000000000..25481906cf8 --- /dev/null +++ b/tests/end-to-end/_files/attribute-based-filtering/src/Foo.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\AttributeBasedFiltering; + +final class Foo +{ + public function bar(): bool + { + return true; + } +} diff --git a/tests/end-to-end/_files/attribute-based-filtering/src/autoload.php b/tests/end-to-end/_files/attribute-based-filtering/src/autoload.php new file mode 100644 index 00000000000..fcbf8ff4050 --- /dev/null +++ b/tests/end-to-end/_files/attribute-based-filtering/src/autoload.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +require __DIR__ . '/f.php'; + +require __DIR__ . '/Foo.php'; diff --git a/tests/end-to-end/_files/attribute-based-filtering/src/f.php b/tests/end-to-end/_files/attribute-based-filtering/src/f.php new file mode 100644 index 00000000000..0c98490d30e --- /dev/null +++ b/tests/end-to-end/_files/attribute-based-filtering/src/f.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\AttributeBasedFiltering; + +function f(): bool +{ + return true; +} diff --git a/tests/end-to-end/_files/attribute-based-filtering/tests/CoversClassTest.php b/tests/end-to-end/_files/attribute-based-filtering/tests/CoversClassTest.php new file mode 100644 index 00000000000..6a5ecf417b5 --- /dev/null +++ b/tests/end-to-end/_files/attribute-based-filtering/tests/CoversClassTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\AttributeBasedFiltering; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Foo::class)] +final class CoversClassTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/attribute-based-filtering/tests/CoversFunctionTest.php b/tests/end-to-end/_files/attribute-based-filtering/tests/CoversFunctionTest.php new file mode 100644 index 00000000000..db81b6e2853 --- /dev/null +++ b/tests/end-to-end/_files/attribute-based-filtering/tests/CoversFunctionTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\AttributeBasedFiltering; + +use PHPUnit\Framework\Attributes\CoversFunction; +use PHPUnit\Framework\TestCase; + +#[CoversFunction('PHPUnit\TestFixture\AttributeBasedFiltering\f')] +final class CoversFunctionTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/attribute-based-filtering/tests/RequiresPhpExtensionTest.php b/tests/end-to-end/_files/attribute-based-filtering/tests/RequiresPhpExtensionTest.php new file mode 100644 index 00000000000..baec8800d1f --- /dev/null +++ b/tests/end-to-end/_files/attribute-based-filtering/tests/RequiresPhpExtensionTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\AttributeBasedFiltering; + +use PHPUnit\Framework\Attributes\RequiresPhpExtension; +use PHPUnit\Framework\TestCase; + +#[RequiresPhpExtension('standard')] +final class RequiresPhpExtensionTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/attribute-based-filtering/tests/UsesFunctionTest.php b/tests/end-to-end/_files/attribute-based-filtering/tests/UsesFunctionTest.php new file mode 100644 index 00000000000..aad32abfea7 --- /dev/null +++ b/tests/end-to-end/_files/attribute-based-filtering/tests/UsesFunctionTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\AttributeBasedFiltering; + +use PHPUnit\Framework\Attributes\UsesFunction; +use PHPUnit\Framework\TestCase; + +#[UsesFunction('PHPUnit\TestFixture\AttributeBasedFiltering\f')] +final class UsesFunctionTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/attribute-based-filtering/tests/UsesTest.php b/tests/end-to-end/_files/attribute-based-filtering/tests/UsesTest.php new file mode 100644 index 00000000000..cffe7cf28e0 --- /dev/null +++ b/tests/end-to-end/_files/attribute-based-filtering/tests/UsesTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\AttributeBasedFiltering; + +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; + +#[UsesClass(Foo::class)] +final class UsesTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/baseline/generate-baseline-suppressed-with-ignored-suppression/.gitignore b/tests/end-to-end/_files/baseline/generate-baseline-suppressed-with-ignored-suppression/.gitignore new file mode 100644 index 00000000000..8d20d1464c8 --- /dev/null +++ b/tests/end-to-end/_files/baseline/generate-baseline-suppressed-with-ignored-suppression/.gitignore @@ -0,0 +1 @@ +baseline.xml diff --git a/tests/end-to-end/_files/baseline/generate-baseline-suppressed-with-ignored-suppression/phpunit.xml b/tests/end-to-end/_files/baseline/generate-baseline-suppressed-with-ignored-suppression/phpunit.xml new file mode 100644 index 00000000000..303ce33dcfc --- /dev/null +++ b/tests/end-to-end/_files/baseline/generate-baseline-suppressed-with-ignored-suppression/phpunit.xml @@ -0,0 +1,24 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/_files/baseline/generate-baseline-suppressed-with-ignored-suppression/src/Source.php b/tests/end-to-end/_files/baseline/generate-baseline-suppressed-with-ignored-suppression/src/Source.php new file mode 100644 index 00000000000..edb4cc6d5ca --- /dev/null +++ b/tests/end-to-end/_files/baseline/generate-baseline-suppressed-with-ignored-suppression/src/Source.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Baseline; + +use const E_USER_DEPRECATED; +use const E_USER_NOTICE; +use const E_USER_WARNING; +use function trigger_error; +use Serializable; + +final class Source +{ + public function triggerDeprecation(): void + { + $this->deprecation(); + } + + public function triggerNotice(): void + { + $this->notice(); + } + + public function triggerWarning(): void + { + $this->warning(); + } + + public function triggerPhpDeprecation(): void + { + $this->phpDeprecation(); + } + + public function triggerPhpNoticeAndWarning(): void + { + $this->phpNoticeAndWarning(); + } + + private function deprecation(): void + { + @trigger_error('deprecation', E_USER_DEPRECATED); + } + + private function notice(): void + { + @trigger_error('notice', E_USER_NOTICE); + } + + private function warning(): void + { + @trigger_error('warning', E_USER_WARNING); + } + + private function phpDeprecation(): void + { + @$o = new class implements Serializable + { + public function serialize(): void + { + } + + public function unserialize(string $data): void + { + } + }; + } + + private function phpNoticeAndWarning(): void + { + $o = new class + { + public static $a = 'b'; + }; + + @$o->a; + } +} diff --git a/tests/end-to-end/_files/baseline/generate-baseline-suppressed-with-ignored-suppression/tests/Test.php b/tests/end-to-end/_files/baseline/generate-baseline-suppressed-with-ignored-suppression/tests/Test.php new file mode 100644 index 00000000000..4f578129f7c --- /dev/null +++ b/tests/end-to-end/_files/baseline/generate-baseline-suppressed-with-ignored-suppression/tests/Test.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Baseline; + +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testDeprecation(): void + { + (new Source)->triggerDeprecation(); + + $this->assertTrue(true); + } + + public function testNotice(): void + { + (new Source)->triggerNotice(); + + $this->assertTrue(true); + } + + public function testWarning(): void + { + (new Source)->triggerWarning(); + + $this->assertTrue(true); + } + + public function testPhpDeprecation(): void + { + (new Source)->triggerPhpDeprecation(); + + $this->assertTrue(true); + } + + public function testPhpNoticeAndWarning(): void + { + (new Source)->triggerPhpNoticeAndWarning(); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/baseline/generate-baseline-suppressed/.gitignore b/tests/end-to-end/_files/baseline/generate-baseline-suppressed/.gitignore new file mode 100644 index 00000000000..8d20d1464c8 --- /dev/null +++ b/tests/end-to-end/_files/baseline/generate-baseline-suppressed/.gitignore @@ -0,0 +1 @@ +baseline.xml diff --git a/tests/end-to-end/_files/baseline/generate-baseline-suppressed/phpunit.xml b/tests/end-to-end/_files/baseline/generate-baseline-suppressed/phpunit.xml new file mode 100644 index 00000000000..fd8b4b4f0bb --- /dev/null +++ b/tests/end-to-end/_files/baseline/generate-baseline-suppressed/phpunit.xml @@ -0,0 +1,18 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/_files/baseline/generate-baseline-suppressed/src/Source.php b/tests/end-to-end/_files/baseline/generate-baseline-suppressed/src/Source.php new file mode 100644 index 00000000000..edb4cc6d5ca --- /dev/null +++ b/tests/end-to-end/_files/baseline/generate-baseline-suppressed/src/Source.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Baseline; + +use const E_USER_DEPRECATED; +use const E_USER_NOTICE; +use const E_USER_WARNING; +use function trigger_error; +use Serializable; + +final class Source +{ + public function triggerDeprecation(): void + { + $this->deprecation(); + } + + public function triggerNotice(): void + { + $this->notice(); + } + + public function triggerWarning(): void + { + $this->warning(); + } + + public function triggerPhpDeprecation(): void + { + $this->phpDeprecation(); + } + + public function triggerPhpNoticeAndWarning(): void + { + $this->phpNoticeAndWarning(); + } + + private function deprecation(): void + { + @trigger_error('deprecation', E_USER_DEPRECATED); + } + + private function notice(): void + { + @trigger_error('notice', E_USER_NOTICE); + } + + private function warning(): void + { + @trigger_error('warning', E_USER_WARNING); + } + + private function phpDeprecation(): void + { + @$o = new class implements Serializable + { + public function serialize(): void + { + } + + public function unserialize(string $data): void + { + } + }; + } + + private function phpNoticeAndWarning(): void + { + $o = new class + { + public static $a = 'b'; + }; + + @$o->a; + } +} diff --git a/tests/end-to-end/_files/baseline/generate-baseline-suppressed/tests/Test.php b/tests/end-to-end/_files/baseline/generate-baseline-suppressed/tests/Test.php new file mode 100644 index 00000000000..4f578129f7c --- /dev/null +++ b/tests/end-to-end/_files/baseline/generate-baseline-suppressed/tests/Test.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Baseline; + +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testDeprecation(): void + { + (new Source)->triggerDeprecation(); + + $this->assertTrue(true); + } + + public function testNotice(): void + { + (new Source)->triggerNotice(); + + $this->assertTrue(true); + } + + public function testWarning(): void + { + (new Source)->triggerWarning(); + + $this->assertTrue(true); + } + + public function testPhpDeprecation(): void + { + (new Source)->triggerPhpDeprecation(); + + $this->assertTrue(true); + } + + public function testPhpNoticeAndWarning(): void + { + (new Source)->triggerPhpNoticeAndWarning(); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/baseline/generate-baseline-with-relative-directory/.gitignore b/tests/end-to-end/_files/baseline/generate-baseline-with-relative-directory/.gitignore new file mode 100644 index 00000000000..8d20d1464c8 --- /dev/null +++ b/tests/end-to-end/_files/baseline/generate-baseline-with-relative-directory/.gitignore @@ -0,0 +1 @@ +baseline.xml diff --git a/tests/end-to-end/_files/baseline/generate-baseline-with-relative-directory/phpunit.xml b/tests/end-to-end/_files/baseline/generate-baseline-with-relative-directory/phpunit.xml new file mode 100644 index 00000000000..76f34f1f6be --- /dev/null +++ b/tests/end-to-end/_files/baseline/generate-baseline-with-relative-directory/phpunit.xml @@ -0,0 +1,17 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/_files/baseline/generate-baseline-with-relative-directory/tests/Test.php b/tests/end-to-end/_files/baseline/generate-baseline-with-relative-directory/tests/Test.php new file mode 100644 index 00000000000..3ebb12cdfa3 --- /dev/null +++ b/tests/end-to-end/_files/baseline/generate-baseline-with-relative-directory/tests/Test.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Baseline; + +use const E_USER_DEPRECATED; +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testDeprecation(): void + { + trigger_error('deprecation', E_USER_DEPRECATED); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/baseline/generate-baseline/.gitignore b/tests/end-to-end/_files/baseline/generate-baseline/.gitignore new file mode 100644 index 00000000000..8d20d1464c8 --- /dev/null +++ b/tests/end-to-end/_files/baseline/generate-baseline/.gitignore @@ -0,0 +1 @@ +baseline.xml diff --git a/tests/end-to-end/_files/baseline/generate-baseline/phpunit.xml b/tests/end-to-end/_files/baseline/generate-baseline/phpunit.xml new file mode 100644 index 00000000000..fd8b4b4f0bb --- /dev/null +++ b/tests/end-to-end/_files/baseline/generate-baseline/phpunit.xml @@ -0,0 +1,18 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/_files/baseline/generate-baseline/src/Source.php b/tests/end-to-end/_files/baseline/generate-baseline/src/Source.php new file mode 100644 index 00000000000..a4a9779a0dc --- /dev/null +++ b/tests/end-to-end/_files/baseline/generate-baseline/src/Source.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Baseline; + +use const E_USER_DEPRECATED; +use const E_USER_NOTICE; +use const E_USER_WARNING; +use function trigger_error; +use Serializable; + +final class Source +{ + public function triggerDeprecation(): void + { + $this->deprecation(); + } + + public function triggerNotice(): void + { + $this->notice(); + } + + public function triggerWarning(): void + { + $this->warning(); + } + + public function triggerPhpDeprecation(): void + { + $this->phpDeprecation(); + } + + public function triggerPhpNoticeAndWarning(): void + { + $this->phpNoticeAndWarning(); + } + + private function deprecation(): void + { + trigger_error('deprecation', E_USER_DEPRECATED); + } + + private function notice(): void + { + trigger_error('notice', E_USER_NOTICE); + } + + private function warning(): void + { + trigger_error('warning', E_USER_WARNING); + } + + private function phpDeprecation(): void + { + $o = new class implements Serializable + { + public function serialize(): void + { + } + + public function unserialize(string $data): void + { + } + }; + } + + private function phpNoticeAndWarning(): void + { + $o = new class + { + public static $a = 'b'; + }; + + $o->a; + } +} diff --git a/tests/end-to-end/_files/baseline/generate-baseline/tests/Test.php b/tests/end-to-end/_files/baseline/generate-baseline/tests/Test.php new file mode 100644 index 00000000000..4f578129f7c --- /dev/null +++ b/tests/end-to-end/_files/baseline/generate-baseline/tests/Test.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Baseline; + +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testDeprecation(): void + { + (new Source)->triggerDeprecation(); + + $this->assertTrue(true); + } + + public function testNotice(): void + { + (new Source)->triggerNotice(); + + $this->assertTrue(true); + } + + public function testWarning(): void + { + (new Source)->triggerWarning(); + + $this->assertTrue(true); + } + + public function testPhpDeprecation(): void + { + (new Source)->triggerPhpDeprecation(); + + $this->assertTrue(true); + } + + public function testPhpNoticeAndWarning(): void + { + (new Source)->triggerPhpNoticeAndWarning(); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/baseline/invalid-baseline/baseline.xml b/tests/end-to-end/_files/baseline/invalid-baseline/baseline.xml new file mode 100644 index 00000000000..b359f0ed462 --- /dev/null +++ b/tests/end-to-end/_files/baseline/invalid-baseline/baseline.xml @@ -0,0 +1,3 @@ + + + diff --git a/tests/end-to-end/_files/baseline/invalid-baseline/phpunit.xml b/tests/end-to-end/_files/baseline/invalid-baseline/phpunit.xml new file mode 100644 index 00000000000..bf0ee15c129 --- /dev/null +++ b/tests/end-to-end/_files/baseline/invalid-baseline/phpunit.xml @@ -0,0 +1,14 @@ + + + + + tests + + + + + + diff --git a/tests/end-to-end/_files/baseline/invalid-baseline/tests/Test.php b/tests/end-to-end/_files/baseline/invalid-baseline/tests/Test.php new file mode 100644 index 00000000000..8104eb56178 --- /dev/null +++ b/tests/end-to-end/_files/baseline/invalid-baseline/tests/Test.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Baseline; + +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/baseline/unsupported-baseline/baseline.xml b/tests/end-to-end/_files/baseline/unsupported-baseline/baseline.xml new file mode 100644 index 00000000000..1745bb2c8db --- /dev/null +++ b/tests/end-to-end/_files/baseline/unsupported-baseline/baseline.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/tests/end-to-end/_files/baseline/unsupported-baseline/phpunit.xml b/tests/end-to-end/_files/baseline/unsupported-baseline/phpunit.xml new file mode 100644 index 00000000000..bf0ee15c129 --- /dev/null +++ b/tests/end-to-end/_files/baseline/unsupported-baseline/phpunit.xml @@ -0,0 +1,14 @@ + + + + + tests + + + + + + diff --git a/tests/end-to-end/_files/baseline/unsupported-baseline/tests/Test.php b/tests/end-to-end/_files/baseline/unsupported-baseline/tests/Test.php new file mode 100644 index 00000000000..8104eb56178 --- /dev/null +++ b/tests/end-to-end/_files/baseline/unsupported-baseline/tests/Test.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Baseline; + +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/baseline/use-baseline-in-another-directory/phpunit.xml b/tests/end-to-end/_files/baseline/use-baseline-in-another-directory/phpunit.xml new file mode 100644 index 00000000000..76f34f1f6be --- /dev/null +++ b/tests/end-to-end/_files/baseline/use-baseline-in-another-directory/phpunit.xml @@ -0,0 +1,17 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/_files/baseline/use-baseline-in-another-directory/tests/Test.php b/tests/end-to-end/_files/baseline/use-baseline-in-another-directory/tests/Test.php new file mode 100644 index 00000000000..3ebb12cdfa3 --- /dev/null +++ b/tests/end-to-end/_files/baseline/use-baseline-in-another-directory/tests/Test.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Baseline; + +use const E_USER_DEPRECATED; +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testDeprecation(): void + { + trigger_error('deprecation', E_USER_DEPRECATED); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/baseline/use-baseline-in-another-directory/tests/baseline.xml b/tests/end-to-end/_files/baseline/use-baseline-in-another-directory/tests/baseline.xml new file mode 100644 index 00000000000..dd8cbc07e02 --- /dev/null +++ b/tests/end-to-end/_files/baseline/use-baseline-in-another-directory/tests/baseline.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/tests/end-to-end/_files/baseline/use-baseline/baseline.xml b/tests/end-to-end/_files/baseline/use-baseline/baseline.xml new file mode 100644 index 00000000000..47be2a96f2a --- /dev/null +++ b/tests/end-to-end/_files/baseline/use-baseline/baseline.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/end-to-end/_files/baseline/use-baseline/phpunit.xml b/tests/end-to-end/_files/baseline/use-baseline/phpunit.xml new file mode 100644 index 00000000000..fd8b4b4f0bb --- /dev/null +++ b/tests/end-to-end/_files/baseline/use-baseline/phpunit.xml @@ -0,0 +1,18 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/_files/baseline/use-baseline/src/Source.php b/tests/end-to-end/_files/baseline/use-baseline/src/Source.php new file mode 100644 index 00000000000..a4a9779a0dc --- /dev/null +++ b/tests/end-to-end/_files/baseline/use-baseline/src/Source.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Baseline; + +use const E_USER_DEPRECATED; +use const E_USER_NOTICE; +use const E_USER_WARNING; +use function trigger_error; +use Serializable; + +final class Source +{ + public function triggerDeprecation(): void + { + $this->deprecation(); + } + + public function triggerNotice(): void + { + $this->notice(); + } + + public function triggerWarning(): void + { + $this->warning(); + } + + public function triggerPhpDeprecation(): void + { + $this->phpDeprecation(); + } + + public function triggerPhpNoticeAndWarning(): void + { + $this->phpNoticeAndWarning(); + } + + private function deprecation(): void + { + trigger_error('deprecation', E_USER_DEPRECATED); + } + + private function notice(): void + { + trigger_error('notice', E_USER_NOTICE); + } + + private function warning(): void + { + trigger_error('warning', E_USER_WARNING); + } + + private function phpDeprecation(): void + { + $o = new class implements Serializable + { + public function serialize(): void + { + } + + public function unserialize(string $data): void + { + } + }; + } + + private function phpNoticeAndWarning(): void + { + $o = new class + { + public static $a = 'b'; + }; + + $o->a; + } +} diff --git a/tests/end-to-end/_files/baseline/use-baseline/tests/SourceTest.php b/tests/end-to-end/_files/baseline/use-baseline/tests/SourceTest.php new file mode 100644 index 00000000000..fc4126239f9 --- /dev/null +++ b/tests/end-to-end/_files/baseline/use-baseline/tests/SourceTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Baseline; + +use PHPUnit\Framework\TestCase; + +final class SourceTest extends TestCase +{ + public function testDeprecation(): void + { + (new Source)->triggerDeprecation(); + + $this->assertTrue(true); + } + + public function testNotice(): void + { + (new Source)->triggerNotice(); + + $this->assertTrue(true); + } + + public function testWarning(): void + { + (new Source)->triggerWarning(); + + $this->assertTrue(true); + } + + public function testPhpDeprecation(): void + { + (new Source)->triggerPhpDeprecation(); + + $this->assertTrue(true); + } + + public function testPhpNoticeAndWarning(): void + { + (new Source)->triggerPhpNoticeAndWarning(); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/basic/SuccessTest.php b/tests/end-to-end/_files/basic/SuccessTest.php new file mode 100644 index 00000000000..5c3cbdd64d9 --- /dev/null +++ b/tests/end-to-end/_files/basic/SuccessTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Basic; + +use PHPUnit\Framework\TestCase; + +final class SuccessTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/basic/configuration.basic.xml b/tests/end-to-end/_files/basic/configuration.basic.xml new file mode 100644 index 00000000000..cd1df56d581 --- /dev/null +++ b/tests/end-to-end/_files/basic/configuration.basic.xml @@ -0,0 +1,15 @@ + + + + + unit + + + + + + + diff --git a/tests/basic/unit/fixtures/SetUpBeforeClassTest.php b/tests/end-to-end/_files/basic/unit/SetUpBeforeClassTest.php similarity index 93% rename from tests/basic/unit/fixtures/SetUpBeforeClassTest.php rename to tests/end-to-end/_files/basic/unit/SetUpBeforeClassTest.php index fe4b7cdfec1..7cd527b419c 100644 --- a/tests/basic/unit/fixtures/SetUpBeforeClassTest.php +++ b/tests/end-to-end/_files/basic/unit/SetUpBeforeClassTest.php @@ -7,7 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SelfTest\Basic; +namespace PHPUnit\TestFixture\Basic; use Exception; use PHPUnit\Framework\TestCase; @@ -32,7 +32,7 @@ public static function setUpBeforeClass(): void throw new Exception('forcing an Exception in setUpBeforeClass()'); } - public function setUp(): void + protected function setUp(): void { throw new Exception('setUp() should never have been run'); } diff --git a/tests/basic/unit/fixtures/SetUpTest.php b/tests/end-to-end/_files/basic/unit/SetUpTest.php similarity index 82% rename from tests/basic/unit/fixtures/SetUpTest.php rename to tests/end-to-end/_files/basic/unit/SetUpTest.php index e9f48deb956..6a04b650be3 100644 --- a/tests/basic/unit/fixtures/SetUpTest.php +++ b/tests/end-to-end/_files/basic/unit/SetUpTest.php @@ -7,7 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SelfTest\Basic; +namespace PHPUnit\TestFixture\Basic; use PHPUnit\Framework\TestCase; use RuntimeException; @@ -16,7 +16,7 @@ * Class SetUpBeforeClassTest. * * Behaviour to test: - * - setUp() errors reacht he the user + * - setUp() errors reach the user * - how many times is setUp() called? * - tests are not executed * @@ -25,18 +25,18 @@ */ class SetUpTest extends TestCase { - public function setUp(): void + protected function setUp(): void { throw new RuntimeException('throw exception in setUp'); } public function testOneWithSetUpException(): void { - $this->fail(); + $this->assertTrue(false); } public function testTwoWithSetUpException(): void { - $this->fail(); + $this->assertTrue(false); } } diff --git a/tests/end-to-end/_files/basic/unit/StatusTest.php b/tests/end-to-end/_files/basic/unit/StatusTest.php new file mode 100644 index 00000000000..ad3f7a85c0e --- /dev/null +++ b/tests/end-to-end/_files/basic/unit/StatusTest.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Basic; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\RequiresPhp; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\MockObject\AnInterface; +use RuntimeException; + +#[CoversClass('Foo')] +#[UsesClass('Bar')] +#[TestDox('Test result status with and without message')] +class StatusTest extends TestCase +{ + public function testSuccess(): void + { + $this->createMock(AnInterface::class); + + $this->assertTrue(true); + } + + public function testFailure(): void + { + $this->assertTrue(false); + } + + public function testError(): void + { + throw new RuntimeException; + } + + public function testIncomplete(): void + { + $this->markTestIncomplete(); + } + + public function testSkipped(): void + { + $this->markTestSkipped(); + } + + public function testRisky(): void + { + } + + public function testSuccessWithMessage(): void + { + $this->assertTrue(true, 'success with custom message'); + } + + public function testFailureWithMessage(): void + { + $this->assertTrue(false, 'failure with custom message'); + } + + public function testErrorWithMessage(): void + { + throw new RuntimeException('error with custom message'); + } + + public function testIncompleteWithMessage(): void + { + $this->markTestIncomplete('incomplete with custom message'); + } + + #[RequiresPhp('> 9000')] + public function testSkippedByMetadata(): void + { + } + + public function testSkippedWithMessage(): void + { + $this->markTestSkipped('skipped with custom message'); + } + + public function testRiskyWithMessage(): void + { + // Custom messages not implemented for risky status + } +} diff --git a/tests/basic/unit/fixtures/TearDownAfterClassTest.php b/tests/end-to-end/_files/basic/unit/TearDownAfterClassTest.php similarity index 95% rename from tests/basic/unit/fixtures/TearDownAfterClassTest.php rename to tests/end-to-end/_files/basic/unit/TearDownAfterClassTest.php index 2866a85eb93..87e93d24323 100644 --- a/tests/basic/unit/fixtures/TearDownAfterClassTest.php +++ b/tests/end-to-end/_files/basic/unit/TearDownAfterClassTest.php @@ -7,7 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\SelfTest\Basic; +namespace PHPUnit\TestFixture\Basic; use Exception; use PHPUnit\Framework\TestCase; diff --git a/tests/end-to-end/_files/bootstrap-for-test-suite/phpunit.xml b/tests/end-to-end/_files/bootstrap-for-test-suite/phpunit.xml new file mode 100644 index 00000000000..1efce2727d0 --- /dev/null +++ b/tests/end-to-end/_files/bootstrap-for-test-suite/phpunit.xml @@ -0,0 +1,15 @@ + + + + + tests/one + + + + tests/two + + + diff --git a/tests/end-to-end/_files/bootstrap-for-test-suite/tests/bootstrap/bootstrap.php b/tests/end-to-end/_files/bootstrap-for-test-suite/tests/bootstrap/bootstrap.php new file mode 100644 index 00000000000..4fce8b0581d --- /dev/null +++ b/tests/end-to-end/_files/bootstrap-for-test-suite/tests/bootstrap/bootstrap.php @@ -0,0 +1,9 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ diff --git a/tests/end-to-end/_files/bootstrap-for-test-suite/tests/bootstrap/bootstrap_one.php b/tests/end-to-end/_files/bootstrap-for-test-suite/tests/bootstrap/bootstrap_one.php new file mode 100644 index 00000000000..9a161288b3e --- /dev/null +++ b/tests/end-to-end/_files/bootstrap-for-test-suite/tests/bootstrap/bootstrap_one.php @@ -0,0 +1,10 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +$GLOBALS['one'] = true; diff --git a/tests/end-to-end/_files/bootstrap-for-test-suite/tests/bootstrap/bootstrap_two.php b/tests/end-to-end/_files/bootstrap-for-test-suite/tests/bootstrap/bootstrap_two.php new file mode 100644 index 00000000000..79abb8796d5 --- /dev/null +++ b/tests/end-to-end/_files/bootstrap-for-test-suite/tests/bootstrap/bootstrap_two.php @@ -0,0 +1,10 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +$GLOBALS['two'] = true; diff --git a/tests/end-to-end/_files/bootstrap-for-test-suite/tests/one/OneTest.php b/tests/end-to-end/_files/bootstrap-for-test-suite/tests/one/OneTest.php new file mode 100644 index 00000000000..070ddf23988 --- /dev/null +++ b/tests/end-to-end/_files/bootstrap-for-test-suite/tests/one/OneTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\BootstrapForTestSuite; + +use PHPUnit\Framework\TestCase; + +final class OneTest extends TestCase +{ + public function testOne(): void + { + $this->assertArrayHasKey('one', $GLOBALS); + } +} diff --git a/tests/end-to-end/_files/bootstrap-for-test-suite/tests/two/TwoTest.php b/tests/end-to-end/_files/bootstrap-for-test-suite/tests/two/TwoTest.php new file mode 100644 index 00000000000..81ef1931805 --- /dev/null +++ b/tests/end-to-end/_files/bootstrap-for-test-suite/tests/two/TwoTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\BootstrapForTestSuite; + +use PHPUnit\Framework\TestCase; + +final class TwoTest extends TestCase +{ + public function testTwo(): void + { + $this->assertArrayHasKey('two', $GLOBALS); + } +} diff --git a/tests/end-to-end/_files/code-coverage-targeting/phpunit.xml b/tests/end-to-end/_files/code-coverage-targeting/phpunit.xml new file mode 100644 index 00000000000..f2074c54f05 --- /dev/null +++ b/tests/end-to-end/_files/code-coverage-targeting/phpunit.xml @@ -0,0 +1,16 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/_files/code-coverage-targeting/src/SomeClass.php b/tests/end-to-end/_files/code-coverage-targeting/src/SomeClass.php new file mode 100644 index 00000000000..bf59bfbb4d9 --- /dev/null +++ b/tests/end-to-end/_files/code-coverage-targeting/src/SomeClass.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\CodeCoverageTargeting\Warnings; + +final readonly class SomeClass +{ +} diff --git a/tests/end-to-end/_files/code-coverage-targeting/tests/CoversAndUsesTest.php b/tests/end-to-end/_files/code-coverage-targeting/tests/CoversAndUsesTest.php new file mode 100644 index 00000000000..2bc46f84440 --- /dev/null +++ b/tests/end-to-end/_files/code-coverage-targeting/tests/CoversAndUsesTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\CodeCoverageTargeting\Warnings; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(SomeClass::class)] +#[UsesClass(SomeClass::class)] +final class CoversAndUsesTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/code-coverage-targeting/tests/CoversClassCoversNothingTest.php b/tests/end-to-end/_files/code-coverage-targeting/tests/CoversClassCoversNothingTest.php new file mode 100644 index 00000000000..0acf9eef9c8 --- /dev/null +++ b/tests/end-to-end/_files/code-coverage-targeting/tests/CoversClassCoversNothingTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\CodeCoverageTargeting\Warnings; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversNothing; +use PHPUnit\Framework\TestCase; + +#[CoversClass(SomeClass::class)] +#[CoversNothing] +final class CoversClassCoversNothingTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/code-coverage-targeting/tests/DuplicateCoversTest.php b/tests/end-to-end/_files/code-coverage-targeting/tests/DuplicateCoversTest.php new file mode 100644 index 00000000000..cc49b5816f4 --- /dev/null +++ b/tests/end-to-end/_files/code-coverage-targeting/tests/DuplicateCoversTest.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\CodeCoverageTargeting\Warnings; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(SomeClass::class)] +#[CoversClass(SomeClass::class)] +final class DuplicateCoversTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/code-coverage-targeting/tests/DuplicateUsesTest.php b/tests/end-to-end/_files/code-coverage-targeting/tests/DuplicateUsesTest.php new file mode 100644 index 00000000000..09540c491fc --- /dev/null +++ b/tests/end-to-end/_files/code-coverage-targeting/tests/DuplicateUsesTest.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\CodeCoverageTargeting\Warnings; + +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; + +#[UsesClass(SomeClass::class)] +#[UsesClass(SomeClass::class)] +final class DuplicateUsesTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/configuration-file-based-test-selection/defaultTestSuite/phpunit.xml b/tests/end-to-end/_files/configuration-file-based-test-selection/defaultTestSuite/phpunit.xml new file mode 100644 index 00000000000..a0695798658 --- /dev/null +++ b/tests/end-to-end/_files/configuration-file-based-test-selection/defaultTestSuite/phpunit.xml @@ -0,0 +1,15 @@ + + + + + tests/unit + + + + tests/end-to-end + + + diff --git a/tests/end-to-end/_files/configuration-file-based-test-selection/defaultTestSuite/tests/end-to-end/EndToEndTest.php b/tests/end-to-end/_files/configuration-file-based-test-selection/defaultTestSuite/tests/end-to-end/EndToEndTest.php new file mode 100644 index 00000000000..f1bdf6e001e --- /dev/null +++ b/tests/end-to-end/_files/configuration-file-based-test-selection/defaultTestSuite/tests/end-to-end/EndToEndTest.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\DefaultTestSuite; + +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; +use PHPUnit\Framework\TestCase; + +#[DoesNotPerformAssertions] +final class EndToEndTest extends TestCase +{ + public function testOne(): void + { + } +} diff --git a/tests/end-to-end/_files/configuration-file-based-test-selection/defaultTestSuite/tests/unit/UnitTest.php b/tests/end-to-end/_files/configuration-file-based-test-selection/defaultTestSuite/tests/unit/UnitTest.php new file mode 100644 index 00000000000..78dd454303f --- /dev/null +++ b/tests/end-to-end/_files/configuration-file-based-test-selection/defaultTestSuite/tests/unit/UnitTest.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\DefaultTestSuite; + +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; +use PHPUnit\Framework\TestCase; + +#[DoesNotPerformAssertions] +final class UnitTest extends TestCase +{ + public function testOne(): void + { + } +} diff --git a/tests/end-to-end/_files/configuration-file-based-test-selection/groups/phpunit.xml b/tests/end-to-end/_files/configuration-file-based-test-selection/groups/phpunit.xml new file mode 100644 index 00000000000..b86338c97a0 --- /dev/null +++ b/tests/end-to-end/_files/configuration-file-based-test-selection/groups/phpunit.xml @@ -0,0 +1,16 @@ + + + + + tests + + + + + + one + + + diff --git a/tests/end-to-end/_files/configuration-file-based-test-selection/groups/tests/ExampleTest.php b/tests/end-to-end/_files/configuration-file-based-test-selection/groups/tests/ExampleTest.php new file mode 100644 index 00000000000..3f345dff517 --- /dev/null +++ b/tests/end-to-end/_files/configuration-file-based-test-selection/groups/tests/ExampleTest.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\Groups; + +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\TestCase; + +#[DoesNotPerformAssertions] +final class ExampleTest extends TestCase +{ + #[Group('one')] + public function testOne(): void + { + } + + #[Group('two')] + public function testTwo(): void + { + } +} diff --git a/tests/end-to-end/_files/controlled-garbage-collection/phpunit.xml b/tests/end-to-end/_files/controlled-garbage-collection/phpunit.xml new file mode 100644 index 00000000000..f9e2acbe85e --- /dev/null +++ b/tests/end-to-end/_files/controlled-garbage-collection/phpunit.xml @@ -0,0 +1,12 @@ + + + + + tests + + + diff --git a/tests/end-to-end/_files/controlled-garbage-collection/tests/GarbageCollectionTest.php b/tests/end-to-end/_files/controlled-garbage-collection/tests/GarbageCollectionTest.php new file mode 100644 index 00000000000..a473314e20e --- /dev/null +++ b/tests/end-to-end/_files/controlled-garbage-collection/tests/GarbageCollectionTest.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\GarbageCollection; + +use PHPUnit\Framework\TestCase; + +final class GarbageCollectionTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } + + public function testTwo(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/coverage-annotation-based-filter/src/AnnotationFilter.php b/tests/end-to-end/_files/coverage-annotation-based-filter/src/AnnotationFilter.php new file mode 100644 index 00000000000..2049c128a69 --- /dev/null +++ b/tests/end-to-end/_files/coverage-annotation-based-filter/src/AnnotationFilter.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\MockObject; + +class AClass +{ +} diff --git a/tests/end-to-end/_files/coverage-annotation-based-filter/tests/AnnotationFilterTest.php b/tests/end-to-end/_files/coverage-annotation-based-filter/tests/AnnotationFilterTest.php new file mode 100644 index 00000000000..0acfa0839b3 --- /dev/null +++ b/tests/end-to-end/_files/coverage-annotation-based-filter/tests/AnnotationFilterTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\TestCase; + +class AlternativeSuffixTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } + + public function testTwo(): void + { + $this->assertTrue(true); + } + + public function testThree(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/coverage/coverage-no-tests-when-missing-coverage-driver.phpt b/tests/end-to-end/_files/coverage/coverage-no-tests-when-missing-coverage-driver.phpt new file mode 100644 index 00000000000..96401ed63f9 --- /dev/null +++ b/tests/end-to-end/_files/coverage/coverage-no-tests-when-missing-coverage-driver.phpt @@ -0,0 +1,28 @@ +--TEST-- +Don't run tests when coverage driver is not loaded +--SKIPIF-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +There was 1 PHPUnit test runner warning: + +1) No code coverage driver available + +No tests executed! + diff --git a/tests/end-to-end/_files/coverage/coverage-no-tests-when-wrong-xdebug-mode.phpt b/tests/end-to-end/_files/coverage/coverage-no-tests-when-wrong-xdebug-mode.phpt new file mode 100644 index 00000000000..550b65ab507 --- /dev/null +++ b/tests/end-to-end/_files/coverage/coverage-no-tests-when-wrong-xdebug-mode.phpt @@ -0,0 +1,30 @@ +--TEST-- +Don't run tests when wrong xdebug mode is set +--SKIPIF-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +There was 1 PHPUnit test runner warning: + +1) XDEBUG_MODE=coverage (environment variable) or xdebug.mode=coverage (PHP configuration setting) has to be set + +No tests executed! + diff --git a/tests/end-to-end/_files/do-not-fail-on/phpunit.xml b/tests/end-to-end/_files/do-not-fail-on/phpunit.xml new file mode 100644 index 00000000000..0a055bf0064 --- /dev/null +++ b/tests/end-to-end/_files/do-not-fail-on/phpunit.xml @@ -0,0 +1,12 @@ + + + + + tests + + + diff --git a/tests/end-to-end/_files/do-not-fail-on/tests/IssueTest.php b/tests/end-to-end/_files/do-not-fail-on/tests/IssueTest.php new file mode 100644 index 00000000000..d56ab788ef8 --- /dev/null +++ b/tests/end-to-end/_files/do-not-fail-on/tests/IssueTest.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\DoNotFailOn; + +use const E_USER_DEPRECATED; +use const E_USER_NOTICE; +use const E_USER_WARNING; +use function trigger_error; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Framework\TestCase; + +final class IssueTest extends TestCase +{ + public function testThatTriggersPhpunitDeprecation(): void + { + EventFacade::emitter()->testTriggeredPhpunitDeprecation( + $this->valueObjectForEvents(), + 'message', + ); + + $this->assertTrue(true); + } + + public function testThatTriggersPhpunitWarning(): void + { + EventFacade::emitter()->testTriggeredPhpunitWarning( + $this->valueObjectForEvents(), + 'message', + ); + + $this->assertTrue(true); + } + + public function testThatTriggersDeprecation(): void + { + trigger_error('message', E_USER_DEPRECATED); + + $this->assertTrue(true); + } + + public function testThatIsSkipped(): void + { + $this->markTestSkipped('message'); + } + + public function testThatIsIncomplete(): void + { + $this->markTestIncomplete('message'); + } + + public function testThatTriggersNotice(): void + { + trigger_error('message', E_USER_NOTICE); + + $this->assertTrue(true); + } + + public function testThatIsRisky(): void + { + } + + public function testThatTriggersWarning(): void + { + trigger_error('message', E_USER_WARNING); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/filter-error-handler/filter-disabled.xml b/tests/end-to-end/_files/filter-error-handler/filter-disabled.xml new file mode 100644 index 00000000000..b5079e29e68 --- /dev/null +++ b/tests/end-to-end/_files/filter-error-handler/filter-disabled.xml @@ -0,0 +1,16 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/_files/filter-error-handler/filter-enabled.xml b/tests/end-to-end/_files/filter-error-handler/filter-enabled.xml new file mode 100644 index 00000000000..7424e4393fb --- /dev/null +++ b/tests/end-to-end/_files/filter-error-handler/filter-enabled.xml @@ -0,0 +1,16 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/_files/filter-error-handler/src/SourceClass.php b/tests/end-to-end/_files/filter-error-handler/src/SourceClass.php new file mode 100644 index 00000000000..224d67684f6 --- /dev/null +++ b/tests/end-to-end/_files/filter-error-handler/src/SourceClass.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\FilterErrorHandler; + +use const E_USER_DEPRECATED; +use const E_USER_NOTICE; +use const E_USER_WARNING; +use function trigger_error; + +final class SourceClass +{ + public function doSomething(): void + { + trigger_error('deprecation', E_USER_DEPRECATED); + trigger_error('notice', E_USER_NOTICE); + trigger_error('warning', E_USER_WARNING); + + (new VendorClass)->doSomething(); + } +} diff --git a/tests/end-to-end/_files/filter-error-handler/tests/SourceClassTest.php b/tests/end-to-end/_files/filter-error-handler/tests/SourceClassTest.php new file mode 100644 index 00000000000..2be91534e71 --- /dev/null +++ b/tests/end-to-end/_files/filter-error-handler/tests/SourceClassTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\FilterErrorHandler; + +use PHPUnit\Framework\TestCase; + +final class SourceClassTest extends TestCase +{ + public function testSomething(): void + { + (new SourceClass)->doSomething(); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/filter-error-handler/vendor/VendorClass.php b/tests/end-to-end/_files/filter-error-handler/vendor/VendorClass.php new file mode 100644 index 00000000000..cc8dbd5b338 --- /dev/null +++ b/tests/end-to-end/_files/filter-error-handler/vendor/VendorClass.php @@ -0,0 +1,12 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/_files/force-covers-annotation/tests/Test.php b/tests/end-to-end/_files/force-covers-annotation/tests/Test.php new file mode 100644 index 00000000000..a2aa00e57b6 --- /dev/null +++ b/tests/end-to-end/_files/force-covers-annotation/tests/Test.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/groups/phpunit.xml b/tests/end-to-end/_files/groups/phpunit.xml new file mode 100644 index 00000000000..8b2df97afce --- /dev/null +++ b/tests/end-to-end/_files/groups/phpunit.xml @@ -0,0 +1,10 @@ + + + + + tests + + + diff --git a/tests/end-to-end/_files/groups/tests/FooTest.php b/tests/end-to-end/_files/groups/tests/FooTest.php new file mode 100644 index 00000000000..53850b69e97 --- /dev/null +++ b/tests/end-to-end/_files/groups/tests/FooTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Groups; + +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\TestCase; + +final class FooTest extends TestCase +{ + #[Group('one')] + public function testOne(): void + { + $this->assertTrue(true); + } + + #[Group('two')] + public function testTwo(): void + { + $this->assertTrue(true); + } + + public function testThree(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/listing-tests-and-groups/ExampleAbstractTestCase.php b/tests/end-to-end/_files/listing-tests-and-groups/ExampleAbstractTestCase.php new file mode 100644 index 00000000000..2b1977896c6 --- /dev/null +++ b/tests/end-to-end/_files/listing-tests-and-groups/ExampleAbstractTestCase.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\ListingTestsAndGroups; + +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\TestCase; + +abstract class ExampleAbstractTestCase extends TestCase +{ + #[Group('abstract-one')] + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/listing-tests-and-groups/ExampleExtendingAbstractTest.php b/tests/end-to-end/_files/listing-tests-and-groups/ExampleExtendingAbstractTest.php new file mode 100644 index 00000000000..01e316b4757 --- /dev/null +++ b/tests/end-to-end/_files/listing-tests-and-groups/ExampleExtendingAbstractTest.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\ListingTestsAndGroups; + +final class ExampleExtendingAbstractTest extends ExampleAbstractTestCase +{ +} diff --git a/tests/end-to-end/_files/listing-tests-and-groups/ExampleTest.php b/tests/end-to-end/_files/listing-tests-and-groups/ExampleTest.php new file mode 100644 index 00000000000..d4f783d722f --- /dev/null +++ b/tests/end-to-end/_files/listing-tests-and-groups/ExampleTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\ListingTestsAndGroups; + +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\TestCase; + +final class ExampleTest extends TestCase +{ + #[Group('one')] + public function testOne(): void + { + $this->assertTrue(true); + } + + #[Group('two')] + public function testTwo(): void + { + $this->assertTrue(true); + } + + #[Group('3')] + public function testThree(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/listing-tests-and-groups/example.phpt b/tests/end-to-end/_files/listing-tests-and-groups/example.phpt new file mode 100644 index 00000000000..412a451c6b7 --- /dev/null +++ b/tests/end-to-end/_files/listing-tests-and-groups/example.phpt @@ -0,0 +1,4 @@ +--TEST-- +This is an example PHPT test, this file is never executed! +--FILE-- +--EXPECT-- diff --git a/tests/end-to-end/_files/log-events-text/Test.php b/tests/end-to-end/_files/log-events-text/Test.php new file mode 100644 index 00000000000..8c21fd3a13f --- /dev/null +++ b/tests/end-to-end/_files/log-events-text/Test.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\LogEventsText; + +use function fopen; +use PHPUnit\Framework\TestCase; +use stdClass; + +final class Test extends TestCase +{ + public function testExportNull(): void + { + $this->assertNull(null); + } + + public function testExportBool(): void + { + $this->assertTrue(true); + } + + public function testExportInt(): void + { + $this->assertSame(1, 1); + } + + public function testExportStr(): void + { + $this->assertSame('hello, world!', 'hello, world!'); + } + + public function testExportArray(): void + { + $arr = [1, 'foo' => 2]; + $this->assertSame($arr, $arr); + } + + public function testExportObject(): void + { + $this->assertSame(new stdClass, new stdClass); + } + + public function testExportResource(): void + { + $this->assertSame(fopen('php://memory', 'rw+'), fopen('php://memory', 'rw+')); + } +} diff --git a/tests/end-to-end/_files/multiple-testsuites/default-test-suite.xml b/tests/end-to-end/_files/multiple-testsuites/default-test-suite.xml new file mode 100644 index 00000000000..1fdf8a59230 --- /dev/null +++ b/tests/end-to-end/_files/multiple-testsuites/default-test-suite.xml @@ -0,0 +1,14 @@ + + + + + tests/unit + + + + tests/end-to-end + + + diff --git a/tests/end-to-end/_files/multiple-testsuites/exclude-group.xml b/tests/end-to-end/_files/multiple-testsuites/exclude-group.xml new file mode 100644 index 00000000000..ce0872c84e2 --- /dev/null +++ b/tests/end-to-end/_files/multiple-testsuites/exclude-group.xml @@ -0,0 +1,19 @@ + + + + + tests/unit + + + + tests/end-to-end + + + + + + default + + + diff --git a/tests/end-to-end/_files/multiple-testsuites/include-group.xml b/tests/end-to-end/_files/multiple-testsuites/include-group.xml new file mode 100644 index 00000000000..d96391fc5a5 --- /dev/null +++ b/tests/end-to-end/_files/multiple-testsuites/include-group.xml @@ -0,0 +1,19 @@ + + + + + tests/unit + + + + tests/end-to-end + + + + + + default + + + diff --git a/tests/end-to-end/_files/multiple-testsuites/phpunit.xml b/tests/end-to-end/_files/multiple-testsuites/phpunit.xml new file mode 100644 index 00000000000..790b18bdfce --- /dev/null +++ b/tests/end-to-end/_files/multiple-testsuites/phpunit.xml @@ -0,0 +1,13 @@ + + + + + tests/unit + + + + tests/end-to-end + + + diff --git a/tests/end-to-end/_files/multiple-testsuites/tests/end-to-end/BarTest.php b/tests/end-to-end/_files/multiple-testsuites/tests/end-to-end/BarTest.php new file mode 100644 index 00000000000..1b60ecbc17d --- /dev/null +++ b/tests/end-to-end/_files/multiple-testsuites/tests/end-to-end/BarTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\TestCase; + +final class BarTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/multiple-testsuites/tests/unit/FooTest.php b/tests/end-to-end/_files/multiple-testsuites/tests/unit/FooTest.php new file mode 100644 index 00000000000..123c21688f2 --- /dev/null +++ b/tests/end-to-end/_files/multiple-testsuites/tests/unit/FooTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\TestCase; + +final class FooTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/no-log-cc-override/NoLogNoCc.php b/tests/end-to-end/_files/no-log-cc-override/NoLogNoCc.php new file mode 100644 index 00000000000..092d394fca1 --- /dev/null +++ b/tests/end-to-end/_files/no-log-cc-override/NoLogNoCc.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +final class NoLogNoCc +{ + public function getTrue(): bool + { + return true; + } +} diff --git a/tests/end-to-end/_files/no-log-cc-override/NoLogNoCcTest.php b/tests/end-to-end/_files/no-log-cc-override/NoLogNoCcTest.php new file mode 100644 index 00000000000..6cb00740fb7 --- /dev/null +++ b/tests/end-to-end/_files/no-log-cc-override/NoLogNoCcTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(NoLogNoCc::class)] +final class NoLogNoCcTest extends TestCase +{ + public function testSuccess(): void + { + $this->assertTrue((new NoLogNoCc)->getTrue()); + } +} diff --git a/tests/end-to-end/_files/no-log-cc-override/phpunit.xml b/tests/end-to-end/_files/no-log-cc-override/phpunit.xml new file mode 100644 index 00000000000..e2920c5bb82 --- /dev/null +++ b/tests/end-to-end/_files/no-log-cc-override/phpunit.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + diff --git a/tests/end-to-end/_files/output-cli-help-color.txt b/tests/end-to-end/_files/output-cli-help-color.txt new file mode 100644 index 00000000000..4e68ba81c2c --- /dev/null +++ b/tests/end-to-end/_files/output-cli-help-color.txt @@ -0,0 +1,244 @@ +Usage: + phpunit [options] ... + +Configuration: + + --bootstrap   A PHP script that is included before the + tests run + -c|--configuration   Read configuration from XML file + --no-configuration  Ignore default configuration file + (phpunit.xml) + --extension   Register test runner extension with + bootstrap + --no-extensions  Do not register test runner extensions + --include-path   Prepend PHP's include_path with given + path(s) + -d   Sets a php.ini value + --cache-directory   Specify cache directory + --generate-configuration  Generate configuration file with + suggested settings + --migrate-configuration  Migrate configuration file to current + format + --generate-baseline   Generate baseline for issues + --use-baseline   Use baseline to ignore issues + --ignore-baseline  Do not use baseline to ignore issues + +Selection: + + --all  Ignore test selection from XML + configuration file + --list-suites  List available test suites + --testsuite   Only run tests from the specified test + suite(s) + --exclude-testsuite   Exclude tests from the specified test + suite(s) + --list-groups  List available test groups + --group   Only run tests from the specified + group(s) + --exclude-group   Exclude tests from the specified + group(s) + --covers   Only run tests that intend to cover + + --uses   Only run tests that intend to use + --requires-php-extension   Only run tests that require PHP + extension + --list-test-files  List available test files + --list-tests  List available tests + --list-tests-xml   List available tests in XML format + --filter   Filter which tests to run + --exclude-filter   Exclude tests for the specified filter + pattern + --test-suffix   Only search for test in files with + specified suffix(es). Default: + Test.php,.phpt + +Execution: + + --process-isolation  Run each test in a separate PHP process + --globals-backup  Backup and restore $GLOBALS for each + test + --static-backup  Backup and restore static properties for + each test + + --strict-coverage  Be strict about code coverage metadata + --strict-global-state  Be strict about changes to global state + --disallow-test-output  Be strict about output during tests + --enforce-time-limit  Enforce time limit based on test size + --default-time-limit   Timeout in seconds for tests that have + no declared size + --do-not-report-useless-tests  Do not report tests that do not test + anything + + --stop-on-defect  Stop after first error, failure, + warning, or risky test + --stop-on-error  Stop after first error + --stop-on-failure  Stop after first failure + --stop-on-warning  Stop after first warning + --stop-on-risky  Stop after first risky test + --stop-on-deprecation  Stop after first test that triggered a + deprecation + --stop-on-notice  Stop after first test that triggered a + notice + --stop-on-skipped  Stop after first skipped test + --stop-on-incomplete  Stop after first incomplete test + + --fail-on-empty-test-suite  Signal failure using shell exit code + when no tests were run + --fail-on-warning  Signal failure using shell exit code + when a warning was triggered + --fail-on-risky  Signal failure using shell exit code + when a test was considered risky + --fail-on-deprecation  Signal failure using shell exit code + when a deprecation was triggered + --fail-on-phpunit-deprecation  Signal failure using shell exit code + when a PHPUnit deprecation was triggered + --fail-on-phpunit-notice  Signal failure using shell exit code + when a PHPUnit notice was triggered + --fail-on-phpunit-warning  Signal failure using shell exit code + when a PHPUnit warning was triggered + --fail-on-notice  Signal failure using shell exit code + when a notice was triggered + --fail-on-skipped  Signal failure using shell exit code + when a test was skipped + --fail-on-incomplete  Signal failure using shell exit code + when a test was marked incomplete + --fail-on-all-issues  Signal failure using shell exit code + when an issue is triggered + + --do-not-fail-on-empty-test-suite  Do not signal failure using shell exit + code when no tests were run + --do-not-fail-on-warning  Do not signal failure using shell exit + code when a warning was triggered + --do-not-fail-on-risky  Do not signal failure using shell exit + code when a test was considered risky + --do-not-fail-on-deprecation  Do not signal failure using shell exit + code when a deprecation was triggered + --do-not-fail-on-phpunit-deprecation Do not signal failure using shell exit + code when a PHPUnit deprecation was + triggered + --do-not-fail-on-phpunit-notice  Do not signal failure using shell exit + code when a PHPUnit notice was triggered + --do-not-fail-on-phpunit-warning  Do not signal failure using shell exit + code when a PHPUnit warning was + triggered + --do-not-fail-on-notice  Do not signal failure using shell exit + code when a notice was triggered + --do-not-fail-on-skipped  Do not signal failure using shell exit + code when a test was skipped + --do-not-fail-on-incomplete  Do not signal failure using shell exit + code when a test was marked incomplete + + --cache-result  Write test results to cache file + --do-not-cache-result  Do not write test results to cache file + + --order-by   Run tests in order: + default|defects|depends|duration|no-depends|random|reverse|size + --random-order-seed   Use the specified random seed when + running tests in random order + +Reporting: + + --colors   Use colors in output ("never", "auto" or + "always") + --columns   Number of columns to use for progress + output + --columns max  Use maximum number of columns for + progress output + --stderr  Write to STDERR instead of STDOUT + + --no-progress  Disable output of test execution + progress + --no-results  Disable output of test results + --no-output  Disable all output + + --display-incomplete  Display details for incomplete tests + --display-skipped  Display details for skipped tests + --display-deprecations  Display details for deprecations + triggered by tests + --display-phpunit-deprecations  Display details for PHPUnit deprecations + --display-phpunit-notices  Display details for PHPUnit notices + --display-errors  Display details for errors triggered by + tests + --display-notices  Display details for notices triggered by + tests + --display-warnings  Display details for warnings triggered + by tests + --display-all-issues  Display details for all issues that are + triggered + --reverse-list  Print defects in reverse order + + --teamcity  Replace default progress and result + output with TeamCity format + --testdox  Replace default result output with + TestDox format + --testdox-summary  Repeat TestDox output for tests with + errors, failures, or issues + + --debug  Replace default progress and result + output with debugging information + --with-telemetry  Include telemetry information in + debugging information output + +Logging: + + --log-junit   Write test results in JUnit XML format + to file + --log-otr   Write test results in Open Test + Reporting XML format to file + --include-git-information  Include Git information in Open Test + Reporting XML logfile + --log-teamcity   Write test results in TeamCity format to + file + --testdox-html   Write test results in TestDox format + (HTML) to file + --testdox-text   Write test results in TestDox format + (plain text) to file + --log-events-text   Stream events as plain text to file + --log-events-verbose-text   Stream events as plain text with + extended information to file + --no-logging  Ignore logging configured in the XML + configuration file + +Code Coverage: + + --coverage-clover   Write code coverage report in Clover XML + format to file + --coverage-openclover   Write code coverage report in OpenClover + XML format to file + --coverage-cobertura   Write code coverage report in Cobertura + XML format to file + --coverage-crap4j   Write code coverage report in Crap4J XML + format to file + --coverage-html   Write code coverage report in HTML + format to directory + --coverage-php   Write serialized code coverage data to + file + --coverage-text=  Write code coverage report in text + format to file [default: standard + output] + --only-summary-for-coverage-text  Option for code coverage report in text + format: only show summary + --show-uncovered-for-coverage-text  Option for code coverage report in text + format: show uncovered files + --coverage-xml   Write code coverage report in XML format + to directory + --warm-coverage-cache  Warm static analysis cache + --coverage-filter   Include in code coverage reporting + --path-coverage  Report path coverage in addition to line + coverage + --disable-coverage-ignore  Disable metadata for ignoring code + coverage + --no-coverage  Ignore code coverage reporting + configured in the XML configuration file + +Miscellaneous: + + -h|--help  Prints this usage information + --version  Prints the version and exits + --atleast-version   Checks that version is greater than + and exits + --check-version  Checks whether PHPUnit is the latest + version and exits + --check-php-configuration  Checks whether PHP configuration follows + best practices + diff --git a/tests/end-to-end/_files/output-cli-usage.txt b/tests/end-to-end/_files/output-cli-usage.txt new file mode 100644 index 00000000000..932879ec51a --- /dev/null +++ b/tests/end-to-end/_files/output-cli-usage.txt @@ -0,0 +1,159 @@ +PHPUnit %s by Sebastian Bergmann and contributors. + +Usage: + phpunit [options] ... + +Configuration: + + --bootstrap A PHP script that is included before the tests run + -c|--configuration Read configuration from XML file + --no-configuration Ignore default configuration file (phpunit.xml) + --extension Register test runner extension with bootstrap + --no-extensions Do not register test runner extensions + --include-path Prepend PHP's include_path with given path(s) + -d Sets a php.ini value + --cache-directory Specify cache directory + --generate-configuration Generate configuration file with suggested settings + --migrate-configuration Migrate configuration file to current format + --generate-baseline Generate baseline for issues + --use-baseline Use baseline to ignore issues + --ignore-baseline Do not use baseline to ignore issues + +Selection: + + --all Ignore test selection from XML configuration file + --list-suites List available test suites + --testsuite Only run tests from the specified test suite(s) + --exclude-testsuite Exclude tests from the specified test suite(s) + --list-groups List available test groups + --group Only run tests from the specified group(s) + --exclude-group Exclude tests from the specified group(s) + --covers Only run tests that intend to cover + --uses Only run tests that intend to use + --requires-php-extension Only run tests that require PHP extension + --list-test-files List available test files + --list-tests List available tests + --list-tests-xml List available tests in XML format + --filter Filter which tests to run + --exclude-filter Exclude tests for the specified filter pattern + --test-suffix Only search for test in files with specified suffix(es). Default: Test.php,.phpt + +Execution: + + --process-isolation Run each test in a separate PHP process + --globals-backup Backup and restore $GLOBALS for each test + --static-backup Backup and restore static properties for each test + + --strict-coverage Be strict about code coverage metadata + --strict-global-state Be strict about changes to global state + --disallow-test-output Be strict about output during tests + --enforce-time-limit Enforce time limit based on test size + --default-time-limit Timeout in seconds for tests that have no declared size + --do-not-report-useless-tests Do not report tests that do not test anything + + --stop-on-defect Stop after first error, failure, warning, or risky test + --stop-on-error Stop after first error + --stop-on-failure Stop after first failure + --stop-on-warning Stop after first warning + --stop-on-risky Stop after first risky test + --stop-on-deprecation Stop after first test that triggered a deprecation + --stop-on-notice Stop after first test that triggered a notice + --stop-on-skipped Stop after first skipped test + --stop-on-incomplete Stop after first incomplete test + + --fail-on-empty-test-suite Signal failure using shell exit code when no tests were run + --fail-on-warning Signal failure using shell exit code when a warning was triggered + --fail-on-risky Signal failure using shell exit code when a test was considered risky + --fail-on-deprecation Signal failure using shell exit code when a deprecation was triggered + --fail-on-phpunit-deprecation Signal failure using shell exit code when a PHPUnit deprecation was triggered + --fail-on-phpunit-notice Signal failure using shell exit code when a PHPUnit notice was triggered + --fail-on-phpunit-warning Signal failure using shell exit code when a PHPUnit warning was triggered + --fail-on-notice Signal failure using shell exit code when a notice was triggered + --fail-on-skipped Signal failure using shell exit code when a test was skipped + --fail-on-incomplete Signal failure using shell exit code when a test was marked incomplete + --fail-on-all-issues Signal failure using shell exit code when an issue is triggered + + --do-not-fail-on-empty-test-suite Do not signal failure using shell exit code when no tests were run + --do-not-fail-on-warning Do not signal failure using shell exit code when a warning was triggered + --do-not-fail-on-risky Do not signal failure using shell exit code when a test was considered risky + --do-not-fail-on-deprecation Do not signal failure using shell exit code when a deprecation was triggered + --do-not-fail-on-phpunit-deprecation Do not signal failure using shell exit code when a PHPUnit deprecation was triggered + --do-not-fail-on-phpunit-notice Do not signal failure using shell exit code when a PHPUnit notice was triggered + --do-not-fail-on-phpunit-warning Do not signal failure using shell exit code when a PHPUnit warning was triggered + --do-not-fail-on-notice Do not signal failure using shell exit code when a notice was triggered + --do-not-fail-on-skipped Do not signal failure using shell exit code when a test was skipped + --do-not-fail-on-incomplete Do not signal failure using shell exit code when a test was marked incomplete + + --cache-result Write test results to cache file + --do-not-cache-result Do not write test results to cache file + + --order-by Run tests in order: default|defects|depends|duration|no-depends|random|reverse|size + --random-order-seed Use the specified random seed when running tests in random order + +Reporting: + + --colors Use colors in output ("never", "auto" or "always") + --columns Number of columns to use for progress output + --columns max Use maximum number of columns for progress output + --stderr Write to STDERR instead of STDOUT + + --no-progress Disable output of test execution progress + --no-results Disable output of test results + --no-output Disable all output + + --display-incomplete Display details for incomplete tests + --display-skipped Display details for skipped tests + --display-deprecations Display details for deprecations triggered by tests + --display-phpunit-deprecations Display details for PHPUnit deprecations + --display-phpunit-notices Display details for PHPUnit notices + --display-errors Display details for errors triggered by tests + --display-notices Display details for notices triggered by tests + --display-warnings Display details for warnings triggered by tests + --display-all-issues Display details for all issues that are triggered + --reverse-list Print defects in reverse order + + --teamcity Replace default progress and result output with TeamCity format + --testdox Replace default result output with TestDox format + --testdox-summary Repeat TestDox output for tests with errors, failures, or issues + + --debug Replace default progress and result output with debugging information + --with-telemetry Include telemetry information in debugging information output + +Logging: + + --log-junit Write test results in JUnit XML format to file + --log-otr Write test results in Open Test Reporting XML format to file + --include-git-information Include Git information in Open Test Reporting XML logfile + --log-teamcity Write test results in TeamCity format to file + --testdox-html Write test results in TestDox format (HTML) to file + --testdox-text Write test results in TestDox format (plain text) to file + --log-events-text Stream events as plain text to file + --log-events-verbose-text Stream events as plain text with extended information to file + --no-logging Ignore logging configured in the XML configuration file + +Code Coverage: + + --coverage-clover Write code coverage report in Clover XML format to file + --coverage-openclover Write code coverage report in OpenClover XML format to file + --coverage-cobertura Write code coverage report in Cobertura XML format to file + --coverage-crap4j Write code coverage report in Crap4J XML format to file + --coverage-html Write code coverage report in HTML format to directory + --coverage-php Write serialized code coverage data to file + --coverage-text= Write code coverage report in text format to file [default: standard output] + --only-summary-for-coverage-text Option for code coverage report in text format: only show summary + --show-uncovered-for-coverage-text Option for code coverage report in text format: show uncovered files + --coverage-xml Write code coverage report in XML format to directory + --warm-coverage-cache Warm static analysis cache + --coverage-filter Include in code coverage reporting + --path-coverage Report path coverage in addition to line coverage + --disable-coverage-ignore Disable metadata for ignoring code coverage + --no-coverage Ignore code coverage reporting configured in the XML configuration file + +Miscellaneous: + + -h|--help Prints this usage information + --version Prints the version and exits + --atleast-version Checks that version is greater than and exits + --check-version Checks whether PHPUnit is the latest version and exits + --check-php-configuration Checks whether PHP configuration follows best practices + diff --git a/tests/end-to-end/_files/overlapping-testsuite-configuration/phpunit.xml b/tests/end-to-end/_files/overlapping-testsuite-configuration/phpunit.xml new file mode 100644 index 00000000000..205407cfc12 --- /dev/null +++ b/tests/end-to-end/_files/overlapping-testsuite-configuration/phpunit.xml @@ -0,0 +1,14 @@ + + + + + tests + + + + tests + + + diff --git a/tests/end-to-end/_files/overlapping-testsuite-configuration/tests/ExampleTest.php b/tests/end-to-end/_files/overlapping-testsuite-configuration/tests/ExampleTest.php new file mode 100644 index 00000000000..20f1b56624e --- /dev/null +++ b/tests/end-to-end/_files/overlapping-testsuite-configuration/tests/ExampleTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\OverlappingTestSuiteConfiguration; + +use PHPUnit\Framework\TestCase; + +final class ExampleTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/phar-extension/phpunit.xml b/tests/end-to-end/_files/phar-extension/phpunit.xml new file mode 100644 index 00000000000..6fe1bb5596c --- /dev/null +++ b/tests/end-to-end/_files/phar-extension/phpunit.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + tests + + + diff --git a/tests/end-to-end/_files/phar-extension/tests/Test.php b/tests/end-to-end/_files/phar-extension/tests/Test.php new file mode 100644 index 00000000000..a6c86c85797 --- /dev/null +++ b/tests/end-to-end/_files/phar-extension/tests/Test.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/phar-extension/tools/phpunit.d/phpunit-test-extension-1.0.0.phar b/tests/end-to-end/_files/phar-extension/tools/phpunit.d/phpunit-test-extension-1.0.0.phar new file mode 100644 index 00000000000..cfdc373840a Binary files /dev/null and b/tests/end-to-end/_files/phar-extension/tools/phpunit.d/phpunit-test-extension-1.0.0.phar differ diff --git a/tests/end-to-end/_files/phpt-clean-parse-error.phpt b/tests/end-to-end/_files/phpt-clean-parse-error.phpt new file mode 100644 index 00000000000..d9d370b1735 --- /dev/null +++ b/tests/end-to-end/_files/phpt-clean-parse-error.phpt @@ -0,0 +1,11 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5991 +--CLEAN-- + diff --git a/tests/end-to-end/_files/phpt-ini-subprocess.phpt b/tests/end-to-end/_files/phpt-ini-subprocess.phpt new file mode 100644 index 00000000000..e27d5824544 --- /dev/null +++ b/tests/end-to-end/_files/phpt-ini-subprocess.phpt @@ -0,0 +1,14 @@ +--TEST-- +PHPT uses a subprocess when --INI-- is present, even if --SKIPIF-- has no side-effect +--INI-- +error_reporting=-1 +--SKIPIF-- + +--EXPECT-- diff --git a/tests/end-to-end/_files/phpt-skipif-exit-subprocess.phpt b/tests/end-to-end/_files/phpt-skipif-exit-subprocess.phpt new file mode 100644 index 00000000000..c482f593222 --- /dev/null +++ b/tests/end-to-end/_files/phpt-skipif-exit-subprocess.phpt @@ -0,0 +1,9 @@ +--TEST-- +PHPT skip condition which exits the subprocess (side-effect) +--FILE-- + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PhpTSkipIfRequiredFile; + +class SomeClass +{ +} diff --git a/tests/end-to-end/_files/requires_environment_variable/SomeTest.php b/tests/end-to-end/_files/requires_environment_variable/SomeTest.php new file mode 100644 index 00000000000..a58c7ecd828 --- /dev/null +++ b/tests/end-to-end/_files/requires_environment_variable/SomeTest.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\requires_environment_variable; + +use PHPUnit\Framework\Attributes\RequiresEnvironmentVariable; +use PHPUnit\Framework\Attributes\RunInSeparateProcess; +use PHPUnit\Framework\TestCase; + +final class SomeTest extends TestCase +{ + #[RequiresEnvironmentVariable('FOO', 'bar')] + public function testShouldNotRunFOOHasWrongValue(): void + { + $this->fail(); + } + + #[RequiresEnvironmentVariable('BAR')] + public function testShouldNotRunBARIsEmpty(): void + { + $this->fail(); + } + + #[RequiresEnvironmentVariable('BAZ')] + public function testShouldNotRunBAZDoesNotExist(): void + { + $this->fail(); + } + + #[RequiresEnvironmentVariable('FOO')] + public function testOneShouldRun(): void + { + $this->assertTrue(true); + } + + #[RequiresEnvironmentVariable('FOO', '1')] + public function testTwoShouldRun(): void + { + $this->assertTrue(true); + } + + #[RequiresEnvironmentVariable('BAR', '')] + public function testThreeShouldRun(): void + { + $this->assertTrue(true); + } + + #[RunInSeparateProcess] + #[RequiresEnvironmentVariable('FOO', '1')] + public function testMustRunInSeparateProcess(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/requires_environment_variable/phpunit.xml b/tests/end-to-end/_files/requires_environment_variable/phpunit.xml new file mode 100644 index 00000000000..f5b185305f0 --- /dev/null +++ b/tests/end-to-end/_files/requires_environment_variable/phpunit.xml @@ -0,0 +1,16 @@ + + + + + SomeTest.php + + + + + + + + diff --git a/tests/end-to-end/_files/size-combinations/LargeMediumTest.php b/tests/end-to-end/_files/size-combinations/LargeMediumTest.php new file mode 100644 index 00000000000..cc48493aca7 --- /dev/null +++ b/tests/end-to-end/_files/size-combinations/LargeMediumTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SizeCombinations; + +use PHPUnit\Framework\Attributes\Large; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\TestCase; + +#[Large] +#[Medium] +final class LargeMediumTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/size-combinations/LargeSmallTest.php b/tests/end-to-end/_files/size-combinations/LargeSmallTest.php new file mode 100644 index 00000000000..4d426211134 --- /dev/null +++ b/tests/end-to-end/_files/size-combinations/LargeSmallTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SizeCombinations; + +use PHPUnit\Framework\Attributes\Large; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[Large] +#[Small] +final class LargeSmallTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/size-combinations/MediumLargeTest.php b/tests/end-to-end/_files/size-combinations/MediumLargeTest.php new file mode 100644 index 00000000000..539a58af2ab --- /dev/null +++ b/tests/end-to-end/_files/size-combinations/MediumLargeTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SizeCombinations; + +use PHPUnit\Framework\Attributes\Large; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\TestCase; + +#[Medium] +#[Large] +final class MediumLargeTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/size-combinations/MediumSmallTest.php b/tests/end-to-end/_files/size-combinations/MediumSmallTest.php new file mode 100644 index 00000000000..96ee5f2ed24 --- /dev/null +++ b/tests/end-to-end/_files/size-combinations/MediumSmallTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SizeCombinations; + +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[Medium] +#[Small] +final class MediumSmallTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/size-combinations/SmallLargeTest.php b/tests/end-to-end/_files/size-combinations/SmallLargeTest.php new file mode 100644 index 00000000000..50d32db5ee9 --- /dev/null +++ b/tests/end-to-end/_files/size-combinations/SmallLargeTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SizeCombinations; + +use PHPUnit\Framework\Attributes\Large; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[Small] +#[Large] +final class SmallLargeTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/size-combinations/SmallMediumTest.php b/tests/end-to-end/_files/size-combinations/SmallMediumTest.php new file mode 100644 index 00000000000..e6ad7dbd263 --- /dev/null +++ b/tests/end-to-end/_files/size-combinations/SmallMediumTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SizeCombinations; + +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[Small] +#[Medium] +final class SmallMediumTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/size-groups/ClassLevelTest.php b/tests/end-to-end/_files/size-groups/ClassLevelTest.php new file mode 100644 index 00000000000..e5cb7a67183 --- /dev/null +++ b/tests/end-to-end/_files/size-groups/ClassLevelTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SizeGroups; + +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\TestCase; + +#[Group('small')] +#[Group('medium')] +#[Group('large')] +final class ClassLevelTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/size-groups/MethodLevelTest.php b/tests/end-to-end/_files/size-groups/MethodLevelTest.php new file mode 100644 index 00000000000..582c8c192db --- /dev/null +++ b/tests/end-to-end/_files/size-groups/MethodLevelTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SizeGroups; + +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\TestCase; + +final class MethodLevelTest extends TestCase +{ + #[Group('small')] + #[Group('medium')] + #[Group('large')] + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/stop-on-fail-on/DeprecationTest.php b/tests/end-to-end/_files/stop-on-fail-on/DeprecationTest.php new file mode 100644 index 00000000000..c43da60dc89 --- /dev/null +++ b/tests/end-to-end/_files/stop-on-fail-on/DeprecationTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestRunnerStopping; + +use const E_USER_DEPRECATED; +use function trigger_error; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Framework\TestCase; + +final class DeprecationTest extends TestCase +{ + public function testOne(): void + { + trigger_error('message', E_USER_DEPRECATED); + + $this->assertTrue(true); + } + + public function testTwo(): void + { + $this->assertTrue(true); + } + + public function testThree(): void + { + $this->assertTrue(true); + + EventFacade::emitter()->testTriggeredPhpunitDeprecation( + $this->valueObjectForEvents(), + 'message', + ); + } +} diff --git a/tests/end-to-end/_files/stop-on-fail-on/ErrorTest.php b/tests/end-to-end/_files/stop-on-fail-on/ErrorTest.php new file mode 100644 index 00000000000..e2beaf53062 --- /dev/null +++ b/tests/end-to-end/_files/stop-on-fail-on/ErrorTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestRunnerStopping; + +use Exception; +use PHPUnit\Framework\TestCase; + +final class ErrorTest extends TestCase +{ + public function testOne(): void + { + throw new Exception('message'); + } + + public function testTwo(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/stop-on-fail-on/FailureTest.php b/tests/end-to-end/_files/stop-on-fail-on/FailureTest.php new file mode 100644 index 00000000000..88fa527c835 --- /dev/null +++ b/tests/end-to-end/_files/stop-on-fail-on/FailureTest.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestRunnerStopping; + +use PHPUnit\Framework\TestCase; + +final class FailureTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(false); + } + + public function testTwo(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/stop-on-fail-on/IncompleteTest.php b/tests/end-to-end/_files/stop-on-fail-on/IncompleteTest.php new file mode 100644 index 00000000000..3968700b242 --- /dev/null +++ b/tests/end-to-end/_files/stop-on-fail-on/IncompleteTest.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestRunnerStopping; + +use PHPUnit\Framework\TestCase; + +final class IncompleteTest extends TestCase +{ + public function testOne(): void + { + $this->markTestIncomplete('message'); + } + + public function testTwo(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/stop-on-fail-on/NoticeTest.php b/tests/end-to-end/_files/stop-on-fail-on/NoticeTest.php new file mode 100644 index 00000000000..aa86486f51e --- /dev/null +++ b/tests/end-to-end/_files/stop-on-fail-on/NoticeTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestRunnerStopping; + +use const E_USER_NOTICE; +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class NoticeTest extends TestCase +{ + public function testOne(): void + { + trigger_error('message', E_USER_NOTICE); + + $this->assertTrue(true); + } + + public function testTwo(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/stop-on-fail-on/PhpunitNoticeTest.php b/tests/end-to-end/_files/stop-on-fail-on/PhpunitNoticeTest.php new file mode 100644 index 00000000000..1ae3188b44d --- /dev/null +++ b/tests/end-to-end/_files/stop-on-fail-on/PhpunitNoticeTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestRunnerStopping; + +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Framework\TestCase; + +final class PhpunitNoticeTest extends TestCase +{ + public function testOne(): void + { + EventFacade::emitter()->testTriggeredPhpunitNotice( + $this->valueObjectForEvents(), + 'message', + ); + + $this->assertTrue(true); + } + + public function testTwo(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/stop-on-fail-on/RiskyTest.php b/tests/end-to-end/_files/stop-on-fail-on/RiskyTest.php new file mode 100644 index 00000000000..77c497bae55 --- /dev/null +++ b/tests/end-to-end/_files/stop-on-fail-on/RiskyTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestRunnerStopping; + +use PHPUnit\Framework\TestCase; + +final class RiskyTest extends TestCase +{ + public function testOne(): void + { + } + + public function testTwo(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/stop-on-fail-on/SkippedBeforeClassTest.php b/tests/end-to-end/_files/stop-on-fail-on/SkippedBeforeClassTest.php new file mode 100644 index 00000000000..d3b0ae50906 --- /dev/null +++ b/tests/end-to-end/_files/stop-on-fail-on/SkippedBeforeClassTest.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestRunnerStopping; + +use PHPUnit\Framework\TestCase; + +final class SkippedBeforeClassTest extends TestCase +{ + public static function setUpBeforeClass(): void + { + self::markTestSkipped('message'); + } + + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/stop-on-fail-on/SkippedTest.php b/tests/end-to-end/_files/stop-on-fail-on/SkippedTest.php new file mode 100644 index 00000000000..029e5d3adf2 --- /dev/null +++ b/tests/end-to-end/_files/stop-on-fail-on/SkippedTest.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestRunnerStopping; + +use PHPUnit\Framework\TestCase; + +final class SkippedTest extends TestCase +{ + public function testOne(): void + { + $this->markTestSkipped('message'); + } + + public function testTwo(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/stop-on-fail-on/SpecificDeprecationTest.php b/tests/end-to-end/_files/stop-on-fail-on/SpecificDeprecationTest.php new file mode 100644 index 00000000000..c0190ca4a65 --- /dev/null +++ b/tests/end-to-end/_files/stop-on-fail-on/SpecificDeprecationTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestRunnerStopping; + +use const E_USER_DEPRECATED; +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class SpecificDeprecationTest extends TestCase +{ + public function testOne(): void + { + trigger_error('...foo...', E_USER_DEPRECATED); + + $this->assertTrue(true); + } + + public function testTwo(): void + { + trigger_error('...bar...', E_USER_DEPRECATED); + + $this->assertTrue(true); + } + + public function testThree(): void + { + trigger_error('...baz...', E_USER_DEPRECATED); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/stop-on-fail-on/WarningTest.php b/tests/end-to-end/_files/stop-on-fail-on/WarningTest.php new file mode 100644 index 00000000000..4b77ac2cbe2 --- /dev/null +++ b/tests/end-to-end/_files/stop-on-fail-on/WarningTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestRunnerStopping; + +use const E_USER_WARNING; +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class WarningTest extends TestCase +{ + public function testOne(): void + { + trigger_error('message', E_USER_WARNING); + + $this->assertTrue(true); + } + + public function testTwo(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/test-attribute-on-hook-methods/TestAttributeOnHookMethodsTest.php b/tests/end-to-end/_files/test-attribute-on-hook-methods/TestAttributeOnHookMethodsTest.php new file mode 100644 index 00000000000..96a16c9a959 --- /dev/null +++ b/tests/end-to-end/_files/test-attribute-on-hook-methods/TestAttributeOnHookMethodsTest.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\AttributesOnTemplateMethods; + +use PHPUnit\Framework\Attributes\After; +use PHPUnit\Framework\Attributes\AfterClass; +use PHPUnit\Framework\Attributes\Before; +use PHPUnit\Framework\Attributes\BeforeClass; +use PHPUnit\Framework\Attributes\PostCondition; +use PHPUnit\Framework\Attributes\PreCondition; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; + +final class TestAttributeOnHookMethodsTest extends TestCase +{ + #[BeforeClass] + #[Test] + public static function before_class(): void + { + } + + #[AfterClass] + #[Test] + public static function after_class(): void + { + } + + #[Test] + public static function setUpBeforeClass(): void + { + } + + #[Test] + public static function tearDownAfterClass(): void + { + } + + #[Test] + public function setUp(): void + { + } + + #[Test] + public function assertPreConditions(): void + { + } + + #[Test] + public function assertPostConditions(): void + { + } + + #[Test] + public function tearDown(): void + { + } + + #[Before] + #[Test] + public function before_method(): void + { + } + + #[PreCondition] + #[Test] + public function pre_condition(): void + { + } + + #[PostCondition] + #[Test] + public function post_condition(): void + { + } + + #[After] + #[Test] + public function after_method(): void + { + } + + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/test-directory-does-not-exist/phpunit.xml b/tests/end-to-end/_files/test-directory-does-not-exist/phpunit.xml new file mode 100644 index 00000000000..1cb2fee9012 --- /dev/null +++ b/tests/end-to-end/_files/test-directory-does-not-exist/phpunit.xml @@ -0,0 +1,9 @@ + + + + + tests + + + diff --git a/tests/end-to-end/_files/transform-exception-hook-method/phpunit.xml b/tests/end-to-end/_files/transform-exception-hook-method/phpunit.xml new file mode 100644 index 00000000000..8bf3399168e --- /dev/null +++ b/tests/end-to-end/_files/transform-exception-hook-method/phpunit.xml @@ -0,0 +1,16 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/_files/transform-exception-hook-method/src/OriginalException.php b/tests/end-to-end/_files/transform-exception-hook-method/src/OriginalException.php new file mode 100644 index 00000000000..06675cf9223 --- /dev/null +++ b/tests/end-to-end/_files/transform-exception-hook-method/src/OriginalException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TransformExceptionHookMethod; + +use RuntimeException; + +final class OriginalException extends RuntimeException +{ +} diff --git a/tests/end-to-end/_files/transform-exception-hook-method/src/TransformedException.php b/tests/end-to-end/_files/transform-exception-hook-method/src/TransformedException.php new file mode 100644 index 00000000000..8cafbbadf2c --- /dev/null +++ b/tests/end-to-end/_files/transform-exception-hook-method/src/TransformedException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TransformExceptionHookMethod; + +use RuntimeException; + +final class TransformedException extends RuntimeException +{ +} diff --git a/tests/end-to-end/_files/transform-exception-hook-method/src/autoload.php b/tests/end-to-end/_files/transform-exception-hook-method/src/autoload.php new file mode 100644 index 00000000000..ed1c5d175b4 --- /dev/null +++ b/tests/end-to-end/_files/transform-exception-hook-method/src/autoload.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +require __DIR__ . '/OriginalException.php'; + +require __DIR__ . '/TransformedException.php'; diff --git a/tests/end-to-end/_files/transform-exception-hook-method/tests/Test.php b/tests/end-to-end/_files/transform-exception-hook-method/tests/Test.php new file mode 100644 index 00000000000..e5c68f016ce --- /dev/null +++ b/tests/end-to-end/_files/transform-exception-hook-method/tests/Test.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TransformExceptionHookMethod; + +use PHPUnit\Framework\TestCase; +use Throwable; + +final class Test extends TestCase +{ + public function testOne(): void + { + throw new OriginalException('original message'); + } + + protected function transformException(Throwable $t): Throwable + { + return new TransformedException('transformed message'); + } +} diff --git a/tests/end-to-end/_files/with_environment_variable/WithEnvironmentVariableHookTest.php b/tests/end-to-end/_files/with_environment_variable/WithEnvironmentVariableHookTest.php new file mode 100644 index 00000000000..58d09c2e6a5 --- /dev/null +++ b/tests/end-to-end/_files/with_environment_variable/WithEnvironmentVariableHookTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\requires_environment_variable; + +use function getenv; +use PHPUnit\Framework\Attributes\WithEnvironmentVariable; +use PHPUnit\Framework\TestCase; + +#[WithEnvironmentVariable('FOO', 'foo')] +final class WithEnvironmentVariableHookTest extends TestCase +{ + public static function setUpBeforeClass(): void + { + self::assertEnvironmentVariablesHaveDefaultValues(); + } + + public static function tearDownAfterClass(): void + { + self::assertEnvironmentVariablesHaveDefaultValues(); + } + + protected function setUp(): void + { + $this->assertEnvironmentVariablesHaveCustomValues(); + } + + protected function tearDown(): void + { + $this->assertEnvironmentVariablesHaveCustomValues(); + } + + #[WithEnvironmentVariable('BAR', 'bar')] + public function testFOOShouldHaveValueErasedFromMethodAttribute(): void + { + $this->assertEnvironmentVariablesHaveCustomValues(); + } + + private function assertEnvironmentVariablesHaveCustomValues(): void + { + $this->assertSame('foo', $_ENV['FOO']); + $this->assertSame('foo', getenv('FOO')); + + $this->assertSame('bar', $_ENV['BAR']); + $this->assertSame('bar', getenv('BAR')); + } + + private static function assertEnvironmentVariablesHaveDefaultValues(): void + { + self::assertSame('1', $_ENV['FOO']); + self::assertSame('1', getenv('FOO')); + + self::assertSame('2', $_ENV['BAR']); + self::assertSame('2', getenv('BAR')); + } +} diff --git a/tests/end-to-end/_files/with_environment_variable/WithEnvironmentVariableTest.php b/tests/end-to-end/_files/with_environment_variable/WithEnvironmentVariableTest.php new file mode 100644 index 00000000000..7938ba64e7e --- /dev/null +++ b/tests/end-to-end/_files/with_environment_variable/WithEnvironmentVariableTest.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\requires_environment_variable; + +use function getenv; +use PHPUnit\Framework\Attributes\Depends; +use PHPUnit\Framework\Attributes\RequiresEnvironmentVariable; +use PHPUnit\Framework\Attributes\RunInSeparateProcess; +use PHPUnit\Framework\Attributes\WithEnvironmentVariable; +use PHPUnit\Framework\TestCase; + +#[WithEnvironmentVariable('FOO', 'foo')] +final class WithEnvironmentVariableTest extends TestCase +{ + public function testFOOShouldHaveValueFromClassAttribute1(): void + { + $this->assertSame('foo', $_ENV['FOO']); + $this->assertSame('foo', getenv('FOO')); + } + + #[WithEnvironmentVariable('FOO', 'bar')] + public function testFOOShouldHaveValueOverriddenFromMethodAttribute(): void + { + $this->assertSame('bar', $_ENV['FOO']); + $this->assertSame('bar', getenv('FOO')); + } + + #[Depends('testFOOShouldHaveValueOverriddenFromMethodAttribute')] + public function testFOOShouldHaveValueFromClassAttribute2(): void + { + $this->testFOOShouldHaveValueFromClassAttribute1(); + } + + #[WithEnvironmentVariable('FOO')] + public function testFOOShouldHaveValueErasedFromMethodAttribute(): void + { + $this->assertFalse(isset($_ENV['FOO'])); + $this->assertFalse(getenv('FOO')); + } + + #[Depends('testFOOShouldHaveValueErasedFromMethodAttribute')] + public function testFOOShouldHaveValueFromClassAttribute3(): void + { + $this->testFOOShouldHaveValueFromClassAttribute1(); + } + + #[WithEnvironmentVariable('BAR', 'bar')] + public function testBARShouldHaveValueFromMethodAttribute(): void + { + $this->assertSame('bar', $_ENV['BAR']); + $this->assertSame('bar', getenv('BAR')); + } + + #[Depends('testBARShouldHaveValueFromMethodAttribute')] + public function testBARShouldHaveBeenRestoredToDefaultValue1(): void + { + $this->assertSame('2', $_ENV['BAR']); + $this->assertSame('2', getenv('BAR')); + } + + #[WithEnvironmentVariable('BAR')] + public function testBARShouldHaveValueErasedFromMethodAttribute(): void + { + $this->assertFalse(isset($_ENV['BAR'])); + $this->assertFalse(getenv('BAR')); + } + + #[Depends('testBARShouldHaveValueErasedFromMethodAttribute')] + public function testBARShouldHaveBeenRestoredToDefaultValue2(): void + { + $this->testBARShouldHaveBeenRestoredToDefaultValue1(); + } + + #[WithEnvironmentVariable('BAZ', 'baz')] + public function testBAZShouldHaveValueFromMethodAttribute(): void + { + $this->assertSame('baz', $_ENV['BAZ']); + $this->assertSame('baz', getenv('BAZ')); + } + + #[Depends('testBAZShouldHaveValueFromMethodAttribute')] + public function testBAZShouldHaveBeenErased(): void + { + $this->assertFalse(isset($_ENV['BAZ'])); + $this->assertFalse(getenv('BAZ')); + } + + #[RunInSeparateProcess] + #[WithEnvironmentVariable('BAR', 'bar')] + public function testRunInSeparateProcess(): void + { + $this->assertSame('foo', $_ENV['FOO']); + $this->assertSame('foo', getenv('FOO')); + + $this->assertSame('bar', $_ENV['BAR']); + $this->assertSame('bar', getenv('BAR')); + } + + #[WithEnvironmentVariable('BAZ', '1')] + #[WithEnvironmentVariable('BAZ', '2')] + #[WithEnvironmentVariable('BAZ', '3')] + public function testMultipleAttributesKeepTheLastValue(): void + { + $this->assertSame('3', $_ENV['BAZ']); + $this->assertSame('3', getenv('BAZ')); + } + + #[WithEnvironmentVariable('BAZ', '1')] + #[RequiresEnvironmentVariable('BAZ', '1')] + public function testUsingAlongWithRequiresEnvironmentVariableAttribute(): void + { + $this->assertSame('1', $_ENV['BAZ']); + $this->assertSame('1', getenv('BAZ')); + } +} diff --git a/tests/end-to-end/_files/with_environment_variable/phpunit.xml b/tests/end-to-end/_files/with_environment_variable/phpunit.xml new file mode 100644 index 00000000000..aeed5d5c254 --- /dev/null +++ b/tests/end-to-end/_files/with_environment_variable/phpunit.xml @@ -0,0 +1,17 @@ + + + + + WithEnvironmentVariableTest.php + WithEnvironmentVariableHookTest.php + + + + + + + + diff --git a/tests/end-to-end/abstract-test-class.phpt b/tests/end-to-end/abstract-test-class.phpt deleted file mode 100644 index a66d44f1e1b..00000000000 --- a/tests/end-to-end/abstract-test-class.phpt +++ /dev/null @@ -1,11 +0,0 @@ ---TEST-- -phpunit ../../_files/AbstractTest.php ---FILE-- -run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Cannot read baseline %sdoes-not-exist.xml, file does not exist + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/baseline/baseline-invalid-xml.phpt b/tests/end-to-end/baseline/baseline-invalid-xml.phpt new file mode 100644 index 00000000000..66ff1b95eb0 --- /dev/null +++ b/tests/end-to-end/baseline/baseline-invalid-xml.phpt @@ -0,0 +1,27 @@ +--TEST-- +phpunit --configuration ../_files/baseline/invalid-baseline/phpunit.xml +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Cannot read baseline %sbaseline.xml: %s + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/baseline/generate-baseline-suppressed-with-ignored-suppression.phpt b/tests/end-to-end/baseline/generate-baseline-suppressed-with-ignored-suppression.phpt new file mode 100644 index 00000000000..3d7d271c60c --- /dev/null +++ b/tests/end-to-end/baseline/generate-baseline-suppressed-with-ignored-suppression.phpt @@ -0,0 +1,56 @@ +--TEST-- +phpunit --configuration ../_files/baseline/generate-baseline-suppressed-with-ignored-suppression/phpunit.xml --generate-baseline +--FILE-- +run($_SERVER['argv']); + +print file_get_contents($baseline); + +@unlink($baseline); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +DNWDW 5 / 5 (100%) + +Time: %s, Memory: %s + +OK, but there were issues! +Tests: 5, Assertions: 5, Warnings: 2, Deprecations: 2, Notices: 2. + +Baseline written to %sbaseline.xml. + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/end-to-end/baseline/generate-baseline-suppressed.phpt b/tests/end-to-end/baseline/generate-baseline-suppressed.phpt new file mode 100644 index 00000000000..183581c4266 --- /dev/null +++ b/tests/end-to-end/baseline/generate-baseline-suppressed.phpt @@ -0,0 +1,36 @@ +--TEST-- +phpunit --configuration ../_files/baseline/generate-baseline-suppressed/phpunit.xml --generate-baseline +--FILE-- +run($_SERVER['argv']); + +print file_get_contents($baseline); + +@unlink($baseline); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +..... 5 / 5 (100%) + +Time: %s, Memory: %s + +OK (5 tests, 5 assertions) + +Baseline written to %sbaseline.xml. + + diff --git a/tests/end-to-end/baseline/generate-baseline-with-relative-directory.phpt b/tests/end-to-end/baseline/generate-baseline-with-relative-directory.phpt new file mode 100644 index 00000000000..ec35876498a --- /dev/null +++ b/tests/end-to-end/baseline/generate-baseline-with-relative-directory.phpt @@ -0,0 +1,43 @@ +--TEST-- +phpunit --configuration ../_files/baseline/generate-baseline-with-relative-directory/phpunit.xml --generate-baseline +--FILE-- +run($_SERVER['argv']); + +print file_get_contents($baseline); + +@unlink($baselineAbsolutePath); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +D 1 / 1 (100%) + +Time: %s, Memory: %s + +OK, but there were issues! +Tests: 1, Assertions: 1, Deprecations: 1. + +Baseline written to %sbaseline.xml. + + + + + + + + diff --git a/tests/end-to-end/baseline/generate-baseline.phpt b/tests/end-to-end/baseline/generate-baseline.phpt new file mode 100644 index 00000000000..aa5938ec4dd --- /dev/null +++ b/tests/end-to-end/baseline/generate-baseline.phpt @@ -0,0 +1,56 @@ +--TEST-- +phpunit --configuration ../_files/baseline/generate-baseline/phpunit.xml --generate-baseline +--FILE-- +run($_SERVER['argv']); + +print file_get_contents($baseline); + +@unlink($baseline); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +DNWDW 5 / 5 (100%) + +Time: %s, Memory: %s + +OK, but there were issues! +Tests: 5, Assertions: 5, Warnings: 2, Deprecations: 2, Notices: 2. + +Baseline written to %sbaseline.xml. + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/end-to-end/baseline/ignore-baseline-testdox.phpt b/tests/end-to-end/baseline/ignore-baseline-testdox.phpt new file mode 100644 index 00000000000..3b501678ca4 --- /dev/null +++ b/tests/end-to-end/baseline/ignore-baseline-testdox.phpt @@ -0,0 +1,105 @@ +--TEST-- +phpunit --configuration ../_files/baseline/use-baseline/phpunit.xml --ignore-baseline --tes +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +DNWDW 5 / 5 (100%) + +Time: %s, Memory: %s + +Source (PHPUnit\TestFixture\Baseline\Source) + ⚠ Deprecation + ⚠ Notice + ⚠ Warning + ⚠ Php deprecation + ⚠ Php notice and warning + +1 test triggered 1 PHP warning: + +1) %sSource.php:81 +Undefined property: class@anonymous::$a + +Triggered by: + +* PHPUnit\TestFixture\Baseline\SourceTest::testPhpNoticeAndWarning + %sSourceTest.php:44 + +-- + +1 test triggered 1 warning: + +1) %sSource.php:57 +warning + +Triggered by: + +* PHPUnit\TestFixture\Baseline\SourceTest::testWarning + %sSourceTest.php:30 + +-- + +1 test triggered 1 PHP notice: + +1) %sSource.php:81 +Accessing static property class@anonymous::$a as non static + +Triggered by: + +* PHPUnit\TestFixture\Baseline\SourceTest::testPhpNoticeAndWarning + %sSourceTest.php:44 + +-- + +1 test triggered 1 notice: + +1) %sSource.php:52 +notice + +Triggered by: + +* PHPUnit\TestFixture\Baseline\SourceTest::testNotice + %sSourceTest.php:23 + +-- + +1 test triggered 1 PHP deprecation: + +1) %sSource.php:62 +Serializable@anonymous implements the Serializable interface, which is deprecated. Implement __serialize() and __unserialize() instead (or in addition, if support for old PHP versions is necessary) + +Triggered by: + +* PHPUnit\TestFixture\Baseline\SourceTest::testPhpDeprecation + %sSourceTest.php:37 + +-- + +1 test triggered 1 deprecation: + +1) %sSource.php:47 +deprecation + +Triggered by: + +* PHPUnit\TestFixture\Baseline\SourceTest::testDeprecation + %sSourceTest.php:16 + +OK, but there were issues! +Tests: 5, Assertions: 5, Warnings: 2, Deprecations: 2, Notices: 2. diff --git a/tests/end-to-end/baseline/ignore-baseline.phpt b/tests/end-to-end/baseline/ignore-baseline.phpt new file mode 100644 index 00000000000..d3e665662f0 --- /dev/null +++ b/tests/end-to-end/baseline/ignore-baseline.phpt @@ -0,0 +1,97 @@ +--TEST-- +phpunit --configuration ../_files/baseline/use-baseline/phpunit.xml --ignore-baseline +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +DNWDW 5 / 5 (100%) + +Time: %s, Memory: %s + +1 test triggered 1 PHP warning: + +1) %sSource.php:81 +Undefined property: class@anonymous::$a + +Triggered by: + +* PHPUnit\TestFixture\Baseline\SourceTest::testPhpNoticeAndWarning + %sSourceTest.php:44 + +-- + +1 test triggered 1 warning: + +1) %sSource.php:57 +warning + +Triggered by: + +* PHPUnit\TestFixture\Baseline\SourceTest::testWarning + %sSourceTest.php:30 + +-- + +1 test triggered 1 PHP notice: + +1) %sSource.php:81 +Accessing static property class@anonymous::$a as non static + +Triggered by: + +* PHPUnit\TestFixture\Baseline\SourceTest::testPhpNoticeAndWarning + %sSourceTest.php:44 + +-- + +1 test triggered 1 notice: + +1) %sSource.php:52 +notice + +Triggered by: + +* PHPUnit\TestFixture\Baseline\SourceTest::testNotice + %sSourceTest.php:23 + +-- + +1 test triggered 1 PHP deprecation: + +1) %sSource.php:62 +Serializable@anonymous implements the Serializable interface, which is deprecated. Implement __serialize() and __unserialize() instead (or in addition, if support for old PHP versions is necessary) + +Triggered by: + +* PHPUnit\TestFixture\Baseline\SourceTest::testPhpDeprecation + %sSourceTest.php:37 + +-- + +1 test triggered 1 deprecation: + +1) %sSource.php:47 +deprecation + +Triggered by: + +* PHPUnit\TestFixture\Baseline\SourceTest::testDeprecation + %sSourceTest.php:16 + +OK, but there were issues! +Tests: 5, Assertions: 5, Warnings: 2, Deprecations: 2, Notices: 2. diff --git a/tests/end-to-end/baseline/unsupported-baseline.phpt b/tests/end-to-end/baseline/unsupported-baseline.phpt new file mode 100644 index 00000000000..69f12e686dd --- /dev/null +++ b/tests/end-to-end/baseline/unsupported-baseline.phpt @@ -0,0 +1,27 @@ +--TEST-- +phpunit --configuration ../_files/baseline/unsupported-baseline/phpunit.xml +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Cannot read baseline %sbaseline.xml, version 0 is not supported + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/baseline/use-baseline-in-another-directory.phpt b/tests/end-to-end/baseline/use-baseline-in-another-directory.phpt new file mode 100644 index 00000000000..8b3e57bef0b --- /dev/null +++ b/tests/end-to-end/baseline/use-baseline-in-another-directory.phpt @@ -0,0 +1,26 @@ +--TEST-- +phpunit --configuration ../_files/baseline/use-baseline-in-another-directory/phpunit.xml --generate-baseline +--FILE-- +run($_SERVER['argv']); + +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) + +1 issue was ignored by baseline. diff --git a/tests/end-to-end/baseline/use-baseline-testdox.phpt b/tests/end-to-end/baseline/use-baseline-testdox.phpt new file mode 100644 index 00000000000..3bd0e04d640 --- /dev/null +++ b/tests/end-to-end/baseline/use-baseline-testdox.phpt @@ -0,0 +1,32 @@ +--TEST-- +phpunit --configuration ../_files/baseline/use-baseline/phpunit.xml --testdox +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +..... 5 / 5 (100%) + +Time: %s, Memory: %s + +Source (PHPUnit\TestFixture\Baseline\Source) + ✔ Deprecation + ✔ Notice + ✔ Warning + ✔ Php deprecation + ✔ Php notice and warning + +OK (5 tests, 5 assertions) + +6 issues were ignored by baseline. diff --git a/tests/end-to-end/baseline/use-baseline.phpt b/tests/end-to-end/baseline/use-baseline.phpt new file mode 100644 index 00000000000..6b2b333ed2f --- /dev/null +++ b/tests/end-to-end/baseline/use-baseline.phpt @@ -0,0 +1,24 @@ +--TEST-- +phpunit --configuration ../_files/baseline/use-baseline/phpunit.xml +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +..... 5 / 5 (100%) + +Time: %s, Memory: %s + +OK (5 tests, 5 assertions) + +6 issues were ignored by baseline. diff --git a/tests/end-to-end/check-php-configuration/failure-with-xdebug.phpt b/tests/end-to-end/check-php-configuration/failure-with-xdebug.phpt new file mode 100644 index 00000000000..0facd455235 --- /dev/null +++ b/tests/end-to-end/check-php-configuration/failure-with-xdebug.phpt @@ -0,0 +1,35 @@ +--TEST-- +phpunit --check-php-configuration (failure, Xdebug loaded) +--SKIPIF-- +run($_SERVER['argv']); +?> +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Checking whether PHP is configured according to https://docs.phpunit.de/en/%s/installation.html#configuring-php-for-development + +display_errors = On ... not ok (0) +display_startup_errors = On ... ok +error_reporting = -1 ... ok +xdebug.show_exception_trace = 0 ... ok +zend.assertions = 1 ... ok +assert.exception = 1 ... ok +memory_limit = -1 ... ok diff --git a/tests/end-to-end/check-php-configuration/failure-without-xdebug.phpt b/tests/end-to-end/check-php-configuration/failure-without-xdebug.phpt new file mode 100644 index 00000000000..6c25d6c145d --- /dev/null +++ b/tests/end-to-end/check-php-configuration/failure-without-xdebug.phpt @@ -0,0 +1,33 @@ +--TEST-- +phpunit --check-php-configuration (failure, Xdebug not loaded) +--SKIPIF-- +run($_SERVER['argv']); +?> +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Checking whether PHP is configured according to https://docs.phpunit.de/en/%s/installation.html#configuring-php-for-development + +display_errors = On ... not ok (0) +display_startup_errors = On ... ok +error_reporting = -1 ... ok +zend.assertions = 1 ... ok +assert.exception = 1 ... ok +memory_limit = -1 ... ok diff --git a/tests/end-to-end/check-php-configuration/success-with-xdebug.phpt b/tests/end-to-end/check-php-configuration/success-with-xdebug.phpt new file mode 100644 index 00000000000..ac98e46d246 --- /dev/null +++ b/tests/end-to-end/check-php-configuration/success-with-xdebug.phpt @@ -0,0 +1,35 @@ +--TEST-- +phpunit --check-php-configuration (success, Xdebug loaded) +--SKIPIF-- +run($_SERVER['argv']); +?> +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Checking whether PHP is configured according to https://docs.phpunit.de/en/%s/installation.html#configuring-php-for-development + +display_errors = On ... ok +display_startup_errors = On ... ok +error_reporting = -1 ... ok +xdebug.show_exception_trace = 0 ... ok +zend.assertions = 1 ... ok +assert.exception = 1 ... ok +memory_limit = -1 ... ok diff --git a/tests/end-to-end/check-php-configuration/success-without-xdebug.phpt b/tests/end-to-end/check-php-configuration/success-without-xdebug.phpt new file mode 100644 index 00000000000..7e8c16deb8e --- /dev/null +++ b/tests/end-to-end/check-php-configuration/success-without-xdebug.phpt @@ -0,0 +1,33 @@ +--TEST-- +phpunit --check-php-configuration (success, Xdebug not loaded) +--SKIPIF-- +run($_SERVER['argv']); +?> +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Checking whether PHP is configured according to https://docs.phpunit.de/en/%s/installation.html#configuring-php-for-development + +display_errors = On ... ok +display_startup_errors = On ... ok +error_reporting = -1 ... ok +zend.assertions = 1 ... ok +assert.exception = 1 ... ok +memory_limit = -1 ... ok diff --git a/tests/end-to-end/cli/_files/MyCommand.php b/tests/end-to-end/cli/_files/MyCommand.php deleted file mode 100644 index 6e624e04303..00000000000 --- a/tests/end-to-end/cli/_files/MyCommand.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\TextUI\Command; - -class MyCommand extends Command -{ - public function __construct() - { - $this->longOptions['my-option='] = 'myHandler'; - $this->longOptions['my-other-option'] = null; - } - - public function myHandler($value): void - { - print __METHOD__ . " {$value}\n"; - } -} diff --git a/tests/end-to-end/cli/_files/output-cli-help-color.txt b/tests/end-to-end/cli/_files/output-cli-help-color.txt deleted file mode 100644 index ed48ecd2f1a..00000000000 --- a/tests/end-to-end/cli/_files/output-cli-help-color.txt +++ /dev/null @@ -1,133 +0,0 @@ -Usage: - phpunit [options] UnitTest.php - phpunit [options] - -Code Coverage Options: - --coverage-clover   Generate code coverage report in Clover - XML format - --coverage-crap4j   Generate code coverage report in Crap4J - XML format - --coverage-html   Generate code coverage report in HTML - format - --coverage-php   Export PHP_CodeCoverage object to file - --coverage-text=  Generate code coverage report in text - format [default: standard output] - --coverage-xml   Generate code coverage report in PHPUnit - XML format - --coverage-filter   Include in code coverage analysis - --disable-coverage-ignore  Disable annotations for ignoring code - coverage - --no-coverage  Ignore code coverage configuration - -Logging Options: - --log-junit   Log test execution in JUnit XML format to - file - --log-teamcity   Log test execution in TeamCity format to - file - --testdox-html   Write agile documentation in HTML format - to file - --testdox-text   Write agile documentation in Text format - to file - --testdox-xml   Write agile documentation in XML format - to file - --reverse-list  Print defects in reverse order - -Test Selection Options: - --filter   Filter which tests to run - --testsuite   Filter which testsuite to run - --group   Only runs tests from the specified - group(s) - --exclude-group   Exclude tests from the specified group(s) - --list-groups  List available test groups - --list-suites  List available test suites - --list-tests  List available tests - --list-tests-xml   List available tests in XML format - --test-suffix   Only search for test in files with - specified suffix(es). Default: - Test.php,.phpt - -Test Execution Options: - --dont-report-useless-tests Do not report tests that do not test - anything - --strict-coverage  Be strict about @covers annotation usage - --strict-global-state  Be strict about changes to global state - --disallow-test-output  Be strict about output during tests - --disallow-resource-usage  Be strict about resource usage during - small tests - --enforce-time-limit  Enforce time limit based on test size - --default-time-limit=  Timeout in seconds for tests without - @small, @medium or @large - --disallow-todo-tests  Disallow @todo-annotated tests - - --process-isolation  Run each test in a separate PHP process - --globals-backup  Backup and restore $GLOBALS for each test - --static-backup  Backup and restore static attributes for - each test - - --colors=  Use colors in output ("never", "auto" or - "always") - --columns   Number of columns to use for progress - output - --columns max  Use maximum number of columns for - progress output - --stderr  Write to STDERR instead of STDOUT - --stop-on-defect  Stop execution upon first not-passed test - --stop-on-error  Stop execution upon first error - --stop-on-failure  Stop execution upon first error or - failure - --stop-on-warning  Stop execution upon first warning - --stop-on-risky  Stop execution upon first risky test - --stop-on-skipped  Stop execution upon first skipped test - --stop-on-incomplete  Stop execution upon first incomplete test - --fail-on-incomplete  Treat incomplete tests as failures - --fail-on-risky  Treat risky tests as failures - --fail-on-skipped  Treat skipped tests as failures - --fail-on-warning  Treat tests with warnings as failures - -v|--verbose  Output more verbose information - --debug  Display debugging information - - --repeat   Runs the test(s) repeatedly - --teamcity  Report test execution progress in - TeamCity format - --testdox  Report test execution progress in TestDox - format - --testdox-group  Only include tests from the specified - group(s) - --testdox-exclude-group  Exclude tests from the specified group(s) - --no-interaction  Disable TestDox progress animation - --printer   TestListener implementation to use - - --order-by=  Run tests in order: - default|defects|duration|no-depends|random|reverse|size - --random-order-seed=  Use a specific random seed for random - order - --cache-result  Write test results to cache file - --do-not-cache-result  Do not write test results to cache file - -Configuration Options: - --prepend   A PHP script that is included as early as - possible - --bootstrap   A PHP script that is included before the - tests run - -c|--configuration   Read configuration from XML file - --no-configuration  Ignore default configuration file - (phpunit.xml) - --no-logging  Ignore logging configuration - --extensions   A comma separated list of PHPUnit - extensions to load - --no-extensions  Do not load PHPUnit extensions - --include-path   Prepend PHP's include_path with given - path(s) - -d   Sets a php.ini value - --generate-configuration  Generate configuration file with - suggested settings - --cache-result-file=  Specify result cache path and filename - -Miscellaneous Options: - -h|--help  Prints this usage information - --version  Prints the version and exits - --atleast-version   Checks that version is greater than min - and exits - --check-version  Check whether PHPUnit is the latest - version - diff --git a/tests/end-to-end/cli/_files/output-cli-usage.txt b/tests/end-to-end/cli/_files/output-cli-usage.txt deleted file mode 100644 index 4709c432023..00000000000 --- a/tests/end-to-end/cli/_files/output-cli-usage.txt +++ /dev/null @@ -1,105 +0,0 @@ -PHPUnit %s by Sebastian Bergmann and contributors. - -Usage: - phpunit [options] UnitTest.php - phpunit [options] - -Code Coverage Options: - - --coverage-clover Generate code coverage report in Clover XML format - --coverage-crap4j Generate code coverage report in Crap4J XML format - --coverage-html Generate code coverage report in HTML format - --coverage-php Export PHP_CodeCoverage object to file - --coverage-text= Generate code coverage report in text format [default: standard output] - --coverage-xml Generate code coverage report in PHPUnit XML format - --coverage-filter Include in code coverage analysis - --disable-coverage-ignore Disable annotations for ignoring code coverage - --no-coverage Ignore code coverage configuration - -Logging Options: - - --log-junit Log test execution in JUnit XML format to file - --log-teamcity Log test execution in TeamCity format to file - --testdox-html Write agile documentation in HTML format to file - --testdox-text Write agile documentation in Text format to file - --testdox-xml Write agile documentation in XML format to file - --reverse-list Print defects in reverse order - -Test Selection Options: - - --filter Filter which tests to run - --testsuite Filter which testsuite to run - --group Only runs tests from the specified group(s) - --exclude-group Exclude tests from the specified group(s) - --list-groups List available test groups - --list-suites List available test suites - --list-tests List available tests - --list-tests-xml List available tests in XML format - --test-suffix Only search for test in files with specified suffix(es). Default: Test.php,.phpt - -Test Execution Options: - - --dont-report-useless-tests Do not report tests that do not test anything - --strict-coverage Be strict about @covers annotation usage - --strict-global-state Be strict about changes to global state - --disallow-test-output Be strict about output during tests - --disallow-resource-usage Be strict about resource usage during small tests - --enforce-time-limit Enforce time limit based on test size - --default-time-limit= Timeout in seconds for tests without @small, @medium or @large - --disallow-todo-tests Disallow @todo-annotated tests - - --process-isolation Run each test in a separate PHP process - --globals-backup Backup and restore $GLOBALS for each test - --static-backup Backup and restore static attributes for each test - - --colors= Use colors in output ("never", "auto" or "always") - --columns Number of columns to use for progress output - --columns max Use maximum number of columns for progress output - --stderr Write to STDERR instead of STDOUT - --stop-on-defect Stop execution upon first not-passed test - --stop-on-error Stop execution upon first error - --stop-on-failure Stop execution upon first error or failure - --stop-on-warning Stop execution upon first warning - --stop-on-risky Stop execution upon first risky test - --stop-on-skipped Stop execution upon first skipped test - --stop-on-incomplete Stop execution upon first incomplete test - --fail-on-incomplete Treat incomplete tests as failures - --fail-on-risky Treat risky tests as failures - --fail-on-skipped Treat skipped tests as failures - --fail-on-warning Treat tests with warnings as failures - -v|--verbose Output more verbose information - --debug Display debugging information - - --repeat Runs the test(s) repeatedly - --teamcity Report test execution progress in TeamCity format - --testdox Report test execution progress in TestDox format - --testdox-group Only include tests from the specified group(s) - --testdox-exclude-group Exclude tests from the specified group(s) - --no-interaction Disable TestDox progress animation - --printer TestListener implementation to use - - --order-by= Run tests in order: default|defects|duration|no-depends|random|reverse|size - --random-order-seed= Use a specific random seed for random order - --cache-result Write test results to cache file - --do-not-cache-result Do not write test results to cache file - -Configuration Options: - - --prepend A PHP script that is included as early as possible - --bootstrap A PHP script that is included before the tests run - -c|--configuration Read configuration from XML file - --no-configuration Ignore default configuration file (phpunit.xml) - --no-logging Ignore logging configuration - --extensions A comma separated list of PHPUnit extensions to load - --no-extensions Do not load PHPUnit extensions - --include-path Prepend PHP's include_path with given path(s) - -d Sets a php.ini value - --generate-configuration Generate configuration file with suggested settings - --cache-result-file= Specify result cache path and filename - -Miscellaneous Options: - - -h|--help Prints this usage information - --version Prints the version and exits - --atleast-version Checks that version is greater than min and exits - --check-version Check whether PHPUnit is the latest version diff --git a/tests/end-to-end/cli/bootstrap-not-found.phpt b/tests/end-to-end/cli/bootstrap-not-found.phpt index 9256eaef9a4..855cfcff297 100644 --- a/tests/end-to-end/cli/bootstrap-not-found.phpt +++ b/tests/end-to-end/cli/bootstrap-not-found.phpt @@ -4,9 +4,9 @@ Test fail on missing bootstrap --no-configuration --bootstrap nonExistingBootstrap.php --FILE-- run($_SERVER['argv']); --EXPECTF-- PHPUnit %s by Sebastian Bergmann and contributors. -Cannot open file "nonExistingBootstrap.php". +Cannot open bootstrap script "nonExistingBootstrap.php" diff --git a/tests/end-to-end/cli/columns-max.phpt b/tests/end-to-end/cli/columns-max.phpt deleted file mode 100644 index 1a63a967c9d..00000000000 --- a/tests/end-to-end/cli/columns-max.phpt +++ /dev/null @@ -1,18 +0,0 @@ ---TEST-- -phpunit --columns=max ../../_files/BankAccountTest.php ---FILE-- -run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +... 3 / 3 (100%) + +Time: %s, Memory: %s + +OK (3 tests, 3 assertions) diff --git a/tests/end-to-end/cli/columns/columns-too-few.phpt b/tests/end-to-end/cli/columns/columns-too-few.phpt new file mode 100644 index 00000000000..2dc69db0d31 --- /dev/null +++ b/tests/end-to-end/cli/columns/columns-too-few.phpt @@ -0,0 +1,27 @@ +--TEST-- +phpunit --columns=1 ../../../_files/BankAccountTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +... 3 / 3 (100%) + + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Less than 16 columns requested, number of columns set to 16 + +OK, but there were issues! +Tests: 3, Assertions: 3, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/cli/columns/columns.phpt b/tests/end-to-end/cli/columns/columns.phpt new file mode 100644 index 00000000000..e0c417548b2 --- /dev/null +++ b/tests/end-to-end/cli/columns/columns.phpt @@ -0,0 +1,21 @@ +--TEST-- +phpunit --columns=40 ../../../_files/BankAccountTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +... 3 / 3 (100%) + +Time: %s, Memory: %s + +OK (3 tests, 3 assertions) diff --git a/tests/end-to-end/cli/configuration-file-based-test-selection/defaultTestSuite-all.phpt b/tests/end-to-end/cli/configuration-file-based-test-selection/defaultTestSuite-all.phpt new file mode 100644 index 00000000000..2e5a2953b09 --- /dev/null +++ b/tests/end-to-end/cli/configuration-file-based-test-selection/defaultTestSuite-all.phpt @@ -0,0 +1,42 @@ +--TEST-- +Tests selected using with "--all" CLI option +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (%sphpunit.xml, 2 tests) +Test Suite Started (unit, 1 test) +Test Suite Started (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\DefaultTestSuite\UnitTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\DefaultTestSuite\UnitTest::testOne) +Test Prepared (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\DefaultTestSuite\UnitTest::testOne) +Test Passed (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\DefaultTestSuite\UnitTest::testOne) +Test Finished (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\DefaultTestSuite\UnitTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\DefaultTestSuite\UnitTest, 1 test) +Test Suite Finished (unit, 1 test) +Test Suite Started (end-to-end, 1 test) +Test Suite Started (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\DefaultTestSuite\EndToEndTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\DefaultTestSuite\EndToEndTest::testOne) +Test Prepared (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\DefaultTestSuite\EndToEndTest::testOne) +Test Passed (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\DefaultTestSuite\EndToEndTest::testOne) +Test Finished (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\DefaultTestSuite\EndToEndTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\DefaultTestSuite\EndToEndTest, 1 test) +Test Suite Finished (end-to-end, 1 test) +Test Suite Finished (%sphpunit.xml, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/configuration-file-based-test-selection/defaultTestSuite.phpt b/tests/end-to-end/cli/configuration-file-based-test-selection/defaultTestSuite.phpt new file mode 100644 index 00000000000..09771346f69 --- /dev/null +++ b/tests/end-to-end/cli/configuration-file-based-test-selection/defaultTestSuite.phpt @@ -0,0 +1,33 @@ +--TEST-- +Tests selected using +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (%sphpunit.xml, 1 test) +Test Suite Started (unit, 1 test) +Test Suite Started (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\DefaultTestSuite\UnitTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\DefaultTestSuite\UnitTest::testOne) +Test Prepared (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\DefaultTestSuite\UnitTest::testOne) +Test Passed (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\DefaultTestSuite\UnitTest::testOne) +Test Finished (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\DefaultTestSuite\UnitTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\DefaultTestSuite\UnitTest, 1 test) +Test Suite Finished (unit, 1 test) +Test Suite Finished (%sphpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/configuration-file-based-test-selection/groups-all.phpt b/tests/end-to-end/cli/configuration-file-based-test-selection/groups-all.phpt new file mode 100644 index 00000000000..519ff80e191 --- /dev/null +++ b/tests/end-to-end/cli/configuration-file-based-test-selection/groups-all.phpt @@ -0,0 +1,38 @@ +--TEST-- +Tests selected using with "--all" CLI option +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (%sphpunit.xml, 2 tests) +Test Suite Started (default, 2 tests) +Test Suite Started (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\Groups\ExampleTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\Groups\ExampleTest::testOne) +Test Prepared (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\Groups\ExampleTest::testOne) +Test Passed (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\Groups\ExampleTest::testOne) +Test Finished (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\Groups\ExampleTest::testOne) +Test Preparation Started (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\Groups\ExampleTest::testTwo) +Test Prepared (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\Groups\ExampleTest::testTwo) +Test Passed (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\Groups\ExampleTest::testTwo) +Test Finished (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\Groups\ExampleTest::testTwo) +Test Suite Finished (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\Groups\ExampleTest, 2 tests) +Test Suite Finished (default, 2 tests) +Test Suite Finished (%sphpunit.xml, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/configuration-file-based-test-selection/groups.phpt b/tests/end-to-end/cli/configuration-file-based-test-selection/groups.phpt new file mode 100644 index 00000000000..107ce7a4968 --- /dev/null +++ b/tests/end-to-end/cli/configuration-file-based-test-selection/groups.phpt @@ -0,0 +1,34 @@ +--TEST-- +Tests selected using +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (1 test) +Test Runner Execution Started (1 test) +Test Suite Started (%sphpunit.xml, 1 test) +Test Suite Started (default, 1 test) +Test Suite Started (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\Groups\ExampleTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\Groups\ExampleTest::testOne) +Test Prepared (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\Groups\ExampleTest::testOne) +Test Passed (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\Groups\ExampleTest::testOne) +Test Finished (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\Groups\ExampleTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\ConfigurationFileBasedTestSelection\Groups\ExampleTest, 1 test) +Test Suite Finished (default, 1 test) +Test Suite Finished (%sphpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/coverage/coverage-no-tests-when-missing-coverage-driver.phpt b/tests/end-to-end/cli/coverage/coverage-no-tests-when-missing-coverage-driver.phpt new file mode 100644 index 00000000000..49a4a9a327b --- /dev/null +++ b/tests/end-to-end/cli/coverage/coverage-no-tests-when-missing-coverage-driver.phpt @@ -0,0 +1,27 @@ +--TEST-- +Don't run tests when coverage driver is not loaded +--SKIPIF-- +run($_SERVER['argv']); +--EXPECTF-- +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/cli/coverage/coverage-no-tests-when-wrong-xdebug-mode.phpt b/tests/end-to-end/cli/coverage/coverage-no-tests-when-wrong-xdebug-mode.phpt new file mode 100644 index 00000000000..b7aa71e1841 --- /dev/null +++ b/tests/end-to-end/cli/coverage/coverage-no-tests-when-wrong-xdebug-mode.phpt @@ -0,0 +1,26 @@ +--TEST-- +Don't run tests when wrong xdebug mode is set +--SKIPIF-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/cli/do-not-fail-on/do-not-fail-on-deprecation.phpt b/tests/end-to-end/cli/do-not-fail-on/do-not-fail-on-deprecation.phpt new file mode 100644 index 00000000000..23b7264c4bf --- /dev/null +++ b/tests/end-to-end/cli/do-not-fail-on/do-not-fail-on-deprecation.phpt @@ -0,0 +1,39 @@ +--TEST-- +todo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (%s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (8 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (1 test) +Test Runner Execution Started (1 test) +Test Suite Started (%sphpunit.xml, 1 test) +Test Suite Started (default, 1 test) +Test Suite Started (PHPUnit\TestFixture\DoNotFailOn\IssueTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatTriggersDeprecation) +Test Prepared (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatTriggersDeprecation) +Test Triggered Deprecation (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatTriggersDeprecation, unknown if issue was triggered in first-party code or third-party code) in %sIssueTest.php:%d +message +Test Passed (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatTriggersDeprecation) +Test Finished (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatTriggersDeprecation) +Test Suite Finished (PHPUnit\TestFixture\DoNotFailOn\IssueTest, 1 test) +Test Suite Finished (default, 1 test) +Test Suite Finished (%sphpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/do-not-fail-on/do-not-fail-on-incomplete.phpt b/tests/end-to-end/cli/do-not-fail-on/do-not-fail-on-incomplete.phpt new file mode 100644 index 00000000000..52ecd8ba19d --- /dev/null +++ b/tests/end-to-end/cli/do-not-fail-on/do-not-fail-on-incomplete.phpt @@ -0,0 +1,38 @@ +--TEST-- +todo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (%s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (8 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (1 test) +Test Runner Execution Started (1 test) +Test Suite Started (%sphpunit.xml, 1 test) +Test Suite Started (default, 1 test) +Test Suite Started (PHPUnit\TestFixture\DoNotFailOn\IssueTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatIsIncomplete) +Test Prepared (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatIsIncomplete) +Test Marked Incomplete (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatIsIncomplete) +message +Test Finished (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatIsIncomplete) +Test Suite Finished (PHPUnit\TestFixture\DoNotFailOn\IssueTest, 1 test) +Test Suite Finished (default, 1 test) +Test Suite Finished (%sphpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/do-not-fail-on/do-not-fail-on-notice.phpt b/tests/end-to-end/cli/do-not-fail-on/do-not-fail-on-notice.phpt new file mode 100644 index 00000000000..1cd528296a4 --- /dev/null +++ b/tests/end-to-end/cli/do-not-fail-on/do-not-fail-on-notice.phpt @@ -0,0 +1,39 @@ +--TEST-- +todo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (%s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (8 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (1 test) +Test Runner Execution Started (1 test) +Test Suite Started (%sphpunit.xml, 1 test) +Test Suite Started (default, 1 test) +Test Suite Started (PHPUnit\TestFixture\DoNotFailOn\IssueTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatTriggersNotice) +Test Prepared (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatTriggersNotice) +Test Triggered Notice (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatTriggersNotice) in %sIssueTest.php:%d +message +Test Passed (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatTriggersNotice) +Test Finished (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatTriggersNotice) +Test Suite Finished (PHPUnit\TestFixture\DoNotFailOn\IssueTest, 1 test) +Test Suite Finished (default, 1 test) +Test Suite Finished (%sphpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/do-not-fail-on/do-not-fail-on-phpunit-deprecation.phpt b/tests/end-to-end/cli/do-not-fail-on/do-not-fail-on-phpunit-deprecation.phpt new file mode 100644 index 00000000000..7ac3dd34953 --- /dev/null +++ b/tests/end-to-end/cli/do-not-fail-on/do-not-fail-on-phpunit-deprecation.phpt @@ -0,0 +1,39 @@ +--TEST-- +todo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (%s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (8 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (1 test) +Test Runner Execution Started (1 test) +Test Suite Started (%sphpunit.xml, 1 test) +Test Suite Started (default, 1 test) +Test Suite Started (PHPUnit\TestFixture\DoNotFailOn\IssueTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatTriggersPhpunitDeprecation) +Test Prepared (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatTriggersPhpunitDeprecation) +Test Triggered PHPUnit Deprecation (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatTriggersPhpunitDeprecation) +message +Test Passed (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatTriggersPhpunitDeprecation) +Test Finished (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatTriggersPhpunitDeprecation) +Test Suite Finished (PHPUnit\TestFixture\DoNotFailOn\IssueTest, 1 test) +Test Suite Finished (default, 1 test) +Test Suite Finished (%sphpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/do-not-fail-on/do-not-fail-on-phpunit-warning.phpt b/tests/end-to-end/cli/do-not-fail-on/do-not-fail-on-phpunit-warning.phpt new file mode 100644 index 00000000000..b6ae1c5e952 --- /dev/null +++ b/tests/end-to-end/cli/do-not-fail-on/do-not-fail-on-phpunit-warning.phpt @@ -0,0 +1,39 @@ +--TEST-- +todo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (%s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (8 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (1 test) +Test Runner Execution Started (1 test) +Test Suite Started (%sphpunit.xml, 1 test) +Test Suite Started (default, 1 test) +Test Suite Started (PHPUnit\TestFixture\DoNotFailOn\IssueTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatTriggersPhpunitWarning) +Test Prepared (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatTriggersPhpunitWarning) +Test Triggered PHPUnit Warning (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatTriggersPhpunitWarning) +message +Test Passed (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatTriggersPhpunitWarning) +Test Finished (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatTriggersPhpunitWarning) +Test Suite Finished (PHPUnit\TestFixture\DoNotFailOn\IssueTest, 1 test) +Test Suite Finished (default, 1 test) +Test Suite Finished (%sphpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/do-not-fail-on/do-not-fail-on-risky.phpt b/tests/end-to-end/cli/do-not-fail-on/do-not-fail-on-risky.phpt new file mode 100644 index 00000000000..422729b06c1 --- /dev/null +++ b/tests/end-to-end/cli/do-not-fail-on/do-not-fail-on-risky.phpt @@ -0,0 +1,39 @@ +--TEST-- +todo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (%s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (8 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (1 test) +Test Runner Execution Started (1 test) +Test Suite Started (%sphpunit.xml, 1 test) +Test Suite Started (default, 1 test) +Test Suite Started (PHPUnit\TestFixture\DoNotFailOn\IssueTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatIsRisky) +Test Prepared (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatIsRisky) +Test Passed (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatIsRisky) +Test Considered Risky (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatIsRisky) +This test did not perform any assertions +Test Finished (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatIsRisky) +Test Suite Finished (PHPUnit\TestFixture\DoNotFailOn\IssueTest, 1 test) +Test Suite Finished (default, 1 test) +Test Suite Finished (%sphpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/do-not-fail-on/do-not-fail-on-skipped.phpt b/tests/end-to-end/cli/do-not-fail-on/do-not-fail-on-skipped.phpt new file mode 100644 index 00000000000..2475b4d1d24 --- /dev/null +++ b/tests/end-to-end/cli/do-not-fail-on/do-not-fail-on-skipped.phpt @@ -0,0 +1,38 @@ +--TEST-- +todo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (%s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (8 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (1 test) +Test Runner Execution Started (1 test) +Test Suite Started (%sphpunit.xml, 1 test) +Test Suite Started (default, 1 test) +Test Suite Started (PHPUnit\TestFixture\DoNotFailOn\IssueTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatIsSkipped) +Test Prepared (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatIsSkipped) +Test Skipped (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatIsSkipped) +message +Test Finished (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatIsSkipped) +Test Suite Finished (PHPUnit\TestFixture\DoNotFailOn\IssueTest, 1 test) +Test Suite Finished (default, 1 test) +Test Suite Finished (%sphpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/do-not-fail-on/do-not-fail-on-warning.phpt b/tests/end-to-end/cli/do-not-fail-on/do-not-fail-on-warning.phpt new file mode 100644 index 00000000000..7f11b5f1d01 --- /dev/null +++ b/tests/end-to-end/cli/do-not-fail-on/do-not-fail-on-warning.phpt @@ -0,0 +1,39 @@ +--TEST-- +todo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (%s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (8 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (1 test) +Test Runner Execution Started (1 test) +Test Suite Started (%sphpunit.xml, 1 test) +Test Suite Started (default, 1 test) +Test Suite Started (PHPUnit\TestFixture\DoNotFailOn\IssueTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatTriggersWarning) +Test Prepared (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatTriggersWarning) +Test Triggered Warning (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatTriggersWarning) in %sIssueTest.php:%d +message +Test Passed (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatTriggersWarning) +Test Finished (PHPUnit\TestFixture\DoNotFailOn\IssueTest::testThatTriggersWarning) +Test Suite Finished (PHPUnit\TestFixture\DoNotFailOn\IssueTest, 1 test) +Test Suite Finished (default, 1 test) +Test Suite Finished (%sphpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/exclude-filter/match.phpt b/tests/end-to-end/cli/exclude-filter/match.phpt new file mode 100644 index 00000000000..c78875601bf --- /dev/null +++ b/tests/end-to-end/cli/exclude-filter/match.phpt @@ -0,0 +1,36 @@ +--TEST-- +phpunit --exclude-filter testThree ../../_files/groups/tests/FooTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (3 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (2 tests) +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\Groups\FooTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Prepared (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Passed (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Finished (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Preparation Started (PHPUnit\TestFixture\Groups\FooTest::testTwo) +Test Prepared (PHPUnit\TestFixture\Groups\FooTest::testTwo) +Test Passed (PHPUnit\TestFixture\Groups\FooTest::testTwo) +Test Finished (PHPUnit\TestFixture\Groups\FooTest::testTwo) +Test Suite Finished (PHPUnit\TestFixture\Groups\FooTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/exclude-multiple-test-suites.phpt b/tests/end-to-end/cli/exclude-multiple-test-suites.phpt new file mode 100644 index 00000000000..eb31b2259ac --- /dev/null +++ b/tests/end-to-end/cli/exclude-multiple-test-suites.phpt @@ -0,0 +1,25 @@ +--TEST-- +Exclude multiple test suites using --exclude-testsuite +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (0 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (0 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/exclude-single-test-suite.phpt b/tests/end-to-end/cli/exclude-single-test-suite.phpt new file mode 100644 index 00000000000..9b33cbfab7c --- /dev/null +++ b/tests/end-to-end/cli/exclude-single-test-suite.phpt @@ -0,0 +1,35 @@ +--TEST-- +Exclude single test suite using --exclude-testsuite +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (%sphpunit.xml, 1 test) +Test Suite Started (end-to-end, 1 test) +Test Suite Started (PHPUnit\TestFixture\BarTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\BarTest::testOne) +Test Prepared (PHPUnit\TestFixture\BarTest::testOne) +Test Passed (PHPUnit\TestFixture\BarTest::testOne) +Test Finished (PHPUnit\TestFixture\BarTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\BarTest, 1 test) +Test Suite Finished (end-to-end, 1 test) +Test Suite Finished (%sphpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/fail-on/do-not-fail-on-deprecation-and-fail-on-deprecation.phpt b/tests/end-to-end/cli/fail-on/do-not-fail-on-deprecation-and-fail-on-deprecation.phpt new file mode 100644 index 00000000000..36cdc936011 --- /dev/null +++ b/tests/end-to-end/cli/fail-on/do-not-fail-on-deprecation-and-fail-on-deprecation.phpt @@ -0,0 +1,18 @@ +--TEST-- +todo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +%A +Test Runner Triggered Warning (Options --fail-on-deprecation and --do-not-fail-on-deprecation cannot be used together) +%A diff --git a/tests/end-to-end/cli/fail-on/do-not-fail-on-empty-test-suite-and-fail-on-empty-test-suite.phpt b/tests/end-to-end/cli/fail-on/do-not-fail-on-empty-test-suite-and-fail-on-empty-test-suite.phpt new file mode 100644 index 00000000000..8724ed7bcc2 --- /dev/null +++ b/tests/end-to-end/cli/fail-on/do-not-fail-on-empty-test-suite-and-fail-on-empty-test-suite.phpt @@ -0,0 +1,20 @@ +--TEST-- +todo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +%A +Test Runner Triggered Warning (Options --fail-on-empty-test-suite and --do-not-fail-on-empty-test-suite cannot be used together) +%A diff --git a/tests/end-to-end/cli/fail-on/do-not-fail-on-empty-test-suite-by-default.phpt b/tests/end-to-end/cli/fail-on/do-not-fail-on-empty-test-suite-by-default.phpt new file mode 100644 index 00000000000..2a0dfae0479 --- /dev/null +++ b/tests/end-to-end/cli/fail-on/do-not-fail-on-empty-test-suite-by-default.phpt @@ -0,0 +1,26 @@ +--TEST-- +Test Runner exits with shell exit code indicating success by default when no tests were run +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (0 tests) +Test Runner Execution Started (0 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/fail-on/do-not-fail-on-incomplete-and-fail-on-incomplete.phpt b/tests/end-to-end/cli/fail-on/do-not-fail-on-incomplete-and-fail-on-incomplete.phpt new file mode 100644 index 00000000000..80739417eb2 --- /dev/null +++ b/tests/end-to-end/cli/fail-on/do-not-fail-on-incomplete-and-fail-on-incomplete.phpt @@ -0,0 +1,18 @@ +--TEST-- +todo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +%A +Test Runner Triggered Warning (Options --fail-on-incomplete and --do-not-fail-on-incomplete cannot be used together) +%A diff --git a/tests/end-to-end/cli/fail-on/do-not-fail-on-notice-and-fail-on-notice.phpt b/tests/end-to-end/cli/fail-on/do-not-fail-on-notice-and-fail-on-notice.phpt new file mode 100644 index 00000000000..e1be85820b0 --- /dev/null +++ b/tests/end-to-end/cli/fail-on/do-not-fail-on-notice-and-fail-on-notice.phpt @@ -0,0 +1,18 @@ +--TEST-- +todo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +%A +Test Runner Triggered Warning (Options --fail-on-notice and --do-not-fail-on-notice cannot be used together) +%A diff --git a/tests/end-to-end/cli/fail-on/do-not-fail-on-phpunit-deprecation-and-fail-on-phpunit-deprecation.phpt b/tests/end-to-end/cli/fail-on/do-not-fail-on-phpunit-deprecation-and-fail-on-phpunit-deprecation.phpt new file mode 100644 index 00000000000..f26a473539d --- /dev/null +++ b/tests/end-to-end/cli/fail-on/do-not-fail-on-phpunit-deprecation-and-fail-on-phpunit-deprecation.phpt @@ -0,0 +1,18 @@ +--TEST-- +todo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +%A +Test Runner Triggered Warning (Options --fail-on-phpunit-deprecation and --do-not-fail-on-phpunit-deprecation cannot be used together) +%A diff --git a/tests/end-to-end/cli/fail-on/do-not-fail-on-risky-and-fail-on-risky.phpt b/tests/end-to-end/cli/fail-on/do-not-fail-on-risky-and-fail-on-risky.phpt new file mode 100644 index 00000000000..d5386a8b939 --- /dev/null +++ b/tests/end-to-end/cli/fail-on/do-not-fail-on-risky-and-fail-on-risky.phpt @@ -0,0 +1,18 @@ +--TEST-- +todo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +%A +Test Runner Triggered Warning (Options --fail-on-risky and --do-not-fail-on-risky cannot be used together) +%A diff --git a/tests/end-to-end/cli/fail-on/do-not-fail-on-skipped-and-fail-on-skipped.phpt b/tests/end-to-end/cli/fail-on/do-not-fail-on-skipped-and-fail-on-skipped.phpt new file mode 100644 index 00000000000..596c1268e64 --- /dev/null +++ b/tests/end-to-end/cli/fail-on/do-not-fail-on-skipped-and-fail-on-skipped.phpt @@ -0,0 +1,18 @@ +--TEST-- +todo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +%A +Test Runner Triggered Warning (Options --fail-on-skipped and --do-not-fail-on-skipped cannot be used together) +%A diff --git a/tests/end-to-end/cli/fail-on/do-not-fail-on-warning-and-fail-on-warning.phpt b/tests/end-to-end/cli/fail-on/do-not-fail-on-warning-and-fail-on-warning.phpt new file mode 100644 index 00000000000..f0859f85279 --- /dev/null +++ b/tests/end-to-end/cli/fail-on/do-not-fail-on-warning-and-fail-on-warning.phpt @@ -0,0 +1,18 @@ +--TEST-- +todo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +%A +Test Runner Triggered Warning (Options --fail-on-warning and --do-not-fail-on-warning cannot be used together) +%A diff --git a/tests/end-to-end/cli/fail-on/fail-on-deprecation-and-do-not-fail-on-deprecation.phpt b/tests/end-to-end/cli/fail-on/fail-on-deprecation-and-do-not-fail-on-deprecation.phpt new file mode 100644 index 00000000000..83eeca34504 --- /dev/null +++ b/tests/end-to-end/cli/fail-on/fail-on-deprecation-and-do-not-fail-on-deprecation.phpt @@ -0,0 +1,18 @@ +--TEST-- +todo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +%A +Test Runner Triggered Warning (Options --do-not-fail-on-deprecation and --fail-on-deprecation cannot be used together) +%A diff --git a/tests/end-to-end/cli/fail-on/fail-on-deprecation-output.phpt b/tests/end-to-end/cli/fail-on/fail-on-deprecation-output.phpt new file mode 100644 index 00000000000..811237e3e32 --- /dev/null +++ b/tests/end-to-end/cli/fail-on/fail-on-deprecation-output.phpt @@ -0,0 +1,38 @@ +--TEST-- +Details for deprecations are displayed when --fail-on-deprecation is used +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +D.D 3 / 3 (100%) + +Time: %s, Memory: %s + +1 test triggered 1 PHPUnit deprecation: + +1) PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testThree +message + +%sDeprecationTest.php:%d + +-- + +1 test triggered 1 deprecation: + +1) %sDeprecationTest.php:%d +message + +OK, but there were issues! +Tests: 3, Assertions: 3, Deprecations: 1, PHPUnit Deprecations: 1. diff --git a/tests/end-to-end/cli/fail-on/fail-on-deprecation.phpt b/tests/end-to-end/cli/fail-on/fail-on-deprecation.phpt new file mode 100644 index 00000000000..6f182f63c96 --- /dev/null +++ b/tests/end-to-end/cli/fail-on/fail-on-deprecation.phpt @@ -0,0 +1,42 @@ +--TEST-- +Test Runner exits with shell exit code indicating failure when all tests are successful but at least one test triggered a deprecation +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (3 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (3 tests) +Test Suite Started (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest, 3 tests) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testOne) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testOne) +Test Triggered Deprecation (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testOne, unknown if issue was triggered in first-party code or third-party code) in %s:%d +message +Test Passed (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testOne) +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testOne) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testTwo) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testTwo) +Test Passed (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testTwo) +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testTwo) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testThree) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testThree) +Test Triggered PHPUnit Deprecation (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testThree) +message +Test Passed (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testThree) +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testThree) +Test Suite Finished (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest, 3 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/cli/fail-on/fail-on-empty-test-suite-and-do-not-fail-on-empty-test-suite.phpt b/tests/end-to-end/cli/fail-on/fail-on-empty-test-suite-and-do-not-fail-on-empty-test-suite.phpt new file mode 100644 index 00000000000..17e8c8af92f --- /dev/null +++ b/tests/end-to-end/cli/fail-on/fail-on-empty-test-suite-and-do-not-fail-on-empty-test-suite.phpt @@ -0,0 +1,20 @@ +--TEST-- +todo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +%A +Test Runner Triggered Warning (Options --do-not-fail-on-empty-test-suite and --fail-on-empty-test-suite cannot be used together) +%A diff --git a/tests/end-to-end/cli/fail-on/fail-on-empty-test-suite.phpt b/tests/end-to-end/cli/fail-on/fail-on-empty-test-suite.phpt new file mode 100644 index 00000000000..e900ce18e87 --- /dev/null +++ b/tests/end-to-end/cli/fail-on/fail-on-empty-test-suite.phpt @@ -0,0 +1,27 @@ +--TEST-- +Test Runner exits with shell exit code indicating failure when no tests were run and --fail-on-empty-test-suite option is used +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (0 tests) +Test Runner Execution Started (0 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/cli/fail-on/fail-on-incomplete-and-do-not-fail-on-incomplete.phpt b/tests/end-to-end/cli/fail-on/fail-on-incomplete-and-do-not-fail-on-incomplete.phpt new file mode 100644 index 00000000000..8e15c5dc0e8 --- /dev/null +++ b/tests/end-to-end/cli/fail-on/fail-on-incomplete-and-do-not-fail-on-incomplete.phpt @@ -0,0 +1,18 @@ +--TEST-- +todo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +%A +Test Runner Triggered Warning (Options --do-not-fail-on-incomplete and --fail-on-incomplete cannot be used together) +%A diff --git a/tests/end-to-end/cli/fail-on/fail-on-incomplete-output.phpt b/tests/end-to-end/cli/fail-on/fail-on-incomplete-output.phpt new file mode 100644 index 00000000000..e22c73e8ea1 --- /dev/null +++ b/tests/end-to-end/cli/fail-on/fail-on-incomplete-output.phpt @@ -0,0 +1,30 @@ +--TEST-- +Details for incomplete tests are displayed when --fail-on-incomplete is used +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +I. 2 / 2 (100%) + +Time: %s, Memory: %s + +There was 1 incomplete test: + +1) PHPUnit\TestFixture\TestRunnerStopping\IncompleteTest::testOne +message + +%sIncompleteTest.php:%d + +OK, but there were issues! +Tests: 2, Assertions: 1, Incomplete: 1. diff --git a/tests/end-to-end/cli/fail-on/fail-on-incomplete.phpt b/tests/end-to-end/cli/fail-on/fail-on-incomplete.phpt new file mode 100644 index 00000000000..a80fb40a1d4 --- /dev/null +++ b/tests/end-to-end/cli/fail-on/fail-on-incomplete.phpt @@ -0,0 +1,35 @@ +--TEST-- +Test Runner exits with shell exit code indicating failure when all tests are successful but at least one test was marked incomplete +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\TestRunnerStopping\IncompleteTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\IncompleteTest::testOne) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\IncompleteTest::testOne) +Test Marked Incomplete (PHPUnit\TestFixture\TestRunnerStopping\IncompleteTest::testOne) +message +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\IncompleteTest::testOne) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\IncompleteTest::testTwo) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\IncompleteTest::testTwo) +Test Passed (PHPUnit\TestFixture\TestRunnerStopping\IncompleteTest::testTwo) +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\IncompleteTest::testTwo) +Test Suite Finished (PHPUnit\TestFixture\TestRunnerStopping\IncompleteTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/cli/fail-on/fail-on-notice-and-do-not-fail-on-notice.phpt b/tests/end-to-end/cli/fail-on/fail-on-notice-and-do-not-fail-on-notice.phpt new file mode 100644 index 00000000000..53f63fb84a7 --- /dev/null +++ b/tests/end-to-end/cli/fail-on/fail-on-notice-and-do-not-fail-on-notice.phpt @@ -0,0 +1,18 @@ +--TEST-- +todo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +%A +Test Runner Triggered Warning (Options --do-not-fail-on-notice and --fail-on-notice cannot be used together) +%A diff --git a/tests/end-to-end/cli/fail-on/fail-on-notice-output.phpt b/tests/end-to-end/cli/fail-on/fail-on-notice-output.phpt new file mode 100644 index 00000000000..1662950e39f --- /dev/null +++ b/tests/end-to-end/cli/fail-on/fail-on-notice-output.phpt @@ -0,0 +1,28 @@ +--TEST-- +Details for notices are displayed when --fail-on-notice is used +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +N. 2 / 2 (100%) + +Time: %s, Memory: %s + +1 test triggered 1 notice: + +1) %sNoticeTest.php:%d +message + +OK, but there were issues! +Tests: 2, Assertions: 2, Notices: 1. diff --git a/tests/end-to-end/cli/fail-on/fail-on-notice.phpt b/tests/end-to-end/cli/fail-on/fail-on-notice.phpt new file mode 100644 index 00000000000..f9c1de85831 --- /dev/null +++ b/tests/end-to-end/cli/fail-on/fail-on-notice.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test Runner exits with shell exit code indicating failure when all tests are successful but at least one test triggered a notice +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\TestRunnerStopping\NoticeTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\NoticeTest::testOne) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\NoticeTest::testOne) +Test Triggered Notice (PHPUnit\TestFixture\TestRunnerStopping\NoticeTest::testOne) in %s:%d +message +Test Passed (PHPUnit\TestFixture\TestRunnerStopping\NoticeTest::testOne) +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\NoticeTest::testOne) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\NoticeTest::testTwo) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\NoticeTest::testTwo) +Test Passed (PHPUnit\TestFixture\TestRunnerStopping\NoticeTest::testTwo) +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\NoticeTest::testTwo) +Test Suite Finished (PHPUnit\TestFixture\TestRunnerStopping\NoticeTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/cli/fail-on/fail-on-phpunit-deprecation-and-do-not-fail-on-phpunit-deprecation.phpt b/tests/end-to-end/cli/fail-on/fail-on-phpunit-deprecation-and-do-not-fail-on-phpunit-deprecation.phpt new file mode 100644 index 00000000000..d350818bd7e --- /dev/null +++ b/tests/end-to-end/cli/fail-on/fail-on-phpunit-deprecation-and-do-not-fail-on-phpunit-deprecation.phpt @@ -0,0 +1,18 @@ +--TEST-- +todo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +%A +Test Runner Triggered Warning (Options --do-not-fail-on-phpunit-deprecation and --fail-on-phpunit-deprecation cannot be used together) +%A diff --git a/tests/end-to-end/cli/fail-on/fail-on-phpunit-deprecation.phpt b/tests/end-to-end/cli/fail-on/fail-on-phpunit-deprecation.phpt new file mode 100644 index 00000000000..e7bbf6b3e0a --- /dev/null +++ b/tests/end-to-end/cli/fail-on/fail-on-phpunit-deprecation.phpt @@ -0,0 +1,42 @@ +--TEST-- +Test Runner exits with shell exit code indicating failure when all tests are successful but at least one test triggered a PHPUnit deprecation +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (3 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (3 tests) +Test Suite Started (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest, 3 tests) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testOne) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testOne) +Test Triggered Deprecation (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testOne, unknown if issue was triggered in first-party code or third-party code) in %s:%d +message +Test Passed (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testOne) +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testOne) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testTwo) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testTwo) +Test Passed (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testTwo) +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testTwo) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testThree) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testThree) +Test Triggered PHPUnit Deprecation (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testThree) +message +Test Passed (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testThree) +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testThree) +Test Suite Finished (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest, 3 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/cli/fail-on/fail-on-phpunit-notice.phpt b/tests/end-to-end/cli/fail-on/fail-on-phpunit-notice.phpt new file mode 100644 index 00000000000..e4ffc139a22 --- /dev/null +++ b/tests/end-to-end/cli/fail-on/fail-on-phpunit-notice.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test Runner exits with shell exit code indicating failure when all tests are successful but at least one test triggered a PHPUnit notice +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\TestRunnerStopping\PhpunitNoticeTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\PhpunitNoticeTest::testOne) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\PhpunitNoticeTest::testOne) +Test Triggered PHPUnit Notice (PHPUnit\TestFixture\TestRunnerStopping\PhpunitNoticeTest::testOne) +message +Test Passed (PHPUnit\TestFixture\TestRunnerStopping\PhpunitNoticeTest::testOne) +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\PhpunitNoticeTest::testOne) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\PhpunitNoticeTest::testTwo) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\PhpunitNoticeTest::testTwo) +Test Passed (PHPUnit\TestFixture\TestRunnerStopping\PhpunitNoticeTest::testTwo) +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\PhpunitNoticeTest::testTwo) +Test Suite Finished (PHPUnit\TestFixture\TestRunnerStopping\PhpunitNoticeTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/cli/fail-on/fail-on-risky-and-do-not-fail-on-risky.phpt b/tests/end-to-end/cli/fail-on/fail-on-risky-and-do-not-fail-on-risky.phpt new file mode 100644 index 00000000000..a4cf71033e8 --- /dev/null +++ b/tests/end-to-end/cli/fail-on/fail-on-risky-and-do-not-fail-on-risky.phpt @@ -0,0 +1,18 @@ +--TEST-- +todo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +%A +Test Runner Triggered Warning (Options --do-not-fail-on-risky and --fail-on-risky cannot be used together) +%A diff --git a/tests/end-to-end/cli/fail-on/fail-on-risky.phpt b/tests/end-to-end/cli/fail-on/fail-on-risky.phpt new file mode 100644 index 00000000000..d7323df117a --- /dev/null +++ b/tests/end-to-end/cli/fail-on/fail-on-risky.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test Runner exits with shell exit code indicating failure when all tests are successful but at least one test was considered risky +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\TestRunnerStopping\RiskyTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\RiskyTest::testOne) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\RiskyTest::testOne) +Test Passed (PHPUnit\TestFixture\TestRunnerStopping\RiskyTest::testOne) +Test Considered Risky (PHPUnit\TestFixture\TestRunnerStopping\RiskyTest::testOne) +This test did not perform any assertions +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\RiskyTest::testOne) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\RiskyTest::testTwo) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\RiskyTest::testTwo) +Test Passed (PHPUnit\TestFixture\TestRunnerStopping\RiskyTest::testTwo) +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\RiskyTest::testTwo) +Test Suite Finished (PHPUnit\TestFixture\TestRunnerStopping\RiskyTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/cli/fail-on/fail-on-skipped-and-do-not-fail-on-skipped.phpt b/tests/end-to-end/cli/fail-on/fail-on-skipped-and-do-not-fail-on-skipped.phpt new file mode 100644 index 00000000000..fded7ee858c --- /dev/null +++ b/tests/end-to-end/cli/fail-on/fail-on-skipped-and-do-not-fail-on-skipped.phpt @@ -0,0 +1,18 @@ +--TEST-- +todo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +%A +Test Runner Triggered Warning (Options --do-not-fail-on-skipped and --fail-on-skipped cannot be used together) +%A diff --git a/tests/end-to-end/cli/fail-on/fail-on-skipped-output.phpt b/tests/end-to-end/cli/fail-on/fail-on-skipped-output.phpt new file mode 100644 index 00000000000..4e5155df1f8 --- /dev/null +++ b/tests/end-to-end/cli/fail-on/fail-on-skipped-output.phpt @@ -0,0 +1,28 @@ +--TEST-- +Details for skipped tests are displayed when --fail-on-skipped is used +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +S. 2 / 2 (100%) + +Time: %s, Memory: %s + +There was 1 skipped test: + +1) PHPUnit\TestFixture\TestRunnerStopping\SkippedTest::testOne +message + +OK, but some tests were skipped! +Tests: 2, Assertions: 1, Skipped: 1. diff --git a/tests/end-to-end/cli/fail-on/fail-on-skipped.phpt b/tests/end-to-end/cli/fail-on/fail-on-skipped.phpt new file mode 100644 index 00000000000..8978243ea19 --- /dev/null +++ b/tests/end-to-end/cli/fail-on/fail-on-skipped.phpt @@ -0,0 +1,35 @@ +--TEST-- +Test Runner exits with shell exit code indicating failure when all tests are successful but at least one test was skipped +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\TestRunnerStopping\SkippedTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\SkippedTest::testOne) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\SkippedTest::testOne) +Test Skipped (PHPUnit\TestFixture\TestRunnerStopping\SkippedTest::testOne) +message +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\SkippedTest::testOne) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\SkippedTest::testTwo) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\SkippedTest::testTwo) +Test Passed (PHPUnit\TestFixture\TestRunnerStopping\SkippedTest::testTwo) +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\SkippedTest::testTwo) +Test Suite Finished (PHPUnit\TestFixture\TestRunnerStopping\SkippedTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/cli/fail-on/fail-on-warning-and-do-not-fail-on-warning.phpt b/tests/end-to-end/cli/fail-on/fail-on-warning-and-do-not-fail-on-warning.phpt new file mode 100644 index 00000000000..8dce24ea17c --- /dev/null +++ b/tests/end-to-end/cli/fail-on/fail-on-warning-and-do-not-fail-on-warning.phpt @@ -0,0 +1,18 @@ +--TEST-- +todo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +%A +Test Runner Triggered Warning (Options --do-not-fail-on-warning and --fail-on-warning cannot be used together) +%A diff --git a/tests/end-to-end/cli/fail-on/fail-on-warning-output.phpt b/tests/end-to-end/cli/fail-on/fail-on-warning-output.phpt new file mode 100644 index 00000000000..8513c5efc6d --- /dev/null +++ b/tests/end-to-end/cli/fail-on/fail-on-warning-output.phpt @@ -0,0 +1,28 @@ +--TEST-- +Details for warnings are displayed when --fail-on-warning is used +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +W. 2 / 2 (100%) + +Time: %s, Memory: %s + +1 test triggered 1 warning: + +1) %sWarningTest.php:%d +message + +OK, but there were issues! +Tests: 2, Assertions: 2, Warnings: 1. diff --git a/tests/end-to-end/cli/fail-on/fail-on-warning.phpt b/tests/end-to-end/cli/fail-on/fail-on-warning.phpt new file mode 100644 index 00000000000..0d3c6783723 --- /dev/null +++ b/tests/end-to-end/cli/fail-on/fail-on-warning.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test Runner exits with shell exit code indicating failure when all tests are successful but at least one warning was triggered +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\TestRunnerStopping\WarningTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\WarningTest::testOne) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\WarningTest::testOne) +Test Triggered Warning (PHPUnit\TestFixture\TestRunnerStopping\WarningTest::testOne) in %s:%d +message +Test Passed (PHPUnit\TestFixture\TestRunnerStopping\WarningTest::testOne) +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\WarningTest::testOne) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\WarningTest::testTwo) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\WarningTest::testTwo) +Test Passed (PHPUnit\TestFixture\TestRunnerStopping\WarningTest::testTwo) +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\WarningTest::testTwo) +Test Suite Finished (PHPUnit\TestFixture\TestRunnerStopping\WarningTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/loggers/failure-reverse-list.phpt b/tests/end-to-end/cli/failure-reverse-list.phpt similarity index 86% rename from tests/end-to-end/loggers/failure-reverse-list.phpt rename to tests/end-to-end/cli/failure-reverse-list.phpt index 7dc5a5b6809..15898e27cd9 100644 --- a/tests/end-to-end/loggers/failure-reverse-list.phpt +++ b/tests/end-to-end/cli/failure-reverse-list.phpt @@ -2,18 +2,19 @@ phpunit --reverse-list ../../_files/FailureTest.php --FILE-- run($_SERVER['argv']); --EXPECTF-- PHPUnit %s by Sebastian Bergmann and contributors. +Runtime: %s + FFFFFFFFFFFFF 13 / 13 (100%) Time: %s, Memory: %s @@ -139,4 +140,4 @@ Failed asserting that two arrays are equal. %s:%d FAILURES! -Tests: 13, Assertions: 14, Failures: 13. +Tests: 13, Assertions: 15, Failures: 13. diff --git a/tests/end-to-end/cli/filter-error-handler/filter-for-error-handler-events-disabled.phpt b/tests/end-to-end/cli/filter-error-handler/filter-for-error-handler-events-disabled.phpt new file mode 100644 index 00000000000..baf8b1113d1 --- /dev/null +++ b/tests/end-to-end/cli/filter-error-handler/filter-for-error-handler-events-disabled.phpt @@ -0,0 +1,84 @@ +--TEST-- +phpunit --configuration ../../_files/filter-error-handler/filter-disabled.xml +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s%efilter-disabled.xml + +W 1 / 1 (100%) + +Time: %s, Memory: %s + +1 test triggered 2 warnings: + +1) %s%esrc%eSourceClass.php:23 +warning + +Triggered by: + +* PHPUnit\TestFixture\FilterErrorHandler\SourceClassTest::testSomething + %s%etests%eSourceClassTest.php:16 + +2) %s%evendor%eVendorClass.php:10 +warning + +Triggered by: + +* PHPUnit\TestFixture\FilterErrorHandler\SourceClassTest::testSomething + %s%etests%eSourceClassTest.php:16 + +-- + +1 test triggered 2 notices: + +1) %s%esrc%eSourceClass.php:22 +notice + +Triggered by: + +* PHPUnit\TestFixture\FilterErrorHandler\SourceClassTest::testSomething + %s%etests%eSourceClassTest.php:16 + +2) %s%evendor%eVendorClass.php:9 +notice + +Triggered by: + +* PHPUnit\TestFixture\FilterErrorHandler\SourceClassTest::testSomething + %s%etests%eSourceClassTest.php:16 + +-- + +1 test triggered 2 deprecations: + +1) %s%esrc%eSourceClass.php:21 +deprecation + +Triggered by: + +* PHPUnit\TestFixture\FilterErrorHandler\SourceClassTest::testSomething + %s%etests%eSourceClassTest.php:16 + +2) %s%evendor%eVendorClass.php:8 +deprecation + +Triggered by: + +* PHPUnit\TestFixture\FilterErrorHandler\SourceClassTest::testSomething + %s%etests%eSourceClassTest.php:16 + +OK, but there were issues! +Tests: 1, Assertions: 1, Warnings: 2, Deprecations: 2, Notices: 2. diff --git a/tests/end-to-end/cli/filter-error-handler/filter-for-error-handler-events-enabled.phpt b/tests/end-to-end/cli/filter-error-handler/filter-for-error-handler-events-enabled.phpt new file mode 100644 index 00000000000..06b9a2f53d6 --- /dev/null +++ b/tests/end-to-end/cli/filter-error-handler/filter-for-error-handler-events-enabled.phpt @@ -0,0 +1,60 @@ +--TEST-- +phpunit --configuration ../../_files/filter-error-handler/filter-enabled.xml +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s%efilter-enabled.xml + +W 1 / 1 (100%) + +Time: %s, Memory: %s + +1 test triggered 1 warning: + +1) %s%esrc%eSourceClass.php:23 +warning + +Triggered by: + +* PHPUnit\TestFixture\FilterErrorHandler\SourceClassTest::testSomething + %s%etests%eSourceClassTest.php:16 + +-- + +1 test triggered 1 notice: + +1) %s%esrc%eSourceClass.php:22 +notice + +Triggered by: + +* PHPUnit\TestFixture\FilterErrorHandler\SourceClassTest::testSomething + %s%etests%eSourceClassTest.php:16 + +-- + +1 test triggered 1 deprecation: + +1) %s%esrc%eSourceClass.php:21 +deprecation + +Triggered by: + +* PHPUnit\TestFixture\FilterErrorHandler\SourceClassTest::testSomething + %s%etests%eSourceClassTest.php:16 + +OK, but there were issues! +Tests: 1, Assertions: 1, Warnings: 1, Deprecations: 1, Notices: 1. diff --git a/tests/end-to-end/cli/filter/filter-class-match-argument.phpt b/tests/end-to-end/cli/filter/filter-class-match-argument.phpt new file mode 100644 index 00000000000..0f8f07a1750 --- /dev/null +++ b/tests/end-to-end/cli/filter/filter-class-match-argument.phpt @@ -0,0 +1,40 @@ +--TEST-- +phpunit --filter FooTest tests/FooTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (3 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (3 tests) +Test Runner Execution Started (3 tests) +Test Suite Started (PHPUnit\TestFixture\Groups\FooTest, 3 tests) +Test Preparation Started (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Prepared (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Passed (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Finished (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Preparation Started (PHPUnit\TestFixture\Groups\FooTest::testTwo) +Test Prepared (PHPUnit\TestFixture\Groups\FooTest::testTwo) +Test Passed (PHPUnit\TestFixture\Groups\FooTest::testTwo) +Test Finished (PHPUnit\TestFixture\Groups\FooTest::testTwo) +Test Preparation Started (PHPUnit\TestFixture\Groups\FooTest::testThree) +Test Prepared (PHPUnit\TestFixture\Groups\FooTest::testThree) +Test Passed (PHPUnit\TestFixture\Groups\FooTest::testThree) +Test Finished (PHPUnit\TestFixture\Groups\FooTest::testThree) +Test Suite Finished (PHPUnit\TestFixture\Groups\FooTest, 3 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/filter/filter-class-match-configuration.phpt b/tests/end-to-end/cli/filter/filter-class-match-configuration.phpt new file mode 100644 index 00000000000..17e98448355 --- /dev/null +++ b/tests/end-to-end/cli/filter/filter-class-match-configuration.phpt @@ -0,0 +1,44 @@ +--TEST-- +phpunit --filter FooTest +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (3 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (3 tests) +Test Runner Execution Started (3 tests) +Test Suite Started (%s%etests%eend-to-end%e_files%egroups%ephpunit.xml, 3 tests) +Test Suite Started (default, 3 tests) +Test Suite Started (PHPUnit\TestFixture\Groups\FooTest, 3 tests) +Test Preparation Started (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Prepared (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Passed (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Finished (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Preparation Started (PHPUnit\TestFixture\Groups\FooTest::testTwo) +Test Prepared (PHPUnit\TestFixture\Groups\FooTest::testTwo) +Test Passed (PHPUnit\TestFixture\Groups\FooTest::testTwo) +Test Finished (PHPUnit\TestFixture\Groups\FooTest::testTwo) +Test Preparation Started (PHPUnit\TestFixture\Groups\FooTest::testThree) +Test Prepared (PHPUnit\TestFixture\Groups\FooTest::testThree) +Test Passed (PHPUnit\TestFixture\Groups\FooTest::testThree) +Test Finished (PHPUnit\TestFixture\Groups\FooTest::testThree) +Test Suite Finished (PHPUnit\TestFixture\Groups\FooTest, 3 tests) +Test Suite Finished (default, 3 tests) +Test Suite Finished (%s%etests%eend-to-end%e_files%egroups%ephpunit.xml, 3 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/filter/filter-class-nomatch-argument.phpt b/tests/end-to-end/cli/filter/filter-class-nomatch-argument.phpt new file mode 100644 index 00000000000..be71939651b --- /dev/null +++ b/tests/end-to-end/cli/filter/filter-class-nomatch-argument.phpt @@ -0,0 +1,26 @@ +--TEST-- +phpunit --filter BarTest tests/FooTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (3 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (0 tests) +Test Runner Execution Started (0 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/filter/filter-class-nomatch-configuration.phpt b/tests/end-to-end/cli/filter/filter-class-nomatch-configuration.phpt new file mode 100644 index 00000000000..23a7967a205 --- /dev/null +++ b/tests/end-to-end/cli/filter/filter-class-nomatch-configuration.phpt @@ -0,0 +1,26 @@ +--TEST-- +phpunit --filter BarTest +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (3 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (0 tests) +Test Runner Execution Started (0 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/filter/filter-dataprovider-by-classname-and-range.phpt b/tests/end-to-end/cli/filter/filter-dataprovider-by-classname-and-range.phpt new file mode 100644 index 00000000000..0e1d538a5a5 --- /dev/null +++ b/tests/end-to-end/cli/filter/filter-dataprovider-by-classname-and-range.phpt @@ -0,0 +1,22 @@ +--TEST-- +phpunit --filter DataProviderFilterTest#1-3 ../../_files/DataProviderFilterTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +... 3 / 3 (100%) + +Time: %s, Memory: %s + +OK (3 tests, 3 assertions) diff --git a/tests/end-to-end/cli/filter/filter-dataprovider-by-number.phpt b/tests/end-to-end/cli/filter/filter-dataprovider-by-number.phpt new file mode 100644 index 00000000000..f38daf08f40 --- /dev/null +++ b/tests/end-to-end/cli/filter/filter-dataprovider-by-number.phpt @@ -0,0 +1,22 @@ +--TEST-- +phpunit --filter testTrue#3 ../../_files/DataProviderFilterTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/cli/filter/filter-dataprovider-by-only-range.phpt b/tests/end-to-end/cli/filter/filter-dataprovider-by-only-range.phpt new file mode 100644 index 00000000000..3ae50c2b86d --- /dev/null +++ b/tests/end-to-end/cli/filter/filter-dataprovider-by-only-range.phpt @@ -0,0 +1,22 @@ +--TEST-- +phpunit --filter \#1-3 ../../_files/DataProviderFilterTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +... 3 / 3 (100%) + +Time: %s, Memory: %s + +OK (3 tests, 3 assertions) diff --git a/tests/end-to-end/cli/filter/filter-dataprovider-by-only-regexp.phpt b/tests/end-to-end/cli/filter/filter-dataprovider-by-only-regexp.phpt new file mode 100644 index 00000000000..e086d3aca04 --- /dev/null +++ b/tests/end-to-end/cli/filter/filter-dataprovider-by-only-regexp.phpt @@ -0,0 +1,22 @@ +--TEST-- +phpunit --filter @false.* ../../_files/DataProviderFilterTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +.. 2 / 2 (100%) + +Time: %s, Memory: %s + +OK (2 tests, 2 assertions) diff --git a/tests/end-to-end/cli/filter/filter-dataprovider-by-only-string.phpt b/tests/end-to-end/cli/filter/filter-dataprovider-by-only-string.phpt new file mode 100644 index 00000000000..57e7b5b0491 --- /dev/null +++ b/tests/end-to-end/cli/filter/filter-dataprovider-by-only-string.phpt @@ -0,0 +1,22 @@ +--TEST-- +phpunit --filter @false\ test ../../_files/DataProviderFilterTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/cli/filter/filter-dataprovider-by-phpstorm-regex-number-key.phpt b/tests/end-to-end/cli/filter/filter-dataprovider-by-phpstorm-regex-number-key.phpt new file mode 100644 index 00000000000..0d0a688d861 --- /dev/null +++ b/tests/end-to-end/cli/filter/filter-dataprovider-by-phpstorm-regex-number-key.phpt @@ -0,0 +1,72 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/pull/6364 +--FILE-- + ['pipe', 'w'], + ], + $pipes, +); + +$stdout = stream_get_contents($pipes[1]); +fclose($pipes[1]); +proc_close($process); + +if (preg_match("/##teamcity\\[testStarted name='testTrue with data set #1' locationHint='([^']+)'/", $stdout, $matches) !== 1) { + echo "Failed to find locationHint.\n"; + echo $stdout; + + return 0; +} + +if (preg_match('#php_qn://(?:[A-Z]:)?[^:]*::\\\\(.*)#', $matches[1], $locationHintMatches) !== 1) { + echo "Failed to parse locationHint.\n"; + echo $matches[1]; + + return 0; +} + +// Simulate how PHPStorm runs an individual numbered test case +$_SERVER['argv'][] = '--do-not-cache-result'; +$_SERVER['argv'][] = '--no-configuration'; +$_SERVER['argv'][] = '--filter'; +$_SERVER['argv'][] = '/' . preg_quote($locationHintMatches[1], '/') . '$/'; +$_SERVER['argv'][] = '--test-suffix'; +$_SERVER['argv'][] = 'DataProviderFilterTest.php'; +$_SERVER['argv'][] = __DIR__ . '/../../../_files'; +$_SERVER['argv'][] = '--teamcity'; + +require_once __DIR__ . '/../../../bootstrap.php'; +(new PHPUnit\TextUI\Application)->run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: PHP %s + +##teamcity[testCount count='1' flowId='%s'] +##teamcity[testSuiteStarted name='CLI Arguments' flowId='%d'] +##teamcity[testSuiteStarted name='PHPUnit\TestFixture\DataProviderFilterTest' locationHint='php_qn://%sDataProviderFilterTest.php::\PHPUnit\TestFixture\DataProviderFilterTest' flowId='%d'] +##teamcity[testSuiteStarted name='testTrue' locationHint='php_qn://%sDataProviderFilterTest.php::\PHPUnit\TestFixture\DataProviderFilterTest::testTrue' flowId='%d'] +##teamcity[testStarted name='testTrue with data set #1' locationHint='php_qn://%sDataProviderFilterTest.php::\PHPUnit\TestFixture\DataProviderFilterTest::testTrue with data set #1' flowId='%d'] +##teamcity[testFinished name='testTrue with data set #1' duration='%s' flowId='%d'] +##teamcity[testSuiteFinished name='testTrue' flowId='%d'] +##teamcity[testSuiteFinished name='PHPUnit\TestFixture\DataProviderFilterTest' flowId='%d'] +##teamcity[testSuiteFinished name='CLI Arguments' flowId='%d'] +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/cli/filter/filter-dataprovider-by-phpstorm-regex-string-key.phpt b/tests/end-to-end/cli/filter/filter-dataprovider-by-phpstorm-regex-string-key.phpt new file mode 100644 index 00000000000..c15fce6c5da --- /dev/null +++ b/tests/end-to-end/cli/filter/filter-dataprovider-by-phpstorm-regex-string-key.phpt @@ -0,0 +1,72 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/pull/6364 +--FILE-- + ['pipe', 'w'], + ], + $pipes, +); + +$stdout = stream_get_contents($pipes[1]); +fclose($pipes[1]); +proc_close($process); + +if (preg_match("/##teamcity\\[testStarted name='testFalse with data set \"false test\"' locationHint='([^']+)'/", $stdout, $matches) !== 1) { + echo "Failed to find locationHint.\n"; + echo $stdout; + + return 0; +} + +if (preg_match('#php_qn://(?:[A-Z]:)?[^:]*::\\\\(.*)#', $matches[1], $locationHintMatches) !== 1) { + echo "Failed to parse locationHint.\n"; + echo $matches[1]; + + return 0; +} + +// Simulate how PHPStorm runs an individual named test case +$_SERVER['argv'][] = '--do-not-cache-result'; +$_SERVER['argv'][] = '--no-configuration'; +$_SERVER['argv'][] = '--filter'; +$_SERVER['argv'][] = '/' . preg_quote($locationHintMatches[1], '/') . '$/'; +$_SERVER['argv'][] = '--test-suffix'; +$_SERVER['argv'][] = 'DataProviderFilterTest.php'; +$_SERVER['argv'][] = __DIR__ . '/../../../_files'; +$_SERVER['argv'][] = '--teamcity'; + +require_once __DIR__ . '/../../../bootstrap.php'; +(new PHPUnit\TextUI\Application)->run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: PHP %s + +##teamcity[testCount count='1' flowId='%s'] +##teamcity[testSuiteStarted name='CLI Arguments' flowId='%d'] +##teamcity[testSuiteStarted name='PHPUnit\TestFixture\DataProviderFilterTest' locationHint='php_qn://%sDataProviderFilterTest.php::\PHPUnit\TestFixture\DataProviderFilterTest' flowId='%d'] +##teamcity[testSuiteStarted name='testFalse' locationHint='php_qn://%sDataProviderFilterTest.php::\PHPUnit\TestFixture\DataProviderFilterTest::testFalse' flowId='%d'] +##teamcity[testStarted name='testFalse with data set "false test"' locationHint='php_qn://%sDataProviderFilterTest.php::\PHPUnit\TestFixture\DataProviderFilterTest::testFalse with data set "false test"' flowId='%d'] +##teamcity[testFinished name='testFalse with data set "false test"' duration='%s' flowId='%d'] +##teamcity[testSuiteFinished name='testFalse' flowId='%d'] +##teamcity[testSuiteFinished name='PHPUnit\TestFixture\DataProviderFilterTest' flowId='%d'] +##teamcity[testSuiteFinished name='CLI Arguments' flowId='%d'] +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/cli/filter/filter-dataprovider-by-range.phpt b/tests/end-to-end/cli/filter/filter-dataprovider-by-range.phpt new file mode 100644 index 00000000000..ed181d47662 --- /dev/null +++ b/tests/end-to-end/cli/filter/filter-dataprovider-by-range.phpt @@ -0,0 +1,22 @@ +--TEST-- +phpunit --filter testTrue#1-3 ../../_files/DataProviderFilterTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +... 3 / 3 (100%) + +Time: %s, Memory: %s + +OK (3 tests, 3 assertions) diff --git a/tests/end-to-end/cli/filter/filter-dataprovider-by-regexp.phpt b/tests/end-to-end/cli/filter/filter-dataprovider-by-regexp.phpt new file mode 100644 index 00000000000..d0a734f8880 --- /dev/null +++ b/tests/end-to-end/cli/filter/filter-dataprovider-by-regexp.phpt @@ -0,0 +1,22 @@ +--TEST-- +phpunit --filter testFalse@false.* ../../_files/DataProviderFilterTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +.. 2 / 2 (100%) + +Time: %s, Memory: %s + +OK (2 tests, 2 assertions) diff --git a/tests/end-to-end/cli/filter/filter-dataprovider-by-string.phpt b/tests/end-to-end/cli/filter/filter-dataprovider-by-string.phpt new file mode 100644 index 00000000000..cf3b79e8cf8 --- /dev/null +++ b/tests/end-to-end/cli/filter/filter-dataprovider-by-string.phpt @@ -0,0 +1,22 @@ +--TEST-- +phpunit --filter testFalse@false\ test ../../_files/DataProviderFilterTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/cli/filter/filter-method-case-insensitive.phpt b/tests/end-to-end/cli/filter/filter-method-case-insensitive.phpt new file mode 100644 index 00000000000..054d468d1af --- /dev/null +++ b/tests/end-to-end/cli/filter/filter-method-case-insensitive.phpt @@ -0,0 +1,22 @@ +--TEST-- +phpunit --filter /balanceIsInitiallyZero/i ../../_files/BankAccountTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/cli/filter/filter-method-case-sensitive-no-result.phpt b/tests/end-to-end/cli/filter/filter-method-case-sensitive-no-result.phpt new file mode 100644 index 00000000000..dbaa062d563 --- /dev/null +++ b/tests/end-to-end/cli/filter/filter-method-case-sensitive-no-result.phpt @@ -0,0 +1,18 @@ +--TEST-- +phpunit --filter balanceIsInitiallyZero ../../_files/BankAccountTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +No tests executed! diff --git a/tests/end-to-end/cli/filter/filter-method-match-argument.phpt b/tests/end-to-end/cli/filter/filter-method-match-argument.phpt new file mode 100644 index 00000000000..8fc0f1b4a58 --- /dev/null +++ b/tests/end-to-end/cli/filter/filter-method-match-argument.phpt @@ -0,0 +1,32 @@ +--TEST-- +phpunit --filter testOne tests/FooTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (3 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (1 test) +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Groups\FooTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Prepared (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Passed (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Finished (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\Groups\FooTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/filter/filter-method-match-configuration.phpt b/tests/end-to-end/cli/filter/filter-method-match-configuration.phpt new file mode 100644 index 00000000000..f74ff0d5d68 --- /dev/null +++ b/tests/end-to-end/cli/filter/filter-method-match-configuration.phpt @@ -0,0 +1,36 @@ +--TEST-- +phpunit --filter testOne +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (3 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (1 test) +Test Runner Execution Started (1 test) +Test Suite Started (%s%etests%eend-to-end%e_files%egroups%ephpunit.xml, 1 test) +Test Suite Started (default, 1 test) +Test Suite Started (PHPUnit\TestFixture\Groups\FooTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Prepared (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Passed (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Finished (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\Groups\FooTest, 1 test) +Test Suite Finished (default, 1 test) +Test Suite Finished (%s%etests%eend-to-end%e_files%egroups%ephpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/filter/filter-method-nomatch-argument.phpt b/tests/end-to-end/cli/filter/filter-method-nomatch-argument.phpt new file mode 100644 index 00000000000..cd052a99bd3 --- /dev/null +++ b/tests/end-to-end/cli/filter/filter-method-nomatch-argument.phpt @@ -0,0 +1,26 @@ +--TEST-- +phpunit --filter testFoo tests/FooTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (3 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (0 tests) +Test Runner Execution Started (0 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/filter/filter-method-nomatch-configuration.phpt b/tests/end-to-end/cli/filter/filter-method-nomatch-configuration.phpt new file mode 100644 index 00000000000..f1b8168a91f --- /dev/null +++ b/tests/end-to-end/cli/filter/filter-method-nomatch-configuration.phpt @@ -0,0 +1,26 @@ +--TEST-- +phpunit --filter testFoo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (3 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (0 tests) +Test Runner Execution Started (0 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/generate-configuration.phpt b/tests/end-to-end/cli/generate-configuration.phpt index 341c7f3d2ef..4909f8f360f 100644 --- a/tests/end-to-end/cli/generate-configuration.phpt +++ b/tests/end-to-end/cli/generate-configuration.phpt @@ -4,18 +4,21 @@ phpunit --generate-configuration + --FILE-- run($_SERVER['argv']); --EXPECTF-- PHPUnit %s by Sebastian Bergmann and contributors. Generating phpunit.xml in %s -Bootstrap script (relative to path shown above; default: vendor/autoload.php): Tests directory (relative to path shown above; default: tests): Source directory (relative to path shown above; default: src): -Generated phpunit.xml in %s +Bootstrap script (relative to path shown above; default: vendor/autoload.php): Tests directory (relative to path shown above; default: tests): Source directory (relative to path shown above; default: src): Cache directory (relative to path shown above; default: .phpunit.cache): +Generated phpunit.xml in %s. +Make sure to exclude the .phpunit.cache directory from version control. diff --git a/tests/end-to-end/cli/group/covers-class.phpt b/tests/end-to-end/cli/group/covers-class.phpt new file mode 100644 index 00000000000..a573705ec4a --- /dev/null +++ b/tests/end-to-end/cli/group/covers-class.phpt @@ -0,0 +1,34 @@ +--TEST-- +phpunit --covers PHPUnit\TestFixture\AttributeBasedFiltering\Foo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (5 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (1 test) +Test Runner Execution Started (1 test) +Test Suite Started (CLI Arguments, 1 test) +Test Suite Started (PHPUnit\TestFixture\AttributeBasedFiltering\CoversClassTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\AttributeBasedFiltering\CoversClassTest::testOne) +Test Prepared (PHPUnit\TestFixture\AttributeBasedFiltering\CoversClassTest::testOne) +Test Passed (PHPUnit\TestFixture\AttributeBasedFiltering\CoversClassTest::testOne) +Test Finished (PHPUnit\TestFixture\AttributeBasedFiltering\CoversClassTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\AttributeBasedFiltering\CoversClassTest, 1 test) +Test Suite Finished (CLI Arguments, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/group/covers-function.phpt b/tests/end-to-end/cli/group/covers-function.phpt new file mode 100644 index 00000000000..44b894c98b2 --- /dev/null +++ b/tests/end-to-end/cli/group/covers-function.phpt @@ -0,0 +1,34 @@ +--TEST-- +phpunit --covers PHPUnit\TestFixture\AttributeBasedFiltering\f +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (5 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (1 test) +Test Runner Execution Started (1 test) +Test Suite Started (CLI Arguments, 1 test) +Test Suite Started (PHPUnit\TestFixture\AttributeBasedFiltering\CoversFunctionTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\AttributeBasedFiltering\CoversFunctionTest::testOne) +Test Prepared (PHPUnit\TestFixture\AttributeBasedFiltering\CoversFunctionTest::testOne) +Test Passed (PHPUnit\TestFixture\AttributeBasedFiltering\CoversFunctionTest::testOne) +Test Finished (PHPUnit\TestFixture\AttributeBasedFiltering\CoversFunctionTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\AttributeBasedFiltering\CoversFunctionTest, 1 test) +Test Suite Finished (CLI Arguments, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/group/exclude-group-argument.phpt b/tests/end-to-end/cli/group/exclude-group-argument.phpt new file mode 100644 index 00000000000..81e0027bed9 --- /dev/null +++ b/tests/end-to-end/cli/group/exclude-group-argument.phpt @@ -0,0 +1,34 @@ +--TEST-- +phpunit --exclude-group one,two tests/FooTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (3 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (1 test) +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Groups\FooTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Groups\FooTest::testThree) +Test Prepared (PHPUnit\TestFixture\Groups\FooTest::testThree) +Test Passed (PHPUnit\TestFixture\Groups\FooTest::testThree) +Test Finished (PHPUnit\TestFixture\Groups\FooTest::testThree) +Test Suite Finished (PHPUnit\TestFixture\Groups\FooTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/group/exclude-group-configuration.phpt b/tests/end-to-end/cli/group/exclude-group-configuration.phpt new file mode 100644 index 00000000000..cd3cb8f911b --- /dev/null +++ b/tests/end-to-end/cli/group/exclude-group-configuration.phpt @@ -0,0 +1,38 @@ +--TEST-- +phpunit --exclude-group one,two +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (3 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (1 test) +Test Runner Execution Started (1 test) +Test Suite Started (%s%etests%eend-to-end%e_files%egroups%ephpunit.xml, 1 test) +Test Suite Started (default, 1 test) +Test Suite Started (PHPUnit\TestFixture\Groups\FooTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Groups\FooTest::testThree) +Test Prepared (PHPUnit\TestFixture\Groups\FooTest::testThree) +Test Passed (PHPUnit\TestFixture\Groups\FooTest::testThree) +Test Finished (PHPUnit\TestFixture\Groups\FooTest::testThree) +Test Suite Finished (PHPUnit\TestFixture\Groups\FooTest, 1 test) +Test Suite Finished (default, 1 test) +Test Suite Finished (%s%etests%eend-to-end%e_files%egroups%ephpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/group/group-argument.phpt b/tests/end-to-end/cli/group/group-argument.phpt new file mode 100644 index 00000000000..8977194b861 --- /dev/null +++ b/tests/end-to-end/cli/group/group-argument.phpt @@ -0,0 +1,32 @@ +--TEST-- +phpunit --group one tests/FooTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (3 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (1 test) +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Groups\FooTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Prepared (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Passed (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Finished (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\Groups\FooTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/group/group-configuration.phpt b/tests/end-to-end/cli/group/group-configuration.phpt new file mode 100644 index 00000000000..f2dfaf744fc --- /dev/null +++ b/tests/end-to-end/cli/group/group-configuration.phpt @@ -0,0 +1,36 @@ +--TEST-- +phpunit --group one +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (3 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (1 test) +Test Runner Execution Started (1 test) +Test Suite Started (%s%etests%eend-to-end%e_files%egroups%ephpunit.xml, 1 test) +Test Suite Started (default, 1 test) +Test Suite Started (PHPUnit\TestFixture\Groups\FooTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Prepared (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Passed (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Finished (PHPUnit\TestFixture\Groups\FooTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\Groups\FooTest, 1 test) +Test Suite Finished (default, 1 test) +Test Suite Finished (%s%etests%eend-to-end%e_files%egroups%ephpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/group/requires-php-extension.phpt b/tests/end-to-end/cli/group/requires-php-extension.phpt new file mode 100644 index 00000000000..4d2e66f33c2 --- /dev/null +++ b/tests/end-to-end/cli/group/requires-php-extension.phpt @@ -0,0 +1,34 @@ +--TEST-- +phpunit --requires-php-extension standard +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (5 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (1 test) +Test Runner Execution Started (1 test) +Test Suite Started (CLI Arguments, 1 test) +Test Suite Started (PHPUnit\TestFixture\AttributeBasedFiltering\RequiresPhpExtensionTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\AttributeBasedFiltering\RequiresPhpExtensionTest::testOne) +Test Prepared (PHPUnit\TestFixture\AttributeBasedFiltering\RequiresPhpExtensionTest::testOne) +Test Passed (PHPUnit\TestFixture\AttributeBasedFiltering\RequiresPhpExtensionTest::testOne) +Test Finished (PHPUnit\TestFixture\AttributeBasedFiltering\RequiresPhpExtensionTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\AttributeBasedFiltering\RequiresPhpExtensionTest, 1 test) +Test Suite Finished (CLI Arguments, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/group/uses-class.phpt b/tests/end-to-end/cli/group/uses-class.phpt new file mode 100644 index 00000000000..a2631cdf3b0 --- /dev/null +++ b/tests/end-to-end/cli/group/uses-class.phpt @@ -0,0 +1,34 @@ +--TEST-- +phpunit --uses PHPUnit\TestFixture\AttributeBasedFiltering\Foo +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (5 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (1 test) +Test Runner Execution Started (1 test) +Test Suite Started (CLI Arguments, 1 test) +Test Suite Started (PHPUnit\TestFixture\AttributeBasedFiltering\UsesTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\AttributeBasedFiltering\UsesTest::testOne) +Test Prepared (PHPUnit\TestFixture\AttributeBasedFiltering\UsesTest::testOne) +Test Passed (PHPUnit\TestFixture\AttributeBasedFiltering\UsesTest::testOne) +Test Finished (PHPUnit\TestFixture\AttributeBasedFiltering\UsesTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\AttributeBasedFiltering\UsesTest, 1 test) +Test Suite Finished (CLI Arguments, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/group/uses-function.phpt b/tests/end-to-end/cli/group/uses-function.phpt new file mode 100644 index 00000000000..2b71c3956bb --- /dev/null +++ b/tests/end-to-end/cli/group/uses-function.phpt @@ -0,0 +1,34 @@ +--TEST-- +phpunit --covers PHPUnit\TestFixture\AttributeBasedFiltering\f +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (5 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (1 test) +Test Runner Execution Started (1 test) +Test Suite Started (CLI Arguments, 1 test) +Test Suite Started (PHPUnit\TestFixture\AttributeBasedFiltering\UsesFunctionTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\AttributeBasedFiltering\UsesFunctionTest::testOne) +Test Prepared (PHPUnit\TestFixture\AttributeBasedFiltering\UsesFunctionTest::testOne) +Test Passed (PHPUnit\TestFixture\AttributeBasedFiltering\UsesFunctionTest::testOne) +Test Finished (PHPUnit\TestFixture\AttributeBasedFiltering\UsesFunctionTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\AttributeBasedFiltering\UsesFunctionTest, 1 test) +Test Suite Finished (CLI Arguments, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/help-color.phpt b/tests/end-to-end/cli/help-color.phpt deleted file mode 100644 index 3b9a85318de..00000000000 --- a/tests/end-to-end/cli/help-color.phpt +++ /dev/null @@ -1,13 +0,0 @@ ---TEST-- -phpunit --help ---ARGS-- ---no-configuration --help ---FILE-- -writeToConsole(); ---EXPECTF_EXTERNAL-- -_files/output-cli-help-color.txt diff --git a/tests/end-to-end/cli/help.phpt b/tests/end-to-end/cli/help.phpt deleted file mode 100644 index 931bf557c13..00000000000 --- a/tests/end-to-end/cli/help.phpt +++ /dev/null @@ -1,10 +0,0 @@ ---TEST-- -phpunit ---ARGS-- ---no-configuration ---FILE-- -generate(); +--EXPECTF_EXTERNAL-- +../../_files/output-cli-help-color.txt diff --git a/tests/end-to-end/cli/help/help.phpt b/tests/end-to-end/cli/help/help.phpt new file mode 100644 index 00000000000..d430f0051f8 --- /dev/null +++ b/tests/end-to-end/cli/help/help.phpt @@ -0,0 +1,10 @@ +--TEST-- +phpunit +--ARGS-- +--no-configuration --columns=80 +--FILE-- +run($_SERVER['argv']); +--EXPECTF_EXTERNAL-- +../../_files/output-cli-usage.txt diff --git a/tests/end-to-end/cli/help/help2.phpt b/tests/end-to-end/cli/help/help2.phpt new file mode 100644 index 00000000000..35879615a2f --- /dev/null +++ b/tests/end-to-end/cli/help/help2.phpt @@ -0,0 +1,10 @@ +--TEST-- +phpunit --help +--ARGS-- +--no-configuration --columns=80 --help +--FILE-- +run($_SERVER['argv']); +--EXPECTF_EXTERNAL-- +../../_files/output-cli-usage.txt diff --git a/tests/end-to-end/cli/help2.phpt b/tests/end-to-end/cli/help2.phpt deleted file mode 100644 index b8574caf653..00000000000 --- a/tests/end-to-end/cli/help2.phpt +++ /dev/null @@ -1,10 +0,0 @@ ---TEST-- -phpunit --help ---ARGS-- ---no-configuration --help ---FILE-- -run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (%sphpunit.xml, 2 tests) +Test Suite Started (unit, 1 test) +Test Suite Started (PHPUnit\TestFixture\FooTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\FooTest::testOne) +Test Prepared (PHPUnit\TestFixture\FooTest::testOne) +Test Passed (PHPUnit\TestFixture\FooTest::testOne) +Test Finished (PHPUnit\TestFixture\FooTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\FooTest, 1 test) +Test Suite Finished (unit, 1 test) +Test Suite Started (end-to-end, 1 test) +Test Suite Started (PHPUnit\TestFixture\BarTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\BarTest::testOne) +Test Prepared (PHPUnit\TestFixture\BarTest::testOne) +Test Passed (PHPUnit\TestFixture\BarTest::testOne) +Test Finished (PHPUnit\TestFixture\BarTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\BarTest, 1 test) +Test Suite Finished (end-to-end, 1 test) +Test Suite Finished (%sphpunit.xml, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/include-single-test-suite.phpt b/tests/end-to-end/cli/include-single-test-suite.phpt new file mode 100644 index 00000000000..88cacb70e1c --- /dev/null +++ b/tests/end-to-end/cli/include-single-test-suite.phpt @@ -0,0 +1,35 @@ +--TEST-- +Include single test suite using --testsuite +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (%sphpunit.xml, 1 test) +Test Suite Started (end-to-end, 1 test) +Test Suite Started (PHPUnit\TestFixture\BarTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\BarTest::testOne) +Test Prepared (PHPUnit\TestFixture\BarTest::testOne) +Test Passed (PHPUnit\TestFixture\BarTest::testOne) +Test Finished (PHPUnit\TestFixture\BarTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\BarTest, 1 test) +Test Suite Finished (end-to-end, 1 test) +Test Suite Finished (%sphpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/list-suites/default-test-suite-all.phpt b/tests/end-to-end/cli/list-suites/default-test-suite-all.phpt new file mode 100644 index 00000000000..f71f93b8662 --- /dev/null +++ b/tests/end-to-end/cli/list-suites/default-test-suite-all.phpt @@ -0,0 +1,19 @@ +--TEST-- +phpunit --configuration ../_files/multiple-testsuites/default-test-suite.xml --list-suites --all +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Available test suites: + - end-to-end (1 test) + - unit (1 test) diff --git a/tests/end-to-end/cli/list-suites/default-test-suite.phpt b/tests/end-to-end/cli/list-suites/default-test-suite.phpt new file mode 100644 index 00000000000..5aa8b35de6b --- /dev/null +++ b/tests/end-to-end/cli/list-suites/default-test-suite.phpt @@ -0,0 +1,17 @@ +--TEST-- +phpunit --configuration ../_files/multiple-testsuites/default-test-suite.xml --list-suites +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Available test suite: + - unit (1 test) diff --git a/tests/end-to-end/cli/list-suites/happy-path.phpt b/tests/end-to-end/cli/list-suites/happy-path.phpt new file mode 100644 index 00000000000..82c006799d8 --- /dev/null +++ b/tests/end-to-end/cli/list-suites/happy-path.phpt @@ -0,0 +1,18 @@ +--TEST-- +phpunit --configuration ../_files/multiple-testsuites/phpunit.xml --list-suites +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Available test suites: + - end-to-end (1 test) + - unit (1 test) diff --git a/tests/end-to-end/cli/list-suites/warning-exclude-groups-cli.phpt b/tests/end-to-end/cli/list-suites/warning-exclude-groups-cli.phpt new file mode 100644 index 00000000000..c1f2dc76849 --- /dev/null +++ b/tests/end-to-end/cli/list-suites/warning-exclude-groups-cli.phpt @@ -0,0 +1,22 @@ +--TEST-- +phpunit --configuration ../_files/multiple-testsuites/phpunit.xml --exclude-group default --list-suites +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +The --exclude-group (CLI) and (XML) options cannot be combined with --list-suites, --exclude-group and are ignored + +Available test suites: + - end-to-end (1 test) + - unit (1 test) diff --git a/tests/end-to-end/cli/list-suites/warning-exclude-groups-xml.phpt b/tests/end-to-end/cli/list-suites/warning-exclude-groups-xml.phpt new file mode 100644 index 00000000000..afcd824bab9 --- /dev/null +++ b/tests/end-to-end/cli/list-suites/warning-exclude-groups-xml.phpt @@ -0,0 +1,20 @@ +--TEST-- +phpunit --configuration ../_files/multiple-testsuites/exclude-group.xml --list-suites +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +The --exclude-group (CLI) and (XML) options cannot be combined with --list-suites, --exclude-group and are ignored + +Available test suites: + - end-to-end (1 test) + - unit (1 test) diff --git a/tests/end-to-end/cli/list-suites/warning-filter.phpt b/tests/end-to-end/cli/list-suites/warning-filter.phpt new file mode 100644 index 00000000000..15f993a0c65 --- /dev/null +++ b/tests/end-to-end/cli/list-suites/warning-filter.phpt @@ -0,0 +1,22 @@ +--TEST-- +phpunit --configuration ../_files/multiple-testsuites/phpunit.xml --filter FooTest --list-suites +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +The --filter and --list-suites options cannot be combined, --filter is ignored + +Available test suites: + - end-to-end (1 test) + - unit (1 test) diff --git a/tests/end-to-end/cli/list-suites/warning-include-groups-cli.phpt b/tests/end-to-end/cli/list-suites/warning-include-groups-cli.phpt new file mode 100644 index 00000000000..5d9b556c0e3 --- /dev/null +++ b/tests/end-to-end/cli/list-suites/warning-include-groups-cli.phpt @@ -0,0 +1,22 @@ +--TEST-- +phpunit --configuration ../_files/multiple-testsuites/phpunit.xml --group default --list-suites +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +The --group (CLI) and (XML) options cannot be combined with --list-suites, --group and are ignored + +Available test suites: + - end-to-end (1 test) + - unit (1 test) diff --git a/tests/end-to-end/cli/list-suites/warning-include-groups-xml.phpt b/tests/end-to-end/cli/list-suites/warning-include-groups-xml.phpt new file mode 100644 index 00000000000..e22262cfd85 --- /dev/null +++ b/tests/end-to-end/cli/list-suites/warning-include-groups-xml.phpt @@ -0,0 +1,20 @@ +--TEST-- +phpunit --configuration ../_files/multiple-testsuites/include-group.xml --list-suites +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +The --group (CLI) and (XML) options cannot be combined with --list-suites, --group and are ignored + +Available test suites: + - end-to-end (1 test) + - unit (1 test) diff --git a/tests/end-to-end/cli/list-suites/warning-testsuite.phpt b/tests/end-to-end/cli/list-suites/warning-testsuite.phpt new file mode 100644 index 00000000000..6c39e1ed63c --- /dev/null +++ b/tests/end-to-end/cli/list-suites/warning-testsuite.phpt @@ -0,0 +1,21 @@ +--TEST-- +phpunit --configuration ../_files/multiple-testsuites/phpunit.xml --testsuite unit --list-suites +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +The --testsuite and --list-suites options cannot be combined, --testsuite is ignored + +Available test suite: + - unit (1 test) diff --git a/tests/end-to-end/cli/list-test-files/list-test-files-exclude-group.phpt b/tests/end-to-end/cli/list-test-files/list-test-files-exclude-group.phpt new file mode 100644 index 00000000000..1f76ba5bdf3 --- /dev/null +++ b/tests/end-to-end/cli/list-test-files/list-test-files-exclude-group.phpt @@ -0,0 +1,19 @@ +--TEST-- +phpunit --list-test-files --exclude-group one ../../_files/listing-tests-and-groups +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Available test files: + - %slisting-tests-and-groups%sExampleExtendingAbstractTest.php + - %slisting-tests-and-groups%sExampleTest.php + - %slisting-tests-and-groups%sexample.phpt diff --git a/tests/end-to-end/cli/list-test-files/list-test-files-group.phpt b/tests/end-to-end/cli/list-test-files/list-test-files-group.phpt new file mode 100644 index 00000000000..851eff96586 --- /dev/null +++ b/tests/end-to-end/cli/list-test-files/list-test-files-group.phpt @@ -0,0 +1,17 @@ +--TEST-- +phpunit --list-test-files --group one ../../_files/listing-tests-and-groups +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Available test files: + - %slisting-tests-and-groups%sExampleTest.php diff --git a/tests/end-to-end/cli/list-test-files/list-test-files.phpt b/tests/end-to-end/cli/list-test-files/list-test-files.phpt new file mode 100644 index 00000000000..5b92fad3d44 --- /dev/null +++ b/tests/end-to-end/cli/list-test-files/list-test-files.phpt @@ -0,0 +1,17 @@ +--TEST-- +phpunit --list-test-files ../../_files/listing-tests-and-groups +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Available test files: + - %slisting-tests-and-groups%sExampleExtendingAbstractTest.php + - %slisting-tests-and-groups%sExampleTest.php + - %slisting-tests-and-groups%sexample.phpt diff --git a/tests/end-to-end/cli/listing-tests-and-groups/list-groups-exclude-filter.phpt b/tests/end-to-end/cli/listing-tests-and-groups/list-groups-exclude-filter.phpt new file mode 100644 index 00000000000..89827356532 --- /dev/null +++ b/tests/end-to-end/cli/listing-tests-and-groups/list-groups-exclude-filter.phpt @@ -0,0 +1,20 @@ +--TEST-- +phpunit --exclude-filter testOne --list-groups ../../_files/listing-tests-and-groups +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Available test groups: + - 3 (1 test) + - two (1 test) diff --git a/tests/end-to-end/cli/listing-tests-and-groups/list-groups-exclude-group.phpt b/tests/end-to-end/cli/listing-tests-and-groups/list-groups-exclude-group.phpt new file mode 100644 index 00000000000..83f38e04a7e --- /dev/null +++ b/tests/end-to-end/cli/listing-tests-and-groups/list-groups-exclude-group.phpt @@ -0,0 +1,22 @@ +--TEST-- +phpunit --exclude-group one --list-groups ../../_files/listing-tests-and-groups +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Available test groups: + - 3 (1 test) + - abstract-one (1 test) + - default (1 test) + - two (1 test) diff --git a/tests/end-to-end/cli/listing-tests-and-groups/list-groups-include-filter.phpt b/tests/end-to-end/cli/listing-tests-and-groups/list-groups-include-filter.phpt new file mode 100644 index 00000000000..89827356532 --- /dev/null +++ b/tests/end-to-end/cli/listing-tests-and-groups/list-groups-include-filter.phpt @@ -0,0 +1,20 @@ +--TEST-- +phpunit --exclude-filter testOne --list-groups ../../_files/listing-tests-and-groups +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Available test groups: + - 3 (1 test) + - two (1 test) diff --git a/tests/end-to-end/cli/listing-tests-and-groups/list-groups-include-group.phpt b/tests/end-to-end/cli/listing-tests-and-groups/list-groups-include-group.phpt new file mode 100644 index 00000000000..483cf202b35 --- /dev/null +++ b/tests/end-to-end/cli/listing-tests-and-groups/list-groups-include-group.phpt @@ -0,0 +1,19 @@ +--TEST-- +phpunit --group one --list-groups ../../_files/listing-tests-and-groups +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Available test group: + - one (1 test) diff --git a/tests/end-to-end/cli/listing-tests-and-groups/list-groups.phpt b/tests/end-to-end/cli/listing-tests-and-groups/list-groups.phpt new file mode 100644 index 00000000000..707eaa102c5 --- /dev/null +++ b/tests/end-to-end/cli/listing-tests-and-groups/list-groups.phpt @@ -0,0 +1,21 @@ +--TEST-- +phpunit --list-groups ../../_files/listing-tests-and-groups +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Available test groups: + - 3 (1 test) + - abstract-one (1 test) + - default (1 test) + - one (1 test) + - two (1 test) diff --git a/tests/end-to-end/cli/listing-tests-and-groups/list-tests-exclude-filter.phpt b/tests/end-to-end/cli/listing-tests-and-groups/list-tests-exclude-filter.phpt new file mode 100644 index 00000000000..80fcfb60c8e --- /dev/null +++ b/tests/end-to-end/cli/listing-tests-and-groups/list-tests-exclude-filter.phpt @@ -0,0 +1,20 @@ +--TEST-- +phpunit --exclude-filter testOne --list-tests ../../_files/listing-tests-and-groups +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Available tests: + - PHPUnit\TestFixture\ListingTestsAndGroups\ExampleTest::testTwo + - PHPUnit\TestFixture\ListingTestsAndGroups\ExampleTest::testThree diff --git a/tests/end-to-end/cli/listing-tests-and-groups/list-tests-exclude-group.phpt b/tests/end-to-end/cli/listing-tests-and-groups/list-tests-exclude-group.phpt new file mode 100644 index 00000000000..4e4ca2d7464 --- /dev/null +++ b/tests/end-to-end/cli/listing-tests-and-groups/list-tests-exclude-group.phpt @@ -0,0 +1,22 @@ +--TEST-- +phpunit --exclude-group one --list-tests ../../_files/listing-tests-and-groups +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Available tests: + - PHPUnit\TestFixture\ListingTestsAndGroups\ExampleExtendingAbstractTest::testOne + - PHPUnit\TestFixture\ListingTestsAndGroups\ExampleTest::testTwo + - PHPUnit\TestFixture\ListingTestsAndGroups\ExampleTest::testThree + - %sexample.phpt diff --git a/tests/end-to-end/cli/listing-tests-and-groups/list-tests-include-filter.phpt b/tests/end-to-end/cli/listing-tests-and-groups/list-tests-include-filter.phpt new file mode 100644 index 00000000000..1ee4053afc6 --- /dev/null +++ b/tests/end-to-end/cli/listing-tests-and-groups/list-tests-include-filter.phpt @@ -0,0 +1,20 @@ +--TEST-- +phpunit --filter testOne --list-tests ../../_files/listing-tests-and-groups +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Available tests: + - PHPUnit\TestFixture\ListingTestsAndGroups\ExampleExtendingAbstractTest::testOne + - PHPUnit\TestFixture\ListingTestsAndGroups\ExampleTest::testOne diff --git a/tests/end-to-end/cli/listing-tests-and-groups/list-tests-include-group.phpt b/tests/end-to-end/cli/listing-tests-and-groups/list-tests-include-group.phpt new file mode 100644 index 00000000000..6e32349f377 --- /dev/null +++ b/tests/end-to-end/cli/listing-tests-and-groups/list-tests-include-group.phpt @@ -0,0 +1,19 @@ +--TEST-- +phpunit --group one --list-tests ../../_files/listing-tests-and-groups +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Available test: + - PHPUnit\TestFixture\ListingTestsAndGroups\ExampleTest::testOne diff --git a/tests/end-to-end/cli/listing-tests-and-groups/list-tests-xml-exclude-filter.phpt b/tests/end-to-end/cli/listing-tests-and-groups/list-tests-xml-exclude-filter.phpt new file mode 100644 index 00000000000..00546a2c6ff --- /dev/null +++ b/tests/end-to-end/cli/listing-tests-and-groups/list-tests-xml-exclude-filter.phpt @@ -0,0 +1,35 @@ +--TEST-- +phpunit --exclude-filter testOne --list-tests-xml php://stdout ../../_files/listing-tests-and-groups +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + + + + + + + + + + + + + + + + + +%A diff --git a/tests/end-to-end/cli/listing-tests-and-groups/list-tests-xml-exclude-group.phpt b/tests/end-to-end/cli/listing-tests-and-groups/list-tests-xml-exclude-group.phpt new file mode 100644 index 00000000000..2140b090194 --- /dev/null +++ b/tests/end-to-end/cli/listing-tests-and-groups/list-tests-xml-exclude-group.phpt @@ -0,0 +1,42 @@ +--TEST-- +phpunit --exclude-group one --list-tests-xml php://stdout ../../_files/listing-tests-and-groups +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + + + + + + + + + + + + + + + + + + + + + + + + +%A diff --git a/tests/end-to-end/cli/listing-tests-and-groups/list-tests-xml-include-filter.phpt b/tests/end-to-end/cli/listing-tests-and-groups/list-tests-xml-include-filter.phpt new file mode 100644 index 00000000000..74334ffa1b9 --- /dev/null +++ b/tests/end-to-end/cli/listing-tests-and-groups/list-tests-xml-include-filter.phpt @@ -0,0 +1,37 @@ +--TEST-- +phpunit --filter testOne --list-tests-xml php://stdout ../../_files/listing-tests-and-groups +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + + + + + + + + + + + + + + + + + + + +%A diff --git a/tests/end-to-end/cli/listing-tests-and-groups/list-tests-xml-include-group.phpt b/tests/end-to-end/cli/listing-tests-and-groups/list-tests-xml-include-group.phpt new file mode 100644 index 00000000000..57ca8004d31 --- /dev/null +++ b/tests/end-to-end/cli/listing-tests-and-groups/list-tests-xml-include-group.phpt @@ -0,0 +1,31 @@ +--TEST-- +phpunit --group one --list-tests-xml php://stdout ../../_files/listing-tests-and-groups +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + + + + + + + + + + + + + +%A diff --git a/tests/end-to-end/cli/listing-tests-and-groups/list-tests-xml.phpt b/tests/end-to-end/cli/listing-tests-and-groups/list-tests-xml.phpt new file mode 100644 index 00000000000..747742612b9 --- /dev/null +++ b/tests/end-to-end/cli/listing-tests-and-groups/list-tests-xml.phpt @@ -0,0 +1,45 @@ +--TEST-- +phpunit --no-output --list-tests-xml php://stdout ../../_files/listing-tests-and-groups +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + + + + + + + + + + + + + + + + + + + + + + + + + + + + +%A diff --git a/tests/end-to-end/cli/listing-tests-and-groups/list-tests.phpt b/tests/end-to-end/cli/listing-tests-and-groups/list-tests.phpt new file mode 100644 index 00000000000..486a7f49e7d --- /dev/null +++ b/tests/end-to-end/cli/listing-tests-and-groups/list-tests.phpt @@ -0,0 +1,21 @@ +--TEST-- +phpunit --list-tests ../../_files/listing-tests-and-groups +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Available tests: + - PHPUnit\TestFixture\ListingTestsAndGroups\ExampleExtendingAbstractTest::testOne + - PHPUnit\TestFixture\ListingTestsAndGroups\ExampleTest::testOne + - PHPUnit\TestFixture\ListingTestsAndGroups\ExampleTest::testTwo + - PHPUnit\TestFixture\ListingTestsAndGroups\ExampleTest::testThree + - %sexample.phpt diff --git a/tests/end-to-end/cli/log-events-text-invalid-argument.phpt b/tests/end-to-end/cli/log-events-text-invalid-argument.phpt new file mode 100644 index 00000000000..f3c3c5bd28b --- /dev/null +++ b/tests/end-to-end/cli/log-events-text-invalid-argument.phpt @@ -0,0 +1,19 @@ +--TEST-- +Test fails with invalid path +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +The path "%s" specified for the --log-events-text option could not be resolved diff --git a/tests/end-to-end/cli/log-events-text.phpt b/tests/end-to-end/cli/log-events-text.phpt new file mode 100644 index 00000000000..d76ef45f3bc --- /dev/null +++ b/tests/end-to-end/cli/log-events-text.phpt @@ -0,0 +1,65 @@ +--TEST-- +phpunit --no-output --log-events-text logfile.txt +--FILE-- +run($_SERVER['argv']); + +print file_get_contents($traceFile); + +unlink($traceFile); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (7 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (7 tests) +Test Suite Started (CLI Arguments, 7 tests) +Test Suite Started (PHPUnit\TestFixture\LogEventsText\Test, 7 tests) +Test Preparation Started (PHPUnit\TestFixture\LogEventsText\Test::testExportNull) +Test Prepared (PHPUnit\TestFixture\LogEventsText\Test::testExportNull) +Test Passed (PHPUnit\TestFixture\LogEventsText\Test::testExportNull) +Test Finished (PHPUnit\TestFixture\LogEventsText\Test::testExportNull) +Test Preparation Started (PHPUnit\TestFixture\LogEventsText\Test::testExportBool) +Test Prepared (PHPUnit\TestFixture\LogEventsText\Test::testExportBool) +Test Passed (PHPUnit\TestFixture\LogEventsText\Test::testExportBool) +Test Finished (PHPUnit\TestFixture\LogEventsText\Test::testExportBool) +Test Preparation Started (PHPUnit\TestFixture\LogEventsText\Test::testExportInt) +Test Prepared (PHPUnit\TestFixture\LogEventsText\Test::testExportInt) +Test Passed (PHPUnit\TestFixture\LogEventsText\Test::testExportInt) +Test Finished (PHPUnit\TestFixture\LogEventsText\Test::testExportInt) +Test Preparation Started (PHPUnit\TestFixture\LogEventsText\Test::testExportStr) +Test Prepared (PHPUnit\TestFixture\LogEventsText\Test::testExportStr) +Test Passed (PHPUnit\TestFixture\LogEventsText\Test::testExportStr) +Test Finished (PHPUnit\TestFixture\LogEventsText\Test::testExportStr) +Test Preparation Started (PHPUnit\TestFixture\LogEventsText\Test::testExportArray) +Test Prepared (PHPUnit\TestFixture\LogEventsText\Test::testExportArray) +Test Passed (PHPUnit\TestFixture\LogEventsText\Test::testExportArray) +Test Finished (PHPUnit\TestFixture\LogEventsText\Test::testExportArray) +Test Preparation Started (PHPUnit\TestFixture\LogEventsText\Test::testExportObject) +Test Prepared (PHPUnit\TestFixture\LogEventsText\Test::testExportObject) +Test Failed (PHPUnit\TestFixture\LogEventsText\Test::testExportObject) +Failed asserting that two variables reference the same object. +Test Finished (PHPUnit\TestFixture\LogEventsText\Test::testExportObject) +Test Preparation Started (PHPUnit\TestFixture\LogEventsText\Test::testExportResource) +Test Prepared (PHPUnit\TestFixture\LogEventsText\Test::testExportResource) +Test Failed (PHPUnit\TestFixture\LogEventsText\Test::testExportResource) +Failed asserting that two variables reference the same resource. +Test Finished (PHPUnit\TestFixture\LogEventsText\Test::testExportResource) +Test Suite Finished (PHPUnit\TestFixture\LogEventsText\Test, 7 tests) +Test Suite Finished (CLI Arguments, 7 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/cli/log-events-verbose-text-invalid-argument.phpt b/tests/end-to-end/cli/log-events-verbose-text-invalid-argument.phpt new file mode 100644 index 00000000000..1e22d7c6a09 --- /dev/null +++ b/tests/end-to-end/cli/log-events-verbose-text-invalid-argument.phpt @@ -0,0 +1,19 @@ +--TEST-- +Test fails with invalid path +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +The path "%s" specified for the --log-events-verbose-text option could not be resolved diff --git a/tests/end-to-end/cli/log-events-verbose-text.phpt b/tests/end-to-end/cli/log-events-verbose-text.phpt new file mode 100644 index 00000000000..f9653cbd6fd --- /dev/null +++ b/tests/end-to-end/cli/log-events-verbose-text.phpt @@ -0,0 +1,65 @@ +--TEST-- +phpunit --no-output --log-events-verbose-text logfile.txt +--FILE-- +run($_SERVER['argv']); + +print file_get_contents($traceFile); + +unlink($traceFile); +--EXPECTF-- +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] PHPUnit Started (%s) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Runner Configured +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Event Facade Sealed +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Suite Loaded (7 tests) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Runner Started +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Suite Sorted +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Runner Execution Started (7 tests) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Suite Started (CLI Arguments, 7 tests) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Suite Started (PHPUnit\TestFixture\LogEventsText\Test, 7 tests) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Preparation Started (PHPUnit\TestFixture\LogEventsText\Test::testExportNull) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Prepared (PHPUnit\TestFixture\LogEventsText\Test::testExportNull) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Passed (PHPUnit\TestFixture\LogEventsText\Test::testExportNull) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Finished (PHPUnit\TestFixture\LogEventsText\Test::testExportNull) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Preparation Started (PHPUnit\TestFixture\LogEventsText\Test::testExportBool) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Prepared (PHPUnit\TestFixture\LogEventsText\Test::testExportBool) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Passed (PHPUnit\TestFixture\LogEventsText\Test::testExportBool) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Finished (PHPUnit\TestFixture\LogEventsText\Test::testExportBool) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Preparation Started (PHPUnit\TestFixture\LogEventsText\Test::testExportInt) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Prepared (PHPUnit\TestFixture\LogEventsText\Test::testExportInt) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Passed (PHPUnit\TestFixture\LogEventsText\Test::testExportInt) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Finished (PHPUnit\TestFixture\LogEventsText\Test::testExportInt) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Preparation Started (PHPUnit\TestFixture\LogEventsText\Test::testExportStr) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Prepared (PHPUnit\TestFixture\LogEventsText\Test::testExportStr) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Passed (PHPUnit\TestFixture\LogEventsText\Test::testExportStr) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Finished (PHPUnit\TestFixture\LogEventsText\Test::testExportStr) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Preparation Started (PHPUnit\TestFixture\LogEventsText\Test::testExportArray) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Prepared (PHPUnit\TestFixture\LogEventsText\Test::testExportArray) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Passed (PHPUnit\TestFixture\LogEventsText\Test::testExportArray) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Finished (PHPUnit\TestFixture\LogEventsText\Test::testExportArray) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Preparation Started (PHPUnit\TestFixture\LogEventsText\Test::testExportObject) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Prepared (PHPUnit\TestFixture\LogEventsText\Test::testExportObject) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Failed (PHPUnit\TestFixture\LogEventsText\Test::testExportObject) + %sFailed asserting that two variables reference the same object. +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Finished (PHPUnit\TestFixture\LogEventsText\Test::testExportObject) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Preparation Started (PHPUnit\TestFixture\LogEventsText\Test::testExportResource) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Prepared (PHPUnit\TestFixture\LogEventsText\Test::testExportResource) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Failed (PHPUnit\TestFixture\LogEventsText\Test::testExportResource) + %sFailed asserting that two variables reference the same resource. +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Finished (PHPUnit\TestFixture\LogEventsText\Test::testExportResource) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Suite Finished (PHPUnit\TestFixture\LogEventsText\Test, 7 tests) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Suite Finished (CLI Arguments, 7 tests) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Runner Execution Finished +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Runner Finished +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/cli/mycommand.phpt b/tests/end-to-end/cli/mycommand.phpt deleted file mode 100644 index 15941f970ba..00000000000 --- a/tests/end-to-end/cli/mycommand.phpt +++ /dev/null @@ -1,25 +0,0 @@ ---TEST-- -phpunit ../../_files/BankAccountTest.php ---FILE-- -run($_SERVER['argv']); + +print file_get_contents($logfile); + +unlink($logfile); +--EXPECTF-- + + + + + + diff --git a/tests/end-to-end/cli/no-output.phpt b/tests/end-to-end/cli/no-output.phpt new file mode 100644 index 00000000000..300c564b3ed --- /dev/null +++ b/tests/end-to-end/cli/no-output.phpt @@ -0,0 +1,13 @@ +--TEST-- +phpunit --no-output ../../_files/BankAccountTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECT-- diff --git a/tests/end-to-end/cli/options-after-arguments.phpt b/tests/end-to-end/cli/options-after-arguments.phpt index d0bbfc1e6ef..d3024e045e2 100644 --- a/tests/end-to-end/cli/options-after-arguments.phpt +++ b/tests/end-to-end/cli/options-after-arguments.phpt @@ -2,18 +2,19 @@ phpunit ../../_files/BankAccountTest.php --colors --FILE-- run($_SERVER['argv']); --EXPECTF-- PHPUnit %s by Sebastian Bergmann and contributors. +Runtime: %s + ... 3 / 3 (100%) Time: %s, Memory: %s diff --git a/tests/end-to-end/cli/overlapping-testsuite-configuration.phpt b/tests/end-to-end/cli/overlapping-testsuite-configuration.phpt new file mode 100644 index 00000000000..91bc72a1aba --- /dev/null +++ b/tests/end-to-end/cli/overlapping-testsuite-configuration.phpt @@ -0,0 +1,27 @@ +--TEST-- +A test file must not be in more than one test suite configured in the XML configuration file +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %soverlapping-testsuite-configuration%sphpunit.xml + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Cannot add file %sExampleTest.php to test suite "two" as it was already added to test suite "one" + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/cli/shutdown-handler-with-message.phpt b/tests/end-to-end/cli/shutdown-handler-with-message.phpt new file mode 100644 index 00000000000..03f69137985 --- /dev/null +++ b/tests/end-to-end/cli/shutdown-handler-with-message.phpt @@ -0,0 +1,29 @@ +--TEST-- +Shutdown Handler: exit(1) +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +messageFatal error: Premature end of PHP process when running PHPUnit\TestFixture\WithExitTest::testWithMessage. +---- diff --git a/tests/end-to-end/cli/shutdown-handler-without-message.phpt b/tests/end-to-end/cli/shutdown-handler-without-message.phpt new file mode 100644 index 00000000000..019a4bb9ece --- /dev/null +++ b/tests/end-to-end/cli/shutdown-handler-without-message.phpt @@ -0,0 +1,29 @@ +--TEST-- +Shutdown Handler: exit('message') +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Fatal error: Premature end of PHP process when running PHPUnit\TestFixture\WithExitTest::testWithoutMessage. +---- diff --git a/tests/end-to-end/cli/stop-on/stop-on-defect-for-error.phpt b/tests/end-to-end/cli/stop-on/stop-on-defect-for-error.phpt new file mode 100644 index 00000000000..1d0b8e9091e --- /dev/null +++ b/tests/end-to-end/cli/stop-on/stop-on-defect-for-error.phpt @@ -0,0 +1,32 @@ +--TEST-- +Stopping test execution after first error works +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\TestRunnerStopping\ErrorTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\ErrorTest::testOne) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\ErrorTest::testOne) +Test Errored (PHPUnit\TestFixture\TestRunnerStopping\ErrorTest::testOne) +message +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\ErrorTest::testOne) +Test Runner Execution Aborted +Test Suite Finished (PHPUnit\TestFixture\TestRunnerStopping\ErrorTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/cli/stop-on/stop-on-defect-for-failure.phpt b/tests/end-to-end/cli/stop-on/stop-on-defect-for-failure.phpt new file mode 100644 index 00000000000..b2f6a9be461 --- /dev/null +++ b/tests/end-to-end/cli/stop-on/stop-on-defect-for-failure.phpt @@ -0,0 +1,32 @@ +--TEST-- +Stopping test execution after first failure works +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\TestRunnerStopping\FailureTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\FailureTest::testOne) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\FailureTest::testOne) +Test Failed (PHPUnit\TestFixture\TestRunnerStopping\FailureTest::testOne) +Failed asserting that false is true. +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\FailureTest::testOne) +Test Runner Execution Aborted +Test Suite Finished (PHPUnit\TestFixture\TestRunnerStopping\FailureTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/cli/stop-on/stop-on-defect-for-risky.phpt b/tests/end-to-end/cli/stop-on/stop-on-defect-for-risky.phpt new file mode 100644 index 00000000000..5b38eb7275b --- /dev/null +++ b/tests/end-to-end/cli/stop-on/stop-on-defect-for-risky.phpt @@ -0,0 +1,33 @@ +--TEST-- +Stopping test execution after first risky test works +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\TestRunnerStopping\RiskyTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\RiskyTest::testOne) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\RiskyTest::testOne) +Test Passed (PHPUnit\TestFixture\TestRunnerStopping\RiskyTest::testOne) +Test Considered Risky (PHPUnit\TestFixture\TestRunnerStopping\RiskyTest::testOne) +This test did not perform any assertions +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\RiskyTest::testOne) +Test Runner Execution Aborted +Test Suite Finished (PHPUnit\TestFixture\TestRunnerStopping\RiskyTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/stop-on/stop-on-defect-for-warning.phpt b/tests/end-to-end/cli/stop-on/stop-on-defect-for-warning.phpt new file mode 100644 index 00000000000..c3cae13763e --- /dev/null +++ b/tests/end-to-end/cli/stop-on/stop-on-defect-for-warning.phpt @@ -0,0 +1,33 @@ +--TEST-- +Stopping test execution after first warning works +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\TestRunnerStopping\WarningTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\WarningTest::testOne) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\WarningTest::testOne) +Test Triggered Warning (PHPUnit\TestFixture\TestRunnerStopping\WarningTest::testOne) in %s:%d +message +Test Passed (PHPUnit\TestFixture\TestRunnerStopping\WarningTest::testOne) +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\WarningTest::testOne) +Test Runner Execution Aborted +Test Suite Finished (PHPUnit\TestFixture\TestRunnerStopping\WarningTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/stop-on/stop-on-deprecation.phpt b/tests/end-to-end/cli/stop-on/stop-on-deprecation.phpt new file mode 100644 index 00000000000..4ac3cfad9f7 --- /dev/null +++ b/tests/end-to-end/cli/stop-on/stop-on-deprecation.phpt @@ -0,0 +1,33 @@ +--TEST-- +Stopping test execution after first deprecation works +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (3 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (3 tests) +Test Suite Started (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest, 3 tests) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testOne) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testOne) +Test Triggered Deprecation (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testOne, unknown if issue was triggered in first-party code or third-party code) in %s:%d +message +Test Passed (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testOne) +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testOne) +Test Runner Execution Aborted +Test Suite Finished (PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest, 3 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/stop-on/stop-on-error.phpt b/tests/end-to-end/cli/stop-on/stop-on-error.phpt new file mode 100644 index 00000000000..d7dd7834be2 --- /dev/null +++ b/tests/end-to-end/cli/stop-on/stop-on-error.phpt @@ -0,0 +1,32 @@ +--TEST-- +Stopping test execution after first error works +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\TestRunnerStopping\ErrorTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\ErrorTest::testOne) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\ErrorTest::testOne) +Test Errored (PHPUnit\TestFixture\TestRunnerStopping\ErrorTest::testOne) +message +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\ErrorTest::testOne) +Test Runner Execution Aborted +Test Suite Finished (PHPUnit\TestFixture\TestRunnerStopping\ErrorTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/cli/stop-on/stop-on-failure.phpt b/tests/end-to-end/cli/stop-on/stop-on-failure.phpt new file mode 100644 index 00000000000..a07f58bd38d --- /dev/null +++ b/tests/end-to-end/cli/stop-on/stop-on-failure.phpt @@ -0,0 +1,32 @@ +--TEST-- +Stopping test execution after first failure works +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\TestRunnerStopping\FailureTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\FailureTest::testOne) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\FailureTest::testOne) +Test Failed (PHPUnit\TestFixture\TestRunnerStopping\FailureTest::testOne) +Failed asserting that false is true. +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\FailureTest::testOne) +Test Runner Execution Aborted +Test Suite Finished (PHPUnit\TestFixture\TestRunnerStopping\FailureTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/cli/stop-on/stop-on-incomplete.phpt b/tests/end-to-end/cli/stop-on/stop-on-incomplete.phpt new file mode 100644 index 00000000000..4b23c90d60a --- /dev/null +++ b/tests/end-to-end/cli/stop-on/stop-on-incomplete.phpt @@ -0,0 +1,32 @@ +--TEST-- +Stopping test execution after first incomplete test works +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\TestRunnerStopping\IncompleteTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\IncompleteTest::testOne) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\IncompleteTest::testOne) +Test Marked Incomplete (PHPUnit\TestFixture\TestRunnerStopping\IncompleteTest::testOne) +message +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\IncompleteTest::testOne) +Test Runner Execution Aborted +Test Suite Finished (PHPUnit\TestFixture\TestRunnerStopping\IncompleteTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/stop-on/stop-on-notice.phpt b/tests/end-to-end/cli/stop-on/stop-on-notice.phpt new file mode 100644 index 00000000000..01d62b731b3 --- /dev/null +++ b/tests/end-to-end/cli/stop-on/stop-on-notice.phpt @@ -0,0 +1,33 @@ +--TEST-- +Stopping test execution after first notice works +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\TestRunnerStopping\NoticeTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\NoticeTest::testOne) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\NoticeTest::testOne) +Test Triggered Notice (PHPUnit\TestFixture\TestRunnerStopping\NoticeTest::testOne) in %s:%d +message +Test Passed (PHPUnit\TestFixture\TestRunnerStopping\NoticeTest::testOne) +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\NoticeTest::testOne) +Test Runner Execution Aborted +Test Suite Finished (PHPUnit\TestFixture\TestRunnerStopping\NoticeTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/stop-on/stop-on-risky.phpt b/tests/end-to-end/cli/stop-on/stop-on-risky.phpt new file mode 100644 index 00000000000..7c81b8df921 --- /dev/null +++ b/tests/end-to-end/cli/stop-on/stop-on-risky.phpt @@ -0,0 +1,33 @@ +--TEST-- +Stopping test execution after first risky test works +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\TestRunnerStopping\RiskyTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\RiskyTest::testOne) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\RiskyTest::testOne) +Test Passed (PHPUnit\TestFixture\TestRunnerStopping\RiskyTest::testOne) +Test Considered Risky (PHPUnit\TestFixture\TestRunnerStopping\RiskyTest::testOne) +This test did not perform any assertions +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\RiskyTest::testOne) +Test Runner Execution Aborted +Test Suite Finished (PHPUnit\TestFixture\TestRunnerStopping\RiskyTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/stop-on/stop-on-skipped.phpt b/tests/end-to-end/cli/stop-on/stop-on-skipped.phpt new file mode 100644 index 00000000000..37d60f01063 --- /dev/null +++ b/tests/end-to-end/cli/stop-on/stop-on-skipped.phpt @@ -0,0 +1,32 @@ +--TEST-- +Stopping test execution after first skipped test works +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\TestRunnerStopping\SkippedTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\SkippedTest::testOne) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\SkippedTest::testOne) +Test Skipped (PHPUnit\TestFixture\TestRunnerStopping\SkippedTest::testOne) +message +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\SkippedTest::testOne) +Test Runner Execution Aborted +Test Suite Finished (PHPUnit\TestFixture\TestRunnerStopping\SkippedTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/stop-on/stop-on-specific-deprecation.phpt b/tests/end-to-end/cli/stop-on/stop-on-specific-deprecation.phpt new file mode 100644 index 00000000000..79843c28b8d --- /dev/null +++ b/tests/end-to-end/cli/stop-on/stop-on-specific-deprecation.phpt @@ -0,0 +1,39 @@ +--TEST-- +Stopping test execution after first deprecation where its message contains a given string +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (3 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (3 tests) +Test Suite Started (PHPUnit\TestFixture\TestRunnerStopping\SpecificDeprecationTest, 3 tests) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\SpecificDeprecationTest::testOne) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\SpecificDeprecationTest::testOne) +Test Triggered Deprecation (PHPUnit\TestFixture\TestRunnerStopping\SpecificDeprecationTest::testOne, unknown if issue was triggered in first-party code or third-party code) in %s:%d +...foo... +Test Passed (PHPUnit\TestFixture\TestRunnerStopping\SpecificDeprecationTest::testOne) +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\SpecificDeprecationTest::testOne) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\SpecificDeprecationTest::testTwo) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\SpecificDeprecationTest::testTwo) +Test Triggered Deprecation (PHPUnit\TestFixture\TestRunnerStopping\SpecificDeprecationTest::testTwo, unknown if issue was triggered in first-party code or third-party code) in %s:%d +...bar... +Test Passed (PHPUnit\TestFixture\TestRunnerStopping\SpecificDeprecationTest::testTwo) +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\SpecificDeprecationTest::testTwo) +Test Runner Execution Aborted +Test Suite Finished (PHPUnit\TestFixture\TestRunnerStopping\SpecificDeprecationTest, 3 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/stop-on/stop-on-warning.phpt b/tests/end-to-end/cli/stop-on/stop-on-warning.phpt new file mode 100644 index 00000000000..0f0e72bcb74 --- /dev/null +++ b/tests/end-to-end/cli/stop-on/stop-on-warning.phpt @@ -0,0 +1,33 @@ +--TEST-- +Stopping test execution after first warning works +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\TestRunnerStopping\WarningTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\TestRunnerStopping\WarningTest::testOne) +Test Prepared (PHPUnit\TestFixture\TestRunnerStopping\WarningTest::testOne) +Test Triggered Warning (PHPUnit\TestFixture\TestRunnerStopping\WarningTest::testOne) in %s:%d +message +Test Passed (PHPUnit\TestFixture\TestRunnerStopping\WarningTest::testOne) +Test Finished (PHPUnit\TestFixture\TestRunnerStopping\WarningTest::testOne) +Test Runner Execution Aborted +Test Suite Finished (PHPUnit\TestFixture\TestRunnerStopping\WarningTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/test-directory-does-not-exist.phpt b/tests/end-to-end/cli/test-directory-does-not-exist.phpt new file mode 100644 index 00000000000..f561519bd02 --- /dev/null +++ b/tests/end-to-end/cli/test-directory-does-not-exist.phpt @@ -0,0 +1,15 @@ +--TEST-- +An error is emitted when a configured test directory does not exist +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Test directory "%stest-directory-does-not-exist%stests" not found diff --git a/tests/end-to-end/cli/test-file-not-found.phpt b/tests/end-to-end/cli/test-file-not-found.phpt index 1483642d222..b0eecacb1f0 100644 --- a/tests/end-to-end/cli/test-file-not-found.phpt +++ b/tests/end-to-end/cli/test-file-not-found.phpt @@ -4,9 +4,9 @@ Test incorrect testFile is reported --no-configuration nonExistingFile.php --FILE-- run($_SERVER['argv']); --EXPECTF-- PHPUnit %s by Sebastian Bergmann and contributors. -Cannot open file "nonExistingFile.php". +Test file "nonExistingFile.php" not found diff --git a/tests/end-to-end/cli/test-suite-bootstrap-default.phpt b/tests/end-to-end/cli/test-suite-bootstrap-default.phpt new file mode 100644 index 00000000000..9b5b84a3c1d --- /dev/null +++ b/tests/end-to-end/cli/test-suite-bootstrap-default.phpt @@ -0,0 +1,44 @@ +--TEST-- +All bootstrap scripts are loaded by default +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s) +Test Runner Configured +Bootstrap Finished (%sbootstrap.php) +Bootstrap Finished (%sbootstrap_one.php) +Bootstrap Finished (%sbootstrap_two.php) +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (%sphpunit.xml, 2 tests) +Test Suite Started (one, 1 test) +Test Suite Started (PHPUnit\TestFixture\BootstrapForTestSuite\OneTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\BootstrapForTestSuite\OneTest::testOne) +Test Prepared (PHPUnit\TestFixture\BootstrapForTestSuite\OneTest::testOne) +Test Passed (PHPUnit\TestFixture\BootstrapForTestSuite\OneTest::testOne) +Test Finished (PHPUnit\TestFixture\BootstrapForTestSuite\OneTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\BootstrapForTestSuite\OneTest, 1 test) +Test Suite Finished (one, 1 test) +Test Suite Started (two, 1 test) +Test Suite Started (PHPUnit\TestFixture\BootstrapForTestSuite\TwoTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\BootstrapForTestSuite\TwoTest::testTwo) +Test Prepared (PHPUnit\TestFixture\BootstrapForTestSuite\TwoTest::testTwo) +Test Passed (PHPUnit\TestFixture\BootstrapForTestSuite\TwoTest::testTwo) +Test Finished (PHPUnit\TestFixture\BootstrapForTestSuite\TwoTest::testTwo) +Test Suite Finished (PHPUnit\TestFixture\BootstrapForTestSuite\TwoTest, 1 test) +Test Suite Finished (two, 1 test) +Test Suite Finished (%sphpunit.xml, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/test-suite-bootstrap-exclude.phpt b/tests/end-to-end/cli/test-suite-bootstrap-exclude.phpt new file mode 100644 index 00000000000..8194ddae87f --- /dev/null +++ b/tests/end-to-end/cli/test-suite-bootstrap-exclude.phpt @@ -0,0 +1,37 @@ +--TEST-- +Bootstrap script specific to test suite is not loaded when the test suite is excluded using --exclude-testsuite +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s) +Test Runner Configured +Bootstrap Finished (%stests/bootstrap/bootstrap.php) +Bootstrap Finished (%stests/bootstrap/bootstrap_one.php) +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (%sphpunit.xml, 1 test) +Test Suite Started (one, 1 test) +Test Suite Started (PHPUnit\TestFixture\BootstrapForTestSuite\OneTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\BootstrapForTestSuite\OneTest::testOne) +Test Prepared (PHPUnit\TestFixture\BootstrapForTestSuite\OneTest::testOne) +Test Passed (PHPUnit\TestFixture\BootstrapForTestSuite\OneTest::testOne) +Test Finished (PHPUnit\TestFixture\BootstrapForTestSuite\OneTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\BootstrapForTestSuite\OneTest, 1 test) +Test Suite Finished (one, 1 test) +Test Suite Finished (%sphpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/test-suite-bootstrap-include.phpt b/tests/end-to-end/cli/test-suite-bootstrap-include.phpt new file mode 100644 index 00000000000..2296fa6ca2d --- /dev/null +++ b/tests/end-to-end/cli/test-suite-bootstrap-include.phpt @@ -0,0 +1,37 @@ +--TEST-- +Bootstrap script specific to test suite is not loaded when the test suite is not selected using --testsuite +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s) +Test Runner Configured +Bootstrap Finished (%stests/bootstrap/bootstrap.php) +Bootstrap Finished (%stests/bootstrap/bootstrap_one.php) +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (%sphpunit.xml, 1 test) +Test Suite Started (one, 1 test) +Test Suite Started (PHPUnit\TestFixture\BootstrapForTestSuite\OneTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\BootstrapForTestSuite\OneTest::testOne) +Test Prepared (PHPUnit\TestFixture\BootstrapForTestSuite\OneTest::testOne) +Test Passed (PHPUnit\TestFixture\BootstrapForTestSuite\OneTest::testOne) +Test Finished (PHPUnit\TestFixture\BootstrapForTestSuite\OneTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\BootstrapForTestSuite\OneTest, 1 test) +Test Suite Finished (one, 1 test) +Test Suite Finished (%sphpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/cli/version.phpt b/tests/end-to-end/cli/version.phpt new file mode 100644 index 00000000000..ef47fbaa274 --- /dev/null +++ b/tests/end-to-end/cli/version.phpt @@ -0,0 +1,14 @@ +--TEST-- +phpunit --version +--FILE-- +run($_SERVER['argv']); +?> +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. diff --git a/tests/end-to-end/concrete-test-class.phpt b/tests/end-to-end/concrete-test-class.phpt deleted file mode 100644 index ced42d6c158..00000000000 --- a/tests/end-to-end/concrete-test-class.phpt +++ /dev/null @@ -1,17 +0,0 @@ ---TEST-- -phpunit ../../_files/ConcreteTest.php ---FILE-- -run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +...... 6 / 6 (100%) + +Time: %s, Memory: %s + +OK (6 tests, 6 assertions) diff --git a/tests/end-to-end/data-provider/dependency-result.phpt b/tests/end-to-end/data-provider/dependency-result.phpt new file mode 100644 index 00000000000..3cde9ce6401 --- /dev/null +++ b/tests/end-to-end/data-provider/dependency-result.phpt @@ -0,0 +1,26 @@ +--TEST-- +phpunit ../../_files/DataProviderDependencyResultTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +...E 4 / 4 (100%) + +Time: %s, Memory: %s + +There was 1 error: + +1) PHPUnit\TestFixture\DataProviderDependencyResultTest::testAdd#2 with data (2, 0) +Error: Cannot use positional argument after named argument during unpacking + +ERRORS! +Tests: 4, Assertions: 5, Errors: 1. diff --git a/tests/end-to-end/data-provider/dependency-void.phpt b/tests/end-to-end/data-provider/dependency-void.phpt new file mode 100644 index 00000000000..946f6b3215f --- /dev/null +++ b/tests/end-to-end/data-provider/dependency-void.phpt @@ -0,0 +1,20 @@ +--TEST-- +phpunit ../../_files/DataProviderDependencyVoidTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +..... 5 / 5 (100%) + +Time: %s, Memory: %s + +OK (5 tests, 5 assertions) diff --git a/tests/end-to-end/data-provider/log-junit-isolation.phpt b/tests/end-to-end/data-provider/log-junit-isolation.phpt new file mode 100644 index 00000000000..3156f6d6cf8 --- /dev/null +++ b/tests/end-to-end/data-provider/log-junit-isolation.phpt @@ -0,0 +1,38 @@ +--TEST-- +phpunit --process-isolation --log-junit php://stdout ../../_files/DataProviderTest.php +--FILE-- +run($_SERVER['argv']); + +print file_get_contents($logfile); + +unlink($logfile); +--EXPECTF-- + + + + + + + + PHPUnit\TestFixture\DataProviderTest::testAdd with data set #2%A +Failed asserting that 2 matches expected 3. +%A +%s:%i + + + + + diff --git a/tests/end-to-end/data-provider/log-junit.phpt b/tests/end-to-end/data-provider/log-junit.phpt new file mode 100644 index 00000000000..f38cfb07ad7 --- /dev/null +++ b/tests/end-to-end/data-provider/log-junit.phpt @@ -0,0 +1,66 @@ +--TEST-- +phpunit --log-junit php://stdout ../../_files/DataProviderTest.php +--FILE-- +run($_SERVER['argv']); + +print file_get_contents($logfile); + +unlink($logfile); +--EXPECTF-- + + + + + + + + + PHPUnit\TestFixture\DataProviderTest::testAdd with data set #2%A +Failed asserting that 2 matches expected 3. +%A +%sDataProviderTest.php:%d + + + + + + + + + + PHPUnit\TestFixture\DataProviderWithStringKeysTest::testAdd with data set "1 + 1 = 3"%A +Failed asserting that 2 matches expected 3. +%A +%sDataProviderWithStringKeysTest.php:%d + + + + + + + failure.phptFailed asserting that two strings are equal.%A +--- Expected ++++ Actual +@@ @@ +-'success' ++'failure' +%A +%s:%d + + + diff --git a/tests/end-to-end/data-provider/process-isolation.phpt b/tests/end-to-end/data-provider/process-isolation.phpt new file mode 100644 index 00000000000..2484e53bc7f --- /dev/null +++ b/tests/end-to-end/data-provider/process-isolation.phpt @@ -0,0 +1,20 @@ +--TEST-- +phpunit ../_files/TestProcessIsolationWithDataProvider.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/data-provider/requires-phpunit.phpt b/tests/end-to-end/data-provider/requires-phpunit.phpt new file mode 100644 index 00000000000..29aa5d7295a --- /dev/null +++ b/tests/end-to-end/data-provider/requires-phpunit.phpt @@ -0,0 +1,34 @@ +--TEST-- +phpunit ../../_files/DataProviderRequiresPhpUnitTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +S..SS 5 / 5 (100%) + +Time: %s, Memory: %s + +There were 3 skipped tests: + +1) PHPUnit\TestFixture\DataProviderRequiresPhpUnitTest::testWithInvalidDataProvider +PHPUnit < 10 is required. + +2) PHPUnit\TestFixture\DataProviderRequiresPhpUnitTest::testWithDataProviderThatThrows +PHPUnit < 10 is required. + +3) PHPUnit\TestFixture\DataProviderRequiresPhpUnitTest::testWithDataProviderExternalThatThrows +PHPUnit < 10 is required. + +OK, but some tests were skipped! +Tests: 5, Assertions: 2, Skipped: 3. + diff --git a/tests/end-to-end/data-provider/too-many-arguments.phpt b/tests/end-to-end/data-provider/too-many-arguments.phpt new file mode 100644 index 00000000000..e51a33cb809 --- /dev/null +++ b/tests/end-to-end/data-provider/too-many-arguments.phpt @@ -0,0 +1,28 @@ +--TEST-- +phpunit ../../_files/DataProviderTooManyArgumentsTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +W...... 7 / 7 (100%) + +Time: %s, Memory: %s + +1 test triggered 1 PHPUnit warning: + +1) PHPUnit\TestFixture\DataProviderTooManyArgumentsTest::testMethodHavingTwoParameters +Data set #2 provided by PHPUnit\TestFixture\DataProviderTooManyArgumentsTest::provider has more arguments (3) than the test method accepts (2) + +%s:%d + +OK, but there were issues! +Tests: 7, Assertions: 7, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/data-provider/warning-when-data-provider-method-has-test-attribute.phpt b/tests/end-to-end/data-provider/warning-when-data-provider-method-has-test-attribute.phpt new file mode 100644 index 00000000000..362d43520a3 --- /dev/null +++ b/tests/end-to-end/data-provider/warning-when-data-provider-method-has-test-attribute.phpt @@ -0,0 +1,41 @@ +--TEST-- +phpunit ../../_files/DataProviderMethodHasTestAttributeTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Data Provider Method Called (PHPUnit\TestFixture\DataProviderMethodHasTestAttributeTest::provider for test method PHPUnit\TestFixture\DataProviderMethodHasTestAttributeTest::testOne) +Test Runner Triggered Warning (Method PHPUnit\TestFixture\DataProviderMethodHasTestAttributeTest::provider() used by test method PHPUnit\TestFixture\DataProviderMethodHasTestAttributeTest::testOne() is also a test method) +Data Provider Method Finished for PHPUnit\TestFixture\DataProviderMethodHasTestAttributeTest::testOne: +- PHPUnit\TestFixture\DataProviderMethodHasTestAttributeTest::provider +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\DataProviderMethodHasTestAttributeTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\DataProviderMethodHasTestAttributeTest::provider) +Test Prepared (PHPUnit\TestFixture\DataProviderMethodHasTestAttributeTest::provider) +Test Passed (PHPUnit\TestFixture\DataProviderMethodHasTestAttributeTest::provider) +Test Considered Risky (PHPUnit\TestFixture\DataProviderMethodHasTestAttributeTest::provider) +This test did not perform any assertions +Test Finished (PHPUnit\TestFixture\DataProviderMethodHasTestAttributeTest::provider) +Test Suite Started (PHPUnit\TestFixture\DataProviderMethodHasTestAttributeTest::testOne, 1 test) +Test Preparation Started (PHPUnit\TestFixture\DataProviderMethodHasTestAttributeTest::testOne#0) +Test Prepared (PHPUnit\TestFixture\DataProviderMethodHasTestAttributeTest::testOne#0) +Test Passed (PHPUnit\TestFixture\DataProviderMethodHasTestAttributeTest::testOne#0) +Test Finished (PHPUnit\TestFixture\DataProviderMethodHasTestAttributeTest::testOne#0) +Test Suite Finished (PHPUnit\TestFixture\DataProviderMethodHasTestAttributeTest::testOne, 1 test) +Test Suite Finished (PHPUnit\TestFixture\DataProviderMethodHasTestAttributeTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/data-provider/warning-when-data-provider-method-name-begins-with-test.phpt b/tests/end-to-end/data-provider/warning-when-data-provider-method-name-begins-with-test.phpt new file mode 100644 index 00000000000..ed1d7878ced --- /dev/null +++ b/tests/end-to-end/data-provider/warning-when-data-provider-method-name-begins-with-test.phpt @@ -0,0 +1,41 @@ +--TEST-- +phpunit ../../_files/DataProviderMethodNameStartsWithTestTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Data Provider Method Called (PHPUnit\TestFixture\DataProviderMethodNameStartsWithTestTest::testProvider for test method PHPUnit\TestFixture\DataProviderMethodNameStartsWithTestTest::testOne) +Test Runner Triggered Warning (Method PHPUnit\TestFixture\DataProviderMethodNameStartsWithTestTest::testProvider() used by test method PHPUnit\TestFixture\DataProviderMethodNameStartsWithTestTest::testOne() is also a test method) +Data Provider Method Finished for PHPUnit\TestFixture\DataProviderMethodNameStartsWithTestTest::testOne: +- PHPUnit\TestFixture\DataProviderMethodNameStartsWithTestTest::testProvider +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\DataProviderMethodNameStartsWithTestTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\DataProviderMethodNameStartsWithTestTest::testProvider) +Test Prepared (PHPUnit\TestFixture\DataProviderMethodNameStartsWithTestTest::testProvider) +Test Passed (PHPUnit\TestFixture\DataProviderMethodNameStartsWithTestTest::testProvider) +Test Considered Risky (PHPUnit\TestFixture\DataProviderMethodNameStartsWithTestTest::testProvider) +This test did not perform any assertions +Test Finished (PHPUnit\TestFixture\DataProviderMethodNameStartsWithTestTest::testProvider) +Test Suite Started (PHPUnit\TestFixture\DataProviderMethodNameStartsWithTestTest::testOne, 1 test) +Test Preparation Started (PHPUnit\TestFixture\DataProviderMethodNameStartsWithTestTest::testOne#0) +Test Prepared (PHPUnit\TestFixture\DataProviderMethodNameStartsWithTestTest::testOne#0) +Test Passed (PHPUnit\TestFixture\DataProviderMethodNameStartsWithTestTest::testOne#0) +Test Finished (PHPUnit\TestFixture\DataProviderMethodNameStartsWithTestTest::testOne#0) +Test Suite Finished (PHPUnit\TestFixture\DataProviderMethodNameStartsWithTestTest::testOne, 1 test) +Test Suite Finished (PHPUnit\TestFixture\DataProviderMethodNameStartsWithTestTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/dataprovider-issue-2833.phpt b/tests/end-to-end/dataprovider-issue-2833.phpt deleted file mode 100644 index 795bb28a689..00000000000 --- a/tests/end-to-end/dataprovider-issue-2833.phpt +++ /dev/null @@ -1,17 +0,0 @@ ---TEST-- -phpunit ../../_files/DataProviderIssue2833/ ---FILE-- - - - - - - - - PHPUnit\TestFixture\DataProviderTest::testAdd with data set #2 (1, 1, 3) -Failed asserting that 2 matches expected 3. - -%s:%i - - - - - - - - -Time: %s, Memory: %s - -There was 1 failure: - -1) PHPUnit\TestFixture\DataProviderTest::testAdd with data set #2 (1, 1, 3) -Failed asserting that 2 matches expected 3. - -%s:%i - -FAILURES! -Tests: 4, Assertions: 4, Failures: 1. diff --git a/tests/end-to-end/dataprovider-log-xml.phpt b/tests/end-to-end/dataprovider-log-xml.phpt deleted file mode 100644 index 448fd1b2192..00000000000 --- a/tests/end-to-end/dataprovider-log-xml.phpt +++ /dev/null @@ -1,44 +0,0 @@ ---TEST-- -phpunit --log-junit php://stdout ../../_files/DataProviderTest.php ---FILE-- - - - - - - - - PHPUnit\TestFixture\DataProviderTest::testAdd with data set #2 (1, 1, 3) -Failed asserting that 2 matches expected 3. - -%s:%i - - - - - - - - -Time: %s, Memory: %s - -There was 1 failure: - -1) PHPUnit\TestFixture\DataProviderTest::testAdd with data set #2 (1, 1, 3) -Failed asserting that 2 matches expected 3. - -%s:%i - -FAILURES! -Tests: 4, Assertions: 4, Failures: 1. diff --git a/tests/end-to-end/default-isolation.phpt b/tests/end-to-end/default-isolation.phpt deleted file mode 100644 index ed5fa057a3f..00000000000 --- a/tests/end-to-end/default-isolation.phpt +++ /dev/null @@ -1,18 +0,0 @@ ---TEST-- -phpunit --process-isolation ../../_files/BankAccountTest.php ---FILE-- - + + + + + + + diff --git a/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-baseline-ignore/phpunit.xml b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-baseline-ignore/phpunit.xml new file mode 100644 index 00000000000..35a68ae4f63 --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-baseline-ignore/phpunit.xml @@ -0,0 +1,21 @@ + + + + + tests + + + + + + src + + + + PHPUnit\TestFixture\BaselineIgnoreDeprecation\trigger_deprecation + + + diff --git a/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-baseline-ignore/src/FirstPartyClass.php b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-baseline-ignore/src/FirstPartyClass.php new file mode 100644 index 00000000000..69ae8106f55 --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-baseline-ignore/src/FirstPartyClass.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\BaselineIgnoreDeprecation; + +final class FirstPartyClass +{ + public function method(): true + { + (new ThirdPartyClass)->method(); + + return true; + } +} diff --git a/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-baseline-ignore/tests/FirstPartyClassTest.php b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-baseline-ignore/tests/FirstPartyClassTest.php new file mode 100644 index 00000000000..0a335791dfd --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-baseline-ignore/tests/FirstPartyClassTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\BaselineIgnoreDeprecation; + +use PHPUnit\Framework\TestCase; + +final class FirstPartyClassTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue((new ThirdPartyClass)->anotherMethod()); + } +} diff --git a/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-baseline-ignore/vendor/ThirdPartyClass.php b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-baseline-ignore/vendor/ThirdPartyClass.php new file mode 100644 index 00000000000..e66043ba5ac --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-baseline-ignore/vendor/ThirdPartyClass.php @@ -0,0 +1,12 @@ + require __DIR__ . '/../src/' . $file, + 'ThirdPartyClass.php' => require __DIR__ . '/' . $file, + default => throw new LogicException + }; +}); + diff --git a/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-baseline-ignore/vendor/trigger_deprecation.php b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-baseline-ignore/vendor/trigger_deprecation.php new file mode 100644 index 00000000000..49d53123aa4 --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-baseline-ignore/vendor/trigger_deprecation.php @@ -0,0 +1,7 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-class/src/FirstPartyClass.php b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-class/src/FirstPartyClass.php new file mode 100644 index 00000000000..e94fad12de3 --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-class/src/FirstPartyClass.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SelfDirectIndirect; + +final class FirstPartyClass +{ + public function method(): true + { + return ThirdPartyClass::A; + } +} diff --git a/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-class/tests/FirstPartyClassTest.php b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-class/tests/FirstPartyClassTest.php new file mode 100644 index 00000000000..b5c44b5867b --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-class/tests/FirstPartyClassTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SelfDirectIndirect; + +use PHPUnit\Framework\TestCase; + +final class FirstPartyClassTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue((new FirstPartyClass)->method()); + } +} diff --git a/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-class/vendor/ThirdPartyClass.php b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-class/vendor/ThirdPartyClass.php new file mode 100644 index 00000000000..a87d69cef99 --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-class/vendor/ThirdPartyClass.php @@ -0,0 +1,9 @@ + require __DIR__ . '/../src/' . $file, + 'ThirdPartyClass.php' => require __DIR__ . '/' . $file, + default => throw new LogicException + }; +}); diff --git a/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-function/phpunit.xml b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-function/phpunit.xml new file mode 100644 index 00000000000..ee9592538c3 --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-function/phpunit.xml @@ -0,0 +1,21 @@ + + + + + tests + + + + + + src + + + + PHPUnit\TestFixture\SelfDirectIndirect\trigger_deprecation + + + diff --git a/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-function/src/FirstPartyClass.php b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-function/src/FirstPartyClass.php new file mode 100644 index 00000000000..aca0b8b8d05 --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-function/src/FirstPartyClass.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SelfDirectIndirect; + +use const E_USER_DEPRECATED; +use function trigger_error; + +final class FirstPartyClass +{ + public function method(): true + { + (new ThirdPartyClass)->method(); + + @trigger_error('deprecation in first-party code', E_USER_DEPRECATED); + + return true; + } +} diff --git a/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-function/tests/FirstPartyClassTest.php b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-function/tests/FirstPartyClassTest.php new file mode 100644 index 00000000000..1abafab73ab --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-function/tests/FirstPartyClassTest.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SelfDirectIndirect; + +use PHPUnit\Framework\TestCase; + +final class FirstPartyClassTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue((new FirstPartyClass)->method()); + } + + public function testTwo(): void + { + $this->assertTrue((new ThirdPartyClass)->anotherMethod()); + } +} diff --git a/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-function/vendor/ThirdPartyClass.php b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-function/vendor/ThirdPartyClass.php new file mode 100644 index 00000000000..b91884d7835 --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-function/vendor/ThirdPartyClass.php @@ -0,0 +1,15 @@ +method(); + } +} diff --git a/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-function/vendor/autoload.php b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-function/vendor/autoload.php new file mode 100644 index 00000000000..c5ef0a2856c --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-function/vendor/autoload.php @@ -0,0 +1,4 @@ + + + + + tests + + + + + + src + + + + PHPUnit\TestFixture\SelfDirectIndirect\DeprecationTrigger::triggerDeprecation + + + diff --git a/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-method/src/FirstPartyClass.php b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-method/src/FirstPartyClass.php new file mode 100644 index 00000000000..aca0b8b8d05 --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-method/src/FirstPartyClass.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SelfDirectIndirect; + +use const E_USER_DEPRECATED; +use function trigger_error; + +final class FirstPartyClass +{ + public function method(): true + { + (new ThirdPartyClass)->method(); + + @trigger_error('deprecation in first-party code', E_USER_DEPRECATED); + + return true; + } +} diff --git a/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-method/tests/FirstPartyClassTest.php b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-method/tests/FirstPartyClassTest.php new file mode 100644 index 00000000000..1abafab73ab --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-method/tests/FirstPartyClassTest.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SelfDirectIndirect; + +use PHPUnit\Framework\TestCase; + +final class FirstPartyClassTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue((new FirstPartyClass)->method()); + } + + public function testTwo(): void + { + $this->assertTrue((new ThirdPartyClass)->anotherMethod()); + } +} diff --git a/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-method/vendor/DeprecationTrigger.php b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-method/vendor/DeprecationTrigger.php new file mode 100644 index 00000000000..b54156ad14b --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-method/vendor/DeprecationTrigger.php @@ -0,0 +1,10 @@ +method(); + } +} diff --git a/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-method/vendor/autoload.php b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-method/vendor/autoload.php new file mode 100644 index 00000000000..fe3e4ed83dd --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-method/vendor/autoload.php @@ -0,0 +1,4 @@ + + + + + tests + + + + + + PHPUnit\TestFixture\DeprecationTrigger\Test::triggerDeprecation + PHPUnit\TestFixture\DeprecationTrigger\triggerDeprecation + + + diff --git a/tests/end-to-end/deprecation-trigger/_files/details-process-isolation/tests/Test.php b/tests/end-to-end/deprecation-trigger/_files/details-process-isolation/tests/Test.php new file mode 100644 index 00000000000..39137a32d4b --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/_files/details-process-isolation/tests/Test.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\DeprecationTrigger; + +use const E_USER_DEPRECATED; +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public static function triggerDeprecation(): void + { + trigger_error('deprecation triggered by method', E_USER_DEPRECATED); + } + + public function testDeprecation(): void + { + self::triggerDeprecation(); + triggerDeprecation(); + + $this->assertTrue(true); + } +} + +function triggerDeprecation(): void +{ + trigger_error('deprecation triggered by function', E_USER_DEPRECATED); +} diff --git a/tests/end-to-end/deprecation-trigger/_files/details/phpunit.xml b/tests/end-to-end/deprecation-trigger/_files/details/phpunit.xml new file mode 100644 index 00000000000..d4c4d73e6cd --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/_files/details/phpunit.xml @@ -0,0 +1,18 @@ + + + + + tests + + + + + + PHPUnit\TestFixture\DeprecationTrigger\Test::triggerDeprecation + PHPUnit\TestFixture\DeprecationTrigger\triggerDeprecation + + + diff --git a/tests/end-to-end/deprecation-trigger/_files/details/tests/Test.php b/tests/end-to-end/deprecation-trigger/_files/details/tests/Test.php new file mode 100644 index 00000000000..39137a32d4b --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/_files/details/tests/Test.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\DeprecationTrigger; + +use const E_USER_DEPRECATED; +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public static function triggerDeprecation(): void + { + trigger_error('deprecation triggered by method', E_USER_DEPRECATED); + } + + public function testDeprecation(): void + { + self::triggerDeprecation(); + triggerDeprecation(); + + $this->assertTrue(true); + } +} + +function triggerDeprecation(): void +{ + trigger_error('deprecation triggered by function', E_USER_DEPRECATED); +} diff --git a/tests/end-to-end/deprecation-trigger/deprecation-trigger-baseline-ignore.phpt b/tests/end-to-end/deprecation-trigger/deprecation-trigger-baseline-ignore.phpt new file mode 100644 index 00000000000..76ac837a0cd --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/deprecation-trigger-baseline-ignore.phpt @@ -0,0 +1,36 @@ +--TEST-- +The right events are emitted in the right order for a test that runs code which triggers E_USER_DEPRECATED +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (%s) +Test Runner Configured +Bootstrap Finished (%sautoload.php) +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (%sphpunit.xml, 1 test) +Test Suite Started (default, 1 test) +Test Suite Started (PHPUnit\TestFixture\BaselineIgnoreDeprecation\FirstPartyClassTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\BaselineIgnoreDeprecation\FirstPartyClassTest::testOne) +Test Prepared (PHPUnit\TestFixture\BaselineIgnoreDeprecation\FirstPartyClassTest::testOne) +Test Triggered Deprecation (PHPUnit\TestFixture\BaselineIgnoreDeprecation\FirstPartyClassTest::testOne, issue triggered by third-party code, suppressed using operator, ignored by baseline) in %s:%d +deprecation in third-party code +Test Passed (PHPUnit\TestFixture\BaselineIgnoreDeprecation\FirstPartyClassTest::testOne) +Test Finished (PHPUnit\TestFixture\BaselineIgnoreDeprecation\FirstPartyClassTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\BaselineIgnoreDeprecation\FirstPartyClassTest, 1 test) +Test Suite Finished (default, 1 test) +Test Suite Finished (%sphpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/deprecation-trigger/deprecation-trigger-class.phpt b/tests/end-to-end/deprecation-trigger/deprecation-trigger-class.phpt new file mode 100644 index 00000000000..fe6b7ff2d47 --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/deprecation-trigger-class.phpt @@ -0,0 +1,36 @@ +--TEST-- +The right events are emitted in the right order for a test that loads a class that triggers E_USER_DEPRECATED +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Bootstrap Finished (%sautoload.php) +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (%sphpunit.xml, 1 test) +Test Suite Started (default, 1 test) +Test Suite Started (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne) +Test Prepared (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne) +Test Triggered Deprecation (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne, issue triggered by third-party code, suppressed using operator) in %s:%d +This class is deprecated +Test Passed (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne) +Test Finished (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest, 1 test) +Test Suite Finished (default, 1 test) +Test Suite Finished (%sphpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/deprecation-trigger/deprecation-trigger-function.phpt b/tests/end-to-end/deprecation-trigger/deprecation-trigger-function.phpt new file mode 100644 index 00000000000..1848ed1b102 --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/deprecation-trigger-function.phpt @@ -0,0 +1,46 @@ +--TEST-- +The right events are emitted in the right order for a test that runs code which triggers E_USER_DEPRECATED +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Bootstrap Finished (%sautoload.php) +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (%sphpunit.xml, 2 tests) +Test Suite Started (default, 2 tests) +Test Suite Started (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne) +Test Prepared (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne) +Test Triggered Deprecation (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne, issue triggered by first-party code calling into third-party code, suppressed using operator) in %s:%d +deprecation in third-party code +Test Triggered Deprecation (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne, issue triggered by first-party code calling into first-party code, suppressed using operator) in %s:%d +deprecation in first-party code +Test Passed (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne) +Test Finished (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne) +Test Preparation Started (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testTwo) +Test Prepared (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testTwo) +Test Triggered Deprecation (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testTwo, issue triggered by first-party code calling into third-party code, suppressed using operator) in %s:%d +deprecation in third-party code +Test Triggered Deprecation (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testTwo, issue triggered by third-party code, suppressed using operator) in %s:%d +deprecation in first-party code +Test Passed (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testTwo) +Test Finished (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testTwo) +Test Suite Finished (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest, 2 tests) +Test Suite Finished (default, 2 tests) +Test Suite Finished (%sphpunit.xml, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/deprecation-trigger/deprecation-trigger-method.phpt b/tests/end-to-end/deprecation-trigger/deprecation-trigger-method.phpt new file mode 100644 index 00000000000..4bd800f9be4 --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/deprecation-trigger-method.phpt @@ -0,0 +1,46 @@ +--TEST-- +The right events are emitted in the right order for a test that runs code which triggers E_USER_DEPRECATED +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Bootstrap Finished (%sautoload.php) +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (%sphpunit.xml, 2 tests) +Test Suite Started (default, 2 tests) +Test Suite Started (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne) +Test Prepared (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne) +Test Triggered Deprecation (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne, issue triggered by first-party code calling into third-party code, suppressed using operator) in %s:%d +deprecation in third-party code +Test Triggered Deprecation (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne, issue triggered by first-party code calling into first-party code, suppressed using operator) in %s:%d +deprecation in first-party code +Test Passed (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne) +Test Finished (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne) +Test Preparation Started (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testTwo) +Test Prepared (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testTwo) +Test Triggered Deprecation (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testTwo, issue triggered by first-party code calling into third-party code, suppressed using operator) in %s:%d +deprecation in third-party code +Test Triggered Deprecation (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testTwo, issue triggered by third-party code, suppressed using operator) in %s:%d +deprecation in first-party code +Test Passed (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testTwo) +Test Finished (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testTwo) +Test Suite Finished (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest, 2 tests) +Test Suite Finished (default, 2 tests) +Test Suite Finished (%sphpunit.xml, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/deprecation-trigger/details-process-isolation.phpt b/tests/end-to-end/deprecation-trigger/details-process-isolation.phpt new file mode 100644 index 00000000000..24f80fdf7c1 --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/details-process-isolation.phpt @@ -0,0 +1,32 @@ +--TEST-- +Configured deprecation triggers are filtered when displaying deprecation details in process isolation +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +D 1 / 1 (100%) + +Time: %s, Memory: %s + +1 test triggered 2 deprecations: + +1) %sTest.php:25 +deprecation triggered by method + +2) %sTest.php:26 +deprecation triggered by function + +OK, but there were issues! +Tests: 1, Assertions: 1, Deprecations: 2. diff --git a/tests/end-to-end/deprecation-trigger/details.phpt b/tests/end-to-end/deprecation-trigger/details.phpt new file mode 100644 index 00000000000..0d7662ff3ac --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/details.phpt @@ -0,0 +1,32 @@ +--TEST-- +Configured deprecation triggers are filtered when displaying deprecation details +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +D 1 / 1 (100%) + +Time: %s, Memory: %s + +1 test triggered 2 deprecations: + +1) %sTest.php:25 +deprecation triggered by method + +2) %sTest.php:26 +deprecation triggered by function + +OK, but there were issues! +Tests: 1, Assertions: 1, Deprecations: 2. diff --git a/tests/end-to-end/dump-xdebug-filter.phpt b/tests/end-to-end/dump-xdebug-filter.phpt deleted file mode 100644 index d81e9620f6f..00000000000 --- a/tests/end-to-end/dump-xdebug-filter.phpt +++ /dev/null @@ -1,33 +0,0 @@ ---TEST-- -phpunit -c ../_files/configuration_xdebug_filter.xml --dump-xdebug-filter 'php://stdout' ---SKIPIF-- - + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\TestCase; + +final class AdditionalInformationTest extends TestCase +{ + public function testSuccess(): void + { + $this->assertTrue(true); + + $this->provideAdditionalInformation('additional information'); + } +} diff --git a/tests/end-to-end/event/_files/ArgumentDataProviderTest.php b/tests/end-to-end/event/_files/ArgumentDataProviderTest.php new file mode 100644 index 00000000000..90be29e089f --- /dev/null +++ b/tests/end-to-end/event/_files/ArgumentDataProviderTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class ArgumentDataProviderTest extends TestCase +{ + public static function values(mixed $argument): array + { + return [[true], [true]]; + } + + #[DataProvider('values')] + public function testSuccess(bool $value): void + { + $this->assertTrue($value); + } +} diff --git a/tests/end-to-end/event/_files/AssertTest.php b/tests/end-to-end/event/_files/AssertTest.php new file mode 100644 index 00000000000..0ca78e7e790 --- /dev/null +++ b/tests/end-to-end/event/_files/AssertTest.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use function assert; +use PHPUnit\Framework\TestCase; + +final class AssertTest extends TestCase +{ + public function testAssert(): void + { + assert(false); + } +} diff --git a/tests/end-to-end/event/_files/AssertionFailureInPostConditionTest.php b/tests/end-to-end/event/_files/AssertionFailureInPostConditionTest.php new file mode 100644 index 00000000000..f8cfa860c23 --- /dev/null +++ b/tests/end-to-end/event/_files/AssertionFailureInPostConditionTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\Attributes\PostCondition; +use PHPUnit\Framework\TestCase; + +final class AssertionFailureInPostConditionTest extends TestCase +{ + #[PostCondition] + public function postCondition(): void + { + $this->assertTrue(false); + } + + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/AssertionFailureInPreConditionTest.php b/tests/end-to-end/event/_files/AssertionFailureInPreConditionTest.php new file mode 100644 index 00000000000..69711477dc5 --- /dev/null +++ b/tests/end-to-end/event/_files/AssertionFailureInPreConditionTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\Attributes\PreCondition; +use PHPUnit\Framework\TestCase; + +final class AssertionFailureInPreConditionTest extends TestCase +{ + #[PreCondition] + public function preCondition(): void + { + $this->assertTrue(false); + } + + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/AssertionFailureInSetUpBeforeClassTest.php b/tests/end-to-end/event/_files/AssertionFailureInSetUpBeforeClassTest.php new file mode 100644 index 00000000000..ce6c0e75bdd --- /dev/null +++ b/tests/end-to-end/event/_files/AssertionFailureInSetUpBeforeClassTest.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\TestCase; + +final class AssertionFailureInSetUpBeforeClassTest extends TestCase +{ + public static function setUpBeforeClass(): void + { + self::assertTrue(false); + } + + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/AssertionFailureInSetUpTest.php b/tests/end-to-end/event/_files/AssertionFailureInSetUpTest.php new file mode 100644 index 00000000000..cf2829e5f5d --- /dev/null +++ b/tests/end-to-end/event/_files/AssertionFailureInSetUpTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\Attributes\Before; +use PHPUnit\Framework\TestCase; + +final class AssertionFailureInSetUpTest extends TestCase +{ + #[Before] + public function beforeTest(): void + { + $this->assertTrue(false); + } + + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/AssertionFailureInTearDownAfterClassTest.php b/tests/end-to-end/event/_files/AssertionFailureInTearDownAfterClassTest.php new file mode 100644 index 00000000000..84f81e79f0c --- /dev/null +++ b/tests/end-to-end/event/_files/AssertionFailureInTearDownAfterClassTest.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\TestCase; + +final class AssertionFailureInTearDownAfterClassTest extends TestCase +{ + public static function tearDownAfterClass(): void + { + self::assertTrue(false); + } + + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/AssertionFailureInTearDownTest.php b/tests/end-to-end/event/_files/AssertionFailureInTearDownTest.php new file mode 100644 index 00000000000..110f1d6c6d8 --- /dev/null +++ b/tests/end-to-end/event/_files/AssertionFailureInTearDownTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\Attributes\After; +use PHPUnit\Framework\TestCase; + +final class AssertionFailureInTearDownTest extends TestCase +{ + #[After] + public function afterTest(): void + { + $this->assertTrue(false); + } + + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/CustomComparator.php b/tests/end-to-end/event/_files/CustomComparator.php new file mode 100644 index 00000000000..3622ff23af9 --- /dev/null +++ b/tests/end-to-end/event/_files/CustomComparator.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use SebastianBergmann\Comparator\Comparator; + +final class CustomComparator extends Comparator +{ + public function accepts($expected, $actual): bool + { + return true; + } + + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false): void + { + } +} diff --git a/tests/end-to-end/event/_files/CustomComparatorTest.php b/tests/end-to-end/event/_files/CustomComparatorTest.php new file mode 100644 index 00000000000..e98c7a12b70 --- /dev/null +++ b/tests/end-to-end/event/_files/CustomComparatorTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\TestCase; + +final class CustomComparatorTest extends TestCase +{ + public function testWithCustomComparator(): void + { + $this->registerComparator(new CustomComparator); + + $this->assertEquals(true, false); + } +} diff --git a/tests/end-to-end/event/_files/DataProvider.php b/tests/end-to-end/event/_files/DataProvider.php new file mode 100644 index 00000000000..201e07c04da --- /dev/null +++ b/tests/end-to-end/event/_files/DataProvider.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +final class DataProvider +{ + public static function values(): array + { + return [[true], [true]]; + } +} diff --git a/tests/end-to-end/event/_files/DataProviderDuplicateKeyTest.php b/tests/end-to-end/event/_files/DataProviderDuplicateKeyTest.php new file mode 100644 index 00000000000..15ea0af26be --- /dev/null +++ b/tests/end-to-end/event/_files/DataProviderDuplicateKeyTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use Generator; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class DataProviderDuplicateKeyTest extends TestCase +{ + public static function provider(): Generator + { + yield 'key' => [true]; + + yield 'key' => [true]; + } + + #[DataProvider('provider')] + public function testSomething(bool $value): void + { + $this->assertTrue($value); + } +} diff --git a/tests/end-to-end/event/_files/DataProviderExternalTest.php b/tests/end-to-end/event/_files/DataProviderExternalTest.php new file mode 100644 index 00000000000..90671b34faf --- /dev/null +++ b/tests/end-to-end/event/_files/DataProviderExternalTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\TestCase; + +final class DataProviderExternalTest extends TestCase +{ + #[DataProviderExternal(DataProvider::class, 'values')] + public function testSuccess(bool $value): void + { + $this->assertTrue($value); + } +} diff --git a/tests/end-to-end/event/_files/DataProviderInParentTest.php b/tests/end-to-end/event/_files/DataProviderInParentTest.php new file mode 100644 index 00000000000..d467478dc2d --- /dev/null +++ b/tests/end-to-end/event/_files/DataProviderInParentTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class DataProviderInParentTest extends ParentTestCase +{ +} + +abstract class ParentTestCase extends TestCase +{ + public static function data_provider(): iterable + { + yield [true]; + } + + #[DataProvider('data_provider')] + public function testSomething(bool $var): void + { + $this->assertTrue($var); + } +} diff --git a/tests/end-to-end/event/_files/DataProviderInvalidKeyTest.php b/tests/end-to-end/event/_files/DataProviderInvalidKeyTest.php new file mode 100644 index 00000000000..57fdfd8e16f --- /dev/null +++ b/tests/end-to-end/event/_files/DataProviderInvalidKeyTest.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use Iterator; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class DataProviderInvalidKeyTest extends TestCase +{ + public static function provider(): Iterator + { + return new class implements Iterator + { + private int $position; + + public function current(): string + { + return 'value'; + } + + public function next(): void + { + $this->position++; + } + + public function key(): float + { + return 0.1; + } + + public function valid(): bool + { + return $this->position === 0; + } + + public function rewind(): void + { + $this->position = 0; + } + }; + } + + #[DataProvider('provider')] + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/DataProviderNotIterableTest.php b/tests/end-to-end/event/_files/DataProviderNotIterableTest.php new file mode 100644 index 00000000000..eba22901403 --- /dev/null +++ b/tests/end-to-end/event/_files/DataProviderNotIterableTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class DataProviderNotIterableTest extends TestCase +{ + public static function provider(): string + { + return 'string'; + } + + #[DataProvider('provider')] + public function testSomething(bool $value): void + { + $this->assertTrue($value); + } +} diff --git a/tests/end-to-end/event/_files/DataProviderTest.php b/tests/end-to-end/event/_files/DataProviderTest.php new file mode 100644 index 00000000000..404c6239812 --- /dev/null +++ b/tests/end-to-end/event/_files/DataProviderTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class DataProviderTest extends TestCase +{ + public static function values(): array + { + return [[true], [true]]; + } + + #[DataProvider('values')] + public function testSuccess(bool $value): void + { + $this->assertTrue($value); + } +} diff --git a/tests/end-to-end/event/_files/DeprecatedFeatureTest.php b/tests/end-to-end/event/_files/DeprecatedFeatureTest.php new file mode 100644 index 00000000000..8cdf64d4f7f --- /dev/null +++ b/tests/end-to-end/event/_files/DeprecatedFeatureTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use const E_USER_DEPRECATED; +use function error_get_last; +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class DeprecatedFeatureTest extends TestCase +{ + public function testDeprecatedFeature(): void + { + trigger_error('message', E_USER_DEPRECATED); + @trigger_error('message', E_USER_DEPRECATED); + + $this->assertTrue(true); + } + + public function testDeprecatedSuppressedErrorGetLast(): void + { + $this->assertNull(error_get_last()); + @trigger_error('message', E_USER_DEPRECATED); + $this->assertIsArray(error_get_last()); + } +} diff --git a/tests/end-to-end/event/_files/DeprecatedPhpFeatureTest.php b/tests/end-to-end/event/_files/DeprecatedPhpFeatureTest.php new file mode 100644 index 00000000000..aba4afe4c0c --- /dev/null +++ b/tests/end-to-end/event/_files/DeprecatedPhpFeatureTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use function strlen; +use PHPUnit\Framework\TestCase; + +final class DeprecatedPhpFeatureTest extends TestCase +{ + public function testDeprecatedPhpFeature(): void + { + strlen(null); + @strlen(null); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/DeprecatedPhpunitFeatureTest.php b/tests/end-to-end/event/_files/DeprecatedPhpunitFeatureTest.php new file mode 100644 index 00000000000..835abf7923f --- /dev/null +++ b/tests/end-to-end/event/_files/DeprecatedPhpunitFeatureTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Framework\TestCase; + +final class DeprecatedPhpunitFeatureTest extends TestCase +{ + public function testDeprecatedPhpunitFeature(): void + { + EventFacade::emitter()->testTriggeredPhpunitDeprecation( + $this->valueObjectForEvents(), + 'message', + ); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/DynamicDataProviderTest.php b/tests/end-to-end/event/_files/DynamicDataProviderTest.php new file mode 100644 index 00000000000..fa9224e2abe --- /dev/null +++ b/tests/end-to-end/event/_files/DynamicDataProviderTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class DynamicDataProviderTest extends TestCase +{ + public function values(): array + { + return [[true], [true]]; + } + + #[DataProvider('values')] + public function testSuccess(bool $value): void + { + $this->assertTrue($value); + } +} diff --git a/tests/end-to-end/event/_files/EmptyDataProviderTest.php b/tests/end-to-end/event/_files/EmptyDataProviderTest.php new file mode 100644 index 00000000000..8f4d04f9af5 --- /dev/null +++ b/tests/end-to-end/event/_files/EmptyDataProviderTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class EmptyDataProviderTest extends TestCase +{ + public static function providerMethod(): array + { + return []; + } + + #[DataProvider('providerMethod')] + public function testCase(): void + { + } +} diff --git a/tests/end-to-end/event/_files/EmptyTest.php b/tests/end-to-end/event/_files/EmptyTest.php new file mode 100644 index 00000000000..085a7edd8b0 --- /dev/null +++ b/tests/end-to-end/event/_files/EmptyTest.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\TestCase; + +final class EmptyTest extends TestCase +{ +} diff --git a/tests/end-to-end/event/_files/ErrorTest.php b/tests/end-to-end/event/_files/ErrorTest.php new file mode 100644 index 00000000000..dd82835fec7 --- /dev/null +++ b/tests/end-to-end/event/_files/ErrorTest.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use Exception; +use PHPUnit\Framework\TestCase; + +final class ErrorTest extends TestCase +{ + public function testError(): void + { + throw new Exception('message'); + } +} diff --git a/tests/end-to-end/event/_files/Example.php b/tests/end-to-end/event/_files/Example.php new file mode 100644 index 00000000000..09994df6c2a --- /dev/null +++ b/tests/end-to-end/event/_files/Example.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +interface Example +{ + public function doSomething(): bool; +} diff --git a/tests/end-to-end/event/_files/ExceptionInDataProviderTest.php b/tests/end-to-end/event/_files/ExceptionInDataProviderTest.php new file mode 100644 index 00000000000..bedfba3136a --- /dev/null +++ b/tests/end-to-end/event/_files/ExceptionInDataProviderTest.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; +use RuntimeException; + +final class ExceptionInDataProviderTest extends TestCase +{ + public static function provider(): array + { + throw new RuntimeException('message'); + } + + #[DataProvider('provider')] + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/ExceptionInSetUpBeforeClassTest.php b/tests/end-to-end/event/_files/ExceptionInSetUpBeforeClassTest.php new file mode 100644 index 00000000000..327010a7280 --- /dev/null +++ b/tests/end-to-end/event/_files/ExceptionInSetUpBeforeClassTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use Exception; +use PHPUnit\Framework\TestCase; + +final class ExceptionInSetUpBeforeClassTest extends TestCase +{ + public static function setUpBeforeClass(): void + { + throw new Exception; + } + + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/ExceptionInSetUpTest.php b/tests/end-to-end/event/_files/ExceptionInSetUpTest.php new file mode 100644 index 00000000000..0e41780445d --- /dev/null +++ b/tests/end-to-end/event/_files/ExceptionInSetUpTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use Exception; +use PHPUnit\Framework\TestCase; + +final class ExceptionInSetUpTest extends TestCase +{ + protected function setUp(): void + { + throw new Exception; + } + + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/ExceptionInTearDownAfterClassTest.php b/tests/end-to-end/event/_files/ExceptionInTearDownAfterClassTest.php new file mode 100644 index 00000000000..22094ddd65d --- /dev/null +++ b/tests/end-to-end/event/_files/ExceptionInTearDownAfterClassTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use Exception; +use PHPUnit\Framework\TestCase; + +final class ExceptionInTearDownAfterClassTest extends TestCase +{ + public static function tearDownAfterClass(): void + { + throw new Exception; + } + + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/ExceptionInTearDownTest.php b/tests/end-to-end/event/_files/ExceptionInTearDownTest.php new file mode 100644 index 00000000000..5b7e35311f9 --- /dev/null +++ b/tests/end-to-end/event/_files/ExceptionInTearDownTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use Exception; +use PHPUnit\Framework\TestCase; + +final class ExceptionInTearDownTest extends TestCase +{ + protected function tearDown(): void + { + throw new Exception; + } + + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/ExpectedAndUnexpectedAssertionsTest.php b/tests/end-to-end/event/_files/ExpectedAndUnexpectedAssertionsTest.php new file mode 100644 index 00000000000..06118eb390f --- /dev/null +++ b/tests/end-to-end/event/_files/ExpectedAndUnexpectedAssertionsTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; +use PHPUnit\Framework\TestCase; + +final class ExpectedAndUnexpectedAssertionsTest extends TestCase +{ + #[DoesNotPerformAssertions] + public function testOne(): void + { + } + + public function testTwo(): void + { + $this->expectNotToPerformAssertions(); + } + + #[DoesNotPerformAssertions] + public function testThree(): void + { + $this->assertTrue(true); + } + + public function testFour(): void + { + $this->expectNotToPerformAssertions(); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/FailedExpectationTest.php b/tests/end-to-end/event/_files/FailedExpectationTest.php new file mode 100644 index 00000000000..2ee6c88d32c --- /dev/null +++ b/tests/end-to-end/event/_files/FailedExpectationTest.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\MockObject\AnInterface; + +final class FailedExpectationTest extends TestCase +{ + public function testOne(): void + { + $mock = $this->createMock(AnInterface::class); + + $mock->expects($this->once())->method('doSomething'); + } +} diff --git a/tests/end-to-end/event/_files/FailureTest.php b/tests/end-to-end/event/_files/FailureTest.php new file mode 100644 index 00000000000..dfa1b9c6c30 --- /dev/null +++ b/tests/end-to-end/event/_files/FailureTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\TestCase; + +final class FailureTest extends TestCase +{ + public function testFailure(): void + { + $this->assertTrue(false); + } +} diff --git a/tests/end-to-end/event/_files/FatalTest.php b/tests/end-to-end/event/_files/FatalTest.php new file mode 100644 index 00000000000..aa76e48fd31 --- /dev/null +++ b/tests/end-to-end/event/_files/FatalTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\TestCase; + +final class FatalTest extends TestCase +{ + public function testOne(): void + { + doesNotExist(); + } +} diff --git a/tests/end-to-end/event/_files/IgnoreDeprecationsTest.php b/tests/end-to-end/event/_files/IgnoreDeprecationsTest.php new file mode 100644 index 00000000000..a165a42fbd6 --- /dev/null +++ b/tests/end-to-end/event/_files/IgnoreDeprecationsTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use const E_USER_DEPRECATED; +use function error_get_last; +use function trigger_error; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; +use PHPUnit\Framework\TestCase; + +final class IgnoreDeprecationsTest extends TestCase +{ + #[IgnoreDeprecations] + public function testOne(): void + { + trigger_error('message', E_USER_DEPRECATED); + + $this->assertTrue(true); + } + + public function testTwo(): void + { + trigger_error('message', E_USER_DEPRECATED); + + $this->assertTrue(true); + } + + #[IgnoreDeprecations] + public function testOneErrorGetLast(): void + { + $this->assertNull(error_get_last()); + trigger_error('message', E_USER_DEPRECATED); + $this->assertIsArray(error_get_last()); + } + + public function testTwoErrorGetLast(): void + { + $this->assertNull(error_get_last()); + trigger_error('message', E_USER_DEPRECATED); + $this->assertIsArray(error_get_last()); + } +} diff --git a/tests/end-to-end/event/_files/IgnoreDeprecationsWithPatternTest.php b/tests/end-to-end/event/_files/IgnoreDeprecationsWithPatternTest.php new file mode 100644 index 00000000000..5c97fc33245 --- /dev/null +++ b/tests/end-to-end/event/_files/IgnoreDeprecationsWithPatternTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use const E_USER_DEPRECATED; +use function trigger_error; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; +use PHPUnit\Framework\TestCase; + +#[IgnoreDeprecations('foo')] +final class IgnoreDeprecationsWithPatternTest extends TestCase +{ + #[IgnoreDeprecations('bar')] + public function testOne(): void + { + trigger_error('foo', E_USER_DEPRECATED); + trigger_error('bar', E_USER_DEPRECATED); + + $this->assertTrue(true); + } + + public function testTwo(): void + { + trigger_error('foo', E_USER_DEPRECATED); + + $this->assertTrue(true); + } + + public function testThree(): void + { + trigger_error('foo', E_USER_DEPRECATED); + trigger_error('baz', E_USER_DEPRECATED); + + $this->assertTrue(true); + } + + #[IgnoreDeprecations('bar|baz')] + public function testFour(): void + { + trigger_error('foo', E_USER_DEPRECATED); + trigger_error('bar', E_USER_DEPRECATED); + trigger_error('baz', E_USER_DEPRECATED); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/IgnoredDeprecatedPhpunitFeatureTest.php b/tests/end-to-end/event/_files/IgnoredDeprecatedPhpunitFeatureTest.php new file mode 100644 index 00000000000..24c49e6577e --- /dev/null +++ b/tests/end-to-end/event/_files/IgnoredDeprecatedPhpunitFeatureTest.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Framework\Attributes\IgnorePhpunitDeprecations; +use PHPUnit\Framework\TestCase; + +final class IgnoredDeprecatedPhpunitFeatureTest extends TestCase +{ + #[IgnorePhpunitDeprecations] + public function testDeprecatedPhpunitFeature(): void + { + EventFacade::emitter()->testTriggeredPhpunitDeprecation( + $this->valueObjectForEvents(), + 'message', + ); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/IncompleteTest.php b/tests/end-to-end/event/_files/IncompleteTest.php new file mode 100644 index 00000000000..b29e84a23ce --- /dev/null +++ b/tests/end-to-end/event/_files/IncompleteTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\TestCase; + +final class IncompleteTest extends TestCase +{ + public function testIncomplete(): void + { + $this->markTestIncomplete('message'); + } +} diff --git a/tests/end-to-end/event/_files/InvalidDataProviderTest.php b/tests/end-to-end/event/_files/InvalidDataProviderTest.php new file mode 100644 index 00000000000..60cff8a16b0 --- /dev/null +++ b/tests/end-to-end/event/_files/InvalidDataProviderTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class InvalidDataProviderTest extends TestCase +{ + public static function provider(): array + { + return [0]; + } + + #[DataProvider('provider')] + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/InvalidDataProviderWithOneTestPassingTest.php b/tests/end-to-end/event/_files/InvalidDataProviderWithOneTestPassingTest.php new file mode 100644 index 00000000000..e5b0e343bb8 --- /dev/null +++ b/tests/end-to-end/event/_files/InvalidDataProviderWithOneTestPassingTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class InvalidDataProviderWithOneTestPassingTest extends TestCase +{ + public static function provider(): array + { + return [0]; + } + + #[DataProvider('provider')] + public function testOne(): void + { + $this->assertTrue(true); + } + + public function testTwo(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/InvalidDependencyTest.php b/tests/end-to-end/event/_files/InvalidDependencyTest.php new file mode 100644 index 00000000000..ded12c8c6a6 --- /dev/null +++ b/tests/end-to-end/event/_files/InvalidDependencyTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\Attributes\Depends; +use PHPUnit\Framework\Attributes\DependsOnClass; +use PHPUnit\Framework\TestCase; + +final class InvalidDependencyTest extends TestCase +{ + #[Depends('doesNotExist')] + public function testOne(): void + { + $this->assertTrue(true); + } + + #[DependsOnClass('DoesNotExist')] + public function testTwo(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/InvalidParameterNameDataProviderTest.php b/tests/end-to-end/event/_files/InvalidParameterNameDataProviderTest.php new file mode 100644 index 00000000000..5e9d3717d43 --- /dev/null +++ b/tests/end-to-end/event/_files/InvalidParameterNameDataProviderTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class InvalidParameterNameDataProviderTest extends TestCase +{ + public static function values(): array + { + return [ + ['value1' => true, 'value2' => true], + ['value3' => true, 'value4' => true], + ]; + } + + #[DataProvider('values')] + public function testSuccess(bool $value1, bool $value2): void + { + $this->assertTrue($value1); + $this->assertTrue($value2); + } +} diff --git a/tests/end-to-end/event/_files/InvalidVersionConstraintTest.php b/tests/end-to-end/event/_files/InvalidVersionConstraintTest.php new file mode 100644 index 00000000000..6d9c262d364 --- /dev/null +++ b/tests/end-to-end/event/_files/InvalidVersionConstraintTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\Attributes\RequiresPhp; +use PHPUnit\Framework\TestCase; + +final class InvalidVersionConstraintTest extends TestCase +{ + #[RequiresPhp('100')] + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/MissingDependencyTest.php b/tests/end-to-end/event/_files/MissingDependencyTest.php new file mode 100644 index 00000000000..6d3a39bf0df --- /dev/null +++ b/tests/end-to-end/event/_files/MissingDependencyTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\Attributes\Depends; +use PHPUnit\Framework\TestCase; + +final class MissingDependencyTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(false); + } + + #[Depends('testOne')] + public function testTwo(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/MockTest.php b/tests/end-to-end/event/_files/MockTest.php new file mode 100644 index 00000000000..980914092f0 --- /dev/null +++ b/tests/end-to-end/event/_files/MockTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\TestCase; + +final class MockTest extends TestCase +{ + public function testSuccess(): void + { + $mock = $this->createMock(Example::class); + + $mock + ->expects($this->never()) + ->method('doSomething'); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/MockWithoutExpectationTest.php b/tests/end-to-end/event/_files/MockWithoutExpectationTest.php new file mode 100644 index 00000000000..3fb7757b39a --- /dev/null +++ b/tests/end-to-end/event/_files/MockWithoutExpectationTest.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace TestFixture\PHPUnit\Event; + +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\Event\Example; + +final class MockWithoutExpectationTest extends TestCase +{ + public function testSuccess(): void + { + $this->createMock(Example::class); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/PhpNoticeTest.php b/tests/end-to-end/event/_files/PhpNoticeTest.php new file mode 100644 index 00000000000..b3eadc55f92 --- /dev/null +++ b/tests/end-to-end/event/_files/PhpNoticeTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\TestCase; + +final class PhpNoticeTest extends TestCase +{ + public function testPhpNotice(): void + { + $f = static function (): void + { + }; + + $a = &$f(); + @$a = &$f(); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/PhpWarningTest.php b/tests/end-to-end/event/_files/PhpWarningTest.php new file mode 100644 index 00000000000..91d81f06e7a --- /dev/null +++ b/tests/end-to-end/event/_files/PhpWarningTest.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\TestCase; + +final class PhpWarningTest extends TestCase +{ + public function testPhpWarning(): void + { + $a = $b; + @$a = $b; + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/PhpunitNoticeTest.php b/tests/end-to-end/event/_files/PhpunitNoticeTest.php new file mode 100644 index 00000000000..20ab335486b --- /dev/null +++ b/tests/end-to-end/event/_files/PhpunitNoticeTest.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Event\Facade; +use PHPUnit\Framework\TestCase; + +final class PhpunitNoticeTest extends TestCase +{ + public function testOne(): void + { + Facade::emitter()->testTriggeredPhpunitNotice( + $this->valueObjectForEvents(), + 'message', + ); + + Facade::emitter()->testRunnerTriggeredPhpunitNotice('message'); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/PhpunitWarningIgnoredTest.php b/tests/end-to-end/event/_files/PhpunitWarningIgnoredTest.php new file mode 100644 index 00000000000..b5514024571 --- /dev/null +++ b/tests/end-to-end/event/_files/PhpunitWarningIgnoredTest.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\IgnorePhpunitWarnings; +use PHPUnit\Framework\TestCase; + +final class PhpunitWarningIgnoredTest extends TestCase +{ + public static function dataProvider(): iterable + { + yield [true]; + } + + #[IgnorePhpunitWarnings] + public function testPhpunitWarning(): void + { + EventFacade::emitter()->testTriggeredPhpunitWarning( + $this->valueObjectForEvents(), + 'warning message', + ); + + $this->assertTrue(true); + } + + #[IgnorePhpunitWarnings('warning message')] + public function testPhpunitWarningWithExactMessage(): void + { + EventFacade::emitter()->testTriggeredPhpunitWarning( + $this->valueObjectForEvents(), + 'warning message', + ); + + $this->assertTrue(true); + } + + #[IgnorePhpunitWarnings('warn(.*)mess(.*)')] + public function testPhpunitWarningWithRegex(): void + { + EventFacade::emitter()->testTriggeredPhpunitWarning( + $this->valueObjectForEvents(), + 'warning message', + ); + + $this->assertTrue(true); + } + + #[IgnorePhpunitWarnings('warning/error(.*)')] + public function testPhpunitWarningWithSlashInRegex(): void + { + EventFacade::emitter()->testTriggeredPhpunitWarning( + $this->valueObjectForEvents(), + 'warning/error message', + ); + + $this->assertTrue(true); + } + + #[IgnorePhpunitWarnings('warn(.*)mess(.*)')] + public function testPhpunitWarningWithWrongPattern(): void + { + EventFacade::emitter()->testTriggeredPhpunitWarning( + $this->valueObjectForEvents(), + 'another message', + ); + + $this->assertTrue(true); + } + + #[DataProvider('dataProvider')] + #[IgnorePhpunitWarnings] + public function testTooManyArgumentsInDataProvider(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/PhpunitWarningTest.php b/tests/end-to-end/event/_files/PhpunitWarningTest.php new file mode 100644 index 00000000000..062cdf08cbb --- /dev/null +++ b/tests/end-to-end/event/_files/PhpunitWarningTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Framework\TestCase; + +final class PhpunitWarningTest extends TestCase +{ + public function testPhpunitWarning(): void + { + EventFacade::emitter()->testTriggeredPhpunitWarning( + $this->valueObjectForEvents(), + 'message', + ); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/PrivateDataProviderTest.php b/tests/end-to-end/event/_files/PrivateDataProviderTest.php new file mode 100644 index 00000000000..e5fe4e178d5 --- /dev/null +++ b/tests/end-to-end/event/_files/PrivateDataProviderTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class PrivateDataProviderTest extends TestCase +{ + #[DataProvider('values')] + public function testSuccess(bool $value): void + { + $this->assertTrue($value); + } + + private static function values(): array + { + return [[true], [true]]; + } +} diff --git a/tests/end-to-end/event/_files/RiskyBecauseGlobalStateModificationTest.php b/tests/end-to-end/event/_files/RiskyBecauseGlobalStateModificationTest.php new file mode 100644 index 00000000000..589e74333a6 --- /dev/null +++ b/tests/end-to-end/event/_files/RiskyBecauseGlobalStateModificationTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\TestCase; + +final class RiskyBecauseGlobalStateModificationTest extends TestCase +{ + public function testOne(): void + { + $GLOBALS['variable'] = 'value'; + } +} diff --git a/tests/end-to-end/event/_files/RiskyBecauseNoAssertionsTest.php b/tests/end-to-end/event/_files/RiskyBecauseNoAssertionsTest.php new file mode 100644 index 00000000000..93964490b29 --- /dev/null +++ b/tests/end-to-end/event/_files/RiskyBecauseNoAssertionsTest.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\TestCase; + +final class RiskyBecauseNoAssertionsTest extends TestCase +{ + public function testOne(): void + { + } +} diff --git a/tests/end-to-end/event/_files/RiskyBecauseOutputTest.php b/tests/end-to-end/event/_files/RiskyBecauseOutputTest.php new file mode 100644 index 00000000000..6bd415b4809 --- /dev/null +++ b/tests/end-to-end/event/_files/RiskyBecauseOutputTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\TestCase; + +final class RiskyBecauseOutputTest extends TestCase +{ + public function testOne(): void + { + print '*'; + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/RiskyBecauseTimeLimitExceededTest.php b/tests/end-to-end/event/_files/RiskyBecauseTimeLimitExceededTest.php new file mode 100644 index 00000000000..05f6a2adae5 --- /dev/null +++ b/tests/end-to-end/event/_files/RiskyBecauseTimeLimitExceededTest.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use function sleep; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[Small] +final class RiskyBecauseTimeLimitExceededTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + + sleep(2); + } +} diff --git a/tests/end-to-end/event/_files/RiskyWithMultipleReasonsTest.php b/tests/end-to-end/event/_files/RiskyWithMultipleReasonsTest.php new file mode 100644 index 00000000000..f722a94d198 --- /dev/null +++ b/tests/end-to-end/event/_files/RiskyWithMultipleReasonsTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\TestCase; + +final class RiskyWithMultipleReasonsTest extends TestCase +{ + public function testOne(): void + { + $GLOBALS['variable'] = 'value'; + } +} diff --git a/tests/end-to-end/event/_files/SeparateProcessesTest.php b/tests/end-to-end/event/_files/SeparateProcessesTest.php new file mode 100644 index 00000000000..549af0818b0 --- /dev/null +++ b/tests/end-to-end/event/_files/SeparateProcessesTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; +use PHPUnit\Framework\TestCase; + +#[RunTestsInSeparateProcesses] +final class SeparateProcessesTest extends TestCase +{ + public function testOne(): array + { + exit; + } +} diff --git a/tests/end-to-end/event/_files/SkippedInSetupBeforeClassTest.php b/tests/end-to-end/event/_files/SkippedInSetupBeforeClassTest.php new file mode 100644 index 00000000000..0e53c97fe72 --- /dev/null +++ b/tests/end-to-end/event/_files/SkippedInSetupBeforeClassTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\TestCase; + +final class SkippedInSetupBeforeClassTest extends TestCase +{ + public static function setUpBeforeClass(): void + { + self::markTestSkipped('message'); + } + + public function testOne(): void + { + } +} diff --git a/tests/end-to-end/event/_files/SkippedInSetupTest.php b/tests/end-to-end/event/_files/SkippedInSetupTest.php new file mode 100644 index 00000000000..c9e81f53193 --- /dev/null +++ b/tests/end-to-end/event/_files/SkippedInSetupTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\TestCase; + +final class SkippedInSetupTest extends TestCase +{ + protected function setUp(): void + { + $this->markTestSkipped(); + } + + public function testOne(): void + { + } +} diff --git a/tests/end-to-end/event/_files/SkippedTest.php b/tests/end-to-end/event/_files/SkippedTest.php new file mode 100644 index 00000000000..916188c2112 --- /dev/null +++ b/tests/end-to-end/event/_files/SkippedTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\TestCase; + +final class SkippedTest extends TestCase +{ + public function testSkipped(): void + { + $this->markTestSkipped('message'); + } +} diff --git a/tests/end-to-end/event/_files/StubTest.php b/tests/end-to-end/event/_files/StubTest.php new file mode 100644 index 00000000000..e33bdd4d4a7 --- /dev/null +++ b/tests/end-to-end/event/_files/StubTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\TestCase; + +final class StubTest extends TestCase +{ + public function testSuccess(): void + { + $this->createStub(Example::class); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/SuccessTest.php b/tests/end-to-end/event/_files/SuccessTest.php new file mode 100644 index 00000000000..75211879366 --- /dev/null +++ b/tests/end-to-end/event/_files/SuccessTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\TestCase; + +final class SuccessTest extends TestCase +{ + public function testSuccess(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/SuccessfulExpectationTest.php b/tests/end-to-end/event/_files/SuccessfulExpectationTest.php new file mode 100644 index 00000000000..0a4d3c9e2ac --- /dev/null +++ b/tests/end-to-end/event/_files/SuccessfulExpectationTest.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\MockObject\AnInterface; + +final class SuccessfulExpectationTest extends TestCase +{ + public function testOne(): void + { + $mock = $this->createMock(AnInterface::class); + + $mock->expects($this->once())->method('doSomething'); + + $mock->doSomething(); + } +} diff --git a/tests/end-to-end/event/_files/SuppressedUserNoticeTest.php b/tests/end-to-end/event/_files/SuppressedUserNoticeTest.php new file mode 100644 index 00000000000..d280987cd2b --- /dev/null +++ b/tests/end-to-end/event/_files/SuppressedUserNoticeTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use const E_USER_NOTICE; +use function error_get_last; +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class SuppressedUserNoticeTest extends TestCase +{ + public function testSuppressedUserNotice(): void + { + $this->assertTrue(true); + + @trigger_error('message', E_USER_NOTICE); + } + + public function testSuppressedUserNoticeErrorGetLast(): void + { + $this->assertNull(error_get_last()); + @trigger_error('message', E_USER_NOTICE); + $this->assertIsArray(error_get_last()); + } +} diff --git a/tests/end-to-end/event/_files/SuppressedUserWarningTest.php b/tests/end-to-end/event/_files/SuppressedUserWarningTest.php new file mode 100644 index 00000000000..d6c362f9cab --- /dev/null +++ b/tests/end-to-end/event/_files/SuppressedUserWarningTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use const E_USER_WARNING; +use function error_get_last; +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class SuppressedUserWarningTest extends TestCase +{ + public function testSuppressedUserWarning(): void + { + $this->assertTrue(true); + + @trigger_error('message', E_USER_WARNING); + } + + public function testSuppressedUserWarningErrorGetLast(): void + { + $this->assertNull(error_get_last()); + @trigger_error('message', E_USER_WARNING); + $this->assertIsArray(error_get_last()); + } +} diff --git a/tests/end-to-end/event/_files/TemplateMethodsTest.php b/tests/end-to-end/event/_files/TemplateMethodsTest.php new file mode 100644 index 00000000000..b1ee82505b5 --- /dev/null +++ b/tests/end-to-end/event/_files/TemplateMethodsTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\TestCase; + +final class TemplateMethodsTest extends TestCase +{ + public static function setUpBeforeClass(): void + { + } + + public static function tearDownAfterClass(): void + { + } + + protected function setUp(): void + { + } + + protected function assertPreConditions(): void + { + } + + protected function assertPostConditions(): void + { + } + + protected function tearDown(): void + { + } + + public function testOne(): void + { + $this->assertTrue(true); + } + + public function testTwo(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/UnsatisfiedRequirementBeforeClassMethodTest.php b/tests/end-to-end/event/_files/UnsatisfiedRequirementBeforeClassMethodTest.php new file mode 100644 index 00000000000..ad0b2c412e0 --- /dev/null +++ b/tests/end-to-end/event/_files/UnsatisfiedRequirementBeforeClassMethodTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\Attributes\RequiresPhp; +use PHPUnit\Framework\TestCase; + +final class UnsatisfiedRequirementBeforeClassMethodTest extends TestCase +{ + #[RequiresPhp('^100.0')] + public static function setUpBeforeClass(): void + { + } + + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/UnsatisfiedRequirementClassTest.php b/tests/end-to-end/event/_files/UnsatisfiedRequirementClassTest.php new file mode 100644 index 00000000000..5013d5c16bc --- /dev/null +++ b/tests/end-to-end/event/_files/UnsatisfiedRequirementClassTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\Attributes\RequiresPhp; +use PHPUnit\Framework\TestCase; + +#[RequiresPhp('^100.0')] +final class UnsatisfiedRequirementClassTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/UnsatisfiedRequirementMethodTest.php b/tests/end-to-end/event/_files/UnsatisfiedRequirementMethodTest.php new file mode 100644 index 00000000000..636e901f94d --- /dev/null +++ b/tests/end-to-end/event/_files/UnsatisfiedRequirementMethodTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\Attributes\RequiresPhp; +use PHPUnit\Framework\TestCase; + +final class UnsatisfiedRequirementMethodTest extends TestCase +{ + #[RequiresPhp('^100.0')] + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/UserErrorTest.php b/tests/end-to-end/event/_files/UserErrorTest.php new file mode 100644 index 00000000000..62ea7d170f8 --- /dev/null +++ b/tests/end-to-end/event/_files/UserErrorTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use const E_USER_ERROR; +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class UserErrorTest extends TestCase +{ + public function testUserError(): void + { + $this->assertTrue(true); + + trigger_error('message', E_USER_ERROR); + } + + public function testUserErrorMustAbortExecution(): void + { + trigger_error('message', E_USER_ERROR); + $this->assertTrue(false); + } +} diff --git a/tests/end-to-end/event/_files/UserNoticeTest.php b/tests/end-to-end/event/_files/UserNoticeTest.php new file mode 100644 index 00000000000..17046c78f5c --- /dev/null +++ b/tests/end-to-end/event/_files/UserNoticeTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use const E_USER_NOTICE; +use function error_get_last; +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class UserNoticeTest extends TestCase +{ + public function testUserNotice(): void + { + $this->assertTrue(true); + + trigger_error('message', E_USER_NOTICE); + } + + public function testUserNoticeErrorGetLast(): void + { + $this->assertNull(error_get_last()); + trigger_error('message', E_USER_NOTICE); + $this->assertIsArray(error_get_last()); + } +} diff --git a/tests/end-to-end/event/_files/UserWarningTest.php b/tests/end-to-end/event/_files/UserWarningTest.php new file mode 100644 index 00000000000..21b232ee50a --- /dev/null +++ b/tests/end-to-end/event/_files/UserWarningTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use const E_USER_WARNING; +use function error_get_last; +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class UserWarningTest extends TestCase +{ + public function testUserWarning(): void + { + $this->assertTrue(true); + + trigger_error('message', E_USER_WARNING); + } + + public function testUserWarningErrorGetLast(): void + { + $this->assertNull(error_get_last()); + trigger_error('message', E_USER_WARNING); + $this->assertIsArray(error_get_last()); + } +} diff --git a/tests/end-to-end/event/_files/XdebugIsDisabled.php b/tests/end-to-end/event/_files/XdebugIsDisabled.php new file mode 100644 index 00000000000..e2cb32d2e92 --- /dev/null +++ b/tests/end-to-end/event/_files/XdebugIsDisabled.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use function extension_loaded; +use function ini_get; +use PHPUnit\Framework\TestCase; + +final class XdebugIsDisabled extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(extension_loaded('xdebug')); + $this->assertSame('', (string) ini_get('xdebug.mode')); + } +} diff --git a/tests/end-to-end/event/_files/custom-error-handler-registered-in-bootstrap-is-not-overwritten-by-phpunit/bootstrap.php b/tests/end-to-end/event/_files/custom-error-handler-registered-in-bootstrap-is-not-overwritten-by-phpunit/bootstrap.php new file mode 100644 index 00000000000..fc1f5dc3f29 --- /dev/null +++ b/tests/end-to-end/event/_files/custom-error-handler-registered-in-bootstrap-is-not-overwritten-by-phpunit/bootstrap.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\ErrorHandlerIsNotOverwritten; + +use function set_error_handler; + +set_error_handler( + static function (int $errorNumber, string $errorString, string $errorFile, int $errorLine): bool + { + return true; + }, +); diff --git a/tests/end-to-end/event/_files/custom-error-handler-registered-in-bootstrap-is-not-overwritten-by-phpunit/phpunit.xml b/tests/end-to-end/event/_files/custom-error-handler-registered-in-bootstrap-is-not-overwritten-by-phpunit/phpunit.xml new file mode 100644 index 00000000000..0e0f270cea9 --- /dev/null +++ b/tests/end-to-end/event/_files/custom-error-handler-registered-in-bootstrap-is-not-overwritten-by-phpunit/phpunit.xml @@ -0,0 +1,11 @@ + + + + + tests + + + diff --git a/tests/end-to-end/event/_files/custom-error-handler-registered-in-bootstrap-is-not-overwritten-by-phpunit/tests/ExampleTest.php b/tests/end-to-end/event/_files/custom-error-handler-registered-in-bootstrap-is-not-overwritten-by-phpunit/tests/ExampleTest.php new file mode 100644 index 00000000000..fa5b3fc3d34 --- /dev/null +++ b/tests/end-to-end/event/_files/custom-error-handler-registered-in-bootstrap-is-not-overwritten-by-phpunit/tests/ExampleTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\ErrorHandlerIsNotOverwritten; + +use const E_USER_DEPRECATED; +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class ExampleTest extends TestCase +{ + public function testOne(): void + { + trigger_error('message', E_USER_DEPRECATED); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/custom-failure-interface/CustomFailureException.php b/tests/end-to-end/event/_files/custom-failure-interface/CustomFailureException.php new file mode 100644 index 00000000000..ab913bcc3c7 --- /dev/null +++ b/tests/end-to-end/event/_files/custom-failure-interface/CustomFailureException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use Exception; + +final class CustomFailureException extends Exception implements CustomFailureInterface +{ +} diff --git a/tests/end-to-end/event/_files/custom-failure-interface/CustomFailureInterface.php b/tests/end-to-end/event/_files/custom-failure-interface/CustomFailureInterface.php new file mode 100644 index 00000000000..b8bb4e120b8 --- /dev/null +++ b/tests/end-to-end/event/_files/custom-failure-interface/CustomFailureInterface.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +interface CustomFailureInterface +{ +} diff --git a/tests/end-to-end/event/_files/custom-failure-interface/CustomFailureInterfaceTest.php b/tests/end-to-end/event/_files/custom-failure-interface/CustomFailureInterfaceTest.php new file mode 100644 index 00000000000..2b1d52cf967 --- /dev/null +++ b/tests/end-to-end/event/_files/custom-failure-interface/CustomFailureInterfaceTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use PHPUnit\Framework\TestCase; + +final class CustomFailureInterfaceTest extends TestCase +{ + public function testOne(): void + { + $this->registerFailureType(CustomFailureInterface::class); + + $this->assertTrue(true); + + throw new CustomFailureException('this should be treated as a failure'); + } + + public function testTwo(): void + { + throw new CustomFailureException('this should be treated as an error'); + } +} diff --git a/tests/end-to-end/event/_files/custom-failure-interface/bootstrap.php b/tests/end-to-end/event/_files/custom-failure-interface/bootstrap.php new file mode 100644 index 00000000000..560c65452b7 --- /dev/null +++ b/tests/end-to-end/event/_files/custom-failure-interface/bootstrap.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +require __DIR__ . '/CustomFailureInterface.php'; + +require __DIR__ . '/CustomFailureException.php'; diff --git a/tests/end-to-end/event/_files/error-handler-can-be-disabled/phpunit.xml b/tests/end-to-end/event/_files/error-handler-can-be-disabled/phpunit.xml new file mode 100644 index 00000000000..07bc4a06a1a --- /dev/null +++ b/tests/end-to-end/event/_files/error-handler-can-be-disabled/phpunit.xml @@ -0,0 +1,14 @@ + + + + + tests + + + diff --git a/tests/end-to-end/event/_files/error-handler-can-be-disabled/src/Foo.php b/tests/end-to-end/event/_files/error-handler-can-be-disabled/src/Foo.php new file mode 100644 index 00000000000..d503df1b221 --- /dev/null +++ b/tests/end-to-end/event/_files/error-handler-can-be-disabled/src/Foo.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\ErrorHandlerCanBeDisabled; + +use const E_USER_WARNING; +use function error_get_last; +use function fopen; +use function trigger_error; +use Exception; + +final class Foo +{ + public function methodA($fileName) + { + $stream_handle = @fopen($fileName, 'wb'); + + if ($stream_handle === false) { + $error = error_get_last(); + + throw new Exception($error['message']); + } + + return $stream_handle; + } + + public function methodB(): ?array + { + @trigger_error('Triggering', E_USER_WARNING); + + return error_get_last(); + } +} diff --git a/tests/end-to-end/event/_files/error-handler-can-be-disabled/tests/FooTest.php b/tests/end-to-end/event/_files/error-handler-can-be-disabled/tests/FooTest.php new file mode 100644 index 00000000000..79222867423 --- /dev/null +++ b/tests/end-to-end/event/_files/error-handler-can-be-disabled/tests/FooTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\ErrorHandlerCanBeDisabled; + +use function restore_error_handler; +use function set_error_handler; +use function sys_get_temp_dir; +use function tempnam; +use Exception; +use PHPUnit\Framework\Attributes\WithoutErrorHandler; +use PHPUnit\Framework\TestCase; + +final class FooTest extends TestCase +{ + #[WithoutErrorHandler] + public function testMethodA(): void + { + $fileName = tempnam(sys_get_temp_dir(), 'RLT') . '/missing/directory'; + + $this->expectException(Exception::class); + $this->expectExceptionMessage('Failed to open stream'); + + (new Foo)->methodA($fileName); + } + + #[WithoutErrorHandler] + public function testMethodB(): void + { + $this->assertSame('Triggering', (new Foo)->methodB()['message']); + } + + public function testErrorHandlerSet(): void + { + $this->assertIsCallable($this->getErrorHandler()); + } + + #[WithoutErrorHandler] + public function testErrorHandlerIsNotSet(): void + { + $this->assertNull($this->getErrorHandler()); + } + + /** + * @return null|callable + */ + private function getErrorHandler() + { + $res = set_error_handler(static fn () => false); + restore_error_handler(); + + return $res; + } +} diff --git a/tests/end-to-end/event/_files/invalid-coverage-metadata/phpunit.xml b/tests/end-to-end/event/_files/invalid-coverage-metadata/phpunit.xml new file mode 100644 index 00000000000..c05484a928c --- /dev/null +++ b/tests/end-to-end/event/_files/invalid-coverage-metadata/phpunit.xml @@ -0,0 +1,16 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/event/_files/invalid-coverage-metadata/src/Foo.php b/tests/end-to-end/event/_files/invalid-coverage-metadata/src/Foo.php new file mode 100644 index 00000000000..44f5b812404 --- /dev/null +++ b/tests/end-to-end/event/_files/invalid-coverage-metadata/src/Foo.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\InvalidCoverageMetadata; + +final class Foo +{ +} diff --git a/tests/end-to-end/event/_files/invalid-coverage-metadata/tests/InvalidCoverageMetadataTest.php b/tests/end-to-end/event/_files/invalid-coverage-metadata/tests/InvalidCoverageMetadataTest.php new file mode 100644 index 00000000000..1e6dec4f4ab --- /dev/null +++ b/tests/end-to-end/event/_files/invalid-coverage-metadata/tests/InvalidCoverageMetadataTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\InvalidCoverageMetadata; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass('PHPUnit\TestFixture\Event\InvalidCoverageMetadata\This\Does\Not\Exist')] +final class InvalidCoverageMetadataTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/test-risky-depends-on-larger-test/LargeTest.php b/tests/end-to-end/event/_files/test-risky-depends-on-larger-test/LargeTest.php new file mode 100644 index 00000000000..567ed9a398b --- /dev/null +++ b/tests/end-to-end/event/_files/test-risky-depends-on-larger-test/LargeTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\RiskyBecauseDependencyOnLargerTest; + +use PHPUnit\Framework\Attributes\Large; +use PHPUnit\Framework\TestCase; + +#[Large] +final class LargeTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/_files/test-risky-depends-on-larger-test/SmallTest.php b/tests/end-to-end/event/_files/test-risky-depends-on-larger-test/SmallTest.php new file mode 100644 index 00000000000..96a072a2e8e --- /dev/null +++ b/tests/end-to-end/event/_files/test-risky-depends-on-larger-test/SmallTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\RiskyBecauseDependencyOnLargerTest; + +use PHPUnit\Framework\Attributes\DependsExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[Small] +final class SmallTest extends TestCase +{ + #[DependsExternal(LargeTest::class, 'testOne')] + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/event/additional-information.phpt b/tests/end-to-end/event/additional-information.phpt new file mode 100644 index 00000000000..70d17f5fe45 --- /dev/null +++ b/tests/end-to-end/event/additional-information.phpt @@ -0,0 +1,31 @@ +--TEST-- +The right events are emitted in the right order for a successful test that provides additional information +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\AdditionalInformationTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\AdditionalInformationTest::testSuccess) +Test Prepared (PHPUnit\TestFixture\Event\AdditionalInformationTest::testSuccess) +Test Provided Additional Information +additional information +Test Passed (PHPUnit\TestFixture\Event\AdditionalInformationTest::testSuccess) +Test Finished (PHPUnit\TestFixture\Event\AdditionalInformationTest::testSuccess) +Test Suite Finished (PHPUnit\TestFixture\Event\AdditionalInformationTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/assert-failure.phpt b/tests/end-to-end/event/assert-failure.phpt new file mode 100644 index 00000000000..16c437ca968 --- /dev/null +++ b/tests/end-to-end/event/assert-failure.phpt @@ -0,0 +1,35 @@ +--TEST-- +The right events are emitted in the right order for a test that fails because of assert() +--SKIPIF-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\AssertTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\AssertTest::testAssert) +Test Prepared (PHPUnit\TestFixture\Event\AssertTest::testAssert) +Test Failed (PHPUnit\TestFixture\Event\AssertTest::testAssert) +assert(false) +Test Finished (PHPUnit\TestFixture\Event\AssertTest::testAssert) +Test Suite Finished (PHPUnit\TestFixture\Event\AssertTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/event/assertion-failure-in-after-class-method.phpt b/tests/end-to-end/event/assertion-failure-in-after-class-method.phpt new file mode 100644 index 00000000000..535f4ff9dda --- /dev/null +++ b/tests/end-to-end/event/assertion-failure-in-after-class-method.phpt @@ -0,0 +1,34 @@ +--TEST-- +The right events are emitted in the right order for a test that fails because of an assertion failure in a "after last test" method +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\AssertionFailureInTearDownAfterClassTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\AssertionFailureInTearDownAfterClassTest::testOne) +Test Prepared (PHPUnit\TestFixture\Event\AssertionFailureInTearDownAfterClassTest::testOne) +Test Passed (PHPUnit\TestFixture\Event\AssertionFailureInTearDownAfterClassTest::testOne) +Test Finished (PHPUnit\TestFixture\Event\AssertionFailureInTearDownAfterClassTest::testOne) +After Last Test Method Called (PHPUnit\TestFixture\Event\AssertionFailureInTearDownAfterClassTest::tearDownAfterClass) +After Last Test Method Failed (PHPUnit\TestFixture\Event\AssertionFailureInTearDownAfterClassTest::tearDownAfterClass) +Failed asserting that false is true. +After Last Test Method Finished: +- PHPUnit\TestFixture\Event\AssertionFailureInTearDownAfterClassTest::tearDownAfterClass +Test Suite Finished (PHPUnit\TestFixture\Event\AssertionFailureInTearDownAfterClassTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/event/assertion-failure-in-after-test-method.phpt b/tests/end-to-end/event/assertion-failure-in-after-test-method.phpt new file mode 100644 index 00000000000..a192920dece --- /dev/null +++ b/tests/end-to-end/event/assertion-failure-in-after-test-method.phpt @@ -0,0 +1,35 @@ +--TEST-- +The right events are emitted in the right order for a test that fails because of an assertion failure in a "after test" method +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\AssertionFailureInTearDownTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\AssertionFailureInTearDownTest::testOne) +Test Prepared (PHPUnit\TestFixture\Event\AssertionFailureInTearDownTest::testOne) +After Test Method Called (PHPUnit\TestFixture\Event\AssertionFailureInTearDownTest::afterTest) +After Test Method Failed (PHPUnit\TestFixture\Event\AssertionFailureInTearDownTest::afterTest) +Failed asserting that false is true. +After Test Method Finished: +- PHPUnit\TestFixture\Event\AssertionFailureInTearDownTest::afterTest +Test Failed (PHPUnit\TestFixture\Event\AssertionFailureInTearDownTest::testOne) +Failed asserting that false is true. +Test Finished (PHPUnit\TestFixture\Event\AssertionFailureInTearDownTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\Event\AssertionFailureInTearDownTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/event/assertion-failure-in-before-class-method.phpt b/tests/end-to-end/event/assertion-failure-in-before-class-method.phpt new file mode 100644 index 00000000000..7e47bb8cf35 --- /dev/null +++ b/tests/end-to-end/event/assertion-failure-in-before-class-method.phpt @@ -0,0 +1,30 @@ +--TEST-- +The right events are emitted in the right order for a test that fails because of an assertion failure in a "before first test" method +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\AssertionFailureInSetUpBeforeClassTest, 1 test) +Before First Test Method Called (PHPUnit\TestFixture\Event\AssertionFailureInSetUpBeforeClassTest::setUpBeforeClass) +Before First Test Method Failed (PHPUnit\TestFixture\Event\AssertionFailureInSetUpBeforeClassTest::setUpBeforeClass) +Failed asserting that false is true. +Before First Test Method Finished: +- PHPUnit\TestFixture\Event\AssertionFailureInSetUpBeforeClassTest::setUpBeforeClass +Test Suite Finished (PHPUnit\TestFixture\Event\AssertionFailureInSetUpBeforeClassTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/event/assertion-failure-in-before-test-method.phpt b/tests/end-to-end/event/assertion-failure-in-before-test-method.phpt new file mode 100644 index 00000000000..9d20fe9c771 --- /dev/null +++ b/tests/end-to-end/event/assertion-failure-in-before-test-method.phpt @@ -0,0 +1,36 @@ +--TEST-- +The right events are emitted in the right order for a test that fails because of an assertion failure in a "before test" method +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\AssertionFailureInSetUpTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\AssertionFailureInSetUpTest::testOne) +Before Test Method Called (PHPUnit\TestFixture\Event\AssertionFailureInSetUpTest::beforeTest) +Before Test Method Failed (PHPUnit\TestFixture\Event\AssertionFailureInSetUpTest::beforeTest) +Failed asserting that false is true. +Before Test Method Finished: +- PHPUnit\TestFixture\Event\AssertionFailureInSetUpTest::beforeTest +Test Preparation Failed (PHPUnit\TestFixture\Event\AssertionFailureInSetUpTest::testOne) +Failed asserting that false is true. +Test Failed (PHPUnit\TestFixture\Event\AssertionFailureInSetUpTest::testOne) +Failed asserting that false is true. +Test Finished (PHPUnit\TestFixture\Event\AssertionFailureInSetUpTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\Event\AssertionFailureInSetUpTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/event/assertion-failure-in-postcondition-method.phpt b/tests/end-to-end/event/assertion-failure-in-postcondition-method.phpt new file mode 100644 index 00000000000..faf026b1c90 --- /dev/null +++ b/tests/end-to-end/event/assertion-failure-in-postcondition-method.phpt @@ -0,0 +1,35 @@ +--TEST-- +The right events are emitted in the right order for a test that fails because of an assertion failure in a "post condition" method +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\AssertionFailureInPostConditionTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\AssertionFailureInPostConditionTest::testOne) +Test Prepared (PHPUnit\TestFixture\Event\AssertionFailureInPostConditionTest::testOne) +Post Condition Method Called (PHPUnit\TestFixture\Event\AssertionFailureInPostConditionTest::postCondition) +Post Condition Method Failed (PHPUnit\TestFixture\Event\AssertionFailureInPostConditionTest::postCondition) +Failed asserting that false is true. +Post Condition Method Finished: +- PHPUnit\TestFixture\Event\AssertionFailureInPostConditionTest::postCondition +Test Failed (PHPUnit\TestFixture\Event\AssertionFailureInPostConditionTest::testOne) +Failed asserting that false is true. +Test Finished (PHPUnit\TestFixture\Event\AssertionFailureInPostConditionTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\Event\AssertionFailureInPostConditionTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/event/assertion-failure-in-precondition-method.phpt b/tests/end-to-end/event/assertion-failure-in-precondition-method.phpt new file mode 100644 index 00000000000..d02904f0d26 --- /dev/null +++ b/tests/end-to-end/event/assertion-failure-in-precondition-method.phpt @@ -0,0 +1,36 @@ +--TEST-- +The right events are emitted in the right order for a test that fails because of an assertion failure in a "pre condition" method +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\AssertionFailureInPreConditionTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\AssertionFailureInPreConditionTest::testOne) +Pre Condition Method Called (PHPUnit\TestFixture\Event\AssertionFailureInPreConditionTest::preCondition) +Pre Condition Method Failed (PHPUnit\TestFixture\Event\AssertionFailureInPreConditionTest::preCondition) +Failed asserting that false is true. +Pre Condition Method Finished: +- PHPUnit\TestFixture\Event\AssertionFailureInPreConditionTest::preCondition +Test Preparation Failed (PHPUnit\TestFixture\Event\AssertionFailureInPreConditionTest::testOne) +Failed asserting that false is true. +Test Failed (PHPUnit\TestFixture\Event\AssertionFailureInPreConditionTest::testOne) +Failed asserting that false is true. +Test Finished (PHPUnit\TestFixture\Event\AssertionFailureInPreConditionTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\Event\AssertionFailureInPreConditionTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/event/assertion-failure-in-test-method.phpt b/tests/end-to-end/event/assertion-failure-in-test-method.phpt new file mode 100644 index 00000000000..e74e57fde9e --- /dev/null +++ b/tests/end-to-end/event/assertion-failure-in-test-method.phpt @@ -0,0 +1,30 @@ +--TEST-- +The right events are emitted in the right order for a test that fails because of an assertion failure in the test method +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\FailureTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\FailureTest::testFailure) +Test Prepared (PHPUnit\TestFixture\Event\FailureTest::testFailure) +Test Failed (PHPUnit\TestFixture\Event\FailureTest::testFailure) +Failed asserting that false is true. +Test Finished (PHPUnit\TestFixture\Event\FailureTest::testFailure) +Test Suite Finished (PHPUnit\TestFixture\Event\FailureTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/event/custom-comparator.phpt b/tests/end-to-end/event/custom-comparator.phpt new file mode 100644 index 00000000000..3665207d1ba --- /dev/null +++ b/tests/end-to-end/event/custom-comparator.phpt @@ -0,0 +1,33 @@ +--TEST-- +The right events are emitted in the right order for a successful test that uses assertEquals() with a custom comparator +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Bootstrap Finished (%sCustomComparator.php) +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\CustomComparatorTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\CustomComparatorTest::testWithCustomComparator) +Test Prepared (PHPUnit\TestFixture\Event\CustomComparatorTest::testWithCustomComparator) +Comparator Registered (PHPUnit\TestFixture\Event\CustomComparator) +Test Passed (PHPUnit\TestFixture\Event\CustomComparatorTest::testWithCustomComparator) +Test Finished (PHPUnit\TestFixture\Event\CustomComparatorTest::testWithCustomComparator) +Test Suite Finished (PHPUnit\TestFixture\Event\CustomComparatorTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/custom-error-handler-registered-in-bootstrap-is-not-overwritten-by-phpunit.phpt b/tests/end-to-end/event/custom-error-handler-registered-in-bootstrap-is-not-overwritten-by-phpunit.phpt new file mode 100644 index 00000000000..f48d3e909bf --- /dev/null +++ b/tests/end-to-end/event/custom-error-handler-registered-in-bootstrap-is-not-overwritten-by-phpunit.phpt @@ -0,0 +1,35 @@ +--TEST-- +A custom error handler registered in the test suite's bootstrap script using set_error_handler() is not overwritten by PHPUnit by default +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Bootstrap Finished (%sbootstrap.php) +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (%sphpunit.xml, 1 test) +Test Suite Started (default, 1 test) +Test Suite Started (PHPUnit\TestFixture\Event\ErrorHandlerIsNotOverwritten\ExampleTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\ErrorHandlerIsNotOverwritten\ExampleTest::testOne) +Test Prepared (PHPUnit\TestFixture\Event\ErrorHandlerIsNotOverwritten\ExampleTest::testOne) +Test Passed (PHPUnit\TestFixture\Event\ErrorHandlerIsNotOverwritten\ExampleTest::testOne) +Test Finished (PHPUnit\TestFixture\Event\ErrorHandlerIsNotOverwritten\ExampleTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\Event\ErrorHandlerIsNotOverwritten\ExampleTest, 1 test) +Test Suite Finished (default, 1 test) +Test Suite Finished (%sphpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/data-provider-does-not-return-iterable.phpt b/tests/end-to-end/event/data-provider-does-not-return-iterable.phpt new file mode 100644 index 00000000000..ae8ac38e6c7 --- /dev/null +++ b/tests/end-to-end/event/data-provider-does-not-return-iterable.phpt @@ -0,0 +1,30 @@ +--TEST-- +The right events are emitted in the right order for a test that uses a data provider that does not return an iterable +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Data Provider Method Called (PHPUnit\TestFixture\Event\DataProviderNotIterableTest::provider for test method PHPUnit\TestFixture\Event\DataProviderNotIterableTest::testSomething) +Data Provider Method Finished for PHPUnit\TestFixture\Event\DataProviderNotIterableTest::testSomething: +- PHPUnit\TestFixture\Event\DataProviderNotIterableTest::provider +Test Triggered PHPUnit Error (PHPUnit\TestFixture\Event\DataProviderNotIterableTest::testSomething) +The data provider PHPUnit\TestFixture\Event\DataProviderNotIterableTest::provider specified for PHPUnit\TestFixture\Event\DataProviderNotIterableTest::testSomething is invalid +Data Provider method PHPUnit\TestFixture\Event\DataProviderNotIterableTest::provider() does not return an iterable +Test Runner Triggered Warning (No tests found in class "PHPUnit\TestFixture\Event\DataProviderNotIterableTest".) +Test Suite Loaded (0 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (0 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/event/data-provider-duplicate-key.phpt b/tests/end-to-end/event/data-provider-duplicate-key.phpt new file mode 100644 index 00000000000..2fc6b652a59 --- /dev/null +++ b/tests/end-to-end/event/data-provider-duplicate-key.phpt @@ -0,0 +1,30 @@ +--TEST-- +The right events are emitted in the right order for a test that uses a data provider that provides data with duplicate keys +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Data Provider Method Called (PHPUnit\TestFixture\Event\DataProviderDuplicateKeyTest::provider for test method PHPUnit\TestFixture\Event\DataProviderDuplicateKeyTest::testSomething) +Data Provider Method Finished for PHPUnit\TestFixture\Event\DataProviderDuplicateKeyTest::testSomething: +- PHPUnit\TestFixture\Event\DataProviderDuplicateKeyTest::provider +Test Triggered PHPUnit Error (PHPUnit\TestFixture\Event\DataProviderDuplicateKeyTest::testSomething) +The data provider specified for PHPUnit\TestFixture\Event\DataProviderDuplicateKeyTest::testSomething is invalid +The key "key" has already been defined by provider PHPUnit\TestFixture\Event\DataProviderDuplicateKeyTest::provider +Test Runner Triggered Warning (No tests found in class "PHPUnit\TestFixture\Event\DataProviderDuplicateKeyTest".) +Test Suite Loaded (0 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (0 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/event/data-provider-empty.phpt b/tests/end-to-end/event/data-provider-empty.phpt new file mode 100644 index 00000000000..83c6f4c54af --- /dev/null +++ b/tests/end-to-end/event/data-provider-empty.phpt @@ -0,0 +1,30 @@ +--TEST-- +The right events are emitted in the right order for a test that uses a data provider that provides no data +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Data Provider Method Called (PHPUnit\TestFixture\Event\EmptyDataProviderTest::providerMethod for test method PHPUnit\TestFixture\Event\EmptyDataProviderTest::testCase) +Data Provider Method Finished for PHPUnit\TestFixture\Event\EmptyDataProviderTest::testCase: +- PHPUnit\TestFixture\Event\EmptyDataProviderTest::providerMethod +Test Triggered PHPUnit Error (PHPUnit\TestFixture\Event\EmptyDataProviderTest::testCase) +The data provider specified for PHPUnit\TestFixture\Event\EmptyDataProviderTest::testCase is invalid +Empty data set provided by data provider +Test Runner Triggered Warning (No tests found in class "PHPUnit\TestFixture\Event\EmptyDataProviderTest".) +Test Suite Loaded (0 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (0 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/event/data-provider-exception.phpt b/tests/end-to-end/event/data-provider-exception.phpt new file mode 100644 index 00000000000..23c1d97ad46 --- /dev/null +++ b/tests/end-to-end/event/data-provider-exception.phpt @@ -0,0 +1,30 @@ +--TEST-- +The right events are emitted in the right order for a test that uses a data provider that raises an exception +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Data Provider Method Called (PHPUnit\TestFixture\Event\ExceptionInDataProviderTest::provider for test method PHPUnit\TestFixture\Event\ExceptionInDataProviderTest::testOne) +Data Provider Method Finished for PHPUnit\TestFixture\Event\ExceptionInDataProviderTest::testOne: +- PHPUnit\TestFixture\Event\ExceptionInDataProviderTest::provider +Test Triggered PHPUnit Error (PHPUnit\TestFixture\Event\ExceptionInDataProviderTest::testOne) +The data provider PHPUnit\TestFixture\Event\ExceptionInDataProviderTest::provider specified for PHPUnit\TestFixture\Event\ExceptionInDataProviderTest::testOne is invalid +message +Test Runner Triggered Warning (No tests found in class "PHPUnit\TestFixture\Event\ExceptionInDataProviderTest".) +Test Suite Loaded (0 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (0 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/event/data-provider-expects-argument.phpt b/tests/end-to-end/event/data-provider-expects-argument.phpt new file mode 100644 index 00000000000..1fddf9a4251 --- /dev/null +++ b/tests/end-to-end/event/data-provider-expects-argument.phpt @@ -0,0 +1,30 @@ +--TEST-- +The right events are emitted in the right order for a test that uses a data provider that expects an argument +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Data Provider Method Called (PHPUnit\TestFixture\Event\ArgumentDataProviderTest::values for test method PHPUnit\TestFixture\Event\ArgumentDataProviderTest::testSuccess) +Data Provider Method Finished for PHPUnit\TestFixture\Event\ArgumentDataProviderTest::testSuccess: +- PHPUnit\TestFixture\Event\ArgumentDataProviderTest::values +Test Triggered PHPUnit Error (PHPUnit\TestFixture\Event\ArgumentDataProviderTest::testSuccess) +The data provider PHPUnit\TestFixture\Event\ArgumentDataProviderTest::values specified for PHPUnit\TestFixture\Event\ArgumentDataProviderTest::testSuccess is invalid +Data Provider method PHPUnit\TestFixture\Event\ArgumentDataProviderTest::values() expects an argument +Test Runner Triggered Warning (No tests found in class "PHPUnit\TestFixture\Event\ArgumentDataProviderTest".) +Test Suite Loaded (0 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (0 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/event/data-provider-external.phpt b/tests/end-to-end/event/data-provider-external.phpt new file mode 100644 index 00000000000..03ff9791292 --- /dev/null +++ b/tests/end-to-end/event/data-provider-external.phpt @@ -0,0 +1,41 @@ +--TEST-- +The right events are emitted in the right order for a successful test that uses a data provider method in a different class +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Bootstrap Finished (%sDataProvider.php) +Event Facade Sealed +Data Provider Method Called (PHPUnit\TestFixture\Event\DataProvider::values for test method PHPUnit\TestFixture\Event\DataProviderExternalTest::testSuccess) +Data Provider Method Finished for PHPUnit\TestFixture\Event\DataProviderExternalTest::testSuccess: +- PHPUnit\TestFixture\Event\DataProvider::values +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\Event\DataProviderExternalTest, 2 tests) +Test Suite Started (PHPUnit\TestFixture\Event\DataProviderExternalTest::testSuccess, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\Event\DataProviderExternalTest::testSuccess#0) +Test Prepared (PHPUnit\TestFixture\Event\DataProviderExternalTest::testSuccess#0) +Test Passed (PHPUnit\TestFixture\Event\DataProviderExternalTest::testSuccess#0) +Test Finished (PHPUnit\TestFixture\Event\DataProviderExternalTest::testSuccess#0) +Test Preparation Started (PHPUnit\TestFixture\Event\DataProviderExternalTest::testSuccess#1) +Test Prepared (PHPUnit\TestFixture\Event\DataProviderExternalTest::testSuccess#1) +Test Passed (PHPUnit\TestFixture\Event\DataProviderExternalTest::testSuccess#1) +Test Finished (PHPUnit\TestFixture\Event\DataProviderExternalTest::testSuccess#1) +Test Suite Finished (PHPUnit\TestFixture\Event\DataProviderExternalTest::testSuccess, 2 tests) +Test Suite Finished (PHPUnit\TestFixture\Event\DataProviderExternalTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/data-provider-in-parent-class.phpt b/tests/end-to-end/event/data-provider-in-parent-class.phpt new file mode 100644 index 00000000000..9a96a36329c --- /dev/null +++ b/tests/end-to-end/event/data-provider-in-parent-class.phpt @@ -0,0 +1,34 @@ +--TEST-- +The right events are emitted in the right order for a test that uses a data provider that provides no data +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Data Provider Method Called (PHPUnit\TestFixture\Event\DataProviderInParentTest::data_provider for test method PHPUnit\TestFixture\Event\DataProviderInParentTest::testSomething) +Data Provider Method Finished for PHPUnit\TestFixture\Event\DataProviderInParentTest::testSomething: +- PHPUnit\TestFixture\Event\DataProviderInParentTest::data_provider +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\DataProviderInParentTest, 1 test) +Test Suite Started (PHPUnit\TestFixture\Event\DataProviderInParentTest::testSomething, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\DataProviderInParentTest::testSomething#0) +Test Prepared (PHPUnit\TestFixture\Event\DataProviderInParentTest::testSomething#0) +Test Passed (PHPUnit\TestFixture\Event\DataProviderInParentTest::testSomething#0) +Test Finished (PHPUnit\TestFixture\Event\DataProviderInParentTest::testSomething#0) +Test Suite Finished (PHPUnit\TestFixture\Event\DataProviderInParentTest::testSomething, 1 test) +Test Suite Finished (PHPUnit\TestFixture\Event\DataProviderInParentTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/data-provider-invalid-argument-name.phpt b/tests/end-to-end/event/data-provider-invalid-argument-name.phpt new file mode 100644 index 00000000000..d5b8b946f5b --- /dev/null +++ b/tests/end-to-end/event/data-provider-invalid-argument-name.phpt @@ -0,0 +1,39 @@ +--TEST-- +The right events are emitted in the right order for a test that uses a data provider that provides data with invalid argument names +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Data Provider Method Called (PHPUnit\TestFixture\Event\InvalidParameterNameDataProviderTest::values for test method PHPUnit\TestFixture\Event\InvalidParameterNameDataProviderTest::testSuccess) +Data Provider Method Finished for PHPUnit\TestFixture\Event\InvalidParameterNameDataProviderTest::testSuccess: +- PHPUnit\TestFixture\Event\InvalidParameterNameDataProviderTest::values +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\Event\InvalidParameterNameDataProviderTest, 2 tests) +Test Suite Started (PHPUnit\TestFixture\Event\InvalidParameterNameDataProviderTest::testSuccess, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\Event\InvalidParameterNameDataProviderTest::testSuccess#0) +Test Prepared (PHPUnit\TestFixture\Event\InvalidParameterNameDataProviderTest::testSuccess#0) +Test Passed (PHPUnit\TestFixture\Event\InvalidParameterNameDataProviderTest::testSuccess#0) +Test Finished (PHPUnit\TestFixture\Event\InvalidParameterNameDataProviderTest::testSuccess#0) +Test Preparation Started (PHPUnit\TestFixture\Event\InvalidParameterNameDataProviderTest::testSuccess#1) +Test Prepared (PHPUnit\TestFixture\Event\InvalidParameterNameDataProviderTest::testSuccess#1) +Test Errored (PHPUnit\TestFixture\Event\InvalidParameterNameDataProviderTest::testSuccess#1) +Unknown named parameter $value3 +Test Finished (PHPUnit\TestFixture\Event\InvalidParameterNameDataProviderTest::testSuccess#1) +Test Suite Finished (PHPUnit\TestFixture\Event\InvalidParameterNameDataProviderTest::testSuccess, 2 tests) +Test Suite Finished (PHPUnit\TestFixture\Event\InvalidParameterNameDataProviderTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/event/data-provider-invalid-key.phpt b/tests/end-to-end/event/data-provider-invalid-key.phpt new file mode 100644 index 00000000000..2ef8fbc2554 --- /dev/null +++ b/tests/end-to-end/event/data-provider-invalid-key.phpt @@ -0,0 +1,30 @@ +--TEST-- +The right events are emitted in the right order for a test that uses a data provider that provides data with invalid keys +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Data Provider Method Called (PHPUnit\TestFixture\Event\DataProviderInvalidKeyTest::provider for test method PHPUnit\TestFixture\Event\DataProviderInvalidKeyTest::testOne) +Data Provider Method Finished for PHPUnit\TestFixture\Event\DataProviderInvalidKeyTest::testOne: +- PHPUnit\TestFixture\Event\DataProviderInvalidKeyTest::provider +Test Triggered PHPUnit Error (PHPUnit\TestFixture\Event\DataProviderInvalidKeyTest::testOne) +The data provider specified for PHPUnit\TestFixture\Event\DataProviderInvalidKeyTest::testOne is invalid +The key must be an integer or a string, float given +Test Runner Triggered Warning (No tests found in class "PHPUnit\TestFixture\Event\DataProviderInvalidKeyTest".) +Test Suite Loaded (0 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (0 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/event/data-provider-not-public.phpt b/tests/end-to-end/event/data-provider-not-public.phpt new file mode 100644 index 00000000000..647b4fac6e8 --- /dev/null +++ b/tests/end-to-end/event/data-provider-not-public.phpt @@ -0,0 +1,30 @@ +--TEST-- +The right events are emitted in the right order for a test that uses a data provider that is not public +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Data Provider Method Called (PHPUnit\TestFixture\Event\PrivateDataProviderTest::values for test method PHPUnit\TestFixture\Event\PrivateDataProviderTest::testSuccess) +Data Provider Method Finished for PHPUnit\TestFixture\Event\PrivateDataProviderTest::testSuccess: +- PHPUnit\TestFixture\Event\PrivateDataProviderTest::values +Test Triggered PHPUnit Error (PHPUnit\TestFixture\Event\PrivateDataProviderTest::testSuccess) +The data provider PHPUnit\TestFixture\Event\PrivateDataProviderTest::values specified for PHPUnit\TestFixture\Event\PrivateDataProviderTest::testSuccess is invalid +Data Provider method PHPUnit\TestFixture\Event\PrivateDataProviderTest::values() is not public +Test Runner Triggered Warning (No tests found in class "PHPUnit\TestFixture\Event\PrivateDataProviderTest".) +Test Suite Loaded (0 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (0 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/event/data-provider-not-static.phpt b/tests/end-to-end/event/data-provider-not-static.phpt new file mode 100644 index 00000000000..dd1ee7fceba --- /dev/null +++ b/tests/end-to-end/event/data-provider-not-static.phpt @@ -0,0 +1,30 @@ +--TEST-- +The right events are emitted in the right order for a test that uses a data provider that is not static +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Data Provider Method Called (PHPUnit\TestFixture\Event\DynamicDataProviderTest::values for test method PHPUnit\TestFixture\Event\DynamicDataProviderTest::testSuccess) +Data Provider Method Finished for PHPUnit\TestFixture\Event\DynamicDataProviderTest::testSuccess: +- PHPUnit\TestFixture\Event\DynamicDataProviderTest::values +Test Triggered PHPUnit Error (PHPUnit\TestFixture\Event\DynamicDataProviderTest::testSuccess) +The data provider PHPUnit\TestFixture\Event\DynamicDataProviderTest::values specified for PHPUnit\TestFixture\Event\DynamicDataProviderTest::testSuccess is invalid +Data Provider method PHPUnit\TestFixture\Event\DynamicDataProviderTest::values() is not static +Test Runner Triggered Warning (No tests found in class "PHPUnit\TestFixture\Event\DynamicDataProviderTest".) +Test Suite Loaded (0 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (0 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/event/data-provider.phpt b/tests/end-to-end/event/data-provider.phpt new file mode 100644 index 00000000000..f50444ec77a --- /dev/null +++ b/tests/end-to-end/event/data-provider.phpt @@ -0,0 +1,38 @@ +--TEST-- +The right events are emitted in the right order for a successful test that uses a data provider +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Data Provider Method Called (PHPUnit\TestFixture\Event\DataProviderTest::values for test method PHPUnit\TestFixture\Event\DataProviderTest::testSuccess) +Data Provider Method Finished for PHPUnit\TestFixture\Event\DataProviderTest::testSuccess: +- PHPUnit\TestFixture\Event\DataProviderTest::values +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\Event\DataProviderTest, 2 tests) +Test Suite Started (PHPUnit\TestFixture\Event\DataProviderTest::testSuccess, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\Event\DataProviderTest::testSuccess#0) +Test Prepared (PHPUnit\TestFixture\Event\DataProviderTest::testSuccess#0) +Test Passed (PHPUnit\TestFixture\Event\DataProviderTest::testSuccess#0) +Test Finished (PHPUnit\TestFixture\Event\DataProviderTest::testSuccess#0) +Test Preparation Started (PHPUnit\TestFixture\Event\DataProviderTest::testSuccess#1) +Test Prepared (PHPUnit\TestFixture\Event\DataProviderTest::testSuccess#1) +Test Passed (PHPUnit\TestFixture\Event\DataProviderTest::testSuccess#1) +Test Finished (PHPUnit\TestFixture\Event\DataProviderTest::testSuccess#1) +Test Suite Finished (PHPUnit\TestFixture\Event\DataProviderTest::testSuccess, 2 tests) +Test Suite Finished (PHPUnit\TestFixture\Event\DataProviderTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/deprecations-can-be-ignored-using-attribute-and-pattern.phpt b/tests/end-to-end/event/deprecations-can-be-ignored-using-attribute-and-pattern.phpt new file mode 100644 index 00000000000..1fb255508be --- /dev/null +++ b/tests/end-to-end/event/deprecations-can-be-ignored-using-attribute-and-pattern.phpt @@ -0,0 +1,28 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5532 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: PHP %s + +..D. 4 / 4 (100%) + +Time: %s, Memory: %s MB + +1 test triggered 1 deprecation: + +1) %sIgnoreDeprecationsWithPatternTest.php:%d +baz + +OK, but there were issues! +Tests: 4, Assertions: 4, Deprecations: 1. diff --git a/tests/end-to-end/event/deprecations-can-be-ignored-using-attribute.phpt b/tests/end-to-end/event/deprecations-can-be-ignored-using-attribute.phpt new file mode 100644 index 00000000000..b0dc9e7b433 --- /dev/null +++ b/tests/end-to-end/event/deprecations-can-be-ignored-using-attribute.phpt @@ -0,0 +1,49 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5532 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (4 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (4 tests) +Test Suite Started (PHPUnit\TestFixture\Event\IgnoreDeprecationsTest, 4 tests) +Test Preparation Started (PHPUnit\TestFixture\Event\IgnoreDeprecationsTest::testOne) +Test Prepared (PHPUnit\TestFixture\Event\IgnoreDeprecationsTest::testOne) +Test Triggered Deprecation (PHPUnit\TestFixture\Event\IgnoreDeprecationsTest::testOne, unknown if issue was triggered in first-party code or third-party code, ignored by test) in %s:%d +message +Test Passed (PHPUnit\TestFixture\Event\IgnoreDeprecationsTest::testOne) +Test Finished (PHPUnit\TestFixture\Event\IgnoreDeprecationsTest::testOne) +Test Preparation Started (PHPUnit\TestFixture\Event\IgnoreDeprecationsTest::testTwo) +Test Prepared (PHPUnit\TestFixture\Event\IgnoreDeprecationsTest::testTwo) +Test Triggered Deprecation (PHPUnit\TestFixture\Event\IgnoreDeprecationsTest::testTwo, unknown if issue was triggered in first-party code or third-party code) in %s:%d +message +Test Passed (PHPUnit\TestFixture\Event\IgnoreDeprecationsTest::testTwo) +Test Finished (PHPUnit\TestFixture\Event\IgnoreDeprecationsTest::testTwo) +Test Preparation Started (PHPUnit\TestFixture\Event\IgnoreDeprecationsTest::testOneErrorGetLast) +Test Prepared (PHPUnit\TestFixture\Event\IgnoreDeprecationsTest::testOneErrorGetLast) +Test Triggered Deprecation (PHPUnit\TestFixture\Event\IgnoreDeprecationsTest::testOneErrorGetLast, unknown if issue was triggered in first-party code or third-party code, ignored by test) in %s:%d +message +Test Passed (PHPUnit\TestFixture\Event\IgnoreDeprecationsTest::testOneErrorGetLast) +Test Finished (PHPUnit\TestFixture\Event\IgnoreDeprecationsTest::testOneErrorGetLast) +Test Preparation Started (PHPUnit\TestFixture\Event\IgnoreDeprecationsTest::testTwoErrorGetLast) +Test Prepared (PHPUnit\TestFixture\Event\IgnoreDeprecationsTest::testTwoErrorGetLast) +Test Triggered Deprecation (PHPUnit\TestFixture\Event\IgnoreDeprecationsTest::testTwoErrorGetLast, unknown if issue was triggered in first-party code or third-party code) in %s:%d +message +Test Passed (PHPUnit\TestFixture\Event\IgnoreDeprecationsTest::testTwoErrorGetLast) +Test Finished (PHPUnit\TestFixture\Event\IgnoreDeprecationsTest::testTwoErrorGetLast) +Test Suite Finished (PHPUnit\TestFixture\Event\IgnoreDeprecationsTest, 4 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/duplicated-cli-options.phpt b/tests/end-to-end/event/duplicated-cli-options.phpt new file mode 100644 index 00000000000..02a599c5764 --- /dev/null +++ b/tests/end-to-end/event/duplicated-cli-options.phpt @@ -0,0 +1,31 @@ +--TEST-- +The right events are emitted in the right order when duplicated CLI options are used +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Triggered Warning (Option --filter cannot be used more than once) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (0 tests) +Test Runner Execution Started (0 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/event/error-handler-can-be-disabled.phpt b/tests/end-to-end/event/error-handler-can-be-disabled.phpt new file mode 100644 index 00000000000..f3609a41a2b --- /dev/null +++ b/tests/end-to-end/event/error-handler-can-be-disabled.phpt @@ -0,0 +1,47 @@ +--TEST-- +The right events are emitted in the right order when PHPUnit's error handler is disabled +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Bootstrap Finished (%s%esrc/Foo.php) +Event Facade Sealed +Test Suite Loaded (4 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (4 tests) +Test Suite Started (%s%ephpunit.xml, 4 tests) +Test Suite Started (default, 4 tests) +Test Suite Started (PHPUnit\TestFixture\Event\ErrorHandlerCanBeDisabled\FooTest, 4 tests) +Test Preparation Started (PHPUnit\TestFixture\Event\ErrorHandlerCanBeDisabled\FooTest::testMethodA) +Test Prepared (PHPUnit\TestFixture\Event\ErrorHandlerCanBeDisabled\FooTest::testMethodA) +Test Passed (PHPUnit\TestFixture\Event\ErrorHandlerCanBeDisabled\FooTest::testMethodA) +Test Finished (PHPUnit\TestFixture\Event\ErrorHandlerCanBeDisabled\FooTest::testMethodA) +Test Preparation Started (PHPUnit\TestFixture\Event\ErrorHandlerCanBeDisabled\FooTest::testMethodB) +Test Prepared (PHPUnit\TestFixture\Event\ErrorHandlerCanBeDisabled\FooTest::testMethodB) +Test Passed (PHPUnit\TestFixture\Event\ErrorHandlerCanBeDisabled\FooTest::testMethodB) +Test Finished (PHPUnit\TestFixture\Event\ErrorHandlerCanBeDisabled\FooTest::testMethodB) +Test Preparation Started (PHPUnit\TestFixture\Event\ErrorHandlerCanBeDisabled\FooTest::testErrorHandlerSet) +Test Prepared (PHPUnit\TestFixture\Event\ErrorHandlerCanBeDisabled\FooTest::testErrorHandlerSet) +Test Passed (PHPUnit\TestFixture\Event\ErrorHandlerCanBeDisabled\FooTest::testErrorHandlerSet) +Test Finished (PHPUnit\TestFixture\Event\ErrorHandlerCanBeDisabled\FooTest::testErrorHandlerSet) +Test Preparation Started (PHPUnit\TestFixture\Event\ErrorHandlerCanBeDisabled\FooTest::testErrorHandlerIsNotSet) +Test Prepared (PHPUnit\TestFixture\Event\ErrorHandlerCanBeDisabled\FooTest::testErrorHandlerIsNotSet) +Test Passed (PHPUnit\TestFixture\Event\ErrorHandlerCanBeDisabled\FooTest::testErrorHandlerIsNotSet) +Test Finished (PHPUnit\TestFixture\Event\ErrorHandlerCanBeDisabled\FooTest::testErrorHandlerIsNotSet) +Test Suite Finished (PHPUnit\TestFixture\Event\ErrorHandlerCanBeDisabled\FooTest, 4 tests) +Test Suite Finished (default, 4 tests) +Test Suite Finished (%s%ephpunit.xml, 4 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/error.phpt b/tests/end-to-end/event/error.phpt new file mode 100644 index 00000000000..76a965154af --- /dev/null +++ b/tests/end-to-end/event/error.phpt @@ -0,0 +1,30 @@ +--TEST-- +The right events are emitted in the right order for an errored test +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\ErrorTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\ErrorTest::testError) +Test Prepared (PHPUnit\TestFixture\Event\ErrorTest::testError) +Test Errored (PHPUnit\TestFixture\Event\ErrorTest::testError) +message +Test Finished (PHPUnit\TestFixture\Event\ErrorTest::testError) +Test Suite Finished (PHPUnit\TestFixture\Event\ErrorTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/event/exception-in-after-class-method.phpt b/tests/end-to-end/event/exception-in-after-class-method.phpt new file mode 100644 index 00000000000..668310abad9 --- /dev/null +++ b/tests/end-to-end/event/exception-in-after-class-method.phpt @@ -0,0 +1,33 @@ +--TEST-- +The right events are emitted in the right order for when an exception is raised in tearDownAfterClass() +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\ExceptionInTearDownAfterClassTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\ExceptionInTearDownAfterClassTest::testOne) +Test Prepared (PHPUnit\TestFixture\Event\ExceptionInTearDownAfterClassTest::testOne) +Test Passed (PHPUnit\TestFixture\Event\ExceptionInTearDownAfterClassTest::testOne) +Test Finished (PHPUnit\TestFixture\Event\ExceptionInTearDownAfterClassTest::testOne) +After Last Test Method Called (PHPUnit\TestFixture\Event\ExceptionInTearDownAfterClassTest::tearDownAfterClass) +After Last Test Method Errored (PHPUnit\TestFixture\Event\ExceptionInTearDownAfterClassTest::tearDownAfterClass) +After Last Test Method Finished: +- PHPUnit\TestFixture\Event\ExceptionInTearDownAfterClassTest::tearDownAfterClass +Test Suite Finished (PHPUnit\TestFixture\Event\ExceptionInTearDownAfterClassTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/event/exception-in-after-test-method.phpt b/tests/end-to-end/event/exception-in-after-test-method.phpt new file mode 100644 index 00000000000..4a38944c3d9 --- /dev/null +++ b/tests/end-to-end/event/exception-in-after-test-method.phpt @@ -0,0 +1,33 @@ +--TEST-- +The right events are emitted in the right order for when an exception is raised in tearDown() +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\ExceptionInTearDownTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\ExceptionInTearDownTest::testOne) +Test Prepared (PHPUnit\TestFixture\Event\ExceptionInTearDownTest::testOne) +After Test Method Called (PHPUnit\TestFixture\Event\ExceptionInTearDownTest::tearDown) +After Test Method Errored (PHPUnit\TestFixture\Event\ExceptionInTearDownTest::tearDown) +After Test Method Finished: +- PHPUnit\TestFixture\Event\ExceptionInTearDownTest::tearDown +Test Errored (PHPUnit\TestFixture\Event\ExceptionInTearDownTest::testOne) +Test Finished (PHPUnit\TestFixture\Event\ExceptionInTearDownTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\Event\ExceptionInTearDownTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/event/exception-in-before-class-method.phpt b/tests/end-to-end/event/exception-in-before-class-method.phpt new file mode 100644 index 00000000000..72278600bbb --- /dev/null +++ b/tests/end-to-end/event/exception-in-before-class-method.phpt @@ -0,0 +1,29 @@ +--TEST-- +The right events are emitted in the right order for when an exception is raised in setUpBeforeClass() +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\ExceptionInSetUpBeforeClassTest, 1 test) +Before First Test Method Called (PHPUnit\TestFixture\Event\ExceptionInSetUpBeforeClassTest::setUpBeforeClass) +Before First Test Method Errored (PHPUnit\TestFixture\Event\ExceptionInSetUpBeforeClassTest::setUpBeforeClass) +Before First Test Method Finished: +- PHPUnit\TestFixture\Event\ExceptionInSetUpBeforeClassTest::setUpBeforeClass +Test Suite Finished (PHPUnit\TestFixture\Event\ExceptionInSetUpBeforeClassTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/event/exception-in-before-test-method.phpt b/tests/end-to-end/event/exception-in-before-test-method.phpt new file mode 100644 index 00000000000..48eb284df00 --- /dev/null +++ b/tests/end-to-end/event/exception-in-before-test-method.phpt @@ -0,0 +1,32 @@ +--TEST-- +The right events are emitted in the right order for when an exception is raised in setUp() +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\ExceptionInSetUpTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\ExceptionInSetUpTest::testOne) +Before Test Method Called (PHPUnit\TestFixture\Event\ExceptionInSetUpTest::setUp) +Before Test Method Errored (PHPUnit\TestFixture\Event\ExceptionInSetUpTest::setUp) +Before Test Method Finished: +- PHPUnit\TestFixture\Event\ExceptionInSetUpTest::setUp +Test Preparation Errored (PHPUnit\TestFixture\Event\ExceptionInSetUpTest::testOne) +Test Errored (PHPUnit\TestFixture\Event\ExceptionInSetUpTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\Event\ExceptionInSetUpTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/event/expectation-on-output.phpt b/tests/end-to-end/event/expectation-on-output.phpt new file mode 100644 index 00000000000..55c4d59cdad --- /dev/null +++ b/tests/end-to-end/event/expectation-on-output.phpt @@ -0,0 +1,39 @@ +--TEST-- +The right events are emitted in the right order for a test with an output expectation +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (3 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (3 tests) +Test Suite Started (PHPUnit\TestFixture\Issue445Test, 3 tests) +Test Preparation Started (PHPUnit\TestFixture\Issue445Test::testOutputWithExpectationBefore) +Test Prepared (PHPUnit\TestFixture\Issue445Test::testOutputWithExpectationBefore) +Test Passed (PHPUnit\TestFixture\Issue445Test::testOutputWithExpectationBefore) +Test Finished (PHPUnit\TestFixture\Issue445Test::testOutputWithExpectationBefore) +Test Preparation Started (PHPUnit\TestFixture\Issue445Test::testOutputWithExpectationAfter) +Test Prepared (PHPUnit\TestFixture\Issue445Test::testOutputWithExpectationAfter) +Test Passed (PHPUnit\TestFixture\Issue445Test::testOutputWithExpectationAfter) +Test Finished (PHPUnit\TestFixture\Issue445Test::testOutputWithExpectationAfter) +Test Preparation Started (PHPUnit\TestFixture\Issue445Test::testNotMatchingOutput) +Test Prepared (PHPUnit\TestFixture\Issue445Test::testNotMatchingOutput) +Test Failed (PHPUnit\TestFixture\Issue445Test::testNotMatchingOutput) +Failed asserting that two strings are identical. +Test Finished (PHPUnit\TestFixture\Issue445Test::testNotMatchingOutput) +Test Suite Finished (PHPUnit\TestFixture\Issue445Test, 3 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/event/expected-and-unexpected-assertions.phpt b/tests/end-to-end/event/expected-and-unexpected-assertions.phpt new file mode 100644 index 00000000000..8dc28eea4ab --- /dev/null +++ b/tests/end-to-end/event/expected-and-unexpected-assertions.phpt @@ -0,0 +1,45 @@ +--TEST-- +The right events are emitted in the right order when a test that is not expected to perform assertions does not perform assertions and when a test that is not expected to perform assertions does perform assertions +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (4 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (4 tests) +Test Suite Started (PHPUnit\TestFixture\Event\ExpectedAndUnexpectedAssertionsTest, 4 tests) +Test Preparation Started (PHPUnit\TestFixture\Event\ExpectedAndUnexpectedAssertionsTest::testOne) +Test Prepared (PHPUnit\TestFixture\Event\ExpectedAndUnexpectedAssertionsTest::testOne) +Test Passed (PHPUnit\TestFixture\Event\ExpectedAndUnexpectedAssertionsTest::testOne) +Test Finished (PHPUnit\TestFixture\Event\ExpectedAndUnexpectedAssertionsTest::testOne) +Test Preparation Started (PHPUnit\TestFixture\Event\ExpectedAndUnexpectedAssertionsTest::testTwo) +Test Prepared (PHPUnit\TestFixture\Event\ExpectedAndUnexpectedAssertionsTest::testTwo) +Test Passed (PHPUnit\TestFixture\Event\ExpectedAndUnexpectedAssertionsTest::testTwo) +Test Finished (PHPUnit\TestFixture\Event\ExpectedAndUnexpectedAssertionsTest::testTwo) +Test Preparation Started (PHPUnit\TestFixture\Event\ExpectedAndUnexpectedAssertionsTest::testThree) +Test Prepared (PHPUnit\TestFixture\Event\ExpectedAndUnexpectedAssertionsTest::testThree) +Test Passed (PHPUnit\TestFixture\Event\ExpectedAndUnexpectedAssertionsTest::testThree) +Test Considered Risky (PHPUnit\TestFixture\Event\ExpectedAndUnexpectedAssertionsTest::testThree) +This test is not expected to perform assertions but performed 1 assertion +Test Finished (PHPUnit\TestFixture\Event\ExpectedAndUnexpectedAssertionsTest::testThree) +Test Preparation Started (PHPUnit\TestFixture\Event\ExpectedAndUnexpectedAssertionsTest::testFour) +Test Prepared (PHPUnit\TestFixture\Event\ExpectedAndUnexpectedAssertionsTest::testFour) +Test Passed (PHPUnit\TestFixture\Event\ExpectedAndUnexpectedAssertionsTest::testFour) +Test Considered Risky (PHPUnit\TestFixture\Event\ExpectedAndUnexpectedAssertionsTest::testFour) +This test is not expected to perform assertions but performed 1 assertion +Test Finished (PHPUnit\TestFixture\Event\ExpectedAndUnexpectedAssertionsTest::testFour) +Test Suite Finished (PHPUnit\TestFixture\Event\ExpectedAndUnexpectedAssertionsTest, 4 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/failed-mock-expectation.phpt b/tests/end-to-end/event/failed-mock-expectation.phpt new file mode 100644 index 00000000000..cec67ead8ff --- /dev/null +++ b/tests/end-to-end/event/failed-mock-expectation.phpt @@ -0,0 +1,32 @@ +--TEST-- +The right events are emitted in the right order for a test with a failed expectation on a mock object +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\FailedExpectationTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\FailedExpectationTest::testOne) +Test Prepared (PHPUnit\TestFixture\Event\FailedExpectationTest::testOne) +Mock Object Created (PHPUnit\TestFixture\MockObject\AnInterface) +Test Failed (PHPUnit\TestFixture\Event\FailedExpectationTest::testOne) +Expectation failed for method name is "doSomething" when invoked 1 time. +Method was expected to be called 1 time, actually called 0 times. +Test Finished (PHPUnit\TestFixture\Event\FailedExpectationTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\Event\FailedExpectationTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/event/incomplete-test.phpt b/tests/end-to-end/event/incomplete-test.phpt new file mode 100644 index 00000000000..f7fec9b8e58 --- /dev/null +++ b/tests/end-to-end/event/incomplete-test.phpt @@ -0,0 +1,30 @@ +--TEST-- +The right events are emitted in the right order for an incomplete test +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\IncompleteTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\IncompleteTest::testIncomplete) +Test Prepared (PHPUnit\TestFixture\Event\IncompleteTest::testIncomplete) +Test Marked Incomplete (PHPUnit\TestFixture\Event\IncompleteTest::testIncomplete) +message +Test Finished (PHPUnit\TestFixture\Event\IncompleteTest::testIncomplete) +Test Suite Finished (PHPUnit\TestFixture\Event\IncompleteTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/invalid-coverage-metadata.phpt b/tests/end-to-end/event/invalid-coverage-metadata.phpt new file mode 100644 index 00000000000..8c7418abb58 --- /dev/null +++ b/tests/end-to-end/event/invalid-coverage-metadata.phpt @@ -0,0 +1,48 @@ +--TEST-- +The right events are emitted in the right order for a test that has invalid code coverage metadata +--SKIPIF-- +run($_SERVER['argv']); +--CLEAN-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Data Provider Method Called (PHPUnit\TestFixture\Event\InvalidDataProviderWithOneTestPassingTest::provider for test method PHPUnit\TestFixture\Event\InvalidDataProviderWithOneTestPassingTest::testOne) +Data Provider Method Finished for PHPUnit\TestFixture\Event\InvalidDataProviderWithOneTestPassingTest::testOne: +- PHPUnit\TestFixture\Event\InvalidDataProviderWithOneTestPassingTest::provider +Test Triggered PHPUnit Error (PHPUnit\TestFixture\Event\InvalidDataProviderWithOneTestPassingTest::testOne) +The data provider specified for PHPUnit\TestFixture\Event\InvalidDataProviderWithOneTestPassingTest::testOne is invalid +Data set #0 provided by PHPUnit\TestFixture\Event\InvalidDataProviderWithOneTestPassingTest::provider is invalid, expected array but got int +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\InvalidDataProviderWithOneTestPassingTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\InvalidDataProviderWithOneTestPassingTest::testTwo) +Test Prepared (PHPUnit\TestFixture\Event\InvalidDataProviderWithOneTestPassingTest::testTwo) +Test Passed (PHPUnit\TestFixture\Event\InvalidDataProviderWithOneTestPassingTest::testTwo) +Test Finished (PHPUnit\TestFixture\Event\InvalidDataProviderWithOneTestPassingTest::testTwo) +Test Suite Finished (PHPUnit\TestFixture\Event\InvalidDataProviderWithOneTestPassingTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/event/invalid-data-provider.phpt b/tests/end-to-end/event/invalid-data-provider.phpt new file mode 100644 index 00000000000..5f0aae6607a --- /dev/null +++ b/tests/end-to-end/event/invalid-data-provider.phpt @@ -0,0 +1,30 @@ +--TEST-- +The right events are emitted in the right order for a test that uses a data provider that returns an invalid array +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Data Provider Method Called (PHPUnit\TestFixture\Event\InvalidDataProviderTest::provider for test method PHPUnit\TestFixture\Event\InvalidDataProviderTest::testOne) +Data Provider Method Finished for PHPUnit\TestFixture\Event\InvalidDataProviderTest::testOne: +- PHPUnit\TestFixture\Event\InvalidDataProviderTest::provider +Test Triggered PHPUnit Error (PHPUnit\TestFixture\Event\InvalidDataProviderTest::testOne) +The data provider specified for PHPUnit\TestFixture\Event\InvalidDataProviderTest::testOne is invalid +Data set #0 provided by PHPUnit\TestFixture\Event\InvalidDataProviderTest::provider is invalid, expected array but got int +Test Runner Triggered Warning (No tests found in class "PHPUnit\TestFixture\Event\InvalidDataProviderTest".) +Test Suite Loaded (0 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (0 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/event/invalid-test-dependency.phpt b/tests/end-to-end/event/invalid-test-dependency.phpt new file mode 100644 index 00000000000..1c660608561 --- /dev/null +++ b/tests/end-to-end/event/invalid-test-dependency.phpt @@ -0,0 +1,29 @@ +--TEST-- +The right events are emitted in the right order for a test that has an invalid dependency +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\Event\InvalidDependencyTest, 2 tests) +Test Errored (PHPUnit\TestFixture\Event\InvalidDependencyTest::testOne) +This test depends on "PHPUnit\TestFixture\Event\InvalidDependencyTest::doesNotExist" which does not exist +Test Errored (PHPUnit\TestFixture\Event\InvalidDependencyTest::testTwo) +This test depends on "DoesNotExist" which does not exist +Test Suite Finished (PHPUnit\TestFixture\Event\InvalidDependencyTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/event/invalid-version-constraint.phpt b/tests/end-to-end/event/invalid-version-constraint.phpt new file mode 100644 index 00000000000..ff04400590e --- /dev/null +++ b/tests/end-to-end/event/invalid-version-constraint.phpt @@ -0,0 +1,30 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6356 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Runner Triggered Warning (Test method PHPUnit\TestFixture\Event\InvalidVersionConstraintTest::testOne has attribute with version constraint string argument without explicit version comparison operator ("100"), version constraint is ignored) +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\InvalidVersionConstraintTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\InvalidVersionConstraintTest::testOne) +Test Prepared (PHPUnit\TestFixture\Event\InvalidVersionConstraintTest::testOne) +Test Passed (PHPUnit\TestFixture\Event\InvalidVersionConstraintTest::testOne) +Test Finished (PHPUnit\TestFixture\Event\InvalidVersionConstraintTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\Event\InvalidVersionConstraintTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/event/missing-test-dependency.phpt b/tests/end-to-end/event/missing-test-dependency.phpt new file mode 100644 index 00000000000..6979f3d828c --- /dev/null +++ b/tests/end-to-end/event/missing-test-dependency.phpt @@ -0,0 +1,32 @@ +--TEST-- +The right events are emitted in the right order for a test that has a missing dependency +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\Event\MissingDependencyTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\Event\MissingDependencyTest::testOne) +Test Prepared (PHPUnit\TestFixture\Event\MissingDependencyTest::testOne) +Test Failed (PHPUnit\TestFixture\Event\MissingDependencyTest::testOne) +Failed asserting that false is true. +Test Finished (PHPUnit\TestFixture\Event\MissingDependencyTest::testOne) +Test Skipped (PHPUnit\TestFixture\Event\MissingDependencyTest::testTwo) +This test depends on "PHPUnit\TestFixture\Event\MissingDependencyTest::testOne" to pass +Test Suite Finished (PHPUnit\TestFixture\Event\MissingDependencyTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/event/mock-object-without-expectation.phpt b/tests/end-to-end/event/mock-object-without-expectation.phpt new file mode 100644 index 00000000000..ca00bbb4f70 --- /dev/null +++ b/tests/end-to-end/event/mock-object-without-expectation.phpt @@ -0,0 +1,35 @@ +--TEST-- +The right events are emitted in the right order for a test that uses a mock object +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Bootstrap Finished (%sExample.php) +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (TestFixture\PHPUnit\Event\MockWithoutExpectationTest, 1 test) +Test Preparation Started (TestFixture\PHPUnit\Event\MockWithoutExpectationTest::testSuccess) +Test Prepared (TestFixture\PHPUnit\Event\MockWithoutExpectationTest::testSuccess) +Mock Object Created (PHPUnit\TestFixture\Event\Example) +Test Triggered PHPUnit Notice (TestFixture\PHPUnit\Event\MockWithoutExpectationTest::testSuccess) +No expectations were configured for the mock object for PHPUnit\TestFixture\Event\Example. You should refactor your test code and use a test stub instead. +Test Passed (TestFixture\PHPUnit\Event\MockWithoutExpectationTest::testSuccess) +Test Finished (TestFixture\PHPUnit\Event\MockWithoutExpectationTest::testSuccess) +Test Suite Finished (TestFixture\PHPUnit\Event\MockWithoutExpectationTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/mock-object.phpt b/tests/end-to-end/event/mock-object.phpt new file mode 100644 index 00000000000..a0067688e30 --- /dev/null +++ b/tests/end-to-end/event/mock-object.phpt @@ -0,0 +1,33 @@ +--TEST-- +The right events are emitted in the right order for a test that uses a mock object +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Bootstrap Finished (%sExample.php) +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\MockTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\MockTest::testSuccess) +Test Prepared (PHPUnit\TestFixture\Event\MockTest::testSuccess) +Mock Object Created (PHPUnit\TestFixture\Event\Example) +Test Passed (PHPUnit\TestFixture\Event\MockTest::testSuccess) +Test Finished (PHPUnit\TestFixture\Event\MockTest::testSuccess) +Test Suite Finished (PHPUnit\TestFixture\Event\MockTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/php-deprecated.phpt b/tests/end-to-end/event/php-deprecated.phpt new file mode 100644 index 00000000000..ab649d45e98 --- /dev/null +++ b/tests/end-to-end/event/php-deprecated.phpt @@ -0,0 +1,36 @@ +--TEST-- +The right events are emitted in the right order for a test that runs code which triggers E_DEPRECATED +--INI-- +error_reporting=-1 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\DeprecatedPhpFeatureTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\DeprecatedPhpFeatureTest::testDeprecatedPhpFeature) +Test Prepared (PHPUnit\TestFixture\Event\DeprecatedPhpFeatureTest::testDeprecatedPhpFeature) +Test Triggered PHP Deprecation (PHPUnit\TestFixture\Event\DeprecatedPhpFeatureTest::testDeprecatedPhpFeature, unknown if issue was triggered in first-party code or third-party code) in %s:%d +strlen(): Passing null to parameter #1 ($string) of type string is deprecated +Test Triggered PHP Deprecation (PHPUnit\TestFixture\Event\DeprecatedPhpFeatureTest::testDeprecatedPhpFeature, unknown if issue was triggered in first-party code or third-party code, suppressed using operator) in %s:%d +strlen(): Passing null to parameter #1 ($string) of type string is deprecated +Test Passed (PHPUnit\TestFixture\Event\DeprecatedPhpFeatureTest::testDeprecatedPhpFeature) +Test Finished (PHPUnit\TestFixture\Event\DeprecatedPhpFeatureTest::testDeprecatedPhpFeature) +Test Suite Finished (PHPUnit\TestFixture\Event\DeprecatedPhpFeatureTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/event/php-notice.phpt b/tests/end-to-end/event/php-notice.phpt new file mode 100644 index 00000000000..e998541ec6c --- /dev/null +++ b/tests/end-to-end/event/php-notice.phpt @@ -0,0 +1,34 @@ +--TEST-- +The right events are emitted in the right order for a test that runs code which triggers E_NOTICE +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\PhpNoticeTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\PhpNoticeTest::testPhpNotice) +Test Prepared (PHPUnit\TestFixture\Event\PhpNoticeTest::testPhpNotice) +Test Triggered PHP Notice (PHPUnit\TestFixture\Event\PhpNoticeTest::testPhpNotice) in %s:%d +Only variables should be assigned by reference +Test Triggered PHP Notice (PHPUnit\TestFixture\Event\PhpNoticeTest::testPhpNotice, suppressed using operator) in %s:%d +Only variables should be assigned by reference +Test Passed (PHPUnit\TestFixture\Event\PhpNoticeTest::testPhpNotice) +Test Finished (PHPUnit\TestFixture\Event\PhpNoticeTest::testPhpNotice) +Test Suite Finished (PHPUnit\TestFixture\Event\PhpNoticeTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/event/php-warning.phpt b/tests/end-to-end/event/php-warning.phpt new file mode 100644 index 00000000000..cc312f7ff7e --- /dev/null +++ b/tests/end-to-end/event/php-warning.phpt @@ -0,0 +1,34 @@ +--TEST-- +The right events are emitted in the right order for a test that runs code which triggers E_WARNING +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\PhpWarningTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\PhpWarningTest::testPhpWarning) +Test Prepared (PHPUnit\TestFixture\Event\PhpWarningTest::testPhpWarning) +Test Triggered PHP Warning (PHPUnit\TestFixture\Event\PhpWarningTest::testPhpWarning) in %s:%d +Undefined variable $b +Test Triggered PHP Warning (PHPUnit\TestFixture\Event\PhpWarningTest::testPhpWarning, suppressed using operator) in %s:%d +Undefined variable $b +Test Passed (PHPUnit\TestFixture\Event\PhpWarningTest::testPhpWarning) +Test Finished (PHPUnit\TestFixture\Event\PhpWarningTest::testPhpWarning) +Test Suite Finished (PHPUnit\TestFixture\Event\PhpWarningTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/event/phpt-clean-process-polluting.phpt b/tests/end-to-end/event/phpt-clean-process-polluting.phpt new file mode 100644 index 00000000000..a584b4a443f --- /dev/null +++ b/tests/end-to-end/event/phpt-clean-process-polluting.phpt @@ -0,0 +1,33 @@ +--TEST-- +The right events are emitted in the right order for a PHPT test with a CLEAN section which pollutes the process +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (%sphpt-clean-process-polluting.phpt, 1 test) +Test Preparation Started (%sphpt-clean-process-polluting.phpt) +Test Prepared (%sphpt-clean-process-polluting.phpt) +Child Process Started +Child Process Finished +Test Passed (%sphpt-clean-process-polluting.phpt) +Child Process Started +Child Process Finished +Test Finished (%sphpt-clean-process-polluting.phpt) +Test Suite Finished (%sphpt-clean-process-polluting.phpt, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/phpt-clean-with-io.phpt b/tests/end-to-end/event/phpt-clean-with-io.phpt new file mode 100644 index 00000000000..ab6f09d48b2 --- /dev/null +++ b/tests/end-to-end/event/phpt-clean-with-io.phpt @@ -0,0 +1,31 @@ +--TEST-- +The right events are emitted in the right order for a PHPT test with a CLEAN section which pollutes the process +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (%sphpt-clean-with-io.phpt, 1 test) +Test Preparation Started (%sphpt-clean-with-io.phpt) +Test Prepared (%sphpt-clean-with-io.phpt) +Child Process Started +Child Process Finished +Test Passed (%sphpt-clean-with-io.phpt) +Test Finished (%sphpt-clean-with-io.phpt) +Test Suite Finished (%sphpt-clean-with-io.phpt, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/phpt-clean.phpt b/tests/end-to-end/event/phpt-clean.phpt new file mode 100644 index 00000000000..f023db4ebaf --- /dev/null +++ b/tests/end-to-end/event/phpt-clean.phpt @@ -0,0 +1,31 @@ +--TEST-- +The right events are emitted in the right order for a PHPT test with a CLEAN section +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (%sphpt-clean.phpt, 1 test) +Test Preparation Started (%sphpt-clean.phpt) +Test Prepared (%sphpt-clean.phpt) +Child Process Started +Child Process Finished +Test Passed (%sphpt-clean.phpt) +Test Finished (%sphpt-clean.phpt) +Test Suite Finished (%sphpt-clean.phpt, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/phpt-ini-clean.phpt b/tests/end-to-end/event/phpt-ini-clean.phpt new file mode 100644 index 00000000000..06de7864d46 --- /dev/null +++ b/tests/end-to-end/event/phpt-ini-clean.phpt @@ -0,0 +1,33 @@ +--TEST-- +The right events are emitted in the right order for a PHPT test using a subprocess via --INI-- +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (%s%ephpt-ini-clean.phpt, 1 test) +Test Preparation Started (%s%ephpt-ini-clean.phpt) +Test Prepared (%s%ephpt-ini-clean.phpt) +Child Process Started +Child Process Finished +Test Passed (%sphpt-ini-clean.phpt) +Child Process Started +Child Process Finished +Test Finished (%s%ephpt-ini-clean.phpt) +Test Suite Finished (%s%ephpt-ini-clean.phpt, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/phpt-ini-subprocess.phpt b/tests/end-to-end/event/phpt-ini-subprocess.phpt new file mode 100644 index 00000000000..4231077f803 --- /dev/null +++ b/tests/end-to-end/event/phpt-ini-subprocess.phpt @@ -0,0 +1,33 @@ +--TEST-- +The right events are emitted in the right order for a PHPT test using a subprocess via --INI-- +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (%s%ephpt-ini-subprocess.phpt, 1 test) +Test Preparation Started (%s%ephpt-ini-subprocess.phpt) +Test Prepared (%s%ephpt-ini-subprocess.phpt) +Child Process Started +Child Process Finished +Child Process Started +Child Process Finished +Test Passed (%s%ephpt-ini-subprocess.phpt) +Test Finished (%s%ephpt-ini-subprocess.phpt) +Test Suite Finished (%s%ephpt-ini-subprocess.phpt, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/phpt-skipif-closing-php-tag.phpt b/tests/end-to-end/event/phpt-skipif-closing-php-tag.phpt new file mode 100644 index 00000000000..c4447974aba --- /dev/null +++ b/tests/end-to-end/event/phpt-skipif-closing-php-tag.phpt @@ -0,0 +1,30 @@ +--TEST-- +The right events are emitted in the right order for a skipped PHPT test +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (%s%ephpt-skipif-closing-php-tag.phpt, 1 test) +Test Preparation Started (%s%ephpt-skipif-closing-php-tag.phpt) +Test Prepared (%s%ephpt-skipif-closing-php-tag.phpt) +Test Skipped (%s%ephpt-skipif-closing-php-tag.phpt) +something terrible happened +Test Finished (%s%ephpt-skipif-closing-php-tag.phpt) +Test Suite Finished (%s%ephpt-skipif-closing-php-tag.phpt, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/phpt-skipif-io.phpt b/tests/end-to-end/event/phpt-skipif-io.phpt new file mode 100644 index 00000000000..e16219bec76 --- /dev/null +++ b/tests/end-to-end/event/phpt-skipif-io.phpt @@ -0,0 +1,31 @@ +--TEST-- +The right events are emitted in the right order for PHPT test using IO in skipif +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (%s%ephpt-skipif-io.phpt, 1 test) +Test Preparation Started (%s%ephpt-skipif-io.phpt) +Test Prepared (%s%ephpt-skipif-io.phpt) +Child Process Started +Child Process Finished +Test Passed (%s%ephpt-skipif-io.phpt) +Test Finished (%s%ephpt-skipif-io.phpt) +Test Suite Finished (%s%ephpt-skipif-io.phpt, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/phpt-skipif-require.phpt b/tests/end-to-end/event/phpt-skipif-require.phpt new file mode 100644 index 00000000000..aaf4a57a2f2 --- /dev/null +++ b/tests/end-to-end/event/phpt-skipif-require.phpt @@ -0,0 +1,33 @@ +--TEST-- +The right events are emitted in the right order for PHPT test using require() in skipif +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (%s%ephpt-skipif-require.phpt, 1 test) +Test Preparation Started (%s%ephpt-skipif-require.phpt) +Test Prepared (%s%ephpt-skipif-require.phpt) +Child Process Started +Child Process Finished +Child Process Started +Child Process Finished +Test Passed (%s%ephpt-skipif-require.phpt) +Test Finished (%s%ephpt-skipif-require.phpt) +Test Suite Finished (%s%ephpt-skipif-require.phpt, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/phpt-skipif-subprocess.phpt b/tests/end-to-end/event/phpt-skipif-subprocess.phpt new file mode 100644 index 00000000000..e54c99c158d --- /dev/null +++ b/tests/end-to-end/event/phpt-skipif-subprocess.phpt @@ -0,0 +1,32 @@ +--TEST-- +The right events are emitted in the right order for a skipped PHPT test using a skipif subprocess +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (%s%ephpt-skipif-exit-subprocess.phpt, 1 test) +Test Preparation Started (%s%ephpt-skipif-exit-subprocess.phpt) +Test Prepared (%s%ephpt-skipif-exit-subprocess.phpt) +Child Process Started +Child Process Finished +Test Skipped (%s%ephpt-skipif-exit-subprocess.phpt) +is test +Test Finished (%s%ephpt-skipif-exit-subprocess.phpt) +Test Suite Finished (%s%ephpt-skipif-exit-subprocess.phpt, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/phpt-skipif.phpt b/tests/end-to-end/event/phpt-skipif.phpt new file mode 100644 index 00000000000..f3bb2853bed --- /dev/null +++ b/tests/end-to-end/event/phpt-skipif.phpt @@ -0,0 +1,30 @@ +--TEST-- +The right events are emitted in the right order for a skipped PHPT test +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (%s%ephpt-skipif-location-hint-example.phpt, 1 test) +Test Preparation Started (%s%ephpt-skipif-location-hint-example.phpt) +Test Prepared (%s%ephpt-skipif-location-hint-example.phpt) +Test Skipped (%s%ephpt-skipif-location-hint-example.phpt) +something terrible happened +Test Finished (%s%ephpt-skipif-location-hint-example.phpt) +Test Suite Finished (%s%ephpt-skipif-location-hint-example.phpt, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/phpunit-deprecated-ignored.phpt b/tests/end-to-end/event/phpunit-deprecated-ignored.phpt new file mode 100644 index 00000000000..d7f36d52076 --- /dev/null +++ b/tests/end-to-end/event/phpunit-deprecated-ignored.phpt @@ -0,0 +1,29 @@ +--TEST-- +The right events are emitted in the right order for a test that uses a deprecated PHPUnit feature when PHPUnit deprecations are ignored using attribute +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\IgnoredDeprecatedPhpunitFeatureTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\IgnoredDeprecatedPhpunitFeatureTest::testDeprecatedPhpunitFeature) +Test Prepared (PHPUnit\TestFixture\Event\IgnoredDeprecatedPhpunitFeatureTest::testDeprecatedPhpunitFeature) +Test Passed (PHPUnit\TestFixture\Event\IgnoredDeprecatedPhpunitFeatureTest::testDeprecatedPhpunitFeature) +Test Finished (PHPUnit\TestFixture\Event\IgnoredDeprecatedPhpunitFeatureTest::testDeprecatedPhpunitFeature) +Test Suite Finished (PHPUnit\TestFixture\Event\IgnoredDeprecatedPhpunitFeatureTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/phpunit-deprecated.phpt b/tests/end-to-end/event/phpunit-deprecated.phpt new file mode 100644 index 00000000000..42b74026e6e --- /dev/null +++ b/tests/end-to-end/event/phpunit-deprecated.phpt @@ -0,0 +1,31 @@ +--TEST-- +The right events are emitted in the right order for a test that uses a deprecated PHPUnit feature +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\DeprecatedPhpunitFeatureTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\DeprecatedPhpunitFeatureTest::testDeprecatedPhpunitFeature) +Test Prepared (PHPUnit\TestFixture\Event\DeprecatedPhpunitFeatureTest::testDeprecatedPhpunitFeature) +Test Triggered PHPUnit Deprecation (PHPUnit\TestFixture\Event\DeprecatedPhpunitFeatureTest::testDeprecatedPhpunitFeature) +message +Test Passed (PHPUnit\TestFixture\Event\DeprecatedPhpunitFeatureTest::testDeprecatedPhpunitFeature) +Test Finished (PHPUnit\TestFixture\Event\DeprecatedPhpunitFeatureTest::testDeprecatedPhpunitFeature) +Test Suite Finished (PHPUnit\TestFixture\Event\DeprecatedPhpunitFeatureTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/phpunit-notice.phpt b/tests/end-to-end/event/phpunit-notice.phpt new file mode 100644 index 00000000000..d38ca29310c --- /dev/null +++ b/tests/end-to-end/event/phpunit-notice.phpt @@ -0,0 +1,32 @@ +--TEST-- +The right events are emitted in the right order for a test that runs code which triggers E_USER_WARNING +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\PhpunitNoticeTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\PhpunitNoticeTest::testOne) +Test Prepared (PHPUnit\TestFixture\Event\PhpunitNoticeTest::testOne) +Test Triggered PHPUnit Notice (PHPUnit\TestFixture\Event\PhpunitNoticeTest::testOne) +message +Test Runner Triggered Notice (message) +Test Passed (PHPUnit\TestFixture\Event\PhpunitNoticeTest::testOne) +Test Finished (PHPUnit\TestFixture\Event\PhpunitNoticeTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\Event\PhpunitNoticeTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/phpunit-warning-ignored.phpt b/tests/end-to-end/event/phpunit-warning-ignored.phpt new file mode 100644 index 00000000000..ece9832ac60 --- /dev/null +++ b/tests/end-to-end/event/phpunit-warning-ignored.phpt @@ -0,0 +1,29 @@ +--TEST-- +The right events are emitted in the right order for a test that runs code which triggers a PHPUnit warning +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +....W. 6 / 6 (100%) + +Time: %s, Memory: %s + +1 test triggered 1 PHPUnit warning: + +1) PHPUnit\TestFixture\Event\PhpunitWarningIgnoredTest::testPhpunitWarningWithWrongPattern +another message + +%sPhpunitWarningIgnoredTest.php:%d + +OK, but there were issues! +Tests: 6, Assertions: 6, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/event/phpunit-warning.phpt b/tests/end-to-end/event/phpunit-warning.phpt new file mode 100644 index 00000000000..c21366a43da --- /dev/null +++ b/tests/end-to-end/event/phpunit-warning.phpt @@ -0,0 +1,31 @@ +--TEST-- +The right events are emitted in the right order for a test that runs code which triggers a PHPUnit warning +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\PhpunitWarningTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\PhpunitWarningTest::testPhpunitWarning) +Test Prepared (PHPUnit\TestFixture\Event\PhpunitWarningTest::testPhpunitWarning) +Test Triggered PHPUnit Warning (PHPUnit\TestFixture\Event\PhpunitWarningTest::testPhpunitWarning) +message +Test Passed (PHPUnit\TestFixture\Event\PhpunitWarningTest::testPhpunitWarning) +Test Finished (PHPUnit\TestFixture\Event\PhpunitWarningTest::testPhpunitWarning) +Test Suite Finished (PHPUnit\TestFixture\Event\PhpunitWarningTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/event/process-isolation-disable-xdebug.phpt b/tests/end-to-end/event/process-isolation-disable-xdebug.phpt new file mode 100644 index 00000000000..a3dbefafb1f --- /dev/null +++ b/tests/end-to-end/event/process-isolation-disable-xdebug.phpt @@ -0,0 +1,40 @@ +--TEST-- +Subprocesses auto-disable xdebug when no debugger is attached. +--SKIPIF-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\XdebugIsDisabled, 1 test) +Child Process Started +Test Preparation Started (PHPUnit\TestFixture\Event\XdebugIsDisabled::testOne) +Test Prepared (PHPUnit\TestFixture\Event\XdebugIsDisabled::testOne) +Test Passed (PHPUnit\TestFixture\Event\XdebugIsDisabled::testOne) +Test Finished (PHPUnit\TestFixture\Event\XdebugIsDisabled::testOne) +Child Process Finished +Test Suite Finished (PHPUnit\TestFixture\Event\XdebugIsDisabled, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/process-isolation-fatal.phpt b/tests/end-to-end/event/process-isolation-fatal.phpt new file mode 100644 index 00000000000..f3d71d320a4 --- /dev/null +++ b/tests/end-to-end/event/process-isolation-fatal.phpt @@ -0,0 +1,33 @@ +--TEST-- +The right events are emitted in the right order for a test that is run in process isolation and triggers a fatal error +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\FatalTest, 1 test) +Child Process Started +Test Preparation Started (PHPUnit\TestFixture\Event\FatalTest::testOne) +Test Prepared (PHPUnit\TestFixture\Event\FatalTest::testOne) +Test Errored (PHPUnit\TestFixture\Event\FatalTest::testOne) +Call to undefined function PHPUnit\TestFixture\Event\doesNotExist() +Test Finished (PHPUnit\TestFixture\Event\FatalTest::testOne) +Child Process Finished +Test Suite Finished (PHPUnit\TestFixture\Event\FatalTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/event/registered-failure-interface.phpt b/tests/end-to-end/event/registered-failure-interface.phpt new file mode 100644 index 00000000000..d05df537573 --- /dev/null +++ b/tests/end-to-end/event/registered-failure-interface.phpt @@ -0,0 +1,38 @@ +--TEST-- +The right events are emitted in the right order for a test that registers a failure interface +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Bootstrap Finished (%sbootstrap.php) +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\Event\CustomFailureInterfaceTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\Event\CustomFailureInterfaceTest::testOne) +Test Prepared (PHPUnit\TestFixture\Event\CustomFailureInterfaceTest::testOne) +Test Failed (PHPUnit\TestFixture\Event\CustomFailureInterfaceTest::testOne) +this should be treated as a failure +Test Finished (PHPUnit\TestFixture\Event\CustomFailureInterfaceTest::testOne) +Test Preparation Started (PHPUnit\TestFixture\Event\CustomFailureInterfaceTest::testTwo) +Test Prepared (PHPUnit\TestFixture\Event\CustomFailureInterfaceTest::testTwo) +Test Errored (PHPUnit\TestFixture\Event\CustomFailureInterfaceTest::testTwo) +this should be treated as an error +Test Finished (PHPUnit\TestFixture\Event\CustomFailureInterfaceTest::testTwo) +Test Suite Finished (PHPUnit\TestFixture\Event\CustomFailureInterfaceTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/event/risky-depends-on-larger-test.phpt b/tests/end-to-end/event/risky-depends-on-larger-test.phpt new file mode 100644 index 00000000000..42ae306fdac --- /dev/null +++ b/tests/end-to-end/event/risky-depends-on-larger-test.phpt @@ -0,0 +1,39 @@ +--TEST-- +The right events are emitted in the right order for a test that is considered risky because it depends on a larger test +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (CLI Arguments, 2 tests) +Test Suite Started (PHPUnit\TestFixture\Event\RiskyBecauseDependencyOnLargerTest\LargeTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\RiskyBecauseDependencyOnLargerTest\LargeTest::testOne) +Test Prepared (PHPUnit\TestFixture\Event\RiskyBecauseDependencyOnLargerTest\LargeTest::testOne) +Test Passed (PHPUnit\TestFixture\Event\RiskyBecauseDependencyOnLargerTest\LargeTest::testOne) +Test Finished (PHPUnit\TestFixture\Event\RiskyBecauseDependencyOnLargerTest\LargeTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\Event\RiskyBecauseDependencyOnLargerTest\LargeTest, 1 test) +Test Suite Started (PHPUnit\TestFixture\Event\RiskyBecauseDependencyOnLargerTest\SmallTest, 1 test) +Test Considered Risky (PHPUnit\TestFixture\Event\RiskyBecauseDependencyOnLargerTest\SmallTest::testOne) +This test depends on a test that is larger than itself +Test Preparation Started (PHPUnit\TestFixture\Event\RiskyBecauseDependencyOnLargerTest\SmallTest::testOne) +Test Prepared (PHPUnit\TestFixture\Event\RiskyBecauseDependencyOnLargerTest\SmallTest::testOne) +Test Passed (PHPUnit\TestFixture\Event\RiskyBecauseDependencyOnLargerTest\SmallTest::testOne) +Test Finished (PHPUnit\TestFixture\Event\RiskyBecauseDependencyOnLargerTest\SmallTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\Event\RiskyBecauseDependencyOnLargerTest\SmallTest, 1 test) +Test Suite Finished (CLI Arguments, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/risky-global-state-modification.phpt b/tests/end-to-end/event/risky-global-state-modification.phpt new file mode 100644 index 00000000000..0f7db3330de --- /dev/null +++ b/tests/end-to-end/event/risky-global-state-modification.phpt @@ -0,0 +1,38 @@ +--TEST-- +The right events are emitted in the right order for a test that is considered risky because it modified global state +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\RiskyBecauseGlobalStateModificationTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\RiskyBecauseGlobalStateModificationTest::testOne) +Test Prepared (PHPUnit\TestFixture\Event\RiskyBecauseGlobalStateModificationTest::testOne) +Test Passed (PHPUnit\TestFixture\Event\RiskyBecauseGlobalStateModificationTest::testOne) +Test Considered Risky (PHPUnit\TestFixture\Event\RiskyBecauseGlobalStateModificationTest::testOne) +This test modified global state but was not expected to do so +--- Global variables before the test ++++ Global variables after the test +%A ++ 'variable' => 'value', +%A +Test Finished (PHPUnit\TestFixture\Event\RiskyBecauseGlobalStateModificationTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\Event\RiskyBecauseGlobalStateModificationTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/risky-no-assertions-isolation.phpt b/tests/end-to-end/event/risky-no-assertions-isolation.phpt new file mode 100644 index 00000000000..93390874eaa --- /dev/null +++ b/tests/end-to-end/event/risky-no-assertions-isolation.phpt @@ -0,0 +1,34 @@ +--TEST-- +The right events are emitted in the right order for a test that is run in an isolated process and is considered risky because it did not perform assertions +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\RiskyBecauseNoAssertionsTest, 1 test) +Child Process Started +Test Preparation Started (PHPUnit\TestFixture\Event\RiskyBecauseNoAssertionsTest::testOne) +Test Prepared (PHPUnit\TestFixture\Event\RiskyBecauseNoAssertionsTest::testOne) +Test Passed (PHPUnit\TestFixture\Event\RiskyBecauseNoAssertionsTest::testOne) +Test Considered Risky (PHPUnit\TestFixture\Event\RiskyBecauseNoAssertionsTest::testOne) +This test did not perform any assertions +Test Finished (PHPUnit\TestFixture\Event\RiskyBecauseNoAssertionsTest::testOne) +Child Process Finished +Test Suite Finished (PHPUnit\TestFixture\Event\RiskyBecauseNoAssertionsTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/risky-no-assertions.phpt b/tests/end-to-end/event/risky-no-assertions.phpt new file mode 100644 index 00000000000..e42b0c0110c --- /dev/null +++ b/tests/end-to-end/event/risky-no-assertions.phpt @@ -0,0 +1,31 @@ +--TEST-- +The right events are emitted in the right order for a test that is considered risky because it did not perform assertions +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\RiskyBecauseNoAssertionsTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\RiskyBecauseNoAssertionsTest::testOne) +Test Prepared (PHPUnit\TestFixture\Event\RiskyBecauseNoAssertionsTest::testOne) +Test Passed (PHPUnit\TestFixture\Event\RiskyBecauseNoAssertionsTest::testOne) +Test Considered Risky (PHPUnit\TestFixture\Event\RiskyBecauseNoAssertionsTest::testOne) +This test did not perform any assertions +Test Finished (PHPUnit\TestFixture\Event\RiskyBecauseNoAssertionsTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\Event\RiskyBecauseNoAssertionsTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/risky-output.phpt b/tests/end-to-end/event/risky-output.phpt new file mode 100644 index 00000000000..387cdb97731 --- /dev/null +++ b/tests/end-to-end/event/risky-output.phpt @@ -0,0 +1,34 @@ +--TEST-- +The right events are emitted in the right order for a test that is considered risky because it prints output +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\RiskyBecauseOutputTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\RiskyBecauseOutputTest::testOne) +Test Prepared (PHPUnit\TestFixture\Event\RiskyBecauseOutputTest::testOne) +Test Passed (PHPUnit\TestFixture\Event\RiskyBecauseOutputTest::testOne) +Test Printed Unexpected Output +* +Test Considered Risky (PHPUnit\TestFixture\Event\RiskyBecauseOutputTest::testOne) +Test code or tested code printed unexpected output: * +Test Finished (PHPUnit\TestFixture\Event\RiskyBecauseOutputTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\Event\RiskyBecauseOutputTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/risky-time-limit-exceeded.phpt b/tests/end-to-end/event/risky-time-limit-exceeded.phpt new file mode 100644 index 00000000000..c78d6cc73e1 --- /dev/null +++ b/tests/end-to-end/event/risky-time-limit-exceeded.phpt @@ -0,0 +1,34 @@ +--TEST-- +The right events are emitted in the right order for a test that is considered risky because it timed out +--SKIPIF-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\RiskyBecauseTimeLimitExceededTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\RiskyBecauseTimeLimitExceededTest::testOne) +Test Prepared (PHPUnit\TestFixture\Event\RiskyBecauseTimeLimitExceededTest::testOne) +Test Considered Risky (PHPUnit\TestFixture\Event\RiskyBecauseTimeLimitExceededTest::testOne) +This test was aborted after 1 second +Test Finished (PHPUnit\TestFixture\Event\RiskyBecauseTimeLimitExceededTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\Event\RiskyBecauseTimeLimitExceededTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/risky-with-multiple-reasons.phpt b/tests/end-to-end/event/risky-with-multiple-reasons.phpt new file mode 100644 index 00000000000..60d01435d6d --- /dev/null +++ b/tests/end-to-end/event/risky-with-multiple-reasons.phpt @@ -0,0 +1,40 @@ +--TEST-- +The right events are emitted in the right order for a test that is considered risky for multiple reasons +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\RiskyWithMultipleReasonsTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\RiskyWithMultipleReasonsTest::testOne) +Test Prepared (PHPUnit\TestFixture\Event\RiskyWithMultipleReasonsTest::testOne) +Test Passed (PHPUnit\TestFixture\Event\RiskyWithMultipleReasonsTest::testOne) +Test Considered Risky (PHPUnit\TestFixture\Event\RiskyWithMultipleReasonsTest::testOne) +This test modified global state but was not expected to do so +--- Global variables before the test ++++ Global variables after the test +%A ++ 'variable' => 'value', +%A +Test Considered Risky (PHPUnit\TestFixture\Event\RiskyWithMultipleReasonsTest::testOne) +This test did not perform any assertions +Test Finished (PHPUnit\TestFixture\Event\RiskyWithMultipleReasonsTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\Event\RiskyWithMultipleReasonsTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/success-process-isolation.phpt b/tests/end-to-end/event/success-process-isolation.phpt new file mode 100644 index 00000000000..7b4f2368adc --- /dev/null +++ b/tests/end-to-end/event/success-process-isolation.phpt @@ -0,0 +1,32 @@ +--TEST-- +The right events are emitted in the right order for a successful test that is run in an isolated process +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\SuccessTest, 1 test) +Child Process Started +Test Preparation Started (PHPUnit\TestFixture\Event\SuccessTest::testSuccess) +Test Prepared (PHPUnit\TestFixture\Event\SuccessTest::testSuccess) +Test Passed (PHPUnit\TestFixture\Event\SuccessTest::testSuccess) +Test Finished (PHPUnit\TestFixture\Event\SuccessTest::testSuccess) +Child Process Finished +Test Suite Finished (PHPUnit\TestFixture\Event\SuccessTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/success-verbose.phpt b/tests/end-to-end/event/success-verbose.phpt new file mode 100644 index 00000000000..2f7ba5ed546 --- /dev/null +++ b/tests/end-to-end/event/success-verbose.phpt @@ -0,0 +1,37 @@ +--TEST-- +The right events are emitted in the right order for a successful test with extended information +--FILE-- +run($_SERVER['argv']); + +print file_get_contents($traceFile); + +unlink($traceFile); +--EXPECTF-- +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] PHPUnit Started (PHPUnit %s using %s) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Runner Configured +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Event Facade Sealed +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Suite Loaded (1 test) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Runner Started +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Suite Sorted +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Runner Execution Started (1 test) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Suite Started (PHPUnit\TestFixture\Event\SuccessTest, 1 test) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Preparation Started (PHPUnit\TestFixture\Event\SuccessTest::testSuccess) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Prepared (PHPUnit\TestFixture\Event\SuccessTest::testSuccess) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Passed (PHPUnit\TestFixture\Event\SuccessTest::testSuccess) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Finished (PHPUnit\TestFixture\Event\SuccessTest::testSuccess) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Suite Finished (PHPUnit\TestFixture\Event\SuccessTest, 1 test) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Runner Execution Finished +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Runner Finished +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/success.phpt b/tests/end-to-end/event/success.phpt new file mode 100644 index 00000000000..c326766b72e --- /dev/null +++ b/tests/end-to-end/event/success.phpt @@ -0,0 +1,29 @@ +--TEST-- +The right events are emitted in the right order for a successful test +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\SuccessTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\SuccessTest::testSuccess) +Test Prepared (PHPUnit\TestFixture\Event\SuccessTest::testSuccess) +Test Passed (PHPUnit\TestFixture\Event\SuccessTest::testSuccess) +Test Finished (PHPUnit\TestFixture\Event\SuccessTest::testSuccess) +Test Suite Finished (PHPUnit\TestFixture\Event\SuccessTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/successful-mock-expectation.phpt b/tests/end-to-end/event/successful-mock-expectation.phpt new file mode 100644 index 00000000000..1f7d881f420 --- /dev/null +++ b/tests/end-to-end/event/successful-mock-expectation.phpt @@ -0,0 +1,30 @@ +--TEST-- +The right events are emitted in the right order for a test with a successful expectation on a mock object +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\SuccessfulExpectationTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\SuccessfulExpectationTest::testOne) +Test Prepared (PHPUnit\TestFixture\Event\SuccessfulExpectationTest::testOne) +Mock Object Created (PHPUnit\TestFixture\MockObject\AnInterface) +Test Passed (PHPUnit\TestFixture\Event\SuccessfulExpectationTest::testOne) +Test Finished (PHPUnit\TestFixture\Event\SuccessfulExpectationTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\Event\SuccessfulExpectationTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/suppressed-user-notice.phpt b/tests/end-to-end/event/suppressed-user-notice.phpt new file mode 100644 index 00000000000..192513d5b95 --- /dev/null +++ b/tests/end-to-end/event/suppressed-user-notice.phpt @@ -0,0 +1,37 @@ +--TEST-- +The right events are emitted in the right order for a test that runs code which triggers a suppressed E_USER_NOTICE +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\Event\SuppressedUserNoticeTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\Event\SuppressedUserNoticeTest::testSuppressedUserNotice) +Test Prepared (PHPUnit\TestFixture\Event\SuppressedUserNoticeTest::testSuppressedUserNotice) +Test Triggered Notice (PHPUnit\TestFixture\Event\SuppressedUserNoticeTest::testSuppressedUserNotice, suppressed using operator) in %s:%d +message +Test Passed (PHPUnit\TestFixture\Event\SuppressedUserNoticeTest::testSuppressedUserNotice) +Test Finished (PHPUnit\TestFixture\Event\SuppressedUserNoticeTest::testSuppressedUserNotice) +Test Preparation Started (PHPUnit\TestFixture\Event\SuppressedUserNoticeTest::testSuppressedUserNoticeErrorGetLast) +Test Prepared (PHPUnit\TestFixture\Event\SuppressedUserNoticeTest::testSuppressedUserNoticeErrorGetLast) +Test Triggered Notice (PHPUnit\TestFixture\Event\SuppressedUserNoticeTest::testSuppressedUserNoticeErrorGetLast, suppressed using operator) in %s:%d +message +Test Passed (PHPUnit\TestFixture\Event\SuppressedUserNoticeTest::testSuppressedUserNoticeErrorGetLast) +Test Finished (PHPUnit\TestFixture\Event\SuppressedUserNoticeTest::testSuppressedUserNoticeErrorGetLast) +Test Suite Finished (PHPUnit\TestFixture\Event\SuppressedUserNoticeTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/suppressed-user-warning.phpt b/tests/end-to-end/event/suppressed-user-warning.phpt new file mode 100644 index 00000000000..68906bd5b28 --- /dev/null +++ b/tests/end-to-end/event/suppressed-user-warning.phpt @@ -0,0 +1,37 @@ +--TEST-- +The right events are emitted in the right order for a test that runs code which triggers a suppressed E_USER_WARNING +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\Event\SuppressedUserWarningTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\Event\SuppressedUserWarningTest::testSuppressedUserWarning) +Test Prepared (PHPUnit\TestFixture\Event\SuppressedUserWarningTest::testSuppressedUserWarning) +Test Triggered Warning (PHPUnit\TestFixture\Event\SuppressedUserWarningTest::testSuppressedUserWarning, suppressed using operator) in %s:%d +message +Test Passed (PHPUnit\TestFixture\Event\SuppressedUserWarningTest::testSuppressedUserWarning) +Test Finished (PHPUnit\TestFixture\Event\SuppressedUserWarningTest::testSuppressedUserWarning) +Test Preparation Started (PHPUnit\TestFixture\Event\SuppressedUserWarningTest::testSuppressedUserWarningErrorGetLast) +Test Prepared (PHPUnit\TestFixture\Event\SuppressedUserWarningTest::testSuppressedUserWarningErrorGetLast) +Test Triggered Warning (PHPUnit\TestFixture\Event\SuppressedUserWarningTest::testSuppressedUserWarningErrorGetLast, suppressed using operator) in %s:%d +message +Test Passed (PHPUnit\TestFixture\Event\SuppressedUserWarningTest::testSuppressedUserWarningErrorGetLast) +Test Finished (PHPUnit\TestFixture\Event\SuppressedUserWarningTest::testSuppressedUserWarningErrorGetLast) +Test Suite Finished (PHPUnit\TestFixture\Event\SuppressedUserWarningTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/template-methods-isolation.phpt b/tests/end-to-end/event/template-methods-isolation.phpt new file mode 100644 index 00000000000..ea1761f95c7 --- /dev/null +++ b/tests/end-to-end/event/template-methods-isolation.phpt @@ -0,0 +1,80 @@ +--TEST-- +The right events are emitted in the right order for the template methods of a test class that is run in process isolation +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\Event\TemplateMethodsTest, 2 tests) +Before First Test Method Called (PHPUnit\TestFixture\Event\TemplateMethodsTest::setUpBeforeClass) +Before First Test Method Finished: +- PHPUnit\TestFixture\Event\TemplateMethodsTest::setUpBeforeClass +Child Process Started +Test Preparation Started (PHPUnit\TestFixture\Event\TemplateMethodsTest::testOne) +Before First Test Method Called (PHPUnit\TestFixture\Event\TemplateMethodsTest::setUpBeforeClass) +Before First Test Method Finished: +- PHPUnit\TestFixture\Event\TemplateMethodsTest::setUpBeforeClass +Before Test Method Called (PHPUnit\TestFixture\Event\TemplateMethodsTest::setUp) +Before Test Method Finished: +- PHPUnit\TestFixture\Event\TemplateMethodsTest::setUp +Pre Condition Method Called (PHPUnit\TestFixture\Event\TemplateMethodsTest::assertPreConditions) +Pre Condition Method Finished: +- PHPUnit\TestFixture\Event\TemplateMethodsTest::assertPreConditions +Test Prepared (PHPUnit\TestFixture\Event\TemplateMethodsTest::testOne) +Post Condition Method Called (PHPUnit\TestFixture\Event\TemplateMethodsTest::assertPostConditions) +Post Condition Method Finished: +- PHPUnit\TestFixture\Event\TemplateMethodsTest::assertPostConditions +After Test Method Called (PHPUnit\TestFixture\Event\TemplateMethodsTest::tearDown) +After Test Method Finished: +- PHPUnit\TestFixture\Event\TemplateMethodsTest::tearDown +After Last Test Method Called (PHPUnit\TestFixture\Event\TemplateMethodsTest::tearDownAfterClass) +After Last Test Method Finished: +- PHPUnit\TestFixture\Event\TemplateMethodsTest::tearDownAfterClass +Test Passed (PHPUnit\TestFixture\Event\TemplateMethodsTest::testOne) +Test Finished (PHPUnit\TestFixture\Event\TemplateMethodsTest::testOne) +Child Process Finished +Child Process Started +Test Preparation Started (PHPUnit\TestFixture\Event\TemplateMethodsTest::testTwo) +Before First Test Method Called (PHPUnit\TestFixture\Event\TemplateMethodsTest::setUpBeforeClass) +Before First Test Method Finished: +- PHPUnit\TestFixture\Event\TemplateMethodsTest::setUpBeforeClass +Before Test Method Called (PHPUnit\TestFixture\Event\TemplateMethodsTest::setUp) +Before Test Method Finished: +- PHPUnit\TestFixture\Event\TemplateMethodsTest::setUp +Pre Condition Method Called (PHPUnit\TestFixture\Event\TemplateMethodsTest::assertPreConditions) +Pre Condition Method Finished: +- PHPUnit\TestFixture\Event\TemplateMethodsTest::assertPreConditions +Test Prepared (PHPUnit\TestFixture\Event\TemplateMethodsTest::testTwo) +Post Condition Method Called (PHPUnit\TestFixture\Event\TemplateMethodsTest::assertPostConditions) +Post Condition Method Finished: +- PHPUnit\TestFixture\Event\TemplateMethodsTest::assertPostConditions +After Test Method Called (PHPUnit\TestFixture\Event\TemplateMethodsTest::tearDown) +After Test Method Finished: +- PHPUnit\TestFixture\Event\TemplateMethodsTest::tearDown +After Last Test Method Called (PHPUnit\TestFixture\Event\TemplateMethodsTest::tearDownAfterClass) +After Last Test Method Finished: +- PHPUnit\TestFixture\Event\TemplateMethodsTest::tearDownAfterClass +Test Passed (PHPUnit\TestFixture\Event\TemplateMethodsTest::testTwo) +Test Finished (PHPUnit\TestFixture\Event\TemplateMethodsTest::testTwo) +Child Process Finished +After Last Test Method Called (PHPUnit\TestFixture\Event\TemplateMethodsTest::tearDownAfterClass) +After Last Test Method Finished: +- PHPUnit\TestFixture\Event\TemplateMethodsTest::tearDownAfterClass +Test Suite Finished (PHPUnit\TestFixture\Event\TemplateMethodsTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/template-methods.phpt b/tests/end-to-end/event/template-methods.phpt new file mode 100644 index 00000000000..ab5df524a8d --- /dev/null +++ b/tests/end-to-end/event/template-methods.phpt @@ -0,0 +1,63 @@ +--TEST-- +The right events are emitted in the right order for the template methods of a test class +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\Event\TemplateMethodsTest, 2 tests) +Before First Test Method Called (PHPUnit\TestFixture\Event\TemplateMethodsTest::setUpBeforeClass) +Before First Test Method Finished: +- PHPUnit\TestFixture\Event\TemplateMethodsTest::setUpBeforeClass +Test Preparation Started (PHPUnit\TestFixture\Event\TemplateMethodsTest::testOne) +Before Test Method Called (PHPUnit\TestFixture\Event\TemplateMethodsTest::setUp) +Before Test Method Finished: +- PHPUnit\TestFixture\Event\TemplateMethodsTest::setUp +Pre Condition Method Called (PHPUnit\TestFixture\Event\TemplateMethodsTest::assertPreConditions) +Pre Condition Method Finished: +- PHPUnit\TestFixture\Event\TemplateMethodsTest::assertPreConditions +Test Prepared (PHPUnit\TestFixture\Event\TemplateMethodsTest::testOne) +Post Condition Method Called (PHPUnit\TestFixture\Event\TemplateMethodsTest::assertPostConditions) +Post Condition Method Finished: +- PHPUnit\TestFixture\Event\TemplateMethodsTest::assertPostConditions +After Test Method Called (PHPUnit\TestFixture\Event\TemplateMethodsTest::tearDown) +After Test Method Finished: +- PHPUnit\TestFixture\Event\TemplateMethodsTest::tearDown +Test Passed (PHPUnit\TestFixture\Event\TemplateMethodsTest::testOne) +Test Finished (PHPUnit\TestFixture\Event\TemplateMethodsTest::testOne) +Test Preparation Started (PHPUnit\TestFixture\Event\TemplateMethodsTest::testTwo) +Before Test Method Called (PHPUnit\TestFixture\Event\TemplateMethodsTest::setUp) +Before Test Method Finished: +- PHPUnit\TestFixture\Event\TemplateMethodsTest::setUp +Pre Condition Method Called (PHPUnit\TestFixture\Event\TemplateMethodsTest::assertPreConditions) +Pre Condition Method Finished: +- PHPUnit\TestFixture\Event\TemplateMethodsTest::assertPreConditions +Test Prepared (PHPUnit\TestFixture\Event\TemplateMethodsTest::testTwo) +Post Condition Method Called (PHPUnit\TestFixture\Event\TemplateMethodsTest::assertPostConditions) +Post Condition Method Finished: +- PHPUnit\TestFixture\Event\TemplateMethodsTest::assertPostConditions +After Test Method Called (PHPUnit\TestFixture\Event\TemplateMethodsTest::tearDown) +After Test Method Finished: +- PHPUnit\TestFixture\Event\TemplateMethodsTest::tearDown +Test Passed (PHPUnit\TestFixture\Event\TemplateMethodsTest::testTwo) +Test Finished (PHPUnit\TestFixture\Event\TemplateMethodsTest::testTwo) +After Last Test Method Called (PHPUnit\TestFixture\Event\TemplateMethodsTest::tearDownAfterClass) +After Last Test Method Finished: +- PHPUnit\TestFixture\Event\TemplateMethodsTest::tearDownAfterClass +Test Suite Finished (PHPUnit\TestFixture\Event\TemplateMethodsTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/test-method-builder-cannot-find-testcase-object.phpt b/tests/end-to-end/event/test-method-builder-cannot-find-testcase-object.phpt new file mode 100644 index 00000000000..297aa8eb02d --- /dev/null +++ b/tests/end-to-end/event/test-method-builder-cannot-find-testcase-object.phpt @@ -0,0 +1,15 @@ +--TEST-- +The right exception is raised when TestMethodBuilder::fromCallStack() cannot find a TestCase object +--SKIPIF-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\SkippedInSetupBeforeClassTest, 1 test) +Before First Test Method Called (PHPUnit\TestFixture\Event\SkippedInSetupBeforeClassTest::setUpBeforeClass) +Test Suite Skipped (PHPUnit\TestFixture\Event\SkippedInSetupBeforeClassTest, message) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/test-skipped-in-setup.phpt b/tests/end-to-end/event/test-skipped-in-setup.phpt new file mode 100644 index 00000000000..aa6bc9806f9 --- /dev/null +++ b/tests/end-to-end/event/test-skipped-in-setup.phpt @@ -0,0 +1,30 @@ +--TEST-- +The right events are emitted in the right order for a test skipped in setUp() +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\SkippedInSetupTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\SkippedInSetupTest::testOne) +Before Test Method Called (PHPUnit\TestFixture\Event\SkippedInSetupTest::setUp) +Before Test Method Finished: +- PHPUnit\TestFixture\Event\SkippedInSetupTest::setUp +Test Skipped (PHPUnit\TestFixture\Event\SkippedInSetupTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\Event\SkippedInSetupTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/test-skipped.phpt b/tests/end-to-end/event/test-skipped.phpt new file mode 100644 index 00000000000..7d3f68453d4 --- /dev/null +++ b/tests/end-to-end/event/test-skipped.phpt @@ -0,0 +1,30 @@ +--TEST-- +The right events are emitted in the right order for a skipped test +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\SkippedTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\SkippedTest::testSkipped) +Test Prepared (PHPUnit\TestFixture\Event\SkippedTest::testSkipped) +Test Skipped (PHPUnit\TestFixture\Event\SkippedTest::testSkipped) +message +Test Finished (PHPUnit\TestFixture\Event\SkippedTest::testSkipped) +Test Suite Finished (PHPUnit\TestFixture\Event\SkippedTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/test-stub.phpt b/tests/end-to-end/event/test-stub.phpt new file mode 100644 index 00000000000..9a2667b567b --- /dev/null +++ b/tests/end-to-end/event/test-stub.phpt @@ -0,0 +1,33 @@ +--TEST-- +The right events are emitted in the right order for a test that uses a test stub +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Bootstrap Finished (%sExample.php) +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\StubTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\StubTest::testSuccess) +Test Prepared (PHPUnit\TestFixture\Event\StubTest::testSuccess) +Test Stub Created (PHPUnit\TestFixture\Event\Example) +Test Passed (PHPUnit\TestFixture\Event\StubTest::testSuccess) +Test Finished (PHPUnit\TestFixture\Event\StubTest::testSuccess) +Test Suite Finished (PHPUnit\TestFixture\Event\StubTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/testwith-attribute-too-many-values.phpt b/tests/end-to-end/event/testwith-attribute-too-many-values.phpt new file mode 100644 index 00000000000..6255dd60b25 --- /dev/null +++ b/tests/end-to-end/event/testwith-attribute-too-many-values.phpt @@ -0,0 +1,33 @@ +--TEST-- +The right events are emitted in the right order for a successful test that uses a TestWith attribute which provides more values than the test method accepts +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Triggered PHPUnit Warning (PHPUnit\TestFixture\Metadata\Attribute\TestWithTooManyValuesTest::testOne) +Data set #0 provided by TestWith#0 attribute has more arguments (3) than the test method accepts (2) +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTooManyValuesTest, 1 test) +Test Suite Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTooManyValuesTest::testOne, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTooManyValuesTest::testOne#0) +Test Prepared (PHPUnit\TestFixture\Metadata\Attribute\TestWithTooManyValuesTest::testOne#0) +Test Passed (PHPUnit\TestFixture\Metadata\Attribute\TestWithTooManyValuesTest::testOne#0) +Test Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTooManyValuesTest::testOne#0) +Test Suite Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTooManyValuesTest::testOne, 1 test) +Test Suite Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTooManyValuesTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/event/testwith-attribute.phpt b/tests/end-to-end/event/testwith-attribute.phpt new file mode 100644 index 00000000000..d5761afdc7c --- /dev/null +++ b/tests/end-to-end/event/testwith-attribute.phpt @@ -0,0 +1,49 @@ +--TEST-- +The right events are emitted in the right order for a successful test that uses the TestWith and TestWithJson attributes +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (4 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (4 tests) +Test Suite Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest, 4 tests) +Test Suite Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testOne, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testOne#0) +Test Prepared (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testOne#0) +Test Passed (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testOne#0) +Test Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testOne#0) +Test Suite Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testOne, 1 test) +Test Suite Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testOneWithName, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testOneWithName#Name1) +Test Prepared (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testOneWithName#Name1) +Test Passed (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testOneWithName#Name1) +Test Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testOneWithName#Name1) +Test Suite Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testOneWithName, 1 test) +Test Suite Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTwo, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTwo#0) +Test Prepared (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTwo#0) +Test Passed (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTwo#0) +Test Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTwo#0) +Test Suite Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTwo, 1 test) +Test Suite Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTwoWithName, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTwoWithName#Name2) +Test Prepared (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTwoWithName#Name2) +Test Passed (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTwoWithName#Name2) +Test Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTwoWithName#Name2) +Test Suite Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest::testTwoWithName, 1 test) +Test Suite Finished (PHPUnit\TestFixture\Metadata\Attribute\TestWithTest, 4 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/testwithjson-attribute-invalid-value.phpt b/tests/end-to-end/event/testwithjson-attribute-invalid-value.phpt new file mode 100644 index 00000000000..e7c4825b1af --- /dev/null +++ b/tests/end-to-end/event/testwithjson-attribute-invalid-value.phpt @@ -0,0 +1,27 @@ +--TEST-- +The right events are emitted in the right order for a test that uses a TestWithJson attribute which provides an invalid value +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Triggered PHPUnit Error (PHPUnit\TestFixture\Metadata\Attribute\TestWithInvalidValueTest::testOne) +The data provider specified for PHPUnit\TestFixture\Metadata\Attribute\TestWithInvalidValueTest::testOne is invalid +Data set #0 provided by TestWith#0 attribute is invalid, expected array but got bool +Test Runner Triggered Warning (No tests found in class "PHPUnit\TestFixture\Metadata\Attribute\TestWithInvalidValueTest".) +Test Suite Loaded (0 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (0 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/event/too-few-columns.phpt b/tests/end-to-end/event/too-few-columns.phpt new file mode 100644 index 00000000000..2b7ee09d69f --- /dev/null +++ b/tests/end-to-end/event/too-few-columns.phpt @@ -0,0 +1,32 @@ +--TEST-- +The right events are emitted in the right order when too few columns are requested +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Triggered Warning (Less than 16 columns requested, number of columns set to 16) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\SuccessTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\SuccessTest::testSuccess) +Test Prepared (PHPUnit\TestFixture\Event\SuccessTest::testSuccess) +Test Passed (PHPUnit\TestFixture\Event\SuccessTest::testSuccess) +Test Finished (PHPUnit\TestFixture\Event\SuccessTest::testSuccess) +Test Suite Finished (PHPUnit\TestFixture\Event\SuccessTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/event/unexpected-end-of-test-in-separate-process.phpt b/tests/end-to-end/event/unexpected-end-of-test-in-separate-process.phpt new file mode 100644 index 00000000000..e36dce48333 --- /dev/null +++ b/tests/end-to-end/event/unexpected-end-of-test-in-separate-process.phpt @@ -0,0 +1,31 @@ +--TEST-- +The right events are emitted in the right order for a test run in a separate process that ends unexpectedly +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\SeparateProcessesTest, 1 test) +Child Process Started +Child Process Errored +Test Errored (PHPUnit\TestFixture\Event\SeparateProcessesTest::testOne) +Test was run in child process and ended unexpectedly +Test Finished (PHPUnit\TestFixture\Event\SeparateProcessesTest::testOne) +Child Process Finished +Test Suite Finished (PHPUnit\TestFixture\Event\SeparateProcessesTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/event/unsatisfied-requirement-before-class-method.phpt b/tests/end-to-end/event/unsatisfied-requirement-before-class-method.phpt new file mode 100644 index 00000000000..b573fdf50b5 --- /dev/null +++ b/tests/end-to-end/event/unsatisfied-requirement-before-class-method.phpt @@ -0,0 +1,25 @@ +--TEST-- +The right events are emitted in the right order for a test that has an unsatisfied requirement (before class method) +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\UnsatisfiedRequirementBeforeClassMethodTest, 1 test) +Test Suite Skipped (PHPUnit\TestFixture\Event\UnsatisfiedRequirementBeforeClassMethodTest, PHP ^100.0 is required.) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/unsatisfied-requirement-class.phpt b/tests/end-to-end/event/unsatisfied-requirement-class.phpt new file mode 100644 index 00000000000..6ed218e86b6 --- /dev/null +++ b/tests/end-to-end/event/unsatisfied-requirement-class.phpt @@ -0,0 +1,28 @@ +--TEST-- +The right events are emitted in the right order for a test that has an unsatisfied requirement (class level) +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\UnsatisfiedRequirementClassTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\UnsatisfiedRequirementClassTest::testOne) +Test Skipped (PHPUnit\TestFixture\Event\UnsatisfiedRequirementClassTest::testOne) +PHP ^100.0 is required. +Test Suite Finished (PHPUnit\TestFixture\Event\UnsatisfiedRequirementClassTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/unsatisfied-requirement-method.phpt b/tests/end-to-end/event/unsatisfied-requirement-method.phpt new file mode 100644 index 00000000000..034a7f6fb04 --- /dev/null +++ b/tests/end-to-end/event/unsatisfied-requirement-method.phpt @@ -0,0 +1,28 @@ +--TEST-- +The right events are emitted in the right order for a test that has an unsatisfied requirement (method level) +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Event\UnsatisfiedRequirementMethodTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\UnsatisfiedRequirementMethodTest::testOne) +Test Skipped (PHPUnit\TestFixture\Event\UnsatisfiedRequirementMethodTest::testOne) +PHP ^100.0 is required. +Test Suite Finished (PHPUnit\TestFixture\Event\UnsatisfiedRequirementMethodTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/user-deprecated.phpt b/tests/end-to-end/event/user-deprecated.phpt new file mode 100644 index 00000000000..b12840e4ce5 --- /dev/null +++ b/tests/end-to-end/event/user-deprecated.phpt @@ -0,0 +1,39 @@ +--TEST-- +The right events are emitted in the right order for a test that runs code which triggers E_USER_DEPRECATED +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\Event\DeprecatedFeatureTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\Event\DeprecatedFeatureTest::testDeprecatedFeature) +Test Prepared (PHPUnit\TestFixture\Event\DeprecatedFeatureTest::testDeprecatedFeature) +Test Triggered Deprecation (PHPUnit\TestFixture\Event\DeprecatedFeatureTest::testDeprecatedFeature, unknown if issue was triggered in first-party code or third-party code) in %s:%d +message +Test Triggered Deprecation (PHPUnit\TestFixture\Event\DeprecatedFeatureTest::testDeprecatedFeature, unknown if issue was triggered in first-party code or third-party code, suppressed using operator) in %s:%d +message +Test Passed (PHPUnit\TestFixture\Event\DeprecatedFeatureTest::testDeprecatedFeature) +Test Finished (PHPUnit\TestFixture\Event\DeprecatedFeatureTest::testDeprecatedFeature) +Test Preparation Started (PHPUnit\TestFixture\Event\DeprecatedFeatureTest::testDeprecatedSuppressedErrorGetLast) +Test Prepared (PHPUnit\TestFixture\Event\DeprecatedFeatureTest::testDeprecatedSuppressedErrorGetLast) +Test Triggered Deprecation (PHPUnit\TestFixture\Event\DeprecatedFeatureTest::testDeprecatedSuppressedErrorGetLast, unknown if issue was triggered in first-party code or third-party code, suppressed using operator) in %s:%d +message +Test Passed (PHPUnit\TestFixture\Event\DeprecatedFeatureTest::testDeprecatedSuppressedErrorGetLast) +Test Finished (PHPUnit\TestFixture\Event\DeprecatedFeatureTest::testDeprecatedSuppressedErrorGetLast) +Test Suite Finished (PHPUnit\TestFixture\Event\DeprecatedFeatureTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/user-error-php-84.phpt b/tests/end-to-end/event/user-error-php-84.phpt new file mode 100644 index 00000000000..58d909979b1 --- /dev/null +++ b/tests/end-to-end/event/user-error-php-84.phpt @@ -0,0 +1,48 @@ +--TEST-- +The right events are emitted in the right order for a test that runs code which triggers E_USER_ERROR +--SKIPIF-- +')) { + print 'skip: PHP 8.4 is required.'; +} +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\Event\UserErrorTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\Event\UserErrorTest::testUserError) +Test Prepared (PHPUnit\TestFixture\Event\UserErrorTest::testUserError) +Test Triggered PHP Deprecation (PHPUnit\TestFixture\Event\UserErrorTest::testUserError, unknown if issue was triggered in first-party code or third-party code) in %s:%d +Passing E_USER_ERROR to trigger_error() is deprecated since 8.4, throw an exception or call exit with a string message instead +Test Triggered Error (PHPUnit\TestFixture\Event\UserErrorTest::testUserError) in %s:%d +message +Test Errored (PHPUnit\TestFixture\Event\UserErrorTest::testUserError) +E_USER_ERROR was triggered +Test Finished (PHPUnit\TestFixture\Event\UserErrorTest::testUserError) +Test Preparation Started (PHPUnit\TestFixture\Event\UserErrorTest::testUserErrorMustAbortExecution) +Test Prepared (PHPUnit\TestFixture\Event\UserErrorTest::testUserErrorMustAbortExecution) +Test Triggered PHP Deprecation (PHPUnit\TestFixture\Event\UserErrorTest::testUserErrorMustAbortExecution, unknown if issue was triggered in first-party code or third-party code) in %s:%d +Passing E_USER_ERROR to trigger_error() is deprecated since 8.4, throw an exception or call exit with a string message instead +Test Triggered Error (PHPUnit\TestFixture\Event\UserErrorTest::testUserErrorMustAbortExecution) in %s:%d +message +Test Errored (PHPUnit\TestFixture\Event\UserErrorTest::testUserErrorMustAbortExecution) +E_USER_ERROR was triggered +Test Finished (PHPUnit\TestFixture\Event\UserErrorTest::testUserErrorMustAbortExecution) +Test Suite Finished (PHPUnit\TestFixture\Event\UserErrorTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/event/user-error.phpt b/tests/end-to-end/event/user-error.phpt new file mode 100644 index 00000000000..2212f04504b --- /dev/null +++ b/tests/end-to-end/event/user-error.phpt @@ -0,0 +1,44 @@ +--TEST-- +The right events are emitted in the right order for a test that runs code which triggers E_USER_ERROR +--SKIPIF-- +=')) { + print 'skip: PHP < 8.4 is required.'; +} +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\Event\UserErrorTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\Event\UserErrorTest::testUserError) +Test Prepared (PHPUnit\TestFixture\Event\UserErrorTest::testUserError) +Test Triggered Error (PHPUnit\TestFixture\Event\UserErrorTest::testUserError) in %s:%d +message +Test Errored (PHPUnit\TestFixture\Event\UserErrorTest::testUserError) +E_USER_ERROR was triggered +Test Finished (PHPUnit\TestFixture\Event\UserErrorTest::testUserError) +Test Preparation Started (PHPUnit\TestFixture\Event\UserErrorTest::testUserErrorMustAbortExecution) +Test Prepared (PHPUnit\TestFixture\Event\UserErrorTest::testUserErrorMustAbortExecution) +Test Triggered Error (PHPUnit\TestFixture\Event\UserErrorTest::testUserErrorMustAbortExecution) in %s:%d +message +Test Errored (PHPUnit\TestFixture\Event\UserErrorTest::testUserErrorMustAbortExecution) +E_USER_ERROR was triggered +Test Finished (PHPUnit\TestFixture\Event\UserErrorTest::testUserErrorMustAbortExecution) +Test Suite Finished (PHPUnit\TestFixture\Event\UserErrorTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/event/user-notice.phpt b/tests/end-to-end/event/user-notice.phpt new file mode 100644 index 00000000000..8b2ba0e0b2d --- /dev/null +++ b/tests/end-to-end/event/user-notice.phpt @@ -0,0 +1,37 @@ +--TEST-- +The right events are emitted in the right order for a test that runs code which triggers an E_USER_NOTICE +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\Event\UserNoticeTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\Event\UserNoticeTest::testUserNotice) +Test Prepared (PHPUnit\TestFixture\Event\UserNoticeTest::testUserNotice) +Test Triggered Notice (PHPUnit\TestFixture\Event\UserNoticeTest::testUserNotice) in %s:%d +message +Test Passed (PHPUnit\TestFixture\Event\UserNoticeTest::testUserNotice) +Test Finished (PHPUnit\TestFixture\Event\UserNoticeTest::testUserNotice) +Test Preparation Started (PHPUnit\TestFixture\Event\UserNoticeTest::testUserNoticeErrorGetLast) +Test Prepared (PHPUnit\TestFixture\Event\UserNoticeTest::testUserNoticeErrorGetLast) +Test Triggered Notice (PHPUnit\TestFixture\Event\UserNoticeTest::testUserNoticeErrorGetLast) in %s:%d +message +Test Passed (PHPUnit\TestFixture\Event\UserNoticeTest::testUserNoticeErrorGetLast) +Test Finished (PHPUnit\TestFixture\Event\UserNoticeTest::testUserNoticeErrorGetLast) +Test Suite Finished (PHPUnit\TestFixture\Event\UserNoticeTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/event/user-warning.phpt b/tests/end-to-end/event/user-warning.phpt new file mode 100644 index 00000000000..a43703d3f85 --- /dev/null +++ b/tests/end-to-end/event/user-warning.phpt @@ -0,0 +1,37 @@ +--TEST-- +The right events are emitted in the right order for a test that runs code which triggers E_USER_WARNING +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\Event\UserWarningTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\Event\UserWarningTest::testUserWarning) +Test Prepared (PHPUnit\TestFixture\Event\UserWarningTest::testUserWarning) +Test Triggered Warning (PHPUnit\TestFixture\Event\UserWarningTest::testUserWarning) in %s:%d +message +Test Passed (PHPUnit\TestFixture\Event\UserWarningTest::testUserWarning) +Test Finished (PHPUnit\TestFixture\Event\UserWarningTest::testUserWarning) +Test Preparation Started (PHPUnit\TestFixture\Event\UserWarningTest::testUserWarningErrorGetLast) +Test Prepared (PHPUnit\TestFixture\Event\UserWarningTest::testUserWarningErrorGetLast) +Test Triggered Warning (PHPUnit\TestFixture\Event\UserWarningTest::testUserWarningErrorGetLast) in %s:%d +message +Test Passed (PHPUnit\TestFixture\Event\UserWarningTest::testUserWarningErrorGetLast) +Test Finished (PHPUnit\TestFixture\Event\UserWarningTest::testUserWarningErrorGetLast) +Test Suite Finished (PHPUnit\TestFixture\Event\UserWarningTest, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/exception-stack.phpt b/tests/end-to-end/exception-stack.phpt deleted file mode 100644 index dccbfcf310d..00000000000 --- a/tests/end-to-end/exception-stack.phpt +++ /dev/null @@ -1,63 +0,0 @@ ---TEST-- -phpunit ../../_files/ExceptionStackTest.php ---FILE-- - 1 -+ 0 => 2 - ) - - -%s:%i - -Caused by -message -Failed asserting that two arrays are equal. ---- Expected -+++ Actual -@@ @@ - Array ( -- 0 => 1 -+ 0 => 2 - ) - -%s:%i - -2) PHPUnit\TestFixture\ExceptionStackTest::testNestedExceptions -Exception: One - -%s:%i - -Caused by -InvalidArgumentException: Two - -%s:%i - -Caused by -Exception: Three - -%s:%i - -ERRORS! -Tests: 2, Assertions: 1, Errors: 2. diff --git a/tests/end-to-end/exclude-group-isolation.phpt b/tests/end-to-end/exclude-group-isolation.phpt deleted file mode 100644 index 9e5a0516a74..00000000000 --- a/tests/end-to-end/exclude-group-isolation.phpt +++ /dev/null @@ -1,20 +0,0 @@ ---TEST-- -phpunit --process-isolation --exclude-group balanceIsInitiallyZero ../../_files/BankAccountTest.php ---FILE-- -assertSame(self::$dependency, $dependency); } - /** - * @depends !clone testOne - */ + #[Depends('testOne')] public function testThree($dependency): void { $this->assertSame(self::$dependency, $dependency); } - /** - * @depends clone testOne - */ + #[DependsUsingDeepClone('testOne')] public function testFour($dependency): void { $this->assertNotSame(self::$dependency, $dependency); } - /** - * @depends !shallowClone testOne - */ + #[Depends('testOne')] public function testFive($dependency): void { $this->assertSame(self::$dependency, $dependency); } - /** - * @depends shallowClone testOne - */ + #[DependsUsingShallowClone('testOne')] public function testSix($dependency): void { $this->assertNotSame(self::$dependency, $dependency); diff --git a/tests/end-to-end/execution-order/_files/DependencyTestSuite.php b/tests/end-to-end/execution-order/_files/DependencyTestSuite.php deleted file mode 100644 index 4025fccf349..00000000000 --- a/tests/end-to-end/execution-order/_files/DependencyTestSuite.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestSuite; - -class DependencyTestSuite -{ - public static function suite() - { - $suite = new TestSuite('Test Dependencies'); - - $suite->addTestSuite(DependencySuccessTest::class); - $suite->addTestSuite(DependencyFailureTest::class); - - return $suite; - } -} diff --git a/tests/end-to-end/execution-order/_files/MultiDependencyTest_result_cache.txt b/tests/end-to-end/execution-order/_files/MultiDependencyTest_result_cache.txt index bc695a9c87d..a9735ef50ec 100644 --- a/tests/end-to-end/execution-order/_files/MultiDependencyTest_result_cache.txt +++ b/tests/end-to-end/execution-order/_files/MultiDependencyTest_result_cache.txt @@ -1 +1 @@ -C:37:"PHPUnit\Runner\DefaultTestResultCache":289:{a:2:{s:7:"defects";a:1:{s:29:"MultiDependencyTest::testFive";i:1;}s:5:"times";a:5:{s:28:"MultiDependencyTest::testOne";d:0;s:28:"MultiDependencyTest::testTwo";d:0;s:30:"MultiDependencyTest::testThree";d:0;s:29:"MultiDependencyTest::testFour";d:0;s:29:"MultiDependencyTest::testFive";d:0;}}} +{"version":2,"defects":{"PHPUnit\\TestFixture\\MultiDependencyTest::testFive":1},"times":{"PHPUnit\\TestFixture\\MultiDependencyTest::testOne":0,"PHPUnit\\TestFixture\\MultiDependencyTest::testTwo":0,"PHPUnit\\TestFixture\\MultiDependencyTest::testThree":0,"PHPUnit\\TestFixture\\MultiDependencyTest::testFour":0,"PHPUnit\\TestFixture\\MultiDependencyTest::testFive":0}} diff --git a/tests/end-to-end/execution-order/_files/StackTest.php b/tests/end-to-end/execution-order/_files/StackTest.php index e9239e4bd0f..f8c2b8da813 100644 --- a/tests/end-to-end/execution-order/_files/StackTest.php +++ b/tests/end-to-end/execution-order/_files/StackTest.php @@ -7,6 +7,11 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + +use function array_pop; +use function end; +use PHPUnit\Framework\Attributes\Depends; use PHPUnit\Framework\TestCase; class StackTest extends TestCase @@ -17,18 +22,16 @@ public function testPush() $this->assertCount(0, $stack); $stack[] = 'foo'; - $this->assertEquals('foo', \end($stack)); + $this->assertEquals('foo', end($stack)); $this->assertCount(1, $stack); return $stack; } - /** - * @depends testPush - */ + #[Depends('testPush')] public function testPop(array $stack): void { - $this->assertEquals('foo', \array_pop($stack)); + $this->assertEquals('foo', array_pop($stack)); $this->assertCount(0, $stack); } } diff --git a/tests/end-to-end/execution-order/_files/TestWithDifferentDurations.php b/tests/end-to-end/execution-order/_files/TestWithDifferentDurations.php index 8d0b6f381a7..b01a39f23db 100644 --- a/tests/end-to-end/execution-order/_files/TestWithDifferentDurations.php +++ b/tests/end-to-end/execution-order/_files/TestWithDifferentDurations.php @@ -7,6 +7,8 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + use PHPUnit\Framework\TestCase; final class TestWithDifferentDurations extends TestCase diff --git a/tests/end-to-end/execution-order/_files/TestWithDifferentDurations.phpunit.result.cache.txt b/tests/end-to-end/execution-order/_files/TestWithDifferentDurations.phpunit.result.cache.txt index 3a14002b065..8fd87ae1f03 100644 --- a/tests/end-to-end/execution-order/_files/TestWithDifferentDurations.phpunit.result.cache.txt +++ b/tests/end-to-end/execution-order/_files/TestWithDifferentDurations.phpunit.result.cache.txt @@ -1 +1 @@ -C:37:"PHPUnit\Runner\DefaultTestResultCache":199:{a:2:{s:7:"defects";a:0:{}s:5:"times";a:3:{s:35:"TestWithDifferentDurations::testOne";d:1.000;s:35:"TestWithDifferentDurations::testTwo";d:0.500;s:37:"TestWithDifferentDurations::testThree";d:1.500;}}} +{"version":2,"defects":[],"times":{"PHPUnit\\TestFixture\\TestWithDifferentDurations::testOne":2.006,"PHPUnit\\TestFixture\\TestWithDifferentDurations::testTwo":0,"PHPUnit\\TestFixture\\TestWithDifferentDurations::testThree":3.001,"PHPUnit\\TestFixture\\ExampleTest::testOne":2.006,"PHPUnit\\TestFixture\\ExampleTest::testTwo":3.001,"PHPUnit\\TestFixture\\ExampleTest::testThree":0}} diff --git a/tests/end-to-end/execution-order/cache-result.phpt b/tests/end-to-end/execution-order/cache-result.phpt index 5da20cb84bc..32d66e71f90 100644 --- a/tests/end-to-end/execution-order/cache-result.phpt +++ b/tests/end-to-end/execution-order/cache-result.phpt @@ -2,31 +2,33 @@ phpunit --order-by=no-depends,reverse --cache-result --cache-result-file ./tests/_files/MultiDependencyTest.php --FILE-- run($_SERVER['argv']); -unlink($target); +print file_get_contents($cacheDirectory . DIRECTORY_SEPARATOR . 'test-results'); --EXPECTF-- PHPUnit %s by Sebastian Bergmann and contributors. +Runtime: %s + .SS.. 5 / 5 (100%) Time: %s, Memory: %s -OK, but incomplete, skipped, or risky tests! +OK, but some tests were skipped! Tests: 5, Assertions: 3, Skipped: 2. -C:37:"PHPUnit\Runner\DefaultTestResultCache":%d:{a:2:{s:7:"defects";a:2:{s:29:"MultiDependencyTest::testFour";i:1;s:30:"MultiDependencyTest::testThree";i:1;}s:5:"times";a:5:{s:29:"MultiDependencyTest::testFive";d:%f;s:29:"MultiDependencyTest::testFour";d:%f;s:30:"MultiDependencyTest::testThree";d:%f;s:28:"MultiDependencyTest::testTwo";d:%f;s:28:"MultiDependencyTest::testOne";d:%f;}}} +{"version":2,"defects":{"PHPUnit\\TestFixture\\MultiDependencyTest::testFour":1,"PHPUnit\\TestFixture\\MultiDependencyTest::testThree":1},"times":{"PHPUnit\\TestFixture\\MultiDependencyTest::testFive":%f,"PHPUnit\\TestFixture\\MultiDependencyTest::testFour":%f,"PHPUnit\\TestFixture\\MultiDependencyTest::testThree":%f,"PHPUnit\\TestFixture\\MultiDependencyTest::testTwo":%f,"PHPUnit\\TestFixture\\MultiDependencyTest::testOne":%f}} +--CLEAN-- +run($_SERVER['argv']); --EXPECTF-- PHPUnit %s by Sebastian Bergmann and contributors. diff --git a/tests/end-to-end/execution-order/dependencies-isolation.phpt b/tests/end-to-end/execution-order/dependencies-isolation.phpt index 7aeb4fba2ef..85225d015e5 100644 --- a/tests/end-to-end/execution-order/dependencies-isolation.phpt +++ b/tests/end-to-end/execution-order/dependencies-isolation.phpt @@ -1,54 +1,54 @@ --TEST-- -phpunit --process-isolation --verbose ../../_files/DependencyTestSuite.php +phpunit --process-isolation --display-skipped ../../_files/dependencies --FILE-- run($_SERVER['argv']); --EXPECTF-- PHPUnit %s by Sebastian Bergmann and contributors. Runtime: %s -...FSSSWS 9 / 9 (100%) +...FSSSEE 9 / 9 (100%) Time: %s, Memory: %s -There was 1 warning: +There were 2 errors: -1) DependencyFailureTest::testHandlesDependsAnnotationForNonexistentTests -This test depends on "DependencyFailureTest::doesNotExist" which does not exist. +1) PHPUnit\TestFixture\DependencyFailureTest::testHandlesDependencyOnTestMethodThatDoesNotExist +This test depends on "PHPUnit\TestFixture\DependencyFailureTest::doesNotExist" which does not exist + +2) PHPUnit\TestFixture\DependencyFailureTest::testHandlesDependencyOnTestMethodWithEmptyName +This test has an invalid dependency -- There was 1 failure: -1) DependencyFailureTest::testOne +1) PHPUnit\TestFixture\DependencyFailureTest::testOne +Failed asserting that false is true. %s:%i -- -There were 4 skipped tests: - -1) DependencyFailureTest::testTwo -This test depends on "DependencyFailureTest::testOne" to pass. +There were 3 skipped tests: -2) DependencyFailureTest::testThree -This test depends on "DependencyFailureTest::testTwo" to pass. +1) PHPUnit\TestFixture\DependencyFailureTest::testTwo +This test depends on "PHPUnit\TestFixture\DependencyFailureTest::testOne" to pass -3) DependencyFailureTest::testFour -This test depends on "DependencyFailureTest::testOne" to pass. +2) PHPUnit\TestFixture\DependencyFailureTest::testThree +This test depends on "PHPUnit\TestFixture\DependencyFailureTest::testTwo" to pass -4) DependencyFailureTest::testHandlesDependsAnnotationWithNoMethodSpecified -This method has an invalid @depends annotation. +3) PHPUnit\TestFixture\DependencyFailureTest::testFour +This test depends on "PHPUnit\TestFixture\DependencyFailureTest::testOne" to pass -FAILURES! -Tests: 9, Assertions: 4, Failures: 1, Warnings: 1, Skipped: 4. +ERRORS! +Tests: 9, Assertions: 4, Errors: 2, Failures: 1, Skipped: 3. diff --git a/tests/end-to-end/execution-order/depends-as-parameter-with-isolation.phpt b/tests/end-to-end/execution-order/depends-as-parameter-with-isolation.phpt index 64a78044b3e..3e1a5ce946f 100644 --- a/tests/end-to-end/execution-order/depends-as-parameter-with-isolation.phpt +++ b/tests/end-to-end/execution-order/depends-as-parameter-with-isolation.phpt @@ -2,18 +2,19 @@ phpunit --process-isolation _files/StackTest.php --FILE-- run($_SERVER['argv']); --EXPECTF-- PHPUnit %s by Sebastian Bergmann and contributors. +Runtime: %s + .. 2 / 2 (100%) Time: %s, Memory: %s diff --git a/tests/end-to-end/execution-order/depends-as-parameter.phpt b/tests/end-to-end/execution-order/depends-as-parameter.phpt index 0d9198467a4..d7461f6eb16 100644 --- a/tests/end-to-end/execution-order/depends-as-parameter.phpt +++ b/tests/end-to-end/execution-order/depends-as-parameter.phpt @@ -2,17 +2,18 @@ phpunit _files/StackTest.php --FILE-- run($_SERVER['argv']); --EXPECTF-- PHPUnit %s by Sebastian Bergmann and contributors. +Runtime: %s + .. 2 / 2 (100%) Time: %s, Memory: %s diff --git a/tests/end-to-end/execution-order/depends-multiple-parameter-with-isolation.phpt b/tests/end-to-end/execution-order/depends-multiple-parameter-with-isolation.phpt index b12867b5821..9f19922e0aa 100644 --- a/tests/end-to-end/execution-order/depends-multiple-parameter-with-isolation.phpt +++ b/tests/end-to-end/execution-order/depends-multiple-parameter-with-isolation.phpt @@ -2,18 +2,19 @@ phpunit --process-isolation _files/MultiDependencyTest.php --FILE-- run($_SERVER['argv']); --EXPECTF-- PHPUnit %s by Sebastian Bergmann and contributors. +Runtime: %s + ..... 5 / 5 (100%) Time: %s, Memory: %s diff --git a/tests/end-to-end/execution-order/depends-multiple-parameters.phpt b/tests/end-to-end/execution-order/depends-multiple-parameters.phpt index 985890b6a62..02c6884b487 100644 --- a/tests/end-to-end/execution-order/depends-multiple-parameters.phpt +++ b/tests/end-to-end/execution-order/depends-multiple-parameters.phpt @@ -2,17 +2,18 @@ phpunit _files/MultiDependencyTest.php --FILE-- run($_SERVER['argv']); --EXPECTF-- PHPUnit %s by Sebastian Bergmann and contributors. +Runtime: %s + ..... 5 / 5 (100%) Time: %s, Memory: %s diff --git a/tests/end-to-end/execution-order/depends-on-class.phpt b/tests/end-to-end/execution-order/depends-on-class.phpt index 627e60d7195..fe963c10897 100644 --- a/tests/end-to-end/execution-order/depends-on-class.phpt +++ b/tests/end-to-end/execution-order/depends-on-class.phpt @@ -2,55 +2,57 @@ phpunit -c ../../_files/configuration.depends-on-class.php --FILE-- run($_SERVER['argv']); --EXPECTF-- PHPUnit %s by Sebastian Bergmann and contributors. Runtime: %s Configuration: %s%etests%e_files%econfiguration.depends-on-class.xml -....SFSSSWS 11 / 11 (100%) +....SFSSSEE 11 / 11 (100%) Time: %s, Memory: %s -There was 1 warning: +There were 2 errors: -1) DependencyFailureTest::testHandlesDependsAnnotationForNonexistentTests -This test depends on "DependencyFailureTest::doesNotExist" which does not exist. +1) PHPUnit\TestFixture\DependencyFailureTest::testHandlesDependencyOnTestMethodThatDoesNotExist +This test depends on "PHPUnit\TestFixture\DependencyFailureTest::doesNotExist" which does not exist + +2) PHPUnit\TestFixture\DependencyFailureTest::testHandlesDependencyOnTestMethodWithEmptyName +This test has an invalid dependency -- There was 1 failure: -1) DependencyFailureTest::testOne +1) PHPUnit\TestFixture\DependencyFailureTest::testOne +Failed asserting that false is true. -%s%etests%e_files%eDependencyFailureTest.php:16 +%s%etests%e_files%edependencies%eDependencyFailureTest.php:20 -- -There were 5 skipped tests: - -1) DependencyOnClassTest::testThatDependsOnAFailingClass -This test depends on "DependencyFailureTest::class" to pass. +There were 4 skipped tests: -2) DependencyFailureTest::testTwo -This test depends on "DependencyFailureTest::testOne" to pass. +1) PHPUnit\TestFixture\DependencyOnClassTest::testThatDependsOnAFailingClass +This test depends on "PHPUnit\TestFixture\DependencyFailureTest::class" to pass -3) DependencyFailureTest::testThree -This test depends on "DependencyFailureTest::testTwo" to pass. +2) PHPUnit\TestFixture\DependencyFailureTest::testTwo +This test depends on "PHPUnit\TestFixture\DependencyFailureTest::testOne" to pass -4) DependencyFailureTest::testFour -This test depends on "DependencyFailureTest::testOne" to pass. +3) PHPUnit\TestFixture\DependencyFailureTest::testThree +This test depends on "PHPUnit\TestFixture\DependencyFailureTest::testTwo" to pass -5) DependencyFailureTest::testHandlesDependsAnnotationWithNoMethodSpecified -This method has an invalid @depends annotation. +4) PHPUnit\TestFixture\DependencyFailureTest::testFour +This test depends on "PHPUnit\TestFixture\DependencyFailureTest::testOne" to pass -FAILURES! -Tests: 11, Assertions: 5, Failures: 1, Warnings: 1, Skipped: 5. +ERRORS! +Tests: 11, Assertions: 5, Errors: 2, Failures: 1, Skipped: 4. diff --git a/tests/end-to-end/execution-order/execution-order-options-via-config.phpt b/tests/end-to-end/execution-order/execution-order-options-via-config.phpt deleted file mode 100644 index 213c369ab08..00000000000 --- a/tests/end-to-end/execution-order/execution-order-options-via-config.phpt +++ /dev/null @@ -1,33 +0,0 @@ ---TEST-- -phpunit -c ../_files/configuration_stop_on_defect.xml ./tests/_files/MultiDependencyTest.php ---FILE-- -run($_SERVER['argv']); --EXPECTF-- PHPUnit %s by Sebastian Bergmann and contributors. diff --git a/tests/end-to-end/execution-order/order-by-duration-via-cli.phpt b/tests/end-to-end/execution-order/order-by-duration-via-cli.phpt index 4f259ca979a..ba52bf934ef 100644 --- a/tests/end-to-end/execution-order/order-by-duration-via-cli.phpt +++ b/tests/end-to-end/execution-order/order-by-duration-via-cli.phpt @@ -2,32 +2,52 @@ phpunit --order-by=duration ./tests/end-to-end/execution-order/_files/TestWithDifferentDurations.php --FILE-- run($_SERVER['argv']); -OK (3 tests, 3 assertions) +unlink($cacheDirectory . DIRECTORY_SEPARATOR . 'test-results'); +rmdir($cacheDirectory); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using PHP %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (3 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (3 tests) +Test Suite Started (PHPUnit\TestFixture\TestWithDifferentDurations, 3 tests) +Test Preparation Started (PHPUnit\TestFixture\TestWithDifferentDurations::testTwo) +Test Prepared (PHPUnit\TestFixture\TestWithDifferentDurations::testTwo) +Test Passed (PHPUnit\TestFixture\TestWithDifferentDurations::testTwo) +Test Finished (PHPUnit\TestFixture\TestWithDifferentDurations::testTwo) +Test Preparation Started (PHPUnit\TestFixture\TestWithDifferentDurations::testOne) +Test Prepared (PHPUnit\TestFixture\TestWithDifferentDurations::testOne) +Test Passed (PHPUnit\TestFixture\TestWithDifferentDurations::testOne) +Test Finished (PHPUnit\TestFixture\TestWithDifferentDurations::testOne) +Test Preparation Started (PHPUnit\TestFixture\TestWithDifferentDurations::testThree) +Test Prepared (PHPUnit\TestFixture\TestWithDifferentDurations::testThree) +Test Passed (PHPUnit\TestFixture\TestWithDifferentDurations::testThree) +Test Finished (PHPUnit\TestFixture\TestWithDifferentDurations::testThree) +Test Suite Finished (PHPUnit\TestFixture\TestWithDifferentDurations, 3 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/execution-order/order-by-duration-via-phpunit-xml.phpt b/tests/end-to-end/execution-order/order-by-duration-via-phpunit-xml.phpt index 02ca396854b..46f01c62f4f 100644 --- a/tests/end-to-end/execution-order/order-by-duration-via-phpunit-xml.phpt +++ b/tests/end-to-end/execution-order/order-by-duration-via-phpunit-xml.phpt @@ -2,32 +2,54 @@ phpunit --configuration=order-by-duration.phpunit.xml --FILE-- run($_SERVER['argv']); -Time: %s, Memory: %s - -OK (3 tests, 3 assertions) +unlink($cacheDirectory . DIRECTORY_SEPARATOR . 'test-results'); +rmdir($cacheDirectory); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using PHP %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (3 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (3 tests) +Test Suite Started (%sorder-by-duration.phpunit.xml, 3 tests) +Test Suite Started (order-by-duration, 3 tests) +Test Suite Started (PHPUnit\TestFixture\TestWithDifferentDurations, 3 tests) +Test Preparation Started (PHPUnit\TestFixture\TestWithDifferentDurations::testTwo) +Test Prepared (PHPUnit\TestFixture\TestWithDifferentDurations::testTwo) +Test Passed (PHPUnit\TestFixture\TestWithDifferentDurations::testTwo) +Test Finished (PHPUnit\TestFixture\TestWithDifferentDurations::testTwo) +Test Preparation Started (PHPUnit\TestFixture\TestWithDifferentDurations::testOne) +Test Prepared (PHPUnit\TestFixture\TestWithDifferentDurations::testOne) +Test Passed (PHPUnit\TestFixture\TestWithDifferentDurations::testOne) +Test Finished (PHPUnit\TestFixture\TestWithDifferentDurations::testOne) +Test Preparation Started (PHPUnit\TestFixture\TestWithDifferentDurations::testThree) +Test Prepared (PHPUnit\TestFixture\TestWithDifferentDurations::testThree) +Test Passed (PHPUnit\TestFixture\TestWithDifferentDurations::testThree) +Test Finished (PHPUnit\TestFixture\TestWithDifferentDurations::testThree) +Test Suite Finished (PHPUnit\TestFixture\TestWithDifferentDurations, 3 tests) +Test Suite Finished (order-by-duration, 3 tests) +Test Suite Finished (%sorder-by-duration.phpunit.xml, 3 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/execution-order/repeat.phpt b/tests/end-to-end/execution-order/repeat.phpt deleted file mode 100644 index 5f076bc81c8..00000000000 --- a/tests/end-to-end/execution-order/repeat.phpt +++ /dev/null @@ -1,22 +0,0 @@ ---TEST-- -phpunit --repeat 3 ../../_files/BankAccountTest.php ---FILE-- -run($_SERVER['argv']); --EXPECTF-- PHPUnit %s by Sebastian Bergmann and contributors. diff --git a/tests/end-to-end/execution-order/test-order-reversed-with-dependency-resolution.phpt b/tests/end-to-end/execution-order/test-order-reversed-with-dependency-resolution.phpt deleted file mode 100644 index e103e7e2078..00000000000 --- a/tests/end-to-end/execution-order/test-order-reversed-with-dependency-resolution.phpt +++ /dev/null @@ -1,35 +0,0 @@ ---TEST-- -phpunit --verbose --order-by=depends,reverse ../execution-order/_files/MultiDependencyTest.php ---FILE-- - + + + + tests + + + diff --git a/tests/end-to-end/extension-cli/_files/class-does-not-exist/tests/Test.php b/tests/end-to-end/extension-cli/_files/class-does-not-exist/tests/Test.php new file mode 100644 index 00000000000..a6c86c85797 --- /dev/null +++ b/tests/end-to-end/extension-cli/_files/class-does-not-exist/tests/Test.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/extension-cli/_files/class-does-not-implement-interface/phpunit.xml b/tests/end-to-end/extension-cli/_files/class-does-not-implement-interface/phpunit.xml new file mode 100644 index 00000000000..aaa950205d4 --- /dev/null +++ b/tests/end-to-end/extension-cli/_files/class-does-not-implement-interface/phpunit.xml @@ -0,0 +1,10 @@ + + + + + tests + + + diff --git a/tests/end-to-end/extension-cli/_files/class-does-not-implement-interface/src/MyExtensionBootstrap.php b/tests/end-to-end/extension-cli/_files/class-does-not-implement-interface/src/MyExtensionBootstrap.php new file mode 100644 index 00000000000..0fbf797d553 --- /dev/null +++ b/tests/end-to-end/extension-cli/_files/class-does-not-implement-interface/src/MyExtensionBootstrap.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +final class MyExtensionBootstrap +{ +} diff --git a/tests/end-to-end/extension-cli/_files/class-does-not-implement-interface/src/autoload.php b/tests/end-to-end/extension-cli/_files/class-does-not-implement-interface/src/autoload.php new file mode 100644 index 00000000000..c84415b0429 --- /dev/null +++ b/tests/end-to-end/extension-cli/_files/class-does-not-implement-interface/src/autoload.php @@ -0,0 +1,11 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +require __DIR__ . '/MyExtensionBootstrap.php'; diff --git a/tests/end-to-end/extension-cli/_files/class-does-not-implement-interface/tests/Test.php b/tests/end-to-end/extension-cli/_files/class-does-not-implement-interface/tests/Test.php new file mode 100644 index 00000000000..a6c86c85797 --- /dev/null +++ b/tests/end-to-end/extension-cli/_files/class-does-not-implement-interface/tests/Test.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/extension-cli/_files/exception-in-extension-bootstrap-method/phpunit.xml b/tests/end-to-end/extension-cli/_files/exception-in-extension-bootstrap-method/phpunit.xml new file mode 100644 index 00000000000..aaa950205d4 --- /dev/null +++ b/tests/end-to-end/extension-cli/_files/exception-in-extension-bootstrap-method/phpunit.xml @@ -0,0 +1,10 @@ + + + + + tests + + + diff --git a/tests/end-to-end/extension-cli/_files/exception-in-extension-bootstrap-method/src/MyExtensionBootstrap.php b/tests/end-to-end/extension-cli/_files/exception-in-extension-bootstrap-method/src/MyExtensionBootstrap.php new file mode 100644 index 00000000000..763a61a70b9 --- /dev/null +++ b/tests/end-to-end/extension-cli/_files/exception-in-extension-bootstrap-method/src/MyExtensionBootstrap.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +use PHPUnit\Runner\Extension\Extension; +use PHPUnit\Runner\Extension\Facade; +use PHPUnit\Runner\Extension\ParameterCollection; +use PHPUnit\TextUI\Configuration\Configuration; +use RuntimeException; + +final class MyExtensionBootstrap implements Extension +{ + public function bootstrap(Configuration $configuration, Facade $facade, ParameterCollection $parameters): void + { + throw new RuntimeException('message'); + } +} diff --git a/tests/end-to-end/extension-cli/_files/exception-in-extension-bootstrap-method/src/autoload.php b/tests/end-to-end/extension-cli/_files/exception-in-extension-bootstrap-method/src/autoload.php new file mode 100644 index 00000000000..c84415b0429 --- /dev/null +++ b/tests/end-to-end/extension-cli/_files/exception-in-extension-bootstrap-method/src/autoload.php @@ -0,0 +1,11 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +require __DIR__ . '/MyExtensionBootstrap.php'; diff --git a/tests/end-to-end/extension-cli/_files/exception-in-extension-bootstrap-method/tests/Test.php b/tests/end-to-end/extension-cli/_files/exception-in-extension-bootstrap-method/tests/Test.php new file mode 100644 index 00000000000..a6c86c85797 --- /dev/null +++ b/tests/end-to-end/extension-cli/_files/exception-in-extension-bootstrap-method/tests/Test.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/extension-cli/_files/exception-in-extension-constructor/phpunit.xml b/tests/end-to-end/extension-cli/_files/exception-in-extension-constructor/phpunit.xml new file mode 100644 index 00000000000..aaa950205d4 --- /dev/null +++ b/tests/end-to-end/extension-cli/_files/exception-in-extension-constructor/phpunit.xml @@ -0,0 +1,10 @@ + + + + + tests + + + diff --git a/tests/end-to-end/extension-cli/_files/exception-in-extension-constructor/src/MyExtensionBootstrap.php b/tests/end-to-end/extension-cli/_files/exception-in-extension-constructor/src/MyExtensionBootstrap.php new file mode 100644 index 00000000000..5967340604c --- /dev/null +++ b/tests/end-to-end/extension-cli/_files/exception-in-extension-constructor/src/MyExtensionBootstrap.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +use PHPUnit\Runner\Extension\Extension; +use PHPUnit\Runner\Extension\Facade; +use PHPUnit\Runner\Extension\ParameterCollection; +use PHPUnit\TextUI\Configuration\Configuration; +use RuntimeException; + +final class MyExtensionBootstrap implements Extension +{ + public function __construct() + { + throw new RuntimeException('message'); + } + + public function bootstrap(Configuration $configuration, Facade $facade, ParameterCollection $parameters): void + { + } +} diff --git a/tests/end-to-end/extension-cli/_files/exception-in-extension-constructor/src/autoload.php b/tests/end-to-end/extension-cli/_files/exception-in-extension-constructor/src/autoload.php new file mode 100644 index 00000000000..c84415b0429 --- /dev/null +++ b/tests/end-to-end/extension-cli/_files/exception-in-extension-constructor/src/autoload.php @@ -0,0 +1,11 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +require __DIR__ . '/MyExtensionBootstrap.php'; diff --git a/tests/end-to-end/extension-cli/_files/exception-in-extension-constructor/tests/Test.php b/tests/end-to-end/extension-cli/_files/exception-in-extension-constructor/tests/Test.php new file mode 100644 index 00000000000..a6c86c85797 --- /dev/null +++ b/tests/end-to-end/extension-cli/_files/exception-in-extension-constructor/tests/Test.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/extension-cli/_files/exception-in-extension-subscriber/phpunit.xml b/tests/end-to-end/extension-cli/_files/exception-in-extension-subscriber/phpunit.xml new file mode 100644 index 00000000000..aaa950205d4 --- /dev/null +++ b/tests/end-to-end/extension-cli/_files/exception-in-extension-subscriber/phpunit.xml @@ -0,0 +1,10 @@ + + + + + tests + + + diff --git a/tests/end-to-end/extension-cli/_files/exception-in-extension-subscriber/src/MyExecutionFinishedSubscriber.php b/tests/end-to-end/extension-cli/_files/exception-in-extension-subscriber/src/MyExecutionFinishedSubscriber.php new file mode 100644 index 00000000000..2c4719b88b3 --- /dev/null +++ b/tests/end-to-end/extension-cli/_files/exception-in-extension-subscriber/src/MyExecutionFinishedSubscriber.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +use PHPUnit\Event\TestRunner\ExecutionFinished; +use PHPUnit\Event\TestRunner\ExecutionFinishedSubscriber; +use RuntimeException; + +final class MyExecutionFinishedSubscriber implements ExecutionFinishedSubscriber +{ + public function notify(ExecutionFinished $event): void + { + throw new RuntimeException('message'); + } +} diff --git a/tests/end-to-end/extension-cli/_files/exception-in-extension-subscriber/src/MyExtensionBootstrap.php b/tests/end-to-end/extension-cli/_files/exception-in-extension-subscriber/src/MyExtensionBootstrap.php new file mode 100644 index 00000000000..9c0174a96cd --- /dev/null +++ b/tests/end-to-end/extension-cli/_files/exception-in-extension-subscriber/src/MyExtensionBootstrap.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +use PHPUnit\Runner\Extension\Extension; +use PHPUnit\Runner\Extension\Facade; +use PHPUnit\Runner\Extension\ParameterCollection; +use PHPUnit\TextUI\Configuration\Configuration; + +final class MyExtensionBootstrap implements Extension +{ + public function bootstrap(Configuration $configuration, Facade $facade, ParameterCollection $parameters): void + { + $facade->registerSubscriber(new MyExecutionFinishedSubscriber); + } +} diff --git a/tests/end-to-end/extension-cli/_files/exception-in-extension-subscriber/src/autoload.php b/tests/end-to-end/extension-cli/_files/exception-in-extension-subscriber/src/autoload.php new file mode 100644 index 00000000000..98531b415f5 --- /dev/null +++ b/tests/end-to-end/extension-cli/_files/exception-in-extension-subscriber/src/autoload.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +require __DIR__ . '/MyExtensionBootstrap.php'; + +require __DIR__ . '/MyExecutionFinishedSubscriber.php'; diff --git a/tests/end-to-end/extension-cli/_files/exception-in-extension-subscriber/tests/Test.php b/tests/end-to-end/extension-cli/_files/exception-in-extension-subscriber/tests/Test.php new file mode 100644 index 00000000000..a6c86c85797 --- /dev/null +++ b/tests/end-to-end/extension-cli/_files/exception-in-extension-subscriber/tests/Test.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/extension-cli/_files/extension-bootstrap/phpunit.xml b/tests/end-to-end/extension-cli/_files/extension-bootstrap/phpunit.xml new file mode 100644 index 00000000000..aaa950205d4 --- /dev/null +++ b/tests/end-to-end/extension-cli/_files/extension-bootstrap/phpunit.xml @@ -0,0 +1,10 @@ + + + + + tests + + + diff --git a/tests/end-to-end/extension-cli/_files/extension-bootstrap/src/MyExecutionFinishedSubscriber.php b/tests/end-to-end/extension-cli/_files/extension-bootstrap/src/MyExecutionFinishedSubscriber.php new file mode 100644 index 00000000000..7e7be91538d --- /dev/null +++ b/tests/end-to-end/extension-cli/_files/extension-bootstrap/src/MyExecutionFinishedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +use const PHP_EOL; +use PHPUnit\Event\TestRunner\ExecutionFinished; +use PHPUnit\Event\TestRunner\ExecutionFinishedSubscriber; + +final class MyExecutionFinishedSubscriber implements ExecutionFinishedSubscriber +{ + private readonly string $message; + + public function __construct(string $message) + { + $this->message = $message; + } + + public function notify(ExecutionFinished $event): void + { + print __METHOD__ . PHP_EOL; + print $this->message . PHP_EOL; + } +} diff --git a/tests/end-to-end/extension-cli/_files/extension-bootstrap/src/MyExtensionBootstrap.php b/tests/end-to-end/extension-cli/_files/extension-bootstrap/src/MyExtensionBootstrap.php new file mode 100644 index 00000000000..a80872588d0 --- /dev/null +++ b/tests/end-to-end/extension-cli/_files/extension-bootstrap/src/MyExtensionBootstrap.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +use PHPUnit\Runner\Extension\Extension; +use PHPUnit\Runner\Extension\Facade; +use PHPUnit\Runner\Extension\ParameterCollection; +use PHPUnit\TextUI\Configuration\Configuration; + +final class MyExtensionBootstrap implements Extension +{ + public function bootstrap(Configuration $configuration, Facade $facade, ParameterCollection $parameters): void + { + $facade->registerSubscriber( + new MyExecutionFinishedSubscriber( + 'the-message', + ), + ); + } +} diff --git a/tests/end-to-end/extension-cli/_files/extension-bootstrap/src/autoload.php b/tests/end-to-end/extension-cli/_files/extension-bootstrap/src/autoload.php new file mode 100644 index 00000000000..98531b415f5 --- /dev/null +++ b/tests/end-to-end/extension-cli/_files/extension-bootstrap/src/autoload.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +require __DIR__ . '/MyExtensionBootstrap.php'; + +require __DIR__ . '/MyExecutionFinishedSubscriber.php'; diff --git a/tests/end-to-end/extension-cli/_files/extension-bootstrap/tests/Test.php b/tests/end-to-end/extension-cli/_files/extension-bootstrap/tests/Test.php new file mode 100644 index 00000000000..a6c86c85797 --- /dev/null +++ b/tests/end-to-end/extension-cli/_files/extension-bootstrap/tests/Test.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/extension-cli/bootstrap.phpt b/tests/end-to-end/extension-cli/bootstrap.phpt new file mode 100644 index 00000000000..256a9e4fd33 --- /dev/null +++ b/tests/end-to-end/extension-cli/bootstrap.phpt @@ -0,0 +1,17 @@ +--TEST-- +A PHPUnit extension can subscribe to events +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit\TestFixture\Event\MyExtension\MyExecutionFinishedSubscriber::notify +the-message diff --git a/tests/end-to-end/extension-cli/class-does-not-exist.phpt b/tests/end-to-end/extension-cli/class-does-not-exist.phpt new file mode 100644 index 00000000000..e3bc355ad4b --- /dev/null +++ b/tests/end-to-end/extension-cli/class-does-not-exist.phpt @@ -0,0 +1,29 @@ +--TEST-- +Test runner exits with error when configured extension class does not exit +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Cannot bootstrap extension because class Does\Not\Exist does not exist + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/extension-cli/class-does-not-implement-interface.phpt b/tests/end-to-end/extension-cli/class-does-not-implement-interface.phpt new file mode 100644 index 00000000000..62daa811cda --- /dev/null +++ b/tests/end-to-end/extension-cli/class-does-not-implement-interface.phpt @@ -0,0 +1,29 @@ +--TEST-- +Test runner exits with error when configured extension class does not implement the interface +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Cannot bootstrap extension because class PHPUnit\TestFixture\Event\MyExtension\MyExtensionBootstrap does not implement interface PHPUnit\Runner\Extension\Extension + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/extension-cli/exception-in-extension-bootstrap-method.phpt b/tests/end-to-end/extension-cli/exception-in-extension-bootstrap-method.phpt new file mode 100644 index 00000000000..aacfa9fef7c --- /dev/null +++ b/tests/end-to-end/extension-cli/exception-in-extension-bootstrap-method.phpt @@ -0,0 +1,30 @@ +--TEST-- +Test runner warning is triggered when an exception is triggered in an extension's bootstrap method +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Bootstrapping of extension PHPUnit\TestFixture\Event\MyExtension\MyExtensionBootstrap failed: message +%A + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/extension-cli/exception-in-extension-constructor.phpt b/tests/end-to-end/extension-cli/exception-in-extension-constructor.phpt new file mode 100644 index 00000000000..70283661738 --- /dev/null +++ b/tests/end-to-end/extension-cli/exception-in-extension-constructor.phpt @@ -0,0 +1,30 @@ +--TEST-- +Test runner warning is triggered when an exception is triggered in an extension's bootstrap class' constructor +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Bootstrapping of extension PHPUnit\TestFixture\Event\MyExtension\MyExtensionBootstrap failed: message +%A + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/extension-cli/exception-in-extension-subscriber.phpt b/tests/end-to-end/extension-cli/exception-in-extension-subscriber.phpt new file mode 100644 index 00000000000..ba96c1fe4ae --- /dev/null +++ b/tests/end-to-end/extension-cli/exception-in-extension-subscriber.phpt @@ -0,0 +1,30 @@ +--TEST-- +Test runner warning is triggered when an exception is triggered in an extension's event subscriber +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Exception in third-party event subscriber: message +%A + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/extension-xml/_files/class-does-not-exist/phpunit.xml b/tests/end-to-end/extension-xml/_files/class-does-not-exist/phpunit.xml new file mode 100644 index 00000000000..470d632a125 --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/class-does-not-exist/phpunit.xml @@ -0,0 +1,13 @@ + + + + + + + + + tests + + + diff --git a/tests/end-to-end/extension-xml/_files/class-does-not-exist/tests/Test.php b/tests/end-to-end/extension-xml/_files/class-does-not-exist/tests/Test.php new file mode 100644 index 00000000000..a6c86c85797 --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/class-does-not-exist/tests/Test.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/extension-xml/_files/class-does-not-implement-interface/phpunit.xml b/tests/end-to-end/extension-xml/_files/class-does-not-implement-interface/phpunit.xml new file mode 100644 index 00000000000..23bddb8fe44 --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/class-does-not-implement-interface/phpunit.xml @@ -0,0 +1,14 @@ + + + + + + + + + tests + + + diff --git a/tests/end-to-end/extension-xml/_files/class-does-not-implement-interface/src/MyExtensionBootstrap.php b/tests/end-to-end/extension-xml/_files/class-does-not-implement-interface/src/MyExtensionBootstrap.php new file mode 100644 index 00000000000..0fbf797d553 --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/class-does-not-implement-interface/src/MyExtensionBootstrap.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +final class MyExtensionBootstrap +{ +} diff --git a/tests/end-to-end/extension-xml/_files/class-does-not-implement-interface/src/autoload.php b/tests/end-to-end/extension-xml/_files/class-does-not-implement-interface/src/autoload.php new file mode 100644 index 00000000000..c84415b0429 --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/class-does-not-implement-interface/src/autoload.php @@ -0,0 +1,11 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +require __DIR__ . '/MyExtensionBootstrap.php'; diff --git a/tests/end-to-end/extension-xml/_files/class-does-not-implement-interface/tests/Test.php b/tests/end-to-end/extension-xml/_files/class-does-not-implement-interface/tests/Test.php new file mode 100644 index 00000000000..a6c86c85797 --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/class-does-not-implement-interface/tests/Test.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/extension-xml/_files/exception-in-extension-bootstrap-method/phpunit.xml b/tests/end-to-end/extension-xml/_files/exception-in-extension-bootstrap-method/phpunit.xml new file mode 100644 index 00000000000..c3ab6c75ae6 --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/exception-in-extension-bootstrap-method/phpunit.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + tests + + + diff --git a/tests/end-to-end/extension-xml/_files/exception-in-extension-bootstrap-method/src/MyExtensionBootstrap.php b/tests/end-to-end/extension-xml/_files/exception-in-extension-bootstrap-method/src/MyExtensionBootstrap.php new file mode 100644 index 00000000000..763a61a70b9 --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/exception-in-extension-bootstrap-method/src/MyExtensionBootstrap.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +use PHPUnit\Runner\Extension\Extension; +use PHPUnit\Runner\Extension\Facade; +use PHPUnit\Runner\Extension\ParameterCollection; +use PHPUnit\TextUI\Configuration\Configuration; +use RuntimeException; + +final class MyExtensionBootstrap implements Extension +{ + public function bootstrap(Configuration $configuration, Facade $facade, ParameterCollection $parameters): void + { + throw new RuntimeException('message'); + } +} diff --git a/tests/end-to-end/extension-xml/_files/exception-in-extension-bootstrap-method/src/autoload.php b/tests/end-to-end/extension-xml/_files/exception-in-extension-bootstrap-method/src/autoload.php new file mode 100644 index 00000000000..c84415b0429 --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/exception-in-extension-bootstrap-method/src/autoload.php @@ -0,0 +1,11 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +require __DIR__ . '/MyExtensionBootstrap.php'; diff --git a/tests/end-to-end/extension-xml/_files/exception-in-extension-bootstrap-method/tests/Test.php b/tests/end-to-end/extension-xml/_files/exception-in-extension-bootstrap-method/tests/Test.php new file mode 100644 index 00000000000..a6c86c85797 --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/exception-in-extension-bootstrap-method/tests/Test.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/extension-xml/_files/exception-in-extension-constructor/phpunit-without-parameter.xml b/tests/end-to-end/extension-xml/_files/exception-in-extension-constructor/phpunit-without-parameter.xml new file mode 100644 index 00000000000..23bddb8fe44 --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/exception-in-extension-constructor/phpunit-without-parameter.xml @@ -0,0 +1,14 @@ + + + + + + + + + tests + + + diff --git a/tests/end-to-end/extension-xml/_files/exception-in-extension-constructor/phpunit.xml b/tests/end-to-end/extension-xml/_files/exception-in-extension-constructor/phpunit.xml new file mode 100644 index 00000000000..c3ab6c75ae6 --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/exception-in-extension-constructor/phpunit.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + tests + + + diff --git a/tests/end-to-end/extension-xml/_files/exception-in-extension-constructor/src/MyExtensionBootstrap.php b/tests/end-to-end/extension-xml/_files/exception-in-extension-constructor/src/MyExtensionBootstrap.php new file mode 100644 index 00000000000..5967340604c --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/exception-in-extension-constructor/src/MyExtensionBootstrap.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +use PHPUnit\Runner\Extension\Extension; +use PHPUnit\Runner\Extension\Facade; +use PHPUnit\Runner\Extension\ParameterCollection; +use PHPUnit\TextUI\Configuration\Configuration; +use RuntimeException; + +final class MyExtensionBootstrap implements Extension +{ + public function __construct() + { + throw new RuntimeException('message'); + } + + public function bootstrap(Configuration $configuration, Facade $facade, ParameterCollection $parameters): void + { + } +} diff --git a/tests/end-to-end/extension-xml/_files/exception-in-extension-constructor/src/autoload.php b/tests/end-to-end/extension-xml/_files/exception-in-extension-constructor/src/autoload.php new file mode 100644 index 00000000000..c84415b0429 --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/exception-in-extension-constructor/src/autoload.php @@ -0,0 +1,11 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +require __DIR__ . '/MyExtensionBootstrap.php'; diff --git a/tests/end-to-end/extension-xml/_files/exception-in-extension-constructor/tests/Test.php b/tests/end-to-end/extension-xml/_files/exception-in-extension-constructor/tests/Test.php new file mode 100644 index 00000000000..a6c86c85797 --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/exception-in-extension-constructor/tests/Test.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/extension-xml/_files/exception-in-extension-subscriber/phpunit.xml b/tests/end-to-end/extension-xml/_files/exception-in-extension-subscriber/phpunit.xml new file mode 100644 index 00000000000..c3ab6c75ae6 --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/exception-in-extension-subscriber/phpunit.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + tests + + + diff --git a/tests/end-to-end/extension-xml/_files/exception-in-extension-subscriber/src/MyExecutionFinishedSubscriber.php b/tests/end-to-end/extension-xml/_files/exception-in-extension-subscriber/src/MyExecutionFinishedSubscriber.php new file mode 100644 index 00000000000..2c4719b88b3 --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/exception-in-extension-subscriber/src/MyExecutionFinishedSubscriber.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +use PHPUnit\Event\TestRunner\ExecutionFinished; +use PHPUnit\Event\TestRunner\ExecutionFinishedSubscriber; +use RuntimeException; + +final class MyExecutionFinishedSubscriber implements ExecutionFinishedSubscriber +{ + public function notify(ExecutionFinished $event): void + { + throw new RuntimeException('message'); + } +} diff --git a/tests/end-to-end/extension-xml/_files/exception-in-extension-subscriber/src/MyExtensionBootstrap.php b/tests/end-to-end/extension-xml/_files/exception-in-extension-subscriber/src/MyExtensionBootstrap.php new file mode 100644 index 00000000000..9c0174a96cd --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/exception-in-extension-subscriber/src/MyExtensionBootstrap.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +use PHPUnit\Runner\Extension\Extension; +use PHPUnit\Runner\Extension\Facade; +use PHPUnit\Runner\Extension\ParameterCollection; +use PHPUnit\TextUI\Configuration\Configuration; + +final class MyExtensionBootstrap implements Extension +{ + public function bootstrap(Configuration $configuration, Facade $facade, ParameterCollection $parameters): void + { + $facade->registerSubscriber(new MyExecutionFinishedSubscriber); + } +} diff --git a/tests/end-to-end/extension-xml/_files/exception-in-extension-subscriber/src/autoload.php b/tests/end-to-end/extension-xml/_files/exception-in-extension-subscriber/src/autoload.php new file mode 100644 index 00000000000..98531b415f5 --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/exception-in-extension-subscriber/src/autoload.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +require __DIR__ . '/MyExtensionBootstrap.php'; + +require __DIR__ . '/MyExecutionFinishedSubscriber.php'; diff --git a/tests/end-to-end/extension-xml/_files/exception-in-extension-subscriber/tests/Test.php b/tests/end-to-end/extension-xml/_files/exception-in-extension-subscriber/tests/Test.php new file mode 100644 index 00000000000..a6c86c85797 --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/exception-in-extension-subscriber/tests/Test.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/extension-xml/_files/extension-bootstrap/phpunit-without-parameter.xml b/tests/end-to-end/extension-xml/_files/extension-bootstrap/phpunit-without-parameter.xml new file mode 100644 index 00000000000..23bddb8fe44 --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/extension-bootstrap/phpunit-without-parameter.xml @@ -0,0 +1,14 @@ + + + + + + + + + tests + + + diff --git a/tests/end-to-end/extension-xml/_files/extension-bootstrap/phpunit.xml b/tests/end-to-end/extension-xml/_files/extension-bootstrap/phpunit.xml new file mode 100644 index 00000000000..c3ab6c75ae6 --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/extension-bootstrap/phpunit.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + tests + + + diff --git a/tests/end-to-end/extension-xml/_files/extension-bootstrap/src/MyExecutionFinishedSubscriber.php b/tests/end-to-end/extension-xml/_files/extension-bootstrap/src/MyExecutionFinishedSubscriber.php new file mode 100644 index 00000000000..7e7be91538d --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/extension-bootstrap/src/MyExecutionFinishedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +use const PHP_EOL; +use PHPUnit\Event\TestRunner\ExecutionFinished; +use PHPUnit\Event\TestRunner\ExecutionFinishedSubscriber; + +final class MyExecutionFinishedSubscriber implements ExecutionFinishedSubscriber +{ + private readonly string $message; + + public function __construct(string $message) + { + $this->message = $message; + } + + public function notify(ExecutionFinished $event): void + { + print __METHOD__ . PHP_EOL; + print $this->message . PHP_EOL; + } +} diff --git a/tests/end-to-end/extension-xml/_files/extension-bootstrap/src/MyExtensionBootstrap.php b/tests/end-to-end/extension-xml/_files/extension-bootstrap/src/MyExtensionBootstrap.php new file mode 100644 index 00000000000..aea06b5b6e5 --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/extension-bootstrap/src/MyExtensionBootstrap.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +use PHPUnit\Runner\Extension\Extension; +use PHPUnit\Runner\Extension\Facade; +use PHPUnit\Runner\Extension\ParameterCollection; +use PHPUnit\TextUI\Configuration\Configuration; + +final class MyExtensionBootstrap implements Extension +{ + public function bootstrap(Configuration $configuration, Facade $facade, ParameterCollection $parameters): void + { + $message = 'the-default-message'; + + if ($parameters->has('message')) { + $message = $parameters->get('message'); + } + + $facade->registerSubscriber( + new MyExecutionFinishedSubscriber( + $message, + ), + ); + } +} diff --git a/tests/end-to-end/extension-xml/_files/extension-bootstrap/src/autoload.php b/tests/end-to-end/extension-xml/_files/extension-bootstrap/src/autoload.php new file mode 100644 index 00000000000..98531b415f5 --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/extension-bootstrap/src/autoload.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +require __DIR__ . '/MyExtensionBootstrap.php'; + +require __DIR__ . '/MyExecutionFinishedSubscriber.php'; diff --git a/tests/end-to-end/extension-xml/_files/extension-bootstrap/tests/Test.php b/tests/end-to-end/extension-xml/_files/extension-bootstrap/tests/Test.php new file mode 100644 index 00000000000..a6c86c85797 --- /dev/null +++ b/tests/end-to-end/extension-xml/_files/extension-bootstrap/tests/Test.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event\MyExtension; + +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/extension-xml/bootstrap-with-parameter.phpt b/tests/end-to-end/extension-xml/bootstrap-with-parameter.phpt new file mode 100644 index 00000000000..ea6ed325f7a --- /dev/null +++ b/tests/end-to-end/extension-xml/bootstrap-with-parameter.phpt @@ -0,0 +1,15 @@ +--TEST-- +A PHPUnit extension can subscribe to events +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit\TestFixture\Event\MyExtension\MyExecutionFinishedSubscriber::notify +the-message diff --git a/tests/end-to-end/extension-xml/bootstrap-without-parameter.phpt b/tests/end-to-end/extension-xml/bootstrap-without-parameter.phpt new file mode 100644 index 00000000000..9e4a18d0ca4 --- /dev/null +++ b/tests/end-to-end/extension-xml/bootstrap-without-parameter.phpt @@ -0,0 +1,15 @@ +--TEST-- +A PHPUnit extension can subscribe to events +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit\TestFixture\Event\MyExtension\MyExecutionFinishedSubscriber::notify +the-default-message diff --git a/tests/end-to-end/extension-xml/class-does-not-exist.phpt b/tests/end-to-end/extension-xml/class-does-not-exist.phpt new file mode 100644 index 00000000000..910e121fcf2 --- /dev/null +++ b/tests/end-to-end/extension-xml/class-does-not-exist.phpt @@ -0,0 +1,27 @@ +--TEST-- +Test runner exits with error when configured extension class does not exit +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Cannot bootstrap extension because class Does\Not\Exist does not exist + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/extension-xml/class-does-not-implement-interface.phpt b/tests/end-to-end/extension-xml/class-does-not-implement-interface.phpt new file mode 100644 index 00000000000..3be29058d55 --- /dev/null +++ b/tests/end-to-end/extension-xml/class-does-not-implement-interface.phpt @@ -0,0 +1,27 @@ +--TEST-- +Test runner exits with error when configured extension class does not implement the interface +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Cannot bootstrap extension because class PHPUnit\TestFixture\Event\MyExtension\MyExtensionBootstrap does not implement interface PHPUnit\Runner\Extension\Extension + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/extension-xml/exception-in-extension-bootstrap-method.phpt b/tests/end-to-end/extension-xml/exception-in-extension-bootstrap-method.phpt new file mode 100644 index 00000000000..672a4256abb --- /dev/null +++ b/tests/end-to-end/extension-xml/exception-in-extension-bootstrap-method.phpt @@ -0,0 +1,28 @@ +--TEST-- +Test runner warning is triggered when an exception is triggered in an extension's bootstrap method +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Bootstrapping of extension PHPUnit\TestFixture\Event\MyExtension\MyExtensionBootstrap failed: message +%A + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/extension-xml/exception-in-extension-constructor.phpt b/tests/end-to-end/extension-xml/exception-in-extension-constructor.phpt new file mode 100644 index 00000000000..168f28b654c --- /dev/null +++ b/tests/end-to-end/extension-xml/exception-in-extension-constructor.phpt @@ -0,0 +1,28 @@ +--TEST-- +Test runner warning is triggered when an exception is triggered in an extension's bootstrap class' constructor +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Bootstrapping of extension PHPUnit\TestFixture\Event\MyExtension\MyExtensionBootstrap failed: message +%A + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/extension-xml/exception-in-extension-subscriber.phpt b/tests/end-to-end/extension-xml/exception-in-extension-subscriber.phpt new file mode 100644 index 00000000000..99884e60057 --- /dev/null +++ b/tests/end-to-end/extension-xml/exception-in-extension-subscriber.phpt @@ -0,0 +1,28 @@ +--TEST-- +Test runner warning is triggered when an exception is triggered in an extension's event subscriber +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Exception in third-party event subscriber: message +%A + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/extension-xml/phar-extension.phpt b/tests/end-to-end/extension-xml/phar-extension.phpt new file mode 100644 index 00000000000..55a9405df79 --- /dev/null +++ b/tests/end-to-end/extension-xml/phar-extension.phpt @@ -0,0 +1,35 @@ +--TEST-- +The right events are emitted in the right order when a PHPUnit extension from a PHAR is loaded +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Extension Loaded from PHAR (phpunit/phpunit-test-extension 1.0.0) +Extension Bootstrapped (PHPUnit\TestFixture\MyExtension\MyExtensionBootstrap) +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (%s%etests%eend-to-end%e_files%ephar-extension%ephpunit.xml, 1 test) +Test Suite Started (default, 1 test) +Test Suite Started (PHPUnit\TestFixture\Event\MyExtension\Test, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Event\MyExtension\Test::testOne) +Test Prepared (PHPUnit\TestFixture\Event\MyExtension\Test::testOne) +Test Passed (PHPUnit\TestFixture\Event\MyExtension\Test::testOne) +Test Finished (PHPUnit\TestFixture\Event\MyExtension\Test::testOne) +Test Suite Finished (PHPUnit\TestFixture\Event\MyExtension\Test, 1 test) +Test Suite Finished (default, 1 test) +Test Suite Finished (%s%etests%eend-to-end%e_files%ephar-extension%ephpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/extensions-via-cli.phpt b/tests/end-to-end/extensions-via-cli.phpt deleted file mode 100644 index f52826a345f..00000000000 --- a/tests/end-to-end/extensions-via-cli.phpt +++ /dev/null @@ -1,28 +0,0 @@ ---TEST-- -phpunit --extensions=... ---FILE-- - 1 -+ 0 => 2 - ) - -%s:%i - -2) PHPUnit\TestFixture\FailureTest::testAssertIntegerEqualsInteger -message -Failed asserting that 2 matches expected 1. - -%s:%i - -3) PHPUnit\TestFixture\FailureTest::testAssertObjectEqualsObject -message -Failed asserting that two objects are equal. ---- Expected -+++ Actual -@@ @@ - stdClass Object ( -- 'foo' => 'bar' -+ 'bar' => 'foo' - ) - -%s:%i - -4) PHPUnit\TestFixture\FailureTest::testAssertNullEqualsString -message -Failed asserting that 'bar' matches expected null. - -%s:%i - -5) PHPUnit\TestFixture\FailureTest::testAssertStringEqualsString -message -Failed asserting that two strings are equal. ---- Expected -+++ Actual -@@ @@ --'foo' -+'bar' - -%s:%i - -6) PHPUnit\TestFixture\FailureTest::testAssertTextEqualsText -message -Failed asserting that two strings are equal. ---- Expected -+++ Actual -@@ @@ - 'foo\n --bar\n -+baz\n - ' - -%s:%i - -7) PHPUnit\TestFixture\FailureTest::testAssertStringMatchesFormat -message -Failed asserting that string matches format description. ---- Expected -+++ Actual -@@ @@ --*%s* -+** - -%s:%i - -8) PHPUnit\TestFixture\FailureTest::testAssertNumericEqualsNumeric -message -Failed asserting that 2 matches expected 1. - -%s:%i - -9) PHPUnit\TestFixture\FailureTest::testAssertTextSameText -message -Failed asserting that two strings are identical. ---- Expected -+++ Actual -@@ @@ --'foo' -+'bar' - -%s:%i - -10) PHPUnit\TestFixture\FailureTest::testAssertObjectSameObject -message -Failed asserting that two variables reference the same object. - -%s:%i - -11) PHPUnit\TestFixture\FailureTest::testAssertObjectSameNull -message -Failed asserting that null is identical to an object of class "stdClass". - -%s:%i - -12) PHPUnit\TestFixture\FailureTest::testAssertFloatSameFloat -message -Failed asserting that 1.5 is identical to 1.0. - -%s:%i - -13) PHPUnit\TestFixture\FailureTest::testAssertStringMatchesFormatFile -Failed asserting that string matches format description. ---- Expected -+++ Actual -@@ @@ --FOO -+...BAR... - -%s:%i - -FAILURES! -Tests: 13, Assertions: 14, Failures: 13. diff --git a/tests/end-to-end/fatal-isolation.phpt b/tests/end-to-end/fatal-isolation.phpt deleted file mode 100644 index be05c71f70d..00000000000 --- a/tests/end-to-end/fatal-isolation.phpt +++ /dev/null @@ -1,23 +0,0 @@ ---TEST-- -phpunit --process-isolation ../../_files/FatalTest.php ---FILE-- - ---EXPECTF-- -PHPUnit %s by Sebastian Bergmann and contributors. - - -Warning: Test case class not matching filename is deprecated - in %sWrongClassNameTest.php - Class name was 'WrongClassNameBar', expected 'WrongClassNameTest' - -. 1 / 1 (100%) - -Time: %s, Memory: %s - -OK (1 test, 1 assertion) diff --git a/tests/end-to-end/filter-class-isolation.phpt b/tests/end-to-end/filter-class-isolation.phpt deleted file mode 100644 index 15dd1c82eab..00000000000 --- a/tests/end-to-end/filter-class-isolation.phpt +++ /dev/null @@ -1,20 +0,0 @@ ---TEST-- -phpunit --process-isolation --filter BankAccountTest ../../_files/BankAccountTest.php ---FILE-- - - - - - tests - - - - - - src - - - diff --git a/tests/end-to-end/force-covers-annotation/tests/Test.php b/tests/end-to-end/force-covers-annotation/tests/Test.php deleted file mode 100644 index 5f937e1533b..00000000000 --- a/tests/end-to-end/force-covers-annotation/tests/Test.php +++ /dev/null @@ -1,18 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; - -final class Test extends TestCase -{ - public function testOne(): void - { - $this->assertTrue(true); - } -} diff --git a/tests/end-to-end/forward-compatibility.phpt b/tests/end-to-end/forward-compatibility.phpt deleted file mode 100644 index 8530f70e1cd..00000000000 --- a/tests/end-to-end/forward-compatibility.phpt +++ /dev/null @@ -1,17 +0,0 @@ ---TEST-- -phpunit ../../_files/BankAccountTest.php ---FILE-- - + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\ExpectNoErrorLog; + +use PHPUnit\Framework\TestCase; + +class FooBar +{ + public function doFoo() + { + return ''; + } +} + +final class ExpectErrorLogFailTest extends TestCase +{ + public function testOne(): void + { + $foo = new FooBar; + + $this->assertSame('', $foo->doFoo()); + $this->expectErrorLog(); + } +} diff --git a/tests/end-to-end/generic/_files/ExpectErrorLogTest.php b/tests/end-to-end/generic/_files/ExpectErrorLogTest.php new file mode 100644 index 00000000000..2c374c1fa01 --- /dev/null +++ b/tests/end-to-end/generic/_files/ExpectErrorLogTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\ExpectErrorLog; + +use function error_log; +use PHPUnit\Framework\TestCase; + +class Foo +{ + public function doFoo() + { + error_log('logged a side effect'); + + return ''; + } +} + +final class ExpectErrorLogTest extends TestCase +{ + public function testOne(): void + { + $foo = new Foo; + + $this->assertSame('', $foo->doFoo()); + $this->expectErrorLog(); + } +} diff --git a/tests/end-to-end/generic/_files/TestForDeprecatedFeatureInIsolationTest.php b/tests/end-to-end/generic/_files/TestForDeprecatedFeatureInIsolationTest.php new file mode 100644 index 00000000000..34022e9fe9e --- /dev/null +++ b/tests/end-to-end/generic/_files/TestForDeprecatedFeatureInIsolationTest.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use const E_USER_DEPRECATED; +use function trigger_error; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; +use PHPUnit\Framework\TestCase; + +#[RunTestsInSeparateProcesses] +final class TestForDeprecatedFeatureInIsolationTest extends TestCase +{ + #[IgnoreDeprecations] + public function testExpectationOnExactDeprecationMessageWorksWhenExpectedDeprecationIsTriggered(): void + { + $this->expectUserDeprecationMessage('message'); + + @trigger_error('message', E_USER_DEPRECATED); + } + + #[IgnoreDeprecations] + public function testExpectationsOnExactDeprecationMessagesWorkWhenExpectedDeprecationsAreTriggered(): void + { + $this->expectUserDeprecationMessage('message'); + $this->expectUserDeprecationMessage('another message'); + + @trigger_error('message', E_USER_DEPRECATED); + @trigger_error('another message', E_USER_DEPRECATED); + } + + #[IgnoreDeprecations] + public function testExpectationOnExactDeprecationMessageWorksWhenExpectedDeprecationIsNotTriggered(): void + { + $this->expectUserDeprecationMessage('message'); + } + + #[IgnoreDeprecations] + public function testExpectationOnExactDeprecationMessageWorksWhenUnexpectedDeprecationIsTriggered(): void + { + $this->expectUserDeprecationMessage('message'); + + @trigger_error('another message', E_USER_DEPRECATED); + } + + #[IgnoreDeprecations] + public function testExpectationOnDeprecationMessageMatchingRegularExpressionWorksWhenExpectedDeprecationIsTriggered(): void + { + $this->expectUserDeprecationMessageMatches('/message/'); + + @trigger_error('...message...', E_USER_DEPRECATED); + } + + #[IgnoreDeprecations] + public function testExpectationsOnDeprecationMessagesMatchingRegularExpressionsWorkWhenExpectedDeprecationsAreTriggered(): void + { + $this->expectUserDeprecationMessageMatches('/foo/'); + $this->expectUserDeprecationMessageMatches('/bar/'); + + @trigger_error('...foo...', E_USER_DEPRECATED); + @trigger_error('...bar...', E_USER_DEPRECATED); + } + + #[IgnoreDeprecations] + public function testExpectationOnDeprecationMessageMatchingRegularExpressionWorksWhenExpectedDeprecationIsNotTriggered(): void + { + $this->expectUserDeprecationMessageMatches('/message/'); + } + + #[IgnoreDeprecations] + public function testExpectationOnDeprecationMessageMatchingRegularExpressionWorksWhenUnepectedDeprecationIsTriggered(): void + { + $this->expectUserDeprecationMessageMatches('/message/'); + + @trigger_error('something else', E_USER_DEPRECATED); + } +} diff --git a/tests/end-to-end/generic/_files/TestForDeprecatedFeatureTest.php b/tests/end-to-end/generic/_files/TestForDeprecatedFeatureTest.php new file mode 100644 index 00000000000..e322b407fa7 --- /dev/null +++ b/tests/end-to-end/generic/_files/TestForDeprecatedFeatureTest.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use const E_USER_DEPRECATED; +use function trigger_error; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; +use PHPUnit\Framework\TestCase; + +final class TestForDeprecatedFeatureTest extends TestCase +{ + #[IgnoreDeprecations] + public function testExpectationOnExactDeprecationMessageWorksWhenExpectedDeprecationIsTriggered(): void + { + $this->expectUserDeprecationMessage('message'); + + @trigger_error('message', E_USER_DEPRECATED); + } + + #[IgnoreDeprecations] + public function testExpectationsOnExactDeprecationMessagesWorkWhenExpectedDeprecationsAreTriggered(): void + { + $this->expectUserDeprecationMessage('message'); + $this->expectUserDeprecationMessage('another message'); + + @trigger_error('message', E_USER_DEPRECATED); + @trigger_error('another message', E_USER_DEPRECATED); + } + + #[IgnoreDeprecations] + public function testExpectationOnExactDeprecationMessageWorksWhenExpectedDeprecationIsNotTriggered(): void + { + $this->expectUserDeprecationMessage('message'); + } + + #[IgnoreDeprecations] + public function testExpectationOnExactDeprecationMessageWorksWhenUnexpectedDeprecationIsTriggered(): void + { + $this->expectUserDeprecationMessage('message'); + + @trigger_error('another message', E_USER_DEPRECATED); + } + + #[IgnoreDeprecations] + public function testExpectationOnDeprecationMessageMatchingRegularExpressionWorksWhenExpectedDeprecationIsTriggered(): void + { + $this->expectUserDeprecationMessageMatches('/message/'); + + @trigger_error('...message...', E_USER_DEPRECATED); + } + + #[IgnoreDeprecations] + public function testExpectationsOnDeprecationMessagesMatchingRegularExpressionsWorkWhenExpectedDeprecationsAreTriggered(): void + { + $this->expectUserDeprecationMessageMatches('/foo/'); + $this->expectUserDeprecationMessageMatches('/bar/'); + + @trigger_error('...foo...', E_USER_DEPRECATED); + @trigger_error('...bar...', E_USER_DEPRECATED); + } + + #[IgnoreDeprecations] + public function testExpectationOnDeprecationMessageMatchingRegularExpressionWorksWhenExpectedDeprecationIsNotTriggered(): void + { + $this->expectUserDeprecationMessageMatches('/message/'); + } + + #[IgnoreDeprecations] + public function testExpectationOnDeprecationMessageMatchingRegularExpressionWorksWhenUnepectedDeprecationIsTriggered(): void + { + $this->expectUserDeprecationMessageMatches('/message/'); + + @trigger_error('something else', E_USER_DEPRECATED); + } +} diff --git a/tests/end-to-end/generic/abstract-test-class/abstract-test-class-with-test-suffix.phpt b/tests/end-to-end/generic/abstract-test-class/abstract-test-class-with-test-suffix.phpt new file mode 100644 index 00000000000..f9a8a9cfe16 --- /dev/null +++ b/tests/end-to-end/generic/abstract-test-class/abstract-test-class-with-test-suffix.phpt @@ -0,0 +1,12 @@ +--TEST-- +phpunit ../../../_files/abstract/with-test-suffix/AbstractTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +Class PHPUnit\TestFixture\AbstractTest declared in %sAbstractTest.php is abstract diff --git a/tests/end-to-end/generic/abstract-test-class/abstract-test-class-without-test-suffix.phpt b/tests/end-to-end/generic/abstract-test-class/abstract-test-class-without-test-suffix.phpt new file mode 100644 index 00000000000..691bbd80eda --- /dev/null +++ b/tests/end-to-end/generic/abstract-test-class/abstract-test-class-without-test-suffix.phpt @@ -0,0 +1,12 @@ +--TEST-- +phpunit ../../../_files/abstract/without-test-suffix/AbstractTestCase.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +Class PHPUnit\TestFixture\AbstractTestCase declared in %sAbstractTestCase.php is abstract diff --git a/tests/end-to-end/generic/abstract-test-class/concrete-test-class-extending-abstract-test-class-with-test-suffix.phpt b/tests/end-to-end/generic/abstract-test-class/concrete-test-class-extending-abstract-test-class-with-test-suffix.phpt new file mode 100644 index 00000000000..f25b38b31c3 --- /dev/null +++ b/tests/end-to-end/generic/abstract-test-class/concrete-test-class-extending-abstract-test-class-with-test-suffix.phpt @@ -0,0 +1,20 @@ +--TEST-- +phpunit ../../../_files/abstract/with-test-suffix/ConcreteTestClassExtendingAbstractTestClassWithTestSuffixTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/generic/abstract-test-class/concrete-test-class-extending-abstract-test-class-without-test-suffix.phpt b/tests/end-to-end/generic/abstract-test-class/concrete-test-class-extending-abstract-test-class-without-test-suffix.phpt new file mode 100644 index 00000000000..4e4d5da20fb --- /dev/null +++ b/tests/end-to-end/generic/abstract-test-class/concrete-test-class-extending-abstract-test-class-without-test-suffix.phpt @@ -0,0 +1,20 @@ +--TEST-- +phpunit ../../../_files/abstract/without-test-suffix/ConcreteTestClassExtendingAbstractTestClassWithoutTestSuffixTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/generic/abstract-test-class/directory-with-concrete-test-class-and-abstract-test-class-with-test-suffix.phpt b/tests/end-to-end/generic/abstract-test-class/directory-with-concrete-test-class-and-abstract-test-class-with-test-suffix.phpt new file mode 100644 index 00000000000..e461db6b5c6 --- /dev/null +++ b/tests/end-to-end/generic/abstract-test-class/directory-with-concrete-test-class-and-abstract-test-class-with-test-suffix.phpt @@ -0,0 +1,25 @@ +--TEST-- +phpunit ../../../_files/abstract/with-test-suffix +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Class PHPUnit\TestFixture\AbstractTest declared in %sAbstractTest.php is abstract + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/generic/abstract-test-class/directory-with-concrete-test-class-and-abstract-test-class-without-test-suffix.phpt b/tests/end-to-end/generic/abstract-test-class/directory-with-concrete-test-class-and-abstract-test-class-without-test-suffix.phpt new file mode 100644 index 00000000000..cf8ec7fa06a --- /dev/null +++ b/tests/end-to-end/generic/abstract-test-class/directory-with-concrete-test-class-and-abstract-test-class-without-test-suffix.phpt @@ -0,0 +1,20 @@ +--TEST-- +phpunit ../../../_files/abstract/without-test-suffix +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/generic/assertion.phpt b/tests/end-to-end/generic/assertion.phpt new file mode 100644 index 00000000000..bff737be7b3 --- /dev/null +++ b/tests/end-to-end/generic/assertion.phpt @@ -0,0 +1,34 @@ +--TEST-- +phpunit ../../_files/AssertionExampleTest.php +--SKIPIF-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +F 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 failure: + +1) PHPUnit\TestFixture\AssertionExampleTest::testOne +assert(false) + +%s:%i +%s:%i + +FAILURES! +Tests: 1, Assertions: 1, Failures: 1. diff --git a/tests/end-to-end/generic/class-name-does-not-match-filename-invocation-with-path.phpt b/tests/end-to-end/generic/class-name-does-not-match-filename-invocation-with-path.phpt new file mode 100644 index 00000000000..f5f2acd9c38 --- /dev/null +++ b/tests/end-to-end/generic/class-name-does-not-match-filename-invocation-with-path.phpt @@ -0,0 +1,21 @@ +--TEST-- +phpunit --version +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +There was 1 PHPUnit test runner warning: + +1) Class WrongClassNameTest cannot be found in %sWrongClassNameTest.php + +No tests executed! diff --git a/tests/end-to-end/generic/class-name-does-not-match-filename-invocation-without-path.phpt b/tests/end-to-end/generic/class-name-does-not-match-filename-invocation-without-path.phpt new file mode 100644 index 00000000000..6cfa84128f6 --- /dev/null +++ b/tests/end-to-end/generic/class-name-does-not-match-filename-invocation-without-path.phpt @@ -0,0 +1,23 @@ +--TEST-- +phpunit --version +--FILE-- +run($_SERVER['argv']); +?> +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +There was 1 PHPUnit test runner warning: + +1) Class WrongClassNameTest cannot be found in %sWrongClassNameTest.php + +No tests executed! diff --git a/tests/end-to-end/generic/configuration-does-not-exist.phpt b/tests/end-to-end/generic/configuration-does-not-exist.phpt new file mode 100644 index 00000000000..10fd993795a --- /dev/null +++ b/tests/end-to-end/generic/configuration-does-not-exist.phpt @@ -0,0 +1,15 @@ +--TEST-- +phpunit --configuration does-not-exist.xml +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Could not read XML from file "does-not-exist.xml" diff --git a/tests/end-to-end/generic/controlled-garbage-collection.phpt b/tests/end-to-end/generic/controlled-garbage-collection.phpt new file mode 100644 index 00000000000..5e08f9c6401 --- /dev/null +++ b/tests/end-to-end/generic/controlled-garbage-collection.phpt @@ -0,0 +1,43 @@ +--TEST-- +phpunit --configuration=__DIR__.'/../_files/controlled-garbage-collection' +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Runner Disabled Garbage Collection +Test Runner Triggered Garbage Collection +Test Suite Started (%s%ephpunit.xml, 2 tests) +Test Suite Started (default, 2 tests) +Test Suite Started (PHPUnit\TestFixture\GarbageCollection\GarbageCollectionTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\GarbageCollection\GarbageCollectionTest::testOne) +Test Prepared (PHPUnit\TestFixture\GarbageCollection\GarbageCollectionTest::testOne) +Test Passed (PHPUnit\TestFixture\GarbageCollection\GarbageCollectionTest::testOne) +Test Finished (PHPUnit\TestFixture\GarbageCollection\GarbageCollectionTest::testOne) +Test Runner Triggered Garbage Collection +Test Preparation Started (PHPUnit\TestFixture\GarbageCollection\GarbageCollectionTest::testTwo) +Test Prepared (PHPUnit\TestFixture\GarbageCollection\GarbageCollectionTest::testTwo) +Test Passed (PHPUnit\TestFixture\GarbageCollection\GarbageCollectionTest::testTwo) +Test Finished (PHPUnit\TestFixture\GarbageCollection\GarbageCollectionTest::testTwo) +Test Runner Triggered Garbage Collection +Test Suite Finished (PHPUnit\TestFixture\GarbageCollection\GarbageCollectionTest, 2 tests) +Test Suite Finished (default, 2 tests) +Test Suite Finished (%s%ephpunit.xml, 2 tests) +Test Runner Execution Finished +Test Runner Triggered Garbage Collection +Test Runner Enabled Garbage Collection +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/generic/cwd-restore-after-test.phpt b/tests/end-to-end/generic/cwd-restore-after-test.phpt new file mode 100644 index 00000000000..cfd976f2a87 --- /dev/null +++ b/tests/end-to-end/generic/cwd-restore-after-test.phpt @@ -0,0 +1,18 @@ +--TEST-- +phpunit ../../_files/CwdRestoreTest.php +--FILE-- +run($_SERVER['argv']); + +var_dump($cwd === getcwd()); +--EXPECTF-- +bool(true) diff --git a/tests/end-to-end/generic/dataprovider-named-arguments.phpt b/tests/end-to-end/generic/dataprovider-named-arguments.phpt new file mode 100644 index 00000000000..8f14acebce7 --- /dev/null +++ b/tests/end-to-end/generic/dataprovider-named-arguments.phpt @@ -0,0 +1,20 @@ +--TEST-- +phpunit ../../_files/DataProviderNamedArgumentsTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +.. 2 / 2 (100%) + +Time: %s, Memory: %s + +OK (2 tests, 2 assertions) diff --git a/tests/end-to-end/generic/debug-with-telemetry.phpt b/tests/end-to-end/generic/debug-with-telemetry.phpt new file mode 100644 index 00000000000..58a8ee1d1cb --- /dev/null +++ b/tests/end-to-end/generic/debug-with-telemetry.phpt @@ -0,0 +1,30 @@ +--TEST-- +phpunit --debug --with-telemetry ../../_files/BankAccountTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] PHPUnit Started (PHPUnit %s using %s) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Runner Configured +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Event Facade Sealed +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Suite Loaded (1 test) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Runner Started +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Suite Sorted +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Runner Execution Started (1 test) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Suite Started (PHPUnit\TestFixture\Basic\SuccessTest, 1 test) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Preparation Started (PHPUnit\TestFixture\Basic\SuccessTest::testOne) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Prepared (PHPUnit\TestFixture\Basic\SuccessTest::testOne) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Passed (PHPUnit\TestFixture\Basic\SuccessTest::testOne) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Finished (PHPUnit\TestFixture\Basic\SuccessTest::testOne) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Suite Finished (PHPUnit\TestFixture\Basic\SuccessTest, 1 test) +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Runner Execution Finished +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] Test Runner Finished +[%s:%s:%s.%s / %s:%s:%s.%s] [%s bytes] PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/generic/debug.phpt b/tests/end-to-end/generic/debug.phpt new file mode 100644 index 00000000000..d43cd14c4b3 --- /dev/null +++ b/tests/end-to-end/generic/debug.phpt @@ -0,0 +1,29 @@ +--TEST-- +phpunit --debug ../../_files/BankAccountTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Basic\SuccessTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Basic\SuccessTest::testOne) +Test Prepared (PHPUnit\TestFixture\Basic\SuccessTest::testOne) +Test Passed (PHPUnit\TestFixture\Basic\SuccessTest::testOne) +Test Finished (PHPUnit\TestFixture\Basic\SuccessTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\Basic\SuccessTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/generic/default-isolation.phpt b/tests/end-to-end/generic/default-isolation.phpt new file mode 100644 index 00000000000..ef1ce89a10f --- /dev/null +++ b/tests/end-to-end/generic/default-isolation.phpt @@ -0,0 +1,21 @@ +--TEST-- +phpunit --process-isolation ../../_files/BankAccountTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +... 3 / 3 (100%) + +Time: %s, Memory: %s + +OK (3 tests, 3 assertions) diff --git a/tests/end-to-end/generic/default.phpt b/tests/end-to-end/generic/default.phpt new file mode 100644 index 00000000000..202968bd678 --- /dev/null +++ b/tests/end-to-end/generic/default.phpt @@ -0,0 +1,20 @@ +--TEST-- +phpunit ../../_files/BankAccountTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +... 3 / 3 (100%) + +Time: %s, Memory: %s + +OK (3 tests, 3 assertions) diff --git a/tests/end-to-end/generic/defaulttestsuite-using-testsuite-without-name.phpt b/tests/end-to-end/generic/defaulttestsuite-using-testsuite-without-name.phpt new file mode 100644 index 00000000000..e44a3990bcc --- /dev/null +++ b/tests/end-to-end/generic/defaulttestsuite-using-testsuite-without-name.phpt @@ -0,0 +1,17 @@ +--TEST-- +phpunit --configuration=__DIR__.'/../../_files/configuration.testsuite_no_name.xml' +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Cannot load XML configuration file %sconfiguration.testsuite_no_name.xml because it has validation errors: + + Line 3: + - Element 'testsuite': The attribute 'name' is required but missing. diff --git a/tests/end-to-end/generic/defaulttestsuite-using-testsuite.phpt b/tests/end-to-end/generic/defaulttestsuite-using-testsuite.phpt new file mode 100644 index 00000000000..244a6cb36f6 --- /dev/null +++ b/tests/end-to-end/generic/defaulttestsuite-using-testsuite.phpt @@ -0,0 +1,26 @@ +--TEST-- +phpunit --testdox --configuration=__DIR__.'/../_files/configuration.defaulttestsuite.xml' --testsuite 'First' +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +Time: %s, Memory: %s + +Dummy Foo (PHPUnit\TestFixture\DummyFoo) + ✔ Foo equals foo + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/generic/defaulttestsuite.phpt b/tests/end-to-end/generic/defaulttestsuite.phpt new file mode 100644 index 00000000000..1c59a2f17bf --- /dev/null +++ b/tests/end-to-end/generic/defaulttestsuite.phpt @@ -0,0 +1,24 @@ +--TEST-- +phpunit --testdox --configuration=__DIR__.'/../_files/configuration.defaulttestsuite.xml' +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +Time: %s, Memory: %s + +Dummy Bar (PHPUnit\TestFixture\DummyBar) + ✔ Bar equals bar + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/generic/deprecation-can-be-expected.phpt b/tests/end-to-end/generic/deprecation-can-be-expected.phpt new file mode 100644 index 00000000000..17ea5eda2e1 --- /dev/null +++ b/tests/end-to-end/generic/deprecation-can-be-expected.phpt @@ -0,0 +1,36 @@ +--TEST-- +E_USER_DEPRECATED issues can be expected +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +..FF..FF 8 / 8 (100%) + +Time: %s, Memory: %s + +There were 4 failures: + +1) PHPUnit\TestFixture\Event\TestForDeprecatedFeatureTest::testExpectationOnExactDeprecationMessageWorksWhenExpectedDeprecationIsNotTriggered +Expected deprecation with message "message" was not triggered + +2) PHPUnit\TestFixture\Event\TestForDeprecatedFeatureTest::testExpectationOnExactDeprecationMessageWorksWhenUnexpectedDeprecationIsTriggered +Expected deprecation with message "message" was not triggered + +3) PHPUnit\TestFixture\Event\TestForDeprecatedFeatureTest::testExpectationOnDeprecationMessageMatchingRegularExpressionWorksWhenExpectedDeprecationIsNotTriggered +Expected deprecation with message matching regular expression "/message/" was not triggered + +4) PHPUnit\TestFixture\Event\TestForDeprecatedFeatureTest::testExpectationOnDeprecationMessageMatchingRegularExpressionWorksWhenUnepectedDeprecationIsTriggered +Expected deprecation with message matching regular expression "/message/" was not triggered + +FAILURES! +Tests: 8, Assertions: 10, Failures: 4. diff --git a/tests/end-to-end/generic/deprecations-can-be-ignored-using-attribute.phpt b/tests/end-to-end/generic/deprecations-can-be-ignored-using-attribute.phpt new file mode 100644 index 00000000000..cb741279e6e --- /dev/null +++ b/tests/end-to-end/generic/deprecations-can-be-ignored-using-attribute.phpt @@ -0,0 +1,31 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5532 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +.D.D 4 / 4 (100%) + +Time: %s, Memory: %s + +2 tests triggered 2 deprecations: + +1) %sIgnoreDeprecationsTest.php:%d +message + +2) %sIgnoreDeprecationsTest.php:%d +message + +OK, but there were issues! +Tests: 4, Assertions: 6, Deprecations: 2. diff --git a/tests/end-to-end/generic/empty-testcase.phpt b/tests/end-to-end/generic/empty-testcase.phpt new file mode 100644 index 00000000000..34013f76f15 --- /dev/null +++ b/tests/end-to-end/generic/empty-testcase.phpt @@ -0,0 +1,20 @@ +--TEST-- +phpunit ../../_files/EmptyTestCaseTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +There was 1 PHPUnit test runner warning: + +1) No tests found in class "PHPUnit\TestFixture\EmptyTestCaseTest". + +No tests executed! diff --git a/tests/end-to-end/generic/exception-in-mock-destructor.phpt b/tests/end-to-end/generic/exception-in-mock-destructor.phpt new file mode 100644 index 00000000000..3a2e2e4f86f --- /dev/null +++ b/tests/end-to-end/generic/exception-in-mock-destructor.phpt @@ -0,0 +1,28 @@ +--TEST-- +phpunit ../../_files/ExceptionInMockDestructorTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +E 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 error: + +1) PHPUnit\TestFixture\ExceptionInMockDestructorTest::testOne +Exception: Some exception. + +%sExceptionInMockDestructor.php:%d + +ERRORS! +Tests: 1, Assertions: 1, Errors: 1. diff --git a/tests/end-to-end/generic/exception-stack.phpt b/tests/end-to-end/generic/exception-stack.phpt new file mode 100644 index 00000000000..ea9de2142a5 --- /dev/null +++ b/tests/end-to-end/generic/exception-stack.phpt @@ -0,0 +1,67 @@ +--TEST-- +phpunit ../../_files/ExceptionStackTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +EE 2 / 2 (100%) + +Time: %s, Memory: %s + +There were 2 errors: + +1) PHPUnit\TestFixture\ExceptionStackTest::testPrintingChildException +PHPUnit\Framework\Exception: Child exception +message +Failed asserting that two arrays are equal. +--- Expected ++++ Actual +@@ @@ + Array ( +- 0 => 1 ++ 0 => 2 + ) + + +%s:%i + +Caused by +message +Failed asserting that two arrays are equal. +--- Expected ++++ Actual +@@ @@ + Array ( +- 0 => 1 ++ 0 => 2 + ) + +%s:%i + +2) PHPUnit\TestFixture\ExceptionStackTest::testNestedExceptions +Exception: One + +%s:%i + +Caused by +InvalidArgumentException: Two + +%s:%i + +Caused by +Exception: Three + +%s:%i + +ERRORS! +Tests: 2, Assertions: 1, Errors: 2. diff --git a/tests/end-to-end/generic/expect-error-log-fail-with-open_basedir.phpt b/tests/end-to-end/generic/expect-error-log-fail-with-open_basedir.phpt new file mode 100644 index 00000000000..a6b92bc66a4 --- /dev/null +++ b/tests/end-to-end/generic/expect-error-log-fail-with-open_basedir.phpt @@ -0,0 +1,42 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6197 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\ExpectNoErrorLog\ExpectErrorLogFailTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\ExpectNoErrorLog\ExpectErrorLogFailTest::testOne) +Test Prepared (PHPUnit\TestFixture\ExpectNoErrorLog\ExpectErrorLogFailTest::testOne) +Test Errored (PHPUnit\TestFixture\ExpectNoErrorLog\ExpectErrorLogFailTest::testOne) +Could not create writable file for error_log() +Test Finished (PHPUnit\TestFixture\ExpectNoErrorLog\ExpectErrorLogFailTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\ExpectNoErrorLog\ExpectErrorLogFailTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/generic/expect-error-log-fail.phpt b/tests/end-to-end/generic/expect-error-log-fail.phpt new file mode 100644 index 00000000000..757457699f6 --- /dev/null +++ b/tests/end-to-end/generic/expect-error-log-fail.phpt @@ -0,0 +1,31 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/2155 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\ExpectNoErrorLog\ExpectErrorLogFailTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\ExpectNoErrorLog\ExpectErrorLogFailTest::testOne) +Test Prepared (PHPUnit\TestFixture\ExpectNoErrorLog\ExpectErrorLogFailTest::testOne) +Test Failed (PHPUnit\TestFixture\ExpectNoErrorLog\ExpectErrorLogFailTest::testOne) +error_log() was not called +Failed asserting that a string is not empty. +Test Finished (PHPUnit\TestFixture\ExpectNoErrorLog\ExpectErrorLogFailTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\ExpectNoErrorLog\ExpectErrorLogFailTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/generic/expect-error-log-with-open_basedir.phpt b/tests/end-to-end/generic/expect-error-log-with-open_basedir.phpt new file mode 100644 index 00000000000..3c71500a56c --- /dev/null +++ b/tests/end-to-end/generic/expect-error-log-with-open_basedir.phpt @@ -0,0 +1,33 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6197 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\ExpectErrorLog\ExpectErrorLogTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\ExpectErrorLog\ExpectErrorLogTest::testOne) +Test Prepared (PHPUnit\TestFixture\ExpectErrorLog\ExpectErrorLogTest::testOne) +logged a side effect +Test Errored (PHPUnit\TestFixture\ExpectErrorLog\ExpectErrorLogTest::testOne) +Could not create writable file for error_log() +Test Finished (PHPUnit\TestFixture\ExpectErrorLog\ExpectErrorLogTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\ExpectErrorLog\ExpectErrorLogTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/generic/expect-error-log.phpt b/tests/end-to-end/generic/expect-error-log.phpt new file mode 100644 index 00000000000..d2171768932 --- /dev/null +++ b/tests/end-to-end/generic/expect-error-log.phpt @@ -0,0 +1,29 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/2155 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\ExpectErrorLog\ExpectErrorLogTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\ExpectErrorLog\ExpectErrorLogTest::testOne) +Test Prepared (PHPUnit\TestFixture\ExpectErrorLog\ExpectErrorLogTest::testOne) +Test Passed (PHPUnit\TestFixture\ExpectErrorLog\ExpectErrorLogTest::testOne) +Test Finished (PHPUnit\TestFixture\ExpectErrorLog\ExpectErrorLogTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\ExpectErrorLog\ExpectErrorLogTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/generic/expecting-exceptions.phpt b/tests/end-to-end/generic/expecting-exceptions.phpt new file mode 100644 index 00000000000..ff6c3b10fc3 --- /dev/null +++ b/tests/end-to-end/generic/expecting-exceptions.phpt @@ -0,0 +1,38 @@ +--TEST-- +phpunit ../_files/ExpectingExceptionsTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +.F.F.F.F.F 10 / 10 (100%) + +Time: %s, Memory: %s + +There were 5 failures: + +1) PHPUnit\TestFixture\ExpectingExceptionsTest::testFailsWhenExpectedExceptionIsNotThrown +Failed asserting that exception of type "Exception" is thrown. + +2) PHPUnit\TestFixture\ExpectingExceptionsTest::testFailsWhenExpectedExceptionIsThrownAndDoesNotHaveMessageThatIsOrContainsExpectedMessage +Failed asserting that exception message '' contains 'message'. + +3) PHPUnit\TestFixture\ExpectingExceptionsTest::testFailsWhenExpectedExceptionIsThrownAndDoesNotHaveMessageThatMatchesRegularExpression +Failed asserting that exception message '' matches '/message/'. + +4) PHPUnit\TestFixture\ExpectingExceptionsTest::testFailsWhenExpectedExceptionIsThrownAndDoesNotHaveExpectedCode +Failed asserting that 0 is equal to expected exception code 1234. + +5) PHPUnit\TestFixture\ExpectingExceptionsTest::testFailsWhenExpectedExceptionObjectIsNotThrown +Failed asserting that exception of type "Exception" is thrown. + +FAILURES! +Tests: 10, Assertions: 18, Failures: 5. diff --git a/tests/end-to-end/failure-isolation.phpt b/tests/end-to-end/generic/failure-isolation.phpt similarity index 87% rename from tests/end-to-end/failure-isolation.phpt rename to tests/end-to-end/generic/failure-isolation.phpt index fdc95239998..6461d69cb5f 100644 --- a/tests/end-to-end/failure-isolation.phpt +++ b/tests/end-to-end/generic/failure-isolation.phpt @@ -2,15 +2,18 @@ phpunit --process-isolation ../../_files/FailureTest.php --FILE-- run($_SERVER['argv']); --EXPECTF-- PHPUnit %s by Sebastian Bergmann and contributors. +Runtime: %s + FFFFFFFFFFFFF 13 / 13 (100%) Time: %s, Memory: %s @@ -136,4 +139,4 @@ Failed asserting that string matches format description. %s:%i FAILURES! -Tests: 13, Assertions: 14, Failures: 13. +Tests: 13, Assertions: 15, Failures: 13. diff --git a/tests/end-to-end/generic/failure.phpt b/tests/end-to-end/generic/failure.phpt new file mode 100644 index 00000000000..2c46c7fb45b --- /dev/null +++ b/tests/end-to-end/generic/failure.phpt @@ -0,0 +1,141 @@ +--TEST-- +phpunit ../../_files/FailureTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +FFFFFFFFFFFFF 13 / 13 (100%) + +Time: %s, Memory: %s + +There were 13 failures: + +1) PHPUnit\TestFixture\FailureTest::testAssertArrayEqualsArray +message +Failed asserting that two arrays are equal. +--- Expected ++++ Actual +@@ @@ + Array ( +- 0 => 1 ++ 0 => 2 + ) + +%s:%i + +2) PHPUnit\TestFixture\FailureTest::testAssertIntegerEqualsInteger +message +Failed asserting that 2 matches expected 1. + +%s:%i + +3) PHPUnit\TestFixture\FailureTest::testAssertObjectEqualsObject +message +Failed asserting that two objects are equal. +--- Expected ++++ Actual +@@ @@ + stdClass Object ( +- 'foo' => 'bar' ++ 'bar' => 'foo' + ) + +%s:%i + +4) PHPUnit\TestFixture\FailureTest::testAssertNullEqualsString +message +Failed asserting that 'bar' matches expected null. + +%s:%i + +5) PHPUnit\TestFixture\FailureTest::testAssertStringEqualsString +message +Failed asserting that two strings are equal. +--- Expected ++++ Actual +@@ @@ +-'foo' ++'bar' + +%s:%i + +6) PHPUnit\TestFixture\FailureTest::testAssertTextEqualsText +message +Failed asserting that two strings are equal. +--- Expected ++++ Actual +@@ @@ + 'foo\n +-bar\n ++baz\n + ' + +%s:%i + +7) PHPUnit\TestFixture\FailureTest::testAssertStringMatchesFormat +message +Failed asserting that string matches format description. +--- Expected ++++ Actual +@@ @@ +-*%s* ++** + +%s:%i + +8) PHPUnit\TestFixture\FailureTest::testAssertNumericEqualsNumeric +message +Failed asserting that 2 matches expected 1. + +%s:%i + +9) PHPUnit\TestFixture\FailureTest::testAssertTextSameText +message +Failed asserting that two strings are identical. +--- Expected ++++ Actual +@@ @@ +-'foo' ++'bar' + +%s:%i + +10) PHPUnit\TestFixture\FailureTest::testAssertObjectSameObject +message +Failed asserting that two variables reference the same object. + +%s:%i + +11) PHPUnit\TestFixture\FailureTest::testAssertObjectSameNull +message +Failed asserting that null is identical to an object of class "stdClass". + +%s:%i + +12) PHPUnit\TestFixture\FailureTest::testAssertFloatSameFloat +message +Failed asserting that 1.5 is identical to 1.0. + +%s:%i + +13) PHPUnit\TestFixture\FailureTest::testAssertStringMatchesFormatFile +Failed asserting that string matches format description. +--- Expected ++++ Actual +@@ @@ +-FOO ++...BAR... + +%s:%i + +FAILURES! +Tests: 13, Assertions: 15, Failures: 13. diff --git a/tests/end-to-end/generic/force-covers-annotation.phpt b/tests/end-to-end/generic/force-covers-annotation.phpt new file mode 100644 index 00000000000..7ea1bc88985 --- /dev/null +++ b/tests/end-to-end/generic/force-covers-annotation.phpt @@ -0,0 +1,30 @@ +--TEST-- +phpunit ../../_files/BankAccountTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +R 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 risky test: + +1) PHPUnit\TestFixture\Test::testOne +This test does not define a code coverage target but is expected to do so + +%s:%d + +OK, but there were issues! +Tests: 1, Assertions: 1, Risky: 1. diff --git a/tests/end-to-end/generic/getActualOutputForAssertion.phpt b/tests/end-to-end/generic/getActualOutputForAssertion.phpt new file mode 100644 index 00000000000..d04aac8206e --- /dev/null +++ b/tests/end-to-end/generic/getActualOutputForAssertion.phpt @@ -0,0 +1,21 @@ +--TEST-- +phpunit ../../_files/ActualOutputTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/generic/ini-isolation.phpt b/tests/end-to-end/generic/ini-isolation.phpt new file mode 100644 index 00000000000..74879843e1d --- /dev/null +++ b/tests/end-to-end/generic/ini-isolation.phpt @@ -0,0 +1,23 @@ +--TEST-- +phpunit --process-isolation -d default_mimetype=application/x-test ../../_files/IniTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/generic/invokable-constraint-and-pipe-operator.phpt b/tests/end-to-end/generic/invokable-constraint-and-pipe-operator.phpt new file mode 100644 index 00000000000..46a0ad4ce4e --- /dev/null +++ b/tests/end-to-end/generic/invokable-constraint-and-pipe-operator.phpt @@ -0,0 +1,34 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6354 +--SKIPIF-- +')) { + print 'skip: PHP 8.5 is required.'; +} +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\InvokableConstraintAndPipeOperatorTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\InvokableConstraintAndPipeOperatorTest::testOne) +Test Prepared (PHPUnit\TestFixture\InvokableConstraintAndPipeOperatorTest::testOne) +Test Passed (PHPUnit\TestFixture\InvokableConstraintAndPipeOperatorTest::testOne) +Test Finished (PHPUnit\TestFixture\InvokableConstraintAndPipeOperatorTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\InvokableConstraintAndPipeOperatorTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/generic/multiple-arguments.phpt b/tests/end-to-end/generic/multiple-arguments.phpt new file mode 100644 index 00000000000..0b7865d34c0 --- /dev/null +++ b/tests/end-to-end/generic/multiple-arguments.phpt @@ -0,0 +1,21 @@ +--TEST-- +phpunit ../../_files/DummyFooTest.php ../../_files/DummyBarTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +.. 2 / 2 (100%) + +Time: %s, Memory: %s + +OK (2 tests, 2 assertions) diff --git a/tests/end-to-end/generic/no-output.phpt b/tests/end-to-end/generic/no-output.phpt new file mode 100644 index 00000000000..5267c89a6ac --- /dev/null +++ b/tests/end-to-end/generic/no-output.phpt @@ -0,0 +1,12 @@ +--TEST-- +phpunit --no-output ../../_files/BankAccountTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- diff --git a/tests/end-to-end/generic/no-progress.phpt b/tests/end-to-end/generic/no-progress.phpt new file mode 100644 index 00000000000..0071d1acbe5 --- /dev/null +++ b/tests/end-to-end/generic/no-progress.phpt @@ -0,0 +1,19 @@ +--TEST-- +phpunit --no-progress ../../_files/BankAccountTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +OK (3 tests, 3 assertions) diff --git a/tests/end-to-end/generic/no-results.phpt b/tests/end-to-end/generic/no-results.phpt new file mode 100644 index 00000000000..05759048bee --- /dev/null +++ b/tests/end-to-end/generic/no-results.phpt @@ -0,0 +1,19 @@ +--TEST-- +phpunit --no-results ../../_files/BankAccountTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +... 3 / 3 (100%) + +Time: %s, Memory: %s diff --git a/tests/end-to-end/generic/one-class-per-file-valid.phpt b/tests/end-to-end/generic/one-class-per-file-valid.phpt new file mode 100644 index 00000000000..af56e35a1a8 --- /dev/null +++ b/tests/end-to-end/generic/one-class-per-file-valid.phpt @@ -0,0 +1,22 @@ +--TEST-- +phpunit --version +--FILE-- +run($_SERVER['argv']); +?> +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/generic/outcome-and-issues-individual-options.phpt b/tests/end-to-end/generic/outcome-and-issues-individual-options.phpt new file mode 100644 index 00000000000..968644811f4 --- /dev/null +++ b/tests/end-to-end/generic/outcome-and-issues-individual-options.phpt @@ -0,0 +1,161 @@ +--TEST-- +phpunit --display-incomplete --display-skipped --display-deprecations --display-errors --display-notices --display-warnings ../_files/OutcomesAndIssuesTest +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +.RDNWFFFEEEDNWDNW 17 / 17 (100%) + +Time: %s, Memory: %s + +There were 3 errors: + +1) PHPUnit\TestFixture\OutcomesAndIssuesTest::testErrorWithDeprecation +Exception: exception message + +%sOutcomesAndIssuesTest.php:%d + +2) PHPUnit\TestFixture\OutcomesAndIssuesTest::testErrorWithNotice +Exception: exception message + +%sOutcomesAndIssuesTest.php:%d + +3) PHPUnit\TestFixture\OutcomesAndIssuesTest::testErrorWithWarning +Exception: exception message + +%sOutcomesAndIssuesTest.php:%d + +-- + +There were 3 failures: + +1) PHPUnit\TestFixture\OutcomesAndIssuesTest::testFailWithDeprecation +Failed asserting that false is true. + +%sOutcomesAndIssuesTest.php:%d + +2) PHPUnit\TestFixture\OutcomesAndIssuesTest::testFailWithNotice +Failed asserting that false is true. + +%sOutcomesAndIssuesTest.php:%d + +3) PHPUnit\TestFixture\OutcomesAndIssuesTest::testFailWithWarning +Failed asserting that false is true. + +%sOutcomesAndIssuesTest.php:%d + +-- + +There was 1 risky test: + +1) PHPUnit\TestFixture\OutcomesAndIssuesTest::testSuccessWithRisky +This test did not perform any assertions + +%sOutcomesAndIssuesTest.php:%d + +-- + +There were 3 incomplete tests: + +1) PHPUnit\TestFixture\OutcomesAndIssuesTest::testIncompleteWithDeprecation +incomplete message + +%sOutcomesAndIssuesTest.php:%d + +2) PHPUnit\TestFixture\OutcomesAndIssuesTest::testIncompleteWithNotice +incomplete message + +%sOutcomesAndIssuesTest.php:%d + +3) PHPUnit\TestFixture\OutcomesAndIssuesTest::testIncompleteWithWarning +incomplete message + +%sOutcomesAndIssuesTest.php:%d + +-- + +There were 3 skipped tests: + +1) PHPUnit\TestFixture\OutcomesAndIssuesTest::testSkippedWithDeprecation +skipped message + +2) PHPUnit\TestFixture\OutcomesAndIssuesTest::testSkippedWithNotice +skipped message + +3) PHPUnit\TestFixture\OutcomesAndIssuesTest::testSkippedWithWarning +skipped message + +-- + +5 tests triggered 5 warnings: + +1) %sOutcomesAndIssuesTest.php:%d +warning message + +2) %sOutcomesAndIssuesTest.php:%d +warning message + +3) %sOutcomesAndIssuesTest.php:%d +warning message + +4) %sOutcomesAndIssuesTest.php:%d +warning message + +5) %sOutcomesAndIssuesTest.php:%d +warning message + +-- + +5 tests triggered 5 notices: + +1) %sOutcomesAndIssuesTest.php:%d +notice message + +2) %sOutcomesAndIssuesTest.php:%d +notice message + +3) %sOutcomesAndIssuesTest.php:%d +notice message + +4) %sOutcomesAndIssuesTest.php:%d +notice message + +5) %sOutcomesAndIssuesTest.php:%d +notice message + +-- + +5 tests triggered 5 deprecations: + +1) %sOutcomesAndIssuesTest.php:%d +deprecation message + +2) %sOutcomesAndIssuesTest.php:%d +deprecation message + +3) %sOutcomesAndIssuesTest.php:%d +deprecation message + +4) %sOutcomesAndIssuesTest.php:%d +deprecation message + +5) %sOutcomesAndIssuesTest.php:%d +deprecation message + +ERRORS! +Tests: 17, Assertions: 7, Errors: 3, Failures: 3, Warnings: 5, Deprecations: 5, Notices: 5, Skipped: 3, Incomplete: 3, Risky: 1. diff --git a/tests/end-to-end/generic/outcome-and-issues.phpt b/tests/end-to-end/generic/outcome-and-issues.phpt new file mode 100644 index 00000000000..a3e35b42722 --- /dev/null +++ b/tests/end-to-end/generic/outcome-and-issues.phpt @@ -0,0 +1,156 @@ +--TEST-- +phpunit --display-all-issues ../_files/OutcomesAndIssuesTest +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +.RDNWFFFEEEDNWDNW 17 / 17 (100%) + +Time: %s, Memory: %s + +There were 3 errors: + +1) PHPUnit\TestFixture\OutcomesAndIssuesTest::testErrorWithDeprecation +Exception: exception message + +%sOutcomesAndIssuesTest.php:%d + +2) PHPUnit\TestFixture\OutcomesAndIssuesTest::testErrorWithNotice +Exception: exception message + +%sOutcomesAndIssuesTest.php:%d + +3) PHPUnit\TestFixture\OutcomesAndIssuesTest::testErrorWithWarning +Exception: exception message + +%sOutcomesAndIssuesTest.php:%d + +-- + +There were 3 failures: + +1) PHPUnit\TestFixture\OutcomesAndIssuesTest::testFailWithDeprecation +Failed asserting that false is true. + +%sOutcomesAndIssuesTest.php:%d + +2) PHPUnit\TestFixture\OutcomesAndIssuesTest::testFailWithNotice +Failed asserting that false is true. + +%sOutcomesAndIssuesTest.php:%d + +3) PHPUnit\TestFixture\OutcomesAndIssuesTest::testFailWithWarning +Failed asserting that false is true. + +%sOutcomesAndIssuesTest.php:%d + +-- + +There was 1 risky test: + +1) PHPUnit\TestFixture\OutcomesAndIssuesTest::testSuccessWithRisky +This test did not perform any assertions + +%sOutcomesAndIssuesTest.php:%d + +-- + +There were 3 incomplete tests: + +1) PHPUnit\TestFixture\OutcomesAndIssuesTest::testIncompleteWithDeprecation +incomplete message + +%sOutcomesAndIssuesTest.php:%d + +2) PHPUnit\TestFixture\OutcomesAndIssuesTest::testIncompleteWithNotice +incomplete message + +%sOutcomesAndIssuesTest.php:%d + +3) PHPUnit\TestFixture\OutcomesAndIssuesTest::testIncompleteWithWarning +incomplete message + +%sOutcomesAndIssuesTest.php:%d + +-- + +There were 3 skipped tests: + +1) PHPUnit\TestFixture\OutcomesAndIssuesTest::testSkippedWithDeprecation +skipped message + +2) PHPUnit\TestFixture\OutcomesAndIssuesTest::testSkippedWithNotice +skipped message + +3) PHPUnit\TestFixture\OutcomesAndIssuesTest::testSkippedWithWarning +skipped message + +-- + +5 tests triggered 5 warnings: + +1) %sOutcomesAndIssuesTest.php:%d +warning message + +2) %sOutcomesAndIssuesTest.php:%d +warning message + +3) %sOutcomesAndIssuesTest.php:%d +warning message + +4) %sOutcomesAndIssuesTest.php:%d +warning message + +5) %sOutcomesAndIssuesTest.php:%d +warning message + +-- + +5 tests triggered 5 notices: + +1) %sOutcomesAndIssuesTest.php:%d +notice message + +2) %sOutcomesAndIssuesTest.php:%d +notice message + +3) %sOutcomesAndIssuesTest.php:%d +notice message + +4) %sOutcomesAndIssuesTest.php:%d +notice message + +5) %sOutcomesAndIssuesTest.php:%d +notice message + +-- + +5 tests triggered 5 deprecations: + +1) %sOutcomesAndIssuesTest.php:%d +deprecation message + +2) %sOutcomesAndIssuesTest.php:%d +deprecation message + +3) %sOutcomesAndIssuesTest.php:%d +deprecation message + +4) %sOutcomesAndIssuesTest.php:%d +deprecation message + +5) %sOutcomesAndIssuesTest.php:%d +deprecation message + +ERRORS! +Tests: 17, Assertions: 7, Errors: 3, Failures: 3, Warnings: 5, Deprecations: 5, Notices: 5, Skipped: 3, Incomplete: 3, Risky: 1. diff --git a/tests/end-to-end/generic/output-isolation.phpt b/tests/end-to-end/generic/output-isolation.phpt new file mode 100644 index 00000000000..b2594bad0c8 --- /dev/null +++ b/tests/end-to-end/generic/output-isolation.phpt @@ -0,0 +1,23 @@ +--TEST-- +phpunit --process-isolation --filter testExpectOutputStringFooActualFoo ../../_files/OutputTestCase.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/generic/phar-extension-suppressed.phpt b/tests/end-to-end/generic/phar-extension-suppressed.phpt new file mode 100644 index 00000000000..eb1c8ebf758 --- /dev/null +++ b/tests/end-to-end/generic/phar-extension-suppressed.phpt @@ -0,0 +1,27 @@ +--TEST-- +phpunit --configuration tests/_files/phar-extension-bootstrap/phpunit.xml --no-extensions +--SKIPIF-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/generic/report-tests-performing-assertions-when-annotated-with-does-not-perform-assertions.phpt b/tests/end-to-end/generic/report-tests-performing-assertions-when-annotated-with-does-not-perform-assertions.phpt new file mode 100644 index 00000000000..ecb204a446b --- /dev/null +++ b/tests/end-to-end/generic/report-tests-performing-assertions-when-annotated-with-does-not-perform-assertions.phpt @@ -0,0 +1,28 @@ +--TEST-- +phpunit ../_files/DoesNotPerformAssertionsButPerformingAssertionsTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +R 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 risky test: + +1) PHPUnit\TestFixture\DoesNotPerformAssertionsButPerformingAssertionsTest::testFalseAndTrueAreStillFine +This test is not expected to perform assertions but performed 2 assertions + +%s:%d + +OK, but there were issues! +Tests: 1, Assertions: 2, Risky: 1. diff --git a/tests/end-to-end/generic/report-useless-tests-incomplete.phpt b/tests/end-to-end/generic/report-useless-tests-incomplete.phpt new file mode 100644 index 00000000000..f2719d5db47 --- /dev/null +++ b/tests/end-to-end/generic/report-useless-tests-incomplete.phpt @@ -0,0 +1,21 @@ +--TEST-- +phpunit ../../_files/IncompleteTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +I 1 / 1 (100%) + +Time: %s, Memory: %s + +OK, but there were issues! +Tests: 1, Assertions: 0, Incomplete: 1. diff --git a/tests/end-to-end/generic/report-useless-tests-isolation.phpt b/tests/end-to-end/generic/report-useless-tests-isolation.phpt new file mode 100644 index 00000000000..cb588744a75 --- /dev/null +++ b/tests/end-to-end/generic/report-useless-tests-isolation.phpt @@ -0,0 +1,29 @@ +--TEST-- +phpunit --process-isolation ../../_files/IncompleteTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +R 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 risky test: + +1) PHPUnit\TestFixture\NothingTest::testNothing +This test did not perform any assertions + +%s:%d + +OK, but there were issues! +Tests: 1, Assertions: 0, Risky: 1. diff --git a/tests/end-to-end/generic/report-useless-tests.phpt b/tests/end-to-end/generic/report-useless-tests.phpt new file mode 100644 index 00000000000..633c9bb51d7 --- /dev/null +++ b/tests/end-to-end/generic/report-useless-tests.phpt @@ -0,0 +1,28 @@ +--TEST-- +phpunit ../../_files/NothingTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +R 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 risky test: + +1) PHPUnit\TestFixture\NothingTest::testNothing +This test did not perform any assertions + +%s:%d + +OK, but there were issues! +Tests: 1, Assertions: 0, Risky: 1. diff --git a/tests/end-to-end/generic/separate-processes-test.phpt b/tests/end-to-end/generic/separate-processes-test.phpt new file mode 100644 index 00000000000..60c12a31238 --- /dev/null +++ b/tests/end-to-end/generic/separate-processes-test.phpt @@ -0,0 +1,29 @@ +--TEST-- +phpunit --no-configuration ../../_files/SeparateProcessesTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +EE 2 / 2 (100%) + +Time: %s, Memory: %s + +There were 2 errors: + +1) PHPUnit\TestFixture\SeparateProcessesTest::testFoo +Test was run in child process and ended unexpectedly + +2) PHPUnit\TestFixture\SeparateProcessesTest::testBar +Test was run in child process and ended unexpectedly + +ERRORS! +Tests: 2, Assertions: 0, Errors: 2. diff --git a/tests/end-to-end/generic/standardtestsuiteloader-issue-4192-1.phpt b/tests/end-to-end/generic/standardtestsuiteloader-issue-4192-1.phpt new file mode 100644 index 00000000000..c181f974542 --- /dev/null +++ b/tests/end-to-end/generic/standardtestsuiteloader-issue-4192-1.phpt @@ -0,0 +1,21 @@ +--TEST-- +phpunit ../../_files/ConcreteTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +.. 2 / 2 (100%) + +Time: %s, Memory: %s + +OK (2 tests, 2 assertions) diff --git a/tests/end-to-end/generic/standardtestsuiteloader-issue-4192-2.phpt b/tests/end-to-end/generic/standardtestsuiteloader-issue-4192-2.phpt new file mode 100644 index 00000000000..c068c550957 --- /dev/null +++ b/tests/end-to-end/generic/standardtestsuiteloader-issue-4192-2.phpt @@ -0,0 +1,21 @@ +--TEST-- +phpunit ../../_files/ConcreteTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +.. 2 / 2 (100%) + +Time: %s, Memory: %s + +OK (2 tests, 2 assertions) diff --git a/tests/end-to-end/generic/test-suffix-multiple.phpt b/tests/end-to-end/generic/test-suffix-multiple.phpt new file mode 100644 index 00000000000..d4b7b552463 --- /dev/null +++ b/tests/end-to-end/generic/test-suffix-multiple.phpt @@ -0,0 +1,24 @@ +--TEST-- +phpunit --test-suffix .test.php,.my.php ../../_files/ +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +..... 5 / 5 (100%) + +Time: %s, Memory: %s + +OK (5 tests, 5 assertions) diff --git a/tests/end-to-end/generic/test-suffix-single.phpt b/tests/end-to-end/generic/test-suffix-single.phpt new file mode 100644 index 00000000000..8ca33638a2b --- /dev/null +++ b/tests/end-to-end/generic/test-suffix-single.phpt @@ -0,0 +1,22 @@ +--TEST-- +phpunit --test-suffix .test.php ../../_files/ +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +... 3 / 3 (100%) + +Time: %s, Memory: %s + +OK (3 tests, 3 assertions) diff --git a/tests/end-to-end/generic/transform-exception-hook-method.phpt b/tests/end-to-end/generic/transform-exception-hook-method.phpt new file mode 100644 index 00000000000..f6a0fdcc9d3 --- /dev/null +++ b/tests/end-to-end/generic/transform-exception-hook-method.phpt @@ -0,0 +1,31 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5300 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +E 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 error: + +1) PHPUnit\TestFixture\TransformExceptionHookMethod\Test::testOne +PHPUnit\TestFixture\TransformExceptionHookMethod\TransformedException: transformed message + +%s%eTest.php:24 + +ERRORS! +Tests: 1, Assertions: 0, Errors: 1. diff --git a/tests/end-to-end/generic/unexpected-output-with-progress-printing.phpt b/tests/end-to-end/generic/unexpected-output-with-progress-printing.phpt new file mode 100644 index 00000000000..b18c34a5ae1 --- /dev/null +++ b/tests/end-to-end/generic/unexpected-output-with-progress-printing.phpt @@ -0,0 +1,28 @@ +--TEST-- +phpunit ../../_files/UnexpectedOutputTest.php +--SKIPIF-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +array(1) { + ["foo"]=> + string(3) "bar" +} +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/generic/unexpected-output-without-progress-printing.phpt b/tests/end-to-end/generic/unexpected-output-without-progress-printing.phpt new file mode 100644 index 00000000000..ee3449b079f --- /dev/null +++ b/tests/end-to-end/generic/unexpected-output-without-progress-printing.phpt @@ -0,0 +1,27 @@ +--TEST-- +phpunit ../../_files/UnexpectedOutputTest.php +--SKIPIF-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +array(1) { + ["foo"]=> + string(3) "bar" +} +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/getActualOutputForAssertion.phpt b/tests/end-to-end/getActualOutputForAssertion.phpt deleted file mode 100644 index ce89ebb9b50..00000000000 --- a/tests/end-to-end/getActualOutputForAssertion.phpt +++ /dev/null @@ -1,18 +0,0 @@ ---TEST-- -phpunit ../../_files/ActualOutputTest.php ---FILE-- - + + + + tests/foo + tests/bar-baz + tests/AnotherTest.php + + + diff --git a/tests/end-to-end/groups-from-configuration/_files/tests/AnotherTest.php b/tests/end-to-end/groups-from-configuration/_files/tests/AnotherTest.php new file mode 100644 index 00000000000..53430c9eefd --- /dev/null +++ b/tests/end-to-end/groups-from-configuration/_files/tests/AnotherTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\GroupsFromConfiguration; + +use PHPUnit\Framework\TestCase; + +final class AnotherTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/groups-from-configuration/_files/tests/bar-baz/BarBazTest.php b/tests/end-to-end/groups-from-configuration/_files/tests/bar-baz/BarBazTest.php new file mode 100644 index 00000000000..fa3412dc9d5 --- /dev/null +++ b/tests/end-to-end/groups-from-configuration/_files/tests/bar-baz/BarBazTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\GroupsFromConfiguration; + +use PHPUnit\Framework\TestCase; + +final class BarBazTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/groups-from-configuration/_files/tests/foo/FooTest.php b/tests/end-to-end/groups-from-configuration/_files/tests/foo/FooTest.php new file mode 100644 index 00000000000..a5019fd098a --- /dev/null +++ b/tests/end-to-end/groups-from-configuration/_files/tests/foo/FooTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\GroupsFromConfiguration; + +use PHPUnit\Framework\TestCase; + +final class FooTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/groups-from-configuration/group-another-group.phpt b/tests/end-to-end/groups-from-configuration/group-another-group.phpt new file mode 100644 index 00000000000..02b04573196 --- /dev/null +++ b/tests/end-to-end/groups-from-configuration/group-another-group.phpt @@ -0,0 +1,36 @@ +--TEST-- +phpunit --configuration _files/phpunit.xml --list-groups +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (3 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (1 test) +Test Runner Execution Started (1 test) +Test Suite Started (%sphpunit.xml, 1 test) +Test Suite Started (default, 1 test) +Test Suite Started (PHPUnit\TestFixture\GroupsFromConfiguration\AnotherTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\GroupsFromConfiguration\AnotherTest::testOne) +Test Prepared (PHPUnit\TestFixture\GroupsFromConfiguration\AnotherTest::testOne) +Test Passed (PHPUnit\TestFixture\GroupsFromConfiguration\AnotherTest::testOne) +Test Finished (PHPUnit\TestFixture\GroupsFromConfiguration\AnotherTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\GroupsFromConfiguration\AnotherTest, 1 test) +Test Suite Finished (default, 1 test) +Test Suite Finished (%sphpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/groups-from-configuration/group-bar.phpt b/tests/end-to-end/groups-from-configuration/group-bar.phpt new file mode 100644 index 00000000000..8d241423263 --- /dev/null +++ b/tests/end-to-end/groups-from-configuration/group-bar.phpt @@ -0,0 +1,36 @@ +--TEST-- +phpunit --configuration _files/phpunit.xml --list-groups +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (3 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (1 test) +Test Runner Execution Started (1 test) +Test Suite Started (%sphpunit.xml, 1 test) +Test Suite Started (default, 1 test) +Test Suite Started (PHPUnit\TestFixture\GroupsFromConfiguration\BarBazTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\GroupsFromConfiguration\BarBazTest::testOne) +Test Prepared (PHPUnit\TestFixture\GroupsFromConfiguration\BarBazTest::testOne) +Test Passed (PHPUnit\TestFixture\GroupsFromConfiguration\BarBazTest::testOne) +Test Finished (PHPUnit\TestFixture\GroupsFromConfiguration\BarBazTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\GroupsFromConfiguration\BarBazTest, 1 test) +Test Suite Finished (default, 1 test) +Test Suite Finished (%sphpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/groups-from-configuration/group-baz.phpt b/tests/end-to-end/groups-from-configuration/group-baz.phpt new file mode 100644 index 00000000000..a407cec537a --- /dev/null +++ b/tests/end-to-end/groups-from-configuration/group-baz.phpt @@ -0,0 +1,36 @@ +--TEST-- +phpunit --configuration _files/phpunit.xml --list-groups +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (3 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (1 test) +Test Runner Execution Started (1 test) +Test Suite Started (%sphpunit.xml, 1 test) +Test Suite Started (default, 1 test) +Test Suite Started (PHPUnit\TestFixture\GroupsFromConfiguration\BarBazTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\GroupsFromConfiguration\BarBazTest::testOne) +Test Prepared (PHPUnit\TestFixture\GroupsFromConfiguration\BarBazTest::testOne) +Test Passed (PHPUnit\TestFixture\GroupsFromConfiguration\BarBazTest::testOne) +Test Finished (PHPUnit\TestFixture\GroupsFromConfiguration\BarBazTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\GroupsFromConfiguration\BarBazTest, 1 test) +Test Suite Finished (default, 1 test) +Test Suite Finished (%sphpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/groups-from-configuration/group-foo.phpt b/tests/end-to-end/groups-from-configuration/group-foo.phpt new file mode 100644 index 00000000000..38e19a13bc2 --- /dev/null +++ b/tests/end-to-end/groups-from-configuration/group-foo.phpt @@ -0,0 +1,36 @@ +--TEST-- +phpunit --configuration _files/phpunit.xml --list-groups +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (3 tests) +Test Runner Started +Test Suite Sorted +Test Suite Filtered (1 test) +Test Runner Execution Started (1 test) +Test Suite Started (%sphpunit.xml, 1 test) +Test Suite Started (default, 1 test) +Test Suite Started (PHPUnit\TestFixture\GroupsFromConfiguration\FooTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\GroupsFromConfiguration\FooTest::testOne) +Test Prepared (PHPUnit\TestFixture\GroupsFromConfiguration\FooTest::testOne) +Test Passed (PHPUnit\TestFixture\GroupsFromConfiguration\FooTest::testOne) +Test Finished (PHPUnit\TestFixture\GroupsFromConfiguration\FooTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\GroupsFromConfiguration\FooTest, 1 test) +Test Suite Finished (default, 1 test) +Test Suite Finished (%sphpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/ini-isolation.phpt b/tests/end-to-end/ini-isolation.phpt deleted file mode 100644 index 9896a29961c..00000000000 --- a/tests/end-to-end/ini-isolation.phpt +++ /dev/null @@ -1,20 +0,0 @@ ---TEST-- -phpunit --process-isolation -d default_mimetype=application/x-test ../../_files/IniTest.php ---FILE-- - - - - - - - - - diff --git a/tests/end-to-end/loggers/_files/HookTest.php b/tests/end-to-end/loggers/_files/HookTest.php deleted file mode 100644 index caca5bcca22..00000000000 --- a/tests/end-to-end/loggers/_files/HookTest.php +++ /dev/null @@ -1,53 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Test; - -use Exception; -use PHPUnit\Framework\RiskyTestError; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\Warning; - -final class HookTest extends TestCase -{ - public function testSuccess(): void - { - $this->assertTrue(true); - } - - public function testFailure(): void - { - $this->assertTrue(false); - } - - public function testError(): void - { - throw new Exception('message'); - } - - public function testIncomplete(): void - { - $this->markTestIncomplete('message'); - } - - public function testRisky(): void - { - throw new RiskyTestError('message'); - } - - public function testSkipped(): void - { - $this->markTestSkipped('message'); - } - - public function testWarning(): void - { - throw new Warning('message'); - } -} diff --git a/tests/end-to-end/loggers/_files/configuration.custom-printer.xml b/tests/end-to-end/loggers/_files/configuration.custom-printer.xml deleted file mode 100644 index 49a26e7643c..00000000000 --- a/tests/end-to-end/loggers/_files/configuration.custom-printer.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/tests/end-to-end/loggers/_files/hooks.xml b/tests/end-to-end/loggers/_files/hooks.xml deleted file mode 100644 index 4200b2c21b5..00000000000 --- a/tests/end-to-end/loggers/_files/hooks.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - 10 - SOME_STRING - 2.3 - - - - diff --git a/tests/end-to-end/loggers/_files/raw_output_StatusTest.txt b/tests/end-to-end/loggers/_files/raw_output_StatusTest.txt deleted file mode 100644 index aea86168152..00000000000 --- a/tests/end-to-end/loggers/_files/raw_output_StatusTest.txt +++ /dev/null @@ -1,131 +0,0 @@ -PHPUnit %s Sebastian Bergmann and contributors. - -Runtime: %s -Configuration: %stests%ebasic%econfiguration.basic.xml - -Test result status with and without message - ✔ Success  %f ms - ✘ Failure  %f ms - ┐ - ├ Failed asserting that false is true. - │ - ╵ %stests%ebasic%eunit%eStatusTest.php:%d - ┴ - - ✘ Error  %f ms - ┐ - ├ RuntimeException: - │ - ╵ %stests%ebasic%eunit%eStatusTest.php:%d - ┴ - - ∅ Incomplete  %f ms - ┐ - ╵ %stests%ebasic%eunit%eStatusTest.php:%d - ┴ - - ↩ Skipped  %f ms - ┐ - ╵ %stests%ebasic%eunit%eStatusTest.php:%d - ┴ - - ☢ Risky  %f ms - ┐ - ├ This test did not perform any assertions%w - ├%w - ├ %stests%ebasic%eunit%eStatusTest.php:%d%w - ┴ - - ⚠ Warning  %f ms - ┐ - ╵ %stests%ebasic%eunit%eStatusTest.php:%d - ┴ - - ✔ Success with message  %f ms - ✘ Failure with message  %f ms - ┐ - ├ failure with custom message%w - ├ Failed asserting that false is true. - │ - ╵ %stests%ebasic%eunit%eStatusTest.php:%d - ┴ - - ✘ Error with message  %f ms - ┐ - ├ RuntimeException: error with custom message - │ - ╵ %stests%ebasic%eunit%eStatusTest.php:%d - ┴ - - ∅ Incomplete with message  %f ms - ┐ - ├ incomplete with custom message - │ - ╵ %stests%ebasic%eunit%eStatusTest.php:%d - ┴ - - ↩ Skipped with message  %f ms - ┐ - ├ skipped with custom message - │ - ╵ %stests%ebasic%eunit%eStatusTest.php:%d - ┴ - - ☢ Risky with message  %f ms - ┐ - ├ This test did not perform any assertions%w - ├%w - ├ %stests%ebasic%eunit%eStatusTest.php:%d%w - ┴ - - ⚠ Warning with message  %f ms - ┐ - ├ warning with custom message - │ - ╵ %stests%ebasic%eunit%eStatusTest.php:%d - ┴ - -Set Up Before Class (PHPUnit\SelfTest\Basic\SetUpBeforeClass) - ✘ One  %f ms - ┐ - ├ Exception: forcing an Exception in setUpBeforeClass() - │ - ╵ %stests%ebasic%eunit%efixtures%eSetUpBeforeClassTest.php:%d - ┴ - - ↩ Two  %f ms - ┐ - ├ Test skipped because of an error in hook method - ┴ - -Set Up (PHPUnit\SelfTest\Basic\SetUp) - ✘ One with set up exception  %f ms - ┐ - ├ RuntimeException: throw exception in setUp - │ - ╵ %stests%ebasic%eunit%efixtures%eSetUpTest.php:%d - ┴ - - ✘ Two with set up exception  %f ms - ┐ - ├ RuntimeException: throw exception in setUp - │ - ╵ %stests%ebasic%eunit%efixtures%eSetUpTest.php:%d - ┴ - -Tear Down After Class (PHPUnit\SelfTest\Basic\TearDownAfterClass) - ✔ One  %f ms - ✔ Two  %f ms - ✘ Tear down after class  0 ms - ┐ - ├ Exception in PHPUnit\SelfTest\Basic\TearDownAfterClassTest::tearDownAfterClass - ├ forcing an Exception in tearDownAfterClass()  - │ - ╵ %stests%ebasic%eunit%efixtures%eTearDownAfterClassTest.php:%d - ┴ - -Time: %s, Memory: %s - - -ERRORS! -Tests: 21, Assertions: 7, Errors: 5, Failures: 3, Warnings: 2, Skipped: 3, Incomplete: 2, Risky: 2. diff --git a/tests/end-to-end/loggers/custom-printer-debug.phpt b/tests/end-to-end/loggers/custom-printer-debug.phpt deleted file mode 100644 index 0c5e32c8727..00000000000 --- a/tests/end-to-end/loggers/custom-printer-debug.phpt +++ /dev/null @@ -1,28 +0,0 @@ ---TEST-- -phpunit -c ../../_files/configuration.custom-printer.xml --debug ../../_files/BankAccountTest.php ---FILE-- - - - - - PHPUnit must look at STDERR when running PHPT tests. - - - - - -Time: %s, Memory: %s - -OK (1 test, 1 assertion) diff --git a/tests/end-to-end/loggers/log-junit.phpt b/tests/end-to-end/loggers/log-junit.phpt deleted file mode 100644 index 6e0794e5664..00000000000 --- a/tests/end-to-end/loggers/log-junit.phpt +++ /dev/null @@ -1,146 +0,0 @@ ---TEST-- -phpunit --log-junit php://stdout _files/StatusTest.php ---FILE-- - - - - - - PHPUnit\SelfTest\Basic\StatusTest::testFailure -Failed asserting that false is true. - -%s%eStatusTest.php:%d - - - - PHPUnit\SelfTest\Basic\StatusTest::testError -RuntimeException:%w - -%s%eStatusTest.php:%d - - - - - - - - - - Risky Test - - - - PHPUnit\SelfTest\Basic\StatusTest::testWarning - -%s%eStatusTest.php:%d - - - - - PHPUnit\SelfTest\Basic\StatusTest::testFailureWithMessage -failure with custom message -Failed asserting that false is true. - -%s%eStatusTest.php:%d - - - - PHPUnit\SelfTest\Basic\StatusTest::testErrorWithMessage -RuntimeException: error with custom message - -%s%eStatusTest.php:%d - - - - - - - - - - Risky Test - - - - PHPUnit\SelfTest\Basic\StatusTest::testWarningWithMessage -warning with custom message - -%s%eStatusTest.php:%d - - - - - - -Time: %s, Memory: %s - -There were 2 errors: - -1) PHPUnit\SelfTest\Basic\StatusTest::testError -RuntimeException:%w - -%s%eStatusTest.php:%d - -2) PHPUnit\SelfTest\Basic\StatusTest::testErrorWithMessage -RuntimeException: error with custom message - -%s%eStatusTest.php:%d - --- - -There were 2 warnings: - -1) PHPUnit\SelfTest\Basic\StatusTest::testWarning - -%s%eStatusTest.php:%d - -2) PHPUnit\SelfTest\Basic\StatusTest::testWarningWithMessage -warning with custom message - -%s%eStatusTest.php:%d - --- - -There were 2 failures: - -1) PHPUnit\SelfTest\Basic\StatusTest::testFailure -Failed asserting that false is true. - -%s%eStatusTest.php:%d - -2) PHPUnit\SelfTest\Basic\StatusTest::testFailureWithMessage -failure with custom message -Failed asserting that false is true. - -%s%eStatusTest.php:%d - --- - -There were 2 risky tests: - -1) PHPUnit\SelfTest\Basic\StatusTest::testRisky -This test did not perform any assertions - -%s%eStatusTest.php:%d - -2) PHPUnit\SelfTest\Basic\StatusTest::testRiskyWithMessage -This test did not perform any assertions - -%s%eStatusTest.php:%d - -ERRORS! -Tests: 14, Assertions: 4, Errors: 2, Failures: 2, Warnings: 2, Skipped: 2, Incomplete: 2, Risky: 2. diff --git a/tests/end-to-end/loggers/log-teamcity-phpt.phpt b/tests/end-to-end/loggers/log-teamcity-phpt.phpt deleted file mode 100644 index 999dbd07f61..00000000000 --- a/tests/end-to-end/loggers/log-teamcity-phpt.phpt +++ /dev/null @@ -1,28 +0,0 @@ ---TEST-- -phpunit --log-teamcity php://stdout ../end-to-end/phpt-stderr.phpt ---FILE-- - 1|n+ 0 => 2|n )|n' details=' %s_files%eExceptionStackTest.php:%d|n |n Caused by|n message|n Failed asserting that two arrays are equal.|n --- Expected|n +++ Actual|n @@ @@|n Array (|n - 0 => 1|n + 0 => 2|n )|n |n %s_files%eExceptionStackTest.php:%d|n ' duration='%d' flowId='%d'] - -##teamcity[testFinished name='testPrintingChildException' duration='%d' flowId='%d'] - -##teamcity[testStarted name='testNestedExceptions' locationHint='php_qn://%s%etests%e_files%eExceptionStackTest.php::\PHPUnit\TestFixture\ExceptionStackTest::testNestedExceptions' flowId='%d'] - -##teamcity[testFailed name='testNestedExceptions' message='Exception : One' details=' %s%etests%e_files%eExceptionStackTest.php:%d|n |n Caused by|n InvalidArgumentException: Two|n |n %s%etests%e_files%eExceptionStackTest.php:%d|n |n Caused by|n Exception: Three|n |n %s%etests%e_files%eExceptionStackTest.php:%d|n ' duration='%d' flowId='%d'] - -##teamcity[testFinished name='testNestedExceptions' duration='%d' flowId='%d'] - -##teamcity[testSuiteFinished name='PHPUnit\TestFixture\ExceptionStackTest' flowId='%d'] - - -Time: %s, Memory: %s - - -ERRORS! -Tests: 2, Assertions: 1, Errors: 2. diff --git a/tests/end-to-end/loggers/teamcity.phpt b/tests/end-to-end/loggers/teamcity.phpt deleted file mode 100644 index d332e5c6818..00000000000 --- a/tests/end-to-end/loggers/teamcity.phpt +++ /dev/null @@ -1,39 +0,0 @@ ---TEST-- -phpunit --teamcity ../../_files/BankAccountTest.php ---FILE-- - - - - - Test Documentation - - - - -

    Bank Account (PHPUnit\TestFixture\BankAccount)

    -
      -... 3 / 3 (100%)
    • ✓ Balance is initially zero
    • -
    • ✓ Balance cannot become negative
    • -
    • ✓ Balance cannot become negative
    • -
    - - - -Time: %s, Memory: %s - -OK (3 tests, 3 assertions) diff --git a/tests/end-to-end/loggers/testdox-text.phpt b/tests/end-to-end/loggers/testdox-text.phpt deleted file mode 100644 index 7a327d2c6e2..00000000000 --- a/tests/end-to-end/loggers/testdox-text.phpt +++ /dev/null @@ -1,27 +0,0 @@ ---TEST-- -phpunit --testdox-text php://stdout ../../_files/BankAccountTest.php ---FILE-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Time: %s, Memory: %s - -There were 2 errors: - -1) PHPUnit\SelfTest\Basic\StatusTest::testError -RuntimeException:%w - -%s%eStatusTest.php:%d - -2) PHPUnit\SelfTest\Basic\StatusTest::testErrorWithMessage -RuntimeException: error with custom message - -%s%eStatusTest.php:%d - --- - -There were 2 warnings: - -1) PHPUnit\SelfTest\Basic\StatusTest::testWarning - -%s%eStatusTest.php:%d - -2) PHPUnit\SelfTest\Basic\StatusTest::testWarningWithMessage -warning with custom message - -%s%eStatusTest.php:%d - --- - -There were 2 failures: - -1) PHPUnit\SelfTest\Basic\StatusTest::testFailure -Failed asserting that false is true. - -%s%eStatusTest.php:%d - -2) PHPUnit\SelfTest\Basic\StatusTest::testFailureWithMessage -failure with custom message -Failed asserting that false is true. - -%s%eStatusTest.php:%d - --- - -There were 2 risky tests: - -1) PHPUnit\SelfTest\Basic\StatusTest::testRisky -This test did not perform any assertions - -%s%eStatusTest.php:%d - -2) PHPUnit\SelfTest\Basic\StatusTest::testRiskyWithMessage -This test did not perform any assertions - -%s%eStatusTest.php:%d - -ERRORS! -Tests: 14, Assertions: 4, Errors: 2, Failures: 2, Warnings: 2, Skipped: 2, Incomplete: 2, Risky: 2. diff --git a/tests/end-to-end/loggers/testdox.phpt b/tests/end-to-end/loggers/testdox.phpt deleted file mode 100644 index e3e21964d19..00000000000 --- a/tests/end-to-end/loggers/testdox.phpt +++ /dev/null @@ -1,46 +0,0 @@ ---TEST-- -phpunit --testdox -c tests/basic/configuration.basic.xml ---FILE-- - + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +final class CaseWithDollarSignTest extends TestCase +{ + public static function dataProvider(): iterable + { + yield ['$12.34']; + + yield ['Some text before the price $5.67']; + + yield ['Dollar sign followed by letter $Q']; + + yield ['Alone $ surrounded by spaces']; + } + + #[DataProvider('dataProvider')] + #[TestDox('The "$x" is used for this test')] + public function testSomething(string $x): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/logging/_files/DataProviderTest.php b/tests/end-to-end/logging/_files/DataProviderTest.php new file mode 100644 index 00000000000..4095ae2090a --- /dev/null +++ b/tests/end-to-end/logging/_files/DataProviderTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TeamCity; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class DataProviderTest extends TestCase +{ + public static function provider(): array + { + return [ + [true], + [false], + ]; + } + + #[DataProvider('provider')] + public function testOne(bool $value): void + { + $this->assertTrue($value); + } +} diff --git a/tests/end-to-end/loggers/_files/TestDoxGroupTest.php b/tests/end-to-end/logging/_files/TestDoxGroupTest.php similarity index 94% rename from tests/end-to-end/loggers/_files/TestDoxGroupTest.php rename to tests/end-to-end/logging/_files/TestDoxGroupTest.php index 6e2ef1330d8..e2b64b799c5 100644 --- a/tests/end-to-end/loggers/_files/TestDoxGroupTest.php +++ b/tests/end-to-end/logging/_files/TestDoxGroupTest.php @@ -7,6 +7,8 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + use PHPUnit\Framework\TestCase; class TestDoxGroupTest extends TestCase diff --git a/tests/end-to-end/logging/_files/ThrowsWithPreviousExceptionTest.php b/tests/end-to-end/logging/_files/ThrowsWithPreviousExceptionTest.php new file mode 100644 index 00000000000..ccff1ed2a19 --- /dev/null +++ b/tests/end-to-end/logging/_files/ThrowsWithPreviousExceptionTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use Exception; +use PHPUnit\Framework\TestCase; + +class ThrowsWithPreviousExceptionTest extends TestCase +{ + public function testFoo(): void + { + throw new Exception( + 'Outer', + previous: new Exception('Inner'), + ); + } +} diff --git a/tests/end-to-end/logging/_files/TypeErrorTest.php b/tests/end-to-end/logging/_files/TypeErrorTest.php new file mode 100644 index 00000000000..dfc908c18b6 --- /dev/null +++ b/tests/end-to-end/logging/_files/TypeErrorTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use DateTime; +use DateTimeImmutable; +use PHPUnit\Framework\TestCase; + +final class TypeErrorTest extends TestCase +{ + private DateTimeImmutable $dateTime; + + protected function setUp(): void + { + $this->dateTime = new DateTime; + } + + public function testMe(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/loggers/_files/raw_output_ColorTest.txt b/tests/end-to-end/logging/_files/raw_output_ColorTest.txt similarity index 100% rename from tests/end-to-end/loggers/_files/raw_output_ColorTest.txt rename to tests/end-to-end/logging/_files/raw_output_ColorTest.txt index 5e24be601fb..193f2eab6e6 100644 --- a/tests/end-to-end/loggers/_files/raw_output_ColorTest.txt +++ b/tests/end-to-end/logging/_files/raw_output_ColorTest.txt @@ -2,6 +2,8 @@ PHPUnit %s by Sebastian Bergmann and contributors. Runtime: %s +Time: %s, Memory: %s + Basic ANSI color highlighting support ✔ Colorize with no·color  %f ms ✔ Colorize with one·color  %f ms @@ -25,6 +27,4 @@ Runtime: %s ✔ TestDox shows name of data set one with value 1  %f ms ✔ TestDox shows name of data set two with value 2  %f ms -Time: %s, Memory: %s - OK (21 tests, 21 assertions) diff --git a/tests/end-to-end/logging/_files/raw_output_StatusTest.txt b/tests/end-to-end/logging/_files/raw_output_StatusTest.txt new file mode 100644 index 00000000000..407586f92ad --- /dev/null +++ b/tests/end-to-end/logging/_files/raw_output_StatusTest.txt @@ -0,0 +1,127 @@ +PHPUnit %s Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %sconfiguration.basic.xml + +Time: %s, Memory: %s + +Set Up Before Class (PHPUnit\TestFixture\Basic\SetUpBeforeClass) + ✘ One  %f ms + ┐ + ├ Exception: forcing an Exception in setUpBeforeClass() + │ + ╵ %stests%eend-to-end%e_files%ebasic%eunit%eSetUpBeforeClassTest.php:32 + ┴ + + ↩ Two  %f ms + ┐ + ├ This test was skipped due to an error in a hook method + ┴ + +Set Up (PHPUnit\TestFixture\Basic\SetUp) + ✘ One with set up exception  %f ms + ┐ + ├ RuntimeException: throw exception in setUp + │ + ╵ %stests%eend-to-end%e_files%ebasic%eunit%eSetUpTest.php:30 + ┴ + + ✘ Two with set up exception  %f ms + ┐ + ├ RuntimeException: throw exception in setUp + │ + ╵ %stests%eend-to-end%e_files%ebasic%eunit%eSetUpTest.php:30 + ┴ + +Test result status with and without message + ✔ Success  %f ms + ✘ Failure  %f ms + ┐ + ├ Failed asserting that false is true. + │ + ╵ %stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php:%d + ┴ + + ✘ Error  %f ms + ┐ + ├ RuntimeException: + │ + ╵ %stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php:%d + ┴ + + ∅ Incomplete  %f ms + ┐ + ╵ %stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php:%d + ┴ + + ↩ Skipped  %f ms + ┐ + ╵ %stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php:%d + ┴ + + ☢ Risky  %f ms + ┐ + ├ This test did not perform any assertions%w + ┴ + + ⚠ Warning  %f ms + ┐ + ╵ %stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php:%d + ┴ + + ✔ Success with message  %f ms + ✘ Failure with message  %f ms + ┐ + ├ failure with custom message%w + ├ Failed asserting that false is true. + │ + ╵ %stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php:%d + ┴ + + ✘ Error with message  %f ms + ┐ + ├ RuntimeException: error with custom message + │ + ╵ %stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php:%d + ┴ + + ∅ Incomplete with message  %f ms + ┐ + ├ incomplete with custom message + │ + ╵ %stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php:%d + ┴ + + ↩ Skipped with message  %f ms + ┐ + ├ skipped with custom message + │ + ╵ %stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php:%d + ┴ + + ☢ Risky with message  %f ms + ┐ + ├ This test did not perform any assertions%w + ┴ + + ⚠ Warning with message  %f ms + ┐ + ├ warning with custom message + │ + ╵ %stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php:%d + ┴ + +Tear Down After Class (PHPUnit\TestFixture\Basic\TearDownAfterClass) + ✔ One  %f ms + ✔ Two  %f ms + ✘ Tear down after class  0 ms + ┐ + ├ Exception in PHPUnit\TestFixture\Basic\TearDownAfterClassTest::tearDownAfterClass + ├ forcing an Exception in tearDownAfterClass()  + │ + ╵ %stests%eend-to-end%e_files%ebasic%eunit%eTearDownAfterClassTest.php:%d + ┴ + + +ERRORS! +Tests: 21, Assertions: 7, Errors: 5, Failures: 3, Warnings: 2, Skipped: 3, Incomplete: 2, Risky: 2. diff --git a/tests/end-to-end/logging/_files/status/phpunit.xml b/tests/end-to-end/logging/_files/status/phpunit.xml new file mode 100644 index 00000000000..f53f30cfd91 --- /dev/null +++ b/tests/end-to-end/logging/_files/status/phpunit.xml @@ -0,0 +1,10 @@ + + + + + tests + + + diff --git a/tests/end-to-end/logging/_files/status/tests/StatusTest.php b/tests/end-to-end/logging/_files/status/tests/StatusTest.php new file mode 100644 index 00000000000..ad3f7a85c0e --- /dev/null +++ b/tests/end-to-end/logging/_files/status/tests/StatusTest.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Basic; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\RequiresPhp; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\MockObject\AnInterface; +use RuntimeException; + +#[CoversClass('Foo')] +#[UsesClass('Bar')] +#[TestDox('Test result status with and without message')] +class StatusTest extends TestCase +{ + public function testSuccess(): void + { + $this->createMock(AnInterface::class); + + $this->assertTrue(true); + } + + public function testFailure(): void + { + $this->assertTrue(false); + } + + public function testError(): void + { + throw new RuntimeException; + } + + public function testIncomplete(): void + { + $this->markTestIncomplete(); + } + + public function testSkipped(): void + { + $this->markTestSkipped(); + } + + public function testRisky(): void + { + } + + public function testSuccessWithMessage(): void + { + $this->assertTrue(true, 'success with custom message'); + } + + public function testFailureWithMessage(): void + { + $this->assertTrue(false, 'failure with custom message'); + } + + public function testErrorWithMessage(): void + { + throw new RuntimeException('error with custom message'); + } + + public function testIncompleteWithMessage(): void + { + $this->markTestIncomplete('incomplete with custom message'); + } + + #[RequiresPhp('> 9000')] + public function testSkippedByMetadata(): void + { + } + + public function testSkippedWithMessage(): void + { + $this->markTestSkipped('skipped with custom message'); + } + + public function testRiskyWithMessage(): void + { + // Custom messages not implemented for risky status + } +} diff --git a/tests/end-to-end/logging/_files/teamcity-warning/phpunit.xml b/tests/end-to-end/logging/_files/teamcity-warning/phpunit.xml new file mode 100644 index 00000000000..73255515244 --- /dev/null +++ b/tests/end-to-end/logging/_files/teamcity-warning/phpunit.xml @@ -0,0 +1,12 @@ + + + + + tests + + + + bar + diff --git a/tests/end-to-end/logging/_files/teamcity-warning/tests/Test.php b/tests/end-to-end/logging/_files/teamcity-warning/tests/Test.php new file mode 100644 index 00000000000..a2aa00e57b6 --- /dev/null +++ b/tests/end-to-end/logging/_files/teamcity-warning/tests/Test.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/logging/junit/invalid-path.phpt b/tests/end-to-end/logging/junit/invalid-path.phpt new file mode 100644 index 00000000000..3d990bd7c25 --- /dev/null +++ b/tests/end-to-end/logging/junit/invalid-path.phpt @@ -0,0 +1,28 @@ +--TEST-- +Test runner emits warning when --log-junit is used with an invalid target path +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Cannot log test results in JUnit XML format to "": Directory "" does not exist and could not be created + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/logging/junit/invalid-socket.phpt b/tests/end-to-end/logging/junit/invalid-socket.phpt new file mode 100644 index 00000000000..207f32dc7c2 --- /dev/null +++ b/tests/end-to-end/logging/junit/invalid-socket.phpt @@ -0,0 +1,28 @@ +--TEST-- +Test runner emits warning when --log-junit is used with an invalid socket +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Cannot log test results in JUnit XML format to "socket://hostname:port:wrong": "socket://hostname:port:wrong" does not match "socket://hostname:port" format + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/logging/junit/skipped-before-class-method.phpt b/tests/end-to-end/logging/junit/skipped-before-class-method.phpt new file mode 100644 index 00000000000..b4d6d85a334 --- /dev/null +++ b/tests/end-to-end/logging/junit/skipped-before-class-method.phpt @@ -0,0 +1,19 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5354 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- + + + + diff --git a/tests/end-to-end/logging/junit/skipped-test-method.phpt b/tests/end-to-end/logging/junit/skipped-test-method.phpt new file mode 100644 index 00000000000..03033bea76b --- /dev/null +++ b/tests/end-to-end/logging/junit/skipped-test-method.phpt @@ -0,0 +1,24 @@ +--TEST-- +JUnit XML: test skipped in test method +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- + + + + + + + + + diff --git a/tests/end-to-end/logging/junit/to-file.phpt b/tests/end-to-end/logging/junit/to-file.phpt new file mode 100644 index 00000000000..3754ea3fc8a --- /dev/null +++ b/tests/end-to-end/logging/junit/to-file.phpt @@ -0,0 +1,70 @@ +--TEST-- +phpunit --log-junit junit.xml _files/StatusTest.php +--FILE-- +run($_SERVER['argv']); + +print file_get_contents($logfile); + +unlink($logfile); +--EXPECTF-- + + + + + + PHPUnit\TestFixture\Basic\StatusTest::testFailure%A +Failed asserting that false is true. +%A +%sStatusTest.php:%d + + + PHPUnit\TestFixture\Basic\StatusTest::testError%A +RuntimeException:%w +%A +%sStatusTest.php:%d + + + + + + + + + + + PHPUnit\TestFixture\Basic\StatusTest::testFailureWithMessage%A +failure with custom message +Failed asserting that false is true. +%A +%sStatusTest.php:%d + + + PHPUnit\TestFixture\Basic\StatusTest::testErrorWithMessage%A +RuntimeException: error with custom message +%A +%sStatusTest.php:%d + + + + + + + + + + + + + diff --git a/tests/end-to-end/logging/junit/to-stdout.phpt b/tests/end-to-end/logging/junit/to-stdout.phpt new file mode 100644 index 00000000000..54014851bed --- /dev/null +++ b/tests/end-to-end/logging/junit/to-stdout.phpt @@ -0,0 +1,64 @@ +--TEST-- +phpunit --log-junit php://stdout _files/StatusTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- + + + + + + PHPUnit\TestFixture\Basic\StatusTest::testFailure%A +Failed asserting that false is true. +%A +%sStatusTest.php:%d + + + PHPUnit\TestFixture\Basic\StatusTest::testError%A +RuntimeException:%w +%A +%sStatusTest.php:%d + + + + + + + + + + + PHPUnit\TestFixture\Basic\StatusTest::testFailureWithMessage%A +failure with custom message +Failed asserting that false is true. +%A +%sStatusTest.php:%d + + + PHPUnit\TestFixture\Basic\StatusTest::testErrorWithMessage%A +RuntimeException: error with custom message +%A +%sStatusTest.php:%d + + + + + + + + + + + + + diff --git a/tests/end-to-end/logging/junit/with-progress-with-errors.phpt b/tests/end-to-end/logging/junit/with-progress-with-errors.phpt new file mode 100644 index 00000000000..ac419c988d7 --- /dev/null +++ b/tests/end-to-end/logging/junit/with-progress-with-errors.phpt @@ -0,0 +1,54 @@ +--TEST-- +phpunit --log-junit php://stdout _files/TypeErrorTest.php +--SKIPIF-- +run($_SERVER['argv']); + +print file_get_contents($logfile); + +unlink($logfile); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +E 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 error: + +1) PHPUnit\TestFixture\TypeErrorTest::testMe +TypeError: Cannot assign DateTime to property PHPUnit\TestFixture\TypeErrorTest::$dateTime of type DateTimeImmutable + +%sTypeErrorTest.php:%d + +ERRORS! +Tests: 1, Assertions: 0, Errors: 1. + + + + + PHPUnit\TestFixture\TypeErrorTest::testMe +TypeError: Cannot assign DateTime to property PHPUnit\TestFixture\TypeErrorTest::$dateTime of type DateTimeImmutable + +%sTypeErrorTest.php:%s + + + diff --git a/tests/end-to-end/logging/open-test-reporting/assertion-failure-in-after-class-method.phpt b/tests/end-to-end/logging/open-test-reporting/assertion-failure-in-after-class-method.phpt new file mode 100644 index 00000000000..5614da84588 --- /dev/null +++ b/tests/end-to-end/logging/open-test-reporting/assertion-failure-in-after-class-method.phpt @@ -0,0 +1,62 @@ +--TEST-- +phpunit --log-otr /path/to/logfile ../../event/_files/AssertionFailureInTearDownAfterClassTest.php +--FILE-- +run($_SERVER['argv']); + +validate_and_print($logfile); + +unlink($logfile); +--EXPECTF-- + + + + %s + %s + %s + %s + %s + + + + + + + + + + + + + + + + + + + + + + + Failed asserting that false is true. + + + + diff --git a/tests/end-to-end/logging/open-test-reporting/assertion-failure-in-after-test-method.phpt b/tests/end-to-end/logging/open-test-reporting/assertion-failure-in-after-test-method.phpt new file mode 100644 index 00000000000..926d9cf942f --- /dev/null +++ b/tests/end-to-end/logging/open-test-reporting/assertion-failure-in-after-test-method.phpt @@ -0,0 +1,60 @@ +--TEST-- +phpunit --log-otr /path/to/logfile ../../event/_files/AssertionFailureInTearDownTest.php +--FILE-- +run($_SERVER['argv']); + +validate_and_print($logfile); + +unlink($logfile); +--EXPECTF-- + + + + %s + %s + %s + %s + %s + + + + + + + + + + + + + + + + + + + + Failed asserting that false is true. + + + + + diff --git a/tests/end-to-end/logging/open-test-reporting/assertion-failure-in-before-class-method.phpt b/tests/end-to-end/logging/open-test-reporting/assertion-failure-in-before-class-method.phpt new file mode 100644 index 00000000000..4c2f186c671 --- /dev/null +++ b/tests/end-to-end/logging/open-test-reporting/assertion-failure-in-before-class-method.phpt @@ -0,0 +1,51 @@ +--TEST-- +phpunit --log-otr /path/to/logfile ../../event/_files/AssertionFailureInSetUpBeforeClassTest.php +--FILE-- +run($_SERVER['argv']); + +validate_and_print($logfile); + +unlink($logfile); +--EXPECTF-- + + + + %s + %s + %s + %s + %s + + + + + + + + + + + + Failed asserting that false is true. + + + + diff --git a/tests/end-to-end/logging/open-test-reporting/assertion-failure-in-before-test-method.phpt b/tests/end-to-end/logging/open-test-reporting/assertion-failure-in-before-test-method.phpt new file mode 100644 index 00000000000..67908ea8af2 --- /dev/null +++ b/tests/end-to-end/logging/open-test-reporting/assertion-failure-in-before-test-method.phpt @@ -0,0 +1,60 @@ +--TEST-- +phpunit --log-otr /path/to/logfile ../../event/_files/AssertionFailureInSetUpTest.php +--FILE-- +run($_SERVER['argv']); + +validate_and_print($logfile); + +unlink($logfile); +--EXPECTF-- + + + + %s + %s + %s + %s + %s + + + + + + + + + + + + + + + + + + + + Failed asserting that false is true. + + + + + diff --git a/tests/end-to-end/logging/open-test-reporting/assertion-failure-in-postcondition-method.phpt b/tests/end-to-end/logging/open-test-reporting/assertion-failure-in-postcondition-method.phpt new file mode 100644 index 00000000000..a83c8b96b07 --- /dev/null +++ b/tests/end-to-end/logging/open-test-reporting/assertion-failure-in-postcondition-method.phpt @@ -0,0 +1,60 @@ +--TEST-- +phpunit --log-otr /path/to/logfile ../../event/_files/AssertionFailureInPostConditionTest.php +--FILE-- +run($_SERVER['argv']); + +validate_and_print($logfile); + +unlink($logfile); +--EXPECTF-- + + + + %s + %s + %s + %s + %s + + + + + + + + + + + + + + + + + + + + Failed asserting that false is true. + + + + + diff --git a/tests/end-to-end/logging/open-test-reporting/assertion-failure-in-precondition-method.phpt b/tests/end-to-end/logging/open-test-reporting/assertion-failure-in-precondition-method.phpt new file mode 100644 index 00000000000..d67fd293a80 --- /dev/null +++ b/tests/end-to-end/logging/open-test-reporting/assertion-failure-in-precondition-method.phpt @@ -0,0 +1,60 @@ +--TEST-- +phpunit --log-otr /path/to/logfile ../../event/_files/AssertionFailureInPreConditionTest.php +--FILE-- +run($_SERVER['argv']); + +validate_and_print($logfile); + +unlink($logfile); +--EXPECTF-- + + + + %s + %s + %s + %s + %s + + + + + + + + + + + + + + + + + + + + Failed asserting that false is true. + + + + + diff --git a/tests/end-to-end/logging/open-test-reporting/basic-test-case-class-source-file-as-cli-argument-with-git-information.phpt b/tests/end-to-end/logging/open-test-reporting/basic-test-case-class-source-file-as-cli-argument-with-git-information.phpt new file mode 100644 index 00000000000..df51235621c --- /dev/null +++ b/tests/end-to-end/logging/open-test-reporting/basic-test-case-class-source-file-as-cli-argument-with-git-information.phpt @@ -0,0 +1,234 @@ +--TEST-- +phpunit --log-otr /path/to/logfile --include-git-information ../_files/StatusTest.php +--FILE-- +run($_SERVER['argv']); + +validate_and_print($logfile); + +unlink($logfile); +--EXPECTF-- + + + + %s + %s + %s + %s + %s + + %s + %s + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failed asserting that false is true. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + failure with custom message +Failed asserting that false is true. + + + + + + + + + + + + + + error with custom message + + + + + + + + + + + + + + incomplete with custom message + + + + + + + + + + + + + + PHP > 9000 is required. + + + + + + + + + + + + + skipped with custom message + + + + + + + + + + + + + + + diff --git a/tests/end-to-end/logging/open-test-reporting/basic-test-case-class-source-file-as-cli-argument.phpt b/tests/end-to-end/logging/open-test-reporting/basic-test-case-class-source-file-as-cli-argument.phpt new file mode 100644 index 00000000000..39f083c05e2 --- /dev/null +++ b/tests/end-to-end/logging/open-test-reporting/basic-test-case-class-source-file-as-cli-argument.phpt @@ -0,0 +1,229 @@ +--TEST-- +phpunit --log-otr /path/to/logfile ../_files/StatusTest.php +--FILE-- +run($_SERVER['argv']); + +validate_and_print($logfile); + +unlink($logfile); +--EXPECTF-- + + + + %s + %s + %s + %s + %s + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failed asserting that false is true. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + failure with custom message +Failed asserting that false is true. + + + + + + + + + + + + + + error with custom message + + + + + + + + + + + + + + incomplete with custom message + + + + + + + + + + + + + + PHP > 9000 is required. + + + + + + + + + + + + + skipped with custom message + + + + + + + + + + + + + + + diff --git a/tests/end-to-end/logging/open-test-reporting/basic-test-suite-from-xml-configuration-file.phpt b/tests/end-to-end/logging/open-test-reporting/basic-test-suite-from-xml-configuration-file.phpt new file mode 100644 index 00000000000..1e131cb125c --- /dev/null +++ b/tests/end-to-end/logging/open-test-reporting/basic-test-suite-from-xml-configuration-file.phpt @@ -0,0 +1,233 @@ +--TEST-- +phpunit --configuration ../_files/status/phpunit.xml --log-otr /path/to/logfile +--FILE-- +run($_SERVER['argv']); + +validate_and_print($logfile); + +unlink($logfile); +--EXPECTF-- + + + + %s + %s + %s + %s + %s + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failed asserting that false is true. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + failure with custom message +Failed asserting that false is true. + + + + + + + + + + + + + + error with custom message + + + + + + + + + + + + + + incomplete with custom message + + + + + + + + + + + + + + PHP > 9000 is required. + + + + + + + + + + + + + skipped with custom message + + + + + + + + + + + + + + + + + diff --git a/tests/end-to-end/logging/open-test-reporting/exception-in-after-class-method.phpt b/tests/end-to-end/logging/open-test-reporting/exception-in-after-class-method.phpt new file mode 100644 index 00000000000..f4ed025a9d5 --- /dev/null +++ b/tests/end-to-end/logging/open-test-reporting/exception-in-after-class-method.phpt @@ -0,0 +1,62 @@ +--TEST-- +phpunit --log-otr /path/to/logfile ../../event/_files/ExceptionInTearDownAfterClassTest.php +--FILE-- +run($_SERVER['argv']); + +validate_and_print($logfile); + +unlink($logfile); +--EXPECTF-- + + + + %s + %s + %s + %s + %s + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/end-to-end/logging/open-test-reporting/exception-in-after-test-method.phpt b/tests/end-to-end/logging/open-test-reporting/exception-in-after-test-method.phpt new file mode 100644 index 00000000000..9c4e5511605 --- /dev/null +++ b/tests/end-to-end/logging/open-test-reporting/exception-in-after-test-method.phpt @@ -0,0 +1,60 @@ +--TEST-- +phpunit --log-otr /path/to/logfile ../../event/_files/ExceptionInTearDownTest.php +--FILE-- +run($_SERVER['argv']); + +validate_and_print($logfile); + +unlink($logfile); +--EXPECTF-- + + + + %s + %s + %s + %s + %s + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/end-to-end/logging/open-test-reporting/exception-in-before-class-method.phpt b/tests/end-to-end/logging/open-test-reporting/exception-in-before-class-method.phpt new file mode 100644 index 00000000000..daca167d0f0 --- /dev/null +++ b/tests/end-to-end/logging/open-test-reporting/exception-in-before-class-method.phpt @@ -0,0 +1,51 @@ +--TEST-- +phpunit --log-otr /path/to/logfile ../../event/_files/ExceptionInSetUpBeforeClassTest.php +--FILE-- +run($_SERVER['argv']); + +validate_and_print($logfile); + +unlink($logfile); +--EXPECTF-- + + + + %s + %s + %s + %s + %s + + + + + + + + + + + + + + + + diff --git a/tests/end-to-end/logging/open-test-reporting/exception-in-before-test-method.phpt b/tests/end-to-end/logging/open-test-reporting/exception-in-before-test-method.phpt new file mode 100644 index 00000000000..1aa131161fd --- /dev/null +++ b/tests/end-to-end/logging/open-test-reporting/exception-in-before-test-method.phpt @@ -0,0 +1,60 @@ +--TEST-- +phpunit --log-otr /path/to/logfile ../../event/_files/ExceptionInSetUpTest.php +--FILE-- +run($_SERVER['argv']); + +validate_and_print($logfile); + +unlink($logfile); +--EXPECTF-- + + + + %s + %s + %s + %s + %s + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/end-to-end/logging/open-test-reporting/invalid-path.phpt b/tests/end-to-end/logging/open-test-reporting/invalid-path.phpt new file mode 100644 index 00000000000..f36acb11942 --- /dev/null +++ b/tests/end-to-end/logging/open-test-reporting/invalid-path.phpt @@ -0,0 +1,28 @@ +--TEST-- +phpunit --log-otr /invalid/path ../../event/_files/basic/SuccessTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Cannot log test results in Open Test Reporting XML format to "/invalid/path": Unable to resolve file path + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/logging/open-test-reporting/skipped-in-before-class-method.phpt b/tests/end-to-end/logging/open-test-reporting/skipped-in-before-class-method.phpt new file mode 100644 index 00000000000..85fca39c8a0 --- /dev/null +++ b/tests/end-to-end/logging/open-test-reporting/skipped-in-before-class-method.phpt @@ -0,0 +1,47 @@ +--TEST-- +phpunit --log-otr /path/to/logfile ../../event/_files/SkippedInSetupBeforeClassTest.php +--FILE-- +run($_SERVER['argv']); + +validate_and_print($logfile); + +unlink($logfile); +--EXPECTF-- + + + + %s + %s + %s + %s + %s + + + + + + + + + + + + message + + + diff --git a/tests/end-to-end/logging/open-test-reporting/skipped-in-before-test-method.phpt b/tests/end-to-end/logging/open-test-reporting/skipped-in-before-test-method.phpt new file mode 100644 index 00000000000..d536ed166a5 --- /dev/null +++ b/tests/end-to-end/logging/open-test-reporting/skipped-in-before-test-method.phpt @@ -0,0 +1,56 @@ +--TEST-- +phpunit --log-otr /path/to/logfile ../../event/_files/SkippedInSetupTest.php +--FILE-- +run($_SERVER['argv']); + +validate_and_print($logfile); + +unlink($logfile); +--EXPECTF-- + + + + %s + %s + %s + %s + %s + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/end-to-end/logging/open-test-reporting/validate_and_print.php b/tests/end-to-end/logging/open-test-reporting/validate_and_print.php new file mode 100644 index 00000000000..18806f61a94 --- /dev/null +++ b/tests/end-to-end/logging/open-test-reporting/validate_and_print.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use const PHP_EOL; +use function file_get_contents; +use function libxml_clear_errors; +use function libxml_get_errors; +use function libxml_use_internal_errors; +use function printf; +use function trim; +use DOMDocument; + +function validate_and_print(string $logfile): void +{ + libxml_use_internal_errors(true); + + $document = new DOMDocument; + $document->load($logfile); + + if (!$document->schemaValidate(__DIR__ . '/../../../../src/Logging/OpenTestReporting/schema/otr.xsd')) { + print 'Generated XML document does not validate against Open Test Reporting schemas:' . PHP_EOL . PHP_EOL; + + foreach (libxml_get_errors() as $error) { + printf( + '- Line %d: %s' . PHP_EOL, + $error->line, + trim($error->message), + ); + } + + print PHP_EOL; + } + + libxml_clear_errors(); + + print file_get_contents($logfile); +} diff --git a/tests/end-to-end/logging/teamcity/log-teamcity-comparisonfailure.phpt b/tests/end-to-end/logging/teamcity/log-teamcity-comparisonfailure.phpt new file mode 100644 index 00000000000..9dfab66f197 --- /dev/null +++ b/tests/end-to-end/logging/teamcity/log-teamcity-comparisonfailure.phpt @@ -0,0 +1,21 @@ +--TEST-- +phpunit --log-teamcity php://stdout ../../_files/BankAccountTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +##teamcity[testCount count='1' flowId='%d'] +##teamcity[testSuiteStarted name='PHPUnit\TestFixture\ComparisonFailureTest' locationHint='php_qn:%sComparisonFailureTest.php::\PHPUnit\TestFixture\ComparisonFailureTest' flowId='%d'] +##teamcity[testStarted name='testOne' locationHint='php_qn:%sComparisonFailureTest.php::\PHPUnit\TestFixture\ComparisonFailureTest::testOne' flowId='%d'] +##teamcity[testFailed name='testOne' message='Failed asserting that false matches expected true.' details='%sComparisonFailureTest.php:%d|n' duration='%s' type='comparisonFailure' actual='false' expected='true' flowId='%d'] +##teamcity[testFinished name='testOne' duration='%s' flowId='%d'] +##teamcity[testSuiteFinished name='PHPUnit\TestFixture\ComparisonFailureTest' flowId='%d'] diff --git a/tests/end-to-end/logging/teamcity/log-teamcity-invalid-path.phpt b/tests/end-to-end/logging/teamcity/log-teamcity-invalid-path.phpt new file mode 100644 index 00000000000..262a9d9bce1 --- /dev/null +++ b/tests/end-to-end/logging/teamcity/log-teamcity-invalid-path.phpt @@ -0,0 +1,28 @@ +--TEST-- +Test runner emits warning when --log-teamcity is used with an invalid target path +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Cannot log test results in TeamCity format to "": Directory "" does not exist and could not be created + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/logging/teamcity/log-teamcity-invalid-socket.phpt b/tests/end-to-end/logging/teamcity/log-teamcity-invalid-socket.phpt new file mode 100644 index 00000000000..259039b2fef --- /dev/null +++ b/tests/end-to-end/logging/teamcity/log-teamcity-invalid-socket.phpt @@ -0,0 +1,28 @@ +--TEST-- +Test runner emits warning when --log-teamcity is used with an invalid socket +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Cannot log test results in TeamCity format to "socket://hostname:port:wrong": "socket://hostname:port:wrong" does not match "socket://hostname:port" format + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/logging/teamcity/log-teamcity.phpt b/tests/end-to-end/logging/teamcity/log-teamcity.phpt new file mode 100644 index 00000000000..0b0efd9078e --- /dev/null +++ b/tests/end-to-end/logging/teamcity/log-teamcity.phpt @@ -0,0 +1,53 @@ +--TEST-- +phpunit --log-teamcity php://stdout ../../basic/unit/StatusTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +##teamcity[testCount count='13' flowId='%d'] +##teamcity[testSuiteStarted name='PHPUnit\TestFixture\Basic\StatusTest' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest' flowId='%d'] +##teamcity[testStarted name='testSuccess' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testSuccess' flowId='%d'] +##teamcity[testFinished name='testSuccess' duration='%d' flowId='%d'] +##teamcity[testStarted name='testFailure' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testFailure' flowId='%d'] +##teamcity[testFailed name='testFailure' message='Failed asserting that false is true.' details='%sStatusTest.php:%d|n' duration='%d' flowId='%d'] +##teamcity[testFinished name='testFailure' duration='%d' flowId='%d'] +##teamcity[testStarted name='testError' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testError' flowId='%d'] +##teamcity[testFailed name='testError' message='RuntimeException' details='%sStatusTest.php:%d|n' duration='%d' flowId='%d'] +##teamcity[testFinished name='testError' duration='%d' flowId='%d'] +##teamcity[testStarted name='testIncomplete' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testIncomplete' flowId='%d'] +##teamcity[testIgnored name='testIncomplete' message='' details='%sStatusTest.php:%d|n' duration='%d' flowId='%d'] +##teamcity[testFinished name='testIncomplete' duration='%d' flowId='%d'] +##teamcity[testStarted name='testSkipped' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testSkipped' flowId='%d'] +##teamcity[testIgnored name='testSkipped' message='' duration='%d' flowId='%d'] +##teamcity[testFinished name='testSkipped' duration='%d' flowId='%d'] +##teamcity[testStarted name='testRisky' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testRisky' flowId='%d'] +##teamcity[testFailed name='testRisky' message='This test did not perform any assertions' details='' duration='%d' flowId='%d'] +##teamcity[testFinished name='testRisky' duration='%d' flowId='%d'] +##teamcity[testStarted name='testSuccessWithMessage' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testSuccessWithMessage' flowId='%d'] +##teamcity[testFinished name='testSuccessWithMessage' duration='%d' flowId='%d'] +##teamcity[testStarted name='testFailureWithMessage' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testFailureWithMessage' flowId='%d'] +##teamcity[testFailed name='testFailureWithMessage' message='failure with custom message|nFailed asserting that false is true.' details='%sStatusTest.php:%d|n' duration='%d' flowId='%d'] +##teamcity[testFinished name='testFailureWithMessage' duration='%d' flowId='%d'] +##teamcity[testStarted name='testErrorWithMessage' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testErrorWithMessage' flowId='%d'] +##teamcity[testFailed name='testErrorWithMessage' message='RuntimeException: error with custom message' details='%sStatusTest.php:%d|n' duration='%d' flowId='%d'] +##teamcity[testFinished name='testErrorWithMessage' duration='%d' flowId='%d'] +##teamcity[testStarted name='testIncompleteWithMessage' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testIncompleteWithMessage' flowId='%d'] +##teamcity[testIgnored name='testIncompleteWithMessage' message='incomplete with custom message' details='%sStatusTest.php:%d|n' duration='%d' flowId='%d'] +##teamcity[testFinished name='testIncompleteWithMessage' duration='%d' flowId='%d'] +##teamcity[testIgnored name='testSkippedByMetadata' message='PHP > 9000 is required.' duration='%d' flowId='%d'] +##teamcity[testStarted name='testSkippedWithMessage' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testSkippedWithMessage' flowId='%d'] +##teamcity[testIgnored name='testSkippedWithMessage' message='skipped with custom message' duration='%d' flowId='%d'] +##teamcity[testFinished name='testSkippedWithMessage' duration='%d' flowId='%d'] +##teamcity[testStarted name='testRiskyWithMessage' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testRiskyWithMessage' flowId='%d'] +##teamcity[testFailed name='testRiskyWithMessage' message='This test did not perform any assertions' details='' duration='%d' flowId='%d'] +##teamcity[testFinished name='testRiskyWithMessage' duration='%d' flowId='%d'] +##teamcity[testSuiteFinished name='PHPUnit\TestFixture\Basic\StatusTest' flowId='%d'] diff --git a/tests/end-to-end/logging/teamcity/teamcity-data-provider.phpt b/tests/end-to-end/logging/teamcity/teamcity-data-provider.phpt new file mode 100644 index 00000000000..303d35ba6d9 --- /dev/null +++ b/tests/end-to-end/logging/teamcity/teamcity-data-provider.phpt @@ -0,0 +1,38 @@ +--TEST-- +phpunit --teamcity _files/DataProviderTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +##teamcity[testCount count='2' flowId='%d'] +##teamcity[testSuiteStarted name='PHPUnit\TestFixture\TeamCity\DataProviderTest' locationHint='php_qn://%sDataProviderTest.php::\PHPUnit\TestFixture\TeamCity\DataProviderTest' flowId='%d'] +##teamcity[testSuiteStarted name='testOne' locationHint='php_qn://%sDataProviderTest.php::\PHPUnit\TestFixture\TeamCity\DataProviderTest::testOne' flowId='%d'] +##teamcity[testStarted name='testOne with data set #0' locationHint='php_qn://%sDataProviderTest.php::\PHPUnit\TestFixture\TeamCity\DataProviderTest::testOne with data set #0' flowId='%d'] +##teamcity[testFinished name='testOne with data set #0' duration='%d' flowId='%d'] +##teamcity[testStarted name='testOne with data set #1' locationHint='php_qn://%sDataProviderTest.php::\PHPUnit\TestFixture\TeamCity\DataProviderTest::testOne with data set #1' flowId='%d'] +##teamcity[testFailed name='testOne with data set #1' message='Failed asserting that false is true.' details='%sDataProviderTest.php:28|n' duration='%d' flowId='%d'] +##teamcity[testFinished name='testOne with data set #1' duration='%d' flowId='%d'] +##teamcity[testSuiteFinished name='testOne' flowId='%d'] +##teamcity[testSuiteFinished name='PHPUnit\TestFixture\TeamCity\DataProviderTest' flowId='%d'] +Time: %s, Memory: %s + +There was 1 failure: + +1) PHPUnit\TestFixture\TeamCity\DataProviderTest::testOne#1 with data (false) +Failed asserting that false is true. + +%sDataProviderTest.php:28 + +FAILURES! +Tests: 2, Assertions: 2, Failures: 1. diff --git a/tests/end-to-end/logging/teamcity/teamcity-directory.phpt b/tests/end-to-end/logging/teamcity/teamcity-directory.phpt new file mode 100644 index 00000000000..03d9afb00dd --- /dev/null +++ b/tests/end-to-end/logging/teamcity/teamcity-directory.phpt @@ -0,0 +1,136 @@ +--TEST-- +phpunit --teamcity ../../basic/unit/ +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +##teamcity[testCount count='19' flowId='%d'] +##teamcity[testSuiteStarted name='CLI Arguments' flowId='%d'] +##teamcity[testSuiteStarted name='PHPUnit\TestFixture\Basic\SetUpBeforeClassTest' locationHint='php_qn://%stests%eend-to-end%e_files%ebasic%eunit%eSetUpBeforeClassTest.php::\PHPUnit\TestFixture\Basic\SetUpBeforeClassTest' flowId='%d'] +##teamcity[testFailed name='PHPUnit\TestFixture\Basic\SetUpBeforeClassTest' message='Exception: forcing an Exception in setUpBeforeClass()' details='%stests%eend-to-end%e_files%ebasic%eunit%eSetUpBeforeClassTest.php:32|n' duration='%d' flowId='%d'] +##teamcity[testSuiteFinished name='PHPUnit\TestFixture\Basic\SetUpBeforeClassTest' message='Exception: forcing an Exception in setUpBeforeClass()' details='%stests%eend-to-end%e_files%ebasic%eunit%eSetUpBeforeClassTest.php:32|n' duration='%d' flowId='%d'] +##teamcity[testSuiteFinished name='PHPUnit\TestFixture\Basic\SetUpBeforeClassTest' flowId='%d'] +##teamcity[testSuiteStarted name='PHPUnit\TestFixture\Basic\SetUpTest' locationHint='php_qn://%stests%eend-to-end%e_files%ebasic%eunit%eSetUpTest.php::\PHPUnit\TestFixture\Basic\SetUpTest' flowId='%d'] +##teamcity[testFailed name='testOneWithSetUpException' message='RuntimeException: throw exception in setUp' details='%stests%eend-to-end%e_files%ebasic%eunit%eSetUpTest.php:%d|n' duration='%d' flowId='%d'] +##teamcity[testFailed name='testTwoWithSetUpException' message='RuntimeException: throw exception in setUp' details='%stests%eend-to-end%e_files%ebasic%eunit%eSetUpTest.php:%d|n' duration='%d' flowId='%d'] +##teamcity[testSuiteFinished name='PHPUnit\TestFixture\Basic\SetUpTest' flowId='%d'] +##teamcity[testSuiteStarted name='PHPUnit\TestFixture\Basic\StatusTest' locationHint='php_qn://%stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest' flowId='%d'] +##teamcity[testStarted name='testSuccess' locationHint='php_qn://%stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testSuccess' flowId='%d'] +##teamcity[testFinished name='testSuccess' duration='%d' flowId='%d'] +##teamcity[testStarted name='testFailure' locationHint='php_qn://%stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testFailure' flowId='%d'] +##teamcity[testFailed name='testFailure' message='Failed asserting that false is true.' details='%stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php:%d|n' duration='%d' flowId='%d'] +##teamcity[testFinished name='testFailure' duration='%d' flowId='%d'] +##teamcity[testStarted name='testError' locationHint='php_qn://%stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testError' flowId='%d'] +##teamcity[testFailed name='testError' message='RuntimeException' details='%stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php:%d|n' duration='%d' flowId='%d'] +##teamcity[testFinished name='testError' duration='%d' flowId='%d'] +##teamcity[testStarted name='testIncomplete' locationHint='php_qn://%stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testIncomplete' flowId='%d'] +##teamcity[testIgnored name='testIncomplete' message='' details='%stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php:%d|n' duration='%d' flowId='%d'] +##teamcity[testFinished name='testIncomplete' duration='%d' flowId='%d'] +##teamcity[testStarted name='testSkipped' locationHint='php_qn://%stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testSkipped' flowId='%d'] +##teamcity[testIgnored name='testSkipped' message='' duration='%d' flowId='%d'] +##teamcity[testFinished name='testSkipped' duration='%d' flowId='%d'] +##teamcity[testStarted name='testRisky' locationHint='php_qn://%stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testRisky' flowId='%d'] +##teamcity[testFailed name='testRisky' message='This test did not perform any assertions' details='' duration='%d' flowId='%d'] +##teamcity[testFinished name='testRisky' duration='%d' flowId='%d'] +##teamcity[testStarted name='testSuccessWithMessage' locationHint='php_qn://%stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testSuccessWithMessage' flowId='%d'] +##teamcity[testFinished name='testSuccessWithMessage' duration='%d' flowId='%d'] +##teamcity[testStarted name='testFailureWithMessage' locationHint='php_qn://%stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testFailureWithMessage' flowId='%d'] +##teamcity[testFailed name='testFailureWithMessage' message='failure with custom message|nFailed asserting that false is true.' details='%stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php:%d|n' duration='%d' flowId='%d'] +##teamcity[testFinished name='testFailureWithMessage' duration='%d' flowId='%d'] +##teamcity[testStarted name='testErrorWithMessage' locationHint='php_qn://%stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testErrorWithMessage' flowId='%d'] +##teamcity[testFailed name='testErrorWithMessage' message='RuntimeException: error with custom message' details='%stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php:%d|n' duration='%d' flowId='%d'] +##teamcity[testFinished name='testErrorWithMessage' duration='%d' flowId='%d'] +##teamcity[testStarted name='testIncompleteWithMessage' locationHint='php_qn://%stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testIncompleteWithMessage' flowId='%d'] +##teamcity[testIgnored name='testIncompleteWithMessage' message='incomplete with custom message' details='%stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php:%d|n' duration='%d' flowId='%d'] +##teamcity[testFinished name='testIncompleteWithMessage' duration='%d' flowId='%d'] +##teamcity[testIgnored name='testSkippedByMetadata' message='PHP > 9000 is required.' duration='%d' flowId='%d'] +##teamcity[testStarted name='testSkippedWithMessage' locationHint='php_qn://%stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testSkippedWithMessage' flowId='%d'] +##teamcity[testIgnored name='testSkippedWithMessage' message='skipped with custom message' duration='%d' flowId='%d'] +##teamcity[testFinished name='testSkippedWithMessage' duration='%d' flowId='%d'] +##teamcity[testStarted name='testRiskyWithMessage' locationHint='php_qn://%stests%eend-to-end%e_files%ebasic%eunit%eStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testRiskyWithMessage' flowId='%d'] +##teamcity[testFailed name='testRiskyWithMessage' message='This test did not perform any assertions' details='' duration='%d' flowId='%d'] +##teamcity[testFinished name='testRiskyWithMessage' duration='%d' flowId='%d'] +##teamcity[testSuiteFinished name='PHPUnit\TestFixture\Basic\StatusTest' flowId='%d'] +##teamcity[testSuiteStarted name='PHPUnit\TestFixture\Basic\TearDownAfterClassTest' locationHint='php_qn://%stests%eend-to-end%e_files%ebasic%eunit%eTearDownAfterClassTest.php::\PHPUnit\TestFixture\Basic\TearDownAfterClassTest' flowId='%d'] +##teamcity[testStarted name='testOne' locationHint='php_qn://%stests%eend-to-end%e_files%ebasic%eunit%eTearDownAfterClassTest.php::\PHPUnit\TestFixture\Basic\TearDownAfterClassTest::testOne' flowId='%d'] +##teamcity[testFinished name='testOne' duration='%d' flowId='%d'] +##teamcity[testStarted name='testTwo' locationHint='php_qn://%stests%eend-to-end%e_files%ebasic%eunit%eTearDownAfterClassTest.php::\PHPUnit\TestFixture\Basic\TearDownAfterClassTest::testTwo' flowId='%d'] +##teamcity[testFinished name='testTwo' duration='%d' flowId='%d'] +##teamcity[testSuiteFinished name='PHPUnit\TestFixture\Basic\TearDownAfterClassTest' flowId='%d'] +##teamcity[testSuiteFinished name='CLI Arguments' flowId='%d'] +Time: %s, Memory: %s + +There were 6 errors: + +1) PHPUnit\TestFixture\Basic\SetUpBeforeClassTest +Exception: forcing an Exception in setUpBeforeClass() + +%s%eSetUpBeforeClassTest.php:%d + +2) PHPUnit\TestFixture\Basic\SetUpTest::testOneWithSetUpException +RuntimeException: throw exception in setUp + +%s%eSetUpTest.php:%d + +3) PHPUnit\TestFixture\Basic\SetUpTest::testTwoWithSetUpException +RuntimeException: throw exception in setUp + +%s%eSetUpTest.php:%d + +4) PHPUnit\TestFixture\Basic\StatusTest::testError +RuntimeException: + +%s%eStatusTest.php:%d + +5) PHPUnit\TestFixture\Basic\StatusTest::testErrorWithMessage +RuntimeException: error with custom message + +%s%eStatusTest.php:%d + +6) PHPUnit\TestFixture\Basic\TearDownAfterClassTest +Exception: forcing an Exception in tearDownAfterClass() + +%s%eTearDownAfterClassTest.php:%d + +-- + +There were 2 failures: + +1) PHPUnit\TestFixture\Basic\StatusTest::testFailure +Failed asserting that false is true. + +%s%eStatusTest.php:%d + +2) PHPUnit\TestFixture\Basic\StatusTest::testFailureWithMessage +failure with custom message +Failed asserting that false is true. + +%s%eStatusTest.php:%d + +-- + +There were 2 risky tests: + +1) PHPUnit\TestFixture\Basic\StatusTest::testRisky +This test did not perform any assertions + +%s%eStatusTest.php:%d + +2) PHPUnit\TestFixture\Basic\StatusTest::testRiskyWithMessage +This test did not perform any assertions + +%s%eStatusTest.php:%d + +ERRORS! +Tests: 18, Assertions: 6, Errors: 6, Failures: 2, Skipped: 3, Incomplete: 2, Risky: 2. diff --git a/tests/end-to-end/logging/teamcity/teamcity-file.phpt b/tests/end-to-end/logging/teamcity/teamcity-file.phpt new file mode 100644 index 00000000000..bd21ba11f1f --- /dev/null +++ b/tests/end-to-end/logging/teamcity/teamcity-file.phpt @@ -0,0 +1,100 @@ +--TEST-- +phpunit --teamcity ../../basic/unit/StatusTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +##teamcity[testCount count='13' flowId='%d'] +##teamcity[testSuiteStarted name='PHPUnit\TestFixture\Basic\StatusTest' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest' flowId='%d'] +##teamcity[testStarted name='testSuccess' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testSuccess' flowId='%d'] +##teamcity[testFinished name='testSuccess' duration='%d' flowId='%d'] +##teamcity[testStarted name='testFailure' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testFailure' flowId='%d'] +##teamcity[testFailed name='testFailure' message='Failed asserting that false is true.' details='%sStatusTest.php:%d|n' duration='%d' flowId='%d'] +##teamcity[testFinished name='testFailure' duration='%d' flowId='%d'] +##teamcity[testStarted name='testError' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testError' flowId='%d'] +##teamcity[testFailed name='testError' message='RuntimeException' details='%sStatusTest.php:%d|n' duration='%d' flowId='%d'] +##teamcity[testFinished name='testError' duration='%d' flowId='%d'] +##teamcity[testStarted name='testIncomplete' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testIncomplete' flowId='%d'] +##teamcity[testIgnored name='testIncomplete' message='' details='%sStatusTest.php:%d|n' duration='%d' flowId='%d'] +##teamcity[testFinished name='testIncomplete' duration='%d' flowId='%d'] +##teamcity[testStarted name='testSkipped' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testSkipped' flowId='%d'] +##teamcity[testIgnored name='testSkipped' message='' duration='%d' flowId='%d'] +##teamcity[testFinished name='testSkipped' duration='%d' flowId='%d'] +##teamcity[testStarted name='testRisky' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testRisky' flowId='%d'] +##teamcity[testFailed name='testRisky' message='This test did not perform any assertions' details='' duration='%d' flowId='%d'] +##teamcity[testFinished name='testRisky' duration='%d' flowId='%d'] +##teamcity[testStarted name='testSuccessWithMessage' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testSuccessWithMessage' flowId='%d'] +##teamcity[testFinished name='testSuccessWithMessage' duration='%d' flowId='%d'] +##teamcity[testStarted name='testFailureWithMessage' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testFailureWithMessage' flowId='%d'] +##teamcity[testFailed name='testFailureWithMessage' message='failure with custom message|nFailed asserting that false is true.' details='%sStatusTest.php:%d|n' duration='%d' flowId='%d'] +##teamcity[testFinished name='testFailureWithMessage' duration='%d' flowId='%d'] +##teamcity[testStarted name='testErrorWithMessage' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testErrorWithMessage' flowId='%d'] +##teamcity[testFailed name='testErrorWithMessage' message='RuntimeException: error with custom message' details='%sStatusTest.php:%d|n' duration='%d' flowId='%d'] +##teamcity[testFinished name='testErrorWithMessage' duration='%d' flowId='%d'] +##teamcity[testStarted name='testIncompleteWithMessage' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testIncompleteWithMessage' flowId='%d'] +##teamcity[testIgnored name='testIncompleteWithMessage' message='incomplete with custom message' details='%sStatusTest.php:%d|n' duration='%d' flowId='%d'] +##teamcity[testFinished name='testIncompleteWithMessage' duration='%d' flowId='%d'] +##teamcity[testIgnored name='testSkippedByMetadata' message='PHP > 9000 is required.' duration='%d' flowId='%d'] +##teamcity[testStarted name='testSkippedWithMessage' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testSkippedWithMessage' flowId='%d'] +##teamcity[testIgnored name='testSkippedWithMessage' message='skipped with custom message' duration='%d' flowId='%d'] +##teamcity[testFinished name='testSkippedWithMessage' duration='%d' flowId='%d'] +##teamcity[testStarted name='testRiskyWithMessage' locationHint='php_qn://%sStatusTest.php::\PHPUnit\TestFixture\Basic\StatusTest::testRiskyWithMessage' flowId='%d'] +##teamcity[testFailed name='testRiskyWithMessage' message='This test did not perform any assertions' details='' duration='%d' flowId='%d'] +##teamcity[testFinished name='testRiskyWithMessage' duration='%d' flowId='%d'] +##teamcity[testSuiteFinished name='PHPUnit\TestFixture\Basic\StatusTest' flowId='%d'] +Time: %s, Memory: %s + +There were 2 errors: + +1) PHPUnit\TestFixture\Basic\StatusTest::testError +RuntimeException: + +%s%eStatusTest.php:%d + +2) PHPUnit\TestFixture\Basic\StatusTest::testErrorWithMessage +RuntimeException: error with custom message + +%s%eStatusTest.php:%d + +-- + +There were 2 failures: + +1) PHPUnit\TestFixture\Basic\StatusTest::testFailure +Failed asserting that false is true. + +%s%eStatusTest.php:%d + +2) PHPUnit\TestFixture\Basic\StatusTest::testFailureWithMessage +failure with custom message +Failed asserting that false is true. + +%s%eStatusTest.php:%d + +-- + +There were 2 risky tests: + +1) PHPUnit\TestFixture\Basic\StatusTest::testRisky +This test did not perform any assertions + +%s%eStatusTest.php:%d + +2) PHPUnit\TestFixture\Basic\StatusTest::testRiskyWithMessage +This test did not perform any assertions + +%s%eStatusTest.php:%d + +ERRORS! +Tests: 13, Assertions: 4, Errors: 2, Failures: 2, Skipped: 3, Incomplete: 2, Risky: 2. diff --git a/tests/end-to-end/logging/teamcity/teamcity-inner-exceptions.phpt b/tests/end-to-end/logging/teamcity/teamcity-inner-exceptions.phpt new file mode 100644 index 00000000000..487b9e50b63 --- /dev/null +++ b/tests/end-to-end/logging/teamcity/teamcity-inner-exceptions.phpt @@ -0,0 +1,25 @@ +--TEST-- +phpunit --log-teamcity php://stdout ../../_files/ExceptionStackTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +##teamcity[testCount count='2' flowId='%d'] +##teamcity[testSuiteStarted name='PHPUnit\TestFixture\ExceptionStackTest' locationHint='php_qn://%s%etests%e_files%eExceptionStackTest.php::\PHPUnit\TestFixture\ExceptionStackTest' flowId='%d'] +##teamcity[testStarted name='testPrintingChildException' locationHint='php_qn://%s%etests%e_files%eExceptionStackTest.php::\PHPUnit\TestFixture\ExceptionStackTest::testPrintingChildException' flowId='%d'] +##teamcity[testFailed name='testPrintingChildException' message='Child exception|nmessage|nFailed asserting that two arrays are equal.|n--- Expected|n+++ Actual|n@@ @@|n Array (|n- 0 => 1|n+ 0 => 2|n )|n' details='%s%etests%e_files%eExceptionStackTest.php:27|n|nCaused by|nmessage|nFailed asserting that two arrays are equal.|n--- Expected|n+++ Actual|n@@ @@|n Array (|n- 0 => 1|n+ 0 => 2|n )|n|n%s%etests%e_files%eExceptionStackTest.php:23|n' duration='%d' flowId='%d'] +##teamcity[testFinished name='testPrintingChildException' duration='%d' flowId='%d'] +##teamcity[testStarted name='testNestedExceptions' locationHint='php_qn://%s%etests%e_files%eExceptionStackTest.php::\PHPUnit\TestFixture\ExceptionStackTest::testNestedExceptions' flowId='%d'] +##teamcity[testFailed name='testNestedExceptions' message='Exception: One' details='%s%etests%e_files%eExceptionStackTest.php:35|n|nCaused by|nInvalidArgumentException: Two|n|n%s%etests%e_files%eExceptionStackTest.php:34|n|nCaused by|nException: Three|n|n%s%etests%e_files%eExceptionStackTest.php:33|n' duration='%d' flowId='%d'] +##teamcity[testFinished name='testNestedExceptions' duration='%d' flowId='%d'] +##teamcity[testSuiteFinished name='PHPUnit\TestFixture\ExceptionStackTest' flowId='%d'] diff --git a/tests/end-to-end/logging/teamcity/teamcity-print-deprecation.phpt b/tests/end-to-end/logging/teamcity/teamcity-print-deprecation.phpt new file mode 100644 index 00000000000..9aee5a41262 --- /dev/null +++ b/tests/end-to-end/logging/teamcity/teamcity-print-deprecation.phpt @@ -0,0 +1,25 @@ +--TEST-- +TeamCity: print deprecation message +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +##teamcity[testCount count='%d' flowId='%d'] +##teamcity[testSuiteStarted name='PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest' locationHint='%sDeprecationTest.php::\PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest' flowId='%d'] +##teamcity[testStarted name='testOne' locationHint='%sDeprecationTest.php::\PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testOne' flowId='%d'] +##teamcity[testFinished name='testOne' duration='%d' flowId='%d'] +##teamcity[testStarted name='testTwo' locationHint='%sDeprecationTest.php::\PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testTwo' flowId='%d'] +##teamcity[testFinished name='testTwo' duration='%d' flowId='%d'] +##teamcity[testStarted name='testThree' locationHint='php_qn:%sDeprecationTest.php::\PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest::testThree' flowId='%d'] +##teamcity[testFinished name='testThree' duration='%d' flowId='%d'] +##teamcity[testSuiteFinished name='PHPUnit\TestFixture\TestRunnerStopping\DeprecationTest' flowId='%d'] diff --git a/tests/end-to-end/logging/teamcity/teamcity-print-error.phpt b/tests/end-to-end/logging/teamcity/teamcity-print-error.phpt new file mode 100644 index 00000000000..ee97b1af979 --- /dev/null +++ b/tests/end-to-end/logging/teamcity/teamcity-print-error.phpt @@ -0,0 +1,24 @@ +--TEST-- +TeamCity: print error message +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +##teamcity[testCount count='2' flowId='%d'] +##teamcity[testSuiteStarted name='PHPUnit\TestFixture\TestRunnerStopping\ErrorTest' locationHint='%sErrorTest.php::\PHPUnit\TestFixture\TestRunnerStopping\ErrorTest' flowId='%d'] +##teamcity[testStarted name='testOne' locationHint='%sErrorTest.php::\PHPUnit\TestFixture\TestRunnerStopping\ErrorTest::testOne' flowId='%d'] +##teamcity[testFailed name='testOne' message='Exception: message' details='%sErrorTest.php:19|n' duration='%d' flowId='%d'] +##teamcity[testFinished name='testOne' duration='%d' flowId='%d'] +##teamcity[testStarted name='testTwo' locationHint='%sErrorTest.php::\PHPUnit\TestFixture\TestRunnerStopping\ErrorTest::testTwo' flowId='%d'] +##teamcity[testFinished name='testTwo' duration='%d' flowId='%d'] +##teamcity[testSuiteFinished name='PHPUnit\TestFixture\TestRunnerStopping\ErrorTest' flowId='%d'] diff --git a/tests/end-to-end/logging/teamcity/teamcity-print-failure.phpt b/tests/end-to-end/logging/teamcity/teamcity-print-failure.phpt new file mode 100644 index 00000000000..1cd2b680011 --- /dev/null +++ b/tests/end-to-end/logging/teamcity/teamcity-print-failure.phpt @@ -0,0 +1,23 @@ +--TEST-- +TeamCity: print failure message +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +##teamcity[testCount count='%d' flowId='%d'] +##teamcity[testSuiteStarted name='PHPUnit\TestFixture\TestRunnerStopping\FailureTest' locationHint='%sFailureTest.php::\PHPUnit\TestFixture\TestRunnerStopping\FailureTest' flowId='%d'] +##teamcity[testStarted name='testOne' locationHint='%sFailureTest.php::\PHPUnit\TestFixture\TestRunnerStopping\FailureTest::testOne' flowId='%d'] +##teamcity[testFailed name='testOne' message='Failed asserting that false is true.' details='%sFailureTest.php:18|n' duration='%d' flowId='%d'] +##teamcity[testFinished name='testOne' duration='%d' flowId='%d'] +##teamcity[testStarted name='testTwo' locationHint='%sFailureTest.php::\PHPUnit\TestFixture\TestRunnerStopping\FailureTest::testTwo' flowId='%d'] +##teamcity[testFinished name='testTwo' duration='%d' flowId='%d'] +##teamcity[testSuiteFinished name='PHPUnit\TestFixture\TestRunnerStopping\FailureTest' flowId='%d'] diff --git a/tests/end-to-end/logging/teamcity/teamcity-print-incomplete.phpt b/tests/end-to-end/logging/teamcity/teamcity-print-incomplete.phpt new file mode 100644 index 00000000000..79ed061ba0a --- /dev/null +++ b/tests/end-to-end/logging/teamcity/teamcity-print-incomplete.phpt @@ -0,0 +1,24 @@ +--TEST-- +TeamCity: print incomplete message +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +##teamcity[testCount count='2' flowId='%d'] +##teamcity[testSuiteStarted name='PHPUnit\TestFixture\TestRunnerStopping\IncompleteTest' locationHint='%sIncompleteTest.php::\PHPUnit\TestFixture\TestRunnerStopping\IncompleteTest' flowId='%d'] +##teamcity[testStarted name='testOne' locationHint='%sIncompleteTest.php::\PHPUnit\TestFixture\TestRunnerStopping\IncompleteTest::testOne' flowId='%d'] +##teamcity[testIgnored name='testOne' message='message' details='%sIncompleteTest.php:18|n' duration='%d' flowId='%d'] +##teamcity[testFinished name='testOne' duration='%d' flowId='%d'] +##teamcity[testStarted name='testTwo' locationHint='%sIncompleteTest.php::\PHPUnit\TestFixture\TestRunnerStopping\IncompleteTest::testTwo' flowId='%d'] +##teamcity[testFinished name='testTwo' duration='%d' flowId='%d'] +##teamcity[testSuiteFinished name='PHPUnit\TestFixture\TestRunnerStopping\IncompleteTest' flowId='%d'] diff --git a/tests/end-to-end/logging/teamcity/teamcity-print-notice.phpt b/tests/end-to-end/logging/teamcity/teamcity-print-notice.phpt new file mode 100644 index 00000000000..7dc22aeaab0 --- /dev/null +++ b/tests/end-to-end/logging/teamcity/teamcity-print-notice.phpt @@ -0,0 +1,23 @@ +--TEST-- +TeamCity: print notice message +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +##teamcity[testCount count='2' flowId='%d'] +##teamcity[testSuiteStarted name='PHPUnit\TestFixture\TestRunnerStopping\NoticeTest' locationHint='%sNoticeTest.php::\PHPUnit\TestFixture\TestRunnerStopping\NoticeTest' flowId='%d'] +##teamcity[testStarted name='testOne' locationHint='%sNoticeTest.php::\PHPUnit\TestFixture\TestRunnerStopping\NoticeTest::testOne' flowId='%d'] +##teamcity[testFinished name='testOne' duration='%d' flowId='%d'] +##teamcity[testStarted name='testTwo' locationHint='%sNoticeTest.php::\PHPUnit\TestFixture\TestRunnerStopping\NoticeTest::testTwo' flowId='%d'] +##teamcity[testFinished name='testTwo' duration='%d' flowId='%d'] +##teamcity[testSuiteFinished name='PHPUnit\TestFixture\TestRunnerStopping\NoticeTest' flowId='%d'] diff --git a/tests/end-to-end/logging/teamcity/teamcity-print-risky.phpt b/tests/end-to-end/logging/teamcity/teamcity-print-risky.phpt new file mode 100644 index 00000000000..bac507578c3 --- /dev/null +++ b/tests/end-to-end/logging/teamcity/teamcity-print-risky.phpt @@ -0,0 +1,23 @@ +--TEST-- +TeamCity: print risky message +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +##teamcity[testCount count='2' flowId='%d'] +##teamcity[testSuiteStarted name='PHPUnit\TestFixture\TestRunnerStopping\RiskyTest' locationHint='%sRiskyTest.php::\PHPUnit\TestFixture\TestRunnerStopping\RiskyTest' flowId='%d'] +##teamcity[testStarted name='testOne' locationHint='%sRiskyTest.php::\PHPUnit\TestFixture\TestRunnerStopping\RiskyTest::testOne' flowId='%d'] +##teamcity[testFailed name='testOne' message='This test did not perform any assertions' details='' duration='%d' flowId='%d'] +##teamcity[testFinished name='testOne' duration='%d' flowId='%d'] +##teamcity[testStarted name='testTwo' locationHint='%sRiskyTest.php::\PHPUnit\TestFixture\TestRunnerStopping\RiskyTest::testTwo' flowId='%d'] +##teamcity[testFinished name='testTwo' duration='%d' flowId='%d'] +##teamcity[testSuiteFinished name='PHPUnit\TestFixture\TestRunnerStopping\RiskyTest' flowId='%d'] diff --git a/tests/end-to-end/logging/teamcity/teamcity-print-skipped-before-class.phpt b/tests/end-to-end/logging/teamcity/teamcity-print-skipped-before-class.phpt new file mode 100644 index 00000000000..3049779f431 --- /dev/null +++ b/tests/end-to-end/logging/teamcity/teamcity-print-skipped-before-class.phpt @@ -0,0 +1,19 @@ +--TEST-- +TeamCity: test skipped in before-class method +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +##teamcity[testCount count='1' flowId='%d'] +##teamcity[testSuiteStarted name='PHPUnit\TestFixture\TestRunnerStopping\SkippedBeforeClassTest' locationHint='php_qn:%sSkippedBeforeClassTest.php::\PHPUnit\TestFixture\TestRunnerStopping\SkippedBeforeClassTest' flowId='%d'] +##teamcity[testIgnored name='PHPUnit\TestFixture\TestRunnerStopping\SkippedBeforeClassTest' message='message' duration='%d' flowId='%d'] +##teamcity[testSuiteFinished name='PHPUnit\TestFixture\TestRunnerStopping\SkippedBeforeClassTest' message='message' duration='%d' flowId='%d'] diff --git a/tests/end-to-end/logging/teamcity/teamcity-print-skipped.phpt b/tests/end-to-end/logging/teamcity/teamcity-print-skipped.phpt new file mode 100644 index 00000000000..13ded3d259e --- /dev/null +++ b/tests/end-to-end/logging/teamcity/teamcity-print-skipped.phpt @@ -0,0 +1,23 @@ +--TEST-- +TeamCity: test skipped in test method +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +##teamcity[testCount count='2' flowId='%d'] +##teamcity[testSuiteStarted name='PHPUnit\TestFixture\TestRunnerStopping\SkippedTest' locationHint='%sSkippedTest.php::\PHPUnit\TestFixture\TestRunnerStopping\SkippedTest' flowId='%d'] +##teamcity[testStarted name='testOne' locationHint='%sSkippedTest.php::\PHPUnit\TestFixture\TestRunnerStopping\SkippedTest::testOne' flowId='%d'] +##teamcity[testIgnored name='testOne' message='message' duration='%d' flowId='%d'] +##teamcity[testFinished name='testOne' duration='%d' flowId='%d'] +##teamcity[testStarted name='testTwo' locationHint='%sSkippedTest.php::\PHPUnit\TestFixture\TestRunnerStopping\SkippedTest::testTwo' flowId='%d'] +##teamcity[testFinished name='testTwo' duration='%d' flowId='%d'] +##teamcity[testSuiteFinished name='PHPUnit\TestFixture\TestRunnerStopping\SkippedTest' flowId='%d'] diff --git a/tests/end-to-end/logging/teamcity/teamcity-print-warning.phpt b/tests/end-to-end/logging/teamcity/teamcity-print-warning.phpt new file mode 100644 index 00000000000..cc6530e37d4 --- /dev/null +++ b/tests/end-to-end/logging/teamcity/teamcity-print-warning.phpt @@ -0,0 +1,23 @@ +--TEST-- +TeamCity: print warning message +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +##teamcity[testCount count='2' flowId='%d'] +##teamcity[testSuiteStarted name='PHPUnit\TestFixture\TestRunnerStopping\WarningTest' locationHint='%sWarningTest.php::\PHPUnit\TestFixture\TestRunnerStopping\WarningTest' flowId='%d'] +##teamcity[testStarted name='testOne' locationHint='%sWarningTest.php::\PHPUnit\TestFixture\TestRunnerStopping\WarningTest::testOne' flowId='%d'] +##teamcity[testFinished name='testOne' duration='%d' flowId='%d'] +##teamcity[testStarted name='testTwo' locationHint='%sWarningTest.php::\PHPUnit\TestFixture\TestRunnerStopping\WarningTest::testTwo' flowId='%d'] +##teamcity[testFinished name='testTwo' duration='%d' flowId='%d'] +##teamcity[testSuiteFinished name='PHPUnit\TestFixture\TestRunnerStopping\WarningTest' flowId='%d'] diff --git a/tests/end-to-end/logging/teamcity/teamcity-warning.phpt b/tests/end-to-end/logging/teamcity/teamcity-warning.phpt new file mode 100644 index 00000000000..6ec0cd946aa --- /dev/null +++ b/tests/end-to-end/logging/teamcity/teamcity-warning.phpt @@ -0,0 +1,39 @@ +--TEST-- +phpunit --teamcity ../../basic/unit/StatusTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +##teamcity[testCount count='1' flowId='%d'] +##teamcity[testSuiteStarted name='%s%etests%eend-to-end%elogging%e_files%eteamcity-warning%ephpunit.xml' flowId='%d'] +##teamcity[testSuiteStarted name='default' flowId='%d'] +##teamcity[testSuiteStarted name='PHPUnit\TestFixture\Test' locationHint='php_qn://%s%eteamcity-warning%etests%eTest.php::\PHPUnit\TestFixture\Test' flowId='%d'] +##teamcity[testStarted name='testOne' locationHint='php_qn://%s%eteamcity-warning%etests%eTest.php::\PHPUnit\TestFixture\Test::testOne' flowId='%d'] +##teamcity[testFinished name='testOne' duration='%s' flowId='%d'] +##teamcity[testSuiteFinished name='PHPUnit\TestFixture\Test' flowId='%d'] +##teamcity[testSuiteFinished name='default' flowId='%d'] +##teamcity[testSuiteFinished name='%s%etests%eend-to-end%elogging%e_files%eteamcity-warning%ephpunit.xml' flowId='%d'] +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Test results may not be as expected because the XML configuration file did not pass validation: + + Line 11: + - Element 'foo': This element is not expected. + + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/logging/testdox/testdox-case-with-dollar-sign.phpt b/tests/end-to-end/logging/testdox/testdox-case-with-dollar-sign.phpt new file mode 100644 index 00000000000..16f28d0da1d --- /dev/null +++ b/tests/end-to-end/logging/testdox/testdox-case-with-dollar-sign.phpt @@ -0,0 +1,28 @@ +--TEST-- +Testdox: output containing dollar signs in the value from data provider +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +.... 4 / 4 (100%) + +Time: %s, Memory: %s + +Case With Dollar Sign (PHPUnit\TestFixture\CaseWithDollarSign) + ✔ The "$12.34" is used for this test + ✔ The "Some text before the price $5.67" is used for this test + ✔ The "Dollar sign followed by letter $Q" is used for this test + ✔ The "Alone $ surrounded by spaces" is used for this test + +OK (4 tests, 4 assertions) diff --git a/tests/end-to-end/logging/testdox/testdox-html-invalid-path.phpt b/tests/end-to-end/logging/testdox/testdox-html-invalid-path.phpt new file mode 100644 index 00000000000..c9586fd5fed --- /dev/null +++ b/tests/end-to-end/logging/testdox/testdox-html-invalid-path.phpt @@ -0,0 +1,28 @@ +--TEST-- +Test runner emits warning when --testdox-html is used with an invalid target path +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Cannot log test results in TestDox HTML format to "": Directory "" does not exist and could not be created + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/logging/testdox/testdox-html-invalid-socket.phpt b/tests/end-to-end/logging/testdox/testdox-html-invalid-socket.phpt new file mode 100644 index 00000000000..60c404f6c61 --- /dev/null +++ b/tests/end-to-end/logging/testdox/testdox-html-invalid-socket.phpt @@ -0,0 +1,28 @@ +--TEST-- +Test runner emits warning when --testdox-html is used with an invalid socket +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Cannot log test results in TestDox HTML format to "socket://hostname:port:wrong": "socket://hostname:port:wrong" does not match "socket://hostname:port" format + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/logging/testdox/testdox-html.phpt b/tests/end-to-end/logging/testdox/testdox-html.phpt new file mode 100644 index 00000000000..987277e2c07 --- /dev/null +++ b/tests/end-to-end/logging/testdox/testdox-html.phpt @@ -0,0 +1,81 @@ +--TEST-- +phpunit --testdox-html php://stdout ../../_files/BankAccountTest.php +--FILE-- +run($_SERVER['argv']); + +print file_get_contents($output); + +unlink($output); +--EXPECTF-- + + + + + Test Documentation + + + +

    Bank Account (PHPUnit\TestFixture\BankAccount)

    +
      +
    • Balance is initially zero
    • +
    • Balance cannot become negative
    • +
    + + diff --git a/tests/end-to-end/logging/testdox/testdox-print-deprecation.phpt b/tests/end-to-end/logging/testdox/testdox-print-deprecation.phpt new file mode 100644 index 00000000000..fb6c33b5e1c --- /dev/null +++ b/tests/end-to-end/logging/testdox/testdox-print-deprecation.phpt @@ -0,0 +1,20 @@ +--TEST-- +Testdox: print deprecation message +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +Deprecation (PHPUnit\TestFixture\TestRunnerStopping\Deprecation) + [x] One + [x] Two + [x] Three diff --git a/tests/end-to-end/logging/testdox/testdox-print-error.phpt b/tests/end-to-end/logging/testdox/testdox-print-error.phpt new file mode 100644 index 00000000000..9e1036a7b4e --- /dev/null +++ b/tests/end-to-end/logging/testdox/testdox-print-error.phpt @@ -0,0 +1,19 @@ +--TEST-- +Testdox: print error message +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +Error (PHPUnit\TestFixture\TestRunnerStopping\Error) + [ ] One + [x] Two diff --git a/tests/end-to-end/logging/testdox/testdox-print-failure.phpt b/tests/end-to-end/logging/testdox/testdox-print-failure.phpt new file mode 100644 index 00000000000..ae968326d3e --- /dev/null +++ b/tests/end-to-end/logging/testdox/testdox-print-failure.phpt @@ -0,0 +1,18 @@ +--TEST-- +Testdox: print failure message +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +Failure (PHPUnit\TestFixture\TestRunnerStopping\Failure) + [ ] One + [x] Two diff --git a/tests/end-to-end/logging/testdox/testdox-print-incomplete.phpt b/tests/end-to-end/logging/testdox/testdox-print-incomplete.phpt new file mode 100644 index 00000000000..bd74a312e32 --- /dev/null +++ b/tests/end-to-end/logging/testdox/testdox-print-incomplete.phpt @@ -0,0 +1,19 @@ +--TEST-- +Testdox: print incomplete message +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +Incomplete (PHPUnit\TestFixture\TestRunnerStopping\Incomplete) + [ ] One + [x] Two diff --git a/tests/end-to-end/logging/testdox/testdox-print-notice.phpt b/tests/end-to-end/logging/testdox/testdox-print-notice.phpt new file mode 100644 index 00000000000..e1e2f4c3c96 --- /dev/null +++ b/tests/end-to-end/logging/testdox/testdox-print-notice.phpt @@ -0,0 +1,19 @@ +--TEST-- +Testdox: print notice message +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +Notice (PHPUnit\TestFixture\TestRunnerStopping\Notice) + [x] One + [x] Two diff --git a/tests/end-to-end/logging/testdox/testdox-print-previous-exception-colorized.phpt b/tests/end-to-end/logging/testdox/testdox-print-previous-exception-colorized.phpt new file mode 100644 index 00000000000..f16c74e95ef --- /dev/null +++ b/tests/end-to-end/logging/testdox/testdox-print-previous-exception-colorized.phpt @@ -0,0 +1,41 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5863 +--SKIPIF-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: PHP %s + +Time: %s, Memory: %s + +Throws With Previous Exception (PHPUnit\TestFixture\ThrowsWithPreviousException) + ✘ Foo + ┐ + ├ Exception: Outer + │ + │ %sThrowsWithPreviousExceptionTest.php:%d%A + │ Caused by: + ├ Exception: Inner + │ + │ %sThrowsWithPreviousExceptionTest.php:%d%A + ┴ + +ERRORS! +Tests: 1, Assertions: 0, Errors: 1. diff --git a/tests/end-to-end/logging/testdox/testdox-print-previous-exception.phpt b/tests/end-to-end/logging/testdox/testdox-print-previous-exception.phpt new file mode 100644 index 00000000000..0d7125833f1 --- /dev/null +++ b/tests/end-to-end/logging/testdox/testdox-print-previous-exception.phpt @@ -0,0 +1,37 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5863 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: PHP %s + +E 1 / 1 (100%) + +Time: %s, Memory: %s + +Throws With Previous Exception (PHPUnit\TestFixture\ThrowsWithPreviousException) + ✘ Foo + │ + │ Exception: Outer + │ + │ %sThrowsWithPreviousExceptionTest.php:%d + │ + │ Caused by: + │ Exception: Inner + │ + │ %sThrowsWithPreviousExceptionTest.php:%d + │ + +ERRORS! +Tests: 1, Assertions: 0, Errors: 1. diff --git a/tests/end-to-end/logging/testdox/testdox-print-risky.phpt b/tests/end-to-end/logging/testdox/testdox-print-risky.phpt new file mode 100644 index 00000000000..d9e76790a43 --- /dev/null +++ b/tests/end-to-end/logging/testdox/testdox-print-risky.phpt @@ -0,0 +1,18 @@ +--TEST-- +Testdox: print risky message +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +Risky (PHPUnit\TestFixture\TestRunnerStopping\Risky) + [x] One + [x] Two diff --git a/tests/end-to-end/logging/testdox/testdox-print-skipped.phpt b/tests/end-to-end/logging/testdox/testdox-print-skipped.phpt new file mode 100644 index 00000000000..84d7bcf22b5 --- /dev/null +++ b/tests/end-to-end/logging/testdox/testdox-print-skipped.phpt @@ -0,0 +1,19 @@ +--TEST-- +Testdox: print skipped message +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +Skipped (PHPUnit\TestFixture\TestRunnerStopping\Skipped) + [ ] One + [x] Two diff --git a/tests/end-to-end/logging/testdox/testdox-print-warning.phpt b/tests/end-to-end/logging/testdox/testdox-print-warning.phpt new file mode 100644 index 00000000000..c2b74136aa9 --- /dev/null +++ b/tests/end-to-end/logging/testdox/testdox-print-warning.phpt @@ -0,0 +1,19 @@ +--TEST-- +Testdox: print warning message +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +Warning (PHPUnit\TestFixture\TestRunnerStopping\Warning) + [x] One + [x] Two diff --git a/tests/end-to-end/logging/testdox/testdox-text-invalid-path.phpt b/tests/end-to-end/logging/testdox/testdox-text-invalid-path.phpt new file mode 100644 index 00000000000..aaaeff0728a --- /dev/null +++ b/tests/end-to-end/logging/testdox/testdox-text-invalid-path.phpt @@ -0,0 +1,28 @@ +--TEST-- +Test runner emits warning when --testdox-text is used with an invalid target path +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Cannot log test results in TestDox plain text format to "": Directory "" does not exist and could not be created + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/logging/testdox/testdox-text-invalid-socket.phpt b/tests/end-to-end/logging/testdox/testdox-text-invalid-socket.phpt new file mode 100644 index 00000000000..f8c558aee30 --- /dev/null +++ b/tests/end-to-end/logging/testdox/testdox-text-invalid-socket.phpt @@ -0,0 +1,28 @@ +--TEST-- +Test runner emits warning when --testdox-text is used with an invalid socket +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Cannot log test results in TestDox plain text format to "socket://hostname:port:wrong": "socket://hostname:port:wrong" does not match "socket://hostname:port" format + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/logging/testdox/testdox-text.phpt b/tests/end-to-end/logging/testdox/testdox-text.phpt new file mode 100644 index 00000000000..cadd880e23a --- /dev/null +++ b/tests/end-to-end/logging/testdox/testdox-text.phpt @@ -0,0 +1,24 @@ +--TEST-- +phpunit --testdox-text php://stdout ../../_files/BankAccountTest.php +--FILE-- +run($_SERVER['argv']); + +print file_get_contents($output); + +unlink($output); +--EXPECTF-- +Bank Account (PHPUnit\TestFixture\BankAccount) + [x] Balance is initially zero + [x] Balance cannot become negative diff --git a/tests/end-to-end/metadata/before-test-method-configured-with-attribute.phpt b/tests/end-to-end/metadata/before-test-method-configured-with-attribute.phpt new file mode 100644 index 00000000000..c16340e0e40 --- /dev/null +++ b/tests/end-to-end/metadata/before-test-method-configured-with-attribute.phpt @@ -0,0 +1,32 @@ +--TEST-- +The right events are emitted in the right order for a successful test that has a before-test method that is configured with attribute +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\DeprecatedAnnotationsTestFixture\BeforeTestMethodWithAttributeTest, 1 test) +Test Preparation Started (PHPUnit\DeprecatedAnnotationsTestFixture\BeforeTestMethodWithAttributeTest::testOne) +Before Test Method Called (PHPUnit\DeprecatedAnnotationsTestFixture\BeforeTestMethodWithAttributeTest::beforeMethod) +Before Test Method Finished: +- PHPUnit\DeprecatedAnnotationsTestFixture\BeforeTestMethodWithAttributeTest::beforeMethod +Test Prepared (PHPUnit\DeprecatedAnnotationsTestFixture\BeforeTestMethodWithAttributeTest::testOne) +Test Passed (PHPUnit\DeprecatedAnnotationsTestFixture\BeforeTestMethodWithAttributeTest::testOne) +Test Finished (PHPUnit\DeprecatedAnnotationsTestFixture\BeforeTestMethodWithAttributeTest::testOne) +Test Suite Finished (PHPUnit\DeprecatedAnnotationsTestFixture\BeforeTestMethodWithAttributeTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/metadata/before-test-method-configured-with-prioritized-attribute.phpt b/tests/end-to-end/metadata/before-test-method-configured-with-prioritized-attribute.phpt new file mode 100644 index 00000000000..81d37999619 --- /dev/null +++ b/tests/end-to-end/metadata/before-test-method-configured-with-prioritized-attribute.phpt @@ -0,0 +1,36 @@ +--TEST-- +The right events are emitted in the right order for a successful test that has a before-test method that is configured with attribute +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\DeprecatedAnnotationsTestFixture\BeforeTestMethodWithPrioritizedAttributeTest, 1 test) +Test Preparation Started (PHPUnit\DeprecatedAnnotationsTestFixture\BeforeTestMethodWithPrioritizedAttributeTest::testOne) +Before Test Method Called (PHPUnit\DeprecatedAnnotationsTestFixture\BeforeTestMethodWithPrioritizedAttributeTest::beforeMethodWithHighPriority) +Before Test Method Called (PHPUnit\DeprecatedAnnotationsTestFixture\BeforeTestMethodWithPrioritizedAttributeTest::setUp) +Before Test Method Called (PHPUnit\DeprecatedAnnotationsTestFixture\BeforeTestMethodWithPrioritizedAttributeTest::beforeMethodWithLowPriority) +Before Test Method Finished: +- PHPUnit\DeprecatedAnnotationsTestFixture\BeforeTestMethodWithPrioritizedAttributeTest::beforeMethodWithHighPriority +- PHPUnit\DeprecatedAnnotationsTestFixture\BeforeTestMethodWithPrioritizedAttributeTest::setUp +- PHPUnit\DeprecatedAnnotationsTestFixture\BeforeTestMethodWithPrioritizedAttributeTest::beforeMethodWithLowPriority +Test Prepared (PHPUnit\DeprecatedAnnotationsTestFixture\BeforeTestMethodWithPrioritizedAttributeTest::testOne) +Test Passed (PHPUnit\DeprecatedAnnotationsTestFixture\BeforeTestMethodWithPrioritizedAttributeTest::testOne) +Test Finished (PHPUnit\DeprecatedAnnotationsTestFixture\BeforeTestMethodWithPrioritizedAttributeTest::testOne) +Test Suite Finished (PHPUnit\DeprecatedAnnotationsTestFixture\BeforeTestMethodWithPrioritizedAttributeTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/metadata/hook-methods-order.phpt b/tests/end-to-end/metadata/hook-methods-order.phpt new file mode 100644 index 00000000000..dd5ba156680 --- /dev/null +++ b/tests/end-to-end/metadata/hook-methods-order.phpt @@ -0,0 +1,58 @@ +--TEST-- +The right events are emitted in the right order for a successful test that has a before-test method that is configured with annotation +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Bootstrap Finished (%sHookMethodsOrderTestCase.php) +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\HookMethodsOrderTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\HookMethodsOrderTest::testOne) +Before Test Method Called (PHPUnit\TestFixture\HookMethodsOrderTest::beforeWithPriorityInParent) +Before Test Method Called (PHPUnit\TestFixture\HookMethodsOrderTest::beforeWithPriority) +Before Test Method Called (PHPUnit\TestFixture\HookMethodsOrderTest::beforeInParent) +Before Test Method Called (PHPUnit\TestFixture\HookMethodsOrderTest::beforeFirst) +Before Test Method Called (PHPUnit\TestFixture\HookMethodsOrderTest::beforeSecond) +Before Test Method Called (PHPUnit\TestFixture\HookMethodsOrderTest::setUp) +Before Test Method Finished: +- PHPUnit\TestFixture\HookMethodsOrderTest::beforeWithPriorityInParent +- PHPUnit\TestFixture\HookMethodsOrderTest::beforeWithPriority +- PHPUnit\TestFixture\HookMethodsOrderTest::beforeInParent +- PHPUnit\TestFixture\HookMethodsOrderTest::beforeFirst +- PHPUnit\TestFixture\HookMethodsOrderTest::beforeSecond +- PHPUnit\TestFixture\HookMethodsOrderTest::setUp +Test Prepared (PHPUnit\TestFixture\HookMethodsOrderTest::testOne) +After Test Method Called (PHPUnit\TestFixture\HookMethodsOrderTest::afterWithPriority) +After Test Method Called (PHPUnit\TestFixture\HookMethodsOrderTest::afterWithPriorityInParent) +After Test Method Called (PHPUnit\TestFixture\HookMethodsOrderTest::tearDown) +After Test Method Called (PHPUnit\TestFixture\HookMethodsOrderTest::afterFirst) +After Test Method Called (PHPUnit\TestFixture\HookMethodsOrderTest::afterSecond) +After Test Method Called (PHPUnit\TestFixture\HookMethodsOrderTest::afterInParent) +After Test Method Finished: +- PHPUnit\TestFixture\HookMethodsOrderTest::afterWithPriority +- PHPUnit\TestFixture\HookMethodsOrderTest::afterWithPriorityInParent +- PHPUnit\TestFixture\HookMethodsOrderTest::tearDown +- PHPUnit\TestFixture\HookMethodsOrderTest::afterFirst +- PHPUnit\TestFixture\HookMethodsOrderTest::afterSecond +- PHPUnit\TestFixture\HookMethodsOrderTest::afterInParent +Test Passed (PHPUnit\TestFixture\HookMethodsOrderTest::testOne) +Test Finished (PHPUnit\TestFixture\HookMethodsOrderTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\HookMethodsOrderTest, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/metadata/mix-attribute-dataproviders.phpt b/tests/end-to-end/metadata/mix-attribute-dataproviders.phpt new file mode 100644 index 00000000000..b15a5f09901 --- /dev/null +++ b/tests/end-to-end/metadata/mix-attribute-dataproviders.phpt @@ -0,0 +1,21 @@ +--TEST-- +phpunit ../_files/TestDataProviderExternalAndDataProviderTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +.. 2 / 2 (100%) + +Time: %s, Memory: %s + +OK (2 tests, 2 assertions) diff --git a/tests/end-to-end/metadata/mix-test-with-dataproviders.phpt b/tests/end-to-end/metadata/mix-test-with-dataproviders.phpt new file mode 100644 index 00000000000..f451bf43596 --- /dev/null +++ b/tests/end-to-end/metadata/mix-test-with-dataproviders.phpt @@ -0,0 +1,21 @@ +--TEST-- +phpunit ../_files/TestWithAndTestWithJsonDataProviderTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +.. 2 / 2 (100%) + +Time: %s, Memory: %s + +OK (2 tests, 2 assertions) diff --git a/tests/end-to-end/metadata/requires-environment-variable.phpt b/tests/end-to-end/metadata/requires-environment-variable.phpt new file mode 100644 index 00000000000..50d88ab98df --- /dev/null +++ b/tests/end-to-end/metadata/requires-environment-variable.phpt @@ -0,0 +1,36 @@ +--TEST-- +Tests are correctly ran based on environment variables requirements +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +SSS.... 7 / 7 (100%) + +Time: %s, Memory: %s + +There were 3 skipped tests: + +1) PHPUnit\TestFixture\requires_environment_variable\SomeTest::testShouldNotRunFOOHasWrongValue +Environment variable "FOO" is required to be "bar". + +2) PHPUnit\TestFixture\requires_environment_variable\SomeTest::testShouldNotRunBARIsEmpty +Environment variable "BAR" is required. + +3) PHPUnit\TestFixture\requires_environment_variable\SomeTest::testShouldNotRunBAZDoesNotExist +Environment variable "BAZ" is required. + +OK, but some tests were skipped! +Tests: 7, Assertions: 4, Skipped: 3. + diff --git a/tests/end-to-end/metadata/warning-covers-and-coversnothing-are-used.phpt b/tests/end-to-end/metadata/warning-covers-and-coversnothing-are-used.phpt new file mode 100644 index 00000000000..b15db084260 --- /dev/null +++ b/tests/end-to-end/metadata/warning-covers-and-coversnothing-are-used.phpt @@ -0,0 +1,31 @@ +--TEST-- +phpunit ../_files/code-coverage-targeting/CoversClassCoversNothingTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +W 1 / 1 (100%) + +Time: %s, Memory: %s + +1 test triggered 1 PHPUnit warning: + +1) PHPUnit\TestFixture\CodeCoverageTargeting\Warnings\CoversClassCoversNothingTest::testOne +#[Covers*] and #[Uses*] attributes do not have an effect when the #[CoversNothing] attribute is used + +%sCoversClassCoversNothingTest.php:%d + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/metadata/warning-covers-and-uses.phpt b/tests/end-to-end/metadata/warning-covers-and-uses.phpt new file mode 100644 index 00000000000..3b79de18a04 --- /dev/null +++ b/tests/end-to-end/metadata/warning-covers-and-uses.phpt @@ -0,0 +1,31 @@ +--TEST-- +phpunit ../_files/code-coverage-targeting/CoversAndUsesTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +W 1 / 1 (100%) + +Time: %s, Memory: %s + +1 test triggered 1 PHPUnit warning: + +1) PHPUnit\TestFixture\CodeCoverageTargeting\Warnings\CoversAndUsesTest::testOne +Class PHPUnit\TestFixture\CodeCoverageTargeting\Warnings\SomeClass is targeted by both "Covers" and "Uses" attributes + +%sCoversAndUsesTest.php:%d + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/metadata/warning-duplicate-covers-targets.phpt b/tests/end-to-end/metadata/warning-duplicate-covers-targets.phpt new file mode 100644 index 00000000000..c337b5e514e --- /dev/null +++ b/tests/end-to-end/metadata/warning-duplicate-covers-targets.phpt @@ -0,0 +1,31 @@ +--TEST-- +phpunit ../_files/code-coverage-targeting/DuplicateCoversTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +W 1 / 1 (100%) + +Time: %s, Memory: %s + +1 test triggered 1 PHPUnit warning: + +1) PHPUnit\TestFixture\CodeCoverageTargeting\Warnings\DuplicateCoversTest::testOne +Class PHPUnit\TestFixture\CodeCoverageTargeting\Warnings\SomeClass is targeted multiple times by the same "Covers" attribute + +%sDuplicateCoversTest.php:%d + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/metadata/warning-duplicate-uses-targets.phpt b/tests/end-to-end/metadata/warning-duplicate-uses-targets.phpt new file mode 100644 index 00000000000..7806ed001b6 --- /dev/null +++ b/tests/end-to-end/metadata/warning-duplicate-uses-targets.phpt @@ -0,0 +1,31 @@ +--TEST-- +phpunit ../_files/code-coverage-targeting/DuplicateUsesTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +W 1 / 1 (100%) + +Time: %s, Memory: %s + +1 test triggered 1 PHPUnit warning: + +1) PHPUnit\TestFixture\CodeCoverageTargeting\Warnings\DuplicateUsesTest::testOne +Class PHPUnit\TestFixture\CodeCoverageTargeting\Warnings\SomeClass is targeted multiple times by the same "Uses" attribute + +%sDuplicateUsesTest.php:%d + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/metadata/warning-large-and-medium-are-used.phpt b/tests/end-to-end/metadata/warning-large-and-medium-are-used.phpt new file mode 100644 index 00000000000..5bb9c3ad204 --- /dev/null +++ b/tests/end-to-end/metadata/warning-large-and-medium-are-used.phpt @@ -0,0 +1,26 @@ +--TEST-- +phpunit ../_files/size-combinations/LargeMediumTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) #[Medium] cannot be combined with #[Small] or #[Large] for class PHPUnit\TestFixture\SizeCombinations\LargeMediumTest + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/metadata/warning-large-and-small-are-used.phpt b/tests/end-to-end/metadata/warning-large-and-small-are-used.phpt new file mode 100644 index 00000000000..4cbcad68446 --- /dev/null +++ b/tests/end-to-end/metadata/warning-large-and-small-are-used.phpt @@ -0,0 +1,26 @@ +--TEST-- +phpunit ../_files/size-combinations/LargeSmallTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) #[Small] cannot be combined with #[Medium] or #[Large] for class PHPUnit\TestFixture\SizeCombinations\LargeSmallTest + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/metadata/warning-medium-and-large-are-used.phpt b/tests/end-to-end/metadata/warning-medium-and-large-are-used.phpt new file mode 100644 index 00000000000..d6918e5dd9a --- /dev/null +++ b/tests/end-to-end/metadata/warning-medium-and-large-are-used.phpt @@ -0,0 +1,26 @@ +--TEST-- +phpunit ../_files/size-combinations/MediumLargeTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) #[Large] cannot be combined with #[Small] or #[Medium] for class PHPUnit\TestFixture\SizeCombinations\MediumLargeTest + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/metadata/warning-medium-and-small-are-used.phpt b/tests/end-to-end/metadata/warning-medium-and-small-are-used.phpt new file mode 100644 index 00000000000..a01d41928ca --- /dev/null +++ b/tests/end-to-end/metadata/warning-medium-and-small-are-used.phpt @@ -0,0 +1,26 @@ +--TEST-- +phpunit ../_files/size-combinations/MediumSmallTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) #[Small] cannot be combined with #[Medium] or #[Large] for class PHPUnit\TestFixture\SizeCombinations\MediumSmallTest + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/metadata/warning-mix-dataprovider-test-with-json-types.phpt b/tests/end-to-end/metadata/warning-mix-dataprovider-test-with-json-types.phpt new file mode 100644 index 00000000000..65ed846b405 --- /dev/null +++ b/tests/end-to-end/metadata/warning-mix-dataprovider-test-with-json-types.phpt @@ -0,0 +1,29 @@ +--TEST-- +phpunit ../_files/TestWithJsonAttributeAndDataProviderTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +W 1 / 1 (100%) + +Time: %s, Memory: %s + +1 test triggered 1 PHPUnit warning: + +1) PHPUnit\TestFixture\TestWithJsonAttributeAndDataProviderTest::testWithDifferentProviderTypes +Mixing #[DataProvider*] and #[TestWith*] attributes is not supported, only the data provided by #[DataProvider*] will be used + +%sTestWithJsonAttributeAndDataProviderTest.php:%d + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/metadata/warning-mix-dataprovider-test-with-types.phpt b/tests/end-to-end/metadata/warning-mix-dataprovider-test-with-types.phpt new file mode 100644 index 00000000000..cd4f1985284 --- /dev/null +++ b/tests/end-to-end/metadata/warning-mix-dataprovider-test-with-types.phpt @@ -0,0 +1,29 @@ +--TEST-- +phpunit ../_files/TestWithAttributeAndDataProviderTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +W 1 / 1 (100%) + +Time: %s, Memory: %s + +1 test triggered 1 PHPUnit warning: + +1) PHPUnit\TestFixture\TestWithAttributeAndDataProviderTest::testWithDifferentProviderTypes +Mixing #[DataProvider*] and #[TestWith*] attributes is not supported, only the data provided by #[DataProvider*] will be used + +%sTestWithAttributeAndDataProviderTest.php:%d + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/metadata/warning-mix-external-dataprovider-with-types.phpt b/tests/end-to-end/metadata/warning-mix-external-dataprovider-with-types.phpt new file mode 100644 index 00000000000..5c30e91da7d --- /dev/null +++ b/tests/end-to-end/metadata/warning-mix-external-dataprovider-with-types.phpt @@ -0,0 +1,29 @@ +--TEST-- +phpunit ../_files/TestWithAttributeAndExternalDataProviderTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +W 1 / 1 (100%) + +Time: %s, Memory: %s + +1 test triggered 1 PHPUnit warning: + +1) PHPUnit\TestFixture\TestWithAttributeAndExternalDataProviderTest::testWithDifferentProviderTypes +Mixing #[DataProvider*] and #[TestWith*] attributes is not supported, only the data provided by #[DataProvider*] will be used + +%sTestWithAttributeAndExternalDataProviderTest.php:%d + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/metadata/warning-size-is-used-as-group-name.phpt b/tests/end-to-end/metadata/warning-size-is-used-as-group-name.phpt new file mode 100644 index 00000000000..9e65bbd802c --- /dev/null +++ b/tests/end-to-end/metadata/warning-size-is-used-as-group-name.phpt @@ -0,0 +1,36 @@ +--TEST-- +phpunit ../_files/size-groups/SizeGroupsTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +.. 2 / 2 (100%) + +Time: %s, Memory: %s + +There were 6 PHPUnit test runner warnings: + +1) Group name "small" is not allowed for class PHPUnit\TestFixture\SizeGroups\ClassLevelTest + +2) Group name "medium" is not allowed for class PHPUnit\TestFixture\SizeGroups\ClassLevelTest + +3) Group name "large" is not allowed for class PHPUnit\TestFixture\SizeGroups\ClassLevelTest + +4) Group name "small" is not allowed for method PHPUnit\TestFixture\SizeGroups\MethodLevelTest::testOne + +5) Group name "medium" is not allowed for method PHPUnit\TestFixture\SizeGroups\MethodLevelTest::testOne + +6) Group name "large" is not allowed for method PHPUnit\TestFixture\SizeGroups\MethodLevelTest::testOne + +OK, but there were issues! +Tests: 2, Assertions: 2, PHPUnit Warnings: 6. diff --git a/tests/end-to-end/metadata/warning-small-and-large-are-used.phpt b/tests/end-to-end/metadata/warning-small-and-large-are-used.phpt new file mode 100644 index 00000000000..c4278c936a5 --- /dev/null +++ b/tests/end-to-end/metadata/warning-small-and-large-are-used.phpt @@ -0,0 +1,26 @@ +--TEST-- +phpunit ../_files/size-combinations/SmallLargeTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) #[Large] cannot be combined with #[Small] or #[Medium] for class PHPUnit\TestFixture\SizeCombinations\SmallLargeTest + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/metadata/warning-small-and-medium-are-used.phpt b/tests/end-to-end/metadata/warning-small-and-medium-are-used.phpt new file mode 100644 index 00000000000..5031346acc2 --- /dev/null +++ b/tests/end-to-end/metadata/warning-small-and-medium-are-used.phpt @@ -0,0 +1,26 @@ +--TEST-- +phpunit ../_files/size-combinations/SmallMediumTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) #[Medium] cannot be combined with #[Small] or #[Large] for class PHPUnit\TestFixture\SizeCombinations\SmallMediumTest + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/metadata/warning-test-attribute-on-hook-methods.phpt b/tests/end-to-end/metadata/warning-test-attribute-on-hook-methods.phpt new file mode 100644 index 00000000000..310e5e7d430 --- /dev/null +++ b/tests/end-to-end/metadata/warning-test-attribute-on-hook-methods.phpt @@ -0,0 +1,48 @@ +--TEST-- +phpunit ../_files/size-combinations/SmallMediumTest.php +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There were 12 PHPUnit test runner warnings: + +1) Method PHPUnit\TestFixture\AttributesOnTemplateMethods\TestAttributeOnHookMethodsTest::before_class() cannot be used both as a hook method and as a test method + +2) Method PHPUnit\TestFixture\AttributesOnTemplateMethods\TestAttributeOnHookMethodsTest::after_class() cannot be used both as a hook method and as a test method + +3) Method PHPUnit\TestFixture\AttributesOnTemplateMethods\TestAttributeOnHookMethodsTest::setUpBeforeClass() cannot be used both as a hook method and as a test method + +4) Method PHPUnit\TestFixture\AttributesOnTemplateMethods\TestAttributeOnHookMethodsTest::tearDownAfterClass() cannot be used both as a hook method and as a test method + +5) Method PHPUnit\TestFixture\AttributesOnTemplateMethods\TestAttributeOnHookMethodsTest::setUp() cannot be used both as a hook method and as a test method + +6) Method PHPUnit\TestFixture\AttributesOnTemplateMethods\TestAttributeOnHookMethodsTest::assertPreConditions() cannot be used both as a hook method and as a test method + +7) Method PHPUnit\TestFixture\AttributesOnTemplateMethods\TestAttributeOnHookMethodsTest::assertPostConditions() cannot be used both as a hook method and as a test method + +8) Method PHPUnit\TestFixture\AttributesOnTemplateMethods\TestAttributeOnHookMethodsTest::tearDown() cannot be used both as a hook method and as a test method + +9) Method PHPUnit\TestFixture\AttributesOnTemplateMethods\TestAttributeOnHookMethodsTest::before_method() cannot be used both as a hook method and as a test method + +10) Method PHPUnit\TestFixture\AttributesOnTemplateMethods\TestAttributeOnHookMethodsTest::pre_condition() cannot be used both as a hook method and as a test method + +11) Method PHPUnit\TestFixture\AttributesOnTemplateMethods\TestAttributeOnHookMethodsTest::post_condition() cannot be used both as a hook method and as a test method + +12) Method PHPUnit\TestFixture\AttributesOnTemplateMethods\TestAttributeOnHookMethodsTest::after_method() cannot be used both as a hook method and as a test method + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 12. diff --git a/tests/end-to-end/metadata/with-environment-variable.phpt b/tests/end-to-end/metadata/with-environment-variable.phpt new file mode 100644 index 00000000000..355d11ff44a --- /dev/null +++ b/tests/end-to-end/metadata/with-environment-variable.phpt @@ -0,0 +1,24 @@ +--TEST-- +Tests are correctly ran based on environment variables requirements +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +............... 15 / 15 (100%) + +Time: %s, Memory: %s + +OK (15 tests, 42 assertions) + diff --git a/tests/end-to-end/migration/_files/migration-from-100/phpunit-10.0.xml b/tests/end-to-end/migration/_files/migration-from-100/phpunit-10.0.xml new file mode 100644 index 00000000000..9967d0551ea --- /dev/null +++ b/tests/end-to-end/migration/_files/migration-from-100/phpunit-10.0.xml @@ -0,0 +1,17 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/migration/_files/migration-from-110/phpunit-11.0.xml b/tests/end-to-end/migration/_files/migration-from-110/phpunit-11.0.xml new file mode 100644 index 00000000000..684b3b9159d --- /dev/null +++ b/tests/end-to-end/migration/_files/migration-from-110/phpunit-11.0.xml @@ -0,0 +1,12 @@ + + + + + tests + + + + + diff --git a/tests/end-to-end/migration/_files/migration-from-85/phpunit-8.5.xml b/tests/end-to-end/migration/_files/migration-from-85/phpunit-8.5.xml new file mode 100644 index 00000000000..f2367d7eabb --- /dev/null +++ b/tests/end-to-end/migration/_files/migration-from-85/phpunit-8.5.xml @@ -0,0 +1,22 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/migration/_files/migration-from-92/phpunit-9.2.xml b/tests/end-to-end/migration/_files/migration-from-92/phpunit-9.2.xml new file mode 100644 index 00000000000..40fcead786c --- /dev/null +++ b/tests/end-to-end/migration/_files/migration-from-92/phpunit-9.2.xml @@ -0,0 +1,23 @@ + + + + + tests + + + + + + src + file.php + + + diff --git a/tests/end-to-end/migration/_files/migration-from-95/phpunit-9.5.xml b/tests/end-to-end/migration/_files/migration-from-95/phpunit-9.5.xml new file mode 100644 index 00000000000..f34a36108b8 --- /dev/null +++ b/tests/end-to-end/migration/_files/migration-from-95/phpunit-9.5.xml @@ -0,0 +1,41 @@ + + + + + tests + + + + + + src + + + + + + + diff --git a/tests/end-to-end/migration/_files/possibility-to-migrate-from-100-is-detected/phpunit.xml b/tests/end-to-end/migration/_files/possibility-to-migrate-from-100-is-detected/phpunit.xml new file mode 100644 index 00000000000..9c00f88e05c --- /dev/null +++ b/tests/end-to-end/migration/_files/possibility-to-migrate-from-100-is-detected/phpunit.xml @@ -0,0 +1,16 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/migration/_files/possibility-to-migrate-from-100-is-detected/src/Greeter.php b/tests/end-to-end/migration/_files/possibility-to-migrate-from-100-is-detected/src/Greeter.php new file mode 100644 index 00000000000..8032685fa53 --- /dev/null +++ b/tests/end-to-end/migration/_files/possibility-to-migrate-from-100-is-detected/src/Greeter.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Migration; + +final class Greeter +{ + public function greet(): string + { + return 'Hello world!'; + } +} diff --git a/tests/end-to-end/migration/_files/possibility-to-migrate-from-100-is-detected/tests/GreeterTest.php b/tests/end-to-end/migration/_files/possibility-to-migrate-from-100-is-detected/tests/GreeterTest.php new file mode 100644 index 00000000000..65f3dfd1d9e --- /dev/null +++ b/tests/end-to-end/migration/_files/possibility-to-migrate-from-100-is-detected/tests/GreeterTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Migration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Greeter::class)] +final class GreeterTest extends TestCase +{ + public function testGreets(): void + { + $this->assertSame('Hello world!', (new Greeter)->greet()); + } +} diff --git a/tests/end-to-end/migration/_files/possibility-to-migrate-from-85-is-detected/phpunit.xml b/tests/end-to-end/migration/_files/possibility-to-migrate-from-85-is-detected/phpunit.xml new file mode 100644 index 00000000000..f2367d7eabb --- /dev/null +++ b/tests/end-to-end/migration/_files/possibility-to-migrate-from-85-is-detected/phpunit.xml @@ -0,0 +1,22 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/migration/_files/possibility-to-migrate-from-85-is-detected/src/Greeter.php b/tests/end-to-end/migration/_files/possibility-to-migrate-from-85-is-detected/src/Greeter.php new file mode 100644 index 00000000000..8032685fa53 --- /dev/null +++ b/tests/end-to-end/migration/_files/possibility-to-migrate-from-85-is-detected/src/Greeter.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Migration; + +final class Greeter +{ + public function greet(): string + { + return 'Hello world!'; + } +} diff --git a/tests/end-to-end/migration/_files/possibility-to-migrate-from-85-is-detected/tests/GreeterTest.php b/tests/end-to-end/migration/_files/possibility-to-migrate-from-85-is-detected/tests/GreeterTest.php new file mode 100644 index 00000000000..65f3dfd1d9e --- /dev/null +++ b/tests/end-to-end/migration/_files/possibility-to-migrate-from-85-is-detected/tests/GreeterTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Migration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Greeter::class)] +final class GreeterTest extends TestCase +{ + public function testGreets(): void + { + $this->assertSame('Hello world!', (new Greeter)->greet()); + } +} diff --git a/tests/end-to-end/migration/_files/possibility-to-migrate-from-92-is-detected/phpunit.xml b/tests/end-to-end/migration/_files/possibility-to-migrate-from-92-is-detected/phpunit.xml new file mode 100644 index 00000000000..b855a43ff25 --- /dev/null +++ b/tests/end-to-end/migration/_files/possibility-to-migrate-from-92-is-detected/phpunit.xml @@ -0,0 +1,22 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/migration/_files/possibility-to-migrate-from-92-is-detected/src/Greeter.php b/tests/end-to-end/migration/_files/possibility-to-migrate-from-92-is-detected/src/Greeter.php new file mode 100644 index 00000000000..8032685fa53 --- /dev/null +++ b/tests/end-to-end/migration/_files/possibility-to-migrate-from-92-is-detected/src/Greeter.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Migration; + +final class Greeter +{ + public function greet(): string + { + return 'Hello world!'; + } +} diff --git a/tests/end-to-end/migration/_files/possibility-to-migrate-from-92-is-detected/tests/GreeterTest.php b/tests/end-to-end/migration/_files/possibility-to-migrate-from-92-is-detected/tests/GreeterTest.php new file mode 100644 index 00000000000..65f3dfd1d9e --- /dev/null +++ b/tests/end-to-end/migration/_files/possibility-to-migrate-from-92-is-detected/tests/GreeterTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Migration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Greeter::class)] +final class GreeterTest extends TestCase +{ + public function testGreets(): void + { + $this->assertSame('Hello world!', (new Greeter)->greet()); + } +} diff --git a/tests/end-to-end/migration/_files/possibility-to-migrate-from-95-is-detected/phpunit.xml b/tests/end-to-end/migration/_files/possibility-to-migrate-from-95-is-detected/phpunit.xml new file mode 100644 index 00000000000..f34a36108b8 --- /dev/null +++ b/tests/end-to-end/migration/_files/possibility-to-migrate-from-95-is-detected/phpunit.xml @@ -0,0 +1,41 @@ + + + + + tests + + + + + + src + + + + + + + diff --git a/tests/end-to-end/migration/_files/possibility-to-migrate-from-95-is-detected/src/Greeter.php b/tests/end-to-end/migration/_files/possibility-to-migrate-from-95-is-detected/src/Greeter.php new file mode 100644 index 00000000000..8032685fa53 --- /dev/null +++ b/tests/end-to-end/migration/_files/possibility-to-migrate-from-95-is-detected/src/Greeter.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Migration; + +final class Greeter +{ + public function greet(): string + { + return 'Hello world!'; + } +} diff --git a/tests/end-to-end/migration/_files/possibility-to-migrate-from-95-is-detected/tests/GreeterTest.php b/tests/end-to-end/migration/_files/possibility-to-migrate-from-95-is-detected/tests/GreeterTest.php new file mode 100644 index 00000000000..65f3dfd1d9e --- /dev/null +++ b/tests/end-to-end/migration/_files/possibility-to-migrate-from-95-is-detected/tests/GreeterTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Migration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Greeter::class)] +final class GreeterTest extends TestCase +{ + public function testGreets(): void + { + $this->assertSame('Hello world!', (new Greeter)->greet()); + } +} diff --git a/tests/end-to-end/migration/_files/unsupported-schema/phpunit.xml b/tests/end-to-end/migration/_files/unsupported-schema/phpunit.xml new file mode 100644 index 00000000000..b24baa02e01 --- /dev/null +++ b/tests/end-to-end/migration/_files/unsupported-schema/phpunit.xml @@ -0,0 +1,6 @@ + + + bar + diff --git a/tests/end-to-end/migration/migration-from-100.phpt b/tests/end-to-end/migration/migration-from-100.phpt new file mode 100644 index 00000000000..edd9aed6c82 --- /dev/null +++ b/tests/end-to-end/migration/migration-from-100.phpt @@ -0,0 +1,22 @@ +--TEST-- +Configuration migration from PHPUnit 10.0 format works +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Created backup: %sphpunit.xml.bak +Migrated configuration: %sphpunit.xml +--CLEAN-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Created backup: %sphpunit.xml.bak +Migrated configuration: %sphpunit.xml +--CLEAN-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Created backup: %scustom.xml.bak +Migrated configuration: %scustom.xml +--CLEAN-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Created backup: %sphpunit.xml.bak +Migrated configuration: %sphpunit.xml +--CLEAN-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Created backup: %sphpunit.xml.bak +Migrated configuration: %sphpunit.xml +--CLEAN-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Created backup: %sphpunit.xml.bak +Migrated configuration: %sphpunit.xml +--CLEAN-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: PHP %s +Configuration: %sphpunit.xml + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner deprecation: + +1) Your XML configuration validates against a deprecated schema. Migrate your XML configuration using "--migrate-configuration"! + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Deprecations: 1. diff --git a/tests/end-to-end/migration/possibility-to-migrate-from-85-is-detected.phpt b/tests/end-to-end/migration/possibility-to-migrate-from-85-is-detected.phpt new file mode 100644 index 00000000000..e089167a5db --- /dev/null +++ b/tests/end-to-end/migration/possibility-to-migrate-from-85-is-detected.phpt @@ -0,0 +1,28 @@ +--TEST-- +Possibility to migrate XML configuration file from PHPUnit 8.5 format is detected +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: PHP %s +Configuration: %sphpunit.xml + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner deprecation: + +1) Your XML configuration validates against a deprecated schema. Migrate your XML configuration using "--migrate-configuration"! + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Deprecations: 1. diff --git a/tests/end-to-end/migration/possibility-to-migrate-from-92-is-detected.phpt b/tests/end-to-end/migration/possibility-to-migrate-from-92-is-detected.phpt new file mode 100644 index 00000000000..b1ffbfca961 --- /dev/null +++ b/tests/end-to-end/migration/possibility-to-migrate-from-92-is-detected.phpt @@ -0,0 +1,28 @@ +--TEST-- +Possibility to migrate XML configuration file from PHPUnit 9.2 format is detected +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: PHP %s +Configuration: %sphpunit.xml + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner deprecation: + +1) Your XML configuration validates against a deprecated schema. Migrate your XML configuration using "--migrate-configuration"! + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Deprecations: 1. diff --git a/tests/end-to-end/migration/possibility-to-migrate-from-95-is-detected.phpt b/tests/end-to-end/migration/possibility-to-migrate-from-95-is-detected.phpt new file mode 100644 index 00000000000..68956d8bcb3 --- /dev/null +++ b/tests/end-to-end/migration/possibility-to-migrate-from-95-is-detected.phpt @@ -0,0 +1,28 @@ +--TEST-- +Possibility to migrate XML configuration file from PHPUnit 9.5 format is detected +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: PHP %s +Configuration: %sphpunit.xml + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner deprecation: + +1) Your XML configuration validates against a deprecated schema. Migrate your XML configuration using "--migrate-configuration"! + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Deprecations: 1. diff --git a/tests/end-to-end/migration/unsupported-schema.phpt b/tests/end-to-end/migration/unsupported-schema.phpt new file mode 100644 index 00000000000..60186771dec --- /dev/null +++ b/tests/end-to-end/migration/unsupported-schema.phpt @@ -0,0 +1,22 @@ +--TEST-- +Configuration migration is not possible when the configuration file does not validate against any known schema +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Migration of %s failed: +The file does not validate against any known schema +--CLEAN-- +generate( - 'Foo', - [], - 'MockFoo', - true, - true + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function speak() { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = []; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 0) { + if (0 !== null && $__phpunit_count > 0) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -73,9 +86,11 @@ class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'speak', $__phpunit_arguments, '', $this, true + 'Foo', 'speak', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/3154_namespaced_constant_resolving.phpt b/tests/end-to-end/mock-objects/generator/3154_namespaced_constant_resolving.phpt index 642f31d895a..b965f871e20 100644 --- a/tests/end-to-end/mock-objects/generator/3154_namespaced_constant_resolving.phpt +++ b/tests/end-to-end/mock-objects/generator/3154_namespaced_constant_resolving.phpt @@ -23,34 +23,47 @@ class Issue3154 return $z."sum: ".($i+$j).$v; } } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; -$generator = new \PHPUnit\Framework\MockObject\Generator; +$generator = new \PHPUnit\Framework\MockObject\Generator\Generator; $mock = $generator->generate( - Issue3154::class, - [], - 'Issue3154Mock', - true, - true + type: Issue3154::class, + mockObject: true, + methods: [], + mockClassName: 'Issue3154Mock', ); -print $mock->getClassCode(); +print $mock->classCode(); --EXPECTF-- declare(strict_types=1); -class Issue3154Mock extends Is\Namespaced\Issue3154 implements PHPUnit\Framework\MockObject\MockObject +class Issue3154Mock extends Is\Namespaced\Issue3154 implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function a(int $i = %d, int $j = 17, string $v = '%s', string $z = '#'): string { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$i, $j, $v, $z]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 4) { + if (4 !== null && $__phpunit_count > 4) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 4; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -58,9 +71,11 @@ class Issue3154Mock extends Is\Namespaced\Issue3154 implements PHPUnit\Framework } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Is\Namespaced\Issue3154', 'a', $__phpunit_arguments, 'string', $this, true + 'Is\Namespaced\Issue3154', 'a', $__phpunit_arguments, 'string', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/3530.phpt b/tests/end-to-end/mock-objects/generator/3530.phpt deleted file mode 100644 index e17582f6182..00000000000 --- a/tests/end-to-end/mock-objects/generator/3530.phpt +++ /dev/null @@ -1,29 +0,0 @@ ---TEST-- -\PHPUnit\Framework\MockObject\Generator::generateClassFromWsdl('3530.wsdl', 'Test') ---SKIPIF-- -generateClassFromWsdl( - __DIR__ . '/../../../_files/3530.wsdl', - 'Test' -); ---EXPECTF-- -declare(strict_types=1); - -class Test extends \SoapClient -{ - public function __construct($wsdl, array $options) - { - parent::__construct('%s/3530.wsdl', $options); - } - - public function Contact_Information($Contact_Id) - { - } -} diff --git a/tests/end-to-end/mock-objects/generator/3967.phpt b/tests/end-to-end/mock-objects/generator/3967.phpt index d9d769e9161..e7fb931a981 100644 --- a/tests/end-to-end/mock-objects/generator/3967.phpt +++ b/tests/end-to-end/mock-objects/generator/3967.phpt @@ -11,33 +11,47 @@ interface Baz extends Bar { } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; -$generator = new \PHPUnit\Framework\MockObject\Generator; +$generator = new \PHPUnit\Framework\MockObject\Generator\Generator; $mock = $generator->generate( - 'Baz', - [], - 'MockBaz', - true, - true + type: 'Baz', + mockObject: true, + methods: [], + mockClassName: 'MockBaz', ); -print $mock->getClassCode(); +print $mock->classCode(); --EXPECT-- declare(strict_types=1); -class MockBaz extends Exception implements Baz, PHPUnit\Framework\MockObject\MockObject +class MockBaz extends Exception implements Baz, PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\ProxiedCloneMethod; public function foo(): string { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = []; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 0) { + if (0 !== null && $__phpunit_count > 0) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -45,9 +59,11 @@ class MockBaz extends Exception implements Baz, PHPUnit\Framework\MockObject\Moc } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Bar', 'foo', $__phpunit_arguments, 'string', $this, true + 'Bar', 'foo', $__phpunit_arguments, 'string', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/397.phpt b/tests/end-to-end/mock-objects/generator/397.phpt index 1cdb38f9a3a..321fcc7ceaa 100644 --- a/tests/end-to-end/mock-objects/generator/397.phpt +++ b/tests/end-to-end/mock-objects/generator/397.phpt @@ -9,34 +9,47 @@ class C } } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; -$generator = new \PHPUnit\Framework\MockObject\Generator; +$generator = new \PHPUnit\Framework\MockObject\Generator\Generator; $mock = $generator->generate( - C::class, - [], - 'MockC', - true, - true + type: C::class, + mockObject: true, + methods: [], + mockClassName: 'MockC', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockC extends C implements PHPUnit\Framework\MockObject\MockObject +class MockC extends C implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function m(?C $other): C { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$other]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -44,9 +57,11 @@ class MockC extends C implements PHPUnit\Framework\MockObject\MockObject } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'C', 'm', $__phpunit_arguments, 'C', $this, true + 'C', 'm', $__phpunit_arguments, 'C', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/4139.phpt b/tests/end-to-end/mock-objects/generator/4139.phpt index 1dc19dd9d3e..50309c50b35 100644 --- a/tests/end-to-end/mock-objects/generator/4139.phpt +++ b/tests/end-to-end/mock-objects/generator/4139.phpt @@ -6,28 +6,45 @@ interface InterfaceWithConstructor { public function __construct(); } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; -$generator = new \PHPUnit\Framework\MockObject\Generator; +$generator = new \PHPUnit\Framework\MockObject\Generator\Generator; -$mock = $generator->generate(InterfaceWithConstructor::class); +$mock = $generator->generate( + type: InterfaceWithConstructor::class, + mockObject: true, +); -print $mock->getClassCode(); +print $mock->classCode(); --EXPECTF-- declare(strict_types=1); -class %s implements PHPUnit\Framework\MockObject\MockObject, InterfaceWithConstructor +class %s implements PHPUnit\Framework\MockObject\MockObjectInternal, InterfaceWithConstructor { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function __construct() { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = []; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 0) { + if (0 !== null && $__phpunit_count > 0) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -35,9 +52,11 @@ class %s implements PHPUnit\Framework\MockObject\MockObject, InterfaceWithConstr } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'InterfaceWithConstructor', '__construct', $__phpunit_arguments, '', $this, true + 'InterfaceWithConstructor', '__construct', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/abstract_class.phpt b/tests/end-to-end/mock-objects/generator/abstract_class.phpt index a7d6f1ea702..de69f4bee64 100644 --- a/tests/end-to-end/mock-objects/generator/abstract_class.phpt +++ b/tests/end-to-end/mock-objects/generator/abstract_class.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', true, true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) --FILE-- generate( - 'Foo', - [], - 'MockFoo', - true, - true + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function one() { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = []; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 0) { + if (0 !== null && $__phpunit_count > 0) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -48,9 +61,11 @@ class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'one', $__phpunit_arguments, '', $this, true + 'Foo', 'one', $__phpunit_arguments, '', $this ) ); @@ -59,10 +74,23 @@ class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject public function two() { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = []; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 0) { + if (0 !== null && $__phpunit_count > 0) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -70,9 +98,11 @@ class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'two', $__phpunit_arguments, '', $this, true + 'Foo', 'two', $__phpunit_arguments, '', $this ) ); @@ -81,10 +111,23 @@ class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject protected function three() { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = []; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 0) { + if (0 !== null && $__phpunit_count > 0) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -92,9 +135,11 @@ class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'three', $__phpunit_arguments, '', $this, true + 'Foo', 'three', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/class.phpt b/tests/end-to-end/mock-objects/generator/class.phpt index adda994386d..77470588c31 100644 --- a/tests/end-to-end/mock-objects/generator/class.phpt +++ b/tests/end-to-end/mock-objects/generator/class.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', true, true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) --FILE-- generate( - 'Foo', - [], - 'MockFoo', - true, - true + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function bar(Foo $foo) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$foo]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -48,9 +61,11 @@ class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, true + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); @@ -59,10 +74,23 @@ class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject public function baz(Foo $foo) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$foo]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -70,9 +98,11 @@ class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'baz', $__phpunit_arguments, '', $this, true + 'Foo', 'baz', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/class_call_parent_clone.phpt b/tests/end-to-end/mock-objects/generator/class_call_parent_clone.phpt index de0072a9e94..a4fdcab1835 100644 --- a/tests/end-to-end/mock-objects/generator/class_call_parent_clone.phpt +++ b/tests/end-to-end/mock-objects/generator/class_call_parent_clone.phpt @@ -1,32 +1,33 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true) --FILE-- generate( - 'Foo', - [], - 'MockFoo', - true + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\UnmockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\ProxiedCloneMethod; } diff --git a/tests/end-to-end/mock-objects/generator/class_call_parent_constructor.phpt b/tests/end-to-end/mock-objects/generator/class_call_parent_constructor.phpt index 1e898da12e4..1558555b381 100644 --- a/tests/end-to-end/mock-objects/generator/class_call_parent_constructor.phpt +++ b/tests/end-to-end/mock-objects/generator/class_call_parent_constructor.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true) --FILE-- generate( - 'Foo', - [], - 'MockFoo', - true + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; } diff --git a/tests/end-to-end/mock-objects/generator/class_dont_call_parent_clone.phpt b/tests/end-to-end/mock-objects/generator/class_dont_call_parent_clone.phpt index e3335fcafbf..805288c37eb 100644 --- a/tests/end-to-end/mock-objects/generator/class_dont_call_parent_clone.phpt +++ b/tests/end-to-end/mock-objects/generator/class_dont_call_parent_clone.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', false) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', false) --FILE-- generate( - 'Foo', - [], - 'MockFoo', - false + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', + callOriginalClone: false ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; } diff --git a/tests/end-to-end/mock-objects/generator/class_dont_call_parent_constructor.phpt b/tests/end-to-end/mock-objects/generator/class_dont_call_parent_constructor.phpt index 1e898da12e4..1558555b381 100644 --- a/tests/end-to-end/mock-objects/generator/class_dont_call_parent_constructor.phpt +++ b/tests/end-to-end/mock-objects/generator/class_dont_call_parent_constructor.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true) --FILE-- generate( - 'Foo', - [], - 'MockFoo', - true + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; } diff --git a/tests/end-to-end/mock-objects/generator/class_implementing_interface_call_parent_constructor.phpt b/tests/end-to-end/mock-objects/generator/class_implementing_interface_call_parent_constructor.phpt index 17ae6c9fef5..6c1ff3accd8 100644 --- a/tests/end-to-end/mock-objects/generator/class_implementing_interface_call_parent_constructor.phpt +++ b/tests/end-to-end/mock-objects/generator/class_implementing_interface_call_parent_constructor.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true) --FILE-- generate( - 'Foo', - [], - 'MockFoo', - true + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; } diff --git a/tests/end-to-end/mock-objects/generator/class_implementing_interface_dont_call_parent_constructor.phpt b/tests/end-to-end/mock-objects/generator/class_implementing_interface_dont_call_parent_constructor.phpt index 17ae6c9fef5..6c1ff3accd8 100644 --- a/tests/end-to-end/mock-objects/generator/class_implementing_interface_dont_call_parent_constructor.phpt +++ b/tests/end-to-end/mock-objects/generator/class_implementing_interface_dont_call_parent_constructor.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true) --FILE-- generate( - 'Foo', - [], - 'MockFoo', - true + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; } diff --git a/tests/end-to-end/mock-objects/generator/class_nonexistent_method.phpt b/tests/end-to-end/mock-objects/generator/class_nonexistent_method.phpt index 3fc3da56cee..8d5df73f928 100644 --- a/tests/end-to-end/mock-objects/generator/class_nonexistent_method.phpt +++ b/tests/end-to-end/mock-objects/generator/class_nonexistent_method.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', array('bar'), 'MockFoo', true, true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', array('bar'), 'MockFoo', true, true) --FILE-- generate( - 'Foo', - array('bar'), - 'MockFoo', - true, - true + type: 'Foo', + mockObject: true, + methods: ['bar'], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function bar() { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = []; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 0) { + if (0 !== null && $__phpunit_count > 0) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -44,9 +57,11 @@ class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, true + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/class_partial.phpt b/tests/end-to-end/mock-objects/generator/class_partial.phpt index 9bbd172291c..2fa52500c34 100644 --- a/tests/end-to-end/mock-objects/generator/class_partial.phpt +++ b/tests/end-to-end/mock-objects/generator/class_partial.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', array('bar'), 'MockFoo', true, true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', array('bar'), 'MockFoo', true, true) --FILE-- generate( - 'Foo', - array('bar'), - 'MockFoo', - true, - true + type: 'Foo', + mockObject: true, + methods: ['bar'], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function bar(Foo $foo) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$foo]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -48,9 +61,11 @@ class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, true + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/class_with_deprecated_method.phpt b/tests/end-to-end/mock-objects/generator/class_with_deprecated_method.phpt index 9b5bf00efc2..3506ad581f4 100644 --- a/tests/end-to-end/mock-objects/generator/class_with_deprecated_method.phpt +++ b/tests/end-to-end/mock-objects/generator/class_with_deprecated_method.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('ClassWithDeprecatedMethod', [], 'MockFoo', TRUE, TRUE) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('ClassWithDeprecatedMethod', [], 'MockFoo', TRUE, TRUE) --FILE-- generate( - 'ClassWithDeprecatedMethod', - [], - 'MockFoo', - TRUE, - TRUE + type: 'ClassWithDeprecatedMethod', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends ClassWithDeprecatedMethod implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends ClassWithDeprecatedMethod implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function deprecatedMethod() { @trigger_error('The ClassWithDeprecatedMethod::deprecatedMethod method is deprecated (this method is deprecated).', E_USER_DEPRECATED); + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = []; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 0) { + if (0 !== null && $__phpunit_count > 0) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -50,9 +63,11 @@ class MockFoo extends ClassWithDeprecatedMethod implements PHPUnit\Framework\Moc } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'ClassWithDeprecatedMethod', 'deprecatedMethod', $__phpunit_arguments, '', $this, true + 'ClassWithDeprecatedMethod', 'deprecatedMethod', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/class_with_final_method.phpt b/tests/end-to-end/mock-objects/generator/class_with_final_method.phpt index 77ea0b5e64d..d68911c1451 100644 --- a/tests/end-to-end/mock-objects/generator/class_with_final_method.phpt +++ b/tests/end-to-end/mock-objects/generator/class_with_final_method.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('ClassWithFinalMethod', [], 'MockFoo', true, true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('ClassWithFinalMethod', [], 'MockFoo', true, true) --FILE-- generate( - 'ClassWithFinalMethod', - [], - 'MockFoo', - true, - true + type: 'ClassWithFinalMethod', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends ClassWithFinalMethod implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends ClassWithFinalMethod implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; } diff --git a/tests/end-to-end/mock-objects/generator/class_with_method_named_method.phpt b/tests/end-to-end/mock-objects/generator/class_with_method_named_method.phpt deleted file mode 100644 index 7e888b8faf5..00000000000 --- a/tests/end-to-end/mock-objects/generator/class_with_method_named_method.phpt +++ /dev/null @@ -1,54 +0,0 @@ ---TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', true, true) ---FILE-- -generate( - 'Foo', - [], - 'MockFoo', - true, - true -); - -print $mock->getClassCode(); ---EXPECTF-- -declare(strict_types=1); - -class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject -{ - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; - - public function method() - { - $__phpunit_arguments = []; - $__phpunit_count = func_num_args(); - - if ($__phpunit_count > 0) { - $__phpunit_arguments_tmp = func_get_args(); - - for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { - $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; - } - } - - $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( - new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'method', $__phpunit_arguments, '', $this, true - ) - ); - - return $__phpunit_result; - } -} diff --git a/tests/end-to-end/mock-objects/generator/class_with_method_with_nullable_typehinted_variadic_arguments.phpt b/tests/end-to-end/mock-objects/generator/class_with_method_with_nullable_typehinted_variadic_arguments.phpt index c0320f785d3..67947213d74 100644 --- a/tests/end-to-end/mock-objects/generator/class_with_method_with_nullable_typehinted_variadic_arguments.phpt +++ b/tests/end-to-end/mock-objects/generator/class_with_method_with_nullable_typehinted_variadic_arguments.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('ClassWithMethodWithVariadicArguments', [], 'MockFoo', true, true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('ClassWithMethodWithVariadicArguments', [], 'MockFoo', true, true) --FILE-- generate( - 'ClassWithMethodWithNullableTypehintedVariadicArguments', - [], - 'MockFoo', - true, - true + type: 'ClassWithMethodWithNullableTypehintedVariadicArguments', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends ClassWithMethodWithNullableTypehintedVariadicArguments implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends ClassWithMethodWithNullableTypehintedVariadicArguments implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function methodWithNullableTypehintedVariadicArguments($a, ?string ...$parameters) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$a]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -44,9 +57,11 @@ class MockFoo extends ClassWithMethodWithNullableTypehintedVariadicArguments imp } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'ClassWithMethodWithNullableTypehintedVariadicArguments', 'methodWithNullableTypehintedVariadicArguments', $__phpunit_arguments, '', $this, true + 'ClassWithMethodWithNullableTypehintedVariadicArguments', 'methodWithNullableTypehintedVariadicArguments', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/class_with_method_with_typehinted_variadic_arguments.phpt b/tests/end-to-end/mock-objects/generator/class_with_method_with_typehinted_variadic_arguments.phpt index 54b5d6ea2da..9c264fb08df 100644 --- a/tests/end-to-end/mock-objects/generator/class_with_method_with_typehinted_variadic_arguments.phpt +++ b/tests/end-to-end/mock-objects/generator/class_with_method_with_typehinted_variadic_arguments.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('ClassWithMethodWithVariadicArguments', [], 'MockFoo', true, true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('ClassWithMethodWithVariadicArguments', [], 'MockFoo', true, true) --FILE-- generate( - 'ClassWithMethodWithTypehintedVariadicArguments', - [], - 'MockFoo', - true, - true + type: 'ClassWithMethodWithTypehintedVariadicArguments', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends ClassWithMethodWithTypehintedVariadicArguments implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends ClassWithMethodWithTypehintedVariadicArguments implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function methodWithTypehintedVariadicArguments($a, string ...$parameters) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$a]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -44,9 +57,11 @@ class MockFoo extends ClassWithMethodWithTypehintedVariadicArguments implements } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'ClassWithMethodWithTypehintedVariadicArguments', 'methodWithTypehintedVariadicArguments', $__phpunit_arguments, '', $this, true + 'ClassWithMethodWithTypehintedVariadicArguments', 'methodWithTypehintedVariadicArguments', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/class_with_method_with_variadic_arguments.phpt b/tests/end-to-end/mock-objects/generator/class_with_method_with_variadic_arguments.phpt index b44927c1a54..1f74bafda4b 100644 --- a/tests/end-to-end/mock-objects/generator/class_with_method_with_variadic_arguments.phpt +++ b/tests/end-to-end/mock-objects/generator/class_with_method_with_variadic_arguments.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('ClassWithMethodWithVariadicArguments', [], 'MockFoo', true, true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('ClassWithMethodWithVariadicArguments', [], 'MockFoo', true, true) --FILE-- generate( - 'ClassWithMethodWithVariadicArguments', - [], - 'MockFoo', - true, - true + type: 'ClassWithMethodWithVariadicArguments', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends ClassWithMethodWithVariadicArguments implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends ClassWithMethodWithVariadicArguments implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function methodWithVariadicArguments($a, ...$parameters) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$a]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -44,9 +57,11 @@ class MockFoo extends ClassWithMethodWithVariadicArguments implements PHPUnit\Fr } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'ClassWithMethodWithVariadicArguments', 'methodWithVariadicArguments', $__phpunit_arguments, '', $this, true + 'ClassWithMethodWithVariadicArguments', 'methodWithVariadicArguments', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/constant_as_parameter_default_value.phpt b/tests/end-to-end/mock-objects/generator/constant_as_parameter_default_value.phpt index 4c2b0d0b2e7..89a5b0079a1 100644 --- a/tests/end-to-end/mock-objects/generator/constant_as_parameter_default_value.phpt +++ b/tests/end-to-end/mock-objects/generator/constant_as_parameter_default_value.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', true, true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) --FILE-- generate( - 'Foo', - [], - 'MockFoo', - true, - true + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); +print $mock->classCode(); --EXPECTF-- declare(strict_types=1); -class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function bar(int $baz = %d) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$baz]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -44,9 +57,11 @@ class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, true + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/extendable_class_with_final_property.phpt b/tests/end-to-end/mock-objects/generator/extendable_class_with_final_property.phpt new file mode 100644 index 00000000000..9a5ba4d1941 --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/extendable_class_with_final_property.phpt @@ -0,0 +1,39 @@ +--TEST-- +Extendable class with property with final property +--SKIPIF-- +generate( + type: Foo::class, + mockObject: false, + methods: [], + mockClassName: 'TestStubFoo', +); + +print $testDoubleClass->classCode(); +--EXPECT-- +declare(strict_types=1); + +class TestStubFoo extends Foo implements PHPUnit\Framework\MockObject\StubInternal +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; +} diff --git a/tests/end-to-end/mock-objects/generator/extendable_class_with_property_with_final_get_hook.phpt b/tests/end-to-end/mock-objects/generator/extendable_class_with_property_with_final_get_hook.phpt new file mode 100644 index 00000000000..753c04237d4 --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/extendable_class_with_property_with_final_get_hook.phpt @@ -0,0 +1,39 @@ +--TEST-- +Extendable class with property with final get property hook +--SKIPIF-- +generate( + type: Foo::class, + mockObject: false, + methods: [], + mockClassName: 'TestStubFoo', +); + +print $testDoubleClass->classCode(); +--EXPECT-- +declare(strict_types=1); + +class TestStubFoo extends Foo implements PHPUnit\Framework\MockObject\StubInternal +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; +} diff --git a/tests/end-to-end/mock-objects/generator/extendable_class_with_property_with_get_and_set_hooks.phpt b/tests/end-to-end/mock-objects/generator/extendable_class_with_property_with_get_and_set_hooks.phpt new file mode 100644 index 00000000000..18de0cdb2da --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/extendable_class_with_property_with_get_and_set_hooks.phpt @@ -0,0 +1,61 @@ +--TEST-- +Extendable class with property with non-final get and set property hooks +--SKIPIF-- +bar = $value; + } + } +} + +require_once __DIR__ . '/../../../bootstrap.php'; + +$generator = new \PHPUnit\Framework\MockObject\Generator\Generator; + +$testDoubleClass = $generator->generate( + type: Foo::class, + mockObject: false, + methods: [], + mockClassName: 'TestStubFoo', +); + +print $testDoubleClass->classCode(); +--EXPECT-- +declare(strict_types=1); + +class TestStubFoo extends Foo implements PHPUnit\Framework\MockObject\StubInternal +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; + + public string $bar { + get { + return $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'TestStubFoo', '$bar::get', [], 'string', $this + ) + ); + } + + set (string $value) { + $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'TestStubFoo', '$bar::set', [$value], 'void', $this + ) + ); + } + } +} diff --git a/tests/end-to-end/mock-objects/generator/extendable_class_with_property_with_get_hook.phpt b/tests/end-to-end/mock-objects/generator/extendable_class_with_property_with_get_hook.phpt new file mode 100644 index 00000000000..1108a49905d --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/extendable_class_with_property_with_get_hook.phpt @@ -0,0 +1,49 @@ +--TEST-- +Extendable class with property with non-final get property hook +--SKIPIF-- +generate( + type: Foo::class, + mockObject: false, + methods: [], + mockClassName: 'TestStubFoo', +); + +print $testDoubleClass->classCode(); +--EXPECT-- +declare(strict_types=1); + +class TestStubFoo extends Foo implements PHPUnit\Framework\MockObject\StubInternal +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; + + public string $bar { + get { + return $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'TestStubFoo', '$bar::get', [], 'string', $this + ) + ); + } + } +} diff --git a/tests/end-to-end/mock-objects/generator/extendable_class_with_property_with_set_hook.phpt b/tests/end-to-end/mock-objects/generator/extendable_class_with_property_with_set_hook.phpt new file mode 100644 index 00000000000..df0c7ba0c60 --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/extendable_class_with_property_with_set_hook.phpt @@ -0,0 +1,49 @@ +--TEST-- +Extendable class with property with non-final set property hook +--SKIPIF-- +bar = $value; + } + } +} + +require_once __DIR__ . '/../../../bootstrap.php'; + +$generator = new \PHPUnit\Framework\MockObject\Generator\Generator; + +$testDoubleClass = $generator->generate( + type: Foo::class, + mockObject: false, + methods: [], + mockClassName: 'TestStubFoo', +); + +print $testDoubleClass->classCode(); +--EXPECT-- +declare(strict_types=1); + +class TestStubFoo extends Foo implements PHPUnit\Framework\MockObject\StubInternal +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; + + public string $bar { + set (string $value) { + $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'TestStubFoo', '$bar::set', [$value], 'void', $this + ) + ); + } + } +} diff --git a/tests/end-to-end/mock-objects/generator/interface.phpt b/tests/end-to-end/mock-objects/generator/interface.phpt index cc49b7651e1..540b502757c 100644 --- a/tests/end-to-end/mock-objects/generator/interface.phpt +++ b/tests/end-to-end/mock-objects/generator/interface.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', true, true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) --FILE-- generate( - 'Foo', - [], - 'MockFoo', - true, - true + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo implements PHPUnit\Framework\MockObject\MockObject, Foo +class MockFoo implements PHPUnit\Framework\MockObject\MockObjectInternal, Foo { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function bar(Foo $foo) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$foo]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -42,9 +55,11 @@ class MockFoo implements PHPUnit\Framework\MockObject\MockObject, Foo } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, true + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/interface_with_nullable_property_with_get_hook.phpt b/tests/end-to-end/mock-objects/generator/interface_with_nullable_property_with_get_hook.phpt new file mode 100644 index 00000000000..5ee090dd1ba --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/interface_with_nullable_property_with_get_hook.phpt @@ -0,0 +1,45 @@ +--TEST-- +Interface with nullable property with get property hook +--SKIPIF-- +generate( + type: Foo::class, + mockObject: false, + methods: [], + mockClassName: 'TestStubFoo', +); + +print $testDoubleClass->classCode(); +--EXPECT-- +declare(strict_types=1); + +class TestStubFoo implements PHPUnit\Framework\MockObject\StubInternal, Foo +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; + + public ?string $bar { + get { + return $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'TestStubFoo', '$bar::get', [], '?string', $this + ) + ); + } + } +} diff --git a/tests/end-to-end/mock-objects/generator/interface_with_nullable_property_with_set_hook.phpt b/tests/end-to-end/mock-objects/generator/interface_with_nullable_property_with_set_hook.phpt new file mode 100644 index 00000000000..a0be2b822a2 --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/interface_with_nullable_property_with_set_hook.phpt @@ -0,0 +1,45 @@ +--TEST-- +Interface with nullable property with set property hook +--SKIPIF-- +generate( + type: Foo::class, + mockObject: false, + methods: [], + mockClassName: 'TestStubFoo', +); + +print $testDoubleClass->classCode(); +--EXPECT-- +declare(strict_types=1); + +class TestStubFoo implements PHPUnit\Framework\MockObject\StubInternal, Foo +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; + + public ?string $bar { + set (?string $value) { + $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'TestStubFoo', '$bar::set', [$value], 'void', $this + ) + ); + } + } +} diff --git a/tests/end-to-end/mock-objects/generator/interface_with_property_with_get_and_set_hooks.phpt b/tests/end-to-end/mock-objects/generator/interface_with_property_with_get_and_set_hooks.phpt new file mode 100644 index 00000000000..cf61e81f91e --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/interface_with_property_with_get_and_set_hooks.phpt @@ -0,0 +1,53 @@ +--TEST-- +Interface with property with get and set property hooks +--SKIPIF-- +generate( + type: Foo::class, + mockObject: false, + methods: [], + mockClassName: 'TestStubFoo', +); + +print $testDoubleClass->classCode(); +--EXPECT-- +declare(strict_types=1); + +class TestStubFoo implements PHPUnit\Framework\MockObject\StubInternal, Foo +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; + + public string $bar { + get { + return $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'TestStubFoo', '$bar::get', [], 'string', $this + ) + ); + } + + set (string $value) { + $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'TestStubFoo', '$bar::set', [$value], 'void', $this + ) + ); + } + } +} diff --git a/tests/end-to-end/mock-objects/generator/interface_with_property_with_get_hook.phpt b/tests/end-to-end/mock-objects/generator/interface_with_property_with_get_hook.phpt new file mode 100644 index 00000000000..442393e34fe --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/interface_with_property_with_get_hook.phpt @@ -0,0 +1,45 @@ +--TEST-- +Interface with property with get property hook +--SKIPIF-- +generate( + type: Foo::class, + mockObject: false, + methods: [], + mockClassName: 'TestStubFoo', +); + +print $testDoubleClass->classCode(); +--EXPECT-- +declare(strict_types=1); + +class TestStubFoo implements PHPUnit\Framework\MockObject\StubInternal, Foo +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; + + public string $bar { + get { + return $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'TestStubFoo', '$bar::get', [], 'string', $this + ) + ); + } + } +} diff --git a/tests/end-to-end/mock-objects/generator/interface_with_property_with_set_hook.phpt b/tests/end-to-end/mock-objects/generator/interface_with_property_with_set_hook.phpt new file mode 100644 index 00000000000..5502369225b --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/interface_with_property_with_set_hook.phpt @@ -0,0 +1,45 @@ +--TEST-- +Interface with property with set property hook +--SKIPIF-- +generate( + type: Foo::class, + mockObject: false, + methods: [], + mockClassName: 'TestStubFoo', +); + +print $testDoubleClass->classCode(); +--EXPECT-- +declare(strict_types=1); + +class TestStubFoo implements PHPUnit\Framework\MockObject\StubInternal, Foo +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; + + public string $bar { + set (string $value) { + $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'TestStubFoo', '$bar::set', [$value], 'void', $this + ) + ); + } + } +} diff --git a/tests/end-to-end/mock-objects/generator/invocation_object_clone_object.phpt b/tests/end-to-end/mock-objects/generator/invocation_object_clone_object.phpt deleted file mode 100644 index 4d215d196d9..00000000000 --- a/tests/end-to-end/mock-objects/generator/invocation_object_clone_object.phpt +++ /dev/null @@ -1,82 +0,0 @@ ---TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', true, true, true) ---FILE-- -generate( - 'Foo', - [], - 'MockFoo', - true, - true, - true -); - -print $mock->getClassCode(); ---EXPECTF-- -declare(strict_types=1); - -class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject -{ - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; - - public function bar(Foo $foo) - { - $__phpunit_arguments = [$foo]; - $__phpunit_count = func_num_args(); - - if ($__phpunit_count > 1) { - $__phpunit_arguments_tmp = func_get_args(); - - for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { - $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; - } - } - - $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( - new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, true - ) - ); - - return $__phpunit_result; - } - - public function baz(Foo $foo) - { - $__phpunit_arguments = [$foo]; - $__phpunit_count = func_num_args(); - - if ($__phpunit_count > 1) { - $__phpunit_arguments_tmp = func_get_args(); - - for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { - $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; - } - } - - $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( - new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'baz', $__phpunit_arguments, '', $this, true - ) - ); - - return $__phpunit_result; - } -} diff --git a/tests/end-to-end/mock-objects/generator/namespaced_class.phpt b/tests/end-to-end/mock-objects/generator/namespaced_class.phpt index 498ce03dc47..75d013545ba 100644 --- a/tests/end-to-end/mock-objects/generator/namespaced_class.phpt +++ b/tests/end-to-end/mock-objects/generator/namespaced_class.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('NS\Foo', [], 'MockFoo', true, true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('NS\Foo', [], 'MockFoo', true, true) --FILE-- generate( - 'NS\Foo', - [], - 'MockFoo', - true, - true + type: 'NS\Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends NS\Foo implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends NS\Foo implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function bar(NS\Foo $foo) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$foo]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -50,9 +63,11 @@ class MockFoo extends NS\Foo implements PHPUnit\Framework\MockObject\MockObject } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'NS\Foo', 'bar', $__phpunit_arguments, '', $this, true + 'NS\Foo', 'bar', $__phpunit_arguments, '', $this ) ); @@ -61,10 +76,23 @@ class MockFoo extends NS\Foo implements PHPUnit\Framework\MockObject\MockObject public function baz(NS\Foo $foo) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$foo]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -72,9 +100,11 @@ class MockFoo extends NS\Foo implements PHPUnit\Framework\MockObject\MockObject } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'NS\Foo', 'baz', $__phpunit_arguments, '', $this, true + 'NS\Foo', 'baz', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/namespaced_class_call_parent_clone.phpt b/tests/end-to-end/mock-objects/generator/namespaced_class_call_parent_clone.phpt index 98b7dc24583..ec75e561f28 100644 --- a/tests/end-to-end/mock-objects/generator/namespaced_class_call_parent_clone.phpt +++ b/tests/end-to-end/mock-objects/generator/namespaced_class_call_parent_clone.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('NS\Foo', [], 'MockFoo', true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('NS\Foo', [], 'MockFoo', true) --FILE-- generate( - 'NS\Foo', - [], - 'MockFoo', - true + type: 'NS\Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends NS\Foo implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends NS\Foo implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\UnmockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\ProxiedCloneMethod; } diff --git a/tests/end-to-end/mock-objects/generator/namespaced_class_call_parent_constructor.phpt b/tests/end-to-end/mock-objects/generator/namespaced_class_call_parent_constructor.phpt index 4f480594db7..4af90495f11 100644 --- a/tests/end-to-end/mock-objects/generator/namespaced_class_call_parent_constructor.phpt +++ b/tests/end-to-end/mock-objects/generator/namespaced_class_call_parent_constructor.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('NS\Foo', [], 'MockFoo', true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('NS\Foo', [], 'MockFoo', true) --FILE-- generate( - 'NS\Foo', - [], - 'MockFoo', - true + type: 'NS\Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends NS\Foo implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends NS\Foo implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; } diff --git a/tests/end-to-end/mock-objects/generator/namespaced_class_dont_call_parent_clone.phpt b/tests/end-to-end/mock-objects/generator/namespaced_class_dont_call_parent_clone.phpt index ee78ef2e870..6fd9e032a9e 100644 --- a/tests/end-to-end/mock-objects/generator/namespaced_class_dont_call_parent_clone.phpt +++ b/tests/end-to-end/mock-objects/generator/namespaced_class_dont_call_parent_clone.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('NS\Foo', [], 'MockFoo', false) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('NS\Foo', [], 'MockFoo', false) --FILE-- generate( - 'NS\Foo', - [], - 'MockFoo', - false + type: 'NS\Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', + callOriginalClone: false, ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends NS\Foo implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends NS\Foo implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; } diff --git a/tests/end-to-end/mock-objects/generator/namespaced_class_dont_call_parent_constructor.phpt b/tests/end-to-end/mock-objects/generator/namespaced_class_dont_call_parent_constructor.phpt index 4f480594db7..4af90495f11 100644 --- a/tests/end-to-end/mock-objects/generator/namespaced_class_dont_call_parent_constructor.phpt +++ b/tests/end-to-end/mock-objects/generator/namespaced_class_dont_call_parent_constructor.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('NS\Foo', [], 'MockFoo', true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('NS\Foo', [], 'MockFoo', true) --FILE-- generate( - 'NS\Foo', - [], - 'MockFoo', - true + type: 'NS\Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends NS\Foo implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends NS\Foo implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; } diff --git a/tests/end-to-end/mock-objects/generator/namespaced_class_implementing_interface_call_parent_constructor.phpt b/tests/end-to-end/mock-objects/generator/namespaced_class_implementing_interface_call_parent_constructor.phpt index 202bf0f58b5..322732cff74 100644 --- a/tests/end-to-end/mock-objects/generator/namespaced_class_implementing_interface_call_parent_constructor.phpt +++ b/tests/end-to-end/mock-objects/generator/namespaced_class_implementing_interface_call_parent_constructor.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('NS\Foo', [], 'MockFoo', true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('NS\Foo', [], 'MockFoo', true) --FILE-- generate( - 'NS\Foo', - [], - 'MockFoo', - true + type: 'NS\Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends NS\Foo implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends NS\Foo implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; } diff --git a/tests/end-to-end/mock-objects/generator/namespaced_class_implementing_interface_dont_call_parent_constructor.phpt b/tests/end-to-end/mock-objects/generator/namespaced_class_implementing_interface_dont_call_parent_constructor.phpt index 202bf0f58b5..322732cff74 100644 --- a/tests/end-to-end/mock-objects/generator/namespaced_class_implementing_interface_dont_call_parent_constructor.phpt +++ b/tests/end-to-end/mock-objects/generator/namespaced_class_implementing_interface_dont_call_parent_constructor.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('NS\Foo', [], 'MockFoo', true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('NS\Foo', [], 'MockFoo', true) --FILE-- generate( - 'NS\Foo', - [], - 'MockFoo', - true + type: 'NS\Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends NS\Foo implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends NS\Foo implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; } diff --git a/tests/end-to-end/mock-objects/generator/namespaced_class_partial.phpt b/tests/end-to-end/mock-objects/generator/namespaced_class_partial.phpt index 993023c94e7..503117b634e 100644 --- a/tests/end-to-end/mock-objects/generator/namespaced_class_partial.phpt +++ b/tests/end-to-end/mock-objects/generator/namespaced_class_partial.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('NS\Foo', array('bar'), 'MockFoo', true, true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('NS\Foo', array('bar'), 'MockFoo', true, true) --FILE-- generate( - 'NS\Foo', - array('bar'), - 'MockFoo', - true, - true + type: 'NS\Foo', + mockObject: true, + methods: ['bar'], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends NS\Foo implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends NS\Foo implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function bar(NS\Foo $foo) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$foo]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -50,9 +63,11 @@ class MockFoo extends NS\Foo implements PHPUnit\Framework\MockObject\MockObject } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'NS\Foo', 'bar', $__phpunit_arguments, '', $this, true + 'NS\Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/namespaced_interface.phpt b/tests/end-to-end/mock-objects/generator/namespaced_interface.phpt index 486120118cc..e204034f8c9 100644 --- a/tests/end-to-end/mock-objects/generator/namespaced_interface.phpt +++ b/tests/end-to-end/mock-objects/generator/namespaced_interface.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('NS\Foo', [], 'MockFoo', true, true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('NS\Foo', [], 'MockFoo', true, true) --FILE-- generate( - 'NS\Foo', - [], - 'MockFoo', - true, - true + type: 'NS\Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo implements PHPUnit\Framework\MockObject\MockObject, NS\Foo +class MockFoo implements PHPUnit\Framework\MockObject\MockObjectInternal, NS\Foo { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function bar(NS\Foo $foo) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$foo]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -44,9 +57,11 @@ class MockFoo implements PHPUnit\Framework\MockObject\MockObject, NS\Foo } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'NS\Foo', 'bar', $__phpunit_arguments, '', $this, true + 'NS\Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/nonexistent_class.phpt b/tests/end-to-end/mock-objects/generator/nonexistent_class.phpt deleted file mode 100644 index 38546bc5d75..00000000000 --- a/tests/end-to-end/mock-objects/generator/nonexistent_class.phpt +++ /dev/null @@ -1,30 +0,0 @@ ---TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('NonExistentClass', [], 'MockFoo', true, true) ---FILE-- -generate( - 'NonExistentClass', - [], - 'MockFoo', - true, - true -); - -print $mock->getClassCode(); ---EXPECTF-- -declare(strict_types=1); - -class NonExistentClass -{ -} - -class MockFoo extends NonExistentClass implements PHPUnit\Framework\MockObject\MockObject -{ - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; -} diff --git a/tests/end-to-end/mock-objects/generator/nonexistent_class_with_namespace.phpt b/tests/end-to-end/mock-objects/generator/nonexistent_class_with_namespace.phpt deleted file mode 100644 index 45625955e76..00000000000 --- a/tests/end-to-end/mock-objects/generator/nonexistent_class_with_namespace.phpt +++ /dev/null @@ -1,38 +0,0 @@ ---TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', true, true) ---FILE-- -generate( - 'NS\Foo', - [], - 'MockFoo', - true, - true -); - -print $mock->getClassCode(); ---EXPECTF-- -declare(strict_types=1); - -namespace NS { - -class Foo -{ -} - -} - -namespace { - -class MockFoo extends NS\Foo implements PHPUnit\Framework\MockObject\MockObject -{ - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; -} - -} diff --git a/tests/end-to-end/mock-objects/generator/nonexistent_class_with_namespace_starting_with_separator.phpt b/tests/end-to-end/mock-objects/generator/nonexistent_class_with_namespace_starting_with_separator.phpt deleted file mode 100644 index 459d400f055..00000000000 --- a/tests/end-to-end/mock-objects/generator/nonexistent_class_with_namespace_starting_with_separator.phpt +++ /dev/null @@ -1,38 +0,0 @@ ---TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', true, true) ---FILE-- -generate( - '\NS\Foo', - [], - 'MockFoo', - true, - true -); - -print $mock->getClassCode(); ---EXPECTF-- -declare(strict_types=1); - -namespace NS { - -class Foo -{ -} - -} - -namespace { - -class MockFoo extends NS\Foo implements PHPUnit\Framework\MockObject\MockObject -{ - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; -} - -} diff --git a/tests/end-to-end/mock-objects/generator/nullable_types.phpt b/tests/end-to-end/mock-objects/generator/nullable_types.phpt index 9e51df22b47..168886b527c 100644 --- a/tests/end-to-end/mock-objects/generator/nullable_types.phpt +++ b/tests/end-to-end/mock-objects/generator/nullable_types.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', true, true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) --FILE-- generate( - 'Foo', - [], - 'MockFoo', - true, - true + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function bar(?int $x) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$x]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -44,9 +57,11 @@ class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, true + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/nullable_union_type_parameter.phpt b/tests/end-to-end/mock-objects/generator/nullable_union_type_parameter.phpt new file mode 100644 index 00000000000..5ff35f55b17 --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/nullable_union_type_parameter.phpt @@ -0,0 +1,70 @@ +--TEST-- +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) +--FILE-- +generate( + type: Foo::class, + mockObject: true, + methods: [], + mockClassName: 'MockFoo', +); + +print $mock->classCode(); +--EXPECT-- +declare(strict_types=1); + +class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObjectInternal +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; + + public function bar(bool|int $baz, Foo|null|stdClass $other) + { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + + $__phpunit_arguments = [$baz, $other]; + $__phpunit_count = func_num_args(); + + if (2 !== null && $__phpunit_count > 2) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = 2; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'Foo', 'bar', $__phpunit_arguments, '', $this + ) + ); + + return $__phpunit_result; + } +} diff --git a/tests/end-to-end/mock-objects/generator/nullable_union_type_return.phpt b/tests/end-to-end/mock-objects/generator/nullable_union_type_return.phpt new file mode 100644 index 00000000000..35db70f87a5 --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/nullable_union_type_return.phpt @@ -0,0 +1,70 @@ +--TEST-- +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) +--FILE-- +generate( + type: Foo::class, + mockObject: true, + methods: [], + mockClassName: 'MockFoo', +); + +print $mock->classCode(); +--EXPECT-- +declare(strict_types=1); + +class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObjectInternal +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; + + public function bar(): bool|int|null + { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + + $__phpunit_arguments = []; + $__phpunit_count = func_num_args(); + + if (0 !== null && $__phpunit_count > 0) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'Foo', 'bar', $__phpunit_arguments, 'bool|int|null', $this + ) + ); + + return $__phpunit_result; + } +} diff --git a/tests/end-to-end/mock-objects/generator/parameter_dnf.phpt b/tests/end-to-end/mock-objects/generator/parameter_dnf.phpt new file mode 100644 index 00000000000..6d3c2aab50b --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/parameter_dnf.phpt @@ -0,0 +1,78 @@ +--TEST-- +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) +--FILE-- +generate( + type: Foo::class, + mockObject: true, + methods: [], + mockClassName: 'MockFoo', +); + +print $mock->classCode(); +--EXPECT-- +declare(strict_types=1); + +class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObjectInternal +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; + + public function bar((A&B)|int|null $baz) + { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + + $__phpunit_arguments = [$baz]; + $__phpunit_count = func_num_args(); + + if (1 !== null && $__phpunit_count > 1) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'Foo', 'bar', $__phpunit_arguments, '', $this + ) + ); + + return $__phpunit_result; + } +} diff --git a/tests/end-to-end/mock-objects/generator/parameter_false.phpt b/tests/end-to-end/mock-objects/generator/parameter_false.phpt new file mode 100644 index 00000000000..356451aaeef --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/parameter_false.phpt @@ -0,0 +1,66 @@ +--TEST-- +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) +--FILE-- +generate( + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', +); + +print $mock->classCode(); +--EXPECT-- +declare(strict_types=1); + +class MockFoo implements PHPUnit\Framework\MockObject\MockObjectInternal, Foo +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; + + public function bar(false $baz): void + { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + + $__phpunit_arguments = [$baz]; + $__phpunit_count = func_num_args(); + + if (1 !== null && $__phpunit_count > 1) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'Foo', 'bar', $__phpunit_arguments, 'void', $this + ) + ); + } +} diff --git a/tests/end-to-end/mock-objects/generator/parameter_intersection.phpt b/tests/end-to-end/mock-objects/generator/parameter_intersection.phpt new file mode 100644 index 00000000000..e30514eff55 --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/parameter_intersection.phpt @@ -0,0 +1,78 @@ +--TEST-- +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) +--FILE-- +generate( + type: Foo::class, + mockObject: true, + methods: [], + mockClassName: 'MockFoo', +); + +print $mock->classCode(); +--EXPECT-- +declare(strict_types=1); + +class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObjectInternal +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; + + public function bar(AnInterface&AnotherInterface $baz) + { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + + $__phpunit_arguments = [$baz]; + $__phpunit_count = func_num_args(); + + if (1 !== null && $__phpunit_count > 1) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'Foo', 'bar', $__phpunit_arguments, '', $this + ) + ); + + return $__phpunit_result; + } +} diff --git a/tests/end-to-end/mock-objects/generator/parameter_null.phpt b/tests/end-to-end/mock-objects/generator/parameter_null.phpt new file mode 100644 index 00000000000..8c091b99f06 --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/parameter_null.phpt @@ -0,0 +1,66 @@ +--TEST-- +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) +--FILE-- +generate( + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', +); + +print $mock->classCode(); +--EXPECT-- +declare(strict_types=1); + +class MockFoo implements PHPUnit\Framework\MockObject\MockObjectInternal, Foo +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; + + public function bar(null $baz): void + { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + + $__phpunit_arguments = [$baz]; + $__phpunit_count = func_num_args(); + + if (1 !== null && $__phpunit_count > 1) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'Foo', 'bar', $__phpunit_arguments, 'void', $this + ) + ); + } +} diff --git a/tests/end-to-end/mock-objects/generator/parameter_true.phpt b/tests/end-to-end/mock-objects/generator/parameter_true.phpt new file mode 100644 index 00000000000..8f1f8b85c67 --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/parameter_true.phpt @@ -0,0 +1,66 @@ +--TEST-- +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) +--FILE-- +generate( + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', +); + +print $mock->classCode(); +--EXPECT-- +declare(strict_types=1); + +class MockFoo implements PHPUnit\Framework\MockObject\MockObjectInternal, Foo +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; + + public function bar(true $baz): void + { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + + $__phpunit_arguments = [$baz]; + $__phpunit_count = func_num_args(); + + if (1 !== null && $__phpunit_count > 1) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'Foo', 'bar', $__phpunit_arguments, 'void', $this + ) + ); + } +} diff --git a/tests/end-to-end/mock-objects/generator/parameter_union.phpt b/tests/end-to-end/mock-objects/generator/parameter_union.phpt new file mode 100644 index 00000000000..2d1007bb48d --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/parameter_union.phpt @@ -0,0 +1,70 @@ +--TEST-- +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) +--FILE-- +generate( + type: Foo::class, + mockObject: true, + methods: [], + mockClassName: 'MockFoo', +); + +print $mock->classCode(); +--EXPECT-- +declare(strict_types=1); + +class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObjectInternal +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; + + public function bar(bool|int $baz, Foo|stdClass $other) + { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + + $__phpunit_arguments = [$baz, $other]; + $__phpunit_count = func_num_args(); + + if (2 !== null && $__phpunit_count > 2) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = 2; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'Foo', 'bar', $__phpunit_arguments, '', $this + ) + ); + + return $__phpunit_result; + } +} diff --git a/tests/end-to-end/mock-objects/generator/proxy.phpt b/tests/end-to-end/mock-objects/generator/proxy.phpt deleted file mode 100644 index 6a9acae5784..00000000000 --- a/tests/end-to-end/mock-objects/generator/proxy.phpt +++ /dev/null @@ -1,77 +0,0 @@ ---TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', null, 'ProxyFoo', true, true, true, true) ---FILE-- -generate( - 'Foo', [], 'ProxyFoo', true, true, true, true -); - -print $mock->getClassCode(); ---EXPECTF-- -declare(strict_types=1); - -class ProxyFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject -{ - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; - - public function bar(Foo $foo) - { - $__phpunit_arguments = [$foo]; - $__phpunit_count = func_num_args(); - - if ($__phpunit_count > 1) { - $__phpunit_arguments_tmp = func_get_args(); - - for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { - $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; - } - } - - $this->__phpunit_getInvocationHandler()->invoke( - new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, true, true - ) - ); - - return call_user_func_array(array($this->__phpunit_originalObject, "bar"), $__phpunit_arguments); - } - - public function baz(Foo $foo) - { - $__phpunit_arguments = [$foo]; - $__phpunit_count = func_num_args(); - - if ($__phpunit_count > 1) { - $__phpunit_arguments_tmp = func_get_args(); - - for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { - $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; - } - } - - $this->__phpunit_getInvocationHandler()->invoke( - new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'baz', $__phpunit_arguments, '', $this, true, true - ) - ); - - return call_user_func_array(array($this->__phpunit_originalObject, "baz"), $__phpunit_arguments); - } -} diff --git a/tests/end-to-end/mock-objects/generator/return_type_declarations_closure.phpt b/tests/end-to-end/mock-objects/generator/return_type_declarations_closure.phpt index e183d9835d5..70a39582f42 100644 --- a/tests/end-to-end/mock-objects/generator/return_type_declarations_closure.phpt +++ b/tests/end-to-end/mock-objects/generator/return_type_declarations_closure.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', true, true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) --FILE-- generate( - 'Foo', - [], - 'MockFoo', - true, - true + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo implements PHPUnit\Framework\MockObject\MockObject, Foo +class MockFoo implements PHPUnit\Framework\MockObject\MockObjectInternal, Foo { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function bar(): Closure { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = []; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 0) { + if (0 !== null && $__phpunit_count > 0) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -42,9 +55,11 @@ class MockFoo implements PHPUnit\Framework\MockObject\MockObject, Foo } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, 'Closure', $this, true + 'Foo', 'bar', $__phpunit_arguments, 'Closure', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/return_type_declarations_dnf.phpt b/tests/end-to-end/mock-objects/generator/return_type_declarations_dnf.phpt new file mode 100644 index 00000000000..c1b3e2cbac9 --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/return_type_declarations_dnf.phpt @@ -0,0 +1,78 @@ +--TEST-- +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) +--FILE-- +generate( + type: Foo::class, + mockObject: true, + methods: [], + mockClassName: 'MockFoo', +); + +print $mock->classCode(); +--EXPECT-- +declare(strict_types=1); + +class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObjectInternal +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; + + public function bar(): (A&B)|int|null + { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + + $__phpunit_arguments = []; + $__phpunit_count = func_num_args(); + + if (0 !== null && $__phpunit_count > 0) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'Foo', 'bar', $__phpunit_arguments, '(A&B)|int|null', $this + ) + ); + + return $__phpunit_result; + } +} diff --git a/tests/end-to-end/mock-objects/generator/return_type_declarations_false.phpt b/tests/end-to-end/mock-objects/generator/return_type_declarations_false.phpt new file mode 100644 index 00000000000..4ee84e1b33e --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/return_type_declarations_false.phpt @@ -0,0 +1,68 @@ +--TEST-- +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) +--FILE-- +generate( + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', +); + +print $mock->classCode(); +--EXPECT-- +declare(strict_types=1); + +class MockFoo implements PHPUnit\Framework\MockObject\MockObjectInternal, Foo +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; + + public function bar(): false + { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + + $__phpunit_arguments = []; + $__phpunit_count = func_num_args(); + + if (0 !== null && $__phpunit_count > 0) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'Foo', 'bar', $__phpunit_arguments, 'false', $this + ) + ); + + return $__phpunit_result; + } +} diff --git a/tests/end-to-end/mock-objects/generator/return_type_declarations_final.phpt b/tests/end-to-end/mock-objects/generator/return_type_declarations_final.phpt index 2bdd222e0d2..40af8ddc812 100644 --- a/tests/end-to-end/mock-objects/generator/return_type_declarations_final.phpt +++ b/tests/end-to-end/mock-objects/generator/return_type_declarations_final.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', true, true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) --FILE-- generate( - 'Foo', - [], - 'MockFoo', - true, - true + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function bar(): FinalClass { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = []; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 0) { + if (0 !== null && $__phpunit_count > 0) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -49,9 +62,11 @@ class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, 'FinalClass', $this, true + 'Foo', 'bar', $__phpunit_arguments, 'FinalClass', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/return_type_declarations_generator.phpt b/tests/end-to-end/mock-objects/generator/return_type_declarations_generator.phpt index 32b6f41c771..227d95a419e 100644 --- a/tests/end-to-end/mock-objects/generator/return_type_declarations_generator.phpt +++ b/tests/end-to-end/mock-objects/generator/return_type_declarations_generator.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', true, true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) --FILE-- generate( - 'Foo', - [], - 'MockFoo', - true, - true + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo implements PHPUnit\Framework\MockObject\MockObject, Foo +class MockFoo implements PHPUnit\Framework\MockObject\MockObjectInternal, Foo { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function bar(): Generator { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = []; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 0) { + if (0 !== null && $__phpunit_count > 0) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -42,9 +55,11 @@ class MockFoo implements PHPUnit\Framework\MockObject\MockObject, Foo } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, 'Generator', $this, true + 'Foo', 'bar', $__phpunit_arguments, 'Generator', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/return_type_declarations_generator_empty_by_default.phpt b/tests/end-to-end/mock-objects/generator/return_type_declarations_generator_empty_by_default.phpt new file mode 100644 index 00000000000..5797c8711a0 --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/return_type_declarations_generator_empty_by_default.phpt @@ -0,0 +1,34 @@ +--TEST-- +Iterable return types should return empty array by default +--SKIPIF-- +testDouble( + type: 'Foo', + mockObject: false, +); + +var_dump(iterator_to_array($mock->forTraversable())); +var_dump(iterator_to_array($mock->forGenerator())); +var_dump(iterator_to_array($mock->forIterable())); +--EXPECT-- +array(0) { +} +array(0) { +} +array(0) { +} diff --git a/tests/end-to-end/mock-objects/generator/return_type_declarations_intersection.phpt b/tests/end-to-end/mock-objects/generator/return_type_declarations_intersection.phpt new file mode 100644 index 00000000000..f4695d45944 --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/return_type_declarations_intersection.phpt @@ -0,0 +1,78 @@ +--TEST-- +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) +--FILE-- +generate( + type: Foo::class, + mockObject: true, + methods: [], + mockClassName: 'MockFoo', +); + +print $mock->classCode(); +--EXPECT-- +declare(strict_types=1); + +class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObjectInternal +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; + + public function bar(): AnInterface&AnotherInterface + { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + + $__phpunit_arguments = []; + $__phpunit_count = func_num_args(); + + if (0 !== null && $__phpunit_count > 0) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'Foo', 'bar', $__phpunit_arguments, 'AnInterface&AnotherInterface', $this + ) + ); + + return $__phpunit_result; + } +} diff --git a/tests/end-to-end/mock-objects/generator/return_type_declarations_never.phpt b/tests/end-to-end/mock-objects/generator/return_type_declarations_never.phpt new file mode 100644 index 00000000000..5d2512f9f8d --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/return_type_declarations_never.phpt @@ -0,0 +1,66 @@ +--TEST-- +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) +--FILE-- +generate( + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', +); + +print $mock->classCode(); +--EXPECT-- +declare(strict_types=1); + +class MockFoo implements PHPUnit\Framework\MockObject\MockObjectInternal, Foo +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; + + public function bar(string $baz): never + { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + + $__phpunit_arguments = [$baz]; + $__phpunit_count = func_num_args(); + + if (1 !== null && $__phpunit_count > 1) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'Foo', 'bar', $__phpunit_arguments, 'never', $this + ) + ); + } +} diff --git a/tests/end-to-end/mock-objects/generator/return_type_declarations_null.phpt b/tests/end-to-end/mock-objects/generator/return_type_declarations_null.phpt new file mode 100644 index 00000000000..f783b4da419 --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/return_type_declarations_null.phpt @@ -0,0 +1,68 @@ +--TEST-- +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) +--FILE-- +generate( + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', +); + +print $mock->classCode(); +--EXPECT-- +declare(strict_types=1); + +class MockFoo implements PHPUnit\Framework\MockObject\MockObjectInternal, Foo +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; + + public function bar(): null + { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + + $__phpunit_arguments = []; + $__phpunit_count = func_num_args(); + + if (0 !== null && $__phpunit_count > 0) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'Foo', 'bar', $__phpunit_arguments, 'null', $this + ) + ); + + return $__phpunit_result; + } +} diff --git a/tests/end-to-end/mock-objects/generator/return_type_declarations_nullable.phpt b/tests/end-to-end/mock-objects/generator/return_type_declarations_nullable.phpt index c06b895677b..c6b2d15c63b 100644 --- a/tests/end-to-end/mock-objects/generator/return_type_declarations_nullable.phpt +++ b/tests/end-to-end/mock-objects/generator/return_type_declarations_nullable.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', true, true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) --FILE-- generate( - 'Foo', - [], - 'MockFoo', - true, - true + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo implements PHPUnit\Framework\MockObject\MockObject, Foo +class MockFoo implements PHPUnit\Framework\MockObject\MockObjectInternal, Foo { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function bar(string $baz): ?string { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$baz]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -42,9 +55,11 @@ class MockFoo implements PHPUnit\Framework\MockObject\MockObject, Foo } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '?string', $this, true + 'Foo', 'bar', $__phpunit_arguments, '?string', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/return_type_declarations_object_method.phpt b/tests/end-to-end/mock-objects/generator/return_type_declarations_object_method.phpt index 994db4f0372..12e368abbe7 100644 --- a/tests/end-to-end/mock-objects/generator/return_type_declarations_object_method.phpt +++ b/tests/end-to-end/mock-objects/generator/return_type_declarations_object_method.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', true, true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) --FILE-- generate( - 'Foo', - [], - 'MockFoo', - true, - true + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function bar(string $baz): Bar { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$baz]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -45,9 +58,11 @@ class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, 'Bar', $this, true + 'Foo', 'bar', $__phpunit_arguments, 'Bar', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/return_type_declarations_parent.phpt b/tests/end-to-end/mock-objects/generator/return_type_declarations_parent.phpt index 98965dd660b..35f8cfc00af 100644 --- a/tests/end-to-end/mock-objects/generator/return_type_declarations_parent.phpt +++ b/tests/end-to-end/mock-objects/generator/return_type_declarations_parent.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Bar', [], 'MockBar', true, true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Bar', [], 'MockBar', true, true) --FILE-- generate( - 'Bar', - [], - 'MockBar', - true, - true + type: 'Bar', + mockObject: true, + methods: [], + mockClassName: 'MockBar', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockBar extends Bar implements PHPUnit\Framework\MockObject\MockObject +class MockBar extends Bar implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function baz(): Foo { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = []; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 0) { + if (0 !== null && $__phpunit_count > 0) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -49,9 +62,11 @@ class MockBar extends Bar implements PHPUnit\Framework\MockObject\MockObject } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Bar', 'baz', $__phpunit_arguments, 'Foo', $this, true + 'Bar', 'baz', $__phpunit_arguments, 'Foo', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/return_type_declarations_self.phpt b/tests/end-to-end/mock-objects/generator/return_type_declarations_self.phpt index eb4415e30cf..53263bb948f 100644 --- a/tests/end-to-end/mock-objects/generator/return_type_declarations_self.phpt +++ b/tests/end-to-end/mock-objects/generator/return_type_declarations_self.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', true, true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) --FILE-- generate( - 'Foo', - [], - 'MockFoo', - true, - true + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo implements PHPUnit\Framework\MockObject\MockObject, Foo +class MockFoo implements PHPUnit\Framework\MockObject\MockObjectInternal, Foo { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function bar(string $baz): Foo { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$baz]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -42,9 +55,11 @@ class MockFoo implements PHPUnit\Framework\MockObject\MockObject, Foo } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, 'Foo', $this, true + 'Foo', 'bar', $__phpunit_arguments, 'Foo', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/return_type_declarations_static.phpt b/tests/end-to-end/mock-objects/generator/return_type_declarations_static.phpt new file mode 100644 index 00000000000..e817c58445f --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/return_type_declarations_static.phpt @@ -0,0 +1,152 @@ +--TEST-- +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) +--FILE-- +generate( + type: 'ClassWithStaticReturnTypes', + mockObject: true, + methods: [], + mockClassName: 'MockClassWithStaticReturnTypes', +); + +print $mock->classCode(); +--EXPECT-- +declare(strict_types=1); + +class MockClassWithStaticReturnTypes extends ClassWithStaticReturnTypes implements PHPUnit\Framework\MockObject\MockObjectInternal +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; + + public function returnsStatic(): static + { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + + $__phpunit_arguments = []; + $__phpunit_count = func_num_args(); + + if (0 !== null && $__phpunit_count > 0) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'ClassWithStaticReturnTypes', 'returnsStatic', $__phpunit_arguments, 'static', $this + ) + ); + + return $__phpunit_result; + } + + public function returnsStaticOrNull(): ?static + { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + + $__phpunit_arguments = []; + $__phpunit_count = func_num_args(); + + if (0 !== null && $__phpunit_count > 0) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'ClassWithStaticReturnTypes', 'returnsStaticOrNull', $__phpunit_arguments, '?static', $this + ) + ); + + return $__phpunit_result; + } + + public function returnsUnionWithStatic(): static|stdClass + { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + + $__phpunit_arguments = []; + $__phpunit_count = func_num_args(); + + if (0 !== null && $__phpunit_count > 0) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'ClassWithStaticReturnTypes', 'returnsUnionWithStatic', $__phpunit_arguments, 'static|stdClass', $this + ) + ); + + return $__phpunit_result; + } +} diff --git a/tests/end-to-end/mock-objects/generator/return_type_declarations_static_method.phpt b/tests/end-to-end/mock-objects/generator/return_type_declarations_static_method.phpt index 6ea9a438b05..cc4beeeadf2 100644 --- a/tests/end-to-end/mock-objects/generator/return_type_declarations_static_method.phpt +++ b/tests/end-to-end/mock-objects/generator/return_type_declarations_static_method.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', true, true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) --FILE-- generate( - 'Foo', - [], - 'MockFoo', - true, - true + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public static function bar(string $baz): Bar { diff --git a/tests/end-to-end/mock-objects/generator/return_type_declarations_true.phpt b/tests/end-to-end/mock-objects/generator/return_type_declarations_true.phpt new file mode 100644 index 00000000000..aca19906fee --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/return_type_declarations_true.phpt @@ -0,0 +1,68 @@ +--TEST-- +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) +--FILE-- +generate( + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', +); + +print $mock->classCode(); +--EXPECT-- +declare(strict_types=1); + +class MockFoo implements PHPUnit\Framework\MockObject\MockObjectInternal, Foo +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; + + public function bar(): true + { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + + $__phpunit_arguments = []; + $__phpunit_count = func_num_args(); + + if (0 !== null && $__phpunit_count > 0) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'Foo', 'bar', $__phpunit_arguments, 'true', $this + ) + ); + + return $__phpunit_result; + } +} diff --git a/tests/end-to-end/mock-objects/generator/return_type_declarations_union.phpt b/tests/end-to-end/mock-objects/generator/return_type_declarations_union.phpt new file mode 100644 index 00000000000..ed575f56c85 --- /dev/null +++ b/tests/end-to-end/mock-objects/generator/return_type_declarations_union.phpt @@ -0,0 +1,70 @@ +--TEST-- +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) +--FILE-- +generate( + type: Foo::class, + mockObject: true, + methods: [], + mockClassName: 'MockFoo', +); + +print $mock->classCode(); +--EXPECT-- +declare(strict_types=1); + +class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObjectInternal +{ + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; + + public function bar(): bool|int + { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + + $__phpunit_arguments = []; + $__phpunit_count = func_num_args(); + + if (0 !== null && $__phpunit_count > 0) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'Foo', 'bar', $__phpunit_arguments, 'bool|int', $this + ) + ); + + return $__phpunit_result; + } +} diff --git a/tests/end-to-end/mock-objects/generator/return_type_declarations_void.phpt b/tests/end-to-end/mock-objects/generator/return_type_declarations_void.phpt index 45878ab5370..cf007f95a16 100644 --- a/tests/end-to-end/mock-objects/generator/return_type_declarations_void.phpt +++ b/tests/end-to-end/mock-objects/generator/return_type_declarations_void.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', true, true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) --FILE-- generate( - 'Foo', - [], - 'MockFoo', - true, - true + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo implements PHPUnit\Framework\MockObject\MockObject, Foo +class MockFoo implements PHPUnit\Framework\MockObject\MockObjectInternal, Foo { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function bar(string $baz): void { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$baz]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -42,9 +55,11 @@ class MockFoo implements PHPUnit\Framework\MockObject\MockObject, Foo } } - $this->__phpunit_getInvocationHandler()->invoke( + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, 'void', $this, true + 'Foo', 'bar', $__phpunit_arguments, 'void', $this ) ); } diff --git a/tests/end-to-end/mock-objects/generator/scalar_type_declarations.phpt b/tests/end-to-end/mock-objects/generator/scalar_type_declarations.phpt index c0c0642e116..b3203db2ea9 100644 --- a/tests/end-to-end/mock-objects/generator/scalar_type_declarations.phpt +++ b/tests/end-to-end/mock-objects/generator/scalar_type_declarations.phpt @@ -1,5 +1,5 @@ --TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', true, true) +\PHPUnit\Framework\MockObject\Generator\Generator::generate('Foo', [], 'MockFoo', true, true) --FILE-- generate( - 'Foo', - [], - 'MockFoo', - true, - true + type: 'Foo', + mockObject: true, + methods: [], + mockClassName: 'MockFoo', ); -print $mock->getClassCode(); ---EXPECTF-- +print $mock->classCode(); +--EXPECT-- declare(strict_types=1); -class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject +class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObjectInternal { - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; + use PHPUnit\Framework\MockObject\StubApi; + use PHPUnit\Framework\MockObject\MockObjectApi; + use PHPUnit\Framework\MockObject\Method; + use PHPUnit\Framework\MockObject\DoubledCloneMethod; public function bar(string $baz) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$baz]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -44,9 +57,11 @@ class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, true + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/generator/union_type_parameter.phpt b/tests/end-to-end/mock-objects/generator/union_type_parameter.phpt deleted file mode 100644 index 1244fe53cdb..00000000000 --- a/tests/end-to-end/mock-objects/generator/union_type_parameter.phpt +++ /dev/null @@ -1,60 +0,0 @@ ---TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', true, true) ---SKIPIF-- -generate( - Foo::class, - [], - 'MockFoo', - true, - true -); - -print $mock->getClassCode(); ---EXPECTF-- -declare(strict_types=1); - -class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject -{ - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; - - public function bar(int|bool $baz) - { - $__phpunit_arguments = [$baz]; - $__phpunit_count = func_num_args(); - - if ($__phpunit_count > 1) { - $__phpunit_arguments_tmp = func_get_args(); - - for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { - $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; - } - } - - $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( - new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, true - ) - ); - - return $__phpunit_result; - } -} diff --git a/tests/end-to-end/mock-objects/generator/union_type_return.phpt b/tests/end-to-end/mock-objects/generator/union_type_return.phpt deleted file mode 100644 index 90baa473c4c..00000000000 --- a/tests/end-to-end/mock-objects/generator/union_type_return.phpt +++ /dev/null @@ -1,60 +0,0 @@ ---TEST-- -\PHPUnit\Framework\MockObject\Generator::generate('Foo', [], 'MockFoo', true, true) ---SKIPIF-- -generate( - Foo::class, - [], - 'MockFoo', - true, - true -); - -print $mock->getClassCode(); ---EXPECTF-- -declare(strict_types=1); - -class MockFoo extends Foo implements PHPUnit\Framework\MockObject\MockObject -{ - use \PHPUnit\Framework\MockObject\Api; - use \PHPUnit\Framework\MockObject\Method; - use \PHPUnit\Framework\MockObject\MockedCloneMethod; - - public function bar(): bool|int - { - $__phpunit_arguments = []; - $__phpunit_count = func_num_args(); - - if ($__phpunit_count > 0) { - $__phpunit_arguments_tmp = func_get_args(); - - for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { - $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; - } - } - - $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( - new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, 'bool|int', $this, true - ) - ); - - return $__phpunit_result; - } -} diff --git a/tests/end-to-end/mock-objects/generator/wsdl_class.phpt b/tests/end-to-end/mock-objects/generator/wsdl_class.phpt deleted file mode 100644 index 2438b41c17d..00000000000 --- a/tests/end-to-end/mock-objects/generator/wsdl_class.phpt +++ /dev/null @@ -1,37 +0,0 @@ ---TEST-- -\PHPUnit\Framework\MockObject\Generator::generateClassFromWsdl('GoogleSearch.wsdl', 'GoogleSearch') ---SKIPIF-- -generateClassFromWsdl( - __DIR__ . '/../../../_files/GoogleSearch.wsdl', - 'GoogleSearch' -); ---EXPECTF-- -declare(strict_types=1); - -class GoogleSearch extends \SoapClient -{ - public function __construct($wsdl, array $options) - { - parent::__construct('%s/GoogleSearch.wsdl', $options); - } - - public function doGoogleSearch($key, $q, $start, $maxResults, $filter, $restrict, $safeSearch, $lr, $ie, $oe) - { - } - - public function doGetCachedPage($key, $url) - { - } - - public function doSpellingSuggestion($key, $phrase) - { - } -} diff --git a/tests/end-to-end/mock-objects/generator/wsdl_class_namespace.phpt b/tests/end-to-end/mock-objects/generator/wsdl_class_namespace.phpt deleted file mode 100644 index b7821ec994e..00000000000 --- a/tests/end-to-end/mock-objects/generator/wsdl_class_namespace.phpt +++ /dev/null @@ -1,39 +0,0 @@ ---TEST-- -\PHPUnit\Framework\MockObject\Generator::generateClassFromWsdl('GoogleSearch.wsdl', 'GoogleSearch') ---SKIPIF-- -generateClassFromWsdl( - __DIR__ . '/../../../_files/GoogleSearch.wsdl', - 'My\\Space\\GoogleSearch' -); ---EXPECTF-- -declare(strict_types=1); - -namespace My\Space; - -class GoogleSearch extends \SoapClient -{ - public function __construct($wsdl, array $options) - { - parent::__construct('%s/GoogleSearch.wsdl', $options); - } - - public function doGoogleSearch($key, $q, $start, $maxResults, $filter, $restrict, $safeSearch, $lr, $ie, $oe) - { - } - - public function doGetCachedPage($key, $url) - { - } - - public function doSpellingSuggestion($key, $phrase) - { - } -} diff --git a/tests/end-to-end/mock-objects/generator/wsdl_class_partial.phpt b/tests/end-to-end/mock-objects/generator/wsdl_class_partial.phpt deleted file mode 100644 index 0c6c5c3b08b..00000000000 --- a/tests/end-to-end/mock-objects/generator/wsdl_class_partial.phpt +++ /dev/null @@ -1,30 +0,0 @@ ---TEST-- -\PHPUnit\Framework\MockObject\Generator::generateClassFromWsdl('GoogleSearch.wsdl', 'GoogleSearch', array('doGoogleSearch')) ---SKIPIF-- -generateClassFromWsdl( - __DIR__ . '/../../../_files/GoogleSearch.wsdl', - 'GoogleSearch', - array('doGoogleSearch') -); ---EXPECTF-- -declare(strict_types=1); - -class GoogleSearch extends \SoapClient -{ - public function __construct($wsdl, array $options) - { - parent::__construct('%s/GoogleSearch.wsdl', $options); - } - - public function doGoogleSearch($key, $q, $start, $maxResults, $filter, $restrict, $safeSearch, $lr, $ie, $oe) - { - } -} diff --git a/tests/end-to-end/mock-objects/mock-method/call_original.phpt b/tests/end-to-end/mock-objects/mock-method/call_original.phpt deleted file mode 100644 index e5d3cd5735a..00000000000 --- a/tests/end-to-end/mock-objects/mock-method/call_original.phpt +++ /dev/null @@ -1,44 +0,0 @@ ---TEST-- -Mock method and call original method ---FILE-- -getMethod('bar'), - true, - false -); - -$code = $mockMethod->generateCode(); - -print $code; ---EXPECT-- - - public function bar() - { - $__phpunit_arguments = []; - $__phpunit_count = func_num_args(); - - if ($__phpunit_count > 0) { - $__phpunit_arguments_tmp = func_get_args(); - - for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { - $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; - } - } - - $this->__phpunit_getInvocationHandler()->invoke( - new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, false, true - ) - ); - - return call_user_func_array(array($this->__phpunit_originalObject, "bar"), $__phpunit_arguments); - } diff --git a/tests/end-to-end/mock-objects/mock-method/call_original_with_argument.phpt b/tests/end-to-end/mock-objects/mock-method/call_original_with_argument.phpt deleted file mode 100644 index 0906561c00b..00000000000 --- a/tests/end-to-end/mock-objects/mock-method/call_original_with_argument.phpt +++ /dev/null @@ -1,44 +0,0 @@ ---TEST-- -Mock method and call original method with argument ---FILE-- -getMethod('bar'), - true, - false -); - -$code = $mockMethod->generateCode(); - -print $code; ---EXPECT-- - -private function bar($arg) - { - $__phpunit_arguments = [$arg]; - $__phpunit_count = func_num_args(); - - if ($__phpunit_count > 1) { - $__phpunit_arguments_tmp = func_get_args(); - - for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { - $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; - } - } - - $this->__phpunit_getInvocationHandler()->invoke( - new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, false, true - ) - ); - - return call_user_func_array(array($this->__phpunit_originalObject, "bar"), $__phpunit_arguments); - } diff --git a/tests/end-to-end/mock-objects/mock-method/call_original_with_argument_variadic.phpt b/tests/end-to-end/mock-objects/mock-method/call_original_with_argument_variadic.phpt deleted file mode 100644 index f2a921c347f..00000000000 --- a/tests/end-to-end/mock-objects/mock-method/call_original_with_argument_variadic.phpt +++ /dev/null @@ -1,44 +0,0 @@ ---TEST-- -Mock method and call original method with variadic argument ---FILE-- -getMethod('bar'), - true, - false -); - -$code = $mockMethod->generateCode(); - -print $code; ---EXPECT-- - -private function bar(...$args) - { - $__phpunit_arguments = []; - $__phpunit_count = func_num_args(); - - if ($__phpunit_count > 0) { - $__phpunit_arguments_tmp = func_get_args(); - - for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { - $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; - } - } - - $this->__phpunit_getInvocationHandler()->invoke( - new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, false, true - ) - ); - - return call_user_func_array(array($this->__phpunit_originalObject, "bar"), $__phpunit_arguments); - } diff --git a/tests/end-to-end/mock-objects/mock-method/call_original_with_return_type_void.phpt b/tests/end-to-end/mock-objects/mock-method/call_original_with_return_type_void.phpt deleted file mode 100644 index b07ce28222e..00000000000 --- a/tests/end-to-end/mock-objects/mock-method/call_original_with_return_type_void.phpt +++ /dev/null @@ -1,44 +0,0 @@ ---TEST-- -Mock method and call original method that has a return type of void ---FILE-- -getMethod('bar'), - true, - false -); - -$code = $mockMethod->generateCode(); - -print $code; ---EXPECTF-- - - public function bar(): void - { - $__phpunit_arguments = []; - $__phpunit_count = func_num_args(); - - if ($__phpunit_count > 0) { - $__phpunit_arguments_tmp = func_get_args(); - - for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { - $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; - } - } - - $this->__phpunit_getInvocationHandler()->invoke( - new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, 'void', $this, false, true - ) - ); - - call_user_func_array(array($this->__phpunit_originalObject, "bar"), $__phpunit_arguments); - } diff --git a/tests/end-to-end/mock-objects/mock-method/clone_method_arguments.phpt b/tests/end-to-end/mock-objects/mock-method/clone_method_arguments.phpt deleted file mode 100644 index 5054a483821..00000000000 --- a/tests/end-to-end/mock-objects/mock-method/clone_method_arguments.phpt +++ /dev/null @@ -1,44 +0,0 @@ ---TEST-- -Mock method and clone method arguments ---FILE-- -getMethod('bar'), - false, - true -); - -$code = $mockMethod->generateCode(); - -print $code; ---EXPECT-- - - public function bar() - { - $__phpunit_arguments = []; - $__phpunit_count = func_num_args(); - - if ($__phpunit_count > 0) { - $__phpunit_arguments_tmp = func_get_args(); - - for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { - $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; - } - } - - $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( - new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, true - ) - ); - - return $__phpunit_result; - } diff --git a/tests/end-to-end/mock-objects/mock-method/deprecated_with_description.phpt b/tests/end-to-end/mock-objects/mock-method/deprecated_with_description.phpt index 78427148a98..f74e48c11d5 100644 --- a/tests/end-to-end/mock-objects/mock-method/deprecated_with_description.phpt +++ b/tests/end-to-end/mock-objects/mock-method/deprecated_with_description.phpt @@ -10,10 +10,10 @@ class Foo public function bar(){} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false @@ -28,10 +28,23 @@ public function bar() { @trigger_error('The Foo::bar method is deprecated (some explanation).', E_USER_DEPRECATED); + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = []; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 0) { + if (0 !== null && $__phpunit_count > 0) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -39,9 +52,11 @@ public function bar() } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, false + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/mock-method/deprecated_without_description.phpt b/tests/end-to-end/mock-objects/mock-method/deprecated_without_description.phpt index 225f3c1d39f..0b6435cd139 100644 --- a/tests/end-to-end/mock-objects/mock-method/deprecated_without_description.phpt +++ b/tests/end-to-end/mock-objects/mock-method/deprecated_without_description.phpt @@ -10,10 +10,10 @@ class Foo public function bar(){} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false @@ -28,10 +28,23 @@ public function bar() { @trigger_error('The Foo::bar method is deprecated ().', E_USER_DEPRECATED); + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = []; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 0) { + if (0 !== null && $__phpunit_count > 0) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -39,9 +52,11 @@ public function bar() } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, false + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/mock-method/private_method.phpt b/tests/end-to-end/mock-objects/mock-method/private_method.phpt index 00f00ec6f03..dd529809c15 100644 --- a/tests/end-to-end/mock-objects/mock-method/private_method.phpt +++ b/tests/end-to-end/mock-objects/mock-method/private_method.phpt @@ -7,10 +7,10 @@ class Foo private function bar(){} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false @@ -23,10 +23,23 @@ print $code; private function bar() { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = []; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 0) { + if (0 !== null && $__phpunit_count > 0) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -34,9 +47,11 @@ private function bar() } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, false + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/mock-method/protected_method.phpt b/tests/end-to-end/mock-objects/mock-method/protected_method.phpt index 24456b3f872..1d60f29750b 100644 --- a/tests/end-to-end/mock-objects/mock-method/protected_method.phpt +++ b/tests/end-to-end/mock-objects/mock-method/protected_method.phpt @@ -7,10 +7,10 @@ class Foo protected function bar(){} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false @@ -23,10 +23,23 @@ print $code; protected function bar() { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = []; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 0) { + if (0 !== null && $__phpunit_count > 0) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -34,9 +47,11 @@ protected function bar() } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, false + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/mock-method/return_by_reference.phpt b/tests/end-to-end/mock-objects/mock-method/return_by_reference.phpt index 6a81e7e51e3..33da1456e9a 100644 --- a/tests/end-to-end/mock-objects/mock-method/return_by_reference.phpt +++ b/tests/end-to-end/mock-objects/mock-method/return_by_reference.phpt @@ -7,10 +7,10 @@ class Foo public function &bar(){} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false @@ -23,10 +23,23 @@ print $code; public function &bar() { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = []; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 0) { + if (0 !== null && $__phpunit_count > 0) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -34,9 +47,11 @@ public function &bar() } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, false + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/mock-method/return_by_reference_with_return_type.phpt b/tests/end-to-end/mock-objects/mock-method/return_by_reference_with_return_type.phpt index e89d8169573..244af33f2e9 100644 --- a/tests/end-to-end/mock-objects/mock-method/return_by_reference_with_return_type.phpt +++ b/tests/end-to-end/mock-objects/mock-method/return_by_reference_with_return_type.phpt @@ -7,10 +7,10 @@ class Foo public function &bar(): string{} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false @@ -23,10 +23,23 @@ print $code; public function &bar(): string { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = []; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 0) { + if (0 !== null && $__phpunit_count > 0) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -34,9 +47,11 @@ public function &bar(): string } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, 'string', $this, false + 'Foo', 'bar', $__phpunit_arguments, 'string', $this ) ); diff --git a/tests/end-to-end/mock-objects/mock-method/return_type.phpt b/tests/end-to-end/mock-objects/mock-method/return_type.phpt index 1043d8b1ab6..b40628173a5 100644 --- a/tests/end-to-end/mock-objects/mock-method/return_type.phpt +++ b/tests/end-to-end/mock-objects/mock-method/return_type.phpt @@ -7,10 +7,10 @@ class Foo public function bar(): string{} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false @@ -23,10 +23,23 @@ print $code; public function bar(): string { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = []; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 0) { + if (0 !== null && $__phpunit_count > 0) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -34,9 +47,11 @@ public function bar(): string } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, 'string', $this, false + 'Foo', 'bar', $__phpunit_arguments, 'string', $this ) ); diff --git a/tests/end-to-end/mock-objects/mock-method/return_type_parent.phpt b/tests/end-to-end/mock-objects/mock-method/return_type_parent.phpt index d84bce290f1..8538678d3c9 100644 --- a/tests/end-to-end/mock-objects/mock-method/return_type_parent.phpt +++ b/tests/end-to-end/mock-objects/mock-method/return_type_parent.phpt @@ -11,10 +11,10 @@ class Foo extends Baz public function bar(): parent {} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false @@ -27,10 +27,23 @@ print $code; public function bar(): Baz { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = []; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 0) { + if (0 !== null && $__phpunit_count > 0) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -38,9 +51,11 @@ public function bar(): Baz } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, 'Baz', $this, false + 'Foo', 'bar', $__phpunit_arguments, 'Baz', $this ) ); diff --git a/tests/end-to-end/mock-objects/mock-method/return_type_self.phpt b/tests/end-to-end/mock-objects/mock-method/return_type_self.phpt index 9a80e2fd20a..a7661a9cb48 100644 --- a/tests/end-to-end/mock-objects/mock-method/return_type_self.phpt +++ b/tests/end-to-end/mock-objects/mock-method/return_type_self.phpt @@ -7,10 +7,10 @@ class Foo public function bar(): self{} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false @@ -23,10 +23,23 @@ print $code; public function bar(): Foo { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = []; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 0) { + if (0 !== null && $__phpunit_count > 0) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -34,9 +47,11 @@ public function bar(): Foo } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, 'Foo', $this, false + 'Foo', 'bar', $__phpunit_arguments, 'Foo', $this ) ); diff --git a/tests/end-to-end/mock-objects/mock-method/static_method.phpt b/tests/end-to-end/mock-objects/mock-method/static_method.phpt index bb9e9080cda..4f975ed29c4 100644 --- a/tests/end-to-end/mock-objects/mock-method/static_method.phpt +++ b/tests/end-to-end/mock-objects/mock-method/static_method.phpt @@ -7,10 +7,10 @@ class Foo public static function bar(){} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false diff --git a/tests/end-to-end/mock-objects/mock-method/static_method_with_return_type.phpt b/tests/end-to-end/mock-objects/mock-method/static_method_with_return_type.phpt index 4ac7552afca..13f014722e7 100644 --- a/tests/end-to-end/mock-objects/mock-method/static_method_with_return_type.phpt +++ b/tests/end-to-end/mock-objects/mock-method/static_method_with_return_type.phpt @@ -7,10 +7,10 @@ class Foo public static function bar(): bool{} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false diff --git a/tests/end-to-end/mock-objects/mock-method/with_argument.phpt b/tests/end-to-end/mock-objects/mock-method/with_argument.phpt index 213f11301cf..aab00186b9a 100644 --- a/tests/end-to-end/mock-objects/mock-method/with_argument.phpt +++ b/tests/end-to-end/mock-objects/mock-method/with_argument.phpt @@ -7,10 +7,10 @@ class Foo private function bar($arg){} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false @@ -23,10 +23,23 @@ print $code; private function bar($arg) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$arg]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -34,9 +47,11 @@ private function bar($arg) } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, false + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/mock-method/with_argument_default.phpt b/tests/end-to-end/mock-objects/mock-method/with_argument_default.phpt index 280703705ae..46e02d5be56 100644 --- a/tests/end-to-end/mock-objects/mock-method/with_argument_default.phpt +++ b/tests/end-to-end/mock-objects/mock-method/with_argument_default.phpt @@ -7,10 +7,10 @@ class Foo private function bar($arg = false){} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false @@ -23,10 +23,23 @@ print $code; private function bar($arg = false) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$arg]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -34,9 +47,11 @@ private function bar($arg = false) } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, false + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/mock-method/with_argument_default_constant.phpt b/tests/end-to-end/mock-objects/mock-method/with_argument_default_constant.phpt index b1dbbe3e5e5..d27fcff2fc2 100644 --- a/tests/end-to-end/mock-objects/mock-method/with_argument_default_constant.phpt +++ b/tests/end-to-end/mock-objects/mock-method/with_argument_default_constant.phpt @@ -12,10 +12,10 @@ class Foo private function bar($a = GLOBAL_CONSTANT, $b = self::CLASS_CONSTANT_PUBLIC, $c = self::CLASS_CONSTANT_PROTECTED, $d = self::CLASS_CONSTANT_PRIVATE){} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false @@ -28,10 +28,23 @@ print $code; private function bar($a = 1, $b = 2, $c = 3, $d = 4) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$a, $b, $c, $d]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 4) { + if (4 !== null && $__phpunit_count > 4) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 4; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -39,9 +52,11 @@ private function bar($a = 1, $b = 2, $c = 3, $d = 4) } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, false + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/mock-method/with_argument_default_new_expression.phpt b/tests/end-to-end/mock-objects/mock-method/with_argument_default_new_expression.phpt new file mode 100644 index 00000000000..1960713225b --- /dev/null +++ b/tests/end-to-end/mock-objects/mock-method/with_argument_default_new_expression.phpt @@ -0,0 +1,66 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/4929 +--FILE-- +getMethod('method'), + false, + false +); + +$code = $mockMethod->generateCode(); + +print $code; +--EXPECT-- + +public function method(Foo $foo = new \Foo(1, 2, 3)) + { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + + $__phpunit_arguments = [$foo]; + $__phpunit_count = func_num_args(); + + if (1 !== null && $__phpunit_count > 1) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + 'Bar', 'method', $__phpunit_arguments, '', $this + ) + ); + + return $__phpunit_result; + } diff --git a/tests/end-to-end/mock-objects/mock-method/with_argument_default_null.phpt b/tests/end-to-end/mock-objects/mock-method/with_argument_default_null.phpt index 0c774cf96ee..9c0eeb94f2c 100644 --- a/tests/end-to-end/mock-objects/mock-method/with_argument_default_null.phpt +++ b/tests/end-to-end/mock-objects/mock-method/with_argument_default_null.phpt @@ -7,10 +7,10 @@ class Foo private function bar($arg = null){} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false @@ -23,10 +23,23 @@ print $code; private function bar($arg = NULL) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$arg]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -34,9 +47,11 @@ private function bar($arg = NULL) } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, false + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/mock-method/with_argument_nullable.phpt b/tests/end-to-end/mock-objects/mock-method/with_argument_nullable.phpt index e5871fd31ca..c06ebd8d363 100644 --- a/tests/end-to-end/mock-objects/mock-method/with_argument_nullable.phpt +++ b/tests/end-to-end/mock-objects/mock-method/with_argument_nullable.phpt @@ -7,10 +7,10 @@ class Foo private function bar(?string $arg){} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false @@ -23,10 +23,23 @@ print $code; private function bar(?string $arg) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$arg]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -34,9 +47,11 @@ private function bar(?string $arg) } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, false + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/mock-method/with_argument_reference.phpt b/tests/end-to-end/mock-objects/mock-method/with_argument_reference.phpt index 3fc4a429d95..02d9c799461 100644 --- a/tests/end-to-end/mock-objects/mock-method/with_argument_reference.phpt +++ b/tests/end-to-end/mock-objects/mock-method/with_argument_reference.phpt @@ -7,10 +7,10 @@ class Foo private function bar(&$arg){} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false @@ -23,10 +23,23 @@ print $code; private function bar(&$arg) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [&$arg]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -34,9 +47,11 @@ private function bar(&$arg) } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, false + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/mock-method/with_argument_typed_array.phpt b/tests/end-to-end/mock-objects/mock-method/with_argument_typed_array.phpt index 622e5c20cfb..80856caabfa 100644 --- a/tests/end-to-end/mock-objects/mock-method/with_argument_typed_array.phpt +++ b/tests/end-to-end/mock-objects/mock-method/with_argument_typed_array.phpt @@ -7,10 +7,10 @@ class Foo private function bar(array $arg){} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false @@ -23,10 +23,23 @@ print $code; private function bar(array $arg) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$arg]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -34,9 +47,11 @@ private function bar(array $arg) } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, false + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/mock-method/with_argument_typed_callable.phpt b/tests/end-to-end/mock-objects/mock-method/with_argument_typed_callable.phpt index eb59b96203d..10422b5fd2f 100644 --- a/tests/end-to-end/mock-objects/mock-method/with_argument_typed_callable.phpt +++ b/tests/end-to-end/mock-objects/mock-method/with_argument_typed_callable.phpt @@ -7,10 +7,10 @@ class Foo private function bar(callable $arg){} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false @@ -23,10 +23,23 @@ print $code; private function bar(callable $arg) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$arg]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -34,9 +47,11 @@ private function bar(callable $arg) } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, false + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/mock-method/with_argument_typed_class.phpt b/tests/end-to-end/mock-objects/mock-method/with_argument_typed_class.phpt index fedd32f2d53..da6aee4ec22 100644 --- a/tests/end-to-end/mock-objects/mock-method/with_argument_typed_class.phpt +++ b/tests/end-to-end/mock-objects/mock-method/with_argument_typed_class.phpt @@ -7,10 +7,10 @@ class Foo private function bar(\Exception $arg){} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false @@ -23,10 +23,23 @@ print $code; private function bar(Exception $arg) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$arg]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -34,9 +47,11 @@ private function bar(Exception $arg) } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, false + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/mock-method/with_argument_typed_scalar.phpt b/tests/end-to-end/mock-objects/mock-method/with_argument_typed_scalar.phpt index 43de4ec6fb3..eddbac31dd3 100644 --- a/tests/end-to-end/mock-objects/mock-method/with_argument_typed_scalar.phpt +++ b/tests/end-to-end/mock-objects/mock-method/with_argument_typed_scalar.phpt @@ -7,10 +7,10 @@ class Foo private function bar(string $arg){} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false @@ -23,10 +23,23 @@ print $code; private function bar(string $arg) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$arg]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -34,9 +47,11 @@ private function bar(string $arg) } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, false + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/mock-method/with_argument_typed_self.phpt b/tests/end-to-end/mock-objects/mock-method/with_argument_typed_self.phpt index ed02aba7929..5be3ad94c9a 100644 --- a/tests/end-to-end/mock-objects/mock-method/with_argument_typed_self.phpt +++ b/tests/end-to-end/mock-objects/mock-method/with_argument_typed_self.phpt @@ -7,10 +7,10 @@ class Foo private function bar(self $arg){} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false @@ -23,10 +23,23 @@ print $code; private function bar(Foo $arg) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$arg]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -34,9 +47,11 @@ private function bar(Foo $arg) } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, false + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/mock-method/with_argument_typed_unkown_class.phpt b/tests/end-to-end/mock-objects/mock-method/with_argument_typed_unkown_class.phpt index d27f0fa7088..cbbb4720eab 100644 --- a/tests/end-to-end/mock-objects/mock-method/with_argument_typed_unkown_class.phpt +++ b/tests/end-to-end/mock-objects/mock-method/with_argument_typed_unkown_class.phpt @@ -7,10 +7,10 @@ class Foo private function bar(UnknownClass $arg){} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false @@ -23,10 +23,23 @@ print $code; private function bar(UnknownClass $arg) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$arg]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 1) { + if (1 !== null && $__phpunit_count > 1) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 1; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -34,9 +47,11 @@ private function bar(UnknownClass $arg) } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, false + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/mock-method/with_argument_typed_variadic.phpt b/tests/end-to-end/mock-objects/mock-method/with_argument_typed_variadic.phpt index 214cfee03d5..556bda3a7eb 100644 --- a/tests/end-to-end/mock-objects/mock-method/with_argument_typed_variadic.phpt +++ b/tests/end-to-end/mock-objects/mock-method/with_argument_typed_variadic.phpt @@ -7,10 +7,10 @@ class Foo private function bar(string ...$args){} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false @@ -23,10 +23,23 @@ print $code; private function bar(string ...$args) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = []; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 0) { + if (0 !== null && $__phpunit_count > 0) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -34,9 +47,11 @@ private function bar(string ...$args) } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, false + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/mock-method/with_argument_variadic.phpt b/tests/end-to-end/mock-objects/mock-method/with_argument_variadic.phpt index 713a44b5c01..5cc69f5295d 100644 --- a/tests/end-to-end/mock-objects/mock-method/with_argument_variadic.phpt +++ b/tests/end-to-end/mock-objects/mock-method/with_argument_variadic.phpt @@ -7,10 +7,10 @@ class Foo private function bar(...$args){} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false @@ -23,10 +23,23 @@ print $code; private function bar(...$args) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = []; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 0) { + if (0 !== null && $__phpunit_count > 0) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 0; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -34,9 +47,11 @@ private function bar(...$args) } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, false + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/mock-objects/mock-method/with_arguments.phpt b/tests/end-to-end/mock-objects/mock-method/with_arguments.phpt index b5a43ac739f..0d3827cfa3e 100644 --- a/tests/end-to-end/mock-objects/mock-method/with_arguments.phpt +++ b/tests/end-to-end/mock-objects/mock-method/with_arguments.phpt @@ -7,10 +7,10 @@ class Foo private function bar($arg1, $arg2){} } -require __DIR__ . '/../../../../vendor/autoload.php'; +require_once __DIR__ . '/../../../bootstrap.php'; $class = new ReflectionClass('Foo'); -$mockMethod = \PHPUnit\Framework\MockObject\MockMethod::fromReflection( +$mockMethod = \PHPUnit\Framework\MockObject\Generator\DoubledMethod::fromReflection( $class->getMethod('bar'), false, false @@ -23,10 +23,23 @@ print $code; private function bar($arg1, $arg2) { + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + $__phpunit_arguments = [$arg1, $arg2]; $__phpunit_count = func_num_args(); - if ($__phpunit_count > 2) { + if (2 !== null && $__phpunit_count > 2) { $__phpunit_arguments_tmp = func_get_args(); for ($__phpunit_i = 2; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { @@ -34,9 +47,11 @@ private function bar($arg1, $arg2) } } + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( new \PHPUnit\Framework\MockObject\Invocation( - 'Foo', 'bar', $__phpunit_arguments, '', $this, false + 'Foo', 'bar', $__phpunit_arguments, '', $this ) ); diff --git a/tests/end-to-end/one-class-per-file-valid.phpt b/tests/end-to-end/one-class-per-file-valid.phpt deleted file mode 100644 index 39a0c94d7fd..00000000000 --- a/tests/end-to-end/one-class-per-file-valid.phpt +++ /dev/null @@ -1,19 +0,0 @@ ---TEST-- -phpunit --version ---FILE-- - ---EXPECTF-- -PHPUnit %s by Sebastian Bergmann and contributors. - -. 1 / 1 (100%) - -Time: %s, Memory: %s - -OK (1 test, 1 assertion) diff --git a/tests/end-to-end/output-isolation.phpt b/tests/end-to-end/output-isolation.phpt deleted file mode 100644 index 03a1356265f..00000000000 --- a/tests/end-to-end/output-isolation.phpt +++ /dev/null @@ -1,20 +0,0 @@ ---TEST-- -phpunit --process-isolation --filter testExpectOutputStringFooActualFoo ../../_files/OutputTestCase.php ---FILE-- - + + + + tests/standard + tests/phpt + + + + + + src + + + diff --git a/tests/end-to-end/phar/src/Greeter.php b/tests/end-to-end/phar/src/Greeter.php new file mode 100644 index 00000000000..00dc4a68c8c --- /dev/null +++ b/tests/end-to-end/phar/src/Greeter.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Phar; + +final class Greeter +{ + public function greet(): string + { + return 'Hello world!'; + } +} diff --git a/tests/end-to-end/phar/src/autoload.php b/tests/end-to-end/phar/src/autoload.php new file mode 100644 index 00000000000..c66cf5e50f1 --- /dev/null +++ b/tests/end-to-end/phar/src/autoload.php @@ -0,0 +1,11 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +require_once __DIR__ . '/Greeter.php'; diff --git a/tests/end-to-end/phar/tests/phpt/greeter.phpt b/tests/end-to-end/phar/tests/phpt/greeter.phpt new file mode 100644 index 00000000000..04ddbf36925 --- /dev/null +++ b/tests/end-to-end/phar/tests/phpt/greeter.phpt @@ -0,0 +1,11 @@ +--TEST-- +Greeter +--FILE-- +greet(); +--EXPECT-- +Hello world! diff --git a/tests/end-to-end/phar/tests/standard/GreeterTest.php b/tests/end-to-end/phar/tests/standard/GreeterTest.php new file mode 100644 index 00000000000..136d29cd2ba --- /dev/null +++ b/tests/end-to-end/phar/tests/standard/GreeterTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Phar; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\RunInSeparateProcess; +use PHPUnit\Framework\Attributes\Ticket; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Greeter::class)] +final class GreeterTest extends TestCase +{ + public function testGreets(): void + { + $this->assertSame('Hello world!', (new Greeter)->greet()); + } + + #[RunInSeparateProcess] + #[Ticket('https://github.com/sebastianbergmann/phpunit/issues/4412')] + public function testGreetsInIsolation(): void + { + $this->assertSame('Hello world!', (new Greeter)->greet()); + } +} diff --git a/tests/end-to-end/phar/tests/standard/Issue4399Test.php b/tests/end-to-end/phar/tests/standard/Issue4399Test.php new file mode 100644 index 00000000000..4900d5c2d4d --- /dev/null +++ b/tests/end-to-end/phar/tests/standard/Issue4399Test.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +use PHPUnit\Framework\TestCase; + +final class Issue4399Test extends TestCase +{ + public function testOne(): void + { + $this->assertCount(0, []); + } +} diff --git a/tests/end-to-end/phpt-env.phpt b/tests/end-to-end/phpt-env.phpt deleted file mode 100644 index 57fb13e46f4..00000000000 --- a/tests/end-to-end/phpt-env.phpt +++ /dev/null @@ -1,11 +0,0 @@ ---TEST-- -PHPT runner should support ENV section ---ENV-- -FOO=bar ---FILE-- -run($_SERVER['argv']); --EXPECTF-- PHPUnit %s by Sebastian Bergmann and contributors. diff --git a/tests/end-to-end/phpt/expect-location-hint.phpt b/tests/end-to-end/phpt/expect-location-hint.phpt index 91a16f1cf28..a7dee4ce2c9 100644 --- a/tests/end-to-end/phpt/expect-location-hint.phpt +++ b/tests/end-to-end/phpt/expect-location-hint.phpt @@ -1,16 +1,18 @@ --TEST-- PHPT EXPECT comparison returns correct code location hint +--SKIPIF-- +run($_SERVER['argv']); --EXPECTF-- PHPUnit %s by Sebastian Bergmann and contributors. diff --git a/tests/end-to-end/phpt/parse-ini-env-section.phpt b/tests/end-to-end/phpt/parse-ini-env-section.phpt index 1a5c837cf36..ecd32669e9d 100644 --- a/tests/end-to-end/phpt/parse-ini-env-section.phpt +++ b/tests/end-to-end/phpt/parse-ini-env-section.phpt @@ -1,5 +1,10 @@ --TEST-- Can parse --INI-- and --ENV-- sections +--SKIPIF-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) CLEAN section triggered a parse error: +Parse error: Unmatched '}' in Standard input code on line 3 + + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/phpt/phpt-env.phpt b/tests/end-to-end/phpt/phpt-env.phpt new file mode 100644 index 00000000000..0d43bc9a64e --- /dev/null +++ b/tests/end-to-end/phpt/phpt-env.phpt @@ -0,0 +1,14 @@ +--TEST-- +PHPT runner should support ENV section +--SKIPIF-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +I 1 / 1 (100%) + +Time: %s, Memory: %s + +OK, but there were issues! +Tests: 1, Assertions: 1, Incomplete: 1. diff --git a/tests/end-to-end/phpt/skipif-location-hint.phpt b/tests/end-to-end/phpt/skipif-location-hint.phpt deleted file mode 100644 index c6567bbc272..00000000000 --- a/tests/end-to-end/phpt/skipif-location-hint.phpt +++ /dev/null @@ -1,31 +0,0 @@ ---TEST-- -PHPT skip condition results in correct code location hint ---FILE-- -run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +1.2. 2 / 2 (100%) + +Time: %s, Memory: %s + +OK (2 tests, 2 assertions) diff --git a/tests/end-to-end/regression/GitHub/1149/Issue1149Test.php b/tests/end-to-end/regression/1149/Issue1149Test.php similarity index 82% rename from tests/end-to-end/regression/GitHub/1149/Issue1149Test.php rename to tests/end-to-end/regression/1149/Issue1149Test.php index 62bdb0bc99d..e5eb518bf91 100644 --- a/tests/end-to-end/regression/GitHub/1149/Issue1149Test.php +++ b/tests/end-to-end/regression/1149/Issue1149Test.php @@ -7,6 +7,9 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\RunInSeparateProcess; use PHPUnit\Framework\TestCase; class Issue1149Test extends TestCase @@ -17,9 +20,7 @@ public function testOne(): void print '1'; } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testTwo(): void { $this->assertTrue(true); diff --git a/tests/end-to-end/regression/1335.phpt b/tests/end-to-end/regression/1335.phpt new file mode 100644 index 00000000000..374fb70153b --- /dev/null +++ b/tests/end-to-end/regression/1335.phpt @@ -0,0 +1,22 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/1335 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +............ 12 / 12 (100%) + +Time: %s, Memory: %s + +OK (12 tests, 12 assertions) diff --git a/tests/end-to-end/regression/GitHub/1335/Issue1335Test.php b/tests/end-to-end/regression/1335/Issue1335Test.php similarity index 90% rename from tests/end-to-end/regression/GitHub/1335/Issue1335Test.php rename to tests/end-to-end/regression/1335/Issue1335Test.php index 4a77cf5c4a3..d706d44d98c 100644 --- a/tests/end-to-end/regression/GitHub/1335/Issue1335Test.php +++ b/tests/end-to-end/regression/1335/Issue1335Test.php @@ -7,12 +7,14 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\PreserveGlobalState; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; use PHPUnit\Framework\TestCase; -/** - * @runTestsInSeparateProcesses - * @preserveGlobalState enabled - */ +#[RunTestsInSeparateProcesses] +#[PreserveGlobalState(true)] class Issue1335Test extends TestCase { public function testGlobalString(): void diff --git a/tests/end-to-end/regression/GitHub/1335/bootstrap1335.php b/tests/end-to-end/regression/1335/bootstrap1335.php similarity index 100% rename from tests/end-to-end/regression/GitHub/1335/bootstrap1335.php rename to tests/end-to-end/regression/1335/bootstrap1335.php diff --git a/tests/end-to-end/regression/1337.phpt b/tests/end-to-end/regression/1337.phpt new file mode 100644 index 00000000000..5b39de21008 --- /dev/null +++ b/tests/end-to-end/regression/1337.phpt @@ -0,0 +1,21 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/1337 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/regression/1337/Issue1337Test.php b/tests/end-to-end/regression/1337/Issue1337Test.php new file mode 100644 index 00000000000..9abd356fad9 --- /dev/null +++ b/tests/end-to-end/regression/1337/Issue1337Test.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +class Issue1337Test extends TestCase +{ + public static function dataProvider(): array + { + return [ + 'c:\\' => [true], + // The following is commented out because it no longer works in PHP >= 8.1 + // 0.9 => [true], + ]; + } + + #[DataProvider('dataProvider')] + public function testProvider($a): void + { + $this->assertTrue($a); + } +} diff --git a/tests/end-to-end/regression/1374.phpt b/tests/end-to-end/regression/1374.phpt new file mode 100644 index 00000000000..ef6317ed968 --- /dev/null +++ b/tests/end-to-end/regression/1374.phpt @@ -0,0 +1,21 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/1374 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +S 1 / 1 (100%) + +Time: %s, Memory: %s + +OK, but some tests were skipped! +Tests: 1, Assertions: 0, Skipped: 1. diff --git a/tests/end-to-end/regression/GitHub/1374/Issue1374Test.php b/tests/end-to-end/regression/1374/Issue1374Test.php similarity index 82% rename from tests/end-to-end/regression/GitHub/1374/Issue1374Test.php rename to tests/end-to-end/regression/1374/Issue1374Test.php index d931f7a443c..70f32b3b7d4 100644 --- a/tests/end-to-end/regression/GitHub/1374/Issue1374Test.php +++ b/tests/end-to-end/regression/1374/Issue1374Test.php @@ -7,11 +7,12 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use PHPUnit\Framework\TestCase; -/** - * @requires extension I_DO_NOT_EXIST - */ +#[RequiresPhpExtension('I_DO_NOT_EXIST')] class Issue1374Test extends TestCase { protected function setUp(): void diff --git a/tests/end-to-end/regression/1437.phpt b/tests/end-to-end/regression/1437.phpt new file mode 100644 index 00000000000..46b4bf3c29e --- /dev/null +++ b/tests/end-to-end/regression/1437.phpt @@ -0,0 +1,37 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/1437 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +F 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 failure: + +1) PHPUnit\TestFixture\Issue1437Test::testFailure +Failed asserting that false is true. + +%sIssue1437Test.php:%i + +-- + +There was 1 risky test: + +1) PHPUnit\TestFixture\Issue1437Test::testFailure +Test code or tested code did not close its own output buffers + +%sIssue1437Test.php:%i + +FAILURES! +Tests: 1, Assertions: 1, Failures: 1, Risky: 1. diff --git a/tests/end-to-end/regression/GitHub/1437/Issue1437Test.php b/tests/end-to-end/regression/1437/Issue1437Test.php similarity index 84% rename from tests/end-to-end/regression/GitHub/1437/Issue1437Test.php rename to tests/end-to-end/regression/1437/Issue1437Test.php index 433f26ce13a..68eb31eb321 100644 --- a/tests/end-to-end/regression/GitHub/1437/Issue1437Test.php +++ b/tests/end-to-end/regression/1437/Issue1437Test.php @@ -7,13 +7,16 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + +use function ob_start; use PHPUnit\Framework\TestCase; class Issue1437Test extends TestCase { public function testFailure(): void { - \ob_start(); + ob_start(); $this->assertTrue(false); } } diff --git a/tests/end-to-end/regression/1471.phpt b/tests/end-to-end/regression/1471.phpt new file mode 100644 index 00000000000..cd729c2857c --- /dev/null +++ b/tests/end-to-end/regression/1471.phpt @@ -0,0 +1,28 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/1471 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +F 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 failure: + +1) PHPUnit\TestFixture\Issue1471Test::testFailure +Failed asserting that false is true. + +%s%eIssue1471Test.php:%d + +FAILURES! +Tests: 1, Assertions: 1, Failures: 1. diff --git a/tests/end-to-end/regression/GitHub/1471/Issue1471Test.php b/tests/end-to-end/regression/1471/Issue1471Test.php similarity index 93% rename from tests/end-to-end/regression/GitHub/1471/Issue1471Test.php rename to tests/end-to-end/regression/1471/Issue1471Test.php index bef2a7c1c6c..af938693418 100644 --- a/tests/end-to-end/regression/GitHub/1471/Issue1471Test.php +++ b/tests/end-to-end/regression/1471/Issue1471Test.php @@ -7,6 +7,8 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + use PHPUnit\Framework\TestCase; class Issue1471Test extends TestCase diff --git a/tests/end-to-end/regression/1570.phpt b/tests/end-to-end/regression/1570.phpt new file mode 100644 index 00000000000..d49e876e880 --- /dev/null +++ b/tests/end-to-end/regression/1570.phpt @@ -0,0 +1,29 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/1570 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +*R 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 risky test: + +1) PHPUnit\TestFixture\Issue1570Test::testOne +Test code or tested code printed unexpected output: * + +%s:%d + +OK, but there were issues! +Tests: 1, Assertions: 1, Risky: 1. diff --git a/tests/end-to-end/regression/GitHub/1570/Issue1570Test.php b/tests/end-to-end/regression/1570/Issue1570Test.php similarity index 85% rename from tests/end-to-end/regression/GitHub/1570/Issue1570Test.php rename to tests/end-to-end/regression/1570/Issue1570Test.php index 0ddb2327a99..c7c5f195f5a 100644 --- a/tests/end-to-end/regression/GitHub/1570/Issue1570Test.php +++ b/tests/end-to-end/regression/1570/Issue1570Test.php @@ -7,6 +7,8 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + use PHPUnit\Framework\TestCase; class Issue1570Test extends TestCase @@ -14,5 +16,7 @@ class Issue1570Test extends TestCase public function testOne(): void { print '*'; + + $this->assertTrue(true); } } diff --git a/tests/end-to-end/regression/2085.phpt b/tests/end-to-end/regression/2085.phpt new file mode 100644 index 00000000000..72b320a7242 --- /dev/null +++ b/tests/end-to-end/regression/2085.phpt @@ -0,0 +1,43 @@ +--TEST-- +Test CLI flags --enforce-time-limit --default-time-limit +--DESCRIPTION-- +https://github.com/sebastianbergmann/phpunit/issues/2085 +--SKIPIF-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +R 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 risky test: + +1) PHPUnit\TestFixture\Issue2085Test::testShouldAbortSlowTestByEnforcingTimeLimit +This test was aborted after 1 second + +%s:%d + +OK, but there were issues! +Tests: 1, Assertions: 1, Risky: 1. diff --git a/tests/end-to-end/regression/GitHub/2085/Issue2085Test.php b/tests/end-to-end/regression/2085/Issue2085Test.php similarity index 87% rename from tests/end-to-end/regression/GitHub/2085/Issue2085Test.php rename to tests/end-to-end/regression/2085/Issue2085Test.php index 699e663a5d1..43dbcca09ef 100644 --- a/tests/end-to-end/regression/GitHub/2085/Issue2085Test.php +++ b/tests/end-to-end/regression/2085/Issue2085Test.php @@ -7,6 +7,9 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + +use function sleep; use PHPUnit\Framework\TestCase; class Issue2085Test extends TestCase @@ -14,7 +17,7 @@ class Issue2085Test extends TestCase public function testShouldAbortSlowTestByEnforcingTimeLimit(): void { $this->assertTrue(true); - \sleep(2); + sleep(2); $this->assertTrue(true); } } diff --git a/tests/end-to-end/regression/2137-filter.phpt b/tests/end-to-end/regression/2137-filter.phpt new file mode 100644 index 00000000000..dbbe8c831d7 --- /dev/null +++ b/tests/end-to-end/regression/2137-filter.phpt @@ -0,0 +1,38 @@ +--TEST-- +#2137: Error message for invalid dataprovider +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +There were 2 PHPUnit errors: + +1) PHPUnit\TestFixture\Issue2137Test::testBrandService +The data provider specified for PHPUnit\TestFixture\Issue2137Test::testBrandService is invalid +Data set #0 provided by PHPUnit\TestFixture\Issue2137Test::provideBrandService is invalid, expected array but got stdClass + +%s:%d + +2) PHPUnit\TestFixture\Issue2137Test::testSomethingElseInvalid +The data provider specified for PHPUnit\TestFixture\Issue2137Test::testSomethingElseInvalid is invalid +Data set #0 provided by PHPUnit\TestFixture\Issue2137Test::provideBrandService is invalid, expected array but got stdClass + +%s:%d + +-- + +There was 1 PHPUnit test runner warning: + +1) No tests found in class "PHPUnit\TestFixture\Issue2137Test". + +No tests executed! diff --git a/tests/end-to-end/regression/2137-no_filter.phpt b/tests/end-to-end/regression/2137-no_filter.phpt new file mode 100644 index 00000000000..0bbb311434a --- /dev/null +++ b/tests/end-to-end/regression/2137-no_filter.phpt @@ -0,0 +1,36 @@ +--TEST-- +#2137: Error message for invalid dataprovider +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +There were 2 PHPUnit errors: + +1) PHPUnit\TestFixture\Issue2137Test::testBrandService +The data provider specified for PHPUnit\TestFixture\Issue2137Test::testBrandService is invalid +Data set #0 provided by PHPUnit\TestFixture\Issue2137Test::provideBrandService is invalid, expected array but got stdClass + +%s:%d + +2) PHPUnit\TestFixture\Issue2137Test::testSomethingElseInvalid +The data provider specified for PHPUnit\TestFixture\Issue2137Test::testSomethingElseInvalid is invalid +Data set #0 provided by PHPUnit\TestFixture\Issue2137Test::provideBrandService is invalid, expected array but got stdClass + +%s:%d + +-- + +There was 1 PHPUnit test runner warning: + +1) No tests found in class "PHPUnit\TestFixture\Issue2137Test". + +No tests executed! diff --git a/tests/end-to-end/regression/2137/Issue2137Test.php b/tests/end-to-end/regression/2137/Issue2137Test.php new file mode 100644 index 00000000000..f046b562d42 --- /dev/null +++ b/tests/end-to-end/regression/2137/Issue2137Test.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; +use SebastianBergmann\RecursionContext\InvalidArgumentException; +use stdClass; + +class Issue2137Test extends TestCase +{ + public static function provideBrandService() + { + return [ + // [true, true] + new stdClass, // not valid + ]; + } + + /** + * @throws Exception + * @throws ExpectationFailedException + * @throws InvalidArgumentException + */ + #[DataProvider('provideBrandService')] + public function testBrandService($provided, $expected): void + { + $this->assertSame($provided, $expected); + } + + /** + * @throws \Exception + * @throws ExpectationFailedException + * @throws InvalidArgumentException + */ + #[DataProvider('provideBrandService')] + public function testSomethingElseInvalid($provided, $expected): void + { + $this->assertSame($provided, $expected); + } +} diff --git a/tests/end-to-end/regression/2145.phpt b/tests/end-to-end/regression/2145.phpt new file mode 100644 index 00000000000..2aa2266ec33 --- /dev/null +++ b/tests/end-to-end/regression/2145.phpt @@ -0,0 +1,29 @@ +--TEST-- +--stop-on-failure fails to stop on PHP 7 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +E + +Time: %s, Memory: %s + +There was 1 error: + +1) PHPUnit\TestFixture\Issue2145Test +Exception: message + +%s:%d + +ERRORS! +Tests: 1, Assertions: 0, Errors: 1. diff --git a/tests/end-to-end/regression/2145/Issue2145Test.php b/tests/end-to-end/regression/2145/Issue2145Test.php new file mode 100644 index 00000000000..3eabdf10491 --- /dev/null +++ b/tests/end-to-end/regression/2145/Issue2145Test.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use Exception; +use PHPUnit\Framework\TestCase; + +class Issue2145Test extends TestCase +{ + public static function setUpBeforeClass(): void + { + throw new Exception('message'); + } + + public function testOne(): void + { + } + + public function testTwo(): void + { + } +} diff --git a/tests/end-to-end/regression/2155-no-expects-log.phpt b/tests/end-to-end/regression/2155-no-expects-log.phpt new file mode 100644 index 00000000000..af9e476d934 --- /dev/null +++ b/tests/end-to-end/regression/2155-no-expects-log.phpt @@ -0,0 +1,26 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/2155 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +logged a side effect +. 1 / 1 (100%) + +Time: %s, Memory: %s + +Issue2155Test_No Expects Log (PHPUnit\TestFixture\Issue2155\Issue2155Test_NoExpectsLog) + ✔ One + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/regression/2155/Issue2155Test_NoExpectsLog.php b/tests/end-to-end/regression/2155/Issue2155Test_NoExpectsLog.php new file mode 100644 index 00000000000..341bdba4f52 --- /dev/null +++ b/tests/end-to-end/regression/2155/Issue2155Test_NoExpectsLog.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue2155; + +use function error_log; +use PHPUnit\Framework\TestCase; + +class Foo +{ + public function doFoo() + { + error_log('logged a side effect'); + + return ''; + } +} + +final class Issue2155Test_NoExpectsLog extends TestCase +{ + public function testOne(): void + { + $foo = new Foo; + + $this->assertSame('', $foo->doFoo()); + } +} diff --git a/tests/end-to-end/regression/2158.phpt b/tests/end-to-end/regression/2158.phpt new file mode 100644 index 00000000000..bca1c290394 --- /dev/null +++ b/tests/end-to-end/regression/2158.phpt @@ -0,0 +1,20 @@ +--TEST-- +#2158: Failure to run tests in separate processes if a file included into main process contains constant definition +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +.. 2 / 2 (100%) + +Time: %s, Memory: %s + +OK (2 tests, 2 assertions) diff --git a/tests/end-to-end/regression/2158/Issue2158Test.php b/tests/end-to-end/regression/2158/Issue2158Test.php new file mode 100644 index 00000000000..4c755287940 --- /dev/null +++ b/tests/end-to-end/regression/2158/Issue2158Test.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use function defined; +use PHPUnit\Framework\Attributes\PreserveGlobalState; +use PHPUnit\Framework\Attributes\RunInSeparateProcess; +use PHPUnit\Framework\TestCase; + +#[PreserveGlobalState(true)] +class Issue2158Test extends TestCase +{ + /** + * Set constant in main process. + */ + public function testSomething(): void + { + include __DIR__ . '/constant.inc'; + $this->assertTrue(true); + } + + /** + * Constant defined previously in main process constant should be available and + * no errors should be yielded by reload of included files. + */ + #[RunInSeparateProcess] + public function testSomethingElse(): void + { + $this->assertTrue(defined('TEST_CONSTANT')); + } +} diff --git a/tests/end-to-end/regression/GitHub/2158/constant.inc b/tests/end-to-end/regression/2158/constant.inc similarity index 100% rename from tests/end-to-end/regression/GitHub/2158/constant.inc rename to tests/end-to-end/regression/2158/constant.inc diff --git a/tests/end-to-end/regression/2380.phpt b/tests/end-to-end/regression/2380.phpt new file mode 100644 index 00000000000..6c7513a79eb --- /dev/null +++ b/tests/end-to-end/regression/2380.phpt @@ -0,0 +1,20 @@ +--TEST-- +#2380: Data Providers cannot be generators anymore +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/regression/2380/Issue2380Test.php b/tests/end-to-end/regression/2380/Issue2380Test.php new file mode 100644 index 00000000000..bd1eafe6eb3 --- /dev/null +++ b/tests/end-to-end/regression/2380/Issue2380Test.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use Generator; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +class Issue2380Test extends TestCase +{ + public static function generatorData(): Generator + { + yield ['testing']; + } + + #[DataProvider('generatorData')] + public function testGeneratorProvider($data): void + { + $this->assertNotEmpty($data); + } +} diff --git a/tests/end-to-end/regression/2435.phpt b/tests/end-to-end/regression/2435.phpt new file mode 100644 index 00000000000..d942126e12a --- /dev/null +++ b/tests/end-to-end/regression/2435.phpt @@ -0,0 +1,20 @@ +--TEST-- +GH-2435: Test empty @group annotation +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/regression/GitHub/2435/Issue2435Test.php b/tests/end-to-end/regression/2435/Issue2435Test.php similarity index 76% rename from tests/end-to-end/regression/GitHub/2435/Issue2435Test.php rename to tests/end-to-end/regression/2435/Issue2435Test.php index 95db7c1c2ef..1f656884525 100644 --- a/tests/end-to-end/regression/GitHub/2435/Issue2435Test.php +++ b/tests/end-to-end/regression/2435/Issue2435Test.php @@ -7,7 +7,11 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -class Issue2435Test extends PHPUnit\Framework\TestCase +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\TestCase; + +class Issue2435Test extends TestCase { public function testOne(): void { diff --git a/tests/end-to-end/regression/2448-existing-test.phpt b/tests/end-to-end/regression/2448-existing-test.phpt new file mode 100644 index 00000000000..fefd18a6381 --- /dev/null +++ b/tests/end-to-end/regression/2448-existing-test.phpt @@ -0,0 +1,25 @@ +--TEST-- +#2448: Weird error when trying to run `Test` from `Test.php` but `Test.php` does not exist +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) +--CLEAN-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Test file "SomeNonExistingTest.php" not found +--CLEAN-- + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\TestCase; + +class Test extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/2724-diff-pid-from-parent-process.phpt b/tests/end-to-end/regression/2724-diff-pid-from-parent-process.phpt new file mode 100644 index 00000000000..4376fe283c0 --- /dev/null +++ b/tests/end-to-end/regression/2724-diff-pid-from-parent-process.phpt @@ -0,0 +1,24 @@ +--TEST-- +GH-2724: Missing initialization of setRunClassInSeparateProcess() for tests without data providers +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 3 assertions) diff --git a/tests/end-to-end/regression/2724/SeparateClassRunMethodInNewProcessTest.php b/tests/end-to-end/regression/2724/SeparateClassRunMethodInNewProcessTest.php new file mode 100644 index 00000000000..6e197fa5d5e --- /dev/null +++ b/tests/end-to-end/regression/2724/SeparateClassRunMethodInNewProcessTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use function file_exists; +use function file_get_contents; +use function getmypid; +use function unlink; +use PHPUnit\Framework\TestCase; + +final class SeparateClassRunMethodInNewProcessTest extends TestCase +{ + public const string PROCESS_ID_FILE_PATH = __DIR__ . '/parent_process_id.txt'; + public const int INITIAL_PARENT_PROCESS_ID = 0; + public const int INITIAL_PROCESS_ID = 1; + public static $parentProcessId = self::INITIAL_PARENT_PROCESS_ID; + public static $processId = self::INITIAL_PROCESS_ID; + + public static function setUpBeforeClass(): void + { + if (file_exists(self::PROCESS_ID_FILE_PATH)) { + self::$parentProcessId = (int) file_get_contents(self::PROCESS_ID_FILE_PATH); + } + } + + public static function tearDownAfterClass(): void + { + if (file_exists(self::PROCESS_ID_FILE_PATH)) { + unlink(self::PROCESS_ID_FILE_PATH); + } + } + + public function testTestMethodIsRunInSeparateProcess(): void + { + self::$processId = getmypid(); + + $this->assertNotSame(self::INITIAL_PROCESS_ID, self::$processId); + $this->assertNotSame(self::INITIAL_PARENT_PROCESS_ID, self::$parentProcessId); + $this->assertNotSame(self::$processId, self::$parentProcessId); + } +} diff --git a/tests/end-to-end/regression/2725-separate-class-before-after-pid.phpt b/tests/end-to-end/regression/2725-separate-class-before-after-pid.phpt new file mode 100644 index 00000000000..1521a31b913 --- /dev/null +++ b/tests/end-to-end/regression/2725-separate-class-before-after-pid.phpt @@ -0,0 +1,21 @@ +--TEST-- +GH-2725: Verify that @runClassInSeparateProcess runs @beforeclass and @afterclass methods in the same process as test methods. +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +.. 2 / 2 (100%) + +Time: %s, Memory: %s + +OK (2 tests, 2 assertions) diff --git a/tests/end-to-end/regression/GitHub/2725/BeforeAfterClassPidTest.php b/tests/end-to-end/regression/2725/BeforeAfterClassPidTest.php similarity index 86% rename from tests/end-to-end/regression/GitHub/2725/BeforeAfterClassPidTest.php rename to tests/end-to-end/regression/2725/BeforeAfterClassPidTest.php index 06faf4a204b..66c91828d99 100644 --- a/tests/end-to-end/regression/GitHub/2725/BeforeAfterClassPidTest.php +++ b/tests/end-to-end/regression/2725/BeforeAfterClassPidTest.php @@ -7,29 +7,24 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace Issue2725; +namespace PHPUnit\TestFixture\Issue2725; use function getmypid; +use PHPUnit\Framework\Attributes\AfterClass; +use PHPUnit\Framework\Attributes\BeforeClass; use PHPUnit\Framework\TestCase; -/** - * @runClassInSeparateProcess - */ class BeforeAfterClassPidTest extends TestCase { public const PID_VARIABLE = 'current_pid'; - /** - * @beforeClass - */ + #[BeforeClass] public static function showPidBefore(): void { $GLOBALS[static::PID_VARIABLE] = getmypid(); } - /** - * @afterClass - */ + #[AfterClass] public static function showPidAfter(): void { if ($GLOBALS[static::PID_VARIABLE] - getmypid() !== 0) { diff --git a/tests/end-to-end/regression/2731.phpt b/tests/end-to-end/regression/2731.phpt new file mode 100644 index 00000000000..fca0836c1e2 --- /dev/null +++ b/tests/end-to-end/regression/2731.phpt @@ -0,0 +1,27 @@ +--TEST-- +GH-2731: Empty exception message cannot be expected +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +F 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 failure: + +1) PHPUnit\TestFixture\Issue2731Test::testOne +Failed asserting that exception message is empty but is 'message'. + +FAILURES! +Tests: 1, Assertions: 2, Failures: 1. diff --git a/tests/end-to-end/regression/GitHub/2731/Issue2731Test.php b/tests/end-to-end/regression/2731/Issue2731Test.php similarity index 78% rename from tests/end-to-end/regression/GitHub/2731/Issue2731Test.php rename to tests/end-to-end/regression/2731/Issue2731Test.php index 3b105d6fd37..afb2aa6bc06 100644 --- a/tests/end-to-end/regression/GitHub/2731/Issue2731Test.php +++ b/tests/end-to-end/regression/2731/Issue2731Test.php @@ -7,7 +7,12 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -class Issue2731Test extends PHPUnit\Framework\TestCase +namespace PHPUnit\TestFixture; + +use Exception; +use PHPUnit\Framework\TestCase; + +class Issue2731Test extends TestCase { public function testOne(): void { diff --git a/tests/end-to-end/regression/2811.phpt b/tests/end-to-end/regression/2811.phpt new file mode 100644 index 00000000000..2ee754d84f4 --- /dev/null +++ b/tests/end-to-end/regression/2811.phpt @@ -0,0 +1,21 @@ +--TEST-- +GH-2811: expectExceptionMessage() does not work without expectException() +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/regression/2811/Issue2811Test.php b/tests/end-to-end/regression/2811/Issue2811Test.php new file mode 100644 index 00000000000..b92275c533e --- /dev/null +++ b/tests/end-to-end/regression/2811/Issue2811Test.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use Exception; +use PHPUnit\Framework\TestCase; + +class Issue2811Test extends TestCase +{ + public function testOne(): void + { + $this->expectExceptionMessage('hello'); + + throw new Exception('hello'); + } +} diff --git a/tests/end-to-end/regression/2830.phpt b/tests/end-to-end/regression/2830.phpt new file mode 100644 index 00000000000..f16b86f8934 --- /dev/null +++ b/tests/end-to-end/regression/2830.phpt @@ -0,0 +1,21 @@ +--TEST-- +GH-2830: @runClassInSeparateProcess fails for tests with a data provider +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/regression/2830/Issue2830Test.php b/tests/end-to-end/regression/2830/Issue2830Test.php new file mode 100644 index 00000000000..b9c55c6cc50 --- /dev/null +++ b/tests/end-to-end/regression/2830/Issue2830Test.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +class Issue2830Test extends TestCase +{ + public static function simpleDataProvider(): array + { + return [ + ['foo'], + ]; + } + + #[DataProvider('simpleDataProvider')] + public function testMethodUsesDataProvider(string $foo): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/2833.phpt b/tests/end-to-end/regression/2833.phpt new file mode 100644 index 00000000000..77b74fc40ec --- /dev/null +++ b/tests/end-to-end/regression/2833.phpt @@ -0,0 +1,21 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/2833 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +.. 2 / 2 (100%) + +Time: %s, Memory: %s + +OK (2 tests, 2 assertions) diff --git a/tests/end-to-end/regression/2972.phpt b/tests/end-to-end/regression/2972.phpt new file mode 100644 index 00000000000..396cf8a8df6 --- /dev/null +++ b/tests/end-to-end/regression/2972.phpt @@ -0,0 +1,20 @@ +--TEST-- +GH-2972: Test suite shouldn't fail when it contains both *.phpt files and unconventionally named tests +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +.. 2 / 2 (100%) + +Time: %s, Memory: %s + +OK (2 tests, 2 assertions) diff --git a/tests/end-to-end/regression/GitHub/2972/issue-2972-test.phpt b/tests/end-to-end/regression/2972/issue-2972-test.phpt similarity index 100% rename from tests/end-to-end/regression/GitHub/2972/issue-2972-test.phpt rename to tests/end-to-end/regression/2972/issue-2972-test.phpt diff --git a/tests/end-to-end/regression/GitHub/2972/unconventiallyNamedIssue2972Test.php b/tests/end-to-end/regression/2972/unconventiallyNamedIssue2972Test.php similarity index 81% rename from tests/end-to-end/regression/GitHub/2972/unconventiallyNamedIssue2972Test.php rename to tests/end-to-end/regression/2972/unconventiallyNamedIssue2972Test.php index b38b73bdb9a..0747cb10080 100644 --- a/tests/end-to-end/regression/GitHub/2972/unconventiallyNamedIssue2972Test.php +++ b/tests/end-to-end/regression/2972/unconventiallyNamedIssue2972Test.php @@ -7,11 +7,11 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace Issue2972; +namespace PHPUnit\TestFixture; use PHPUnit\Framework\TestCase; -class Issue2972Test extends TestCase +class unconventiallyNamedIssue2972Test extends TestCase { public function testHello(): void { diff --git a/tests/end-to-end/regression/3093/Issue3093Test.php b/tests/end-to-end/regression/3093/Issue3093Test.php new file mode 100644 index 00000000000..40b1a86e8d9 --- /dev/null +++ b/tests/end-to-end/regression/3093/Issue3093Test.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Depends; +use PHPUnit\Framework\TestCase; + +class Issue3093Test extends TestCase +{ + public static function someDataProvider(): array + { + return [['some values']]; + } + + public function testFirstWithoutDependencies(): void + { + $this->assertTrue(true); + } + + #[Depends('testFirstWithoutDependencies')] + #[DataProvider('someDataProvider')] + public function testSecondThatDependsOnFirstAndDataprovider($value): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/3093/issue-3093-test.phpt b/tests/end-to-end/regression/3093/issue-3093-test.phpt new file mode 100644 index 00000000000..233c5171bff --- /dev/null +++ b/tests/end-to-end/regression/3093/issue-3093-test.phpt @@ -0,0 +1,21 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/3093 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +.. 2 / 2 (100%) + +Time: %s, Memory: %s + +OK (2 tests, 2 assertions) diff --git a/tests/end-to-end/regression/3156/Issue3156Test.php b/tests/end-to-end/regression/3156/Issue3156Test.php new file mode 100644 index 00000000000..f43dc312371 --- /dev/null +++ b/tests/end-to-end/regression/3156/Issue3156Test.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Depends; +use PHPUnit\Framework\TestCase; +use stdClass; + +class Issue3156Test extends TestCase +{ + public static function dataSelectOperatorsProvider(): array + { + return [ + ['1'], + ['2'], + ]; + } + + public function testConstants(): stdClass + { + $this->assertStringEndsWith('/', '/'); + + return new stdClass; + } + + #[Depends('testConstants')] + #[DataProvider('dataSelectOperatorsProvider')] + public function testDependsRequire(string $val, stdClass $obj): void + { + $this->assertStringEndsWith('/', '/'); + } +} diff --git a/tests/end-to-end/regression/3881.phpt b/tests/end-to-end/regression/3881.phpt new file mode 100644 index 00000000000..a06081409ee --- /dev/null +++ b/tests/end-to-end/regression/3881.phpt @@ -0,0 +1,21 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/3881 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/regression/GitHub/3881/Issue3881Test.php b/tests/end-to-end/regression/3881/Issue3881Test.php similarity index 93% rename from tests/end-to-end/regression/GitHub/3881/Issue3881Test.php rename to tests/end-to-end/regression/3881/Issue3881Test.php index 4e0accadc9b..330e3d76435 100644 --- a/tests/end-to-end/regression/GitHub/3881/Issue3881Test.php +++ b/tests/end-to-end/regression/3881/Issue3881Test.php @@ -7,6 +7,8 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + use PHPUnit\Framework\TestCase; abstract class AbstractIssue3881Test extends TestCase diff --git a/tests/end-to-end/regression/3904.phpt b/tests/end-to-end/regression/3904.phpt new file mode 100644 index 00000000000..d437b7a48fa --- /dev/null +++ b/tests/end-to-end/regression/3904.phpt @@ -0,0 +1,21 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/3904 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/regression/GitHub/3904/Issue3904Test.php b/tests/end-to-end/regression/3904/Issue3904Test.php similarity index 93% rename from tests/end-to-end/regression/GitHub/3904/Issue3904Test.php rename to tests/end-to-end/regression/3904/Issue3904Test.php index 47ead50d9bf..6c5f792ff59 100644 --- a/tests/end-to-end/regression/GitHub/3904/Issue3904Test.php +++ b/tests/end-to-end/regression/3904/Issue3904Test.php @@ -7,6 +7,8 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + use PHPUnit\Framework\TestCase; class Bar extends TestCase diff --git a/tests/end-to-end/regression/3983-1.phpt b/tests/end-to-end/regression/3983-1.phpt new file mode 100644 index 00000000000..082997723a8 --- /dev/null +++ b/tests/end-to-end/regression/3983-1.phpt @@ -0,0 +1,20 @@ +--TEST-- +phpunit 3983 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/regression/3983-2.phpt b/tests/end-to-end/regression/3983-2.phpt new file mode 100644 index 00000000000..965b64de474 --- /dev/null +++ b/tests/end-to-end/regression/3983-2.phpt @@ -0,0 +1,20 @@ +--TEST-- +phpunit 3983/ +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/regression/GitHub/3983/Issue3983Test.php b/tests/end-to-end/regression/3983/Issue3983Test.php similarity index 92% rename from tests/end-to-end/regression/GitHub/3983/Issue3983Test.php rename to tests/end-to-end/regression/3983/Issue3983Test.php index 13e2ff97138..3ec4d18289e 100644 --- a/tests/end-to-end/regression/GitHub/3983/Issue3983Test.php +++ b/tests/end-to-end/regression/3983/Issue3983Test.php @@ -7,6 +7,8 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + use PHPUnit\Framework\TestCase; final class Issue3983Test extends TestCase diff --git a/tests/end-to-end/regression/4232.phpt b/tests/end-to-end/regression/4232.phpt new file mode 100644 index 00000000000..be898dcb671 --- /dev/null +++ b/tests/end-to-end/regression/4232.phpt @@ -0,0 +1,23 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/4232 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/regression/GitHub/4232/Issue4232Test.php b/tests/end-to-end/regression/4232/Issue4232Test.php similarity index 85% rename from tests/end-to-end/regression/GitHub/4232/Issue4232Test.php rename to tests/end-to-end/regression/4232/Issue4232Test.php index 0ad484a83e8..ef7dbf8e9ad 100644 --- a/tests/end-to-end/regression/GitHub/4232/Issue4232Test.php +++ b/tests/end-to-end/regression/4232/Issue4232Test.php @@ -7,9 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace Foo\Bar; - -require __DIR__ . '/ParentIssue4232Test.php'; +namespace PHPUnit\TestFixture; final class Issue4232Test extends ParentIssue4232Test { diff --git a/tests/end-to-end/regression/GitHub/4232/ParentIssue4232Test.php b/tests/end-to-end/regression/4232/ParentIssue4232Test.php similarity index 91% rename from tests/end-to-end/regression/GitHub/4232/ParentIssue4232Test.php rename to tests/end-to-end/regression/4232/ParentIssue4232Test.php index 64d8179fce8..e8ebc00da90 100644 --- a/tests/end-to-end/regression/GitHub/4232/ParentIssue4232Test.php +++ b/tests/end-to-end/regression/4232/ParentIssue4232Test.php @@ -7,7 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace Foo\Bar; +namespace PHPUnit\TestFixture; use PHPUnit\Framework\TestCase; diff --git a/tests/end-to-end/regression/433.phpt b/tests/end-to-end/regression/433.phpt new file mode 100644 index 00000000000..a82286c3d02 --- /dev/null +++ b/tests/end-to-end/regression/433.phpt @@ -0,0 +1,31 @@ +--TEST-- +GH-433: expectOutputString not completely working as expected +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +..F 3 / 3 (100%) + +Time: %s, Memory: %s + +There was 1 failure: + +1) PHPUnit\TestFixture\Issue433Test::testNotMatchingOutput +Failed asserting that two strings are identical. +--- Expected ++++ Actual +@@ @@ +-'foo' ++'bar' + +FAILURES! +Tests: 3, Assertions: 3, Failures: 1. diff --git a/tests/end-to-end/regression/GitHub/433/Issue433Test.php b/tests/end-to-end/regression/433/Issue433Test.php similarity index 95% rename from tests/end-to-end/regression/GitHub/433/Issue433Test.php rename to tests/end-to-end/regression/433/Issue433Test.php index 799638594ba..d4f1654bea8 100644 --- a/tests/end-to-end/regression/GitHub/433/Issue433Test.php +++ b/tests/end-to-end/regression/433/Issue433Test.php @@ -7,6 +7,8 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + use PHPUnit\Framework\TestCase; class Issue433Test extends TestCase diff --git a/tests/end-to-end/regression/4347.phpt b/tests/end-to-end/regression/4347.phpt new file mode 100644 index 00000000000..8793f115f81 --- /dev/null +++ b/tests/end-to-end/regression/4347.phpt @@ -0,0 +1,21 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/4347 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/regression/4347/TestIssue4347.php b/tests/end-to-end/regression/4347/TestIssue4347.php new file mode 100644 index 00000000000..a17c96a0b12 --- /dev/null +++ b/tests/end-to-end/regression/4347/TestIssue4347.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use Exception; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +class TestIssue4347 extends TestCase +{ + public static function thisMethodDataProvider() + { + return [ + [new Exception('my message')], + ]; + } + + #[DataProvider('thisMethodDataProvider')] + public function testThisMethod(Exception $expectedException): void + { + $this->assertSame('my message', $expectedException->getMessage()); + } +} diff --git a/tests/end-to-end/regression/4376.phpt b/tests/end-to-end/regression/4376.phpt new file mode 100644 index 00000000000..3f090f78114 --- /dev/null +++ b/tests/end-to-end/regression/4376.phpt @@ -0,0 +1,30 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/4376 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +E 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 error: + +1) PHPUnit\TestFixture\Test::testOne +Error: Class %sC%s not found + +%sTest.php:%d + +ERRORS! +Tests: 1, Assertions: 0, Errors: 1. diff --git a/tests/end-to-end/regression/4376/phpunit.xml b/tests/end-to-end/regression/4376/phpunit.xml new file mode 100644 index 00000000000..d59f95ea8ba --- /dev/null +++ b/tests/end-to-end/regression/4376/phpunit.xml @@ -0,0 +1,10 @@ + + + + + tests + + + diff --git a/tests/end-to-end/regression/4376/tests/Test.php b/tests/end-to-end/regression/4376/tests/Test.php new file mode 100644 index 00000000000..0a4afa88f23 --- /dev/null +++ b/tests/end-to-end/regression/4376/tests/Test.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use C; +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testOne(): void + { + $o = new C; + } +} diff --git a/tests/end-to-end/regression/4391-separate-class-requires-in-class.phpt b/tests/end-to-end/regression/4391-separate-class-requires-in-class.phpt new file mode 100644 index 00000000000..e92bbdc82bc --- /dev/null +++ b/tests/end-to-end/regression/4391-separate-class-requires-in-class.phpt @@ -0,0 +1,32 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/4391 +--INI-- +disable_functions=proc_open +--FILE-- +setValue(null, $version); +(new PHPUnit\TextUI\Application)->run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +SS 2 / 2 (100%) + +Time: %s, Memory: %s + +OK, but some tests were skipped! +Tests: 2, Assertions: 0, Skipped: 2. diff --git a/tests/end-to-end/regression/4391-separate-class-requires-in-method.phpt b/tests/end-to-end/regression/4391-separate-class-requires-in-method.phpt new file mode 100644 index 00000000000..a003bf341a4 --- /dev/null +++ b/tests/end-to-end/regression/4391-separate-class-requires-in-method.phpt @@ -0,0 +1,32 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/4391 +--INI-- +disable_functions=proc_open +--FILE-- +setValue(null, $version); +(new PHPUnit\TextUI\Application)->run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +SS 2 / 2 (100%) + +Time: %s, Memory: %s + +OK, but some tests were skipped! +Tests: 2, Assertions: 0, Skipped: 2. diff --git a/tests/end-to-end/regression/4391-separate-requires-in-class.phpt b/tests/end-to-end/regression/4391-separate-requires-in-class.phpt new file mode 100644 index 00000000000..a05dbe9baa6 --- /dev/null +++ b/tests/end-to-end/regression/4391-separate-requires-in-class.phpt @@ -0,0 +1,31 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/4391 +--INI-- +disable_functions=proc_open +--FILE-- +setValue(null, $version); +(new PHPUnit\TextUI\Application)->run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +S 1 / 1 (100%) + +Time: %s, Memory: %s + +OK, but some tests were skipped! +Tests: 1, Assertions: 0, Skipped: 1. diff --git a/tests/end-to-end/regression/4391-separate-requires-in-method.phpt b/tests/end-to-end/regression/4391-separate-requires-in-method.phpt new file mode 100644 index 00000000000..6ff019b69a4 --- /dev/null +++ b/tests/end-to-end/regression/4391-separate-requires-in-method.phpt @@ -0,0 +1,31 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/4391 +--INI-- +disable_functions=proc_open +--FILE-- +setValue(null, $version); +(new PHPUnit\TextUI\Application)->run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +S 1 / 1 (100%) + +Time: %s, Memory: %s + +OK, but some tests were skipped! +Tests: 1, Assertions: 0, Skipped: 1. diff --git a/tests/end-to-end/regression/4391-separate-tests-requires-in-class.phpt b/tests/end-to-end/regression/4391-separate-tests-requires-in-class.phpt new file mode 100644 index 00000000000..1c78606064a --- /dev/null +++ b/tests/end-to-end/regression/4391-separate-tests-requires-in-class.phpt @@ -0,0 +1,31 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/4391 +--INI-- +disable_functions=proc_open +--FILE-- +setValue(null, $version); +(new PHPUnit\TextUI\Application)->run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +SS 2 / 2 (100%) + +Time: %s, Memory: %s + +OK, but some tests were skipped! +Tests: 2, Assertions: 0, Skipped: 2. diff --git a/tests/end-to-end/regression/4391-separate-tests-requires-in-method.phpt b/tests/end-to-end/regression/4391-separate-tests-requires-in-method.phpt new file mode 100644 index 00000000000..2f39ecc322b --- /dev/null +++ b/tests/end-to-end/regression/4391-separate-tests-requires-in-method.phpt @@ -0,0 +1,31 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/4391 +--INI-- +disable_functions=proc_open +--FILE-- +setValue(null, $version); +(new PHPUnit\TextUI\Application)->run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +SS 2 / 2 (100%) + +Time: %s, Memory: %s + +OK, but some tests were skipped! +Tests: 2, Assertions: 0, Skipped: 2. diff --git a/tests/end-to-end/regression/4391/RunClassInSeparateProcessClassTest.php b/tests/end-to-end/regression/4391/RunClassInSeparateProcessClassTest.php new file mode 100644 index 00000000000..2362f0842c1 --- /dev/null +++ b/tests/end-to-end/regression/4391/RunClassInSeparateProcessClassTest.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue4391; + +use Exception; +use PHPUnit\Framework\Attributes\RequiresPhpunit; +use PHPUnit\Framework\TestCase; + +#[RequiresPhpunit('< 10')] +final class RunClassInSeparateProcessClassTest extends TestCase +{ + public function testOne(): void + { + throw new Exception('message'); + } + + public function testTwo(): void + { + throw new Exception('message'); + } +} diff --git a/tests/end-to-end/regression/4391/RunClassInSeparateProcessMethodTest.php b/tests/end-to-end/regression/4391/RunClassInSeparateProcessMethodTest.php new file mode 100644 index 00000000000..0857d22caad --- /dev/null +++ b/tests/end-to-end/regression/4391/RunClassInSeparateProcessMethodTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue4391; + +use Exception; +use PHPUnit\Framework\Attributes\RequiresPhpunit; +use PHPUnit\Framework\TestCase; + +final class RunClassInSeparateProcessMethodTest extends TestCase +{ + #[RequiresPhpunit('< 10')] + public function testOne(): void + { + throw new Exception('message'); + } + + #[RequiresPhpunit('< 10')] + public function testTwo(): void + { + throw new Exception('message'); + } +} diff --git a/tests/end-to-end/regression/4391/RunInSeparateProcessClassTest.php b/tests/end-to-end/regression/4391/RunInSeparateProcessClassTest.php new file mode 100644 index 00000000000..9e7df4d1c63 --- /dev/null +++ b/tests/end-to-end/regression/4391/RunInSeparateProcessClassTest.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue4391; + +use Exception; +use PHPUnit\Framework\Attributes\RequiresPhpunit; +use PHPUnit\Framework\Attributes\RunInSeparateProcess; +use PHPUnit\Framework\TestCase; + +#[RequiresPhpunit('< 10')] +final class RunInSeparateProcessClassTest extends TestCase +{ + #[RunInSeparateProcess] + public function testOne(): void + { + throw new Exception('message'); + } +} diff --git a/tests/end-to-end/regression/4391/RunInSeparateProcessMethodTest.php b/tests/end-to-end/regression/4391/RunInSeparateProcessMethodTest.php new file mode 100644 index 00000000000..88fcd68fb39 --- /dev/null +++ b/tests/end-to-end/regression/4391/RunInSeparateProcessMethodTest.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue4391; + +use Exception; +use PHPUnit\Framework\Attributes\RequiresPhpunit; +use PHPUnit\Framework\Attributes\RunInSeparateProcess; +use PHPUnit\Framework\TestCase; + +final class RunInSeparateProcessMethodTest extends TestCase +{ + #[RunInSeparateProcess] + #[RequiresPhpunit('< 10')] + public function testOne(): void + { + throw new Exception('message'); + } +} diff --git a/tests/end-to-end/regression/4391/RunTestsInSeparateProcessesClassTest.php b/tests/end-to-end/regression/4391/RunTestsInSeparateProcessesClassTest.php new file mode 100644 index 00000000000..b6c5513ea0c --- /dev/null +++ b/tests/end-to-end/regression/4391/RunTestsInSeparateProcessesClassTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue4391; + +use Exception; +use PHPUnit\Framework\Attributes\RequiresPhpunit; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; +use PHPUnit\Framework\TestCase; + +#[RunTestsInSeparateProcesses] +#[RequiresPhpunit('< 10')] +final class RunTestsInSeparateProcessesClassTest extends TestCase +{ + public function testOne(): void + { + throw new Exception('message'); + } + + public function testTwo(): void + { + throw new Exception('message'); + } +} diff --git a/tests/end-to-end/regression/4391/RunTestsInSeparateProcessesMethodTest.php b/tests/end-to-end/regression/4391/RunTestsInSeparateProcessesMethodTest.php new file mode 100644 index 00000000000..9a6dbe62ddb --- /dev/null +++ b/tests/end-to-end/regression/4391/RunTestsInSeparateProcessesMethodTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue4391; + +use Exception; +use PHPUnit\Framework\Attributes\RequiresPhpunit; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; +use PHPUnit\Framework\TestCase; + +#[RunTestsInSeparateProcesses] +final class RunTestsInSeparateProcessesMethodTest extends TestCase +{ + #[RequiresPhpunit('< 10')] + public function testOne(): void + { + throw new Exception('message'); + } + + #[RequiresPhpunit('< 10')] + public function testTwo(): void + { + throw new Exception('message'); + } +} diff --git a/tests/end-to-end/regression/445.phpt b/tests/end-to-end/regression/445.phpt new file mode 100644 index 00000000000..0ee0f1b2c93 --- /dev/null +++ b/tests/end-to-end/regression/445.phpt @@ -0,0 +1,32 @@ +--TEST-- +GH-455: expectOutputString not working in strict mode +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +..F 3 / 3 (100%) + +Time: %s, Memory: %s + +There was 1 failure: + +1) PHPUnit\TestFixture\Issue445Test::testNotMatchingOutput +Failed asserting that two strings are identical. +--- Expected ++++ Actual +@@ @@ +-'foo' ++'bar' + +FAILURES! +Tests: 3, Assertions: 3, Failures: 1. diff --git a/tests/end-to-end/regression/GitHub/445/Issue445Test.php b/tests/end-to-end/regression/445/Issue445Test.php similarity index 95% rename from tests/end-to-end/regression/GitHub/445/Issue445Test.php rename to tests/end-to-end/regression/445/Issue445Test.php index e9392c5ca81..ae7549c1fe0 100644 --- a/tests/end-to-end/regression/GitHub/445/Issue445Test.php +++ b/tests/end-to-end/regression/445/Issue445Test.php @@ -7,6 +7,8 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + use PHPUnit\Framework\TestCase; class Issue445Test extends TestCase diff --git a/tests/end-to-end/regression/4498.phpt b/tests/end-to-end/regression/4498.phpt new file mode 100644 index 00000000000..cef20c59ebf --- /dev/null +++ b/tests/end-to-end/regression/4498.phpt @@ -0,0 +1,22 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/4498 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/regression/4498/Issue4498Test.php b/tests/end-to-end/regression/4498/Issue4498Test.php new file mode 100644 index 00000000000..cad6b0cf6a2 --- /dev/null +++ b/tests/end-to-end/regression/4498/Issue4498Test.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\TestCase; + +class issue4498test extends TestCase +{ + public function testFoo(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/4620.phpt b/tests/end-to-end/regression/4620.phpt new file mode 100644 index 00000000000..54b399a58be --- /dev/null +++ b/tests/end-to-end/regression/4620.phpt @@ -0,0 +1,25 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/4620 +https://github.com/sebastianbergmann/phpunit/issues/4877 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Error in bootstrap script: PHPUnit\TestFixture\MyException: +Big boom. Big bada boom. +%a + +Previous error: Exception: +Previous boom. +%a diff --git a/tests/end-to-end/regression/4620/Issue4620Test.php b/tests/end-to-end/regression/4620/Issue4620Test.php new file mode 100644 index 00000000000..bf0d8fe904e --- /dev/null +++ b/tests/end-to-end/regression/4620/Issue4620Test.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\TestCase; + +final class Issue4620Test extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/4620/bootstrap.php b/tests/end-to-end/regression/4620/bootstrap.php new file mode 100644 index 00000000000..706c9d6e883 --- /dev/null +++ b/tests/end-to-end/regression/4620/bootstrap.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use Exception; + +final class MyException extends Exception +{ +} + +throw new MyException('Big boom. Big bada boom.', 0, new Exception('Previous boom.')); diff --git a/tests/end-to-end/regression/498.phpt b/tests/end-to-end/regression/498.phpt new file mode 100644 index 00000000000..61e56b77e78 --- /dev/null +++ b/tests/end-to-end/regression/498.phpt @@ -0,0 +1,26 @@ +--TEST-- +GH-498: The test methods won't be run if a dataProvider throws Exception and --group is added in command line +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +There was 1 PHPUnit error: + +1) PHPUnit\TestFixture\Issue498Test::shouldBeFalse +The data provider PHPUnit\TestFixture\Issue498Test::shouldBeFalseDataProvider specified for PHPUnit\TestFixture\Issue498Test::shouldBeFalse is invalid +Can't create the data + +%s:%d + +No tests executed! diff --git a/tests/end-to-end/regression/498/Issue498Test.php b/tests/end-to-end/regression/498/Issue498Test.php new file mode 100644 index 00000000000..a28111831a2 --- /dev/null +++ b/tests/end-to-end/regression/498/Issue498Test.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use Exception; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; + +class Issue498Test extends TestCase +{ + public static function shouldBeTrueDataProvider(): array + { + return [ + [true], + [false], + ]; + } + + public static function shouldBeFalseDataProvider(): array + { + throw new Exception("Can't create the data"); + } + + #[Test] + #[DataProvider('shouldBeTrueDataProvider')] + #[Group('falseOnly')] + public function shouldBeTrue($testData): void + { + $this->assertTrue(true); + } + + #[Test] + #[DataProvider('shouldBeFalseDataProvider')] + #[Group('trueOnly')] + public function shouldBeFalse($testData): void + { + $this->assertFalse(false); + } +} diff --git a/tests/end-to-end/regression/5020.phpt b/tests/end-to-end/regression/5020.phpt new file mode 100644 index 00000000000..696dfc424a8 --- /dev/null +++ b/tests/end-to-end/regression/5020.phpt @@ -0,0 +1,20 @@ +--TEST-- +GH-5020: Load PSR-0 tests +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/regression/5020/Under/Score/Issue5020Test.php b/tests/end-to-end/regression/5020/Under/Score/Issue5020Test.php new file mode 100644 index 00000000000..9c71d0befe8 --- /dev/null +++ b/tests/end-to-end/regression/5020/Under/Score/Issue5020Test.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +use PHPUnit\Framework\TestCase; + +/* + * This file is part of PHPUnit. + * + * (c) Sebastian Bergmann + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +class Under_Score_Issue5020Test extends TestCase +{ + public function testTrue(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/503.phpt b/tests/end-to-end/regression/503.phpt new file mode 100644 index 00000000000..d0f0a31d9b1 --- /dev/null +++ b/tests/end-to-end/regression/503.phpt @@ -0,0 +1,35 @@ +--TEST-- +GH-503: assertEquals() Line Ending Differences Are Obscure +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +F 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 failure: + +1) PHPUnit\TestFixture\Issue503Test::testCompareDifferentLineEndings +Failed asserting that two strings are identical. +--- Expected ++++ Actual +@@ @@ + #Warning: Strings contain different line endings! +-'foo ++'foo + ' + +%s:%i + +FAILURES! +Tests: 1, Assertions: 1, Failures: 1. diff --git a/tests/end-to-end/regression/GitHub/503/Issue503Test.php b/tests/end-to-end/regression/503/Issue503Test.php similarity index 89% rename from tests/end-to-end/regression/GitHub/503/Issue503Test.php rename to tests/end-to-end/regression/503/Issue503Test.php index 2deacfd3da2..b0097799fc4 100644 --- a/tests/end-to-end/regression/GitHub/503/Issue503Test.php +++ b/tests/end-to-end/regression/503/Issue503Test.php @@ -7,6 +7,8 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + use PHPUnit\Framework\TestCase; class Issue503Test extends TestCase @@ -15,7 +17,7 @@ public function testCompareDifferentLineEndings(): void { $this->assertSame( "foo\n", - "foo\r\n" + "foo\r\n", ); } } diff --git a/tests/end-to-end/regression/5138.phpt b/tests/end-to-end/regression/5138.phpt new file mode 100644 index 00000000000..1915872b48e --- /dev/null +++ b/tests/end-to-end/regression/5138.phpt @@ -0,0 +1,31 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5138 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +There was 1 PHPUnit error: + +1) PHPUnit\TestFixture\Issue5138Test::testOne +The data provider PHPUnit\TestFixture\Issue5138Test::provideData specified for PHPUnit\TestFixture\Issue5138Test::testOne is invalid +message + +%s:%d + +-- + +There was 1 PHPUnit test runner warning: + +1) No tests found in class "PHPUnit\TestFixture\Issue5138Test". + +No tests executed! diff --git a/tests/end-to-end/regression/5138/phpunit.xml b/tests/end-to-end/regression/5138/phpunit.xml new file mode 100644 index 00000000000..1689799b616 --- /dev/null +++ b/tests/end-to-end/regression/5138/phpunit.xml @@ -0,0 +1,9 @@ + + + + + tests + + + diff --git a/tests/end-to-end/regression/5138/tests/Issue5138Test.php b/tests/end-to-end/regression/5138/tests/Issue5138Test.php new file mode 100644 index 00000000000..a2098c0cf30 --- /dev/null +++ b/tests/end-to-end/regression/5138/tests/Issue5138Test.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; +use RuntimeException; + +final class Issue5138Test extends TestCase +{ + public static function provideData(): void + { + throw new RuntimeException('message'); + } + + #[DataProvider('provideData')] + public function testOne(): void + { + } +} diff --git a/tests/end-to-end/regression/5157.phpt b/tests/end-to-end/regression/5157.phpt new file mode 100644 index 00000000000..6ea50448bef --- /dev/null +++ b/tests/end-to-end/regression/5157.phpt @@ -0,0 +1,21 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5157 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/regression/5157/phpunit.xml b/tests/end-to-end/regression/5157/phpunit.xml new file mode 100644 index 00000000000..a627748fe37 --- /dev/null +++ b/tests/end-to-end/regression/5157/phpunit.xml @@ -0,0 +1,10 @@ + + + + + tests + + + diff --git a/tests/end-to-end/regression/5157/tests/Test.php b/tests/end-to-end/regression/5157/tests/Test.php new file mode 100644 index 00000000000..d601cbd458e --- /dev/null +++ b/tests/end-to-end/regression/5157/tests/Test.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + private static bool $variable = false; + + public static function setUpBeforeClass(): void + { + self::$variable = true; + } + + public function testOne(): void + { + $this->assertTrue(self::$variable); + } +} diff --git a/tests/end-to-end/regression/5165.phpt b/tests/end-to-end/regression/5165.phpt new file mode 100644 index 00000000000..1a22692db67 --- /dev/null +++ b/tests/end-to-end/regression/5165.phpt @@ -0,0 +1,22 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5165 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +There was 1 skipped test suite: + +1) Issue5165Test +message + +No tests executed! diff --git a/tests/end-to-end/regression/5165/Issue5165Test.php b/tests/end-to-end/regression/5165/Issue5165Test.php new file mode 100644 index 00000000000..8aec1b54ac5 --- /dev/null +++ b/tests/end-to-end/regression/5165/Issue5165Test.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +use PHPUnit\Framework\TestCase; + +final class Issue5165Test extends TestCase +{ + public static function setUpBeforeClass(): void + { + self::markTestSkipped('message'); + } + + public function testOne(): void + { + } + + public function testTwo(): void + { + } +} diff --git a/tests/end-to-end/regression/5172.phpt b/tests/end-to-end/regression/5172.phpt new file mode 100644 index 00000000000..7a0a720aa6c --- /dev/null +++ b/tests/end-to-end/regression/5172.phpt @@ -0,0 +1,31 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5172 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +Issue5172 (PHPUnit\TestFixture\Issue5172) + ✔ One + +There was 1 PHPUnit test runner deprecation: + +1) Your XML configuration validates against a deprecated schema. Migrate your XML configuration using "--migrate-configuration"! + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Deprecations: 1. diff --git a/tests/end-to-end/regression/5172/phpunit.xml b/tests/end-to-end/regression/5172/phpunit.xml new file mode 100644 index 00000000000..1cc108d9936 --- /dev/null +++ b/tests/end-to-end/regression/5172/phpunit.xml @@ -0,0 +1,14 @@ + + + + + tests + + + + + + + diff --git a/tests/end-to-end/regression/5172/tests/Issue5172Test.php b/tests/end-to-end/regression/5172/tests/Issue5172Test.php new file mode 100644 index 00000000000..05ebfa20b33 --- /dev/null +++ b/tests/end-to-end/regression/5172/tests/Issue5172Test.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\TestCase; + +final class Issue5172Test extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/5178.phpt b/tests/end-to-end/regression/5178.phpt new file mode 100644 index 00000000000..af550887ba3 --- /dev/null +++ b/tests/end-to-end/regression/5178.phpt @@ -0,0 +1,20 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5178 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +... 3 / 3 (100%) + +Time: %s, Memory: %s + +OK (3 tests, 3 assertions) diff --git a/tests/end-to-end/regression/5178/Issue5178Test.php b/tests/end-to-end/regression/5178/Issue5178Test.php new file mode 100644 index 00000000000..5d94aee31d1 --- /dev/null +++ b/tests/end-to-end/regression/5178/Issue5178Test.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Depends; +use PHPUnit\Framework\TestCase; + +final class Issue5178Test extends TestCase +{ + public static function fooDataProvider(): array + { + return [ + 'foo 1' => ['Hello'], + 'foo 2' => ['World'], + ]; + } + + #[DataProvider('fooDataProvider')] + public function testFoo(string $input): void + { + $this->assertNotEmpty($input); + } + + #[Depends('testFoo')] + public function testBar(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/5192.phpt b/tests/end-to-end/regression/5192.phpt new file mode 100644 index 00000000000..69627ad1d62 --- /dev/null +++ b/tests/end-to-end/regression/5192.phpt @@ -0,0 +1,17 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5192 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +No tests executed! diff --git a/tests/end-to-end/regression/5192/phpunit.xml b/tests/end-to-end/regression/5192/phpunit.xml new file mode 100644 index 00000000000..0dbd87e2524 --- /dev/null +++ b/tests/end-to-end/regression/5192/phpunit.xml @@ -0,0 +1,10 @@ + + + + + tests + + + diff --git a/tests/end-to-end/regression/5192/tests/.gitkeep b/tests/end-to-end/regression/5192/tests/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/end-to-end/regression/5210.phpt b/tests/end-to-end/regression/5210.phpt new file mode 100644 index 00000000000..efac9e93d50 --- /dev/null +++ b/tests/end-to-end/regression/5210.phpt @@ -0,0 +1,29 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5210 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +E 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 error: + +1) Issue5210Test::testOne +Exception: test + +%s:%d + +ERRORS! +Tests: 1, Assertions: 1, Errors: 1. diff --git a/tests/end-to-end/regression/5210/Issue5210Test.php b/tests/end-to-end/regression/5210/Issue5210Test.php new file mode 100644 index 00000000000..7c56aaaee04 --- /dev/null +++ b/tests/end-to-end/regression/5210/Issue5210Test.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +use PHPUnit\Framework\TestCase; + +final class Issue5210Test extends TestCase +{ + protected function tearDown(): void + { + throw new Exception('test'); + } + + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/5218.phpt b/tests/end-to-end/regression/5218.phpt new file mode 100644 index 00000000000..4d4eef6e8db --- /dev/null +++ b/tests/end-to-end/regression/5218.phpt @@ -0,0 +1,44 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5218 +--INI-- +pcov.directory=tests/end-to-end/regression/5218/src/ +--SKIPIF-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s MB + +OK (1 test, 1 assertion) + + +Code Coverage Report: + %s + + Summary: + Classes: 100.00% (1/1) + Methods: 100.00% (1/1) + Lines: 100.00% (1/1) + +PHPUnit\TestFixture\Issue5218\Issue5218 + Methods: 100.00% ( 1/ 1) Lines: 100.00% ( 1/ 1) diff --git a/tests/end-to-end/regression/5218/src/Issue5218.php b/tests/end-to-end/regression/5218/src/Issue5218.php new file mode 100644 index 00000000000..5c6fd9bc4b3 --- /dev/null +++ b/tests/end-to-end/regression/5218/src/Issue5218.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5218; + +final class Issue5218 +{ + public function returnMe(string $return): string + { + return $return; + } +} diff --git a/tests/end-to-end/regression/5218/tests/Issue5218Test.php b/tests/end-to-end/regression/5218/tests/Issue5218Test.php new file mode 100644 index 00000000000..0f2585ccdf4 --- /dev/null +++ b/tests/end-to-end/regression/5218/tests/Issue5218Test.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5218; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Issue5218::class)] +final class Issue5218Test extends TestCase +{ + public function testReturn(): void + { + $this->assertSame('foo', (new Issue5218)->returnMe('foo')); + } +} diff --git a/tests/end-to-end/regression/5234.phpt b/tests/end-to-end/regression/5234.phpt new file mode 100644 index 00000000000..39c04455135 --- /dev/null +++ b/tests/end-to-end/regression/5234.phpt @@ -0,0 +1,22 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5234 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s MB + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/regression/5234/phpunit.xml b/tests/end-to-end/regression/5234/phpunit.xml new file mode 100644 index 00000000000..c84aeb637ad --- /dev/null +++ b/tests/end-to-end/regression/5234/phpunit.xml @@ -0,0 +1,14 @@ + + + + + tests + + + + + + + diff --git a/tests/end-to-end/regression/5234/tests/Issue5234Test.php b/tests/end-to-end/regression/5234/tests/Issue5234Test.php new file mode 100644 index 00000000000..aede3034053 --- /dev/null +++ b/tests/end-to-end/regression/5234/tests/Issue5234Test.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5234; + +use PHPUnit\Framework\Attributes\RunInSeparateProcess; +use PHPUnit\Framework\TestCase; + +final class Issue5234Test extends TestCase +{ + #[RunInSeparateProcess] + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/5234/tests/bootstrap.php b/tests/end-to-end/regression/5234/tests/bootstrap.php new file mode 100644 index 00000000000..41202f7126d --- /dev/null +++ b/tests/end-to-end/regression/5234/tests/bootstrap.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +if (!\defined('CONST_TEST')) { + exit; +} diff --git a/tests/end-to-end/regression/5258.phpt b/tests/end-to-end/regression/5258.phpt new file mode 100644 index 00000000000..4d45223594a --- /dev/null +++ b/tests/end-to-end/regression/5258.phpt @@ -0,0 +1,39 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5258 +--FILE-- +run($_SERVER['argv']); + +print file_get_contents($junitFile); + +unlink($junitFile); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +.S 2 / 2 (100%) + +Time: %s, Memory: %s MB + +OK, but some tests were skipped! +Tests: 2, Assertions: 1, Skipped: 1. + + + + + + + + + diff --git a/tests/end-to-end/regression/5258/Issue5258Test.php b/tests/end-to-end/regression/5258/Issue5258Test.php new file mode 100644 index 00000000000..8175c04eca6 --- /dev/null +++ b/tests/end-to-end/regression/5258/Issue5258Test.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5258; + +use PHPUnit\Framework\Attributes\RequiresPhpExtension; +use PHPUnit\Framework\Attributes\RunInSeparateProcess; +use PHPUnit\Framework\TestCase; + +final class Issue5258Test extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } + + #[RunInSeparateProcess] + #[RequiresPhpExtension('notinstalled')] + public function testTwo(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/5278.phpt b/tests/end-to-end/regression/5278.phpt new file mode 100644 index 00000000000..9ec033e9fd8 --- /dev/null +++ b/tests/end-to-end/regression/5278.phpt @@ -0,0 +1,26 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5278 +--SKIPIF-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +bool(true) +. 1 / 1 (100%) + +Time: %s, Memory: %s MB + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/regression/5278/Issue5278Test.php b/tests/end-to-end/regression/5278/Issue5278Test.php new file mode 100644 index 00000000000..5807be9684f --- /dev/null +++ b/tests/end-to-end/regression/5278/Issue5278Test.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5278; + +use function var_dump; +use PHPUnit\Framework\TestCase; + +final class Issue5278Test extends TestCase +{ + public function testOne(): void + { + $v = true; + + var_dump($v); + + $this->assertTrue($v); + } +} diff --git a/tests/end-to-end/regression/5287.phpt b/tests/end-to-end/regression/5287.phpt new file mode 100644 index 00000000000..db0517cd433 --- /dev/null +++ b/tests/end-to-end/regression/5287.phpt @@ -0,0 +1,52 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5287 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Bootstrap Finished (%sMyClassTest.php) +Event Facade Sealed +Data Provider Method Called (PHPUnit\TestFixture\Issue5278\A\AnotherClassTest::provide for test method PHPUnit\TestFixture\Issue5278\A\AnotherClassTest::test) +Data Provider Method Finished for PHPUnit\TestFixture\Issue5278\A\AnotherClassTest::test: +- PHPUnit\TestFixture\Issue5278\A\AnotherClassTest::provide +Test Suite Loaded (3 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (3 tests) +Test Suite Started (CLI Arguments, 3 tests) +Test Suite Started (PHPUnit\TestFixture\Issue5278\A\AnotherClassTest, 1 test) +Test Suite Started (PHPUnit\TestFixture\Issue5278\A\AnotherClassTest::test, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Issue5278\A\AnotherClassTest::test#0) +Test Prepared (PHPUnit\TestFixture\Issue5278\A\AnotherClassTest::test#0) +Test Passed (PHPUnit\TestFixture\Issue5278\A\AnotherClassTest::test#0) +Test Finished (PHPUnit\TestFixture\Issue5278\A\AnotherClassTest::test#0) +Test Suite Finished (PHPUnit\TestFixture\Issue5278\A\AnotherClassTest::test, 1 test) +Test Suite Finished (PHPUnit\TestFixture\Issue5278\A\AnotherClassTest, 1 test) +Test Suite Started (PHPUnit\TestFixture\Issue5278\B\MyClassTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Issue5278\B\MyClassTest::test) +Test Prepared (PHPUnit\TestFixture\Issue5278\B\MyClassTest::test) +Test Failed (PHPUnit\TestFixture\Issue5278\B\MyClassTest::test) +Failed asserting that false is true. +Test Finished (PHPUnit\TestFixture\Issue5278\B\MyClassTest::test) +Test Suite Finished (PHPUnit\TestFixture\Issue5278\B\MyClassTest, 1 test) +Test Suite Started (PHPUnit\TestFixture\Issue5278\C\MyClassTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Issue5278\C\MyClassTest::test) +Test Prepared (PHPUnit\TestFixture\Issue5278\C\MyClassTest::test) +Test Passed (PHPUnit\TestFixture\Issue5278\C\MyClassTest::test) +Test Finished (PHPUnit\TestFixture\Issue5278\C\MyClassTest::test) +Test Suite Finished (PHPUnit\TestFixture\Issue5278\C\MyClassTest, 1 test) +Test Suite Finished (CLI Arguments, 3 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/regression/5287/A/AnotherClassTest.php b/tests/end-to-end/regression/5287/A/AnotherClassTest.php new file mode 100644 index 00000000000..6026157d2d7 --- /dev/null +++ b/tests/end-to-end/regression/5287/A/AnotherClassTest.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5278\A; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\Issue5278\C\MyClassTest; + +final class AnotherClassTest extends TestCase +{ + public static function provide(): array + { + return [[MyClassTest::VALUE]]; + } + + #[DataProvider('provide')] + public function test(bool $value): void + { + $this->assertTrue($value); + } +} diff --git a/tests/end-to-end/regression/5287/B/MyClassTest.php b/tests/end-to-end/regression/5287/B/MyClassTest.php new file mode 100644 index 00000000000..46843c217af --- /dev/null +++ b/tests/end-to-end/regression/5287/B/MyClassTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5278\B; + +use PHPUnit\Framework\TestCase; + +final class MyClassTest extends TestCase +{ + public function test(): void + { + $this->assertTrue(false); + } +} diff --git a/tests/end-to-end/regression/5287/C/MyClassTest.php b/tests/end-to-end/regression/5287/C/MyClassTest.php new file mode 100644 index 00000000000..e1503a1d43d --- /dev/null +++ b/tests/end-to-end/regression/5287/C/MyClassTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5278\C; + +use PHPUnit\Framework\TestCase; + +final class MyClassTest extends TestCase +{ + public const bool VALUE = true; + + public function test(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/5288.phpt b/tests/end-to-end/regression/5288.phpt new file mode 100644 index 00000000000..c53ed59aef8 --- /dev/null +++ b/tests/end-to-end/regression/5288.phpt @@ -0,0 +1,21 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5288 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +%s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/regression/5288/Issue5288Test.php b/tests/end-to-end/regression/5288/Issue5288Test.php new file mode 100644 index 00000000000..90358ccc5b1 --- /dev/null +++ b/tests/end-to-end/regression/5288/Issue5288Test.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5288; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class Issue5288Test extends TestCase +{ + public static function provider(): array + { + return [[true]]; + } + + #[DataProvider('provider')] + public function testOne(bool $value): void + { + $this->assertTrue($value); + } +} diff --git a/tests/end-to-end/regression/5340.phpt b/tests/end-to-end/regression/5340.phpt new file mode 100644 index 00000000000..b1a583432d1 --- /dev/null +++ b/tests/end-to-end/regression/5340.phpt @@ -0,0 +1,48 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5340 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +%s + +output printed from passing test +R +output printed from failing test +F 2 / 2 (100%) + +Time: %s, Memory: %s MB + +There was 1 failure: + +1) Issue5340Test::testTwo +Failed asserting that false is true. + +%s + +-- + +There were 2 risky tests: + +1) Issue5340Test::testOne +Test code or tested code printed unexpected output: output printed from passing test + +%s%eIssue5340Test.php:%d + +2) Issue5340Test::testTwo +Test code or tested code printed unexpected output: +output printed from failing test + +%s%eIssue5340Test.php:%d + +FAILURES! +Tests: 2, Assertions: 2, Failures: 1, Risky: 2. diff --git a/tests/end-to-end/regression/5340/Issue5340Test.php b/tests/end-to-end/regression/5340/Issue5340Test.php new file mode 100644 index 00000000000..2820b8c7ac5 --- /dev/null +++ b/tests/end-to-end/regression/5340/Issue5340Test.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +use PHPUnit\Framework\TestCase; + +final class Issue5340Test extends TestCase +{ + public function testOne(): void + { + print 'output printed from passing test' . \PHP_EOL; + + $this->assertTrue(true); + } + + public function testTwo(): void + { + print \PHP_EOL . 'output printed from failing test' . \PHP_EOL; + + $this->assertTrue(false); + } +} diff --git a/tests/end-to-end/regression/5342.phpt b/tests/end-to-end/regression/5342.phpt new file mode 100644 index 00000000000..e8e6ee34b70 --- /dev/null +++ b/tests/end-to-end/regression/5342.phpt @@ -0,0 +1,37 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/1437 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +F 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 failure: + +1) PHPUnit\TestFixture\Issue5342Test::testFailure +Failed asserting that false is true. + +%sIssue5342Test.php:%i + +-- + +There was 1 risky test: + +1) PHPUnit\TestFixture\Issue5342Test::testFailure +Test code or tested code closed output buffers other than its own + +%sIssue5342Test.php:%i + +FAILURES! +Tests: 1, Assertions: 1, Failures: 1, Risky: 1. diff --git a/tests/end-to-end/regression/5342/Issue5342Test.php b/tests/end-to-end/regression/5342/Issue5342Test.php new file mode 100644 index 00000000000..d2c9a763ac2 --- /dev/null +++ b/tests/end-to-end/regression/5342/Issue5342Test.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use function ob_end_clean; +use PHPUnit\Framework\TestCase; + +class Issue5342Test extends TestCase +{ + public function testFailure(): void + { + ob_end_clean(); + $this->assertTrue(false); + } +} diff --git a/tests/end-to-end/regression/5351.phpt b/tests/end-to-end/regression/5351.phpt new file mode 100644 index 00000000000..dc81d61e84b --- /dev/null +++ b/tests/end-to-end/regression/5351.phpt @@ -0,0 +1,47 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5351 +--INI-- +pcov.directory=tests/end-to-end/regression/5351/src/ +--SKIPIF-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +W 1 / 1 (100%) + +Time: %s, Memory: %s MB + +1 test triggered 1 PHPUnit warning: + +1) PHPUnit\TestFixture\Issue5351\GreeterTest::testGreets +Class PHPUnit\TestFixture\Issue5351\DoesNotExist is not a valid target for code coverage + +%sGreeterTest.php:18 + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. + + +Code Coverage Report: + %s + + Summary: + Classes: 0.00% (0/1) + Methods: 0.00% (0/1) + Lines: 0.00% (0/1) + diff --git a/tests/end-to-end/regression/5351/phpunit.xml b/tests/end-to-end/regression/5351/phpunit.xml new file mode 100644 index 00000000000..5ff287ed27f --- /dev/null +++ b/tests/end-to-end/regression/5351/phpunit.xml @@ -0,0 +1,22 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/regression/5351/src/Greeter.php b/tests/end-to-end/regression/5351/src/Greeter.php new file mode 100644 index 00000000000..745685a4275 --- /dev/null +++ b/tests/end-to-end/regression/5351/src/Greeter.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5351; + +final class Greeter +{ + public function greet(): string + { + return 'Hello world!'; + } +} diff --git a/tests/end-to-end/regression/5351/tests/GreeterTest.php b/tests/end-to-end/regression/5351/tests/GreeterTest.php new file mode 100644 index 00000000000..d8131219204 --- /dev/null +++ b/tests/end-to-end/regression/5351/tests/GreeterTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5351; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass('PHPUnit\TestFixture\Issue5351\DoesNotExist')] +final class GreeterTest extends TestCase +{ + public function testGreets(): void + { + $this->assertSame('Hello world!', (new Greeter)->greet()); + } +} diff --git a/tests/end-to-end/regression/5364.phpt b/tests/end-to-end/regression/5364.phpt new file mode 100644 index 00000000000..9561ed4563b --- /dev/null +++ b/tests/end-to-end/regression/5364.phpt @@ -0,0 +1,25 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5364 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Class PHPUnit\TestFixture\Issue5364\BarTest declared in %sBarTest.php does not extend PHPUnit\Framework\TestCase + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/regression/5364/BarTest.php b/tests/end-to-end/regression/5364/BarTest.php new file mode 100644 index 00000000000..06d42560271 --- /dev/null +++ b/tests/end-to-end/regression/5364/BarTest.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5364; + +final class BarTest +{ +} diff --git a/tests/end-to-end/regression/5364/FooTest.php b/tests/end-to-end/regression/5364/FooTest.php new file mode 100644 index 00000000000..7dfced85ee0 --- /dev/null +++ b/tests/end-to-end/regression/5364/FooTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5364; + +use PHPUnit\Framework\TestCase; + +final class FooTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/5451.phpt b/tests/end-to-end/regression/5451.phpt new file mode 100644 index 00000000000..f06192cf85c --- /dev/null +++ b/tests/end-to-end/regression/5451.phpt @@ -0,0 +1,35 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5451 +--SKIPIF-- +run($_SERVER['argv'])); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit error: + +1) PHPUnit\TestFixture\Issue5451\Issue5451Test::testWithErrorInDataProvider +The data provider PHPUnit\TestFixture\Issue5451\Issue5451Test::dataProviderThatTriggersPhpError specified for PHPUnit\TestFixture\Issue5451\Issue5451Test::testWithErrorInDataProvider is invalid +Call to a member function bar() on array + +%s%eIssue5451Test.php:26 + +ERRORS! +Tests: 1, Assertions: 1, Errors: 1. +int(2) diff --git a/tests/end-to-end/regression/5451/Issue5451Test.php b/tests/end-to-end/regression/5451/Issue5451Test.php new file mode 100644 index 00000000000..8879307ea09 --- /dev/null +++ b/tests/end-to-end/regression/5451/Issue5451Test.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5451; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class Issue5451Test extends TestCase +{ + public static function dataProviderThatTriggersPhpError(): array + { + $foo = []; + $foo->bar(); + + return [[]]; + } + + #[DataProvider('dataProviderThatTriggersPhpError')] + public function testWithErrorInDataProvider(): void + { + } + + public function testThatWorks(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/5456.phpt b/tests/end-to-end/regression/5456.phpt new file mode 100644 index 00000000000..1bec8c504aa --- /dev/null +++ b/tests/end-to-end/regression/5456.phpt @@ -0,0 +1,21 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5456 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/regression/5456/Issue5456Test.php b/tests/end-to-end/regression/5456/Issue5456Test.php new file mode 100644 index 00000000000..f98685f37f6 --- /dev/null +++ b/tests/end-to-end/regression/5456/Issue5456Test.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5456; + +use function ob_end_clean; +use function ob_start; +use PHPUnit\Framework\TestCase; + +final class Issue5456Test extends TestCase +{ + protected function tearDown(): void + { + ob_end_clean(); + } + + public function testOne(): void + { + $this->assertTrue(true); + + ob_start(); + } +} diff --git a/tests/end-to-end/regression/5493.phpt b/tests/end-to-end/regression/5493.phpt new file mode 100644 index 00000000000..c828eb60e5c --- /dev/null +++ b/tests/end-to-end/regression/5493.phpt @@ -0,0 +1,29 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5493 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +F 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 failure: + +1) PHPUnit\TestFixture\Issue5493\Issue5493Test::testOne +Failed asserting that false is true. + +%sIssue5493Test.php:19 + +FAILURES! +Tests: 1, Assertions: 1, Failures: 1. diff --git a/tests/end-to-end/regression/5493/Issue5493Test.php b/tests/end-to-end/regression/5493/Issue5493Test.php new file mode 100644 index 00000000000..5be15f4d06f --- /dev/null +++ b/tests/end-to-end/regression/5493/Issue5493Test.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5493; + +use PHPUnit\Framework\TestCase; + +final class Issue5493Test extends TestCase +{ + protected function setUp(): void + { + /** @noinspection PhpUnitAssertCanBeReplacedWithFailInspection */ + $this->assertTrue(false); + } + + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/5498.phpt b/tests/end-to-end/regression/5498.phpt new file mode 100644 index 00000000000..120d32b7f4a --- /dev/null +++ b/tests/end-to-end/regression/5498.phpt @@ -0,0 +1,44 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5498 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (%s) +Test Runner Configured +Bootstrap Finished (%sTestCase.php) +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (CLI Arguments, 1 test) +Test Suite Started (PHPUnit\TestFixture\Issue5498\Test, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Issue5498\Test::testOne) +Before Test Method Called (PHPUnit\TestFixture\Issue5498\Test::parentBefore) +Before Test Method Called (PHPUnit\TestFixture\Issue5498\Test::before) +Before Test Method Finished: +- PHPUnit\TestFixture\Issue5498\Test::parentBefore +- PHPUnit\TestFixture\Issue5498\Test::before +Test Prepared (PHPUnit\TestFixture\Issue5498\Test::testOne) +After Test Method Called (PHPUnit\TestFixture\Issue5498\Test::after) +After Test Method Called (PHPUnit\TestFixture\Issue5498\Test::parentAfter) +After Test Method Finished: +- PHPUnit\TestFixture\Issue5498\Test::after +- PHPUnit\TestFixture\Issue5498\Test::parentAfter +Test Passed (PHPUnit\TestFixture\Issue5498\Test::testOne) +Test Finished (PHPUnit\TestFixture\Issue5498\Test::testOne) +Test Suite Finished (PHPUnit\TestFixture\Issue5498\Test, 1 test) +Test Suite Finished (CLI Arguments, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/regression/5498/Test.php b/tests/end-to-end/regression/5498/Test.php new file mode 100644 index 00000000000..db92e41ada8 --- /dev/null +++ b/tests/end-to-end/regression/5498/Test.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5498; + +use PHPUnit\Framework\Attributes\After; +use PHPUnit\Framework\Attributes\Before; + +final class Test extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } + + #[Before] + protected function before(): void + { + } + + #[After] + protected function after(): void + { + } +} diff --git a/tests/end-to-end/regression/5498/TestCase.php b/tests/end-to-end/regression/5498/TestCase.php new file mode 100644 index 00000000000..b577abf152f --- /dev/null +++ b/tests/end-to-end/regression/5498/TestCase.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5498; + +use PHPUnit\Framework\Attributes\After; +use PHPUnit\Framework\Attributes\Before; + +abstract class TestCase extends \PHPUnit\Framework\TestCase +{ + #[Before] + protected function parentBefore(): void + { + } + + #[After] + protected function parentAfter(): void + { + } +} diff --git a/tests/end-to-end/regression/5561.phpt b/tests/end-to-end/regression/5561.phpt new file mode 100644 index 00000000000..5c033944a5e --- /dev/null +++ b/tests/end-to-end/regression/5561.phpt @@ -0,0 +1,26 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5561 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- + + + + + PHPUnit\TestFixture\Issue5561\Issue5561Test::testOne%A +Failed asserting that false is true. +%A +%sIssue5561Test.php:18 + + + diff --git a/tests/end-to-end/regression/5561/Issue5561Test.php b/tests/end-to-end/regression/5561/Issue5561Test.php new file mode 100644 index 00000000000..b81c334dfb7 --- /dev/null +++ b/tests/end-to-end/regression/5561/Issue5561Test.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5561; + +use PHPUnit\Framework\TestCase; + +final class Issue5561Test extends TestCase +{ + protected function setUp(): void + { + $this->assertTrue(false); + } + + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/5567.phpt b/tests/end-to-end/regression/5567.phpt new file mode 100644 index 00000000000..abcd46020ff --- /dev/null +++ b/tests/end-to-end/regression/5567.phpt @@ -0,0 +1,32 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5567 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +F 1 / 1 (100%) + +Time: %s, Memory: %s MB + +There was 1 failure: + +1) PHPUnit\TestFixture\Issue5567\Issue5567Test::testAnythingThatFailsWithRecursiveArray +Failed asserting that Array &0 [ + 'self' => Array &1 [ + 'self' => Array &1, + ], +] is false. + +%sIssue5567Test.php:%d + +FAILURES! +Tests: 1, Assertions: 1, Failures: 1. diff --git a/tests/end-to-end/regression/5567/Issue5567Test.php b/tests/end-to-end/regression/5567/Issue5567Test.php new file mode 100644 index 00000000000..cbbcec219ad --- /dev/null +++ b/tests/end-to-end/regression/5567/Issue5567Test.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5567; + +use PHPUnit\Framework\TestCase; + +final class Issue5567Test extends TestCase +{ + public function testAnythingThatFailsWithRecursiveArray(): void + { + $array = []; + $array['self'] = &$array; + + $this->assertFalse($array); + } +} diff --git a/tests/end-to-end/regression/5574.phpt b/tests/end-to-end/regression/5574.phpt new file mode 100644 index 00000000000..bb1718ffb44 --- /dev/null +++ b/tests/end-to-end/regression/5574.phpt @@ -0,0 +1,33 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5574 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +E 1 / 1 (100%) + +Time: %s, Memory: %s MB + +There was 1 error: + +1) PHPUnit\TestFixture\Issue5574\Issue5574Test::testThrownWrappedThrowablesOutputsCorrectStackTraceForEach +Exception: My exception + +%sIssue5574Test.php:22 + +Caused by +Error: Inner Exception + +%sIssue5574Test.php:21 + +ERRORS! +Tests: 1, Assertions: 0, Errors: 1. diff --git a/tests/end-to-end/regression/5574/Issue5574Test.php b/tests/end-to-end/regression/5574/Issue5574Test.php new file mode 100644 index 00000000000..4fde4db842e --- /dev/null +++ b/tests/end-to-end/regression/5574/Issue5574Test.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5574; + +use Error; +use Exception; +use PHPUnit\Framework\TestCase; + +final class Issue5574Test extends TestCase +{ + public function testThrownWrappedThrowablesOutputsCorrectStackTraceForEach(): void + { + $innerException = new Error('Inner Exception'); + $outerException = new Exception('My exception', 0, $innerException); + + throw $outerException; + } +} diff --git a/tests/end-to-end/regression/5592-process-isolation-events.phpt b/tests/end-to-end/regression/5592-process-isolation-events.phpt new file mode 100644 index 00000000000..f18e32dea04 --- /dev/null +++ b/tests/end-to-end/regression/5592-process-isolation-events.phpt @@ -0,0 +1,73 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/pull/5592 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (6 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (6 tests) +Test Suite Started (PHPUnit\TestFixture\Issue5592TestIsolation, 6 tests) +Child Process Started +Test Preparation Started (PHPUnit\TestFixture\Issue5592TestIsolation::testAddedAndRemovedErrorHandler) +Test Prepared (PHPUnit\TestFixture\Issue5592TestIsolation::testAddedAndRemovedErrorHandler) +Test Passed (PHPUnit\TestFixture\Issue5592TestIsolation::testAddedAndRemovedErrorHandler) +Test Finished (PHPUnit\TestFixture\Issue5592TestIsolation::testAddedAndRemovedErrorHandler) +Child Process Finished +Child Process Started +Test Preparation Started (PHPUnit\TestFixture\Issue5592TestIsolation::testAddedErrorHandler) +Test Prepared (PHPUnit\TestFixture\Issue5592TestIsolation::testAddedErrorHandler) +Test Failed (PHPUnit\TestFixture\Issue5592TestIsolation::testAddedErrorHandler) +Failed asserting that false is true. +Test Finished (PHPUnit\TestFixture\Issue5592TestIsolation::testAddedErrorHandler) +Child Process Finished +Child Process Started +Test Preparation Started (PHPUnit\TestFixture\Issue5592TestIsolation::testRemovedErrorHandler) +Test Prepared (PHPUnit\TestFixture\Issue5592TestIsolation::testRemovedErrorHandler) +Test Failed (PHPUnit\TestFixture\Issue5592TestIsolation::testRemovedErrorHandler) +Failed asserting that false is true. +Test Considered Risky (PHPUnit\TestFixture\Issue5592TestIsolation::testRemovedErrorHandler) +Test code or tested code removed error handlers other than its own +Test Finished (PHPUnit\TestFixture\Issue5592TestIsolation::testRemovedErrorHandler) +Child Process Finished +Child Process Started +Test Preparation Started (PHPUnit\TestFixture\Issue5592TestIsolation::testAddedAndRemovedExceptionHandler) +Test Prepared (PHPUnit\TestFixture\Issue5592TestIsolation::testAddedAndRemovedExceptionHandler) +Test Passed (PHPUnit\TestFixture\Issue5592TestIsolation::testAddedAndRemovedExceptionHandler) +Test Finished (PHPUnit\TestFixture\Issue5592TestIsolation::testAddedAndRemovedExceptionHandler) +Child Process Finished +Child Process Started +Test Preparation Started (PHPUnit\TestFixture\Issue5592TestIsolation::testAddedExceptionHandler) +Test Prepared (PHPUnit\TestFixture\Issue5592TestIsolation::testAddedExceptionHandler) +Test Failed (PHPUnit\TestFixture\Issue5592TestIsolation::testAddedExceptionHandler) +Failed asserting that false is true. +Test Finished (PHPUnit\TestFixture\Issue5592TestIsolation::testAddedExceptionHandler) +Child Process Finished +Child Process Started +Test Preparation Started (PHPUnit\TestFixture\Issue5592TestIsolation::testRemovedExceptionHandler) +Test Prepared (PHPUnit\TestFixture\Issue5592TestIsolation::testRemovedExceptionHandler) +Test Failed (PHPUnit\TestFixture\Issue5592TestIsolation::testRemovedExceptionHandler) +Failed asserting that false is true. +Test Finished (PHPUnit\TestFixture\Issue5592TestIsolation::testRemovedExceptionHandler) +Child Process Finished +Test Suite Finished (PHPUnit\TestFixture\Issue5592TestIsolation, 6 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 1) diff --git a/tests/end-to-end/regression/5592-process-isolation.phpt b/tests/end-to-end/regression/5592-process-isolation.phpt new file mode 100644 index 00000000000..695950f3bf9 --- /dev/null +++ b/tests/end-to-end/regression/5592-process-isolation.phpt @@ -0,0 +1,59 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/pull/5592 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +.FF.FF 6 / 6 (100%) + +Time: %s, Memory: %s + +There were 4 failures: + +1) PHPUnit\TestFixture\Issue5592TestIsolation::testAddedErrorHandler +Failed asserting that false is true. + +%sIssue5592TestIsolation.php:%i + +2) PHPUnit\TestFixture\Issue5592TestIsolation::testRemovedErrorHandler +Failed asserting that false is true. + +%sIssue5592TestIsolation.php:%i + +3) PHPUnit\TestFixture\Issue5592TestIsolation::testAddedExceptionHandler +Failed asserting that false is true. + +%sIssue5592TestIsolation.php:%i + +4) PHPUnit\TestFixture\Issue5592TestIsolation::testRemovedExceptionHandler +Failed asserting that false is true. + +%sIssue5592TestIsolation.php:%i + +-- + +There was 1 risky test: + +1) PHPUnit\TestFixture\Issue5592TestIsolation::testRemovedErrorHandler +Test code or tested code removed error handlers other than its own + +%sIssue5592TestIsolation.php:%i + +FAILURES! +Tests: 6, Assertions: 10, Failures: 4, Risky: 1. diff --git a/tests/end-to-end/regression/5592.phpt b/tests/end-to-end/regression/5592.phpt new file mode 100644 index 00000000000..c689580f3cf --- /dev/null +++ b/tests/end-to-end/regression/5592.phpt @@ -0,0 +1,73 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/pull/5592 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +.FF.FF 6 / 6 (100%) + +Time: %s, Memory: %s + +There were 4 failures: + +1) PHPUnit\TestFixture\Issue5592Test::testAddedErrorHandler +Failed asserting that false is true. + +%sIssue5592Test.php:%i + +2) PHPUnit\TestFixture\Issue5592Test::testRemovedErrorHandler +Failed asserting that false is true. + +%sIssue5592Test.php:%i + +3) PHPUnit\TestFixture\Issue5592Test::testAddedExceptionHandler +Failed asserting that false is true. + +%sIssue5592Test.php:%i + +4) PHPUnit\TestFixture\Issue5592Test::testRemovedExceptionHandler +Failed asserting that false is true. + +%sIssue5592Test.php:%i + +-- + +There were 4 risky tests: + +1) PHPUnit\TestFixture\Issue5592Test::testAddedErrorHandler +Test code or tested code did not remove its own error handlers + +%sIssue5592Test.php:%i + +2) PHPUnit\TestFixture\Issue5592Test::testRemovedErrorHandler +Test code or tested code removed error handlers other than its own + +%sIssue5592Test.php:%i + +3) PHPUnit\TestFixture\Issue5592Test::testAddedExceptionHandler +Test code or tested code did not remove its own exception handlers + +%sIssue5592Test.php:%i + +4) PHPUnit\TestFixture\Issue5592Test::testRemovedExceptionHandler +Test code or tested code removed exception handlers other than its own + +%sIssue5592Test.php:%i + +FAILURES! +Tests: 6, Assertions: 10, Failures: 4, Risky: 4. diff --git a/tests/end-to-end/regression/5592/Issue5592Test.php b/tests/end-to-end/regression/5592/Issue5592Test.php new file mode 100644 index 00000000000..1499e24f27e --- /dev/null +++ b/tests/end-to-end/regression/5592/Issue5592Test.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use function restore_error_handler; +use function restore_exception_handler; +use function set_error_handler; +use function set_exception_handler; +use PHPUnit\Framework\TestCase; +use PHPUnit\Runner\ErrorHandler; +use Throwable; + +class Issue5592Test extends TestCase +{ + public function testAddedAndRemovedErrorHandler(): void + { + $previous = set_error_handler([$this, 'addedAndRemovedErrorHandler']); + restore_error_handler(); + + $this->assertInstanceOf(ErrorHandler::class, $previous); + $this->assertTrue(true); + } + + public function testAddedErrorHandler(): void + { + $previous = set_error_handler([$this, 'addedErrorHandler']); + + $this->assertInstanceOf(ErrorHandler::class, $previous); + $this->assertTrue(false); + } + + public function testRemovedErrorHandler(): void + { + restore_error_handler(); + $this->assertTrue(false); + } + + public function testAddedAndRemovedExceptionHandler(): void + { + $previous = set_exception_handler([$this, 'addedAndRemovedExceptionHandler']); + restore_exception_handler(); + + $this->assertSame('global5592ExceptionHandler', $previous); + $this->assertTrue(true); + } + + public function testAddedExceptionHandler(): void + { + $previous = set_exception_handler([$this, 'addedExceptionHandler']); + + $this->assertSame('global5592ExceptionHandler', $previous); + $this->assertTrue(false); + } + + public function testRemovedExceptionHandler(): void + { + restore_exception_handler(); + $this->assertTrue(false); + } + + public function addedAndRemovedErrorHandler($errno, $errstr, $errfile, $errline): bool + { + return false; + } + + public function addedErrorHandler($errno, $errstr, $errfile, $errline): bool + { + return false; + } + + public function addedAndRemovedExceptionHandler(Throwable $exception): void + { + } + + public function addedExceptionHandler(Throwable $exception): void + { + } +} diff --git a/tests/end-to-end/regression/5592/Issue5592TestIsolation.php b/tests/end-to-end/regression/5592/Issue5592TestIsolation.php new file mode 100644 index 00000000000..81e6d286d9b --- /dev/null +++ b/tests/end-to-end/regression/5592/Issue5592TestIsolation.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use function restore_error_handler; +use function restore_exception_handler; +use function set_error_handler; +use function set_exception_handler; +use PHPUnit\Framework\TestCase; +use PHPUnit\Runner\ErrorHandler; +use Throwable; + +class Issue5592TestIsolation extends TestCase +{ + public function testAddedAndRemovedErrorHandler(): void + { + $previous = set_error_handler([$this, 'addedAndRemovedErrorHandler']); + restore_error_handler(); + + $this->assertInstanceOf(ErrorHandler::class, $previous); + $this->assertTrue(true); + } + + public function testAddedErrorHandler(): void + { + $previous = set_error_handler([$this, 'addedErrorHandler']); + + $this->assertInstanceOf(ErrorHandler::class, $previous); + $this->assertTrue(false); + } + + public function testRemovedErrorHandler(): void + { + restore_error_handler(); + $this->assertTrue(false); + } + + public function testAddedAndRemovedExceptionHandler(): void + { + $previous = set_exception_handler([$this, 'addedAndRemovedExceptionHandler']); + restore_exception_handler(); + + $this->assertNull($previous); + $this->assertTrue(true); + } + + public function testAddedExceptionHandler(): void + { + $previous = set_exception_handler([$this, 'addedExceptionHandler']); + + $this->assertNull($previous); + $this->assertTrue(false); + } + + public function testRemovedExceptionHandler(): void + { + restore_exception_handler(); + $this->assertTrue(false); + } + + public function addedAndRemovedErrorHandler($errno, $errstr, $errfile, $errline): bool + { + return false; + } + + public function addedErrorHandler($errno, $errstr, $errfile, $errline): bool + { + return false; + } + + public function addedAndRemovedExceptionHandler(Throwable $exception): void + { + } + + public function addedExceptionHandler(Throwable $exception): void + { + } +} diff --git a/tests/end-to-end/regression/5614.phpt b/tests/end-to-end/regression/5614.phpt new file mode 100644 index 00000000000..ac69cf4de35 --- /dev/null +++ b/tests/end-to-end/regression/5614.phpt @@ -0,0 +1,20 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5614 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s MB + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/regression/5614/Issue5614Test.php b/tests/end-to-end/regression/5614/Issue5614Test.php new file mode 100644 index 00000000000..4866aeb99ad --- /dev/null +++ b/tests/end-to-end/regression/5614/Issue5614Test.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5614; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class Issue5614Test extends TestCase +{ + public static function provideRecursiveArray(): iterable + { + $array = []; + $array[0] = &$array; + + yield [$array]; + } + + #[DataProvider('provideRecursiveArray')] + public function testRecursiveArray(array $array): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/5616.phpt b/tests/end-to-end/regression/5616.phpt new file mode 100644 index 00000000000..506ced27b6c --- /dev/null +++ b/tests/end-to-end/regression/5616.phpt @@ -0,0 +1,28 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5616 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +F 1 / 1 (100%) + +Time: %s, Memory: %s MB + +There was 1 failure: + +1) PHPUnit\TestFixture\Issue5616\Issue5616Test::testOne#0 with data (1, '2', 3.0, true) +Failed asserting that false is true. + +%sIssue5616Test.php:%d + +FAILURES! +Tests: 1, Assertions: 1, Failures: 1. diff --git a/tests/end-to-end/regression/5616/Issue5616Test.php b/tests/end-to-end/regression/5616/Issue5616Test.php new file mode 100644 index 00000000000..48f670ea436 --- /dev/null +++ b/tests/end-to-end/regression/5616/Issue5616Test.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5616; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class Issue5616Test extends TestCase +{ + public static function provider(): array + { + return [ + [1, '2', 3.0, true], + ]; + } + + #[DataProvider('provider')] + public function testOne(int $a, string $b, float $c, bool $d): void + { + $this->assertTrue(false); + } +} diff --git a/tests/end-to-end/regression/5760.phpt b/tests/end-to-end/regression/5760.phpt new file mode 100644 index 00000000000..03cf2c61358 --- /dev/null +++ b/tests/end-to-end/regression/5760.phpt @@ -0,0 +1,31 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5760 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +E 1 / 1 (100%) + +Time: %s, Memory: %s + +Issue5760 (PHPUnit\TestFixture\Issue5760\Issue5760) + ✘ One + │ + │ Exception: message + │ + │ %s:19 + │ + +ERRORS! +Tests: 1, Assertions: 0, Errors: 1. diff --git a/tests/end-to-end/regression/5760/Issue5760Test.php b/tests/end-to-end/regression/5760/Issue5760Test.php new file mode 100644 index 00000000000..b4babfb9f11 --- /dev/null +++ b/tests/end-to-end/regression/5760/Issue5760Test.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5760; + +use Exception; +use PHPUnit\Framework\TestCase; + +final class Issue5760Test extends TestCase +{ + protected function setUp(): void + { + throw new Exception('message'); + } + + public function testOne(): void + { + } +} diff --git a/tests/end-to-end/regression/5764/5764.phpt b/tests/end-to-end/regression/5764/5764.phpt new file mode 100644 index 00000000000..5c56917a27e --- /dev/null +++ b/tests/end-to-end/regression/5764/5764.phpt @@ -0,0 +1,21 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5764 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +There was 1 PHPUnit test runner warning: + +1) No tests found in class "PHPUnit\TestFixture\Issue5764\Issue5764Test". + +No tests executed! diff --git a/tests/end-to-end/regression/5764/error-handler.php b/tests/end-to-end/regression/5764/error-handler.php new file mode 100644 index 00000000000..0c245d77722 --- /dev/null +++ b/tests/end-to-end/regression/5764/error-handler.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +\set_error_handler(static function (int $err_lvl, string $err_msg, string $err_file, int $err_line): bool +{ + throw new ErrorException($err_msg, 0, $err_lvl, $err_file, $err_line); +}); diff --git a/tests/end-to-end/regression/5764/phpunit.xml b/tests/end-to-end/regression/5764/phpunit.xml new file mode 100644 index 00000000000..e3f2c4b08fd --- /dev/null +++ b/tests/end-to-end/regression/5764/phpunit.xml @@ -0,0 +1,11 @@ + + + + + tests + + + diff --git a/tests/end-to-end/regression/5764/tests/Issue5764Test.php b/tests/end-to-end/regression/5764/tests/Issue5764Test.php new file mode 100644 index 00000000000..c1d2614fd43 --- /dev/null +++ b/tests/end-to-end/regression/5764/tests/Issue5764Test.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5764; + +use PHPUnit\Framework\TestCase; + +final class Issue5764Test extends TestCase +{ +} diff --git a/tests/end-to-end/regression/5771.phpt b/tests/end-to-end/regression/5771.phpt new file mode 100644 index 00000000000..c9e6c1a972a --- /dev/null +++ b/tests/end-to-end/regression/5771.phpt @@ -0,0 +1,24 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5771 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- + + + + + PHPUnit\TestFixture\Issue5771\Issue5771Test::testOne%A +Test was run in child process and ended unexpectedly + + + diff --git a/tests/end-to-end/regression/5771/Issue5771Test.php b/tests/end-to-end/regression/5771/Issue5771Test.php new file mode 100644 index 00000000000..33147cdfac6 --- /dev/null +++ b/tests/end-to-end/regression/5771/Issue5771Test.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5771; + +use PHPUnit\Framework\Attributes\RunInSeparateProcess; +use PHPUnit\Framework\TestCase; + +final class Issue5771Test extends TestCase +{ + #[RunInSeparateProcess] + public function testOne(): void + { + exit; + } +} diff --git a/tests/end-to-end/regression/5807.phpt b/tests/end-to-end/regression/5807.phpt new file mode 100644 index 00000000000..ed2f9e6a93d --- /dev/null +++ b/tests/end-to-end/regression/5807.phpt @@ -0,0 +1,22 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5807 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/regression/5807/phpunit.xml b/tests/end-to-end/regression/5807/phpunit.xml new file mode 100644 index 00000000000..c298a260cbd --- /dev/null +++ b/tests/end-to-end/regression/5807/phpunit.xml @@ -0,0 +1,17 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/regression/5807/src/Greeter.php b/tests/end-to-end/regression/5807/src/Greeter.php new file mode 100644 index 00000000000..afa3b4afb53 --- /dev/null +++ b/tests/end-to-end/regression/5807/src/Greeter.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5807; + +final class Greeter +{ + public function greet(): string + { + return 'Hello world!'; + } +} diff --git a/tests/end-to-end/regression/5807/tests/GreeterTest.php b/tests/end-to-end/regression/5807/tests/GreeterTest.php new file mode 100644 index 00000000000..b748b91c801 --- /dev/null +++ b/tests/end-to-end/regression/5807/tests/GreeterTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5807; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\TestCase; + +#[CoversMethod(Greeter::class, 'greet')] +final class GreeterTest extends TestCase +{ + public function testGreets(): void + { + $this->assertSame('Hello world!', (new Greeter)->greet()); + } +} diff --git a/tests/end-to-end/regression/581.phpt b/tests/end-to-end/regression/581.phpt new file mode 100644 index 00000000000..45048a55862 --- /dev/null +++ b/tests/end-to-end/regression/581.phpt @@ -0,0 +1,40 @@ +--TEST-- +GH-581: PHPUnit_Util_Type::export adds extra newlines in Windows +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +F 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 failure: + +1) PHPUnit\TestFixture\Issue581Test::testExportingObjectsDoesNotBreakWindowsLineFeeds +Failed asserting that two objects are equal. +--- Expected ++++ Actual +@@ @@ + 1 => 2 + 2 => 'Test\r\n' + 3 => 4 +- 4 => 5 ++ 4 => 1 + 5 => 6 + 6 => 7 + 7 => 8 + ) + +%s:%i + +FAILURES! +Tests: 1, Assertions: 1, Failures: 1. diff --git a/tests/end-to-end/regression/GitHub/581/Issue581Test.php b/tests/end-to-end/regression/581/Issue581Test.php similarity index 85% rename from tests/end-to-end/regression/GitHub/581/Issue581Test.php rename to tests/end-to-end/regression/581/Issue581Test.php index 61171e30b95..e150e89d7a2 100644 --- a/tests/end-to-end/regression/GitHub/581/Issue581Test.php +++ b/tests/end-to-end/regression/581/Issue581Test.php @@ -7,6 +7,8 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + use PHPUnit\Framework\TestCase; class Issue581Test extends TestCase @@ -15,7 +17,7 @@ public function testExportingObjectsDoesNotBreakWindowsLineFeeds(): void { $this->assertEquals( (object) [1, 2, "Test\r\n", 4, 5, 6, 7, 8], - (object) [1, 2, "Test\r\n", 4, 1, 6, 7, 8] + (object) [1, 2, "Test\r\n", 4, 1, 6, 7, 8], ); } } diff --git a/tests/end-to-end/regression/5822.phpt b/tests/end-to-end/regression/5822.phpt new file mode 100644 index 00000000000..2440499ae14 --- /dev/null +++ b/tests/end-to-end/regression/5822.phpt @@ -0,0 +1,22 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/pull/5592 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +D 1 / 1 (100%) + +Time: %s, Memory: %s + +OK, but there were issues! +Tests: 1, Assertions: 1, Deprecations: 1. diff --git a/tests/end-to-end/regression/5822/phpunit.xml b/tests/end-to-end/regression/5822/phpunit.xml new file mode 100644 index 00000000000..867b70dc246 --- /dev/null +++ b/tests/end-to-end/regression/5822/phpunit.xml @@ -0,0 +1,15 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/regression/5822/src/.keep b/tests/end-to-end/regression/5822/src/.keep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/end-to-end/regression/5822/tests/Issue5822Test.php b/tests/end-to-end/regression/5822/tests/Issue5822Test.php new file mode 100644 index 00000000000..7e746ffb447 --- /dev/null +++ b/tests/end-to-end/regression/5822/tests/Issue5822Test.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5822; + +use const E_USER_DEPRECATED; +use function call_user_func; +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class Issue5822Test extends TestCase +{ + public function testDebugBacktrace(): void + { + $this->callUserFuncExample(); + $this->assertTrue(true); + } + + private function callUserFuncExample(): void + { + call_user_func([$this, 'exampleCallback']); + } + + private function exampleCallback(): void + { + trigger_error('My Deprecation Error', E_USER_DEPRECATED); + } +} diff --git a/tests/end-to-end/regression/5844.phpt b/tests/end-to-end/regression/5844.phpt new file mode 100644 index 00000000000..a4907d37366 --- /dev/null +++ b/tests/end-to-end/regression/5844.phpt @@ -0,0 +1,34 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5844 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Bootstrap Finished (%s5844%sbootstrap.php) +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Issue5844\Issue5844Test, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Issue5844\Issue5844Test::testOne) +Test Considered Risky (PHPUnit\TestFixture\Issue5844\Issue5844Test::testOne) +At least one error handler is not callable outside the scope it was registered in +Test Prepared (PHPUnit\TestFixture\Issue5844\Issue5844Test::testOne) +Test Passed (PHPUnit\TestFixture\Issue5844\Issue5844Test::testOne) +Test Finished (PHPUnit\TestFixture\Issue5844\Issue5844Test::testOne) +Test Suite Finished (PHPUnit\TestFixture\Issue5844\Issue5844Test, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/regression/5844/Issue5844Test.php b/tests/end-to-end/regression/5844/Issue5844Test.php new file mode 100644 index 00000000000..5fe84a20279 --- /dev/null +++ b/tests/end-to-end/regression/5844/Issue5844Test.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5844; + +use PHPUnit\Framework\TestCase; + +final class Issue5844Test extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/5844/bootstrap.php b/tests/end-to-end/regression/5844/bootstrap.php new file mode 100644 index 00000000000..47bdc13d701 --- /dev/null +++ b/tests/end-to-end/regression/5844/bootstrap.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5844; + +use function set_error_handler; + +final class CustomErrorHandler +{ + public function __construct() + { + set_error_handler([$this, 'handleError']); + } + + private function handleError(): void + { + } +} + +new CustomErrorHandler; diff --git a/tests/end-to-end/regression/5875.phpt b/tests/end-to-end/regression/5875.phpt new file mode 100644 index 00000000000..67b658c9044 --- /dev/null +++ b/tests/end-to-end/regression/5875.phpt @@ -0,0 +1,21 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5875 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +..... 5 / 5 (100%) + +Time: %s, Memory: %s + +OK (5 tests, 5 assertions) diff --git a/tests/end-to-end/regression/5875/Issue5875Test.php b/tests/end-to-end/regression/5875/Issue5875Test.php new file mode 100644 index 00000000000..3940e8cf51d --- /dev/null +++ b/tests/end-to-end/regression/5875/Issue5875Test.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5875; + +use PHPUnit\Framework\Attributes\Depends; +use PHPUnit\Framework\Attributes\TestWith; +use PHPUnit\Framework\TestCase; + +final class Issue5875Test extends TestCase +{ + private static int $destructsDone = 0; + + public function __destruct() + { + self::$destructsDone++; + } + + public function testFirstTest(): void + { + $this->assertSame(0, self::$destructsDone); + } + + #[Depends('testFirstTest')] + public function testSecondTest(): void + { + $this->assertSame(1, self::$destructsDone); + } + + #[Depends('testSecondTest')] + #[TestWith([2])] + #[TestWith([3])] + #[TestWith([4])] + public function testThirdTestWhichUsesDataProvider($numberOfTestsBeforeThisOne): void + { + $this->assertSame($numberOfTestsBeforeThisOne, self::$destructsDone); + } +} diff --git a/tests/end-to-end/regression/5884.phpt b/tests/end-to-end/regression/5884.phpt new file mode 100644 index 00000000000..1a1105b2c01 --- /dev/null +++ b/tests/end-to-end/regression/5884.phpt @@ -0,0 +1,36 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5884 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +Time: %s, Memory: %s + +Foo (PHPUnit\TestFixture\Issue5884\Foo) + ⚠ Expect user deprecation message n o t ignoring deprecations + ✔ Expect user deprecation message a n d ignoring deprecations + ✔ Pcre has utf 8 support + ✔ Stream to non writable file with p h p unit error handler + ✔ Stream to non writable file without p h p unit error handler + ✔ Stream to invalid file + +1 test triggered 1 deprecation: + +1) %sFooTest.php:31 +foo + +OK, but there were issues! +Tests: 6, Assertions: 7, Deprecations: 1. diff --git a/tests/end-to-end/regression/5884/phpunit.xml b/tests/end-to-end/regression/5884/phpunit.xml new file mode 100644 index 00000000000..517ddd75e48 --- /dev/null +++ b/tests/end-to-end/regression/5884/phpunit.xml @@ -0,0 +1,21 @@ + + + + + tests + + + diff --git a/tests/end-to-end/regression/5884/src/Foo.php b/tests/end-to-end/regression/5884/src/Foo.php new file mode 100644 index 00000000000..6f973bc4fd7 --- /dev/null +++ b/tests/end-to-end/regression/5884/src/Foo.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5884; + +use function error_get_last; +use function fopen; +use function is_array; +use function preg_match; +use Exception; + +final class Foo +{ + public static function pcreHasUtf8Support() + { + // This regex deliberately has a compile error to demonstrate the issue. + return (bool) @preg_match('/^.[/u', 'a'); + } + + public static function openFile($filename): void + { + // Silenced the PHP native warning in favour of throwing an exception. + $download = @fopen($filename, 'wb'); + + if ($download === false) { + $error = error_get_last(); + + if (!is_array($error)) { + // Shouldn't be possible, but can happen in test situations. + $error = ['message' => 'Failed to open stream']; + } + + throw new Exception($error['message']); + } + } +} diff --git a/tests/end-to-end/regression/5884/tests/FooTest.php b/tests/end-to-end/regression/5884/tests/FooTest.php new file mode 100644 index 00000000000..a1a5ce0213a --- /dev/null +++ b/tests/end-to-end/regression/5884/tests/FooTest.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5884; + +use const E_USER_DEPRECATED; +use function chmod; +use function file_get_contents; +use function file_put_contents; +use function sys_get_temp_dir; +use function tempnam; +use function trigger_error; +use function unlink; +use Exception; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; +use PHPUnit\Framework\Attributes\WithoutErrorHandler; +use PHPUnit\Framework\TestCase; + +final class FooTest extends TestCase +{ + public function testExpectUserDeprecationMessageNOTIgnoringDeprecations(): void + { + $this->expectUserDeprecationMessage('foo'); + + trigger_error('foo', E_USER_DEPRECATED); + } + + #[IgnoreDeprecations] + public function testExpectUserDeprecationMessageANDIgnoringDeprecations(): void + { + $this->expectUserDeprecationMessage('foo'); + + trigger_error('foo', E_USER_DEPRECATED); + } + + public function testPcreHasUtf8Support(): void + { + $this->assertIsBool(Foo::pcreHasUtf8Support()); + } + + public function testStreamToNonWritableFileWithPHPUnitErrorHandler(): void + { + // Create an unwritable file. + $filename = tempnam(sys_get_temp_dir(), 'RLT'); + + if (file_put_contents($filename, 'foo')) { + chmod($filename, 0o444); + } + + try { + Foo::openFile($filename); + } catch (Exception $e) { + // This "Failed to open stream" exception is expected. + } + + // Now verify the original file is unchanged. + $contents = file_get_contents($filename); + + chmod($filename, 0o755); + unlink($filename); + + $this->assertSame('foo', $contents); + } + + #[WithoutErrorHandler] + public function testStreamToNonWritableFileWithoutPHPUnitErrorHandler(): void + { + // Create an unwritable file. + $filename = tempnam(sys_get_temp_dir(), 'RLT'); + + if (file_put_contents($filename, 'foo')) { + chmod($filename, 0o444); + } + + try { + Foo::openFile($filename); + } catch (Exception $e) { + // This "Failed to open stream" exception is expected. + } + + // Now verify the original file is unchanged. + $contents = file_get_contents($filename); + + chmod($filename, 0o755); + unlink($filename); + + $this->assertSame('foo', $contents); + } + + public function testStreamToInvalidFile(): void + { + $filename = tempnam(sys_get_temp_dir(), 'RLT') . '/missing/directory'; + + $this->expectException(Exception::class); + // First character (F) can be upper or lowercase depending on PHP version. + $this->expectExceptionMessage('ailed to open stream'); + + Foo::openFile($filename); + } +} diff --git a/tests/end-to-end/regression/5891.phpt b/tests/end-to-end/regression/5891.phpt new file mode 100644 index 00000000000..f10afad8236 --- /dev/null +++ b/tests/end-to-end/regression/5891.phpt @@ -0,0 +1,20 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5891 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +.. 2 / 2 (100%) + +Time: %s, Memory: %s MB + +OK (2 tests, 4 assertions) diff --git a/tests/end-to-end/regression/5891/Issue5891Test.php b/tests/end-to-end/regression/5891/Issue5891Test.php new file mode 100644 index 00000000000..d22c5ed846d --- /dev/null +++ b/tests/end-to-end/regression/5891/Issue5891Test.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5891; + +use PHPUnit\Framework\TestCase; + +final class Issue5891Test extends TestCase +{ + public function testVariadicParam(): void + { + $mock = $this->createMock(ArgumentList::class); + $mock + ->method('foo') + ->with($this->callback(static function (...$items): bool + { + self::assertSame([1, 2, 3], $items); + + return true; + })); + + $mock->foo(1, 2, 3); + } + + public function testTwoParametersAndVariadicParam(): void + { + $mock = $this->createMock(TwoParametersAndArgumentList::class); + $mock + ->method('foo') + ->with($this->callback(static function (...$items): bool + { + self::assertSame(['1st', '2nd', '3rd', '4th'], $items); + + return true; + })); + + $mock->foo('1st', '2nd', '3rd', '4th'); + } +} + +interface ArgumentList +{ + public function foo(int ...$items): void; +} + +interface TwoParametersAndArgumentList +{ + public function foo(string $first, string $second, string ...$rest): void; +} diff --git a/tests/end-to-end/regression/5898.phpt b/tests/end-to-end/regression/5898.phpt new file mode 100644 index 00000000000..b65a285f62d --- /dev/null +++ b/tests/end-to-end/regression/5898.phpt @@ -0,0 +1,31 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5898 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (%s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (%s5898.phpt, 1 test) +Test Preparation Started (%s5898.phpt) +Test Prepared (%s5898.phpt) +Child Process Started +Child Process Finished +Test Passed (%s5898.phpt) +Test Finished (%s5898.phpt) +Test Suite Finished (%s5898.phpt, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/regression/5898/5898.phpt b/tests/end-to-end/regression/5898/5898.phpt new file mode 100644 index 00000000000..3cdf2a2d8ee --- /dev/null +++ b/tests/end-to-end/regression/5898/5898.phpt @@ -0,0 +1,7 @@ +--TEST-- +PHPT test that passes +--FILE-- +run($_SERVER['argv']); + +unlink($file); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +There were errors: + +The data provider PHPUnit\TestFixture\Issue5908\Issue5908Test::provider specified for PHPUnit\TestFixture\Issue5908\Issue5908Test::testOne is invalid +message diff --git a/tests/end-to-end/regression/5908-list-tests.phpt b/tests/end-to-end/regression/5908-list-tests.phpt new file mode 100644 index 00000000000..b83c346cfcb --- /dev/null +++ b/tests/end-to-end/regression/5908-list-tests.phpt @@ -0,0 +1,19 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5908 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +There were errors: + +The data provider PHPUnit\TestFixture\Issue5908\Issue5908Test::provider specified for PHPUnit\TestFixture\Issue5908\Issue5908Test::testOne is invalid +message diff --git a/tests/end-to-end/regression/5908/Issue5908Test.php b/tests/end-to-end/regression/5908/Issue5908Test.php new file mode 100644 index 00000000000..e3e6658d2a6 --- /dev/null +++ b/tests/end-to-end/regression/5908/Issue5908Test.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5908; + +use Exception; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class Issue5908Test extends TestCase +{ + public static function provider(): array + { + throw new Exception('message'); + } + + #[DataProvider('provider')] + public function testOne(int $value): void + { + } +} diff --git a/tests/end-to-end/regression/5943.phpt b/tests/end-to-end/regression/5943.phpt new file mode 100644 index 00000000000..6745c8946c8 --- /dev/null +++ b/tests/end-to-end/regression/5943.phpt @@ -0,0 +1,20 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5943 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Available test groups: + - bar (1 test) + - barbara (1 test) + - baz (1 test) + - foo (1 test) diff --git a/tests/end-to-end/regression/5943/phpunit.xml b/tests/end-to-end/regression/5943/phpunit.xml new file mode 100644 index 00000000000..bb66b5abc66 --- /dev/null +++ b/tests/end-to-end/regression/5943/phpunit.xml @@ -0,0 +1,11 @@ + + + + + tests/a + tests/b + + + diff --git a/tests/end-to-end/regression/5943/tests/a/OneTest.php b/tests/end-to-end/regression/5943/tests/a/OneTest.php new file mode 100644 index 00000000000..1b5ead72569 --- /dev/null +++ b/tests/end-to-end/regression/5943/tests/a/OneTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5943; + +use PHPUnit\Framework\TestCase; + +final class OneTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/5943/tests/b/TwoTest.php b/tests/end-to-end/regression/5943/tests/b/TwoTest.php new file mode 100644 index 00000000000..6479eacece7 --- /dev/null +++ b/tests/end-to-end/regression/5943/tests/b/TwoTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5943; + +use PHPUnit\Framework\TestCase; + +final class TwoTest extends TestCase +{ + public function testTwo(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/5949.phpt b/tests/end-to-end/regression/5949.phpt new file mode 100644 index 00000000000..4bbe1b277d0 --- /dev/null +++ b/tests/end-to-end/regression/5949.phpt @@ -0,0 +1,41 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5949 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +........ 8 / 8 (100%) + +Time: %s, Memory: %s + +Issue5949 (PHPUnit\TestFixture\Issue5949\Issue5949) + ✔ Test 1. No dollar sign. + + ✔ Test 2. No dollar sign. + + ✔ Test 3. Dollar sign ($). + + ✔ Test 4. No dollar sign. + + ✔ Test 5. Dollar $ sign. + More text. + + ✔ Test 6. No dollar sign. + + ✔ Test 7. No dollar sign. + + ✔ Test 8. No dollar sign. + + +OK (8 tests, 8 assertions) diff --git a/tests/end-to-end/regression/5949/Issue5949Test.php b/tests/end-to-end/regression/5949/Issue5949Test.php new file mode 100644 index 00000000000..28610250275 --- /dev/null +++ b/tests/end-to-end/regression/5949/Issue5949Test.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5949; + +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +class Issue5949Test extends TestCase +{ + #[TestDox("Test 1. No dollar sign.\n")] + public function test1(): void + { + $this->assertTrue(true); + } + + #[TestDox("Test 2. No dollar sign.\n")] + public function test2(): void + { + $this->assertTrue(true); + } + + #[TestDox("Test 3. Dollar sign (\$).\n")] + public function test3(): void + { + $this->assertTrue(true); + } + + #[TestDox("Test 4. No dollar sign.\n")] + public function test4(): void + { + $this->assertTrue(true); + } + + #[TestDox("Test 5. Dollar \$ sign.\n More text.\n")] + public function test5(): void + { + $this->assertTrue(true); + } + + #[TestDox("Test 6. No dollar sign.\n")] + public function test6(): void + { + $this->assertTrue(true); + } + + #[TestDox("Test 7. No dollar sign.\n")] + public function test7(): void + { + $this->assertTrue(true); + } + + #[TestDox("Test 8. No dollar sign.\n")] + public function test8(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/5965.phpt b/tests/end-to-end/regression/5965.phpt new file mode 100644 index 00000000000..3d61438eb7b --- /dev/null +++ b/tests/end-to-end/regression/5965.phpt @@ -0,0 +1,35 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5965 +--SKIPIF-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Issue5891\Issue5965Test, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Issue5891\Issue5965Test::testOne) +Test Prepared (PHPUnit\TestFixture\Issue5891\Issue5965Test::testOne) +Test Errored (PHPUnit\TestFixture\Issue5891\Issue5965Test::testOne) +(exception code: HY000) +Test Finished (PHPUnit\TestFixture\Issue5891\Issue5965Test::testOne) +Test Suite Finished (PHPUnit\TestFixture\Issue5891\Issue5965Test, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/regression/5965/Issue5965Test.php b/tests/end-to-end/regression/5965/Issue5965Test.php new file mode 100644 index 00000000000..2345a1fccee --- /dev/null +++ b/tests/end-to-end/regression/5965/Issue5965Test.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5891; + +use IteratorAggregate; +use PDOException; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; +use PHPUnit\Framework\TestCase; +use ReflectionClass; +use Traversable; + +#[RequiresPhpExtension('pdo')] +final class Issue5965Test extends TestCase +{ + public function testOne(): void + { + $exception = new PDOException; + $reflector = new ReflectionClass($exception); + + $property = $reflector->getProperty('code'); + $property->setValue($exception, 'HY000'); + + $this->assertIsString($exception->getCode()); + + $iterator = new class($exception) implements IteratorAggregate + { + public PDOException $exception; + + public function __construct($exception) + { + $this->exception = $exception; + } + + public function getIterator(): Traversable + { + throw $this->exception; + } + }; + + $this->assertCount(0, $iterator); + } +} diff --git a/tests/end-to-end/regression/5976.phpt b/tests/end-to-end/regression/5976.phpt new file mode 100644 index 00000000000..66d70c84127 --- /dev/null +++ b/tests/end-to-end/regression/5976.phpt @@ -0,0 +1,30 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5976 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +E 1 / 1 (100%) + +Time: %s, Memory: %s + +These before-first-test methods errored: + +1) PHPUnit\TestFixture\Issue5967\Issue5976Test::setUpBeforeClass +Exception: message + +%sIssue5976Test.php:%d + +ERRORS! +Tests: 1, Assertions: 0, Errors: 1. diff --git a/tests/end-to-end/regression/5976/Issue5976Test.php b/tests/end-to-end/regression/5976/Issue5976Test.php new file mode 100644 index 00000000000..adbae81ecc4 --- /dev/null +++ b/tests/end-to-end/regression/5976/Issue5976Test.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue5967; + +use Exception; +use PHPUnit\Framework\TestCase; + +final class Issue5976Test extends TestCase +{ + /** + * @throws Exception + */ + public static function setUpBeforeClass(): void + { + throw new Exception('message'); + } + + public function testOne(): void + { + } +} diff --git a/tests/end-to-end/regression/5991.phpt b/tests/end-to-end/regression/5991.phpt new file mode 100644 index 00000000000..d70be538ffc --- /dev/null +++ b/tests/end-to-end/regression/5991.phpt @@ -0,0 +1,28 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/5991 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) SKIPIF section triggered a parse error: +Parse error: Unmatched '}' in Standard input code on line 3 + + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/end-to-end/regression/6094.phpt b/tests/end-to-end/regression/6094.phpt new file mode 100644 index 00000000000..d90631a65c6 --- /dev/null +++ b/tests/end-to-end/regression/6094.phpt @@ -0,0 +1,29 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6094 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 error: + +1) PHPUnit\TestFixture\Issue6094\Issue6094Test +Exception: message + +%sIssue6094Test.php:19 + +ERRORS! +Tests: 1, Assertions: 1, Errors: 1. diff --git a/tests/end-to-end/regression/6094/Issue6094Test.php b/tests/end-to-end/regression/6094/Issue6094Test.php new file mode 100644 index 00000000000..c213f926373 --- /dev/null +++ b/tests/end-to-end/regression/6094/Issue6094Test.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue6094; + +use Exception; +use PHPUnit\Framework\TestCase; + +final class Issue6094Test extends TestCase +{ + public static function tearDownAfterClass(): void + { + throw new Exception('message'); + } + + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/6095.phpt b/tests/end-to-end/regression/6095.phpt new file mode 100644 index 00000000000..33b0898e028 --- /dev/null +++ b/tests/end-to-end/regression/6095.phpt @@ -0,0 +1,29 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6095 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +F 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 failure: + +1) PHPUnit\TestFixture\Issue6095\Issue6095Test::testOne +PHPUnit\TestFixture\MockObject\AnInterface::doSomething(): bool was not expected to be called more than once. + +%sIssue6095Test.php:26 + +FAILURES! +Tests: 1, Assertions: 1, Failures: 1. diff --git a/tests/end-to-end/regression/6095/Issue6095Test.php b/tests/end-to-end/regression/6095/Issue6095Test.php new file mode 100644 index 00000000000..9c737beaca1 --- /dev/null +++ b/tests/end-to-end/regression/6095/Issue6095Test.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue6095; + +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\MockObject\AnInterface; + +final class Issue6095Test extends TestCase +{ + public function testOne(): void + { + $mock = $this->createMock(AnInterface::class); + + $mock + ->expects($this->once()) + ->method('doSomething'); + + $mock->doSomething(); + $mock->doSomething(); + } +} diff --git a/tests/end-to-end/regression/6098.phpt b/tests/end-to-end/regression/6098.phpt new file mode 100644 index 00000000000..400cca6ba30 --- /dev/null +++ b/tests/end-to-end/regression/6098.phpt @@ -0,0 +1,23 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6098 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- + + + + + output + + + diff --git a/tests/end-to-end/regression/6098/Issue6098Test.php b/tests/end-to-end/regression/6098/Issue6098Test.php new file mode 100644 index 00000000000..4ef2f374411 --- /dev/null +++ b/tests/end-to-end/regression/6098/Issue6098Test.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue6098; + +use PHPUnit\Framework\TestCase; + +final class Issue6098Test extends TestCase +{ + public function testOne(): void + { + print 'output'; + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/6100.phpt b/tests/end-to-end/regression/6100.phpt new file mode 100644 index 00000000000..f4be3532fb7 --- /dev/null +++ b/tests/end-to-end/regression/6100.phpt @@ -0,0 +1,37 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6100 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (PHPUnit\TestFixture\Issue6100\Issue6100Test, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\Issue6100\Issue6100Test::testOne) +Test Prepared (PHPUnit\TestFixture\Issue6100\Issue6100Test::testOne) +Test Triggered Deprecation (PHPUnit\TestFixture\Issue6100\Issue6100Test::testOne, unknown if issue was triggered in first-party code or third-party code, suppressed using operator) in %s:%d +test +Test Passed (PHPUnit\TestFixture\Issue6100\Issue6100Test::testOne) +Test Finished (PHPUnit\TestFixture\Issue6100\Issue6100Test::testOne) +Test Preparation Started (PHPUnit\TestFixture\Issue6100\Issue6100Test::testTwo) +Test Prepared (PHPUnit\TestFixture\Issue6100\Issue6100Test::testTwo) +Test Passed (PHPUnit\TestFixture\Issue6100\Issue6100Test::testTwo) +Test Finished (PHPUnit\TestFixture\Issue6100\Issue6100Test::testTwo) +Test Suite Finished (PHPUnit\TestFixture\Issue6100\Issue6100Test, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/regression/6100/Issue6100Test.php b/tests/end-to-end/regression/6100/Issue6100Test.php new file mode 100644 index 00000000000..7432d150e17 --- /dev/null +++ b/tests/end-to-end/regression/6100/Issue6100Test.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue6100; + +use const E_USER_DEPRECATED; +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class Issue6100Test extends TestCase +{ + public function testOne(): void + { + @trigger_error('test', E_USER_DEPRECATED); + + $this->assertTrue(true); + } + + public function testTwo(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/6102-process-isolation-via-attribute.phpt b/tests/end-to-end/regression/6102-process-isolation-via-attribute.phpt new file mode 100644 index 00000000000..df41f69a556 --- /dev/null +++ b/tests/end-to-end/regression/6102-process-isolation-via-attribute.phpt @@ -0,0 +1,38 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6102 +--XFAIL-- +https://github.com/sebastianbergmann/phpunit/issues/6102 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +..FF..FF 8 / 8 (100%) + +Time: %s, Memory: %s + +There were 4 failures: + +1) PHPUnit\TestFixture\Event\TestForDeprecatedFeatureInIsolationTest::testExpectationOnExactDeprecationMessageWorksWhenExpectedDeprecationIsNotTriggered +Expected deprecation with message "message" was not triggered + +2) PHPUnit\TestFixture\Event\TestForDeprecatedFeatureInIsolationTest::testExpectationOnExactDeprecationMessageWorksWhenUnexpectedDeprecationIsTriggered +Expected deprecation with message "message" was not triggered + +3) PHPUnit\TestFixture\Event\TestForDeprecatedFeatureInIsolationTest::testExpectationOnDeprecationMessageMatchingRegularExpressionWorksWhenExpectedDeprecationIsNotTriggered +Expected deprecation with message matching regular expression "/message/" was not triggered + +4) PHPUnit\TestFixture\Event\TestForDeprecatedFeatureInIsolationTest::testExpectationOnDeprecationMessageMatchingRegularExpressionWorksWhenUnepectedDeprecationIsTriggered +Expected deprecation with message matching regular expression "/message/" was not triggered + +FAILURES! +Tests: 8, Assertions: 10, Failures: 4. diff --git a/tests/end-to-end/regression/6102-process-isolation-via-cli-option.phpt b/tests/end-to-end/regression/6102-process-isolation-via-cli-option.phpt new file mode 100644 index 00000000000..5b6c346b8f4 --- /dev/null +++ b/tests/end-to-end/regression/6102-process-isolation-via-cli-option.phpt @@ -0,0 +1,39 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6102 +--XFAIL-- +https://github.com/sebastianbergmann/phpunit/issues/6102 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +..FF..FF 8 / 8 (100%) + +Time: %s, Memory: %s + +There were 4 failures: + +1) PHPUnit\TestFixture\Event\TestForDeprecatedFeatureTest::testExpectationOnExactDeprecationMessageWorksWhenExpectedDeprecationIsNotTriggered +Expected deprecation with message "message" was not triggered + +2) PHPUnit\TestFixture\Event\TestForDeprecatedFeatureTest::testExpectationOnExactDeprecationMessageWorksWhenUnexpectedDeprecationIsTriggered +Expected deprecation with message "message" was not triggered + +3) PHPUnit\TestFixture\Event\TestForDeprecatedFeatureTest::testExpectationOnDeprecationMessageMatchingRegularExpressionWorksWhenExpectedDeprecationIsNotTriggered +Expected deprecation with message matching regular expression "/message/" was not triggered + +4) PHPUnit\TestFixture\Event\TestForDeprecatedFeatureTest::testExpectationOnDeprecationMessageMatchingRegularExpressionWorksWhenUnepectedDeprecationIsTriggered +Expected deprecation with message matching regular expression "/message/" was not triggered + +FAILURES! +Tests: 8, Assertions: 10, Failures: 4. diff --git a/tests/end-to-end/regression/6103.phpt b/tests/end-to-end/regression/6103.phpt new file mode 100644 index 00000000000..cbebf70c862 --- /dev/null +++ b/tests/end-to-end/regression/6103.phpt @@ -0,0 +1,21 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6103 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +*. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/regression/6103/Issue6103Test.php b/tests/end-to-end/regression/6103/Issue6103Test.php new file mode 100644 index 00000000000..be6488a2644 --- /dev/null +++ b/tests/end-to-end/regression/6103/Issue6103Test.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue6103; + +use PHPUnit\Framework\Attributes\RunInSeparateProcess; +use PHPUnit\Framework\TestCase; + +final class Issue6103Test extends TestCase +{ + #[RunInSeparateProcess] + public function testOne(): void + { + print '*'; + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/6105.phpt b/tests/end-to-end/regression/6105.phpt new file mode 100644 index 00000000000..df4806878be --- /dev/null +++ b/tests/end-to-end/regression/6105.phpt @@ -0,0 +1,41 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6105 +--SKIPIF-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +.. 2 / 2 (100%) + +Time: %s, Memory: %s + +OK (2 tests, 3 assertions) diff --git a/tests/end-to-end/regression/6105/IssueTest6105.php b/tests/end-to-end/regression/6105/IssueTest6105.php new file mode 100644 index 00000000000..b2467042fcc --- /dev/null +++ b/tests/end-to-end/regression/6105/IssueTest6105.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace IssueTest6105; + +use function header; +use function ob_get_clean; +use function ob_start; +use function xdebug_get_headers; +use PHPUnit\Framework\Attributes\CoversNothing; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; +use PHPUnit\Framework\Attributes\RunInSeparateProcess; +use PHPUnit\Framework\TestCase; + +#[CoversNothing] +class IssueTest6105 extends TestCase +{ + public function test_1(): void + { + $this->assertTrue(true); + } + + #[RunInSeparateProcess] + #[RequiresPhpExtension('xdebug')] + public function test_case_2_check(): void + { + ob_start(); + header('X-Test: Testing'); + print 'asd'; + $content = ob_get_clean(); + + $this->assertSame('asd', $content); + $this->assertSame(['X-Test: Testing'], xdebug_get_headers()); + } +} diff --git a/tests/end-to-end/regression/6109.phpt b/tests/end-to-end/regression/6109.phpt new file mode 100644 index 00000000000..090d73c970a --- /dev/null +++ b/tests/end-to-end/regression/6109.phpt @@ -0,0 +1,23 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6109 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- + + + + + + + + diff --git a/tests/end-to-end/regression/6109/Issue6109Test.php b/tests/end-to-end/regression/6109/Issue6109Test.php new file mode 100644 index 00000000000..131ab6bdb28 --- /dev/null +++ b/tests/end-to-end/regression/6109/Issue6109Test.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue6109; + +use PHPUnit\Framework\TestCase; + +final class Issue6109Test extends TestCase +{ + protected function setUp(): void + { + print '*'; + + $this->markTestSkipped('message'); + } + + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/6115.phpt b/tests/end-to-end/regression/6115.phpt new file mode 100644 index 00000000000..36342e2e6e3 --- /dev/null +++ b/tests/end-to-end/regression/6115.phpt @@ -0,0 +1,25 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6115 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +Issue6115 (PHPUnit\TestFixture\Issue6115\Issue6115) + ✔ 1 + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/regression/6115/Issue6115Test.php b/tests/end-to-end/regression/6115/Issue6115Test.php new file mode 100644 index 00000000000..c323b05511d --- /dev/null +++ b/tests/end-to-end/regression/6115/Issue6115Test.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue6115; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +enum Enumeration: int +{ + case A = 1; +} + +final class Issue6115Test extends TestCase +{ + public static function provider(): array + { + return [ + [ + Enumeration::A, + ], + ]; + } + + #[DataProvider('provider')] + #[TestDox('$enumeration')] + public function testOne(Enumeration $enumeration): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/6138.phpt b/tests/end-to-end/regression/6138.phpt new file mode 100644 index 00000000000..ad8f3cb5a1f --- /dev/null +++ b/tests/end-to-end/regression/6138.phpt @@ -0,0 +1,38 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6138 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +F 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 failure: + +1) PHPUnit\TestFixture\Issue6138\Issue6138Test::testOne +Expectation failed for method name is "m" when invoked 1 time +Parameter 0 for invocation PHPUnit\TestFixture\Issue6138\I::m(PHPUnit\TestFixture\Issue6138\C Object (...)): void does not match expected value. +Failed asserting that two objects are equal. +--- Expected ++++ Actual +@@ @@ + PHPUnit\TestFixture\Issue6138\C Object ( +- 'foo' => 'bar' ++ 'foo' => 'baz' + ) + +%sIssue6138Test.php:%d + +FAILURES! +Tests: 1, Assertions: 1, Failures: 1. diff --git a/tests/end-to-end/regression/6138/Issue6138Test.php b/tests/end-to-end/regression/6138/Issue6138Test.php new file mode 100644 index 00000000000..36fa00341ff --- /dev/null +++ b/tests/end-to-end/regression/6138/Issue6138Test.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue6138; + +use PHPUnit\Framework\TestCase; + +final class C +{ + private string $foo; + + public function __construct(string $foo) + { + $this->foo = $foo; + } +} + +interface I +{ + public function m(C $c): void; +} + +final class Issue6138Test extends TestCase +{ + public function testOne(): void + { + $i = $this->createMock(I::class); + + $i->expects($this->once())->method('m')->with(new C('bar')); + + $i->m(new C('baz')); + } +} diff --git a/tests/end-to-end/regression/6142.phpt b/tests/end-to-end/regression/6142.phpt new file mode 100644 index 00000000000..52e695b40f8 --- /dev/null +++ b/tests/end-to-end/regression/6142.phpt @@ -0,0 +1,38 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6142 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +F 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 failure: + +1) PHPUnit\TestFixture\Issue6142\Issue6142Test::testOne +Failed asserting that '{"key": false}\n +' matches JSON string "{"key": true} +". +--- Expected ++++ Actual +@@ @@ + { +- "key": true ++ "key": false + } + +%sIssue6142Test.php:%d + +FAILURES! +Tests: 1, Assertions: 7, Failures: 1. diff --git a/tests/end-to-end/regression/6142/Issue6142Test.php b/tests/end-to-end/regression/6142/Issue6142Test.php new file mode 100644 index 00000000000..246189ca935 --- /dev/null +++ b/tests/end-to-end/regression/6142/Issue6142Test.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue6142; + +use PHPUnit\Framework\TestCase; + +final class Issue6142Test extends TestCase +{ + public function testOne(): void + { + $expected = __DIR__ . '/expected.json'; + $actual = __DIR__ . '/actual.json'; + + $this->assertJsonFileEqualsJsonFile($expected, $actual); + } +} diff --git a/tests/end-to-end/regression/6142/actual.json b/tests/end-to-end/regression/6142/actual.json new file mode 100644 index 00000000000..f96f9eb3185 --- /dev/null +++ b/tests/end-to-end/regression/6142/actual.json @@ -0,0 +1 @@ +{"key": false} diff --git a/tests/end-to-end/regression/6142/expected.json b/tests/end-to-end/regression/6142/expected.json new file mode 100644 index 00000000000..95ff2f8c32c --- /dev/null +++ b/tests/end-to-end/regression/6142/expected.json @@ -0,0 +1 @@ +{"key": true} diff --git a/tests/end-to-end/regression/6173.phpt b/tests/end-to-end/regression/6173.phpt new file mode 100644 index 00000000000..df6fce927b2 --- /dev/null +++ b/tests/end-to-end/regression/6173.phpt @@ -0,0 +1,31 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6173 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +hello, success! +.hello, fail! +F 2 / 2 (100%) + +Time: %s, Memory: %s + +There was 1 failure: + +1) PHPUnit\TestFixture\Issue6173\Issue6173Test::test_log_fail +Failed asserting that false is true. + +%sIssue6173Test.php:%d + +FAILURES! +Tests: 2, Assertions: 2, Failures: 1. diff --git a/tests/end-to-end/regression/6173/Issue6173Test.php b/tests/end-to-end/regression/6173/Issue6173Test.php new file mode 100644 index 00000000000..87b34efb218 --- /dev/null +++ b/tests/end-to-end/regression/6173/Issue6173Test.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue6173; + +use function error_log; +use PHPUnit\Framework\TestCase; + +final class Issue6173Test extends TestCase +{ + public function test_log_success(): void + { + error_log('hello, success!'); + $this->assertTrue(true); + } + + public function test_log_fail(): void + { + error_log('hello, fail!'); + $this->assertTrue(false); + } +} diff --git a/tests/end-to-end/regression/6222.phpt b/tests/end-to-end/regression/6222.phpt new file mode 100644 index 00000000000..e0c6ffc49e6 --- /dev/null +++ b/tests/end-to-end/regression/6222.phpt @@ -0,0 +1,55 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6222 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +F....FSFFS 10 / 10 (100%) + +Time: %s, Memory: %s + +There were 4 failures: + +1) PHPUnit\TestFixture\Issue6222\Issue6222Test::testOne +Failed asserting that false is true. + +%sIssue6222Test.php:%d + +2) PHPUnit\TestFixture\Issue6222\Issue6222Test::testOneCasePassing#1 with data (2) +Failed asserting that 2 is identical to 1. + +%sIssue6222Test.php:%d + +3) PHPUnit\TestFixture\Issue6222\Issue6222Test::testZeroCasesPassing#0 with data (1) +Failed asserting that 1 is identical to 3. + +%sIssue6222Test.php:%d + +4) PHPUnit\TestFixture\Issue6222\Issue6222Test::testZeroCasesPassing#1 with data (2) +Failed asserting that 2 is identical to 3. + +%sIssue6222Test.php:%d + +-- + +There were 2 skipped tests: + +1) PHPUnit\TestFixture\Issue6222\Issue6222Test::testDependingOnOneCasePassing +This test depends on "PHPUnit\TestFixture\Issue6222\Issue6222Test::testOneCasePassing" to pass + +2) PHPUnit\TestFixture\Issue6222\Issue6222Test::testDependingOnZeroCasesPassing +This test depends on "PHPUnit\TestFixture\Issue6222\Issue6222Test::testZeroCasesPassing" to pass + +FAILURES! +Tests: 10, Assertions: 8, Failures: 4, Skipped: 2. diff --git a/tests/end-to-end/regression/6222/Issue6222Test.php b/tests/end-to-end/regression/6222/Issue6222Test.php new file mode 100644 index 00000000000..d1334e71af9 --- /dev/null +++ b/tests/end-to-end/regression/6222/Issue6222Test.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue6222; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Depends; +use PHPUnit\Framework\TestCase; + +class Issue6222Test extends TestCase +{ + public static function provider(): iterable + { + yield [1]; + + yield [2]; + } + + public function testOne(): void + { + $this->assertTrue(false); + } + + #[DataProvider('provider')] + public function testTwoCasesPassing(int $x): void + { + $this->assertGreaterThan(0, $x); + } + + #[Depends('testTwoCasesPassing')] + public function testDependingOnTwoCasesPassing(): void + { + $this->assertTrue(true); + } + + #[DataProvider('provider')] + public function testOneCasePassing(int $x): void + { + $this->assertSame(1, $x); + } + + #[Depends('testOneCasePassing')] + public function testDependingOnOneCasePassing(): void + { + $this->assertTrue(true); + } + + #[DataProvider('provider')] + public function testZeroCasesPassing(int $x): void + { + $this->assertSame(3, $x); + } + + #[Depends('testZeroCasesPassing')] + public function testDependingOnZeroCasesPassing(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/6234.phpt b/tests/end-to-end/regression/6234.phpt new file mode 100644 index 00000000000..ede356b99fd --- /dev/null +++ b/tests/end-to-end/regression/6234.phpt @@ -0,0 +1,17 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6234 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Fatal error: Premature end of PHP process when running PHPUnit\TestFixture\Issue6234Test::testExitWithoutProcessIsolation. diff --git a/tests/end-to-end/regression/6234/Issue6234Test.php b/tests/end-to-end/regression/6234/Issue6234Test.php new file mode 100644 index 00000000000..e21794cbc39 --- /dev/null +++ b/tests/end-to-end/regression/6234/Issue6234Test.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\TestCase; + +final class Issue6234Test extends TestCase +{ + public function testExitWithoutProcessIsolation(): void + { + $this->assertTrue(true); + $this->assertTrue(true); + + exit(1); + } +} diff --git a/tests/end-to-end/regression/6279.phpt b/tests/end-to-end/regression/6279.phpt new file mode 100644 index 00000000000..5ceb7fa57ab --- /dev/null +++ b/tests/end-to-end/regression/6279.phpt @@ -0,0 +1,47 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6279 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +.D.DD....DD. 12 / 12 (100%) + +Time: %s, Memory: %s + +5 tests triggered 5 deprecations: + +1) %sTriggersDeprecationInDataProvider1Test.php:26 +some deprecation + +Triggered by: + +* PHPUnit\TestFixture\Issue6279\TriggersDeprecationInDataProvider1Test::method2#0 + %sTriggersDeprecationInDataProvider1Test.php:48 + +* PHPUnit\TestFixture\Issue6279\TriggersDeprecationInDataProvider1Test::method4#0 + %sTriggersDeprecationInDataProvider1Test.php:61 + +2) %sTriggersDeprecationInDataProvider1Test.php:33 +first + +3) %sTriggersDeprecationInDataProvider1Test.php:34 +second + +4) %sTriggersDeprecationInDataProviderUsingIgnoreDeprecationsTest.php:32 +some deprecation 2 + +5) %sTriggersDeprecationInDataProviderUsingIgnoreDeprecationsTest.php:39 +some deprecation 3 + +OK, but there were issues! +Tests: 12, Assertions: 12, Deprecations: 5. diff --git a/tests/end-to-end/regression/6279/TriggersDeprecationInDataProvider1Test.php b/tests/end-to-end/regression/6279/TriggersDeprecationInDataProvider1Test.php new file mode 100644 index 00000000000..2d5e6fbc7e3 --- /dev/null +++ b/tests/end-to-end/regression/6279/TriggersDeprecationInDataProvider1Test.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue6279; + +use const E_USER_DEPRECATED; +use const E_USER_WARNING; +use function trigger_error; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; + +class TriggersDeprecationInDataProvider1Test extends TestCase +{ + public static function dataProvider(): iterable + { + @trigger_error('some deprecation', E_USER_DEPRECATED); + + yield [true]; + } + + public static function dataWith2Deprecations(): iterable + { + @trigger_error('first', E_USER_DEPRECATED); + @trigger_error('second', E_USER_DEPRECATED); + @trigger_error('warning', E_USER_WARNING); + + yield [true]; + } + + #[Test] + public function method1(): void + { + $this->assertTrue(true); + } + + #[Test] + #[DataProvider('dataProvider')] + public function method2(bool $value): void + { + $this->assertTrue($value); + } + + #[Test] + public function method3(): void + { + $this->assertTrue(true); + } + + #[Test] + #[DataProvider('dataProvider')] + public function method4(bool $value): void + { + $this->assertTrue($value); + } + + #[Test] + #[DataProviderExternal(self::class, 'dataWith2Deprecations')] + public function method5(bool $value): void + { + $this->assertTrue($value); + } + + #[Test] + public function methodWithSameNameThanInAnotherTest(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/6279/TriggersDeprecationInDataProvider2Test.php b/tests/end-to-end/regression/6279/TriggersDeprecationInDataProvider2Test.php new file mode 100644 index 00000000000..1371cad7842 --- /dev/null +++ b/tests/end-to-end/regression/6279/TriggersDeprecationInDataProvider2Test.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue6279; + +use const E_USER_DEPRECATED; +use function trigger_error; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; + +class TriggersDeprecationInDataProvider2Test extends TestCase +{ + public static function dataProvider(): iterable + { + @trigger_error('some deprecation 1', E_USER_DEPRECATED); + + yield [true]; + } + + #[Test] + #[DataProvider('dataProvider')] + #[IgnoreDeprecations] + public function methodWithSameNameThanInAnotherTest(bool $value): void + { + $this->assertTrue($value); + } +} diff --git a/tests/end-to-end/regression/6279/TriggersDeprecationInDataProviderUsingIgnoreDeprecationsTest.php b/tests/end-to-end/regression/6279/TriggersDeprecationInDataProviderUsingIgnoreDeprecationsTest.php new file mode 100644 index 00000000000..1a66dcee83a --- /dev/null +++ b/tests/end-to-end/regression/6279/TriggersDeprecationInDataProviderUsingIgnoreDeprecationsTest.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue6279; + +use const E_USER_DEPRECATED; +use function trigger_error; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; + +class TriggersDeprecationInDataProviderUsingIgnoreDeprecationsTest extends TestCase +{ + public static function dataProvider1(): iterable + { + @trigger_error('some deprecation', E_USER_DEPRECATED); + + yield [true]; + } + + public static function dataProvider2(): iterable + { + @trigger_error('some deprecation 2', E_USER_DEPRECATED); + + yield [true]; + } + + public static function dataProvider3(): iterable + { + @trigger_error('some deprecation 3', E_USER_DEPRECATED); + + yield [true]; + } + + #[Test] + #[DataProvider('dataProvider1')] + #[IgnoreDeprecations] + public function someMethod1(bool $value): void + { + $this->assertTrue($value); + } + + #[Test] + #[DataProvider('dataProvider2')] + #[IgnoreDeprecations] + public function method2_1(bool $value): void + { + $this->assertTrue($value); + } + + #[Test] + #[DataProvider('dataProvider2')] + public function method2_2(bool $value): void + { + $this->assertTrue($value); + } + + #[Test] + #[DataProvider('dataProvider3')] + public function method3_1(bool $value): void + { + $this->assertTrue($value); + } + + #[Test] + #[DataProvider('dataProvider3')] + #[IgnoreDeprecations] + public function method3_2(bool $value): void + { + $this->assertTrue($value); + } +} diff --git a/tests/end-to-end/regression/6281.phpt b/tests/end-to-end/regression/6281.phpt new file mode 100644 index 00000000000..8cfc37acd2a --- /dev/null +++ b/tests/end-to-end/regression/6281.phpt @@ -0,0 +1,38 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6281 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (PHPUnit\TestFixture\Issue6281\Issue6281Test, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Issue6281\Issue6281Test::testOne) +Before Test Method Called (PHPUnit\TestFixture\Issue6281\Issue6281Test::setUp) +Before Test Method Finished: +- PHPUnit\TestFixture\Issue6281\Issue6281Test::setUp +Test Skipped (PHPUnit\TestFixture\Issue6281\Issue6281Test::testOne) +skip message +After Test Method Called (PHPUnit\TestFixture\Issue6281\Issue6281Test::tearDown) +After Test Method Errored (PHPUnit\TestFixture\Issue6281\Issue6281Test::tearDown) +exception message +After Test Method Finished: +- PHPUnit\TestFixture\Issue6281\Issue6281Test::tearDown +Test Errored (PHPUnit\TestFixture\Issue6281\Issue6281Test::testOne) +exception message +Test Suite Finished (PHPUnit\TestFixture\Issue6281\Issue6281Test, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/regression/6281/Issue6281Test.php b/tests/end-to-end/regression/6281/Issue6281Test.php new file mode 100644 index 00000000000..509437f91ac --- /dev/null +++ b/tests/end-to-end/regression/6281/Issue6281Test.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue6281; + +use Exception; +use PHPUnit\Framework\TestCase; + +final class Issue6281Test extends TestCase +{ + protected function setUp(): void + { + $this->markTestSkipped('skip message'); + } + + protected function tearDown(): void + { + throw new Exception('exception message'); + } + + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/6294-display-errors-off.phpt b/tests/end-to-end/regression/6294-display-errors-off.phpt new file mode 100644 index 00000000000..3d80ec5fb4c --- /dev/null +++ b/tests/end-to-end/regression/6294-display-errors-off.phpt @@ -0,0 +1,18 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6294 +--INI-- +display_errors=0 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Fatal error: Premature end of PHPUnit's PHP process. Use display_errors=On to see the error message. diff --git a/tests/end-to-end/regression/6294-display-errors-on.phpt b/tests/end-to-end/regression/6294-display-errors-on.phpt new file mode 100644 index 00000000000..b62bd3d61ea --- /dev/null +++ b/tests/end-to-end/regression/6294-display-errors-on.phpt @@ -0,0 +1,19 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6294 +--INI-- +display_errors=1 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + + +Fatal error: Access level to PHPUnit\TestFixture\B::someFunction() must be public (as in class PHPUnit\TestFixture\A) in %sB.php on line %i%A diff --git a/tests/end-to-end/regression/6294/A.php b/tests/end-to-end/regression/6294/A.php new file mode 100644 index 00000000000..6de88993a17 --- /dev/null +++ b/tests/end-to-end/regression/6294/A.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use const PHP_EOL; + +class A +{ + public function someFunction(): void + { + print 'A::someFunction' . PHP_EOL; + } +} diff --git a/tests/end-to-end/regression/6294/B.php b/tests/end-to-end/regression/6294/B.php new file mode 100644 index 00000000000..2ec7de7bd0a --- /dev/null +++ b/tests/end-to-end/regression/6294/B.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use const PHP_EOL; + +class B extends A +{ + // incorrect access level + protected function someFunction(): void + { + parent::someFunction(); + + print 'B::someFunction' . PHP_EOL; + } +} diff --git a/tests/end-to-end/regression/6294/IssueTest6294.php b/tests/end-to-end/regression/6294/IssueTest6294.php new file mode 100644 index 00000000000..f59c79a5e2e --- /dev/null +++ b/tests/end-to-end/regression/6294/IssueTest6294.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\TestCase; + +final class IssueTest6294 extends TestCase +{ + public function testOne(): void + { + require_once 'A.php'; + + require_once 'B.php'; + + $this->assertSame(1, 1); + } +} diff --git a/tests/end-to-end/regression/6311.phpt b/tests/end-to-end/regression/6311.phpt new file mode 100644 index 00000000000..5b71c2e7453 --- /dev/null +++ b/tests/end-to-end/regression/6311.phpt @@ -0,0 +1,27 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6311 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) SKIPIF section triggered a fatal error: +Warning: require(%afile.php): Failed to open stream: No such file or directory in Standard input code on line %d + +Fatal error: Uncaught Error: Failed opening required%a +%A diff --git a/tests/end-to-end/regression/6329-runner-deprecation-ignored.phpt b/tests/end-to-end/regression/6329-runner-deprecation-ignored.phpt new file mode 100644 index 00000000000..44cfb498ef7 --- /dev/null +++ b/tests/end-to-end/regression/6329-runner-deprecation-ignored.phpt @@ -0,0 +1,22 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6329 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s MB + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/regression/6329-runner-deprecation.phpt b/tests/end-to-end/regression/6329-runner-deprecation.phpt new file mode 100644 index 00000000000..e727b2c4e3e --- /dev/null +++ b/tests/end-to-end/regression/6329-runner-deprecation.phpt @@ -0,0 +1,27 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6329 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s MB + +There was 1 PHPUnit test runner deprecation: + +1) A runner deprecation! + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Deprecations: 1. diff --git a/tests/end-to-end/regression/6329/Issue6329DeprecationIgnoredTest.php b/tests/end-to-end/regression/6329/Issue6329DeprecationIgnoredTest.php new file mode 100644 index 00000000000..17ffa46ab2d --- /dev/null +++ b/tests/end-to-end/regression/6329/Issue6329DeprecationIgnoredTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Framework\Attributes\IgnorePhpunitDeprecations; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; +use PHPUnit\Framework\TestCase; + +#[RunTestsInSeparateProcesses] +final class Issue6329DeprecationIgnoredTest extends TestCase +{ + #[IgnorePhpunitDeprecations] + public function testOne(): void + { + EventFacade::emitter()->testRunnerTriggeredPhpunitDeprecation( + 'A runner deprecation!', + ); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/6329/Issue6329DeprecationTest.php b/tests/end-to-end/regression/6329/Issue6329DeprecationTest.php new file mode 100644 index 00000000000..5a1c9d9c6d3 --- /dev/null +++ b/tests/end-to-end/regression/6329/Issue6329DeprecationTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; +use PHPUnit\Framework\TestCase; + +#[RunTestsInSeparateProcesses] +final class Issue6329DeprecationTest extends TestCase +{ + public function testOne(): void + { + EventFacade::emitter()->testRunnerTriggeredPhpunitDeprecation( + 'A runner deprecation!', + ); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/6366.phpt b/tests/end-to-end/regression/6366.phpt new file mode 100644 index 00000000000..98bf01d154b --- /dev/null +++ b/tests/end-to-end/regression/6366.phpt @@ -0,0 +1,21 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6366 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 0 assertions) diff --git a/tests/end-to-end/regression/6366/Issue6366Test.php b/tests/end-to-end/regression/6366/Issue6366Test.php new file mode 100644 index 00000000000..c393793f16e --- /dev/null +++ b/tests/end-to-end/regression/6366/Issue6366Test.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue6366; + +use Exception; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; +use PHPUnit\Framework\TestCase; + +final class Issue6366Test extends TestCase +{ + #[DoesNotPerformAssertions] + public function testOne(): void + { + $this->createStub(Exception::class); + } +} diff --git a/tests/end-to-end/regression/6368.phpt b/tests/end-to-end/regression/6368.phpt new file mode 100644 index 00000000000..c38342083bd --- /dev/null +++ b/tests/end-to-end/regression/6368.phpt @@ -0,0 +1,36 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6368 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (%sphpunit.xml, 1 test) +Test Suite Started (default, 1 test) +Test Suite Started (PHPUnit\TestFixture\Issue6368\Issue6368Test, 1 test) +Test Preparation Started (PHPUnit\TestFixture\Issue6368\Issue6368Test::testOne) +Test Prepared (PHPUnit\TestFixture\Issue6368\Issue6368Test::testOne) +Test Runner Triggered Warning (message) +Test Triggered PHPUnit Warning (PHPUnit\TestFixture\Issue6368\Issue6368Test::testOne) +message +Test Passed (PHPUnit\TestFixture\Issue6368\Issue6368Test::testOne) +Test Finished (PHPUnit\TestFixture\Issue6368\Issue6368Test::testOne) +Test Suite Finished (PHPUnit\TestFixture\Issue6368\Issue6368Test, 1 test) +Test Suite Finished (default, 1 test) +Test Suite Finished (%sphpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/regression/6368/phpunit.xml b/tests/end-to-end/regression/6368/phpunit.xml new file mode 100644 index 00000000000..0a94b4d4277 --- /dev/null +++ b/tests/end-to-end/regression/6368/phpunit.xml @@ -0,0 +1,11 @@ + + + + + tests + + + diff --git a/tests/end-to-end/regression/6368/tests/Issue6368Test.php b/tests/end-to-end/regression/6368/tests/Issue6368Test.php new file mode 100644 index 00000000000..b5c97d854f5 --- /dev/null +++ b/tests/end-to-end/regression/6368/tests/Issue6368Test.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue6368; + +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; +use PHPUnit\Framework\TestCase; + +final class Issue6368Test extends TestCase +{ + #[DoesNotPerformAssertions] + public function testOne(): void + { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning('message'); + EventFacade::emitter()->testTriggeredPhpunitWarning($this->valueObjectForEvents(), 'message'); + } +} diff --git a/tests/end-to-end/regression/6382.phpt b/tests/end-to-end/regression/6382.phpt new file mode 100644 index 00000000000..eef500862bc --- /dev/null +++ b/tests/end-to-end/regression/6382.phpt @@ -0,0 +1,19 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6382 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + + +Fatal error: Declaration of PHPUnit\TestFixture\Issue6382\Child6382::__invoke() must be compatible with PHPUnit\TestFixture\Issue6382\Ancestor6382::__invoke(): void in %sChild.php on line %d +%AFatal error: Premature end of PHP process when running PHPUnit\TestFixture\Issue6382\Issue6382Test::testExample. diff --git a/tests/end-to-end/regression/6382/Ancestor.php b/tests/end-to-end/regression/6382/Ancestor.php new file mode 100644 index 00000000000..6d26d86e1f1 --- /dev/null +++ b/tests/end-to-end/regression/6382/Ancestor.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue6382; + +interface Ancestor6382 +{ + public function __invoke(): void; +} diff --git a/tests/end-to-end/regression/6382/Child.php b/tests/end-to-end/regression/6382/Child.php new file mode 100644 index 00000000000..69be671d53f --- /dev/null +++ b/tests/end-to-end/regression/6382/Child.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue6382; + +interface Child6382 extends Ancestor6382 +{ + public function __invoke(); +} diff --git a/tests/end-to-end/regression/6382/Issue6382Test.php b/tests/end-to-end/regression/6382/Issue6382Test.php new file mode 100644 index 00000000000..c4d3ee1e480 --- /dev/null +++ b/tests/end-to-end/regression/6382/Issue6382Test.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue6382; + +use PHPUnit\Framework\TestCase; + +class Issue6382Test extends TestCase +{ + public function testExample(): void + { + require_once __DIR__ . '/Ancestor.php'; + + require_once __DIR__ . '/Child.php'; + + new Child6382; + } +} diff --git a/tests/end-to-end/regression/6391.phpt b/tests/end-to-end/regression/6391.phpt new file mode 100644 index 00000000000..4fd143adb31 --- /dev/null +++ b/tests/end-to-end/regression/6391.phpt @@ -0,0 +1,34 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6391 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (TestFixture\Issue6391\Issue6391Test, 1 test) +Before First Test Method Called (TestFixture\Issue6391\Issue6391Test::setUpBeforeClass) +Before First Test Method Finished: +- TestFixture\Issue6391\Issue6391Test::setUpBeforeClass +Test Preparation Started (TestFixture\Issue6391\Issue6391Test::testOne) +Test Preparation Failed (TestFixture\Issue6391\Issue6391Test::testOne) +Object of class TestFixture\Issue6391\Issue6391 could not be converted to string +Test Errored (TestFixture\Issue6391\Issue6391Test::testOne) +Object of class TestFixture\Issue6391\Issue6391 could not be converted to string +Test Suite Finished (TestFixture\Issue6391\Issue6391Test, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 2) diff --git a/tests/end-to-end/regression/6391/src/Issue6391.php b/tests/end-to-end/regression/6391/src/Issue6391.php new file mode 100644 index 00000000000..2b7741f8e84 --- /dev/null +++ b/tests/end-to-end/regression/6391/src/Issue6391.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace TestFixture\Issue6391; + +use Error; + +class Issue6391 +{ + public static $instance; + + public function __unserialize(array $data): void + { + throw new Error("Cannot unserialize '{$this}'"); + } +} diff --git a/tests/end-to-end/regression/6391/tests/Issue6391Test.php b/tests/end-to-end/regression/6391/tests/Issue6391Test.php new file mode 100644 index 00000000000..e496413dcf2 --- /dev/null +++ b/tests/end-to-end/regression/6391/tests/Issue6391Test.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace TestFixture\Issue6391; + +use PHPUnit\Framework\TestCase; + +require __DIR__ . '/../src/Issue6391.php'; + +final class Issue6391Test extends TestCase +{ + public static function setUpBeforeClass(): void + { + Issue6391::$instance = new Issue6391; + } + + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/6406.phpt b/tests/end-to-end/regression/6406.phpt new file mode 100644 index 00000000000..e265d5c1de1 --- /dev/null +++ b/tests/end-to-end/regression/6406.phpt @@ -0,0 +1,21 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6406 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +.. 2 / 2 (100%) + +Time: %s, Memory: %s + +OK (2 tests, 2 assertions) diff --git a/tests/end-to-end/regression/6406/Issue6406Test.php b/tests/end-to-end/regression/6406/Issue6406Test.php new file mode 100644 index 00000000000..a3de5a9b79a --- /dev/null +++ b/tests/end-to-end/regression/6406/Issue6406Test.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Issue6406; + +use const INF; +use const NAN; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class Issue6406Test extends TestCase +{ + public static function provider(): array + { + return [ + 'inf' => [INF], + 'nan' => [NAN], + ]; + } + + #[DataProvider('provider')] + public function testOne($value): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/74.phpt b/tests/end-to-end/regression/74.phpt new file mode 100644 index 00000000000..5d7050a9fd7 --- /dev/null +++ b/tests/end-to-end/regression/74.phpt @@ -0,0 +1,30 @@ +--TEST-- +GH-74: catchable fatal error in 3.5 +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +E 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 error: + +1) PHPUnit\TestFixture\Issue74Test::testCreateAndThrowNewExceptionInProcessIsolation +PHPUnit\TestFixture\NewException: Testing GH-74 + +%sIssue74Test.php:%d + +ERRORS! +Tests: 1, Assertions: 0, Errors: 1. diff --git a/tests/end-to-end/regression/GitHub/74/Issue74Test.php b/tests/end-to-end/regression/74/Issue74Test.php similarity index 94% rename from tests/end-to-end/regression/GitHub/74/Issue74Test.php rename to tests/end-to-end/regression/74/Issue74Test.php index caaa1461ae3..ea1b16e9ca3 100644 --- a/tests/end-to-end/regression/GitHub/74/Issue74Test.php +++ b/tests/end-to-end/regression/74/Issue74Test.php @@ -7,6 +7,8 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + use PHPUnit\Framework\TestCase; class Issue74Test extends TestCase diff --git a/tests/end-to-end/regression/GitHub/74/NewException.php b/tests/end-to-end/regression/74/NewException.php similarity index 85% rename from tests/end-to-end/regression/GitHub/74/NewException.php rename to tests/end-to-end/regression/74/NewException.php index 6c5201634c8..7b1526250f3 100644 --- a/tests/end-to-end/regression/GitHub/74/NewException.php +++ b/tests/end-to-end/regression/74/NewException.php @@ -7,6 +7,10 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + +use Exception; + class NewException extends Exception { } diff --git a/tests/end-to-end/regression/765.phpt b/tests/end-to-end/regression/765.phpt new file mode 100644 index 00000000000..062b85ce739 --- /dev/null +++ b/tests/end-to-end/regression/765.phpt @@ -0,0 +1,35 @@ +--TEST-- +GH-765: Fatal error triggered in PHPUnit when exception is thrown in data provider of a test with a dependency +--SKIPIF-- +run($_SERVER['argv'])); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit error: + +1) PHPUnit\TestFixture\Issue765Test::testDependent +The data provider PHPUnit\TestFixture\Issue765Test::dependentProvider specified for PHPUnit\TestFixture\Issue765Test::testDependent is invalid + + +%s:%d + +ERRORS! +Tests: 1, Assertions: 1, Errors: 1. +int(2) diff --git a/tests/end-to-end/regression/765/Issue765Test.php b/tests/end-to-end/regression/765/Issue765Test.php new file mode 100644 index 00000000000..5dfd61bcd1a --- /dev/null +++ b/tests/end-to-end/regression/765/Issue765Test.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +use Exception; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Depends; +use PHPUnit\Framework\TestCase; + +class Issue765Test extends TestCase +{ + public static function dependentProvider(): void + { + throw new Exception; + } + + public function testDependee(): void + { + $this->assertTrue(true); + } + + #[Depends('testDependee')] + #[DataProvider('dependentProvider')] + public function testDependent($a): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/regression/797.phpt b/tests/end-to-end/regression/797.phpt new file mode 100644 index 00000000000..ee556987d7b --- /dev/null +++ b/tests/end-to-end/regression/797.phpt @@ -0,0 +1,23 @@ +--TEST-- +GH-797: Disabled $preserveGlobalState does not load bootstrap.php. +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/regression/GitHub/797/Issue797Test.php b/tests/end-to-end/regression/797/Issue797Test.php similarity index 79% rename from tests/end-to-end/regression/GitHub/797/Issue797Test.php rename to tests/end-to-end/regression/797/Issue797Test.php index 9ba4c28ffb1..07ed61ac5b7 100644 --- a/tests/end-to-end/regression/GitHub/797/Issue797Test.php +++ b/tests/end-to-end/regression/797/Issue797Test.php @@ -7,12 +7,14 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\TestFixture; + +use PHPUnit\Framework\Attributes\PreserveGlobalState; use PHPUnit\Framework\TestCase; +#[PreserveGlobalState(true)] class Issue797Test extends TestCase { - protected $preserveGlobalState = false; - public function testBootstrapPhpIsExecutedInIsolation(): void { $this->assertEquals(GITHUB_ISSUE, 797); diff --git a/tests/end-to-end/regression/GitHub/797/bootstrap797.php b/tests/end-to-end/regression/797/bootstrap797.php similarity index 86% rename from tests/end-to-end/regression/GitHub/797/bootstrap797.php rename to tests/end-to-end/regression/797/bootstrap797.php index 169f2dfc467..68b036c2c2e 100644 --- a/tests/end-to-end/regression/GitHub/797/bootstrap797.php +++ b/tests/end-to-end/regression/797/bootstrap797.php @@ -1,4 +1,5 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; - -class Issue1216Test extends TestCase -{ - public function testConfigAvailableInBootstrap(): void - { - $this->assertTrue($_ENV['configAvailableInBootstrap']); - } -} diff --git a/tests/end-to-end/regression/GitHub/1216/bootstrap1216.php b/tests/end-to-end/regression/GitHub/1216/bootstrap1216.php deleted file mode 100644 index 9886f690520..00000000000 --- a/tests/end-to-end/regression/GitHub/1216/bootstrap1216.php +++ /dev/null @@ -1,10 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -$_ENV['configAvailableInBootstrap'] = isset($_ENV['loadedFromConfig']); diff --git a/tests/end-to-end/regression/GitHub/1216/phpunit1216.xml b/tests/end-to-end/regression/GitHub/1216/phpunit1216.xml deleted file mode 100644 index c1cc9bd63a6..00000000000 --- a/tests/end-to-end/regression/GitHub/1216/phpunit1216.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - Issue1216Test.php - - - - - diff --git a/tests/end-to-end/regression/GitHub/1265.phpt b/tests/end-to-end/regression/GitHub/1265.phpt deleted file mode 100644 index 5ff7a04dbbc..00000000000 --- a/tests/end-to-end/regression/GitHub/1265.phpt +++ /dev/null @@ -1,20 +0,0 @@ ---TEST-- -GH-1265: Could not use "PHPUnit\Runner\StandardTestSuiteLoader" as loader ---FILE-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; - -class Issue1265Test extends TestCase -{ - public function testTrue(): void - { - $this->assertTrue(true); - } -} diff --git a/tests/end-to-end/regression/GitHub/1265/phpunit1265.xml b/tests/end-to-end/regression/GitHub/1265/phpunit1265.xml deleted file mode 100644 index 417c8e7e967..00000000000 --- a/tests/end-to-end/regression/GitHub/1265/phpunit1265.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/tests/end-to-end/regression/GitHub/1330.phpt b/tests/end-to-end/regression/GitHub/1330.phpt deleted file mode 100644 index 6bb0d34a65c..00000000000 --- a/tests/end-to-end/regression/GitHub/1330.phpt +++ /dev/null @@ -1,21 +0,0 @@ ---TEST-- -GH-1330: Allow non-ambiguous shortened longopts ---FILE-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; - -class Issue1330Test extends TestCase -{ - public function testTrue(): void - { - $this->assertTrue(PHPUNIT_1330); - } -} diff --git a/tests/end-to-end/regression/GitHub/1330/phpunit1330.xml b/tests/end-to-end/regression/GitHub/1330/phpunit1330.xml deleted file mode 100644 index a61e0cc1022..00000000000 --- a/tests/end-to-end/regression/GitHub/1330/phpunit1330.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/tests/end-to-end/regression/GitHub/1335.phpt b/tests/end-to-end/regression/GitHub/1335.phpt deleted file mode 100644 index 81e319f40ec..00000000000 --- a/tests/end-to-end/regression/GitHub/1335.phpt +++ /dev/null @@ -1,19 +0,0 @@ ---TEST-- -https://github.com/sebastianbergmann/phpunit/issues/1335 ---FILE-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; - -class Issue1337Test extends TestCase -{ - /** - * @dataProvider dataProvider - */ - public function testProvider($a): void - { - $this->assertTrue($a); - } - - public function dataProvider() - { - return [ - 'c:\\' => [true], - 0.9 => [true], - ]; - } -} diff --git a/tests/end-to-end/regression/GitHub/1348.phpt b/tests/end-to-end/regression/GitHub/1348.phpt deleted file mode 100644 index b3328084294..00000000000 --- a/tests/end-to-end/regression/GitHub/1348.phpt +++ /dev/null @@ -1,31 +0,0 @@ ---TEST-- -https://github.com/sebastianbergmann/phpunit/issues/1348 ---SKIPIF-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; - -class Issue1348Test extends TestCase -{ - public function testSTDOUT(): void - { - \fwrite(\STDOUT, "\nSTDOUT does not break test result\n"); - $this->assertTrue(true); - } - - public function testSTDERR(): void - { - \fwrite(\STDERR, 'STDERR works as usual.'); - } -} diff --git a/tests/end-to-end/regression/GitHub/1351.phpt b/tests/end-to-end/regression/GitHub/1351.phpt deleted file mode 100644 index 91c099b117c..00000000000 --- a/tests/end-to-end/regression/GitHub/1351.phpt +++ /dev/null @@ -1,44 +0,0 @@ ---TEST-- -https://github.com/sebastianbergmann/phpunit/issues/1351 ---SKIPIF-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -class ChildProcessClass1351 -{ -} diff --git a/tests/end-to-end/regression/GitHub/1351/Issue1351Test.php b/tests/end-to-end/regression/GitHub/1351/Issue1351Test.php deleted file mode 100644 index f411aa1bac1..00000000000 --- a/tests/end-to-end/regression/GitHub/1351/Issue1351Test.php +++ /dev/null @@ -1,59 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; - -class Issue1351Test extends TestCase -{ - protected $instance; - - /** - * @runInSeparateProcess - */ - public function testFailurePre(): void - { - $this->instance = new ChildProcessClass1351; - $this->assertFalse(true, 'Expected failure.'); - } - - public function testFailurePost(): void - { - $this->assertNull($this->instance); - $this->assertFalse(\class_exists(ChildProcessClass1351::class, false), 'ChildProcessClass1351 is not loaded.'); - } - - /** - * @runInSeparateProcess - */ - public function testExceptionPre(): void - { - $this->instance = new ChildProcessClass1351; - - try { - throw new LogicException('Expected exception.'); - } catch (LogicException $e) { - throw new RuntimeException('Expected rethrown exception.', 0, $e); - } - } - - public function testExceptionPost(): void - { - $this->assertNull($this->instance); - $this->assertFalse(\class_exists(ChildProcessClass1351::class, false), 'ChildProcessClass1351 is not loaded.'); - } - - public function testPhpCoreLanguageException(): void - { - // User-space code cannot instantiate a PDOException with a string code, - // so trigger a real one. - $connection = new PDO('sqlite::memory:'); - $connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - $connection->query("DELETE FROM php_wtf WHERE exception_code = 'STRING'"); - } -} diff --git a/tests/end-to-end/regression/GitHub/1374.phpt b/tests/end-to-end/regression/GitHub/1374.phpt deleted file mode 100644 index 6b564c863e6..00000000000 --- a/tests/end-to-end/regression/GitHub/1374.phpt +++ /dev/null @@ -1,18 +0,0 @@ ---TEST-- -https://github.com/sebastianbergmann/phpunit/issues/1374 ---FILE-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; - -class Issue1468Test extends TestCase -{ - /** - * @todo Implement this test - */ - public function testFailure(): void - { - $this->markTestIncomplete(); - } -} diff --git a/tests/end-to-end/regression/GitHub/1471.phpt b/tests/end-to-end/regression/GitHub/1471.phpt deleted file mode 100644 index 594487a75ff..00000000000 --- a/tests/end-to-end/regression/GitHub/1471.phpt +++ /dev/null @@ -1,25 +0,0 @@ ---TEST-- -https://github.com/sebastianbergmann/phpunit/issues/1471 ---FILE-- - - diff --git a/tests/end-to-end/regression/GitHub/2137-filter.phpt b/tests/end-to-end/regression/GitHub/2137-filter.phpt deleted file mode 100644 index d309208a5c7..00000000000 --- a/tests/end-to-end/regression/GitHub/2137-filter.phpt +++ /dev/null @@ -1,26 +0,0 @@ ---TEST-- -#2137: Error message for invalid dataprovider ---FILE-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -class Issue2137Test extends PHPUnit\Framework\TestCase -{ - /** - * @dataProvider provideBrandService - * - * @throws Exception - * @throws \PHPUnit\Framework\ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function testBrandService($provided, $expected): void - { - $this->assertSame($provided, $expected); - } - - public function provideBrandService() - { - return [ - //[true, true] - new stdClass, // not valid - ]; - } - - /** - * @dataProvider provideBrandService - * - * @throws Exception - * @throws \PHPUnit\Framework\ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function testSomethingElseInvalid($provided, $expected): void - { - $this->assertSame($provided, $expected); - } -} diff --git a/tests/end-to-end/regression/GitHub/2145.phpt b/tests/end-to-end/regression/GitHub/2145.phpt deleted file mode 100644 index 5d569636551..00000000000 --- a/tests/end-to-end/regression/GitHub/2145.phpt +++ /dev/null @@ -1,24 +0,0 @@ ---TEST-- ---stop-on-failure fails to stop on PHP 7 ---FILE-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -class Issue2145Test extends PHPUnit\Framework\TestCase -{ - public static function setUpBeforeClass(): void - { - throw new Exception; - } - - public function testOne(): void - { - } - - public function testTwo(): void - { - } -} diff --git a/tests/end-to-end/regression/GitHub/2158.phpt b/tests/end-to-end/regression/GitHub/2158.phpt deleted file mode 100644 index 5f933840f69..00000000000 --- a/tests/end-to-end/regression/GitHub/2158.phpt +++ /dev/null @@ -1,17 +0,0 @@ ---TEST-- -#2158: Failure to run tests in separate processes if a file included into main process contains constant definition ---FILE-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; - -class Issue2158Test extends TestCase -{ - /** - * Set constant in main process. - */ - public function testSomething(): void - { - include __DIR__ . '/constant.inc'; - $this->assertTrue(true); - } - - /** - * Constant defined previously in main process constant should be available and - * no errors should be yielded by reload of included files. - * - * @runInSeparateProcess - */ - public function testSomethingElse(): void - { - $this->assertTrue(\defined('TEST_CONSTANT')); - } -} diff --git a/tests/end-to-end/regression/GitHub/2366.phpt b/tests/end-to-end/regression/GitHub/2366.phpt deleted file mode 100644 index f7ee8fa240e..00000000000 --- a/tests/end-to-end/regression/GitHub/2366.phpt +++ /dev/null @@ -1,17 +0,0 @@ ---TEST-- -#2366: Using a test double from a data provider only works once ---FILE-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; - -class Issue2366 -{ - public function foo(): bool - { - return false; - } -} - -class Issue2366Test extends TestCase -{ - /** - * @dataProvider provider - */ - public function testOne($o): void - { - $this->assertEquals(true, $o->foo()); - } - - public function provider() - { - $o = $this->createMock(Issue2366::class); - - $o->method('foo')->willReturn(true); - - return [ - [$o], - [$o], - ]; - } -} diff --git a/tests/end-to-end/regression/GitHub/2380.phpt b/tests/end-to-end/regression/GitHub/2380.phpt deleted file mode 100644 index 2070653a5d9..00000000000 --- a/tests/end-to-end/regression/GitHub/2380.phpt +++ /dev/null @@ -1,17 +0,0 @@ ---TEST-- -#2380: Data Providers cannot be generators anymore ---FILE-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; - -class Issue2380Test extends TestCase -{ - /** - * @dataProvider generatorData - */ - public function testGeneratorProvider($data): void - { - $this->assertNotEmpty($data); - } - - /** - * @return Generator - */ - public function generatorData() - { - yield ['testing']; - } -} diff --git a/tests/end-to-end/regression/GitHub/2382.phpt b/tests/end-to-end/regression/GitHub/2382.phpt deleted file mode 100644 index d4f44c1f552..00000000000 --- a/tests/end-to-end/regression/GitHub/2382.phpt +++ /dev/null @@ -1,17 +0,0 @@ ---TEST-- -#2382: Data Providers throw error with uncloneable object ---FILE-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; - -class Issue2382Test extends TestCase -{ - /** - * @dataProvider dataProvider - */ - public function testOne($test): void - { - $this->assertInstanceOf(\Exception::class, $test); - } - - public function dataProvider() - { - return [ - [ - $this->getMockBuilder(\Exception::class)->getMock(), - ], - ]; - } -} diff --git a/tests/end-to-end/regression/GitHub/2435.phpt b/tests/end-to-end/regression/GitHub/2435.phpt deleted file mode 100644 index 23dbe35da05..00000000000 --- a/tests/end-to-end/regression/GitHub/2435.phpt +++ /dev/null @@ -1,17 +0,0 @@ ---TEST-- -GH-2435: Test empty @group annotation ---FILE-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -class Test extends PHPUnit\Framework\TestCase -{ - public function testOne(): void - { - $this->assertTrue(true); - } -} diff --git a/tests/end-to-end/regression/GitHub/2724-diff-pid-from-master-process.phpt b/tests/end-to-end/regression/GitHub/2724-diff-pid-from-master-process.phpt deleted file mode 100644 index 481e4e1c2eb..00000000000 --- a/tests/end-to-end/regression/GitHub/2724-diff-pid-from-master-process.phpt +++ /dev/null @@ -1,20 +0,0 @@ ---TEST-- -GH-2724: Missing initialization of setRunClassInSeparateProcess() for tests without data providers ---FILE-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -/** - * @runClassInSeparateProcess - */ -class SeparateClassRunMethodInNewProcessTest extends PHPUnit\Framework\TestCase -{ - public const PROCESS_ID_FILE_PATH = __DIR__ . '/parent_process_id.txt'; - - public const INITIAL_MASTER_PID = 0; - - public const INITIAL_PID1 = 1; - - public static $masterPid = self::INITIAL_MASTER_PID; - - public static $pid1 = self::INITIAL_PID1; - - public static function setUpBeforeClass(): void - { - parent::setUpBeforeClass(); - - if (\file_exists(self::PROCESS_ID_FILE_PATH)) { - static::$masterPid = (int) \file_get_contents(self::PROCESS_ID_FILE_PATH); - } - } - - public static function tearDownAfterClass(): void - { - parent::tearDownAfterClass(); - - if (\file_exists(self::PROCESS_ID_FILE_PATH)) { - \unlink(self::PROCESS_ID_FILE_PATH); - } - } - - public function testMethodShouldGetDifferentPidThanMaster(): void - { - static::$pid1 = \getmypid(); - - $this->assertNotEquals(self::INITIAL_PID1, static::$pid1); - $this->assertNotEquals(self::INITIAL_MASTER_PID, static::$masterPid); - - $this->assertNotEquals(static::$pid1, static::$masterPid); - } -} diff --git a/tests/end-to-end/regression/GitHub/2725-separate-class-before-after-pid.phpt b/tests/end-to-end/regression/GitHub/2725-separate-class-before-after-pid.phpt deleted file mode 100644 index 313d3fed764..00000000000 --- a/tests/end-to-end/regression/GitHub/2725-separate-class-before-after-pid.phpt +++ /dev/null @@ -1,17 +0,0 @@ ---TEST-- -GH-2725: Verify that @runClassInSeparateProcess runs @beforeclass and @afterclass methods in the same process as test methods. ---FILE-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -class Issue2811Test extends PHPUnit\Framework\TestCase -{ - public function testOne(): void - { - $this->expectExceptionMessage('hello'); - - throw new \Exception('hello'); - } -} diff --git a/tests/end-to-end/regression/GitHub/2830.phpt b/tests/end-to-end/regression/GitHub/2830.phpt deleted file mode 100644 index abb221185cb..00000000000 --- a/tests/end-to-end/regression/GitHub/2830.phpt +++ /dev/null @@ -1,18 +0,0 @@ ---TEST-- -GH-2830: @runClassInSeparateProcess fails for tests with a @dataProvider ---FILE-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -class Issue2830Test extends PHPUnit\Framework\TestCase -{ - /** - * @dataProvider simpleDataProvider - */ - public function testMethodUsesDataProvider(): void - { - $this->assertTrue(true); - } - - public function simpleDataProvider() - { - return [ - ['foo'], - ]; - } -} diff --git a/tests/end-to-end/regression/GitHub/2972.phpt b/tests/end-to-end/regression/GitHub/2972.phpt deleted file mode 100644 index 0c244b1b804..00000000000 --- a/tests/end-to-end/regression/GitHub/2972.phpt +++ /dev/null @@ -1,22 +0,0 @@ ---TEST-- -GH-2972: Test suite shouldn't fail when it contains both *.phpt files and unconventionally named tests ---FILE-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -class Issue3093Test extends \PHPUnit\Framework\TestCase -{ - public function someDataProvider(): array - { - return [['some values']]; - } - - public function testFirstWithoutDependencies(): void - { - self::assertTrue(true); - } - - /** - * @depends testFirstWithoutDependencies - * @dataProvider someDataProvider - */ - public function testSecondThatDependsOnFirstAndDataprovider($value): void - { - self::assertTrue(true); - } -} diff --git a/tests/end-to-end/regression/GitHub/3093/issue-3093-test.phpt b/tests/end-to-end/regression/GitHub/3093/issue-3093-test.phpt deleted file mode 100644 index 6a6b912b720..00000000000 --- a/tests/end-to-end/regression/GitHub/3093/issue-3093-test.phpt +++ /dev/null @@ -1,18 +0,0 @@ ---TEST-- -https://github.com/sebastianbergmann/phpunit/issues/3093 ---FILE-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Test; - -use PHPUnit\Framework\TestCase; -use stdClass; - -class Issue3156Test extends TestCase -{ - public function testConstants(): stdClass - { - $this->assertStringEndsWith('/', '/'); - - return new stdClass; - } - - public function dataSelectOperatorsProvider(): array - { - return [ - ['1'], - ['2'], - ]; - } - - /** - * @depends testConstants - * @dataProvider dataSelectOperatorsProvider - */ - public function testDependsRequire(string $val, stdClass $obj): void - { - $this->assertStringEndsWith('/', '/'); - } -} diff --git a/tests/end-to-end/regression/GitHub/322.phpt b/tests/end-to-end/regression/GitHub/322.phpt deleted file mode 100644 index 6e685056bec..00000000000 --- a/tests/end-to-end/regression/GitHub/322.phpt +++ /dev/null @@ -1,24 +0,0 @@ ---TEST-- -GH-322: group commandline option should override group/exclude setting in phpunit.xml ---FILE-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; - -class Issue322Test extends TestCase -{ - /** - * @group one - */ - public function testOne(): void - { - $this->assertTrue(true); - } - - /** - * @group two - */ - public function testTwo(): void - { - $this->assertTrue(true); - } -} diff --git a/tests/end-to-end/regression/GitHub/322/phpunit322.xml b/tests/end-to-end/regression/GitHub/322/phpunit322.xml deleted file mode 100644 index e3b29483933..00000000000 --- a/tests/end-to-end/regression/GitHub/322/phpunit322.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - Test.php - - - - - one - - - diff --git a/tests/end-to-end/regression/GitHub/3379.phpt b/tests/end-to-end/regression/GitHub/3379.phpt deleted file mode 100644 index f05dd24a24f..00000000000 --- a/tests/end-to-end/regression/GitHub/3379.phpt +++ /dev/null @@ -1,20 +0,0 @@ ---TEST-- -GH-3379: Dependent test of skipped test has status -1 ---FILE-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Test; - -use PHPUnit\Framework\TestCase; - -class Issue3379Test extends TestCase -{ - public function testOne(): void - { - $this->markTestSkipped(); - } - - /** - * @depends testOne - */ - public function testTwo(): void - { - $this->assertTrue(true); - } -} diff --git a/tests/end-to-end/regression/GitHub/3379/Issue3379TestListener.php b/tests/end-to-end/regression/GitHub/3379/Issue3379TestListener.php deleted file mode 100644 index c8607a15ee6..00000000000 --- a/tests/end-to-end/regression/GitHub/3379/Issue3379TestListener.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestListener; -use PHPUnit\Framework\TestListenerDefaultImplementation; - -class Issue3379TestListener implements TestListener -{ - use TestListenerDefaultImplementation; - - public function addSkippedTest(Test $test, Throwable $t, float $time): void - { - if ($test instanceof TestCase) { - print 'Skipped test ' . $test->getName() . ', status: ' . $test->getStatus() . \PHP_EOL; - } - } -} diff --git a/tests/end-to-end/regression/GitHub/3379/phpunit.xml b/tests/end-to-end/regression/GitHub/3379/phpunit.xml deleted file mode 100644 index 890523282e3..00000000000 --- a/tests/end-to-end/regression/GitHub/3379/phpunit.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - . - - - - - - - diff --git a/tests/end-to-end/regression/GitHub/3380/issue-3380-test.phpt b/tests/end-to-end/regression/GitHub/3380/issue-3380-test.phpt deleted file mode 100644 index 357c2aa544f..00000000000 --- a/tests/end-to-end/regression/GitHub/3380/issue-3380-test.phpt +++ /dev/null @@ -1,63 +0,0 @@ ---TEST-- -https://github.com/sebastianbergmann/phpunit/issues/3380 ---FILE-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Issue3739; - -use function unlink; -use PHPUnit\Framework\TestCase; - -class Issue3739 -{ - public function unlinkFileThatDoesNotExistWithErrorSuppression(): bool - { - return @unlink(__DIR__ . '/DOES_NOT_EXIST'); - } - - public function unlinkFileThatDoesNotExistWithoutErrorSuppression(): bool - { - return unlink(__DIR__ . '/DOES_NOT_EXIST'); - } -} - -final class Issue3739Test extends TestCase -{ - public function testWithErrorSuppression(): void - { - $this->assertFalse((new Issue3739())->unlinkFileThatDoesNotExistWithErrorSuppression()); - } - - public function testWithoutErrorSuppression(): void - { - $this->assertFalse((new Issue3739())->unlinkFileThatDoesNotExistWithoutErrorSuppression()); - } -} diff --git a/tests/end-to-end/regression/GitHub/3881.phpt b/tests/end-to-end/regression/GitHub/3881.phpt deleted file mode 100644 index f04fd10bdc3..00000000000 --- a/tests/end-to-end/regression/GitHub/3881.phpt +++ /dev/null @@ -1,18 +0,0 @@ ---TEST-- -https://github.com/sebastianbergmann/phpunit/issues/3881 ---FILE-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; - -class Issue498Test extends TestCase -{ - /** - * @test - * @dataProvider shouldBeTrueDataProvider - * @group falseOnly - */ - public function shouldBeTrue($testData): void - { - $this->assertTrue(true); - } - - /** - * @test - * @dataProvider shouldBeFalseDataProvider - * @group trueOnly - */ - public function shouldBeFalse($testData): void - { - $this->assertFalse(false); - } - - public function shouldBeTrueDataProvider() - { - - //throw new Exception("Can't create the data"); - return [ - [true], - [false], - ]; - } - - public function shouldBeFalseDataProvider() - { - throw new Exception("Can't create the data"); - - return [ - [true], - [false], - ]; - } -} diff --git a/tests/end-to-end/regression/GitHub/503.phpt b/tests/end-to-end/regression/GitHub/503.phpt deleted file mode 100644 index 21f9d3b5a3d..00000000000 --- a/tests/end-to-end/regression/GitHub/503.phpt +++ /dev/null @@ -1,32 +0,0 @@ ---TEST-- -GH-503: assertEquals() Line Ending Differences Are Obscure ---FILE-- - 2 - 2 => 'Test\r\n' - 3 => 4 -- 4 => 5 -+ 4 => 1 - 5 => 6 - 6 => 7 - 7 => 8 - ) - -%s:%i - -FAILURES! -Tests: 1, Assertions: 1, Failures: 1. diff --git a/tests/end-to-end/regression/GitHub/74.phpt b/tests/end-to-end/regression/GitHub/74.phpt deleted file mode 100644 index 61d906791cd..00000000000 --- a/tests/end-to-end/regression/GitHub/74.phpt +++ /dev/null @@ -1,26 +0,0 @@ ---TEST-- -GH-74: catchable fatal error in 3.5 ---FILE-- - -%sIssue765Test.php:%d - -WARNINGS! -Tests: 2, Assertions: 1, Warnings: 1. diff --git a/tests/end-to-end/regression/GitHub/765/Issue765Test.php b/tests/end-to-end/regression/GitHub/765/Issue765Test.php deleted file mode 100644 index 1fbb7ad9eab..00000000000 --- a/tests/end-to-end/regression/GitHub/765/Issue765Test.php +++ /dev/null @@ -1,32 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; - -class Issue765Test extends TestCase -{ - public function testDependee(): void - { - $this->assertTrue(true); - } - - /** - * @depends testDependee - * @dataProvider dependentProvider - */ - public function testDependent($a): void - { - $this->assertTrue(true); - } - - public function dependentProvider(): void - { - throw new Exception; - } -} diff --git a/tests/end-to-end/regression/GitHub/797.phpt b/tests/end-to-end/regression/GitHub/797.phpt deleted file mode 100644 index 33b5030634f..00000000000 --- a/tests/end-to-end/regression/GitHub/797.phpt +++ /dev/null @@ -1,20 +0,0 @@ ---TEST-- -GH-797: Disabled $preserveGlobalState does not load bootstrap.php. ---FILE-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -if (\extension_loaded('xdebug') && \version_compare(\phpversion('xdebug'), '3', '<')) { - \xdebug_disable(); -} - - throw new Exception( - 'PHPUnit suppresses exceptions thrown outside of test case function' - ); diff --git a/tests/end-to-end/regression/Trac/1021.phpt b/tests/end-to-end/regression/Trac/1021.phpt deleted file mode 100644 index 9d27e54c8ed..00000000000 --- a/tests/end-to-end/regression/Trac/1021.phpt +++ /dev/null @@ -1,17 +0,0 @@ ---TEST-- -#1021: Depending on a test that uses a data provider does not work ---FILE-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; - -class Issue1021Test extends TestCase -{ - /** - * @dataProvider provider - */ - public function testSomething($data): void - { - $this->assertTrue($data); - } - - /** - * @depends testSomething - */ - public function testSomethingElse(): void - { - $this->assertTrue(true); - } - - public function provider() - { - return [[true]]; - } -} diff --git a/tests/end-to-end/regression/Trac/578.phpt b/tests/end-to-end/regression/Trac/578.phpt deleted file mode 100644 index d1b04edac4e..00000000000 --- a/tests/end-to-end/regression/Trac/578.phpt +++ /dev/null @@ -1,40 +0,0 @@ ---TEST-- -#578: Double printing of trace line for exceptions from notices and warnings ---SKIPIF-- -= 8) { - print 'skip: PHP < 8 is required.'; -} ---FILE-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; - -class Issue578Test extends TestCase -{ - public function testNoticesDoublePrintStackTrace(): void - { - $this->iniSet('error_reporting', (string) (\E_ALL | \E_NOTICE)); - \trigger_error('Stack Trace Test Notice', \E_NOTICE); - } - - public function testWarningsDoublePrintStackTrace(): void - { - $this->iniSet('error_reporting', (string) (\E_ALL | \E_NOTICE)); - \trigger_error('Stack Trace Test Notice', \E_WARNING); - } - - public function testUnexpectedExceptionsPrintsCorrectly(): void - { - throw new Exception('Double printed exception'); - } -} diff --git a/tests/end-to-end/regression/Trac/684.phpt b/tests/end-to-end/regression/Trac/684.phpt deleted file mode 100644 index 3ae3966ac16..00000000000 --- a/tests/end-to-end/regression/Trac/684.phpt +++ /dev/null @@ -1,23 +0,0 @@ ---TEST-- -#684: Unable to find test class when no test methods exists ---FILE-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; - -class Issue684Test extends TestCase -{ -} diff --git a/tests/end-to-end/regression/Trac/783.phpt b/tests/end-to-end/regression/Trac/783.phpt deleted file mode 100644 index 62d25e1045d..00000000000 --- a/tests/end-to-end/regression/Trac/783.phpt +++ /dev/null @@ -1,19 +0,0 @@ ---TEST-- -#783: Tests getting executed twice when using multiple groups ---FILE-- - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestSuite; - -require_once 'OneTest.php'; - -require_once 'TwoTest.php'; - -class ChildSuite -{ - public static function suite() - { - $suite = new TestSuite('Child'); - $suite->addTestSuite(OneTest::class); - $suite->addTestSuite(TwoTest::class); - - return $suite; - } -} diff --git a/tests/end-to-end/regression/Trac/783/OneTest.php b/tests/end-to-end/regression/Trac/783/OneTest.php deleted file mode 100644 index b7b27eef3d1..00000000000 --- a/tests/end-to-end/regression/Trac/783/OneTest.php +++ /dev/null @@ -1,21 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; - -/** - * @group foo - */ -class OneTest extends TestCase -{ - public function testSomething(): void - { - $this->assertTrue(true); - } -} diff --git a/tests/end-to-end/regression/Trac/783/ParentSuite.php b/tests/end-to-end/regression/Trac/783/ParentSuite.php deleted file mode 100644 index 9ba9ad0c712..00000000000 --- a/tests/end-to-end/regression/Trac/783/ParentSuite.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestSuite; - -require_once 'ChildSuite.php'; - -class ParentSuite -{ - public static function suite() - { - $suite = new TestSuite('Parent'); - $suite->addTest(ChildSuite::suite()); - - return $suite; - } -} diff --git a/tests/end-to-end/regression/Trac/783/TwoTest.php b/tests/end-to-end/regression/Trac/783/TwoTest.php deleted file mode 100644 index 365e66c28d5..00000000000 --- a/tests/end-to-end/regression/Trac/783/TwoTest.php +++ /dev/null @@ -1,21 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; - -/** - * @group bar - */ -class TwoTest extends TestCase -{ - public function testSomething(): void - { - $this->assertTrue(true); - } -} diff --git a/tests/end-to-end/report-tests-performing-assertions-when-annotated-with-does-not-perform-assertions.phpt b/tests/end-to-end/report-tests-performing-assertions-when-annotated-with-does-not-perform-assertions.phpt deleted file mode 100644 index 04864ffdb8c..00000000000 --- a/tests/end-to-end/report-tests-performing-assertions-when-annotated-with-does-not-perform-assertions.phpt +++ /dev/null @@ -1,23 +0,0 @@ ---TEST-- -phpunit ../_files/DoesNotPerformAssertionsButPerformingAssertionsTest.php ---FILE-- - + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/self-direct-indirect/_files/deprecation-in-test-code-ignored/src/.gitkeep b/tests/end-to-end/self-direct-indirect/_files/deprecation-in-test-code-ignored/src/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/end-to-end/self-direct-indirect/_files/deprecation-in-test-code-ignored/tests/DeprecationInTestCodeTest.php b/tests/end-to-end/self-direct-indirect/_files/deprecation-in-test-code-ignored/tests/DeprecationInTestCodeTest.php new file mode 100644 index 00000000000..1213b7f2d2e --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/_files/deprecation-in-test-code-ignored/tests/DeprecationInTestCodeTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SelfDirectIndirect; + +use const E_USER_DEPRECATED; +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class DeprecationInTestCodeTest extends TestCase +{ + public function testOne(): void + { + trigger_error('message', E_USER_DEPRECATED); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/self-direct-indirect/_files/deprecation-in-test-code/phpunit.xml b/tests/end-to-end/self-direct-indirect/_files/deprecation-in-test-code/phpunit.xml new file mode 100644 index 00000000000..a501786e420 --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/_files/deprecation-in-test-code/phpunit.xml @@ -0,0 +1,16 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/self-direct-indirect/_files/deprecation-in-test-code/src/.gitkeep b/tests/end-to-end/self-direct-indirect/_files/deprecation-in-test-code/src/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/end-to-end/self-direct-indirect/_files/deprecation-in-test-code/tests/DeprecationInTestCodeTest.php b/tests/end-to-end/self-direct-indirect/_files/deprecation-in-test-code/tests/DeprecationInTestCodeTest.php new file mode 100644 index 00000000000..1213b7f2d2e --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/_files/deprecation-in-test-code/tests/DeprecationInTestCodeTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SelfDirectIndirect; + +use const E_USER_DEPRECATED; +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class DeprecationInTestCodeTest extends TestCase +{ + public function testOne(): void + { + trigger_error('message', E_USER_DEPRECATED); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/self-direct-indirect/_files/invalid-deprecation-trigger/phpunit.xml b/tests/end-to-end/self-direct-indirect/_files/invalid-deprecation-trigger/phpunit.xml new file mode 100644 index 00000000000..f2237a09c1d --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/_files/invalid-deprecation-trigger/phpunit.xml @@ -0,0 +1,18 @@ + + + + + tests + + + + + + does_not_exist + invalid-string + DoesNotExist::doesNotExist + + + diff --git a/tests/end-to-end/self-direct-indirect/_files/invalid-deprecation-trigger/tests/ExampleTest.php b/tests/end-to-end/self-direct-indirect/_files/invalid-deprecation-trigger/tests/ExampleTest.php new file mode 100644 index 00000000000..e99f90f5b79 --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/_files/invalid-deprecation-trigger/tests/ExampleTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SelfDirectIndirect; + +use PHPUnit\Framework\TestCase; + +final class ExampleTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct-indirect/phpunit.xml b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct-indirect/phpunit.xml new file mode 100644 index 00000000000..5c0fcac4da1 --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct-indirect/phpunit.xml @@ -0,0 +1,17 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct-indirect/src/FirstPartyClass.php b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct-indirect/src/FirstPartyClass.php new file mode 100644 index 00000000000..aca0b8b8d05 --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct-indirect/src/FirstPartyClass.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SelfDirectIndirect; + +use const E_USER_DEPRECATED; +use function trigger_error; + +final class FirstPartyClass +{ + public function method(): true + { + (new ThirdPartyClass)->method(); + + @trigger_error('deprecation in first-party code', E_USER_DEPRECATED); + + return true; + } +} diff --git a/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct-indirect/tests/FirstPartyClassTest.php b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct-indirect/tests/FirstPartyClassTest.php new file mode 100644 index 00000000000..1abafab73ab --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct-indirect/tests/FirstPartyClassTest.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SelfDirectIndirect; + +use PHPUnit\Framework\TestCase; + +final class FirstPartyClassTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue((new FirstPartyClass)->method()); + } + + public function testTwo(): void + { + $this->assertTrue((new ThirdPartyClass)->anotherMethod()); + } +} diff --git a/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct-indirect/vendor/ThirdPartyClass.php b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct-indirect/vendor/ThirdPartyClass.php new file mode 100644 index 00000000000..b373e4addad --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct-indirect/vendor/ThirdPartyClass.php @@ -0,0 +1,15 @@ +method(); + } +} diff --git a/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct-indirect/vendor/autoload.php b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct-indirect/vendor/autoload.php new file mode 100644 index 00000000000..95f0626f283 --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct-indirect/vendor/autoload.php @@ -0,0 +1,3 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct/src/FirstPartyClass.php b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct/src/FirstPartyClass.php new file mode 100644 index 00000000000..aca0b8b8d05 --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct/src/FirstPartyClass.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SelfDirectIndirect; + +use const E_USER_DEPRECATED; +use function trigger_error; + +final class FirstPartyClass +{ + public function method(): true + { + (new ThirdPartyClass)->method(); + + @trigger_error('deprecation in first-party code', E_USER_DEPRECATED); + + return true; + } +} diff --git a/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct/tests/FirstPartyClassTest.php b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct/tests/FirstPartyClassTest.php new file mode 100644 index 00000000000..1abafab73ab --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct/tests/FirstPartyClassTest.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SelfDirectIndirect; + +use PHPUnit\Framework\TestCase; + +final class FirstPartyClassTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue((new FirstPartyClass)->method()); + } + + public function testTwo(): void + { + $this->assertTrue((new ThirdPartyClass)->anotherMethod()); + } +} diff --git a/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct/vendor/ThirdPartyClass.php b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct/vendor/ThirdPartyClass.php new file mode 100644 index 00000000000..b373e4addad --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct/vendor/ThirdPartyClass.php @@ -0,0 +1,15 @@ +method(); + } +} diff --git a/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct/vendor/autoload.php b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct/vendor/autoload.php new file mode 100644 index 00000000000..95f0626f283 --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self-direct/vendor/autoload.php @@ -0,0 +1,3 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self/src/FirstPartyClass.php b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self/src/FirstPartyClass.php new file mode 100644 index 00000000000..aca0b8b8d05 --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self/src/FirstPartyClass.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SelfDirectIndirect; + +use const E_USER_DEPRECATED; +use function trigger_error; + +final class FirstPartyClass +{ + public function method(): true + { + (new ThirdPartyClass)->method(); + + @trigger_error('deprecation in first-party code', E_USER_DEPRECATED); + + return true; + } +} diff --git a/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self/tests/FirstPartyClassTest.php b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self/tests/FirstPartyClassTest.php new file mode 100644 index 00000000000..1abafab73ab --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self/tests/FirstPartyClassTest.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SelfDirectIndirect; + +use PHPUnit\Framework\TestCase; + +final class FirstPartyClassTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue((new FirstPartyClass)->method()); + } + + public function testTwo(): void + { + $this->assertTrue((new ThirdPartyClass)->anotherMethod()); + } +} diff --git a/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self/vendor/ThirdPartyClass.php b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self/vendor/ThirdPartyClass.php new file mode 100644 index 00000000000..b373e4addad --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self/vendor/ThirdPartyClass.php @@ -0,0 +1,15 @@ +method(); + } +} diff --git a/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self/vendor/autoload.php b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self/vendor/autoload.php new file mode 100644 index 00000000000..95f0626f283 --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/_files/user-deprecation-report-self/vendor/autoload.php @@ -0,0 +1,3 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/self-direct-indirect/_files/user-deprecation/src/FirstPartyClass.php b/tests/end-to-end/self-direct-indirect/_files/user-deprecation/src/FirstPartyClass.php new file mode 100644 index 00000000000..aca0b8b8d05 --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/_files/user-deprecation/src/FirstPartyClass.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SelfDirectIndirect; + +use const E_USER_DEPRECATED; +use function trigger_error; + +final class FirstPartyClass +{ + public function method(): true + { + (new ThirdPartyClass)->method(); + + @trigger_error('deprecation in first-party code', E_USER_DEPRECATED); + + return true; + } +} diff --git a/tests/end-to-end/self-direct-indirect/_files/user-deprecation/tests/FirstPartyClassTest.php b/tests/end-to-end/self-direct-indirect/_files/user-deprecation/tests/FirstPartyClassTest.php new file mode 100644 index 00000000000..1abafab73ab --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/_files/user-deprecation/tests/FirstPartyClassTest.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SelfDirectIndirect; + +use PHPUnit\Framework\TestCase; + +final class FirstPartyClassTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue((new FirstPartyClass)->method()); + } + + public function testTwo(): void + { + $this->assertTrue((new ThirdPartyClass)->anotherMethod()); + } +} diff --git a/tests/end-to-end/self-direct-indirect/_files/user-deprecation/vendor/ThirdPartyClass.php b/tests/end-to-end/self-direct-indirect/_files/user-deprecation/vendor/ThirdPartyClass.php new file mode 100644 index 00000000000..b373e4addad --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/_files/user-deprecation/vendor/ThirdPartyClass.php @@ -0,0 +1,15 @@ +method(); + } +} diff --git a/tests/end-to-end/self-direct-indirect/_files/user-deprecation/vendor/autoload.php b/tests/end-to-end/self-direct-indirect/_files/user-deprecation/vendor/autoload.php new file mode 100644 index 00000000000..95f0626f283 --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/_files/user-deprecation/vendor/autoload.php @@ -0,0 +1,3 @@ +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %sphpunit.xml + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/self-direct-indirect/deprecation-in-test-code.phpt b/tests/end-to-end/self-direct-indirect/deprecation-in-test-code.phpt new file mode 100644 index 00000000000..e9d2738339e --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/deprecation-in-test-code.phpt @@ -0,0 +1,29 @@ +--TEST-- +Deprecation in test code is reported when it is configured to be reported +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %sphpunit.xml + +D 1 / 1 (100%) + +Time: %s, Memory: %s + +1 test triggered 1 deprecation: + +1) %sDeprecationInTestCodeTest.php:20 +message + +OK, but there were issues! +Tests: 1, Assertions: 1, Deprecations: 1. diff --git a/tests/end-to-end/self-direct-indirect/invalid-deprecation-trigger.phpt b/tests/end-to-end/self-direct-indirect/invalid-deprecation-trigger.phpt new file mode 100644 index 00000000000..9819d857571 --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/invalid-deprecation-trigger.phpt @@ -0,0 +1,32 @@ +--TEST-- +Test Runner warnings are displayed correctly when invalid deprecation triggers are configured in the XML configuration file +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +There were 3 PHPUnit test runner warnings: + +1) Function does_not_exist cannot be configured as a deprecation trigger because it is not declared + +2) invalid-string cannot be configured as a deprecation trigger because it is not in ClassName::methodName format + +3) Method DoesNotExist::doesNotExist cannot be configured as a deprecation trigger because it is not declared + +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 3. diff --git a/tests/end-to-end/self-direct-indirect/user-deprecation-report-self-direct-indirect.phpt b/tests/end-to-end/self-direct-indirect/user-deprecation-report-self-direct-indirect.phpt new file mode 100644 index 00000000000..d0bd429c5e1 --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/user-deprecation-report-self-direct-indirect.phpt @@ -0,0 +1,48 @@ +--TEST-- +All deprecations are reported when deprecations triggered from first-party code and deprecations triggered from third-party code should be reported +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +DD 2 / 2 (100%) + +Time: %s, Memory: %s + +2 tests triggered 2 deprecations: + +1) %sThirdPartyClass.php:8 +deprecation in third-party code + +Triggered by: + +* PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne + %s:16 + +* PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testTwo + %s:21 + +2) %sFirstPartyClass.php:21 +deprecation in first-party code + +Triggered by: + +* PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne + %sFirstPartyClassTest.php:16 + +* PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testTwo + %sFirstPartyClassTest.php:21 + +OK, but there were issues! +Tests: 2, Assertions: 2, Deprecations: 2. diff --git a/tests/end-to-end/self-direct-indirect/user-deprecation-report-self-direct.phpt b/tests/end-to-end/self-direct-indirect/user-deprecation-report-self-direct.phpt new file mode 100644 index 00000000000..cc3f8a3e302 --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/user-deprecation-report-self-direct.phpt @@ -0,0 +1,45 @@ +--TEST-- +The correct deprecations are reported when deprecations triggered from third-party code should be ignored +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +DD 2 / 2 (100%) + +Time: %s, Memory: %s + +2 tests triggered 2 deprecations: + +1) %sThirdPartyClass.php:8 +deprecation in third-party code + +Triggered by: + +* PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne + %s:16 + +* PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testTwo + %s:21 + +2) %sFirstPartyClass.php:21 +deprecation in first-party code + +Triggered by: + +* PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne + %sFirstPartyClassTest.php:16 + +OK, but there were issues! +Tests: 2, Assertions: 2, Deprecations: 2. diff --git a/tests/end-to-end/self-direct-indirect/user-deprecation-report-self.phpt b/tests/end-to-end/self-direct-indirect/user-deprecation-report-self.phpt new file mode 100644 index 00000000000..52362fca891 --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/user-deprecation-report-self.phpt @@ -0,0 +1,34 @@ +--TEST-- +The correct deprecations are reported when only deprecations in first-party code triggered from first-party code should be reported +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +D. 2 / 2 (100%) + +Time: %s, Memory: %s + +1 test triggered 1 deprecation: + +1) %sFirstPartyClass.php:21 +deprecation in first-party code + +Triggered by: + +* PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne + %sFirstPartyClassTest.php:16 + +OK, but there were issues! +Tests: 2, Assertions: 2, Deprecations: 1. diff --git a/tests/end-to-end/self-direct-indirect/user-deprecation.phpt b/tests/end-to-end/self-direct-indirect/user-deprecation.phpt new file mode 100644 index 00000000000..0d9bb5cf2a3 --- /dev/null +++ b/tests/end-to-end/self-direct-indirect/user-deprecation.phpt @@ -0,0 +1,46 @@ +--TEST-- +The right events are emitted in the right order for a test that runs code which triggers E_USER_DEPRECATED +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Bootstrap Finished (%sautoload.php) +Event Facade Sealed +Test Suite Loaded (2 tests) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (2 tests) +Test Suite Started (%sphpunit.xml, 2 tests) +Test Suite Started (default, 2 tests) +Test Suite Started (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest, 2 tests) +Test Preparation Started (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne) +Test Prepared (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne) +Test Triggered Deprecation (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne, issue triggered by first-party code calling into third-party code, suppressed using operator) in %s:%d +deprecation in third-party code +Test Triggered Deprecation (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne, issue triggered by first-party code calling into first-party code, suppressed using operator) in %s:%d +deprecation in first-party code +Test Passed (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne) +Test Finished (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne) +Test Preparation Started (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testTwo) +Test Prepared (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testTwo) +Test Triggered Deprecation (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testTwo, issue triggered by first-party code calling into third-party code, suppressed using operator) in %s:%d +deprecation in third-party code +Test Triggered Deprecation (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testTwo, issue triggered by third-party code, suppressed using operator) in %s:%d +deprecation in first-party code +Test Passed (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testTwo) +Test Finished (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testTwo) +Test Suite Finished (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest, 2 tests) +Test Suite Finished (default, 2 tests) +Test Suite Finished (%sphpunit.xml, 2 tests) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/end-to-end/separate-processes-test.phpt b/tests/end-to-end/separate-processes-test.phpt deleted file mode 100644 index 3ee033caa50..00000000000 --- a/tests/end-to-end/separate-processes-test.phpt +++ /dev/null @@ -1,26 +0,0 @@ ---TEST-- -phpunit --no-configuration ../../_files/SeparateProcessesTest.php ---FILE-- -run([ - 'phpunit', - (new \ReflectionClass(\ConcreteTest::class))->getFileName() -], false); ---EXPECTF-- -PHPUnit %s by Sebastian Bergmann and contributors. - -Runtime: PHP %s -Configuration: %s - -.. 2 / 2 (100%) - -Time: %s, Memory: %s - -OK (2 tests, 2 assertions) diff --git a/tests/end-to-end/standardtestsuiteloader-issue-4192-2.phpt b/tests/end-to-end/standardtestsuiteloader-issue-4192-2.phpt deleted file mode 100644 index 8af1c33104f..00000000000 --- a/tests/end-to-end/standardtestsuiteloader-issue-4192-2.phpt +++ /dev/null @@ -1,21 +0,0 @@ ---TEST-- -phpunit ../../_files/ConcreteTest.php ---FILE-- -run([ - 'phpunit', - realpath(__DIR__.'/../_files/ConcreteTest.php') -], false); ---EXPECTF-- -PHPUnit %s by Sebastian Bergmann and contributors. - -Runtime: PHP %s -Configuration: %s - -.. 2 / 2 (100%) - -Time: %s, Memory: %s - -OK (2 tests, 2 assertions) diff --git a/tests/end-to-end/test-suffix-multiple.phpt b/tests/end-to-end/test-suffix-multiple.phpt deleted file mode 100644 index d305dabbf52..00000000000 --- a/tests/end-to-end/test-suffix-multiple.phpt +++ /dev/null @@ -1,27 +0,0 @@ ---TEST-- -phpunit --test-suffix .test.php,.my.php ../../_files/ ---FILE-- - + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestDox; + +enum Bar +{ + case FOO; +} diff --git a/tests/end-to-end/testdox/_files/CamelCaseTest.php b/tests/end-to-end/testdox/_files/CamelCaseTest.php new file mode 100644 index 00000000000..74c6c708177 --- /dev/null +++ b/tests/end-to-end/testdox/_files/CamelCaseTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestDox; + +use PHPUnit\Framework\TestCase; + +final class CamelCaseTest extends TestCase +{ + public function testSomethingThatWorks(): void + { + $this->assertTrue(true); + } + + public function testSomethingThatDoesNotWork(): void + { + /* @noinspection PhpUnitAssertTrueWithIncompatibleTypeArgumentInspection */ + $this->assertTrue(false); + } +} diff --git a/tests/end-to-end/testdox/_files/DataProviderWithNumericDataSetNameAndMetadataTest.php b/tests/end-to-end/testdox/_files/DataProviderWithNumericDataSetNameAndMetadataTest.php new file mode 100644 index 00000000000..16fd815c295 --- /dev/null +++ b/tests/end-to-end/testdox/_files/DataProviderWithNumericDataSetNameAndMetadataTest.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestDox; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[TestDox('Text from class-level TestDox metadata')] +final class DataProviderWithNumericDataSetNameAndMetadataTest extends TestCase +{ + public static function provider(): array + { + return [ + 0 => [ + 'string', + 0, + 0.0, + ['key' => 'value'], + true, + ], + ]; + } + + #[DataProvider('provider')] + #[TestDox('Text from method-level TestDox metadata for successful test')] + public function testSomethingThatWorks(string $a, int $b, float $c, array $d, bool $e): void + { + $this->assertTrue(true); + } + + #[DataProvider('provider')] + #[TestDox('Text from method-level TestDox metadata for failing test')] + public function testSomethingThatDoesNotWork(string $a, int $b, float $c, array $d, bool $e): void + { + /* @noinspection PhpUnitAssertTrueWithIncompatibleTypeArgumentInspection */ + $this->assertTrue(false); + } +} diff --git a/tests/end-to-end/testdox/_files/DataProviderWithNumericDataSetNameAndMetadataWithPlaceholdersTest.php b/tests/end-to-end/testdox/_files/DataProviderWithNumericDataSetNameAndMetadataWithPlaceholdersTest.php new file mode 100644 index 00000000000..74d626baa3d --- /dev/null +++ b/tests/end-to-end/testdox/_files/DataProviderWithNumericDataSetNameAndMetadataWithPlaceholdersTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestDox; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[TestDox('Text from class-level TestDox metadata')] +final class DataProviderWithNumericDataSetNameAndMetadataWithPlaceholdersTest extends TestCase +{ + public static function provider(): array + { + return [ + 0 => [ + 'string', + 0, + 0.0, + ['key' => 'value'], + true, + Foo::BAR, + Bar::FOO, + ], + ]; + } + + #[DataProvider('provider')] + #[TestDox('Text from method-level TestDox metadata for successful test with placeholders ($a, $b, $c, $d, $e, $f, $g)')] + public function testSomethingThatWorks(string $a, int $b, float $c, array $d, bool $e, Foo $f, Bar $g): void + { + $this->assertTrue(true); + } + + #[DataProvider('provider')] + #[TestDox('Text from method-level TestDox metadata for failing test with placeholders ($a, $b, $c, $d, $e, $f, $g)')] + public function testSomethingThatDoesNotWork(string $a, int $b, float $c, array $d, bool $e, Foo $f, Bar $g): void + { + /* @noinspection PhpUnitAssertTrueWithIncompatibleTypeArgumentInspection */ + $this->assertTrue(false); + } +} diff --git a/tests/end-to-end/testdox/_files/DataProviderWithNumericDataSetNameTest.php b/tests/end-to-end/testdox/_files/DataProviderWithNumericDataSetNameTest.php new file mode 100644 index 00000000000..3fd259c9a0d --- /dev/null +++ b/tests/end-to-end/testdox/_files/DataProviderWithNumericDataSetNameTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestDox; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class DataProviderWithNumericDataSetNameTest extends TestCase +{ + public static function provider(): array + { + return [ + 0 => [ + 'string', + 0, + 0.0, + ['key' => 'value'], + true, + ], + ]; + } + + #[DataProvider('provider')] + public function testSomethingThatWorks(string $a, int $b, float $c, array $d, bool $e): void + { + $this->assertTrue(true); + } + + #[DataProvider('provider')] + public function testSomethingThatDoesNotWork(string $a, int $b, float $c, array $d, bool $e): void + { + /* @noinspection PhpUnitAssertTrueWithIncompatibleTypeArgumentInspection */ + $this->assertTrue(false); + } +} diff --git a/tests/end-to-end/testdox/_files/DataProviderWithStringDataSetNameAndMetadataTest.php b/tests/end-to-end/testdox/_files/DataProviderWithStringDataSetNameAndMetadataTest.php new file mode 100644 index 00000000000..eaae55d805e --- /dev/null +++ b/tests/end-to-end/testdox/_files/DataProviderWithStringDataSetNameAndMetadataTest.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestDox; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[TestDox('Text from class-level TestDox metadata')] +final class DataProviderWithStringDataSetNameAndMetadataTest extends TestCase +{ + public static function provider(): array + { + return [ + 'data set name' => [ + 'string', + 0, + 0.0, + ['key' => 'value'], + true, + ], + ]; + } + + #[DataProvider('provider')] + #[TestDox('Text from method-level TestDox metadata for successful test')] + public function testSomethingThatWorks(string $a, int $b, float $c, array $d, bool $e): void + { + $this->assertTrue(true); + } + + #[DataProvider('provider')] + #[TestDox('Text from method-level TestDox metadata for failing test')] + public function testSomethingThatDoesNotWork(string $a, int $b, float $c, array $d, bool $e): void + { + /* @noinspection PhpUnitAssertTrueWithIncompatibleTypeArgumentInspection */ + $this->assertTrue(false); + } +} diff --git a/tests/end-to-end/testdox/_files/DataProviderWithStringDataSetNameAndMetadataWithPlaceholdersTest.php b/tests/end-to-end/testdox/_files/DataProviderWithStringDataSetNameAndMetadataWithPlaceholdersTest.php new file mode 100644 index 00000000000..ac4b397d4f9 --- /dev/null +++ b/tests/end-to-end/testdox/_files/DataProviderWithStringDataSetNameAndMetadataWithPlaceholdersTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestDox; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[TestDox('Text from class-level TestDox metadata')] +final class DataProviderWithStringDataSetNameAndMetadataWithPlaceholdersTest extends TestCase +{ + public static function provider(): array + { + return [ + 'data set name' => [ + 'string', + 0, + 0.0, + ['key' => 'value'], + true, + Foo::BAR, + Bar::FOO, + ], + ]; + } + + #[DataProvider('provider')] + #[TestDox('Text from method-level TestDox metadata for successful test with placeholders ($a, $b, $c, $d, $e, $f, $g)')] + public function testSomethingThatWorks(string $a, int $b, float $c, array $d, bool $e, Foo $f, Bar $g): void + { + $this->assertTrue(true); + } + + #[DataProvider('provider')] + #[TestDox('Text from method-level TestDox metadata for failing test with placeholders ($a, $b, $c, $d, $e, $f, $g)')] + public function testSomethingThatDoesNotWork(string $a, int $b, float $c, array $d, bool $e, Foo $f, Bar $g): void + { + /* @noinspection PhpUnitAssertTrueWithIncompatibleTypeArgumentInspection */ + $this->assertTrue(false); + } +} diff --git a/tests/end-to-end/testdox/_files/DataProviderWithStringDataSetNameTest.php b/tests/end-to-end/testdox/_files/DataProviderWithStringDataSetNameTest.php new file mode 100644 index 00000000000..556bb1c9998 --- /dev/null +++ b/tests/end-to-end/testdox/_files/DataProviderWithStringDataSetNameTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestDox; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +final class DataProviderWithStringDataSetNameTest extends TestCase +{ + public static function provider(): array + { + return [ + 'data set name' => [ + 'string', + 0, + 0.0, + ['key' => 'value'], + true, + ], + ]; + } + + #[DataProvider('provider')] + public function testSomethingThatWorks(string $a, int $b, float $c, array $d, bool $e): void + { + $this->assertTrue(true); + } + + #[DataProvider('provider')] + public function testSomethingThatDoesNotWork(string $a, int $b, float $c, array $d, bool $e): void + { + /* @noinspection PhpUnitAssertTrueWithIncompatibleTypeArgumentInspection */ + $this->assertTrue(false); + } +} diff --git a/tests/end-to-end/testdox/_files/DeprecationTest.php b/tests/end-to-end/testdox/_files/DeprecationTest.php new file mode 100644 index 00000000000..14377d714c6 --- /dev/null +++ b/tests/end-to-end/testdox/_files/DeprecationTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestDox; + +use const E_USER_DEPRECATED; +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class DeprecationTest extends TestCase +{ + public function testDeprecation(): void + { + trigger_error('deprecation', E_USER_DEPRECATED); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/testdox/_files/DiffTest.php b/tests/end-to-end/testdox/_files/DiffTest.php new file mode 100644 index 00000000000..58b54d8ddd6 --- /dev/null +++ b/tests/end-to-end/testdox/_files/DiffTest.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestDox; + +use PHPUnit\Framework\TestCase; + +final class DiffTest extends TestCase +{ + public function testSomethingThatDoesNotWork(): void + { + $this->assertEquals( + "foo\nbar\nbaz\n", + "foo\nbaz\nbar\n", + ); + } +} diff --git a/tests/end-to-end/testdox/_files/Foo.php b/tests/end-to-end/testdox/_files/Foo.php new file mode 100644 index 00000000000..9f4cc089428 --- /dev/null +++ b/tests/end-to-end/testdox/_files/Foo.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestDox; + +enum Foo: string +{ + case BAR = 'bar'; +} diff --git a/tests/end-to-end/testdox/_files/FormatterMethodDoesNotExistTest.php b/tests/end-to-end/testdox/_files/FormatterMethodDoesNotExistTest.php new file mode 100644 index 00000000000..97a7cb5ce71 --- /dev/null +++ b/tests/end-to-end/testdox/_files/FormatterMethodDoesNotExistTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestDox; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestDoxFormatter; +use PHPUnit\Framework\TestCase; + +final class FormatterMethodDoesNotExistTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function provider(): array + { + return [ + ['string'], + ]; + } + + #[DataProvider('provider')] + #[TestDoxFormatter('formatter')] + public function testOne(string $value): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/testdox/_files/FormatterMethodIsNotPublicTest.php b/tests/end-to-end/testdox/_files/FormatterMethodIsNotPublicTest.php new file mode 100644 index 00000000000..df773a153d6 --- /dev/null +++ b/tests/end-to-end/testdox/_files/FormatterMethodIsNotPublicTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestDox; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestDoxFormatter; +use PHPUnit\Framework\TestCase; + +final class FormatterMethodIsNotPublicTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function provider(): array + { + return [ + ['string'], + ]; + } + + #[DataProvider('provider')] + #[TestDoxFormatter('formatter')] + public function testOne(string $value): void + { + $this->assertTrue(true); + } + + private static function formatter(string $value): string + { + return 'formatted ' . $value; + } +} diff --git a/tests/end-to-end/testdox/_files/FormatterMethodIsNotStaticTest.php b/tests/end-to-end/testdox/_files/FormatterMethodIsNotStaticTest.php new file mode 100644 index 00000000000..1fe952d51d4 --- /dev/null +++ b/tests/end-to-end/testdox/_files/FormatterMethodIsNotStaticTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestDox; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestDoxFormatter; +use PHPUnit\Framework\TestCase; + +final class FormatterMethodIsNotStaticTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function provider(): array + { + return [ + ['string'], + ]; + } + + public function formatter(string $value): string + { + return 'formatted ' . $value; + } + + #[DataProvider('provider')] + #[TestDoxFormatter('formatter')] + public function testOne(string $value): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/testdox/_files/FormatterMethodThrowsExceptionTest.php b/tests/end-to-end/testdox/_files/FormatterMethodThrowsExceptionTest.php new file mode 100644 index 00000000000..e5848875565 --- /dev/null +++ b/tests/end-to-end/testdox/_files/FormatterMethodThrowsExceptionTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestDox; + +use Exception; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestDoxFormatter; +use PHPUnit\Framework\TestCase; + +final class FormatterMethodThrowsExceptionTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function provider(): array + { + return [ + ['string'], + ]; + } + + public static function formatter(string $value): string + { + throw new Exception('message'); + } + + #[DataProvider('provider')] + #[TestDoxFormatter('formatter')] + public function testOne(string $value): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/testdox/_files/FormatterTest.php b/tests/end-to-end/testdox/_files/FormatterTest.php new file mode 100644 index 00000000000..ef1a905d4a5 --- /dev/null +++ b/tests/end-to-end/testdox/_files/FormatterTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestDox; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestDoxFormatter; +use PHPUnit\Framework\TestCase; + +final class FormatterTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function provider(): array + { + return [ + ['string'], + ]; + } + + public static function formatter(string $value): string + { + return 'formatted ' . $value; + } + + #[DataProvider('provider')] + #[TestDoxFormatter('formatter')] + public function testOne(string $value): void + { + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/testdox/_files/MetadataTest.php b/tests/end-to-end/testdox/_files/MetadataTest.php new file mode 100644 index 00000000000..471dc344742 --- /dev/null +++ b/tests/end-to-end/testdox/_files/MetadataTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestDox; + +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[TestDox('Text from class-level TestDox metadata')] +final class MetadataTest extends TestCase +{ + #[TestDox('Text from method-level TestDox metadata for successful test')] + public function testSomethingThatWorks(): void + { + $this->assertTrue(true); + } + + #[TestDox('Text from method-level TestDox metadata for failing test')] + public function testSomethingThatDoesNotWork(): void + { + /* @noinspection PhpUnitAssertTrueWithIncompatibleTypeArgumentInspection */ + $this->assertTrue(false); + } +} diff --git a/tests/end-to-end/testdox/_files/NoticeTest.php b/tests/end-to-end/testdox/_files/NoticeTest.php new file mode 100644 index 00000000000..94f71d4d02e --- /dev/null +++ b/tests/end-to-end/testdox/_files/NoticeTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestDox; + +use const E_USER_NOTICE; +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class NoticeTest extends TestCase +{ + public function testNotice(): void + { + trigger_error('notice', E_USER_NOTICE); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/testdox/_files/OutcomeAndIssuesTest.php b/tests/end-to-end/testdox/_files/OutcomeAndIssuesTest.php new file mode 100644 index 00000000000..225df8c2594 --- /dev/null +++ b/tests/end-to-end/testdox/_files/OutcomeAndIssuesTest.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestDox; + +use const E_USER_DEPRECATED; +use const E_USER_NOTICE; +use const E_USER_WARNING; +use function trigger_error; +use Exception; +use PHPUnit\Framework\TestCase; + +final class OutcomeAndIssuesTest extends TestCase +{ + public function testSuccess(): void + { + $this->assertTrue(true); + } + + public function testSuccessButRisky(): void + { + } + + public function testSuccessButDeprecation(): void + { + $this->assertTrue(true); + + trigger_error('message', E_USER_DEPRECATED); + } + + public function testSuccessButNotice(): void + { + $this->assertTrue(true); + + trigger_error('message', E_USER_NOTICE); + } + + public function testSuccessButWarning(): void + { + $this->assertTrue(true); + + trigger_error('message', E_USER_WARNING); + } + + public function testFailure(): void + { + $this->assertTrue(false); + } + + public function testError(): void + { + throw new Exception('message'); + } + + public function testIncomplete(): void + { + $this->markTestIncomplete('message'); + } + + public function testSkipped(): void + { + $this->markTestSkipped('message'); + } +} diff --git a/tests/end-to-end/testdox/_files/RiskyTest.php b/tests/end-to-end/testdox/_files/RiskyTest.php new file mode 100644 index 00000000000..c09f7074646 --- /dev/null +++ b/tests/end-to-end/testdox/_files/RiskyTest.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestDox; + +use PHPUnit\Framework\TestCase; + +final class RiskyTest extends TestCase +{ + public function test_this_is_a_useless_test_that_does_not_test_anything(): void + { + } +} diff --git a/tests/end-to-end/testdox/_files/SnakeCaseTest.php b/tests/end-to-end/testdox/_files/SnakeCaseTest.php new file mode 100644 index 00000000000..3bbe34500d2 --- /dev/null +++ b/tests/end-to-end/testdox/_files/SnakeCaseTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestDox; + +use PHPUnit\Framework\TestCase; + +final class SnakeCaseTest extends TestCase +{ + public function test_something_that_works(): void + { + $this->assertTrue(true); + } + + public function test_something_that_does_not_work(): void + { + /* @noinspection PhpUnitAssertTrueWithIncompatibleTypeArgumentInspection */ + $this->assertTrue(false); + } +} diff --git a/tests/end-to-end/testdox/_files/WarningTest.php b/tests/end-to-end/testdox/_files/WarningTest.php new file mode 100644 index 00000000000..c92c5c4a61f --- /dev/null +++ b/tests/end-to-end/testdox/_files/WarningTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\TestDox; + +use const E_USER_WARNING; +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class WarningTest extends TestCase +{ + public function testWarning(): void + { + trigger_error('warning', E_USER_WARNING); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/testdox/_files/bootstrap.php b/tests/end-to-end/testdox/_files/bootstrap.php new file mode 100644 index 00000000000..cea41cada23 --- /dev/null +++ b/tests/end-to-end/testdox/_files/bootstrap.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +require __DIR__ . '/Foo.php'; + +require __DIR__ . '/Bar.php'; diff --git a/tests/end-to-end/testdox/data-provider-with-numeric-data-set-name-colorized.phpt b/tests/end-to-end/testdox/data-provider-with-numeric-data-set-name-colorized.phpt new file mode 100644 index 00000000000..7328bdd78e6 --- /dev/null +++ b/tests/end-to-end/testdox/data-provider-with-numeric-data-set-name-colorized.phpt @@ -0,0 +1,32 @@ +--TEST-- +TestDox: Default output; Data Provider with numeric data set name; No TestDox metadata; Colorized +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Data Provider With Numeric Data Set Name (PHPUnit\TestFixture\TestDox\DataProviderWithNumericDataSetName) + ✔ Something that works with data set 0 + ✘ Something that does not work with data set 0 + ┐ + ├ Failed asserting that false is true. + │ + │ %s_files%eDataProviderWithNumericDataSetNameTest.php:%d + ┴ + +FAILURES! +Tests: 2, Assertions: 2, Failures: 1. diff --git a/tests/end-to-end/testdox/data-provider-with-numeric-data-set-name.phpt b/tests/end-to-end/testdox/data-provider-with-numeric-data-set-name.phpt new file mode 100644 index 00000000000..5fa8c3fba19 --- /dev/null +++ b/tests/end-to-end/testdox/data-provider-with-numeric-data-set-name.phpt @@ -0,0 +1,32 @@ +--TEST-- +TestDox: Default output; Data Provider with numeric data set name; No TestDox metadata +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Data Provider With Numeric Data Set Name (PHPUnit\TestFixture\TestDox\DataProviderWithNumericDataSetName) + ✔ Something that works with data set #0 + ✘ Something that does not work with data set #0 + │ + │ Failed asserting that false is true. + │ + │ %s:%d + │ + +FAILURES! +Tests: 2, Assertions: 2, Failures: 1. diff --git a/tests/end-to-end/testdox/data-provider-with-string-data-set-name-colorized.phpt b/tests/end-to-end/testdox/data-provider-with-string-data-set-name-colorized.phpt new file mode 100644 index 00000000000..2cf11575f92 --- /dev/null +++ b/tests/end-to-end/testdox/data-provider-with-string-data-set-name-colorized.phpt @@ -0,0 +1,32 @@ +--TEST-- +TestDox: Default output; Data Provider with numeric data set name; No TestDox metadata; Colorized +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Data Provider With String Data Set Name (PHPUnit\TestFixture\TestDox\DataProviderWithStringDataSetName) + ✔ Something that works with data·set·name + ✘ Something that does not work with data·set·name + ┐ + ├ Failed asserting that false is true. + │ + │ %s_files%eDataProviderWithStringDataSetNameTest.php:%d + ┴ + +FAILURES! +Tests: 2, Assertions: 2, Failures: 1. diff --git a/tests/end-to-end/testdox/data-provider-with-string-data-set-name.phpt b/tests/end-to-end/testdox/data-provider-with-string-data-set-name.phpt new file mode 100644 index 00000000000..111e0b8f8ed --- /dev/null +++ b/tests/end-to-end/testdox/data-provider-with-string-data-set-name.phpt @@ -0,0 +1,32 @@ +--TEST-- +TestDox: Default output; Data Provider with numeric data set name; No TestDox metadata +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Data Provider With String Data Set Name (PHPUnit\TestFixture\TestDox\DataProviderWithStringDataSetName) + ✔ Something that works with data set "data set name" + ✘ Something that does not work with data set "data set name" + │ + │ Failed asserting that false is true. + │ + │ %s:%d + │ + +FAILURES! +Tests: 2, Assertions: 2, Failures: 1. diff --git a/tests/end-to-end/testdox/diff-colorized-windows.phpt b/tests/end-to-end/testdox/diff-colorized-windows.phpt new file mode 100644 index 00000000000..33afae323f9 --- /dev/null +++ b/tests/end-to-end/testdox/diff-colorized-windows.phpt @@ -0,0 +1,44 @@ +--TEST-- +TestDox: Diff; Colorized; Windows +--SKIPIF-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Diff (PHPUnit\TestFixture\TestDox\Diff) + ✘ Something that does not work + ┐ + ├ Failed asserting that two strings are equal. + ├ --- Expected  + ├ +++ Actual  + ├ @@ @@  + ├  'foo\n  + ├ +baz\n  + ├  bar\n  + ├ -baz\n  + ├  '  + │ + │ %s_files%eDiffTest.php:%d + ┴ + +FAILURES! +Tests: 1, Assertions: 1, Failures: 1. diff --git a/tests/end-to-end/testdox/diff-colorized.phpt b/tests/end-to-end/testdox/diff-colorized.phpt new file mode 100644 index 00000000000..16635fff496 --- /dev/null +++ b/tests/end-to-end/testdox/diff-colorized.phpt @@ -0,0 +1,44 @@ +--TEST-- +TestDox: Diff; Colorized; *nix +--SKIPIF-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Diff (PHPUnit\TestFixture\TestDox\Diff) + ✘ Something that does not work + ┐ + ├ Failed asserting that two strings are equal. + ┊ ---·Expected + ┊ +++·Actual + ┊ @@ @@ + ┊ 'foo\n + ┊ +baz\n + ┊ bar\n + ┊ -baz\n + ┊ ' + │ + │ %s_files%eDiffTest.php:%d + ┴ + +FAILURES! +Tests: 1, Assertions: 1, Failures: 1. diff --git a/tests/end-to-end/testdox/diff.phpt b/tests/end-to-end/testdox/diff.phpt new file mode 100644 index 00000000000..b30e825b263 --- /dev/null +++ b/tests/end-to-end/testdox/diff.phpt @@ -0,0 +1,39 @@ +--TEST-- +TestDox: Diff +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Diff (PHPUnit\TestFixture\TestDox\Diff) + ✘ Something that does not work + │ + │ Failed asserting that two strings are equal. + │ --- Expected + │ +++ Actual + │ @@ @@ + │ 'foo\n + │ +baz\n + │ bar\n + │ -baz\n + │ ' + │ + │ %sDiffTest.php:%d + │ + +FAILURES! +Tests: 1, Assertions: 1, Failures: 1. diff --git a/tests/end-to-end/testdox/formatter-method-does-not-exist.phpt b/tests/end-to-end/testdox/formatter-method-does-not-exist.phpt new file mode 100644 index 00000000000..6b4ebec81aa --- /dev/null +++ b/tests/end-to-end/testdox/formatter-method-does-not-exist.phpt @@ -0,0 +1,33 @@ +--TEST-- +#[TestDoxFormatter]: Formatter method does not exist +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Formatter Method Does Not Exist (PHPUnit\TestFixture\TestDox\FormatterMethodDoesNotExist) + ✔ One with data set #0 + +There was 1 PHPUnit error: + +1) PHPUnit\TestFixture\TestDox\FormatterMethodDoesNotExistTest::testOne#0 with data ('string') +Method PHPUnit\TestFixture\TestDox\FormatterMethodDoesNotExistTest::formatter() cannot be used as a TestDox formatter because it does not exist + +%sFormatterMethodDoesNotExistTest.php:%d + +ERRORS! +Tests: 1, Assertions: 1, Errors: 1. diff --git a/tests/end-to-end/testdox/formatter-method-is-not-public.phpt b/tests/end-to-end/testdox/formatter-method-is-not-public.phpt new file mode 100644 index 00000000000..1f62c536847 --- /dev/null +++ b/tests/end-to-end/testdox/formatter-method-is-not-public.phpt @@ -0,0 +1,33 @@ +--TEST-- +#[TestDoxFormatter]: Formatter method is not public +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Formatter Method Is Not Public (PHPUnit\TestFixture\TestDox\FormatterMethodIsNotPublic) + ✔ One with data set #0 + +There was 1 PHPUnit error: + +1) PHPUnit\TestFixture\TestDox\FormatterMethodIsNotPublicTest::testOne#0 with data ('string') +Method PHPUnit\TestFixture\TestDox\FormatterMethodIsNotPublicTest::formatter() cannot be used as a TestDox formatter because it is not public + +%sFormatterMethodIsNotPublicTest.php:%d + +ERRORS! +Tests: 1, Assertions: 1, Errors: 1. diff --git a/tests/end-to-end/testdox/formatter-method-is-not-static.phpt b/tests/end-to-end/testdox/formatter-method-is-not-static.phpt new file mode 100644 index 00000000000..6e78da51bce --- /dev/null +++ b/tests/end-to-end/testdox/formatter-method-is-not-static.phpt @@ -0,0 +1,33 @@ +--TEST-- +#[TestDoxFormatter]: Formatter method is not static +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Formatter Method Is Not Static (PHPUnit\TestFixture\TestDox\FormatterMethodIsNotStatic) + ✔ One with data set #0 + +There was 1 PHPUnit error: + +1) PHPUnit\TestFixture\TestDox\FormatterMethodIsNotStaticTest::testOne#0 with data ('string') +Method PHPUnit\TestFixture\TestDox\FormatterMethodIsNotStaticTest::formatter() cannot be used as a TestDox formatter because it is not static + +%sFormatterMethodIsNotStaticTest.php:%d + +ERRORS! +Tests: 1, Assertions: 1, Errors: 1. diff --git a/tests/end-to-end/testdox/formatter-method-throws-exception.phpt b/tests/end-to-end/testdox/formatter-method-throws-exception.phpt new file mode 100644 index 00000000000..7f884c1d1b1 --- /dev/null +++ b/tests/end-to-end/testdox/formatter-method-throws-exception.phpt @@ -0,0 +1,34 @@ +--TEST-- +#[TestDoxFormatter]: Formatter method throws exception +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Formatter Method Throws Exception (PHPUnit\TestFixture\TestDox\FormatterMethodThrowsException) + ✔ One with data set #0 + +There was 1 PHPUnit error: + +1) PHPUnit\TestFixture\TestDox\FormatterMethodThrowsExceptionTest::testOne#0 with data ('string') +TestDox formatter PHPUnit\TestFixture\TestDox\FormatterMethodThrowsExceptionTest::formatter() triggered an error: message +%sFormatterMethodThrowsExceptionTest.php:%d + +%sFormatterMethodThrowsExceptionTest.php:%d + +ERRORS! +Tests: 1, Assertions: 1, Errors: 1. diff --git a/tests/end-to-end/testdox/formatter.phpt b/tests/end-to-end/testdox/formatter.phpt new file mode 100644 index 00000000000..852138db2db --- /dev/null +++ b/tests/end-to-end/testdox/formatter.phpt @@ -0,0 +1,25 @@ +--TEST-- +#[TestDoxFormatter]: Correct use +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Formatter (PHPUnit\TestFixture\TestDox\Formatter) + ✔ formatted string + +OK (1 test, 1 assertion) diff --git a/tests/end-to-end/testdox/metadata-colorized.phpt b/tests/end-to-end/testdox/metadata-colorized.phpt new file mode 100644 index 00000000000..a40b00b915a --- /dev/null +++ b/tests/end-to-end/testdox/metadata-colorized.phpt @@ -0,0 +1,32 @@ +--TEST-- +TestDox: Default output; TestDox metadata; Colorized +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Text from class-level TestDox metadata + ✔ Text from method-level TestDox metadata for successful test + ✘ Text from method-level TestDox metadata for failing test + ┐ + ├ Failed asserting that false is true. + │ + │ %s_files%eMetadataTest.php:%d + ┴ + +FAILURES! +Tests: 2, Assertions: 2, Failures: 1. diff --git a/tests/end-to-end/testdox/metadata-data-provider-with-numeric-data-set-name-colorized.phpt b/tests/end-to-end/testdox/metadata-data-provider-with-numeric-data-set-name-colorized.phpt new file mode 100644 index 00000000000..40195ecd6ce --- /dev/null +++ b/tests/end-to-end/testdox/metadata-data-provider-with-numeric-data-set-name-colorized.phpt @@ -0,0 +1,32 @@ +--TEST-- +TestDox: Default output; Data Provider with numeric data set name; TestDox metadata without placeholders; Colorized +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Text from class-level TestDox metadata + ✔ Text from method-level TestDox metadata for successful test with data set 0 + ✘ Text from method-level TestDox metadata for failing test with data set 0 + ┐ + ├ Failed asserting that false is true. + │ + │ %s_files%eDataProviderWithNumericDataSetNameAndMetadataTest.php:%d + ┴ + +FAILURES! +Tests: 2, Assertions: 2, Failures: 1. diff --git a/tests/end-to-end/testdox/metadata-data-provider-with-numeric-data-set-name.phpt b/tests/end-to-end/testdox/metadata-data-provider-with-numeric-data-set-name.phpt new file mode 100644 index 00000000000..66a02221372 --- /dev/null +++ b/tests/end-to-end/testdox/metadata-data-provider-with-numeric-data-set-name.phpt @@ -0,0 +1,32 @@ +--TEST-- +TestDox: Default output; Data Provider with numeric data set name; TestDox metadata without placeholders +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Text from class-level TestDox metadata + ✔ Text from method-level TestDox metadata for successful test with data set #0 + ✘ Text from method-level TestDox metadata for failing test with data set #0 + │ + │ Failed asserting that false is true. + │ + │ %s:%d + │ + +FAILURES! +Tests: 2, Assertions: 2, Failures: 1. diff --git a/tests/end-to-end/testdox/metadata-data-provider-with-string-data-set-name-colorized.phpt b/tests/end-to-end/testdox/metadata-data-provider-with-string-data-set-name-colorized.phpt new file mode 100644 index 00000000000..0f8554b8404 --- /dev/null +++ b/tests/end-to-end/testdox/metadata-data-provider-with-string-data-set-name-colorized.phpt @@ -0,0 +1,32 @@ +--TEST-- +TestDox: Default output; Data Provider with string data set name; TestDox metadata without placeholders; Colorized +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Text from class-level TestDox metadata + ✔ Text from method-level TestDox metadata for successful test with data·set·name + ✘ Text from method-level TestDox metadata for failing test with data·set·name + ┐ + ├ Failed asserting that false is true. + │ + │ %s_files%eDataProviderWithStringDataSetNameAndMetadataTest.php:%d + ┴ + +FAILURES! +Tests: 2, Assertions: 2, Failures: 1. diff --git a/tests/end-to-end/testdox/metadata-data-provider-with-string-data-set-name.phpt b/tests/end-to-end/testdox/metadata-data-provider-with-string-data-set-name.phpt new file mode 100644 index 00000000000..1ff84fdf46d --- /dev/null +++ b/tests/end-to-end/testdox/metadata-data-provider-with-string-data-set-name.phpt @@ -0,0 +1,32 @@ +--TEST-- +TestDox: Default output; Data Provider with string data set name; TestDox metadata without placeholders +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Text from class-level TestDox metadata + ✔ Text from method-level TestDox metadata for successful test with data set "data set name" + ✘ Text from method-level TestDox metadata for failing test with data set "data set name" + │ + │ Failed asserting that false is true. + │ + │ %s:%d + │ + +FAILURES! +Tests: 2, Assertions: 2, Failures: 1. diff --git a/tests/end-to-end/testdox/metadata-with-placeholders-data-provider-with-numeric-data-set-name-colorized.phpt b/tests/end-to-end/testdox/metadata-with-placeholders-data-provider-with-numeric-data-set-name-colorized.phpt new file mode 100644 index 00000000000..b7fc160f2c7 --- /dev/null +++ b/tests/end-to-end/testdox/metadata-with-placeholders-data-provider-with-numeric-data-set-name-colorized.phpt @@ -0,0 +1,34 @@ +--TEST-- +TestDox: Default output; Data Provider with numeric data set name; TestDox metadata with placeholders; Colorized +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Text from class-level TestDox metadata + ✔ Text from method-level TestDox metadata for successful test with placeholders (string, 0, 0.0, array, true, bar, FOO) + ✘ Text from method-level TestDox metadata for failing test with placeholders (string, 0, 0.0, array, true, bar, FOO) + ┐ + ├ Failed asserting that false is true. + │ + │ %s_files%eDataProviderWithNumericDataSetNameAndMetadataWithPlaceholdersTest.php:%d + ┴ + +FAILURES! +Tests: 2, Assertions: 2, Failures: 1. diff --git a/tests/end-to-end/testdox/metadata-with-placeholders-data-provider-with-numeric-data-set-name.phpt b/tests/end-to-end/testdox/metadata-with-placeholders-data-provider-with-numeric-data-set-name.phpt new file mode 100644 index 00000000000..057df234645 --- /dev/null +++ b/tests/end-to-end/testdox/metadata-with-placeholders-data-provider-with-numeric-data-set-name.phpt @@ -0,0 +1,34 @@ +--TEST-- +TestDox: Default output; Data Provider with numeric data set name; TestDox metadata with placeholders +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Text from class-level TestDox metadata + ✔ Text from method-level TestDox metadata for successful test with placeholders (string, 0, 0.0, array, true, bar, FOO) + ✘ Text from method-level TestDox metadata for failing test with placeholders (string, 0, 0.0, array, true, bar, FOO) + │ + │ Failed asserting that false is true. + │ + │ %s:%d + │ + +FAILURES! +Tests: 2, Assertions: 2, Failures: 1. diff --git a/tests/end-to-end/testdox/metadata-with-placeholders-data-provider-with-string-data-set-name-colorized.phpt b/tests/end-to-end/testdox/metadata-with-placeholders-data-provider-with-string-data-set-name-colorized.phpt new file mode 100644 index 00000000000..e9d44602b06 --- /dev/null +++ b/tests/end-to-end/testdox/metadata-with-placeholders-data-provider-with-string-data-set-name-colorized.phpt @@ -0,0 +1,34 @@ +--TEST-- +TestDox: Default output; Data Provider with string data set name; TestDox metadata with placeholders; Colorized +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Text from class-level TestDox metadata + ✔ Text from method-level TestDox metadata for successful test with placeholders (string, 0, 0.0, array, true, bar, FOO) + ✘ Text from method-level TestDox metadata for failing test with placeholders (string, 0, 0.0, array, true, bar, FOO) + ┐ + ├ Failed asserting that false is true. + │ + │ %s_files%eDataProviderWithStringDataSetNameAndMetadataWithPlaceholdersTest.php:%d + ┴ + +FAILURES! +Tests: 2, Assertions: 2, Failures: 1. diff --git a/tests/end-to-end/testdox/metadata-with-placeholders-data-provider-with-string-data-set-name.phpt b/tests/end-to-end/testdox/metadata-with-placeholders-data-provider-with-string-data-set-name.phpt new file mode 100644 index 00000000000..a4dffe0119c --- /dev/null +++ b/tests/end-to-end/testdox/metadata-with-placeholders-data-provider-with-string-data-set-name.phpt @@ -0,0 +1,34 @@ +--TEST-- +TestDox: Default output; Data Provider with string data set name; TestDox metadata with placeholders +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Text from class-level TestDox metadata + ✔ Text from method-level TestDox metadata for successful test with placeholders (string, 0, 0.0, array, true, bar, FOO) + ✘ Text from method-level TestDox metadata for failing test with placeholders (string, 0, 0.0, array, true, bar, FOO) + │ + │ Failed asserting that false is true. + │ + │ %s:%d + │ + +FAILURES! +Tests: 2, Assertions: 2, Failures: 1. diff --git a/tests/end-to-end/testdox/metadata.phpt b/tests/end-to-end/testdox/metadata.phpt new file mode 100644 index 00000000000..a122437474d --- /dev/null +++ b/tests/end-to-end/testdox/metadata.phpt @@ -0,0 +1,32 @@ +--TEST-- +TestDox: Default output; TestDox metadata +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Text from class-level TestDox metadata + ✔ Text from method-level TestDox metadata for successful test + ✘ Text from method-level TestDox metadata for failing test + │ + │ Failed asserting that false is true. + │ + │ %s:%d + │ + +FAILURES! +Tests: 2, Assertions: 2, Failures: 1. diff --git a/tests/end-to-end/testdox/no-metadata-camel-case-colorized.phpt b/tests/end-to-end/testdox/no-metadata-camel-case-colorized.phpt new file mode 100644 index 00000000000..4027d9bcaef --- /dev/null +++ b/tests/end-to-end/testdox/no-metadata-camel-case-colorized.phpt @@ -0,0 +1,32 @@ +--TEST-- +TestDox: Default output; Test name in camel-case notation; No TestDox metadata; Colorized +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Camel Case (PHPUnit\TestFixture\TestDox\CamelCase) + ✔ Something that works + ✘ Something that does not work + ┐ + ├ Failed asserting that false is true. + │ + │ %s_files%eCamelCaseTest.php:%d + ┴ + +FAILURES! +Tests: 2, Assertions: 2, Failures: 1. diff --git a/tests/end-to-end/testdox/no-metadata-camel-case.phpt b/tests/end-to-end/testdox/no-metadata-camel-case.phpt new file mode 100644 index 00000000000..dea35a7ed51 --- /dev/null +++ b/tests/end-to-end/testdox/no-metadata-camel-case.phpt @@ -0,0 +1,32 @@ +--TEST-- +TestDox: Default output; Test name in camel-case notation; No TestDox metadata +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Camel Case (PHPUnit\TestFixture\TestDox\CamelCase) + ✔ Something that works + ✘ Something that does not work + │ + │ Failed asserting that false is true. + │ + │ %sCamelCaseTest.php:%d + │ + +FAILURES! +Tests: 2, Assertions: 2, Failures: 1. diff --git a/tests/end-to-end/testdox/no-metadata-snake-case-colorized.phpt b/tests/end-to-end/testdox/no-metadata-snake-case-colorized.phpt new file mode 100644 index 00000000000..8e8c9ada5e0 --- /dev/null +++ b/tests/end-to-end/testdox/no-metadata-snake-case-colorized.phpt @@ -0,0 +1,32 @@ +--TEST-- +TestDox: Default output; Test name in snake-case notation; No TestDox metadata; Colorized +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Snake Case (PHPUnit\TestFixture\TestDox\SnakeCase) + ✔ Something that works + ✘ Something that does not work + ┐ + ├ Failed asserting that false is true. + │ + │ %s_files%eSnakeCaseTest.php:%d + ┴ + +FAILURES! +Tests: 2, Assertions: 2, Failures: 1. diff --git a/tests/end-to-end/testdox/no-metadata-snake-case.phpt b/tests/end-to-end/testdox/no-metadata-snake-case.phpt new file mode 100644 index 00000000000..6588faa7ceb --- /dev/null +++ b/tests/end-to-end/testdox/no-metadata-snake-case.phpt @@ -0,0 +1,32 @@ +--TEST-- +TestDox: Default output; Test name in snake-case notation; No TestDox metadata +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Snake Case (PHPUnit\TestFixture\TestDox\SnakeCase) + ✔ Something that works + ✘ Something that does not work + │ + │ Failed asserting that false is true. + │ + │ %sSnakeCaseTest.php:%d + │ + +FAILURES! +Tests: 2, Assertions: 2, Failures: 1. diff --git a/tests/end-to-end/testdox/outcome-and-issues-with-summary.phpt b/tests/end-to-end/testdox/outcome-and-issues-with-summary.phpt new file mode 100644 index 00000000000..2aeddcec11d --- /dev/null +++ b/tests/end-to-end/testdox/outcome-and-issues-with-summary.phpt @@ -0,0 +1,110 @@ +--TEST-- +Different outcomes and issues (with TestDox summary) +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Outcome And Issues (PHPUnit\TestFixture\TestDox\OutcomeAndIssues) + ✔ Success + ⚠ Success but risky + ⚠ Success but deprecation + ⚠ Success but notice + ⚠ Success but warning + ✘ Failure + │ + │ Failed asserting that false is true. + │ + │ %sOutcomeAndIssuesTest.php:53 + │ + ✘ Error + │ + │ Exception: message + │ + │ %sOutcomeAndIssuesTest.php:58 + │ + ∅ Incomplete + │ + │ message + │ + │ %sOutcomeAndIssuesTest.php:63 + │ + ↩ Skipped + +Summary of tests with errors, failures, or issues: + +Outcome And Issues (PHPUnit\TestFixture\TestDox\OutcomeAndIssues) + ⚠ Success but risky + ⚠ Success but deprecation + ⚠ Success but notice + ⚠ Success but warning + ✘ Failure + │ + │ Failed asserting that false is true. + │ + │ %sOutcomeAndIssuesTest.php:53 + │ + ✘ Error + │ + │ Exception: message + │ + │ %sOutcomeAndIssuesTest.php:58 + │ + ∅ Incomplete + │ + │ message + │ + │ %sOutcomeAndIssuesTest.php:63 + │ + ↩ Skipped + +There was 1 risky test: + +1) PHPUnit\TestFixture\TestDox\OutcomeAndIssuesTest::testSuccessButRisky +This test did not perform any assertions + +%sOutcomeAndIssuesTest.php:26 + +-- + +1 test triggered 1 warning: + +1) %sOutcomeAndIssuesTest.php:48 +message + +-- + +1 test triggered 1 notice: + +1) %sOutcomeAndIssuesTest.php:41 +message + +-- + +1 test triggered 1 deprecation: + +1) %sOutcomeAndIssuesTest.php:34 +message + +ERRORS! +Tests: 9, Assertions: 5, Errors: 1, Failures: 1, Warnings: 1, Deprecations: 1, Notices: 1, Skipped: 1, Incomplete: 1, Risky: 1. diff --git a/tests/end-to-end/testdox/outcome-and-issues-without-summary.phpt b/tests/end-to-end/testdox/outcome-and-issues-without-summary.phpt new file mode 100644 index 00000000000..3a423e8f627 --- /dev/null +++ b/tests/end-to-end/testdox/outcome-and-issues-without-summary.phpt @@ -0,0 +1,82 @@ +--TEST-- +Different outcomes and issues (without TestDox summary) +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Outcome And Issues (PHPUnit\TestFixture\TestDox\OutcomeAndIssues) + ✔ Success + ⚠ Success but risky + ⚠ Success but deprecation + ⚠ Success but notice + ⚠ Success but warning + ✘ Failure + │ + │ Failed asserting that false is true. + │ + │ %sOutcomeAndIssuesTest.php:53 + │ + ✘ Error + │ + │ Exception: message + │ + │ %sOutcomeAndIssuesTest.php:58 + │ + ∅ Incomplete + │ + │ message + │ + │ %sOutcomeAndIssuesTest.php:63 + │ + ↩ Skipped + +There was 1 risky test: + +1) PHPUnit\TestFixture\TestDox\OutcomeAndIssuesTest::testSuccessButRisky +This test did not perform any assertions + +%sOutcomeAndIssuesTest.php:26 + +-- + +1 test triggered 1 warning: + +1) %sOutcomeAndIssuesTest.php:48 +message + +-- + +1 test triggered 1 notice: + +1) %sOutcomeAndIssuesTest.php:41 +message + +-- + +1 test triggered 1 deprecation: + +1) %sOutcomeAndIssuesTest.php:34 +message + +ERRORS! +Tests: 9, Assertions: 5, Errors: 1, Failures: 1, Warnings: 1, Deprecations: 1, Notices: 1, Skipped: 1, Incomplete: 1, Risky: 1. diff --git a/tests/end-to-end/testdox/risky-test-colorized.phpt b/tests/end-to-end/testdox/risky-test-colorized.phpt new file mode 100644 index 00000000000..6b36a558ff2 --- /dev/null +++ b/tests/end-to-end/testdox/risky-test-colorized.phpt @@ -0,0 +1,33 @@ +--TEST-- +TestDox: Risky; Colorized +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Risky (PHPUnit\TestFixture\TestDox\Risky) + ⚠ This is a useless test that does not test anything + +There was 1 risky test: + +1) PHPUnit\TestFixture\TestDox\RiskyTest::test_this_is_a_useless_test_that_does_not_test_anything +This test did not perform any assertions + +%s:16 + +OK, but there were issues! +Tests: 1, Assertions: 0, Risky: 1. diff --git a/tests/end-to-end/testdox/risky-test.phpt b/tests/end-to-end/testdox/risky-test.phpt new file mode 100644 index 00000000000..4cf3ec43df4 --- /dev/null +++ b/tests/end-to-end/testdox/risky-test.phpt @@ -0,0 +1,33 @@ +--TEST-- +TestDox: Risky +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Risky (PHPUnit\TestFixture\TestDox\Risky) + ⚠ This is a useless test that does not test anything + +There was 1 risky test: + +1) PHPUnit\TestFixture\TestDox\RiskyTest::test_this_is_a_useless_test_that_does_not_test_anything +This test did not perform any assertions + +%s:16 + +OK, but there were issues! +Tests: 1, Assertions: 0, Risky: 1. diff --git a/tests/end-to-end/testdox/test-that-triggers-deprecation-default.phpt b/tests/end-to-end/testdox/test-that-triggers-deprecation-default.phpt new file mode 100644 index 00000000000..e1a02cdf59e --- /dev/null +++ b/tests/end-to-end/testdox/test-that-triggers-deprecation-default.phpt @@ -0,0 +1,26 @@ +--TEST-- +TestDox: Test triggers deprecation and --display-deprecations is not used +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Deprecation (PHPUnit\TestFixture\TestDox\Deprecation) + ⚠ Deprecation + +OK, but there were issues! +Tests: 1, Assertions: 1, Deprecations: 1. diff --git a/tests/end-to-end/testdox/test-that-triggers-deprecation-details.phpt b/tests/end-to-end/testdox/test-that-triggers-deprecation-details.phpt new file mode 100644 index 00000000000..619114598f6 --- /dev/null +++ b/tests/end-to-end/testdox/test-that-triggers-deprecation-details.phpt @@ -0,0 +1,32 @@ +--TEST-- +TestDox: Test triggers deprecation and --display-deprecations is used +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Deprecation (PHPUnit\TestFixture\TestDox\Deprecation) + ⚠ Deprecation + +1 test triggered 1 deprecation: + +1) %sDeprecationTest.php:20 +deprecation + +OK, but there were issues! +Tests: 1, Assertions: 1, Deprecations: 1. diff --git a/tests/end-to-end/testdox/test-that-triggers-notice-default.phpt b/tests/end-to-end/testdox/test-that-triggers-notice-default.phpt new file mode 100644 index 00000000000..eda8af96993 --- /dev/null +++ b/tests/end-to-end/testdox/test-that-triggers-notice-default.phpt @@ -0,0 +1,26 @@ +--TEST-- +TestDox: Test triggers notice and --display-notices is not used +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Notice (PHPUnit\TestFixture\TestDox\Notice) + ⚠ Notice + +OK, but there were issues! +Tests: 1, Assertions: 1, Notices: 1. diff --git a/tests/end-to-end/testdox/test-that-triggers-notice-details.phpt b/tests/end-to-end/testdox/test-that-triggers-notice-details.phpt new file mode 100644 index 00000000000..2c26f02999c --- /dev/null +++ b/tests/end-to-end/testdox/test-that-triggers-notice-details.phpt @@ -0,0 +1,32 @@ +--TEST-- +TestDox: Test triggers notice and --display-notices is used +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Notice (PHPUnit\TestFixture\TestDox\Notice) + ⚠ Notice + +1 test triggered 1 notice: + +1) %sNoticeTest.php:20 +notice + +OK, but there were issues! +Tests: 1, Assertions: 1, Notices: 1. diff --git a/tests/end-to-end/testdox/test-that-triggers-warning-default.phpt b/tests/end-to-end/testdox/test-that-triggers-warning-default.phpt new file mode 100644 index 00000000000..34123752399 --- /dev/null +++ b/tests/end-to-end/testdox/test-that-triggers-warning-default.phpt @@ -0,0 +1,26 @@ +--TEST-- +TestDox: Test triggers warning and --display-warning is not used +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Warning (PHPUnit\TestFixture\TestDox\Warning) + ⚠ Warning + +OK, but there were issues! +Tests: 1, Assertions: 1, Warnings: 1. diff --git a/tests/end-to-end/testdox/test-that-triggers-warning-details.phpt b/tests/end-to-end/testdox/test-that-triggers-warning-details.phpt new file mode 100644 index 00000000000..1cdc34efb24 --- /dev/null +++ b/tests/end-to-end/testdox/test-that-triggers-warning-details.phpt @@ -0,0 +1,32 @@ +--TEST-- +TestDox: Test triggers warning and --display-warning is used +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +Time: %s, Memory: %s + +Warning (PHPUnit\TestFixture\TestDox\Warning) + ⚠ Warning + +1 test triggered 1 warning: + +1) %sWarningTest.php:20 +warning + +OK, but there were issues! +Tests: 1, Assertions: 1, Warnings: 1. diff --git a/tests/end-to-end/two-classes-per-file-invalid.phpt b/tests/end-to-end/two-classes-per-file-invalid.phpt deleted file mode 100644 index a89371d8fa2..00000000000 --- a/tests/end-to-end/two-classes-per-file-invalid.phpt +++ /dev/null @@ -1,29 +0,0 @@ ---TEST-- -phpunit --version ---FILE-- - ---EXPECTF-- -PHPUnit %s by Sebastian Bergmann and contributors. - - -Warning: Test case class not matching filename is deprecated - in %sTwoClassesInvalidTest.php - Class name was 'TwoClassesInvalid', expected 'TwoClassesInvalidTest' -Warning: Test case class not matching filename is deprecated - in %sTwoClassesInvalidTest.php - Class name was 'TwoClassesInvalid2', expected 'TwoClassesInvalidTest' -Warning: Multiple test case classes per file is deprecated - in %sTwoClassesInvalidTest.php - -.. 2 / 2 (100%) - -Time: %s, Memory: %s - -OK (2 tests, 2 assertions) diff --git a/tests/end-to-end/version.phpt b/tests/end-to-end/version.phpt deleted file mode 100644 index bdd38596c87..00000000000 --- a/tests/end-to-end/version.phpt +++ /dev/null @@ -1,13 +0,0 @@ ---TEST-- -phpunit --version ---FILE-- - ---EXPECTF-- -PHPUnit %s by Sebastian Bergmann and contributors. diff --git a/tests/fail/fail.phpt b/tests/fail/fail.phpt deleted file mode 100644 index b88454fdf99..00000000000 --- a/tests/fail/fail.phpt +++ /dev/null @@ -1,5 +0,0 @@ ---TEST-- -// This test intentionally fails and it is checked by Travis. ---FILE-- ---EXPECTF-- -unexpected diff --git a/tests/static-analysis/TestUsingMocks.php b/tests/static-analysis/TestUsingMocks.php deleted file mode 100644 index 24770e4c0d6..00000000000 --- a/tests/static-analysis/TestUsingMocks.php +++ /dev/null @@ -1,94 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\StaticAnalysis; - -use PHPUnit\Framework\TestCase; - -class HelloWorldClass -{ - public function sayHello(): string - { - return 'hello world!'; - } -} - -/** - * @small - */ -final class TestUsingMocks extends TestCase -{ - public function testWillSayHelloThroughCreateMock(): void - { - $mock = $this->createMock(HelloWorldClass::class); - - $mock - ->method('sayHello') - ->willReturn('hello mock!'); - - self::assertSame('hello mock!', $mock->sayHello()); - } - - public function testWillSayHelloThroughCreateStub(): void - { - $mock = $this->createStub(HelloWorldClass::class); - - $mock - ->method('sayHello') - ->willReturn('hello stub!'); - - self::assertSame('hello stub!', $mock->sayHello()); - } - - public function testWillSayHelloThroughCreateConfiguredMock(): void - { - $mock = $this->createConfiguredMock(HelloWorldClass::class, []); - - $mock - ->method('sayHello') - ->willReturn('hello mock!'); - - self::assertSame('hello mock!', $mock->sayHello()); - } - - public function testWillSayHelloThroughCreatePartialMock(): void - { - $mock = $this->createPartialMock(HelloWorldClass::class, []); - - $mock - ->method('sayHello') - ->willReturn('hello mock!'); - - self::assertSame('hello mock!', $mock->sayHello()); - } - - public function testWillSayHelloThroughCreateTestProxy(): void - { - $mock = $this->createTestProxy(HelloWorldClass::class, []); - - $mock - ->method('sayHello') - ->willReturn('hello mock!'); - - self::assertSame('hello mock!', $mock->sayHello()); - } - - public function testWillSayHelloThroughGetMockBuilder(): void - { - $mock = $this - ->getMockBuilder(HelloWorldClass::class) - ->getMock(); - - $mock - ->method('sayHello') - ->willReturn('hello mock!'); - - self::assertSame('hello mock!', $mock->sayHello()); - } -} diff --git a/tests/static-analysis/happy-path/assert-empty.php b/tests/static-analysis/happy-path/assert-empty.php deleted file mode 100644 index 24fd7749f48..00000000000 --- a/tests/static-analysis/happy-path/assert-empty.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath; - -use PHPUnit\Framework\Assert; - -/** @return null */ -function consume(?object $value) -{ - Assert::assertEmpty($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-false.php b/tests/static-analysis/happy-path/assert-false.php deleted file mode 100644 index 2278fb04264..00000000000 --- a/tests/static-analysis/happy-path/assert-false.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertFalse; - -use PHPUnit\Framework\Assert; - -/** - * @param mixed $value - * - * @return false - */ -function consume($value) -{ - Assert::assertFalse($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-instance-of.php b/tests/static-analysis/happy-path/assert-instance-of.php deleted file mode 100644 index 5f098943917..00000000000 --- a/tests/static-analysis/happy-path/assert-instance-of.php +++ /dev/null @@ -1,19 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertInstanceOf; - -use PHPUnit\Framework\Assert; - -function consume(object $value): \stdClass -{ - Assert::assertInstanceOf(\stdClass::class, $value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-is-array.php b/tests/static-analysis/happy-path/assert-is-array.php deleted file mode 100644 index 2571be3e6ca..00000000000 --- a/tests/static-analysis/happy-path/assert-is-array.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertIsArray; - -use PHPUnit\Framework\Assert; - -/** @param mixed $value */ -function consume($value): array -{ - Assert::assertIsArray($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-is-bool.php b/tests/static-analysis/happy-path/assert-is-bool.php deleted file mode 100644 index 91617e741f7..00000000000 --- a/tests/static-analysis/happy-path/assert-is-bool.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertIsBool; - -use PHPUnit\Framework\Assert; - -/** @param mixed $value */ -function consume($value): bool -{ - Assert::assertIsBool($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-is-callable.php b/tests/static-analysis/happy-path/assert-is-callable.php deleted file mode 100644 index d331c76541c..00000000000 --- a/tests/static-analysis/happy-path/assert-is-callable.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertIsCallable; - -use PHPUnit\Framework\Assert; - -/** @param mixed $value */ -function consume($value): callable -{ - Assert::assertIsCallable($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-is-float.php b/tests/static-analysis/happy-path/assert-is-float.php deleted file mode 100644 index 228f6fee2e6..00000000000 --- a/tests/static-analysis/happy-path/assert-is-float.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertIsFloat; - -use PHPUnit\Framework\Assert; - -/** @param mixed $value */ -function consume($value): float -{ - Assert::assertIsFloat($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-is-int.php b/tests/static-analysis/happy-path/assert-is-int.php deleted file mode 100644 index 8b72dfc5426..00000000000 --- a/tests/static-analysis/happy-path/assert-is-int.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertIsInt; - -use PHPUnit\Framework\Assert; - -/** @param mixed $value */ -function consume($value): int -{ - Assert::assertIsInt($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-is-iterable.php b/tests/static-analysis/happy-path/assert-is-iterable.php deleted file mode 100644 index 489472ccdb9..00000000000 --- a/tests/static-analysis/happy-path/assert-is-iterable.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertIsIterable; - -use PHPUnit\Framework\Assert; - -/** @param mixed $value */ -function consume($value): iterable -{ - Assert::assertIsIterable($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-is-not-array.php b/tests/static-analysis/happy-path/assert-is-not-array.php deleted file mode 100644 index 815987cb501..00000000000 --- a/tests/static-analysis/happy-path/assert-is-not-array.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertIsNotArray; - -use PHPUnit\Framework\Assert; - -/** @param array|int $value */ -function consume($value): int -{ - Assert::assertIsNotArray($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-is-not-bool.php b/tests/static-analysis/happy-path/assert-is-not-bool.php deleted file mode 100644 index b0c7b4c72f1..00000000000 --- a/tests/static-analysis/happy-path/assert-is-not-bool.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertIsNotBool; - -use PHPUnit\Framework\Assert; - -/** @param bool|int $value */ -function consume($value): int -{ - Assert::assertIsNotBool($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-is-not-callable.php b/tests/static-analysis/happy-path/assert-is-not-callable.php deleted file mode 100644 index 853b7b810e8..00000000000 --- a/tests/static-analysis/happy-path/assert-is-not-callable.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertIsCallable; - -use PHPUnit\Framework\Assert; - -/** @param callable|int $value */ -function consume($value): int -{ - Assert::assertIsNotCallable($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-is-not-float.php b/tests/static-analysis/happy-path/assert-is-not-float.php deleted file mode 100644 index 6ef3d47225a..00000000000 --- a/tests/static-analysis/happy-path/assert-is-not-float.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertIsNotFloat; - -use PHPUnit\Framework\Assert; - -/** @param float|int $value */ -function consume($value): int -{ - Assert::assertIsNotFloat($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-is-not-int.php b/tests/static-analysis/happy-path/assert-is-not-int.php deleted file mode 100644 index 5787921339c..00000000000 --- a/tests/static-analysis/happy-path/assert-is-not-int.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertIsNotInt; - -use PHPUnit\Framework\Assert; - -/** @param float|int $value */ -function consume($value): float -{ - Assert::assertIsNotInt($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-is-not-iterable.php b/tests/static-analysis/happy-path/assert-is-not-iterable.php deleted file mode 100644 index 0cb93f22593..00000000000 --- a/tests/static-analysis/happy-path/assert-is-not-iterable.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertIsNotIterable; - -use PHPUnit\Framework\Assert; - -/** @param int|iterable $value */ -function consume($value): int -{ - Assert::assertIsNotIterable($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-is-not-numeric.php b/tests/static-analysis/happy-path/assert-is-not-numeric.php deleted file mode 100644 index c1d63c2e2ba..00000000000 --- a/tests/static-analysis/happy-path/assert-is-not-numeric.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertIsNotNumeric; - -use PHPUnit\Framework\Assert; - -/** @param array|numeric $value */ -function consume($value): array -{ - Assert::assertIsNotNumeric($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-is-not-object.php b/tests/static-analysis/happy-path/assert-is-not-object.php deleted file mode 100644 index 87056210295..00000000000 --- a/tests/static-analysis/happy-path/assert-is-not-object.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertIsNotObject; - -use PHPUnit\Framework\Assert; - -/** @param int|object $value */ -function consume($value): int -{ - Assert::assertIsNotObject($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-is-not-resource.php b/tests/static-analysis/happy-path/assert-is-not-resource.php deleted file mode 100644 index 882f711ef7c..00000000000 --- a/tests/static-analysis/happy-path/assert-is-not-resource.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertIsNotResource; - -use PHPUnit\Framework\Assert; - -/** @param int|resource $value */ -function consume($value): int -{ - Assert::assertIsNotResource($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-is-not-scalar.php b/tests/static-analysis/happy-path/assert-is-not-scalar.php deleted file mode 100644 index 361264eb463..00000000000 --- a/tests/static-analysis/happy-path/assert-is-not-scalar.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertIsNotScalar; - -use PHPUnit\Framework\Assert; - -/** @param object|scalar $value */ -function consume($value): object -{ - Assert::assertIsNotScalar($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-is-not-string.php b/tests/static-analysis/happy-path/assert-is-not-string.php deleted file mode 100644 index 045510f18a0..00000000000 --- a/tests/static-analysis/happy-path/assert-is-not-string.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertIsNotString; - -use PHPUnit\Framework\Assert; - -/** @param int|string $value */ -function consume($value): int -{ - Assert::assertIsNotString($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-is-numeric.php b/tests/static-analysis/happy-path/assert-is-numeric.php deleted file mode 100644 index 17e0f3739f9..00000000000 --- a/tests/static-analysis/happy-path/assert-is-numeric.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertIsNumeric; - -use PHPUnit\Framework\Assert; - -/** - * @param mixed $value - * - * @return numeric - */ -function consume($value) -{ - Assert::assertIsNumeric($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-is-object.php b/tests/static-analysis/happy-path/assert-is-object.php deleted file mode 100644 index 33a132ab6be..00000000000 --- a/tests/static-analysis/happy-path/assert-is-object.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertIsObject; - -use PHPUnit\Framework\Assert; - -/** @param mixed $value */ -function consume($value): object -{ - Assert::assertIsObject($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-is-resource.php b/tests/static-analysis/happy-path/assert-is-resource.php deleted file mode 100644 index 8cfc606fd9e..00000000000 --- a/tests/static-analysis/happy-path/assert-is-resource.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertIsResource; - -use PHPUnit\Framework\Assert; - -/** - * @param mixed $value - * - * @return resource - */ -function consume($value) -{ - Assert::assertIsResource($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-is-scalar.php b/tests/static-analysis/happy-path/assert-is-scalar.php deleted file mode 100644 index e83ecc1cb85..00000000000 --- a/tests/static-analysis/happy-path/assert-is-scalar.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertIsScalar; - -use PHPUnit\Framework\Assert; - -/** - * @param mixed $value - * - * @psalm-return scalar - */ -function consume($value) -{ - Assert::assertIsScalar($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-is-string.php b/tests/static-analysis/happy-path/assert-is-string.php deleted file mode 100644 index a15fcf148d2..00000000000 --- a/tests/static-analysis/happy-path/assert-is-string.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertIsString; - -use PHPUnit\Framework\Assert; - -/** @param mixed $value */ -function consume($value): string -{ - Assert::assertIsString($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-not-empty.php b/tests/static-analysis/happy-path/assert-not-empty.php deleted file mode 100644 index 323724b1918..00000000000 --- a/tests/static-analysis/happy-path/assert-not-empty.php +++ /dev/null @@ -1,19 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertNotEmpty; - -use PHPUnit\Framework\Assert; - -function consume(?int $value): int -{ - Assert::assertNotEmpty($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-not-false.php b/tests/static-analysis/happy-path/assert-not-false.php deleted file mode 100644 index 582c7dcc416..00000000000 --- a/tests/static-analysis/happy-path/assert-not-false.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertNotFalse; - -use PHPUnit\Framework\Assert; - -/** @param false|int $value */ -function consume($value): int -{ - Assert::assertNotFalse($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-not-instance-of.php b/tests/static-analysis/happy-path/assert-not-instance-of.php deleted file mode 100644 index 88d981657c4..00000000000 --- a/tests/static-analysis/happy-path/assert-not-instance-of.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertNotInstanceOf; - -use PHPUnit\Framework\Assert; - -class A -{ -} -class B -{ -} - -/** @param A|B $value */ -function consume(object $value): B -{ - Assert::assertNotInstanceOf(A::class, $value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-not-null.php b/tests/static-analysis/happy-path/assert-not-null.php deleted file mode 100644 index f84d87ee96a..00000000000 --- a/tests/static-analysis/happy-path/assert-not-null.php +++ /dev/null @@ -1,19 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertNotNull; - -use PHPUnit\Framework\Assert; - -function consume(?int $value): int -{ - Assert::assertNotNull($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-not-true.php b/tests/static-analysis/happy-path/assert-not-true.php deleted file mode 100644 index ba43275cfe1..00000000000 --- a/tests/static-analysis/happy-path/assert-not-true.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertNotTrue; - -use PHPUnit\Framework\Assert; - -/** @param int|true $value */ -function consume($value): int -{ - Assert::assertNotTrue($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-null.php b/tests/static-analysis/happy-path/assert-null.php deleted file mode 100644 index b7696b17327..00000000000 --- a/tests/static-analysis/happy-path/assert-null.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertNull; - -use PHPUnit\Framework\Assert; - -/** - * @param mixed $value - * - * @return null - */ -function consume($value) -{ - Assert::assertNull($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/assert-same.php b/tests/static-analysis/happy-path/assert-same.php deleted file mode 100644 index ae6ddaecf3a..00000000000 --- a/tests/static-analysis/happy-path/assert-same.php +++ /dev/null @@ -1,19 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertSame; - -use PHPUnit\Framework\Assert; - -function consume(\stdClass $a, object $b): \stdClass -{ - Assert::assertSame($a, $b); - - return $b; -} diff --git a/tests/static-analysis/happy-path/assert-true.php b/tests/static-analysis/happy-path/assert-true.php deleted file mode 100644 index 5ee070111f3..00000000000 --- a/tests/static-analysis/happy-path/assert-true.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertTrue; - -use PHPUnit\Framework\Assert; - -/** - * @param mixed $value - * - * @return true - */ -function consume($value): bool -{ - Assert::assertTrue($value); - - return $value; -} diff --git a/tests/static-analysis/happy-path/fail.php b/tests/static-analysis/happy-path/fail.php deleted file mode 100644 index 100d2980918..00000000000 --- a/tests/static-analysis/happy-path/fail.php +++ /dev/null @@ -1,22 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\StaticAnalysis\HappyPath\AssertEmpty\Fail; - -use PHPUnit\Framework\Assert; - -/** @param int|string $value */ -function consume($value): int -{ - if (\is_string($value)) { - Assert::fail(); - } - - return $value; -} diff --git a/tests/unit/Event/AbstractEventTestCase.php b/tests/unit/Event/AbstractEventTestCase.php new file mode 100644 index 00000000000..0dbf1da9876 --- /dev/null +++ b/tests/unit/Event/AbstractEventTestCase.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use function hrtime; +use PHPUnit\Event\Code\TestCollection; +use PHPUnit\Event\Code\TestDoxBuilder; +use PHPUnit\Event\Telemetry\Duration; +use PHPUnit\Event\Telemetry\HRTime; +use PHPUnit\Event\TestData\TestDataCollection; +use PHPUnit\Event\TestSuite\TestSuiteWithName; +use PHPUnit\Framework\TestCase; +use PHPUnit\Metadata\MetadataCollection; + +abstract class AbstractEventTestCase extends TestCase +{ + final protected function telemetryInfo(): Telemetry\Info + { + return new Telemetry\Info( + new Telemetry\Snapshot( + HRTime::fromSecondsAndNanoseconds(...hrtime(false)), + Telemetry\MemoryUsage::fromBytes(1000), + Telemetry\MemoryUsage::fromBytes(2000), + new Telemetry\GarbageCollectorStatus(0, 0, 0, 0, 0.0, 0.0, 0.0, 0.0, false, false, false, 0), + ), + Duration::fromSecondsAndNanoseconds(123, 456), + Telemetry\MemoryUsage::fromBytes(2000), + Duration::fromSecondsAndNanoseconds(234, 567), + Telemetry\MemoryUsage::fromBytes(3000), + ); + } + + final protected function testValueObject(): Code\TestMethod + { + return new Code\TestMethod( + 'FooTest', + 'testBar', + 'FooTest.php', + 1, + TestDoxBuilder::fromClassNameAndMethodName('Foo', 'bar'), + MetadataCollection::fromArray([]), + TestDataCollection::fromArray([]), + ); + } + + final protected function testSuiteValueObject(): TestSuiteWithName + { + return new TestSuiteWithName( + 'foo', + 9001, + TestCollection::fromArray([]), + ); + } +} diff --git a/tests/unit/Event/Dispatcher/CollectingDispatcherTest.php b/tests/unit/Event/Dispatcher/CollectingDispatcherTest.php new file mode 100644 index 00000000000..35d8b9e10a1 --- /dev/null +++ b/tests/unit/Event/Dispatcher/CollectingDispatcherTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(CollectingDispatcher::class)] +#[Small] +final class CollectingDispatcherTest extends TestCase +{ + public function testHasNoCollectedEventsWhenFlushedImmediatelyAfterCreation(): void + { + $typeMap = new TypeMap; + $typeMap->addMapping(Test\DeprecationTriggeredSubscriber::class, Test\DeprecationTriggered::class); + + $dispatcher = new CollectingDispatcher(new DirectDispatcher($typeMap)); + + $this->assertEmpty($dispatcher->flush()); + } + + public function testCollectsDispatchedEventsUntilFlushed(): void + { + $typeMap = new TypeMap; + $typeMap->addMapping(Test\DeprecationTriggeredSubscriber::class, Test\DeprecationTriggered::class); + + $dispatcher = new CollectingDispatcher(new DirectDispatcher($typeMap)); + $event = $this->createStub(Event::class); + + $dispatcher->dispatch($event); + + $this->assertSame([$event], $dispatcher->flush()->asArray()); + } +} diff --git a/tests/unit/Event/Dispatcher/DeferringDispatcherTest.php b/tests/unit/Event/Dispatcher/DeferringDispatcherTest.php new file mode 100644 index 00000000000..1a30b010b40 --- /dev/null +++ b/tests/unit/Event/Dispatcher/DeferringDispatcherTest.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use PHPUnit\Event\Tracer\Tracer; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\DummySubscriber; + +#[CoversClass(DeferringDispatcher::class)] +#[Small] +final class DeferringDispatcherTest extends TestCase +{ + public function testCollectsEventsUntilFlush(): void + { + $subscribableDispatcher = $this->createMock(SubscribableDispatcher::class); + + $subscribableDispatcher + ->expects($this->never()) + ->method('dispatch'); + + $deferringDispatcher = new DeferringDispatcher($subscribableDispatcher); + + $deferringDispatcher->dispatch($this->createStub(Event::class)); + } + + public function testFlushesCollectedEvents(): void + { + $event = $this->createStub(Event::class); + + $subscribableDispatcher = $this->createMock(SubscribableDispatcher::class); + + $subscribableDispatcher + ->expects($this->once()) + ->method('dispatch') + ->with($this->identicalTo($event)); + + $deferringDispatcher = new DeferringDispatcher($subscribableDispatcher); + + $deferringDispatcher->dispatch($event); + + $deferringDispatcher->flush(); + } + + public function testSubscriberCanBeRegistered(): void + { + $subscriber = $this->createMock(DummySubscriber::class); + + $subscribableDispatcher = $this->createMock(SubscribableDispatcher::class); + + $subscribableDispatcher + ->expects($this->once()) + ->method('registerSubscriber') + ->with($this->identicalTo($subscriber)); + + $deferringDispatcher = new DeferringDispatcher($subscribableDispatcher); + + $deferringDispatcher->registerSubscriber($subscriber); + } + + public function testTracerCanBeRegistered(): void + { + $tracer = $this->createStub(Tracer::class); + + $subscribableDispatcher = $this->createMock(SubscribableDispatcher::class); + + $subscribableDispatcher + ->expects($this->once()) + ->method('registerTracer') + ->with($this->identicalTo($tracer)); + + $deferringDispatcher = new DeferringDispatcher($subscribableDispatcher); + + $deferringDispatcher->registerTracer($tracer); + } +} diff --git a/tests/unit/Event/Dispatcher/DirectDispatcherTest.php b/tests/unit/Event/Dispatcher/DirectDispatcherTest.php new file mode 100644 index 00000000000..220efcbedcb --- /dev/null +++ b/tests/unit/Event/Dispatcher/DirectDispatcherTest.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use PHPUnit\Event\Tracer\Tracer; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\DummyEvent; +use PHPUnit\TestFixture\DummySubscriber; +use RuntimeException; + +#[CoversClass(DirectDispatcher::class)] +#[Small] +final class DirectDispatcherTest extends TestCase +{ + public function testDispatchesEventToKnownSubscribers(): void + { + $event = new DummyEvent; + $typeMap = $this->typeMap(); + + $dispatcher = new DirectDispatcher($typeMap); + + $subscriber = $this->createMock(DummySubscriber::class); + + $subscriber + ->expects($this->once()) + ->method('notify') + ->with($this->identicalTo($event)); + + $dispatcher->registerSubscriber($subscriber); + + $dispatcher->dispatch($event); + } + + public function testDispatchesEventToTracers(): void + { + $event = new DummyEvent; + $typeMap = $this->typeMap(); + + $dispatcher = new DirectDispatcher($typeMap); + + $tracer = $this->createMock(Tracer::class); + + $tracer + ->expects($this->once()) + ->method('trace') + ->with($this->identicalTo($event)); + + $dispatcher->registerTracer($tracer); + + $dispatcher->dispatch($event); + } + + public function testRegisterRejectsUnknownSubscriber(): void + { + $subscriber = $this->createStub(Subscriber::class); + + $dispatcher = new DirectDispatcher(new TypeMap); + + $this->expectException(RuntimeException::class); + + $dispatcher->registerSubscriber($subscriber); + } + + public function testDispatchRejectsUnknownEventType(): void + { + $event = new DummyEvent; + + $dispatcher = new DirectDispatcher(new TypeMap); + + $this->expectException(RuntimeException::class); + + $dispatcher->dispatch($event); + } + + private function typeMap(): TypeMap + { + $typeMap = new TypeMap; + + $typeMap->addMapping(DummySubscriber::class, DummyEvent::class); + + return $typeMap; + } +} diff --git a/tests/unit/Event/Emitter/DispatchingEmitterTest.php b/tests/unit/Event/Emitter/DispatchingEmitterTest.php new file mode 100644 index 00000000000..b65f5d09c1a --- /dev/null +++ b/tests/unit/Event/Emitter/DispatchingEmitterTest.php @@ -0,0 +1,3531 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use Exception; +use PHPUnit\Event\Code\ClassMethod; +use PHPUnit\Event\Code\IssueTrigger\IssueTrigger; +use PHPUnit\Event\Code\TestCollection; +use PHPUnit\Event\Code\TestDoxBuilder; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\Code\TestMethodBuilder; +use PHPUnit\Event\Code\ThrowableBuilder; +use PHPUnit\Event\Telemetry\SystemGarbageCollectorStatusProvider; +use PHPUnit\Event\TestData\TestDataCollection; +use PHPUnit\Event\TestRunner\ChildProcessErrored; +use PHPUnit\Event\TestRunner\ChildProcessErroredSubscriber; +use PHPUnit\Event\TestRunner\ChildProcessFinished; +use PHPUnit\Event\TestRunner\ChildProcessFinishedSubscriber; +use PHPUnit\Event\TestRunner\ChildProcessStarted; +use PHPUnit\Event\TestRunner\ChildProcessStartedSubscriber; +use PHPUnit\Event\TestRunner\DeprecationTriggered as TestRunnerDeprecationTriggered; +use PHPUnit\Event\TestRunner\DeprecationTriggeredSubscriber as TestRunnerDeprecationTriggeredSubscriber; +use PHPUnit\Event\TestRunner\ExecutionAborted; +use PHPUnit\Event\TestRunner\ExecutionAbortedSubscriber; +use PHPUnit\Event\TestRunner\ExecutionFinished; +use PHPUnit\Event\TestRunner\ExecutionFinishedSubscriber; +use PHPUnit\Event\TestRunner\ExecutionStarted; +use PHPUnit\Event\TestRunner\ExecutionStartedSubscriber; +use PHPUnit\Event\TestRunner\GarbageCollectionDisabled; +use PHPUnit\Event\TestRunner\GarbageCollectionDisabledSubscriber; +use PHPUnit\Event\TestRunner\GarbageCollectionEnabled; +use PHPUnit\Event\TestRunner\GarbageCollectionEnabledSubscriber; +use PHPUnit\Event\TestRunner\GarbageCollectionTriggered; +use PHPUnit\Event\TestRunner\GarbageCollectionTriggeredSubscriber; +use PHPUnit\Event\TestRunner\NoticeTriggered as TestRunnerNoticeTriggered; +use PHPUnit\Event\TestRunner\NoticeTriggeredSubscriber as TestRunnerNoticeTriggeredSubscriber; +use PHPUnit\Event\TestRunner\WarningTriggered as TestRunnerWarningTriggered; +use PHPUnit\Event\TestRunner\WarningTriggeredSubscriber as TestRunnerWarningTriggeredSubscriber; +use PHPUnit\Event\TestSuite\Filtered as TestSuiteFiltered; +use PHPUnit\Event\TestSuite\FilteredSubscriber as TestSuiteFilteredSubscriber; +use PHPUnit\Event\TestSuite\Finished as TestSuiteFinished; +use PHPUnit\Event\TestSuite\FinishedSubscriber as TestSuiteFinishedSubscriber; +use PHPUnit\Event\TestSuite\Loaded as TestSuiteLoaded; +use PHPUnit\Event\TestSuite\LoadedSubscriber as TestSuiteLoadedSubscriber; +use PHPUnit\Event\TestSuite\Skipped as TestSuiteSkipped; +use PHPUnit\Event\TestSuite\SkippedSubscriber as TestSuiteSkippedSubscriber; +use PHPUnit\Event\TestSuite\Sorted as TestSuiteSorted; +use PHPUnit\Event\TestSuite\SortedSubscriber as TestSuiteSortedSubscriber; +use PHPUnit\Event\TestSuite\Started as TestSuiteStarted; +use PHPUnit\Event\TestSuite\StartedSubscriber as TestSuiteStartedSubscriber; +use PHPUnit\Event\TestSuite\TestSuiteWithName; +use PHPUnit\Framework; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Metadata\MetadataCollection; +use PHPUnit\TestFixture\RecordingSubscriber; +use PHPUnit\TextUI\CliArguments\Builder; +use PHPUnit\TextUI\Configuration\Merger; +use PHPUnit\TextUI\XmlConfiguration\DefaultConfiguration; + +#[CoversClass(DispatchingEmitter::class)] +#[Small] +final class DispatchingEmitterTest extends Framework\TestCase +{ + #[TestDox('applicationStarted() emits Application\Started event')] + public function testApplicationStartedEmitsApplicationStartedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Application\StartedSubscriber + { + public function notify(Application\Started $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Application\StartedSubscriber::class, + Application\Started::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $emitter->applicationStarted(); + + $this->assertSame(1, $subscriber->recordedEventCount()); + $this->assertInstanceOf(Application\Started::class, $subscriber->lastRecordedEvent()); + } + + #[TestDox('testRunnerStartedStaticAnalysisForCodeCoverage() emits TestRunner\StaticAnalysisForCodeCoverageStarted event')] + public function testTestRunnerStartedStaticAnalysisForCodeCoverageDispatchesStaticAnalysisForCodeCoverageStartedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements TestRunner\StaticAnalysisForCodeCoverageStartedSubscriber + { + public function notify(TestRunner\StaticAnalysisForCodeCoverageStarted $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + TestRunner\StaticAnalysisForCodeCoverageStartedSubscriber::class, + TestRunner\StaticAnalysisForCodeCoverageStarted::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $emitter->testRunnerStartedStaticAnalysisForCodeCoverage(); + + $this->assertSame(1, $subscriber->recordedEventCount()); + $this->assertInstanceOf(TestRunner\StaticAnalysisForCodeCoverageStarted::class, $subscriber->lastRecordedEvent()); + } + + #[TestDox('testRunnerFinishedStaticAnalysisForCodeCoverage() emits TestRunner\StaticAnalysisForCodeCoverageFinished event')] + public function testTestRunnerFinishedStaticAnalysisForCodeCoverageDispatchesStaticAnalysisForCodeCoverageFinishedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements TestRunner\StaticAnalysisForCodeCoverageFinishedSubscriber + { + public function notify(TestRunner\StaticAnalysisForCodeCoverageFinished $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + TestRunner\StaticAnalysisForCodeCoverageFinishedSubscriber::class, + TestRunner\StaticAnalysisForCodeCoverageFinished::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $cacheHits = 1; + $cacheMisses = 2; + + $emitter->testRunnerFinishedStaticAnalysisForCodeCoverage($cacheHits, $cacheMisses); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(TestRunner\StaticAnalysisForCodeCoverageFinished::class, $event); + + $this->assertSame($cacheHits, $event->cacheHits()); + $this->assertSame($cacheMisses, $event->cacheMisses()); + } + + #[TestDox('testRunnerStarted() emits TestRunner\Started event')] + public function testTestRunnerStartedEmitsTestRunnerStartedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements TestRunner\StartedSubscriber + { + public function notify(TestRunner\Started $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + TestRunner\StartedSubscriber::class, + TestRunner\Started::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $emitter->testRunnerStarted(); + + $this->assertSame(1, $subscriber->recordedEventCount()); + $this->assertInstanceOf(TestRunner\Started::class, $subscriber->lastRecordedEvent()); + } + + #[TestDox('testRunnerConfigured() emits TestRunner\Configured event')] + public function testTestRunnerConfiguredEmitsTestRunnerConfiguredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements TestRunner\ConfiguredSubscriber + { + public function notify(TestRunner\Configured $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + TestRunner\ConfiguredSubscriber::class, + TestRunner\Configured::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $configuration = (new Merger)->merge( + (new Builder)->fromParameters([]), + DefaultConfiguration::create(), + ); + + $emitter->testRunnerConfigured($configuration); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(TestRunner\Configured::class, $event); + $this->assertSame($configuration, $event->configuration()); + } + + #[TestDox('bootstrapFinished() emits TestRunner\BootstrapFinished event')] + public function testBootstrapFinishedEmitsBootstrapFinishedEvent(): void + { + $filename = __FILE__; + + $subscriber = new class extends RecordingSubscriber implements TestRunner\BootstrapFinishedSubscriber + { + public function notify(TestRunner\BootstrapFinished $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + TestRunner\BootstrapFinishedSubscriber::class, + TestRunner\BootstrapFinished::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $emitter->testRunnerBootstrapFinished($filename); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(TestRunner\BootstrapFinished::class, $event); + + $this->assertSame($filename, $event->filename()); + } + + #[TestDox('testRunnerLoadedExtensionFromPhar() emits TestRunner\ExtensionLoadedFromPhar event')] + public function testTestRunnerLoadedExtensionFromPharEmitsExtensionLoadedFromPharEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements TestRunner\ExtensionLoadedFromPharSubscriber + { + public function notify(TestRunner\ExtensionLoadedFromPhar $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + TestRunner\ExtensionLoadedFromPharSubscriber::class, + TestRunner\ExtensionLoadedFromPhar::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $emitter->testRunnerLoadedExtensionFromPhar( + 'filename', + 'example-extension', + '1.2.3', + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + $this->assertInstanceOf(TestRunner\ExtensionLoadedFromPhar::class, $subscriber->lastRecordedEvent()); + } + + #[TestDox('testRunnerBootstrappedExtension() emits TestRunner\ExtensionBootstrapped event')] + public function testTestRunnerBootstrappedExtensionEmitsExtensionBootstrappedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements TestRunner\ExtensionBootstrappedSubscriber + { + public function notify(TestRunner\ExtensionBootstrapped $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + TestRunner\ExtensionBootstrappedSubscriber::class, + TestRunner\ExtensionBootstrapped::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $className = 'the-extension'; + $parameters = ['foo' => 'bar']; + + $emitter->testRunnerBootstrappedExtension($className, $parameters); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(TestRunner\ExtensionBootstrapped::class, $event); + $this->assertSame($className, $event->className()); + $this->assertSame($parameters, $event->parameters()); + } + + #[TestDox('dataProviderMethodCalled() emits Test\DataProviderMethodCalled event')] + public function testDataProviderMethodCalledEmitsDataProviderMethodCalledEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\DataProviderMethodCalledSubscriber + { + public function notify(Test\DataProviderMethodCalled $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\DataProviderMethodCalledSubscriber::class, + Test\DataProviderMethodCalled::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testMethod = new ClassMethod('test-class', 'test-method'); + $dataProviderMethod = new ClassMethod('test-class', 'data-provider-method'); + + $emitter->dataProviderMethodCalled($testMethod, $dataProviderMethod); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\DataProviderMethodCalled::class, $event); + $this->assertSame($testMethod, $event->testMethod()); + $this->assertSame($dataProviderMethod, $event->dataProviderMethod()); + } + + #[TestDox('dataProviderMethodFinished() emits Test\DataProviderMethodFinished event')] + public function testDataProviderMethodFinishedEmitsDataProviderMethodFinishedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\DataProviderMethodFinishedSubscriber + { + public function notify(Test\DataProviderMethodFinished $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\DataProviderMethodFinishedSubscriber::class, + Test\DataProviderMethodFinished::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testMethod = new ClassMethod('test-class', 'test-method'); + $dataProviderMethod = new ClassMethod('test-class', 'data-provider-method'); + + $emitter->dataProviderMethodFinished($testMethod, $dataProviderMethod); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\DataProviderMethodFinished::class, $event); + $this->assertSame($testMethod, $event->testMethod()); + $this->assertSame([$dataProviderMethod], $event->calledMethods()); + } + + #[TestDox('testSuiteLoaded() emits TestSuite\Loaded event')] + public function testTestSuiteLoadedEmitsTestSuiteLoadedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements TestSuiteLoadedSubscriber + { + public function notify(TestSuiteLoaded $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + TestSuiteLoadedSubscriber::class, + TestSuiteLoaded::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $emitter->testSuiteLoaded($this->testSuiteValueObject()); + + $this->assertSame(1, $subscriber->recordedEventCount()); + $this->assertInstanceOf(TestSuiteLoaded::class, $subscriber->lastRecordedEvent()); + } + + #[TestDox('testSuiteFiltered() emits TestSuite\Filtered event')] + public function testTestSuiteFilteredEmitsTestSuiteFilteredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements TestSuiteFilteredSubscriber + { + public function notify(TestSuiteFiltered $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + TestSuiteFilteredSubscriber::class, + TestSuiteFiltered::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testSuite = $this->testSuiteValueObject(); + + $emitter->testSuiteFiltered($testSuite); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(TestSuiteFiltered::class, $event); + + $this->assertSame($testSuite, $event->testSuite()); + } + + #[TestDox('testSuiteSorted() emits TestSuite\Sorted event')] + public function testTestSuiteSortedEmitsTestSuiteSortedEvent(): void + { + $executionOrder = 9001; + $executionOrderDefects = 5; + $resolveDependencies = true; + + $subscriber = new class extends RecordingSubscriber implements TestSuiteSortedSubscriber + { + public function notify(TestSuiteSorted $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + TestSuiteSortedSubscriber::class, + TestSuiteSorted::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $emitter->testSuiteSorted( + $executionOrder, + $executionOrderDefects, + $resolveDependencies, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(TestSuiteSorted::class, $event); + + $this->assertSame($executionOrder, $event->executionOrder()); + $this->assertSame($executionOrderDefects, $event->executionOrderDefects()); + $this->assertSame($resolveDependencies, $event->resolveDependencies()); + } + + #[TestDox('testRunnerEventFacadeSealed() emits TestRunner\EventFacadeSealed event')] + public function testTestRunnerEventFacadeSealedEmitsTestRunnerEventFacadeSealedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements TestRunner\EventFacadeSealedSubscriber + { + public function notify(TestRunner\EventFacadeSealed $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + TestRunner\EventFacadeSealedSubscriber::class, + TestRunner\EventFacadeSealed::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $emitter->testRunnerEventFacadeSealed(); + + $this->assertSame(1, $subscriber->recordedEventCount()); + $this->assertInstanceOf(TestRunner\EventFacadeSealed::class, $subscriber->lastRecordedEvent()); + } + + #[TestDox('testRunnerExecutionStarted() emits TestRunner\ExecutionStarted event')] + public function testTestRunnerExecutionStartedEmitsTestRunnerExecutionStartedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements ExecutionStartedSubscriber + { + public function notify(ExecutionStarted $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + ExecutionStartedSubscriber::class, + ExecutionStarted::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testSuite = $this->testSuiteValueObject(); + + $emitter->testRunnerExecutionStarted($testSuite); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(ExecutionStarted::class, $event); + $this->assertSame($testSuite, $event->testSuite()); + } + + #[TestDox('testRunnerDisabledGarbageCollection() emits TestRunner\GarbageCollectionDisabled event')] + public function testTestRunnerDisabledGarbageCollectionEmitsTestRunnerGarbageCollectionDisabledEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements GarbageCollectionDisabledSubscriber + { + public function notify(GarbageCollectionDisabled $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + GarbageCollectionDisabledSubscriber::class, + GarbageCollectionDisabled::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $emitter->testRunnerDisabledGarbageCollection(); + + $this->assertSame(1, $subscriber->recordedEventCount()); + $this->assertInstanceOf(GarbageCollectionDisabled::class, $subscriber->lastRecordedEvent()); + } + + #[TestDox('testRunnerTriggeredGarbageCollection() emits TestRunner\GarbageCollectionTriggered event')] + public function testTestRunnerTriggeredGarbageCollectionEmitsTestRunnerGarbageCollectionTriggeredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements GarbageCollectionTriggeredSubscriber + { + public function notify(GarbageCollectionTriggered $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + GarbageCollectionTriggeredSubscriber::class, + GarbageCollectionTriggered::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $emitter->testRunnerTriggeredGarbageCollection(); + + $this->assertSame(1, $subscriber->recordedEventCount()); + $this->assertInstanceOf(GarbageCollectionTriggered::class, $subscriber->lastRecordedEvent()); + } + + #[TestDox('childProcessStarted() emits TestRunner\ChildProcessStarted event')] + public function testChildProcessStartedEmitsTestRunnerChildProcessStartedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements ChildProcessStartedSubscriber + { + public function notify(ChildProcessStarted $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + ChildProcessStartedSubscriber::class, + ChildProcessStarted::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $emitter->childProcessStarted(); + + $this->assertSame(1, $subscriber->recordedEventCount()); + $this->assertInstanceOf(ChildProcessStarted::class, $subscriber->lastRecordedEvent()); + } + + #[TestDox('childProcessErrored() emits TestRunner\ChildProcessErrored event')] + public function testChildProcessErroredEmitsTestRunnerChildProcessStartedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements ChildProcessErroredSubscriber + { + public function notify(ChildProcessErrored $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + ChildProcessErroredSubscriber::class, + ChildProcessErrored::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $emitter->childProcessErrored(); + + $this->assertSame(1, $subscriber->recordedEventCount()); + $this->assertInstanceOf(ChildProcessErrored::class, $subscriber->lastRecordedEvent()); + } + + #[TestDox('childProcessFinished() emits TestRunner\ChildProcessFinished event')] + public function testChildProcessFinishedEmitsTestRunnerChildProcessFinishedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements ChildProcessFinishedSubscriber + { + public function notify(ChildProcessFinished $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + ChildProcessFinishedSubscriber::class, + ChildProcessFinished::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $stdout = 'stdout'; + $stderr = 'stderr'; + + $emitter->childProcessFinished($stdout, $stderr); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(ChildProcessFinished::class, $event); + $this->assertSame($stdout, $event->stdout()); + $this->assertSame($stderr, $event->stderr()); + } + + #[TestDox('testSuiteSkipped() emits TestSuite\Skipped event')] + public function testTestSuiteSkippedEmitsTestSuiteSkippedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements TestSuiteSkippedSubscriber + { + public function notify(TestSuiteSkipped $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + TestSuiteSkippedSubscriber::class, + TestSuiteSkipped::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testSuite = $this->testSuiteValueObject(); + $message = 'message'; + + $emitter->testSuiteSkipped($testSuite, $message); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(TestSuiteSkipped::class, $event); + $this->assertSame($testSuite, $event->testSuite()); + $this->assertSame($message, $event->message()); + } + + #[TestDox('testSuiteStarted() emits TestSuite\Started event')] + public function testTestSuiteStartedEmitsTestSuiteStartedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements TestSuiteStartedSubscriber + { + public function notify(TestSuiteStarted $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + TestSuiteStartedSubscriber::class, + TestSuiteStarted::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testSuite = $this->testSuiteValueObject(); + + $emitter->testSuiteStarted($testSuite); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(TestSuiteStarted::class, $event); + $this->assertSame($testSuite, $event->testSuite()); + } + + #[TestDox('testPreparationStarted() emits Test\PreparationStarted event')] + public function testTestPreparationStartedEmitsTestPreparationStartedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\PreparationStartedSubscriber + { + public function notify(Test\PreparationStarted $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\PreparationStartedSubscriber::class, + Test\PreparationStarted::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $test = $this->testValueObject(); + + $emitter->testPreparationStarted($test); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\PreparationStarted::class, $event); + $this->assertSame($test, $event->test()); + } + + #[TestDox('testPreparationErrored() emits Test\PreparationErrored event')] + public function testTestPreparationErroredEmitsTestPreparationErroredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\PreparationErroredSubscriber + { + public function notify(Test\PreparationErrored $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\PreparationErroredSubscriber::class, + Test\PreparationErrored::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $test = $this->testValueObject(); + $throwable = ThrowableBuilder::from(new Exception('message')); + + $emitter->testPreparationErrored($test, $throwable); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\PreparationErrored::class, $event); + $this->assertSame($test, $event->test()); + $this->assertSame($throwable, $event->throwable()); + } + + #[TestDox('testPreparationFailed() emits Test\PreparationFailed event')] + public function testTestPreparationFailedEmitsTestPreparationFailedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\PreparationFailedSubscriber + { + public function notify(Test\PreparationFailed $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\PreparationFailedSubscriber::class, + Test\PreparationFailed::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $test = $this->testValueObject(); + $throwable = ThrowableBuilder::from(new Exception('message')); + + $emitter->testPreparationFailed($test, $throwable); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\PreparationFailed::class, $event); + $this->assertSame($test, $event->test()); + $this->assertSame($throwable, $event->throwable()); + } + + #[TestDox('beforeFirstTestMethodCalled() emits Test\BeforeFirstTestMethodCalled event')] + public function testTestBeforeFirstTestMethodCalledEmitsTestBeforeFirstTestMethodEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\BeforeFirstTestMethodCalledSubscriber + { + public function notify(Test\BeforeFirstTestMethodCalled $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\BeforeFirstTestMethodCalledSubscriber::class, + Test\BeforeFirstTestMethodCalled::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testClassName = 'test-class'; + $calledMethod = new ClassMethod('test-class', 'method'); + + $emitter->beforeFirstTestMethodCalled( + $testClassName, + $calledMethod, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\BeforeFirstTestMethodCalled::class, $event); + + $this->assertSame($testClassName, $event->testClassName()); + $this->assertSame($calledMethod, $event->calledMethod()); + } + + #[TestDox('beforeFirstTestMethodErrored() emits Test\BeforeFirstTestMethodErrored event')] + public function testTestBeforeFirstTestMethodErroredEmitsTestBeforeFirstTestMethodErroredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\BeforeFirstTestMethodErroredSubscriber + { + public function notify(Test\BeforeFirstTestMethodErrored $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\BeforeFirstTestMethodErroredSubscriber::class, + Test\BeforeFirstTestMethodErrored::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testClassName = 'test-class'; + $calledMethod = new ClassMethod('test-class', 'method'); + $throwable = ThrowableBuilder::from(new Exception('message')); + + $emitter->beforeFirstTestMethodErrored( + $testClassName, + $calledMethod, + $throwable, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\BeforeFirstTestMethodErrored::class, $event); + + $this->assertSame($testClassName, $event->testClassName()); + $this->assertSame($calledMethod, $event->calledMethod()); + $this->assertSame($throwable, $event->throwable()); + } + + #[TestDox('beforeFirstTestMethodFailed() emits Test\BeforeFirstTestMethodFailed event')] + public function testTestBeforeFirstTestMethodFailedEmitsTestBeforeFirstTestMethodFailedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\BeforeFirstTestMethodFailedSubscriber + { + public function notify(Test\BeforeFirstTestMethodFailed $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\BeforeFirstTestMethodFailedSubscriber::class, + Test\BeforeFirstTestMethodFailed::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testClassName = 'test-class'; + $calledMethod = new ClassMethod('test-class', 'method'); + $throwable = ThrowableBuilder::from(new Exception('message')); + + $emitter->beforeFirstTestMethodFailed( + $testClassName, + $calledMethod, + $throwable, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\BeforeFirstTestMethodFailed::class, $event); + + $this->assertSame($testClassName, $event->testClassName()); + $this->assertSame($calledMethod, $event->calledMethod()); + $this->assertSame($throwable, $event->throwable()); + } + + #[TestDox('beforeFirstTestMethodFinished() emits Test\BeforeFirstTestMethodFinished event')] + public function testTestBeforeFirstTestMethodFinishedEmitsTestBeforeFirstTestMethodFinishedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\BeforeFirstTestMethodFinishedSubscriber + { + public function notify(Test\BeforeFirstTestMethodFinished $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\BeforeFirstTestMethodFinishedSubscriber::class, + Test\BeforeFirstTestMethodFinished::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testClassName = 'test-class'; + $calledMethod = new ClassMethod('test-class', 'method'); + + $emitter->beforeFirstTestMethodFinished( + $testClassName, + $calledMethod, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\BeforeFirstTestMethodFinished::class, $event); + + $this->assertSame($testClassName, $event->testClassName()); + $this->assertSame([$calledMethod], $event->calledMethods()); + } + + #[TestDox('beforeTestMethodCalled() emits Test\BeforeTestMethodCalled event')] + public function testTestBeforeTestMethodCalledEmitsTestBeforeTestMethodEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\BeforeTestMethodCalledSubscriber + { + public function notify(Test\BeforeTestMethodCalled $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\BeforeTestMethodCalledSubscriber::class, + Test\BeforeTestMethodCalled::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testMethod = $this->testMethod(); + $calledMethod = new ClassMethod('test-class', 'method'); + + $emitter->beforeTestMethodCalled( + $testMethod, + $calledMethod, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\BeforeTestMethodCalled::class, $event); + + $this->assertSame($testMethod, $event->test()); + $this->assertSame($calledMethod, $event->calledMethod()); + } + + #[TestDox('beforeTestMethodErrored() emits Test\BeforeTestMethodErrored event')] + public function testTestBeforeTestMethodErroredEmitsTestBeforeTestMethodErroredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\BeforeTestMethodErroredSubscriber + { + public function notify(Test\BeforeTestMethodErrored $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\BeforeTestMethodErroredSubscriber::class, + Test\BeforeTestMethodErrored::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testMethod = $this->testMethod(); + $calledMethod = new ClassMethod('test-class', 'method'); + $throwable = ThrowableBuilder::from(new Exception('message')); + + $emitter->beforeTestMethodErrored( + $testMethod, + $calledMethod, + $throwable, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\BeforeTestMethodErrored::class, $event); + + $this->assertSame($testMethod, $event->test()); + $this->assertSame($calledMethod, $event->calledMethod()); + $this->assertSame($throwable, $event->throwable()); + } + + #[TestDox('beforeTestMethodFailed() emits Test\BeforeTestMethodFailed event')] + public function testTestBeforeTestMethodFailedEmitsTestBeforeTestMethodFailedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\BeforeTestMethodFailedSubscriber + { + public function notify(Test\BeforeTestMethodFailed $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\BeforeTestMethodFailedSubscriber::class, + Test\BeforeTestMethodFailed::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testMethod = $this->testMethod(); + $calledMethod = new ClassMethod('test-class', 'method'); + $throwable = ThrowableBuilder::from(new Exception('message')); + + $emitter->beforeTestMethodFailed( + $testMethod, + $calledMethod, + $throwable, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\BeforeTestMethodFailed::class, $event); + + $this->assertSame($testMethod, $event->test()); + $this->assertSame($calledMethod, $event->calledMethod()); + $this->assertSame($throwable, $event->throwable()); + } + + #[TestDox('beforeTestMethodFinished() emits Test\BeforeTestMethodFinished event')] + public function testTestBeforeTestMethodFinishedEmitsTestBeforeTestMethodFinishedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\BeforeTestMethodFinishedSubscriber + { + public function notify(Test\BeforeTestMethodFinished $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\BeforeTestMethodFinishedSubscriber::class, + Test\BeforeTestMethodFinished::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testMethod = $this->testMethod(); + $calledMethod = new ClassMethod('test-class', 'method'); + + $emitter->beforeTestMethodFinished( + $testMethod, + $calledMethod, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\BeforeTestMethodFinished::class, $event); + + $this->assertSame($testMethod, $event->test()); + $this->assertSame([$calledMethod], $event->calledMethods()); + } + + #[TestDox('preConditionCalled() emits Test\PreConditionCalled event')] + public function testPreConditionCalledEmitsTestPreConditionCalledEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\PreConditionCalledSubscriber + { + public function notify(Test\PreConditionCalled $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\PreConditionCalledSubscriber::class, + Test\PreConditionCalled::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testMethod = $this->testMethod(); + $calledMethod = new ClassMethod('test-class', 'method'); + + $emitter->preConditionCalled( + $testMethod, + $calledMethod, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\PreConditionCalled::class, $event); + + $this->assertSame($testMethod, $event->test()); + $this->assertSame($calledMethod, $event->calledMethod()); + } + + #[TestDox('preConditionErrored() emits Test\PreConditionErrored event')] + public function testPreConditionErroredEmitsTestPreConditionErroredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\PreConditionErroredSubscriber + { + public function notify(Test\PreConditionErrored $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\PreConditionErroredSubscriber::class, + Test\PreConditionErrored::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testMethod = $this->testMethod(); + $calledMethod = new ClassMethod('test-class', 'method'); + $throwable = ThrowableBuilder::from(new Exception('message')); + + $emitter->preConditionErrored( + $testMethod, + $calledMethod, + $throwable, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\PreConditionErrored::class, $event); + + $this->assertSame($testMethod, $event->test()); + $this->assertSame($calledMethod, $event->calledMethod()); + $this->assertSame($throwable, $event->throwable()); + } + + #[TestDox('preConditionFailed() emits Test\PreConditionFailed event')] + public function testPreConditionFailedEmitsTestPreConditionFailedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\PreConditionFailedSubscriber + { + public function notify(Test\PreConditionFailed $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\PreConditionFailedSubscriber::class, + Test\PreConditionFailed::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testMethod = $this->testMethod(); + $calledMethod = new ClassMethod('test-class', 'method'); + $throwable = ThrowableBuilder::from(new Exception('message')); + + $emitter->preConditionFailed( + $testMethod, + $calledMethod, + $throwable, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\PreConditionFailed::class, $event); + + $this->assertSame($testMethod, $event->test()); + $this->assertSame($calledMethod, $event->calledMethod()); + $this->assertSame($throwable, $event->throwable()); + } + + #[TestDox('preConditionFinished() emits Test\PreConditionFinished event')] + public function testPreConditionFinishedEmitsTestPreConditionFinishedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\PreConditionFinishedSubscriber + { + public function notify(Test\PreConditionFinished $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\PreConditionFinishedSubscriber::class, + Test\PreConditionFinished::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testMethod = $this->testMethod(); + $calledMethod = new ClassMethod('test-class', 'method'); + + $emitter->preConditionFinished( + $testMethod, + $calledMethod, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\PreConditionFinished::class, $event); + + $this->assertSame($testMethod, $event->test()); + $this->assertSame([$calledMethod], $event->calledMethods()); + } + + #[TestDox('testPrepared() emits Test\Prepared event')] + public function testTestPreparedEmitsTestPreparedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\PreparedSubscriber + { + public function notify(Test\Prepared $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\PreparedSubscriber::class, + Test\Prepared::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $test = $this->testValueObject(); + + $emitter->testPrepared($test); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\Prepared::class, $event); + + $this->assertSame($test, $event->test()); + } + + #[TestDox('testRegisteredComparator() emits Test\ComparatorRegistered event')] + public function testComparatorRegisteredEmitsComparatorRegisteredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\ComparatorRegisteredSubscriber + { + public function notify(Test\ComparatorRegistered $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\ComparatorRegisteredSubscriber::class, + Test\ComparatorRegistered::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $className = 'the-class'; + + $emitter->testRegisteredComparator($className); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\ComparatorRegistered::class, $event); + + $this->assertSame($className, $event->className()); + } + + #[TestDox('testCreatedMockObject() emits Test\MockObjectCreated event')] + public function testTestCreatedMockObjectEmitsTestMockObjectCreatedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\MockObjectCreatedSubscriber + { + public function notify(Test\MockObjectCreated $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\MockObjectCreatedSubscriber::class, + Test\MockObjectCreated::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $className = 'the-class'; + + $emitter->testCreatedMockObject($className); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\MockObjectCreated::class, $event); + + $this->assertSame($className, $event->className()); + } + + #[TestDox('testCreatedMockObjectForIntersectionOfInterfaces() emits Test\MockObjectForIntersectionOfInterfacesCreated event')] + public function testTestCreatedMockObjectForIntersectionOfInterfacesEmitsTestMockObjectForIntersectionOfInterfacesCreatedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\MockObjectForIntersectionOfInterfacesCreatedSubscriber + { + public function notify(Test\MockObjectForIntersectionOfInterfacesCreated $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\MockObjectForIntersectionOfInterfacesCreatedSubscriber::class, + Test\MockObjectForIntersectionOfInterfacesCreated::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $interfaces = ['a', 'b']; + + $emitter->testCreatedMockObjectForIntersectionOfInterfaces($interfaces); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\MockObjectForIntersectionOfInterfacesCreated::class, $event); + + $this->assertSame($interfaces, $event->interfaces()); + } + + #[TestDox('testCreatedPartialMockObject() emits Test\PartialMockObjectCreated event')] + public function testTestCreatedPartialMockObjectEmitsTestPartialMockObjectCreatedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\PartialMockObjectCreatedSubscriber + { + public function notify(Test\PartialMockObjectCreated $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\PartialMockObjectCreatedSubscriber::class, + Test\PartialMockObjectCreated::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $className = 'the-class'; + $methodNames = ['foo', 'bar', 'baz']; + + $emitter->testCreatedPartialMockObject( + $className, + ...$methodNames, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\PartialMockObjectCreated::class, $event); + + $this->assertSame($className, $event->className()); + $this->assertSame($methodNames, $event->methodNames()); + } + + #[TestDox('testCreatedStub() emits Test\TestStubCreated event')] + public function testTestStubCreatedEmitsTestTestStubCreatedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\TestStubCreatedSubscriber + { + public function notify(Test\TestStubCreated $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\TestStubCreatedSubscriber::class, + Test\TestStubCreated::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $className = 'the-class'; + + $emitter->testCreatedStub($className); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\TestStubCreated::class, $event); + + $this->assertSame($className, $event->className()); + } + + #[TestDox('testCreatedStubForIntersectionOfInterfaces() emits Test\TestStubForIntersectionOfInterfacesCreated event')] + public function testTestCreatedTestStubForIntersectionOfInterfacesEmitsTestTestStubForIntersectionOfInterfacesCreatedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\TestStubForIntersectionOfInterfacesCreatedSubscriber + { + public function notify(Test\TestStubForIntersectionOfInterfacesCreated $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\TestStubForIntersectionOfInterfacesCreatedSubscriber::class, + Test\TestStubForIntersectionOfInterfacesCreated::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $interfaces = ['a', 'b']; + + $emitter->testCreatedStubForIntersectionOfInterfaces($interfaces); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\TestStubForIntersectionOfInterfacesCreated::class, $event); + + $this->assertSame($interfaces, $event->interfaces()); + } + + #[TestDox('testErrored() emits Test\Errored event')] + public function testTestErroredEmitsTestErroredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\ErroredSubscriber + { + public function notify(Test\Errored $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\ErroredSubscriber::class, + Test\Errored::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $test = $this->testValueObject(); + $throwable = ThrowableBuilder::from(new Exception('error')); + + $emitter->testErrored( + $test, + $throwable, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\Errored::class, $event); + + $this->assertSame($test, $event->test()); + $this->assertSame($throwable, $event->throwable()); + } + + #[TestDox('testFailed() emits Test\Failed event')] + public function testTestFailedEmitsTestFailedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\FailedSubscriber + { + public function notify(Test\Failed $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\FailedSubscriber::class, + Test\Failed::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $test = $this->testValueObject(); + $throwable = ThrowableBuilder::from(new Exception('failure')); + $failure = null; + + $emitter->testFailed( + $test, + $throwable, + $failure, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\Failed::class, $event); + + $this->assertSame($test, $event->test()); + $this->assertSame($throwable, $event->throwable()); + $this->assertFalse($event->hasComparisonFailure()); + } + + #[TestDox('testPassed() emits Test\Passed event')] + public function testTestPassedEmitsTestPassedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\PassedSubscriber + { + public function notify(Test\Passed $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\PassedSubscriber::class, + Test\Passed::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $test = $this->testValueObject(); + + $emitter->testPassed($test); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\Passed::class, $event); + + $this->assertSame($test, $event->test()); + } + + #[TestDox('testConsideredRisky() emits Test\ConsideredRisky event')] + public function testTestConsideredRiskyEmitsTestConsideredRiskyEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\ConsideredRiskySubscriber + { + public function notify(Test\ConsideredRisky $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\ConsideredRiskySubscriber::class, + Test\ConsideredRisky::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $test = $this->testValueObject(); + $message = 'message'; + + $emitter->testConsideredRisky($test, $message); + + $this->assertSame(1, $subscriber->recordedEventCount()); + $this->assertInstanceOf(Test\ConsideredRisky::class, $subscriber->lastRecordedEvent()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + } + + #[TestDox('testMarkedAsIncomplete() emits Test\MarkedIncomplete event')] + public function testTestMarkedIncompleteEmitsTestMarkedIncompleteEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\MarkedIncompleteSubscriber + { + public function notify(Test\MarkedIncomplete $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\MarkedIncompleteSubscriber::class, + Test\MarkedIncomplete::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $test = $this->testValueObject(); + $throwable = ThrowableBuilder::from(new Exception('incomplete')); + + $emitter->testMarkedAsIncomplete( + $test, + $throwable, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\MarkedIncomplete::class, $event); + + $this->assertSame($test, $event->test()); + $this->assertSame($throwable, $event->throwable()); + } + + #[TestDox('testSkipped() emits Test\Skipped event')] + public function testTestSkippedEmitsTestSkippedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\SkippedSubscriber + { + public function notify(Test\Skipped $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\SkippedSubscriber::class, + Test\Skipped::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $test = $this->testValueObject(); + $message = 'skipped'; + + $emitter->testSkipped( + $test, + $message, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\Skipped::class, $event); + + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + } + + #[TestDox('testTriggeredPhpunitDeprecation() emits Test\PhpunitDeprecationTriggered event')] + public function testTestTriggeredPhpunitDeprecationEmitsTestPhpunitDeprecationTriggeredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\PhpunitDeprecationTriggeredSubscriber + { + public function notify(Test\PhpunitDeprecationTriggered $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\PhpunitDeprecationTriggeredSubscriber::class, + Test\PhpunitDeprecationTriggered::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $test = $this->testValueObject(); + $message = 'message'; + + $emitter->testTriggeredPhpunitDeprecation( + $test, + $message, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\PhpunitDeprecationTriggered::class, $event); + + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + } + + #[TestDox('testTriggeredPhpunitNotice() emits Test\PhpunitNoticeTriggered event')] + public function testTestTriggeredPhpunitNoticeEmitsTestPhpunitNoticeTriggeredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\PhpunitNoticeTriggeredSubscriber + { + public function notify(Test\PhpunitNoticeTriggered $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\PhpunitNoticeTriggeredSubscriber::class, + Test\PhpunitNoticeTriggered::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $test = $this->testValueObject(); + $message = 'message'; + + $emitter->testTriggeredPhpunitNotice( + $test, + $message, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\PhpunitNoticeTriggered::class, $event); + + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + } + + #[TestDox('testTriggeredPhpDeprecation() emits Test\PhpDeprecationTriggered event')] + public function testTestTriggeredPhpDeprecationEmitsTestPhpDeprecationTriggeredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\PhpDeprecationTriggeredSubscriber + { + public function notify(Test\PhpDeprecationTriggered $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\PhpDeprecationTriggeredSubscriber::class, + Test\PhpDeprecationTriggered::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $test = $this->testValueObject(); + $message = 'message'; + $file = 'file.php'; + $line = 1; + $suppressed = false; + $ignoredByBaseline = false; + $ignoredByTest = false; + $trigger = IssueTrigger::unknown(); + + $emitter->testTriggeredPhpDeprecation( + $test, + $message, + $file, + $line, + $suppressed, + $ignoredByBaseline, + $ignoredByTest, + $trigger, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\PhpDeprecationTriggered::class, $event); + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + $this->assertSame($file, $event->file()); + $this->assertSame($line, $event->line()); + $this->assertSame($suppressed, $event->wasSuppressed()); + $this->assertSame($ignoredByBaseline, $event->ignoredByBaseline()); + $this->assertSame($ignoredByTest, $event->ignoredByTest()); + $this->assertSame($trigger, $event->trigger()); + } + + #[TestDox('testTriggeredDeprecation() emits Test\DeprecationTriggered event')] + public function testTestTriggeredDeprecationEmitsTestDeprecationTriggeredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\DeprecationTriggeredSubscriber + { + public function notify(Test\DeprecationTriggered $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\DeprecationTriggeredSubscriber::class, + Test\DeprecationTriggered::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $test = $this->testValueObject(); + $message = 'message'; + $file = 'file.php'; + $line = 1; + $suppressed = false; + $ignoredByBaseline = false; + $ignoredByTest = false; + $trigger = IssueTrigger::unknown(); + $stackTrace = 'stack-trace'; + + $emitter->testTriggeredDeprecation( + $test, + $message, + $file, + $line, + $suppressed, + $ignoredByBaseline, + $ignoredByTest, + $trigger, + $stackTrace, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\DeprecationTriggered::class, $event); + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + $this->assertSame($file, $event->file()); + $this->assertSame($line, $event->line()); + $this->assertSame($suppressed, $event->wasSuppressed()); + $this->assertSame($ignoredByBaseline, $event->ignoredByBaseline()); + $this->assertSame($ignoredByTest, $event->ignoredByTest()); + $this->assertSame($trigger, $event->trigger()); + $this->assertSame($stackTrace, $event->stackTrace()); + } + + #[TestDox('testTriggeredError() emits Test\ErrorTriggered event')] + public function testTestTriggeredErrorEmitsTestErrorTriggeredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\ErrorTriggeredSubscriber + { + public function notify(Test\ErrorTriggered $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\ErrorTriggeredSubscriber::class, + Test\ErrorTriggered::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $test = $this->testValueObject(); + $message = 'message'; + $file = 'file.php'; + $line = 1; + $suppressed = false; + + $emitter->testTriggeredError( + $test, + $message, + $file, + $line, + $suppressed, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\ErrorTriggered::class, $event); + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + $this->assertSame($file, $event->file()); + $this->assertSame($line, $event->line()); + $this->assertSame($suppressed, $event->wasSuppressed()); + } + + #[TestDox('testTriggeredNotice() emits Test\NoticeTriggered event')] + public function testTestTriggeredNoticeEmitsTestNoticeTriggeredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\NoticeTriggeredSubscriber + { + public function notify(Test\NoticeTriggered $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\NoticeTriggeredSubscriber::class, + Test\NoticeTriggered::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $test = $this->testValueObject(); + $message = 'message'; + $file = 'file.php'; + $line = 1; + $suppressed = false; + $ignoredByBaseline = false; + + $emitter->testTriggeredNotice( + $test, + $message, + $file, + $line, + $suppressed, + $ignoredByBaseline, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\NoticeTriggered::class, $event); + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + $this->assertSame($file, $event->file()); + $this->assertSame($line, $event->line()); + $this->assertSame($suppressed, $event->wasSuppressed()); + $this->assertSame($ignoredByBaseline, $event->ignoredByBaseline()); + } + + #[TestDox('testTriggeredPhpNotice() emits Test\PhpNoticeTriggered event')] + public function testTestTriggeredPhpNoticeEmitsTestPhpNoticeTriggeredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\PhpNoticeTriggeredSubscriber + { + public function notify(Test\PhpNoticeTriggered $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\PhpNoticeTriggeredSubscriber::class, + Test\PhpNoticeTriggered::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $test = $this->testValueObject(); + $message = 'message'; + $file = 'file.php'; + $line = 1; + $suppressed = false; + $ignoredByBaseline = false; + + $emitter->testTriggeredPhpNotice( + $test, + $message, + $file, + $line, + $suppressed, + $ignoredByBaseline, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\PhpNoticeTriggered::class, $event); + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + $this->assertSame($file, $event->file()); + $this->assertSame($line, $event->line()); + $this->assertSame($suppressed, $event->wasSuppressed()); + $this->assertSame($ignoredByBaseline, $event->ignoredByBaseline()); + } + + #[TestDox('testTriggeredWarning() emits Test\WarningTriggered event')] + public function testTestTriggeredWarningEmitsTestWarningTriggeredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\WarningTriggeredSubscriber + { + public function notify(Test\WarningTriggered $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\WarningTriggeredSubscriber::class, + Test\WarningTriggered::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $test = $this->testValueObject(); + $message = 'message'; + $file = 'file.php'; + $line = 1; + $suppressed = false; + $ignoredByBaseline = false; + + $emitter->testTriggeredWarning( + $test, + $message, + $file, + $line, + $suppressed, + $ignoredByBaseline, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\WarningTriggered::class, $event); + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + $this->assertSame($file, $event->file()); + $this->assertSame($line, $event->line()); + $this->assertSame($suppressed, $event->wasSuppressed()); + $this->assertSame($ignoredByBaseline, $event->ignoredByBaseline()); + } + + #[TestDox('testTriggeredPhpWarning() emits Test\PhpWarningTriggered event')] + public function testTestTriggeredPhpWarningEmitsTestPhpWarningTriggeredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\PhpWarningTriggeredSubscriber + { + public function notify(Test\PhpWarningTriggered $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\PhpWarningTriggeredSubscriber::class, + Test\PhpWarningTriggered::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $test = $this->testValueObject(); + $message = 'message'; + $file = 'file.php'; + $line = 1; + $suppressed = false; + $ignoredByBaseline = false; + + $emitter->testTriggeredPhpWarning( + $test, + $message, + $file, + $line, + $suppressed, + $ignoredByBaseline, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\PhpWarningTriggered::class, $event); + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + $this->assertSame($file, $event->file()); + $this->assertSame($line, $event->line()); + $this->assertSame($suppressed, $event->wasSuppressed()); + $this->assertSame($ignoredByBaseline, $event->ignoredByBaseline()); + } + + #[TestDox('testTriggeredPhpunitError() emits Test\PhpunitErrorTriggered event')] + public function testTestTriggeredPhpunitErrorEmitsTestPhpunitErrorTriggeredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\PhpunitErrorTriggeredSubscriber + { + public function notify(Test\PhpunitErrorTriggered $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\PhpunitErrorTriggeredSubscriber::class, + Test\PhpunitErrorTriggered::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $test = $this->testValueObject(); + $message = 'message'; + + $emitter->testTriggeredPhpunitError( + $test, + $message, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\PhpunitErrorTriggered::class, $event); + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + } + + #[TestDox('testTriggeredPhpunitWarning() emits Test\PhpunitWarningTriggered event')] + public function testTestTriggeredPhpunitWarningEmitsTestPhpunitWarningTriggeredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\PhpunitWarningTriggeredSubscriber + { + public function notify(Test\PhpunitWarningTriggered $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\PhpunitWarningTriggeredSubscriber::class, + Test\PhpunitWarningTriggered::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $test = TestMethodBuilder::fromTestCase($this); + $message = 'message'; + + $emitter->testTriggeredPhpunitWarning( + $test, + $message, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\PhpunitWarningTriggered::class, $event); + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + $this->assertFalse($event->ignoredByTest()); + } + + #[TestDox('testPrintedUnexpectedOutput() emits Test\PrintedUnexpectedOutput event')] + public function testTestPrintedUnexpectedOutputEmitsTestPrintedUnexpectedOutputEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\PrintedUnexpectedOutputSubscriber + { + public function notify(Test\PrintedUnexpectedOutput $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\PrintedUnexpectedOutputSubscriber::class, + Test\PrintedUnexpectedOutput::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $output = 'output'; + + $emitter->testPrintedUnexpectedOutput( + $output, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\PrintedUnexpectedOutput::class, $event); + $this->assertSame($output, $event->output()); + } + + #[TestDox('testProvidedAdditionalInformation() emits Test\AdditionalInformationProvided event')] + public function testTestProvidedAdditionalInformationEmitsAdditionalInformationProvidedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\AdditionalInformationProvidedSubscriber + { + public function notify(Test\AdditionalInformationProvided $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\AdditionalInformationProvidedSubscriber::class, + Test\AdditionalInformationProvided::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + $test = $this->testValueObject(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $additionalInformation = 'addtional information'; + + $emitter->testProvidedAdditionalInformation( + $test, + $additionalInformation, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\AdditionalInformationProvided::class, $event); + $this->assertSame($test, $event->test()); + $this->assertSame($additionalInformation, $event->additionalInformation()); + } + + #[TestDox('testFinished() emits Test\Finished event')] + public function testTestFinishedEmitsTestFinishedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\FinishedSubscriber + { + public function notify(Test\Finished $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\FinishedSubscriber::class, + Test\Finished::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $test = $this->testValueObject(); + + $emitter->testFinished($test, 1); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\Finished::class, $event); + + $this->assertSame($test, $event->test()); + $this->assertSame(1, $event->numberOfAssertionsPerformed()); + } + + #[TestDox('postConditionCalled() emits Test\PostConditionCalled event')] + public function testPostConditionCalledEmitsTestPostConditionCalledEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\PostConditionCalledSubscriber + { + public function notify(Test\PostConditionCalled $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\PostConditionCalledSubscriber::class, + Test\PostConditionCalled::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testMethod = $this->testMethod(); + $calledMethod = new ClassMethod('test-class', 'method'); + + $emitter->postConditionCalled( + $testMethod, + $calledMethod, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\PostConditionCalled::class, $event); + + $this->assertSame($testMethod, $event->test()); + $this->assertSame($calledMethod, $event->calledMethod()); + } + + #[TestDox('postConditionErrored() emits Test\PostConditionErrored event')] + public function testPostConditionErroredEmitsTestPostConditionErroredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\PostConditionErroredSubscriber + { + public function notify(Test\PostConditionErrored $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\PostConditionErroredSubscriber::class, + Test\PostConditionErrored::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testMethod = $this->testMethod(); + $calledMethod = new ClassMethod('test-class', 'method'); + $throwable = ThrowableBuilder::from(new Exception('message')); + + $emitter->postConditionErrored( + $testMethod, + $calledMethod, + $throwable, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\PostConditionErrored::class, $event); + + $this->assertSame($testMethod, $event->test()); + $this->assertSame($calledMethod, $event->calledMethod()); + $this->assertSame($throwable, $event->throwable()); + } + + #[TestDox('postConditionFailed() emits Test\PostConditionFailed event')] + public function testPostConditionFailedEmitsTestPostConditionFailedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\PostConditionFailedSubscriber + { + public function notify(Test\PostConditionFailed $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\PostConditionFailedSubscriber::class, + Test\PostConditionFailed::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testMethod = $this->testMethod(); + $calledMethod = new ClassMethod('test-class', 'method'); + $throwable = ThrowableBuilder::from(new Exception('message')); + + $emitter->postConditionFailed( + $testMethod, + $calledMethod, + $throwable, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\PostConditionFailed::class, $event); + + $this->assertSame($testMethod, $event->test()); + $this->assertSame($calledMethod, $event->calledMethod()); + $this->assertSame($throwable, $event->throwable()); + } + + #[TestDox('postConditionFinished() emits Test\PostConditionFinished event')] + public function testPostConditionFinishedEmitsTestPostConditionFinishedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\PostConditionFinishedSubscriber + { + public function notify(Test\PostConditionFinished $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\PostConditionFinishedSubscriber::class, + Test\PostConditionFinished::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testMethod = $this->testMethod(); + $calledMethod = new ClassMethod('test-class', 'method'); + + $emitter->postConditionFinished( + $testMethod, + $calledMethod, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\PostConditionFinished::class, $event); + + $this->assertSame($testMethod, $event->test()); + $this->assertSame([$calledMethod], $event->calledMethods()); + } + + #[TestDox('afterTestMethodCalled() emits Test\AfterTestMethodCalled event')] + public function testTestAfterTestMethodCalledEmitsTestAfterTestMethodEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\AfterTestMethodCalledSubscriber + { + public function notify(Test\AfterTestMethodCalled $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\AfterTestMethodCalledSubscriber::class, + Test\AfterTestMethodCalled::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testMethod = $this->testMethod(); + $calledMethod = new ClassMethod('test-class', 'method'); + + $emitter->afterTestMethodCalled( + $testMethod, + $calledMethod, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\AfterTestMethodCalled::class, $event); + + $this->assertSame($testMethod, $event->test()); + $this->assertSame($calledMethod, $event->calledMethod()); + } + + #[TestDox('afterTestMethodErrored() emits Test\AfterTestMethodErrored event')] + public function testTestAfterTestMethodErroredEmitsTestAfterTestMethodErroredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\AfterTestMethodErroredSubscriber + { + public function notify(Test\AfterTestMethodErrored $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\AfterTestMethodErroredSubscriber::class, + Test\AfterTestMethodErrored::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testMethod = $this->testMethod(); + $calledMethod = new ClassMethod('test-class', 'method'); + $throwable = ThrowableBuilder::from(new Exception('message')); + + $emitter->afterTestMethodErrored( + $testMethod, + $calledMethod, + $throwable, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\AfterTestMethodErrored::class, $event); + + $this->assertSame($testMethod, $event->test()); + $this->assertSame($calledMethod, $event->calledMethod()); + $this->assertSame($throwable, $event->throwable()); + } + + #[TestDox('afterTestMethodFailed() emits Test\AfterTestMethodFailed event')] + public function testTestAfterTestMethodFailedEmitsTestAfterTestMethodFailedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\AfterTestMethodFailedSubscriber + { + public function notify(Test\AfterTestMethodFailed $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\AfterTestMethodFailedSubscriber::class, + Test\AfterTestMethodFailed::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testMethod = $this->testMethod(); + $calledMethod = new ClassMethod('test-class', 'method'); + $throwable = ThrowableBuilder::from(new Exception('message')); + + $emitter->afterTestMethodFailed( + $testMethod, + $calledMethod, + $throwable, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\AfterTestMethodFailed::class, $event); + + $this->assertSame($testMethod, $event->test()); + $this->assertSame($calledMethod, $event->calledMethod()); + $this->assertSame($throwable, $event->throwable()); + } + + #[TestDox('afterTestMethodFinished() emits Test\AfterTestMethodFinished event')] + public function testTestAfterTestMethodFinishedEmitsTestAfterTestMethodFinishedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\AfterTestMethodFinishedSubscriber + { + public function notify(Test\AfterTestMethodFinished $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\AfterTestMethodFinishedSubscriber::class, + Test\AfterTestMethodFinished::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testMethod = $this->testMethod(); + $calledMethod = new ClassMethod('test-class', 'method'); + + $emitter->afterTestMethodFinished( + $testMethod, + $calledMethod, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\AfterTestMethodFinished::class, $event); + + $this->assertSame($testMethod, $event->test()); + $this->assertSame([$calledMethod], $event->calledMethods()); + } + + #[TestDox('afterLastTestMethodCalled() emits Test\AfterLastTestMethodCalled event')] + public function testTestAfterLastTestMethodCalledEmitsTestAfterLastTestMethodEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\AfterLastTestMethodCalledSubscriber + { + public function notify(Test\AfterLastTestMethodCalled $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\AfterLastTestMethodCalledSubscriber::class, + Test\AfterLastTestMethodCalled::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testClassName = 'test-class'; + $calledMethod = new ClassMethod('test-class', 'method'); + + $emitter->afterLastTestMethodCalled( + $testClassName, + $calledMethod, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\AfterLastTestMethodCalled::class, $event); + + $this->assertSame($testClassName, $event->testClassName()); + $this->assertSame($calledMethod, $event->calledMethod()); + } + + #[TestDox('afterLastTestMethodErrored() emits Test\AfterLastTestMethodErrored event')] + public function testTestAfterLastTestMethodErroredEmitsTestAfterLastTestMethodErroredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\AfterLastTestMethodErroredSubscriber + { + public function notify(Test\AfterLastTestMethodErrored $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\AfterLastTestMethodErroredSubscriber::class, + Test\AfterLastTestMethodErrored::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testClassName = 'test-class'; + $calledMethod = new ClassMethod('test-class', 'method'); + $throwable = ThrowableBuilder::from(new Exception('message')); + + $emitter->afterLastTestMethodErrored( + $testClassName, + $calledMethod, + $throwable, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\AfterLastTestMethodErrored::class, $event); + + $this->assertSame($testClassName, $event->testClassName()); + $this->assertSame($calledMethod, $event->calledMethod()); + $this->assertSame($throwable, $event->throwable()); + } + + #[TestDox('afterLastTestMethodFailed() emits Test\AfterLastTestMethodFailed event')] + public function testTestAfterLastTestMethodFailedEmitsTestAfterLastTestMethodFailedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\AfterLastTestMethodFailedSubscriber + { + public function notify(Test\AfterLastTestMethodFailed $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\AfterLastTestMethodFailedSubscriber::class, + Test\AfterLastTestMethodFailed::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testClassName = 'test-class'; + $calledMethod = new ClassMethod('test-class', 'method'); + $throwable = ThrowableBuilder::from(new Exception('message')); + + $emitter->afterLastTestMethodFailed( + $testClassName, + $calledMethod, + $throwable, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\AfterLastTestMethodFailed::class, $event); + + $this->assertSame($testClassName, $event->testClassName()); + $this->assertSame($calledMethod, $event->calledMethod()); + $this->assertSame($throwable, $event->throwable()); + } + + #[TestDox('afterLastTestMethodFinished() emits Test\AfterLastTestMethodFinished event')] + public function testTestAfterLastTestMethodFinishedEmitsTestAfterLastTestMethodFinishedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Test\AfterLastTestMethodFinishedSubscriber + { + public function notify(Test\AfterLastTestMethodFinished $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Test\AfterLastTestMethodFinishedSubscriber::class, + Test\AfterLastTestMethodFinished::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $testClassName = 'test-class'; + $calledMethod = new ClassMethod('test-class', 'method'); + + $emitter->afterLastTestMethodFinished( + $testClassName, + $calledMethod, + ); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Test\AfterLastTestMethodFinished::class, $event); + + $this->assertSame($testClassName, $event->testClassName()); + $this->assertSame([$calledMethod], $event->calledMethods()); + } + + #[TestDox('testSuiteFinished() emits TestSuite\Finished event')] + public function testTestSuiteFinishedEmitsTestSuiteFinishedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements TestSuiteFinishedSubscriber + { + public function notify(TestSuiteFinished $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + TestSuiteFinishedSubscriber::class, + TestSuiteFinished::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $emitter->testSuiteFinished($this->testSuiteValueObject()); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(TestSuiteFinished::class, $event); + + $this->assertSame('Test Suite', $event->testSuite()->name()); + $this->assertSame(0, $event->testSuite()->count()); + } + + #[TestDox('testRunnerTriggeredPhpunitDeprecation() emits TestRunner\DeprecationTriggered event')] + public function testTestRunnerTriggeredPhpunitDeprecationEmitsTestRunnerDeprecationTriggeredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements TestRunnerDeprecationTriggeredSubscriber + { + public function notify(TestRunnerDeprecationTriggered $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + TestRunnerDeprecationTriggeredSubscriber::class, + TestRunnerDeprecationTriggered::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $message = 'message'; + + $emitter->testRunnerTriggeredPhpunitDeprecation($message); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(TestRunnerDeprecationTriggered::class, $event); + + $this->assertSame($message, $event->message()); + } + + #[TestDox('testRunnerTriggeredPhpunitNotice() emits TestRunner\NoticeTriggered event')] + public function testTestRunnerTriggeredPhpunitNoticeEmitsTestRunnerNoticeTriggeredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements TestRunnerNoticeTriggeredSubscriber + { + public function notify(TestRunnerNoticeTriggered $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + TestRunnerNoticeTriggeredSubscriber::class, + TestRunnerNoticeTriggered::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $message = 'message'; + + $emitter->testRunnerTriggeredPhpunitNotice($message); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(TestRunnerNoticeTriggered::class, $event); + + $this->assertSame($message, $event->message()); + } + + #[TestDox('testRunnerTriggeredPhpunitWarning() emits TestRunner\WarningTriggered event')] + public function testTestRunnerTriggeredPhpunitWarningEmitsTestRunnerWarningTriggeredEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements TestRunnerWarningTriggeredSubscriber + { + public function notify(TestRunnerWarningTriggered $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + TestRunnerWarningTriggeredSubscriber::class, + TestRunnerWarningTriggered::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $message = 'message'; + + $emitter->testRunnerTriggeredPhpunitWarning($message); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(TestRunnerWarningTriggered::class, $event); + + $this->assertSame($message, $event->message()); + } + + #[TestDox('testRunnerEnabledGarbageCollection() emits TestRunner\GarbageCollectionEnabled event')] + public function testTestRunnerEnabledGarbageCollectionEmitsTestRunnerGarbageCollectionEnabledEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements GarbageCollectionEnabledSubscriber + { + public function notify(GarbageCollectionEnabled $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + GarbageCollectionEnabledSubscriber::class, + GarbageCollectionEnabled::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $emitter->testRunnerEnabledGarbageCollection(); + + $this->assertSame(1, $subscriber->recordedEventCount()); + $this->assertInstanceOf(GarbageCollectionEnabled::class, $subscriber->lastRecordedEvent()); + } + + #[TestDox('testRunnerExecutionAborted() emits TestRunner\ExecutionAborted event')] + public function testTestRunnerExecutionAbortedEmitsTestRunnerExecutionAbortedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements ExecutionAbortedSubscriber + { + public function notify(ExecutionAborted $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + ExecutionAbortedSubscriber::class, + ExecutionAborted::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $emitter->testRunnerExecutionAborted(); + + $this->assertSame(1, $subscriber->recordedEventCount()); + $this->assertInstanceOf(ExecutionAborted::class, $subscriber->lastRecordedEvent()); + } + + #[TestDox('testRunnerExecutionFinished() emits TestRunner\ExecutionFinished event')] + public function testTestRunnerExecutionFinishedEmitsTestRunnerExecutionFinishedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements ExecutionFinishedSubscriber + { + public function notify(ExecutionFinished $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + ExecutionFinishedSubscriber::class, + ExecutionFinished::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $emitter->testRunnerExecutionFinished(); + + $this->assertSame(1, $subscriber->recordedEventCount()); + $this->assertInstanceOf(ExecutionFinished::class, $subscriber->lastRecordedEvent()); + } + + #[TestDox('testRunnerFinished() emits TestRunner\Finished event')] + public function testTestRunnerFinishedEmitsTestRunnerFinishedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements TestRunner\FinishedSubscriber + { + public function notify(TestRunner\Finished $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + TestRunner\FinishedSubscriber::class, + TestRunner\Finished::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $emitter->testRunnerFinished(); + + $this->assertSame(1, $subscriber->recordedEventCount()); + $this->assertInstanceOf(TestRunner\Finished::class, $subscriber->lastRecordedEvent()); + } + + #[TestDox('applicationFinished() emits Application\Finished event')] + public function testApplicationFinishedEmitsApplicationFinishedEvent(): void + { + $subscriber = new class extends RecordingSubscriber implements Application\FinishedSubscriber + { + public function notify(Application\Finished $event): void + { + $this->record($event); + } + }; + + $dispatcher = $this->dispatcherWithRegisteredSubscriber( + Application\FinishedSubscriber::class, + Application\Finished::class, + $subscriber, + ); + + $telemetrySystem = $this->telemetrySystem(); + + $emitter = new DispatchingEmitter( + $dispatcher, + $telemetrySystem, + ); + + $shellExitCode = 0; + + $emitter->applicationFinished($shellExitCode); + + $this->assertSame(1, $subscriber->recordedEventCount()); + + $event = $subscriber->lastRecordedEvent(); + + $this->assertInstanceOf(Application\Finished::class, $event); + $this->assertSame($shellExitCode, $event->shellExitCode()); + } + + private function testSuiteValueObject(): TestSuiteWithName + { + return new TestSuiteWithName( + 'Test Suite', + 0, + TestCollection::fromArray([]), + ); + } + + private function dispatcherWithRegisteredSubscriber(string $subscriberInterface, string $eventClass, Subscriber $subscriber): DirectDispatcher + { + $typeMap = new TypeMap; + + $typeMap->addMapping( + $subscriberInterface, + $eventClass, + ); + + $dispatcher = new DirectDispatcher($typeMap); + + $dispatcher->registerSubscriber($subscriber); + + return $dispatcher; + } + + private function telemetrySystem(): Telemetry\System + { + return new Telemetry\System( + new Telemetry\SystemStopWatch, + new Telemetry\SystemMemoryMeter, + new SystemGarbageCollectorStatusProvider, + ); + } + + private function testValueObject(): TestMethod + { + return new TestMethod( + 'FooTest', + 'testBar', + 'FooTest.php', + 1, + TestDoxBuilder::fromClassNameAndMethodName('Foo', 'bar'), + MetadataCollection::fromArray([]), + TestDataCollection::fromArray([]), + ); + } + + private function testMethod(): TestMethod + { + return new TestMethod( + 'TestClass', + 'testMethod', + 'TestClass.php', + 1, + new Code\TestDox('', '', ''), + MetadataCollection::fromArray([]), + TestDataCollection::fromArray([]), + ); + } +} diff --git a/tests/unit/Event/EventCollectionTest.php b/tests/unit/Event/EventCollectionTest.php new file mode 100644 index 00000000000..47e8b340d18 --- /dev/null +++ b/tests/unit/Event/EventCollectionTest.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(EventCollection::class)] +#[CoversClass(EventCollectionIterator::class)] +#[Small] +final class EventCollectionTest extends TestCase +{ + public function testIsInitiallyEmpty(): void + { + $events = new EventCollection; + + $this->assertEmpty($events); + $this->assertTrue($events->isEmpty()); + $this->assertFalse($events->isNotEmpty()); + $this->assertSame([], $events->asArray()); + } + + public function testCollectsEventObjects(): void + { + $event = $this->createStub(Event::class); + $events = new EventCollection; + + $events->add($event); + + $this->assertNotEmpty($events); + $this->assertTrue($events->isNotEmpty()); + $this->assertFalse($events->isEmpty()); + $this->assertSame([$event], $events->asArray()); + } + + public function testCanBeIterated(): void + { + $event = $this->createStub(Event::class); + $events = new EventCollection; + + $events->add($event); + + foreach ($events as $index => $_event) { + $this->assertSame(0, $index); + $this->assertSame($event, $_event); + } + } +} diff --git a/tests/unit/Event/Events/Application/FinishedTest.php b/tests/unit/Event/Events/Application/FinishedTest.php new file mode 100644 index 00000000000..eabf1f52b33 --- /dev/null +++ b/tests/unit/Event/Events/Application/FinishedTest.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Application; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(Finished::class)] +#[Small] +final class FinishedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $shellExitCode = 0; + + $event = new Finished($telemetryInfo, $shellExitCode); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($shellExitCode, $event->shellExitCode()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new Finished($this->telemetryInfo(), 0); + + $this->assertSame('PHPUnit Finished (Shell Exit Code: 0)', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/Application/StartedTest.php b/tests/unit/Event/Events/Application/StartedTest.php new file mode 100644 index 00000000000..3d7f6d18efe --- /dev/null +++ b/tests/unit/Event/Events/Application/StartedTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Application; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Runtime\Runtime; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(Started::class)] +#[Small] +final class StartedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $runtime = new Runtime; + + $event = new Started( + $telemetryInfo, + $runtime, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($runtime, $event->runtime()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new Started( + $this->telemetryInfo(), + new Runtime, + ); + + $this->assertStringMatchesFormat('PHPUnit Started (PHPUnit %s using PHP %s)', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/Test/AdditionalInformationProvidedTest.php b/tests/unit/Event/Events/Test/AdditionalInformationProvidedTest.php new file mode 100644 index 00000000000..80210406681 --- /dev/null +++ b/tests/unit/Event/Events/Test/AdditionalInformationProvidedTest.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(AdditionalInformationProvided::class)] +#[Small] +final class AdditionalInformationProvidedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $additionalInformation = 'additional information'; + + $event = new AdditionalInformationProvided( + $telemetryInfo, + $test, + $additionalInformation, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($additionalInformation, $event->additionalInformation()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new AdditionalInformationProvided( + $this->telemetryInfo(), + $this->testValueObject(), + 'additional information', + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +Test Provided Additional Information +additional information +EOT + , + $event->asString(), + ); + } +} diff --git a/tests/unit/Event/Events/Test/ComparatorRegisteredTest.php b/tests/unit/Event/Events/Test/ComparatorRegisteredTest.php new file mode 100644 index 00000000000..1d505790d82 --- /dev/null +++ b/tests/unit/Event/Events/Test/ComparatorRegisteredTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(ComparatorRegistered::class)] +#[Small] +final class ComparatorRegisteredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $className = 'ClassName'; + + $event = new ComparatorRegistered( + $telemetryInfo, + $className, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($className, $event->className()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new ComparatorRegistered( + $this->telemetryInfo(), + 'ClassName', + ); + + $this->assertSame('Comparator Registered (ClassName)', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/Test/HookMethod/AfterLastTestMethodCalledTest.php b/tests/unit/Event/Events/Test/HookMethod/AfterLastTestMethodCalledTest.php new file mode 100644 index 00000000000..1278a101f09 --- /dev/null +++ b/tests/unit/Event/Events/Test/HookMethod/AfterLastTestMethodCalledTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(AfterLastTestMethodCalled::class)] +#[Small] +final class AfterLastTestMethodCalledTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $testClassName = 'Test'; + $calledMethod = $this->calledMethod(); + + $event = new AfterLastTestMethodCalled( + $telemetryInfo, + $testClassName, + $calledMethod, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($testClassName, $event->testClassName()); + $this->assertSame($calledMethod, $event->calledMethod()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new AfterLastTestMethodCalled( + $this->telemetryInfo(), + 'test class name', + $this->calledMethod(), + ); + + $this->assertSame('After Last Test Method Called (HookClass::hookMethod)', $event->asString()); + } + + private function calledMethod(): Code\ClassMethod + { + return new Code\ClassMethod('HookClass', 'hookMethod'); + } +} diff --git a/tests/unit/Event/Events/Test/HookMethod/AfterLastTestMethodErroredTest.php b/tests/unit/Event/Events/Test/HookMethod/AfterLastTestMethodErroredTest.php new file mode 100644 index 00000000000..f79c58599d6 --- /dev/null +++ b/tests/unit/Event/Events/Test/HookMethod/AfterLastTestMethodErroredTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use Exception; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(AfterLastTestMethodErrored::class)] +#[Small] +final class AfterLastTestMethodErroredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $testClassName = 'Test'; + $calledMethod = $this->calledMethod(); + $throwable = Code\ThrowableBuilder::from(new Exception('message')); + + $event = new AfterLastTestMethodErrored( + $telemetryInfo, + $testClassName, + $calledMethod, + $throwable, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($testClassName, $event->testClassName()); + $this->assertSame($calledMethod, $event->calledMethod()); + $this->assertSame($throwable, $event->throwable()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new AfterLastTestMethodErrored( + $this->telemetryInfo(), + 'test class name', + $this->calledMethod(), + Code\ThrowableBuilder::from(new Exception('message')), + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +After Last Test Method Errored (HookClass::hookMethod) +message +EOT, + $event->asString(), + ); + } + + private function calledMethod(): Code\ClassMethod + { + return new Code\ClassMethod('HookClass', 'hookMethod'); + } +} diff --git a/tests/unit/Event/Events/Test/HookMethod/AfterLastTestMethodFailedTest.php b/tests/unit/Event/Events/Test/HookMethod/AfterLastTestMethodFailedTest.php new file mode 100644 index 00000000000..2ce12a3410b --- /dev/null +++ b/tests/unit/Event/Events/Test/HookMethod/AfterLastTestMethodFailedTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use Exception; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(AfterLastTestMethodFailed::class)] +#[Small] +final class AfterLastTestMethodFailedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $testClassName = 'Test'; + $calledMethod = $this->calledMethod(); + $throwable = Code\ThrowableBuilder::from(new Exception('message')); + + $event = new AfterLastTestMethodFailed( + $telemetryInfo, + $testClassName, + $calledMethod, + $throwable, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($testClassName, $event->testClassName()); + $this->assertSame($calledMethod, $event->calledMethod()); + $this->assertSame($throwable, $event->throwable()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new AfterLastTestMethodFailed( + $this->telemetryInfo(), + 'test class name', + $this->calledMethod(), + Code\ThrowableBuilder::from(new Exception('message')), + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +After Last Test Method Failed (HookClass::hookMethod) +message +EOT, + $event->asString(), + ); + } + + private function calledMethod(): Code\ClassMethod + { + return new Code\ClassMethod('HookClass', 'hookMethod'); + } +} diff --git a/tests/unit/Event/Events/Test/HookMethod/AfterLastTestMethodFinishedTest.php b/tests/unit/Event/Events/Test/HookMethod/AfterLastTestMethodFinishedTest.php new file mode 100644 index 00000000000..472d1c33bb6 --- /dev/null +++ b/tests/unit/Event/Events/Test/HookMethod/AfterLastTestMethodFinishedTest.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(AfterLastTestMethodFinished::class)] +#[Small] +final class AfterLastTestMethodFinishedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $testClassName = 'Test'; + $calledMethods = $this->calledMethods(); + + $event = new AfterLastTestMethodFinished( + $telemetryInfo, + $testClassName, + ...$calledMethods, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($testClassName, $event->testClassName()); + $this->assertSame($calledMethods, $event->calledMethods()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new AfterLastTestMethodFinished( + $this->telemetryInfo(), + self::class, + ...$this->calledMethods(), + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +After Last Test Method Finished: +- HookClass::hookMethod +- AnotherHookClass::anotherHookMethod +EOT + , + $event->asString(), + ); + } + + /** + * @return list + */ + private function calledMethods(): array + { + return [ + new Code\ClassMethod('HookClass', 'hookMethod'), + new Code\ClassMethod('AnotherHookClass', 'anotherHookMethod'), + ]; + } +} diff --git a/tests/unit/Event/Events/Test/HookMethod/AfterTestMethodCalledTest.php b/tests/unit/Event/Events/Test/HookMethod/AfterTestMethodCalledTest.php new file mode 100644 index 00000000000..49b7af516eb --- /dev/null +++ b/tests/unit/Event/Events/Test/HookMethod/AfterTestMethodCalledTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(AfterTestMethodCalled::class)] +#[Small] +final class AfterTestMethodCalledTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $calledMethod = $this->calledMethod(); + + $event = new AfterTestMethodCalled( + $telemetryInfo, + $test, + $calledMethod, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($calledMethod, $event->calledMethod()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new AfterTestMethodCalled( + $this->telemetryInfo(), + $this->testValueObject(), + $this->calledMethod(), + ); + + $this->assertSame('After Test Method Called (HookClass::hookMethod)', $event->asString()); + } + + private function calledMethod(): Code\ClassMethod + { + return new Code\ClassMethod('HookClass', 'hookMethod'); + } +} diff --git a/tests/unit/Event/Events/Test/HookMethod/AfterTestMethodErroredTest.php b/tests/unit/Event/Events/Test/HookMethod/AfterTestMethodErroredTest.php new file mode 100644 index 00000000000..dbae42f4f94 --- /dev/null +++ b/tests/unit/Event/Events/Test/HookMethod/AfterTestMethodErroredTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use Exception; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(AfterTestMethodErrored::class)] +#[Small] +final class AfterTestMethodErroredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $calledMethod = $this->calledMethod(); + $throwable = Code\ThrowableBuilder::from(new Exception('message')); + + $event = new AfterTestMethodErrored( + $telemetryInfo, + $test, + $calledMethod, + $throwable, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($calledMethod, $event->calledMethod()); + $this->assertSame($throwable, $event->throwable()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new AfterTestMethodErrored( + $this->telemetryInfo(), + $this->testValueObject(), + $this->calledMethod(), + Code\ThrowableBuilder::from(new Exception('message')), + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +After Test Method Errored (HookClass::hookMethod) +message +EOT, + $event->asString(), + ); + } + + private function calledMethod(): Code\ClassMethod + { + return new Code\ClassMethod('HookClass', 'hookMethod'); + } +} diff --git a/tests/unit/Event/Events/Test/HookMethod/AfterTestMethodFailedTest.php b/tests/unit/Event/Events/Test/HookMethod/AfterTestMethodFailedTest.php new file mode 100644 index 00000000000..dd70b70bdb0 --- /dev/null +++ b/tests/unit/Event/Events/Test/HookMethod/AfterTestMethodFailedTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use Exception; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(AfterTestMethodFailed::class)] +#[Small] +final class AfterTestMethodFailedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $calledMethod = $this->calledMethod(); + $throwable = Code\ThrowableBuilder::from(new Exception('message')); + + $event = new AfterTestMethodFailed( + $telemetryInfo, + $test, + $calledMethod, + $throwable, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($calledMethod, $event->calledMethod()); + $this->assertSame($throwable, $event->throwable()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new AfterTestMethodFailed( + $this->telemetryInfo(), + $this->testValueObject(), + $this->calledMethod(), + Code\ThrowableBuilder::from(new Exception('message')), + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +After Test Method Failed (HookClass::hookMethod) +message +EOT, + $event->asString(), + ); + } + + private function calledMethod(): Code\ClassMethod + { + return new Code\ClassMethod('HookClass', 'hookMethod'); + } +} diff --git a/tests/unit/Event/Events/Test/HookMethod/AfterTestMethodFinishedTest.php b/tests/unit/Event/Events/Test/HookMethod/AfterTestMethodFinishedTest.php new file mode 100644 index 00000000000..cc2d82add8f --- /dev/null +++ b/tests/unit/Event/Events/Test/HookMethod/AfterTestMethodFinishedTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(AfterTestMethodFinished::class)] +#[Small] +final class AfterTestMethodFinishedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $calledMethods = $this->calledMethods(); + + $event = new AfterTestMethodFinished( + $telemetryInfo, + $test, + ...$calledMethods, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($calledMethods, $event->calledMethods()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new AfterTestMethodFinished( + $this->telemetryInfo(), + $this->testValueObject(), + ...$this->calledMethods(), + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +After Test Method Finished: +- HookClass::hookMethod +- AnotherHookClass::anotherHookMethod +EOT, + $event->asString(), + ); + } + + /** + * @return list + */ + private function calledMethods(): array + { + return [ + new Code\ClassMethod('HookClass', 'hookMethod'), + new Code\ClassMethod('AnotherHookClass', 'anotherHookMethod'), + ]; + } +} diff --git a/tests/unit/Event/Events/Test/HookMethod/BeforeFirstTestMethodCalledTest.php b/tests/unit/Event/Events/Test/HookMethod/BeforeFirstTestMethodCalledTest.php new file mode 100644 index 00000000000..55445240a51 --- /dev/null +++ b/tests/unit/Event/Events/Test/HookMethod/BeforeFirstTestMethodCalledTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(BeforeFirstTestMethodCalled::class)] +#[Small] +final class BeforeFirstTestMethodCalledTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $testClassName = 'Test'; + $calledMethod = $this->calledMethod(); + + $event = new BeforeFirstTestMethodCalled( + $telemetryInfo, + $testClassName, + $calledMethod, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($testClassName, $event->testClassName()); + $this->assertSame($calledMethod, $event->calledMethod()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new BeforeFirstTestMethodCalled( + $this->telemetryInfo(), + 'Test', + $this->calledMethod(), + ); + + $this->assertSame('Before First Test Method Called (HookClass::hookMethod)', $event->asString()); + } + + private function calledMethod(): Code\ClassMethod + { + return new Code\ClassMethod('HookClass', 'hookMethod'); + } +} diff --git a/tests/unit/Event/Events/Test/HookMethod/BeforeFirstTestMethodErroredTest.php b/tests/unit/Event/Events/Test/HookMethod/BeforeFirstTestMethodErroredTest.php new file mode 100644 index 00000000000..fdba3ff4b1b --- /dev/null +++ b/tests/unit/Event/Events/Test/HookMethod/BeforeFirstTestMethodErroredTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use Exception; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(BeforeFirstTestMethodErrored::class)] +#[Small] +final class BeforeFirstTestMethodErroredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $testClassName = 'Test'; + $calledMethod = $this->calledMethod(); + $throwable = Code\ThrowableBuilder::from(new Exception('message')); + + $event = new BeforeFirstTestMethodErrored( + $telemetryInfo, + $testClassName, + $calledMethod, + $throwable, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($testClassName, $event->testClassName()); + $this->assertSame($calledMethod, $event->calledMethod()); + $this->assertSame($throwable, $event->throwable()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new BeforeFirstTestMethodErrored( + $this->telemetryInfo(), + 'Test', + $this->calledMethod(), + Code\ThrowableBuilder::from(new Exception('message')), + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +Before First Test Method Errored (HookClass::hookMethod) +message +EOT, + $event->asString(), + ); + } + + private function calledMethod(): Code\ClassMethod + { + return new Code\ClassMethod('HookClass', 'hookMethod'); + } +} diff --git a/tests/unit/Event/Events/Test/HookMethod/BeforeFirstTestMethodFailedTest.php b/tests/unit/Event/Events/Test/HookMethod/BeforeFirstTestMethodFailedTest.php new file mode 100644 index 00000000000..a08bb3f3b42 --- /dev/null +++ b/tests/unit/Event/Events/Test/HookMethod/BeforeFirstTestMethodFailedTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use Exception; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(BeforeFirstTestMethodFailed::class)] +#[Small] +final class BeforeFirstTestMethodFailedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $testClassName = 'Test'; + $calledMethod = $this->calledMethod(); + $throwable = Code\ThrowableBuilder::from(new Exception('message')); + + $event = new BeforeFirstTestMethodFailed( + $telemetryInfo, + $testClassName, + $calledMethod, + $throwable, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($testClassName, $event->testClassName()); + $this->assertSame($calledMethod, $event->calledMethod()); + $this->assertSame($throwable, $event->throwable()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new BeforeFirstTestMethodFailed( + $this->telemetryInfo(), + 'Test', + $this->calledMethod(), + Code\ThrowableBuilder::from(new Exception('message')), + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +Before First Test Method Failed (HookClass::hookMethod) +message +EOT, + $event->asString(), + ); + } + + private function calledMethod(): Code\ClassMethod + { + return new Code\ClassMethod('HookClass', 'hookMethod'); + } +} diff --git a/tests/unit/Event/Events/Test/HookMethod/BeforeFirstTestMethodFinishedTest.php b/tests/unit/Event/Events/Test/HookMethod/BeforeFirstTestMethodFinishedTest.php new file mode 100644 index 00000000000..c88dd24d5c7 --- /dev/null +++ b/tests/unit/Event/Events/Test/HookMethod/BeforeFirstTestMethodFinishedTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(BeforeFirstTestMethodFinished::class)] +#[Small] +final class BeforeFirstTestMethodFinishedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $testClassName = 'Test'; + $calledMethods = $this->calledMethods(); + + $event = new BeforeFirstTestMethodFinished( + $telemetryInfo, + $testClassName, + ...$calledMethods, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($testClassName, $event->testClassName()); + $this->assertSame($calledMethods, $event->calledMethods()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new BeforeFirstTestMethodFinished( + $this->telemetryInfo(), + 'Test', + ...$this->calledMethods(), + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +Before First Test Method Finished: +- HookClass::hookMethod +- AnotherHookClass::anotherHookMethod +EOT, + $event->asString(), + ); + } + + /** + * @return list + */ + private function calledMethods(): array + { + return [ + new Code\ClassMethod('HookClass', 'hookMethod'), + new Code\ClassMethod('AnotherHookClass', 'anotherHookMethod'), + ]; + } +} diff --git a/tests/unit/Event/Events/Test/HookMethod/BeforeTestMethodCalledTest.php b/tests/unit/Event/Events/Test/HookMethod/BeforeTestMethodCalledTest.php new file mode 100644 index 00000000000..878cef8df74 --- /dev/null +++ b/tests/unit/Event/Events/Test/HookMethod/BeforeTestMethodCalledTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(BeforeTestMethodCalled::class)] +#[Small] +final class BeforeTestMethodCalledTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $calledMethod = $this->calledMethod(); + + $event = new BeforeTestMethodCalled( + $telemetryInfo, + $test, + $calledMethod, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($calledMethod, $event->calledMethod()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new BeforeTestMethodCalled( + $this->telemetryInfo(), + $this->testValueObject(), + $this->calledMethod(), + ); + + $this->assertSame('Before Test Method Called (HookClass::hookMethod)', $event->asString()); + } + + private function calledMethod(): Code\ClassMethod + { + return new Code\ClassMethod('HookClass', 'hookMethod'); + } +} diff --git a/tests/unit/Event/Events/Test/HookMethod/BeforeTestMethodErroredTest.php b/tests/unit/Event/Events/Test/HookMethod/BeforeTestMethodErroredTest.php new file mode 100644 index 00000000000..e8edf684984 --- /dev/null +++ b/tests/unit/Event/Events/Test/HookMethod/BeforeTestMethodErroredTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use Exception; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(BeforeTestMethodErrored::class)] +#[Small] +final class BeforeTestMethodErroredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $calledMethod = $this->calledMethod(); + $throwable = Code\ThrowableBuilder::from(new Exception('message')); + + $event = new BeforeTestMethodErrored( + $telemetryInfo, + $test, + $calledMethod, + $throwable, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($calledMethod, $event->calledMethod()); + $this->assertSame($throwable, $event->throwable()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new BeforeTestMethodErrored( + $this->telemetryInfo(), + $this->testValueObject(), + $this->calledMethod(), + Code\ThrowableBuilder::from(new Exception('message')), + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +Before Test Method Errored (HookClass::hookMethod) +message +EOT, + $event->asString(), + ); + } + + private function calledMethod(): Code\ClassMethod + { + return new Code\ClassMethod('HookClass', 'hookMethod'); + } +} diff --git a/tests/unit/Event/Events/Test/HookMethod/BeforeTestMethodFailedTest.php b/tests/unit/Event/Events/Test/HookMethod/BeforeTestMethodFailedTest.php new file mode 100644 index 00000000000..cbd3e21497b --- /dev/null +++ b/tests/unit/Event/Events/Test/HookMethod/BeforeTestMethodFailedTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use Exception; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(BeforeTestMethodFailed::class)] +#[Small] +final class BeforeTestMethodFailedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $calledMethod = $this->calledMethod(); + $throwable = Code\ThrowableBuilder::from(new Exception('message')); + + $event = new BeforeTestMethodFailed( + $telemetryInfo, + $test, + $calledMethod, + $throwable, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($calledMethod, $event->calledMethod()); + $this->assertSame($throwable, $event->throwable()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new BeforeTestMethodFailed( + $this->telemetryInfo(), + $this->testValueObject(), + $this->calledMethod(), + Code\ThrowableBuilder::from(new Exception('message')), + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +Before Test Method Failed (HookClass::hookMethod) +message +EOT, + $event->asString(), + ); + } + + private function calledMethod(): Code\ClassMethod + { + return new Code\ClassMethod('HookClass', 'hookMethod'); + } +} diff --git a/tests/unit/Event/Events/Test/HookMethod/BeforeTestMethodFinishedTest.php b/tests/unit/Event/Events/Test/HookMethod/BeforeTestMethodFinishedTest.php new file mode 100644 index 00000000000..cbbac3f0365 --- /dev/null +++ b/tests/unit/Event/Events/Test/HookMethod/BeforeTestMethodFinishedTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(BeforeTestMethodFinished::class)] +#[Small] +final class BeforeTestMethodFinishedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $calledMethods = $this->calledMethods(); + + $event = new BeforeTestMethodFinished( + $telemetryInfo, + $test, + ...$calledMethods, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($calledMethods, $event->calledMethods()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new BeforeTestMethodFinished( + $this->telemetryInfo(), + $this->testValueObject(), + ...$this->calledMethods(), + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +Before Test Method Finished: +- HookClass::hookMethod +- AnotherHookClass::anotherHookMethod +EOT, + $event->asString(), + ); + } + + /** + * @return list + */ + private function calledMethods(): array + { + return [ + new Code\ClassMethod('HookClass', 'hookMethod'), + new Code\ClassMethod('AnotherHookClass', 'anotherHookMethod'), + ]; + } +} diff --git a/tests/unit/Event/Events/Test/HookMethod/PostConditionCalledTest.php b/tests/unit/Event/Events/Test/HookMethod/PostConditionCalledTest.php new file mode 100644 index 00000000000..2117922a850 --- /dev/null +++ b/tests/unit/Event/Events/Test/HookMethod/PostConditionCalledTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(PostConditionCalled::class)] +#[Small] +final class PostConditionCalledTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $calledMethod = $this->calledMethod(); + + $event = new PostConditionCalled( + $telemetryInfo, + $test, + $calledMethod, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($calledMethod, $event->calledMethod()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new PostConditionCalled( + $this->telemetryInfo(), + $this->testValueObject(), + $this->calledMethod(), + ); + + $this->assertSame('Post Condition Method Called (HookClass::hookMethod)', $event->asString()); + } + + private function calledMethod(): Code\ClassMethod + { + return new Code\ClassMethod('HookClass', 'hookMethod'); + } +} diff --git a/tests/unit/Event/Events/Test/HookMethod/PostConditionErroredTest.php b/tests/unit/Event/Events/Test/HookMethod/PostConditionErroredTest.php new file mode 100644 index 00000000000..440f3c1191f --- /dev/null +++ b/tests/unit/Event/Events/Test/HookMethod/PostConditionErroredTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use Exception; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(PostConditionErrored::class)] +#[Small] +final class PostConditionErroredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $calledMethod = $this->calledMethod(); + $throwable = Code\ThrowableBuilder::from(new Exception('message')); + + $event = new PostConditionErrored( + $telemetryInfo, + $test, + $calledMethod, + $throwable, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($calledMethod, $event->calledMethod()); + $this->assertSame($throwable, $event->throwable()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new PostConditionErrored( + $this->telemetryInfo(), + $this->testValueObject(), + $this->calledMethod(), + Code\ThrowableBuilder::from(new Exception('message')), + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +Post Condition Method Errored (HookClass::hookMethod) +message +EOT, + $event->asString(), + ); + } + + private function calledMethod(): Code\ClassMethod + { + return new Code\ClassMethod('HookClass', 'hookMethod'); + } +} diff --git a/tests/unit/Event/Events/Test/HookMethod/PostConditionFailedTest.php b/tests/unit/Event/Events/Test/HookMethod/PostConditionFailedTest.php new file mode 100644 index 00000000000..9b9c776a068 --- /dev/null +++ b/tests/unit/Event/Events/Test/HookMethod/PostConditionFailedTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use Exception; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(PostConditionFailed::class)] +#[Small] +final class PostConditionFailedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $calledMethod = $this->calledMethod(); + $throwable = Code\ThrowableBuilder::from(new Exception('message')); + + $event = new PostConditionFailed( + $telemetryInfo, + $test, + $calledMethod, + $throwable, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($calledMethod, $event->calledMethod()); + $this->assertSame($throwable, $event->throwable()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new PostConditionFailed( + $this->telemetryInfo(), + $this->testValueObject(), + $this->calledMethod(), + Code\ThrowableBuilder::from(new Exception('message')), + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +Post Condition Method Failed (HookClass::hookMethod) +message +EOT, + $event->asString(), + ); + } + + private function calledMethod(): Code\ClassMethod + { + return new Code\ClassMethod('HookClass', 'hookMethod'); + } +} diff --git a/tests/unit/Event/Events/Test/HookMethod/PostConditionFinishedTest.php b/tests/unit/Event/Events/Test/HookMethod/PostConditionFinishedTest.php new file mode 100644 index 00000000000..047f627b3dd --- /dev/null +++ b/tests/unit/Event/Events/Test/HookMethod/PostConditionFinishedTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(PostConditionFinished::class)] +#[Small] +final class PostConditionFinishedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $calledMethods = $this->calledMethods(); + + $event = new PostConditionFinished( + $telemetryInfo, + $test, + ...$calledMethods, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($calledMethods, $event->calledMethods()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new PostConditionFinished( + $this->telemetryInfo(), + $this->testValueObject(), + ...$this->calledMethods(), + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +Post Condition Method Finished: +- HookClass::hookMethod +- AnotherHookClass::anotherHookMethod +EOT, + $event->asString(), + ); + } + + /** + * @return list + */ + private function calledMethods(): array + { + return [ + new Code\ClassMethod('HookClass', 'hookMethod'), + new Code\ClassMethod('AnotherHookClass', 'anotherHookMethod'), + ]; + } +} diff --git a/tests/unit/Event/Events/Test/HookMethod/PreConditionCalledTest.php b/tests/unit/Event/Events/Test/HookMethod/PreConditionCalledTest.php new file mode 100644 index 00000000000..a738492ec1f --- /dev/null +++ b/tests/unit/Event/Events/Test/HookMethod/PreConditionCalledTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(PreConditionCalled::class)] +#[Small] +final class PreConditionCalledTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $calledMethod = $this->calledMethod(); + + $event = new PreConditionCalled( + $telemetryInfo, + $test, + $calledMethod, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($calledMethod, $event->calledMethod()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new PreConditionCalled( + $this->telemetryInfo(), + $this->testValueObject(), + $this->calledMethod(), + ); + + $this->assertSame('Pre Condition Method Called (HookClass::hookMethod)', $event->asString()); + } + + private function calledMethod(): Code\ClassMethod + { + return new Code\ClassMethod('HookClass', 'hookMethod'); + } +} diff --git a/tests/unit/Event/Events/Test/HookMethod/PreConditionErroredTest.php b/tests/unit/Event/Events/Test/HookMethod/PreConditionErroredTest.php new file mode 100644 index 00000000000..064737f863e --- /dev/null +++ b/tests/unit/Event/Events/Test/HookMethod/PreConditionErroredTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use Exception; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(PreConditionErrored::class)] +#[Small] +final class PreConditionErroredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $calledMethod = $this->calledMethod(); + $throwable = Code\ThrowableBuilder::from(new Exception('message')); + + $event = new PreConditionErrored( + $telemetryInfo, + $test, + $calledMethod, + $throwable, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($calledMethod, $event->calledMethod()); + $this->assertSame($throwable, $event->throwable()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new PreConditionErrored( + $this->telemetryInfo(), + $this->testValueObject(), + $this->calledMethod(), + Code\ThrowableBuilder::from(new Exception('message')), + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +Pre Condition Method Errored (HookClass::hookMethod) +message +EOT, + $event->asString(), + ); + } + + private function calledMethod(): Code\ClassMethod + { + return new Code\ClassMethod('HookClass', 'hookMethod'); + } +} diff --git a/tests/unit/Event/Events/Test/HookMethod/PreConditionFailedTest.php b/tests/unit/Event/Events/Test/HookMethod/PreConditionFailedTest.php new file mode 100644 index 00000000000..2d8e4c54ee3 --- /dev/null +++ b/tests/unit/Event/Events/Test/HookMethod/PreConditionFailedTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use Exception; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(PreConditionFailed::class)] +#[Small] +final class PreConditionFailedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $calledMethod = $this->calledMethod(); + $throwable = Code\ThrowableBuilder::from(new Exception('message')); + + $event = new PreConditionFailed( + $telemetryInfo, + $test, + $calledMethod, + $throwable, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($calledMethod, $event->calledMethod()); + $this->assertSame($throwable, $event->throwable()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new PreConditionFailed( + $this->telemetryInfo(), + $this->testValueObject(), + $this->calledMethod(), + Code\ThrowableBuilder::from(new Exception('message')), + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +Pre Condition Method Failed (HookClass::hookMethod) +message +EOT, + $event->asString(), + ); + } + + private function calledMethod(): Code\ClassMethod + { + return new Code\ClassMethod('HookClass', 'hookMethod'); + } +} diff --git a/tests/unit/Event/Events/Test/HookMethod/PreConditionFinishedTest.php b/tests/unit/Event/Events/Test/HookMethod/PreConditionFinishedTest.php new file mode 100644 index 00000000000..0bca77d9e30 --- /dev/null +++ b/tests/unit/Event/Events/Test/HookMethod/PreConditionFinishedTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(PreConditionFinished::class)] +#[Small] +final class PreConditionFinishedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $calledMethods = $this->calledMethods(); + + $event = new PreConditionFinished( + $telemetryInfo, + $test, + ...$calledMethods, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($calledMethods, $event->calledMethods()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new PreConditionFinished( + $this->telemetryInfo(), + $this->testValueObject(), + ...$this->calledMethods(), + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +Pre Condition Method Finished: +- HookClass::hookMethod +- AnotherHookClass::anotherHookMethod +EOT, + $event->asString(), + ); + } + + /** + * @return list + */ + private function calledMethods(): array + { + return [ + new Code\ClassMethod('HookClass', 'hookMethod'), + new Code\ClassMethod('AnotherHookClass', 'anotherHookMethod'), + ]; + } +} diff --git a/tests/unit/Event/Events/Test/Issue/ConsideredRiskyTest.php b/tests/unit/Event/Events/Test/Issue/ConsideredRiskyTest.php new file mode 100644 index 00000000000..3b7c61f9588 --- /dev/null +++ b/tests/unit/Event/Events/Test/Issue/ConsideredRiskyTest.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(ConsideredRisky::class)] +#[Small] +final class ConsideredRiskyTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + + $message = 'message'; + + $event = new ConsideredRisky( + $telemetryInfo, + $test, + $message, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new ConsideredRisky( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +Test Considered Risky (FooTest::testBar) +message +EOT, + $event->asString(), + ); + } +} diff --git a/tests/unit/Event/Events/Test/Issue/DeprecationTriggeredTest.php b/tests/unit/Event/Events/Test/Issue/DeprecationTriggeredTest.php new file mode 100644 index 00000000000..c796b622817 --- /dev/null +++ b/tests/unit/Event/Events/Test/Issue/DeprecationTriggeredTest.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code\IssueTrigger\IssueTrigger; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(DeprecationTriggered::class)] +#[Small] +final class DeprecationTriggeredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $message = 'message'; + $file = 'file'; + $line = 1; + $suppressed = false; + $ignoredByBaseline = false; + $ignoredByTest = false; + $trigger = IssueTrigger::unknown(); + $stackTrace = 'stack trace'; + + $event = new DeprecationTriggered( + $telemetryInfo, + $test, + $message, + $file, + $line, + $suppressed, + $ignoredByBaseline, + $ignoredByTest, + $trigger, + $stackTrace, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + $this->assertSame($file, $event->file()); + $this->assertSame($line, $event->line()); + $this->assertSame($suppressed, $event->wasSuppressed()); + $this->assertSame($ignoredByBaseline, $event->ignoredByBaseline()); + $this->assertSame($ignoredByTest, $event->ignoredByTest()); + $this->assertSame('Test Triggered Deprecation (FooTest::testBar, unknown if issue was triggered in first-party code or third-party code) in file:1' . PHP_EOL . 'message', $event->asString()); + $this->assertSame($trigger, $event->trigger()); + $this->assertSame($stackTrace, $event->stackTrace()); + } + + public function testCanBeIgnoredByBaseline(): void + { + $event = new DeprecationTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + false, + true, + false, + IssueTrigger::unknown(), + 'stack trace', + ); + + $this->assertTrue($event->ignoredByBaseline()); + $this->assertSame('Test Triggered Deprecation (FooTest::testBar, unknown if issue was triggered in first-party code or third-party code, ignored by baseline) in file:1' . PHP_EOL . 'message', $event->asString()); + } + + public function testCanBeIgnoredByTest(): void + { + $event = new DeprecationTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + false, + false, + true, + IssueTrigger::unknown(), + 'stack trace', + ); + + $this->assertTrue($event->ignoredByTest()); + $this->assertSame('Test Triggered Deprecation (FooTest::testBar, unknown if issue was triggered in first-party code or third-party code, ignored by test) in file:1' . PHP_EOL . 'message', $event->asString()); + } + + public function testCanBeSuppressed(): void + { + $event = new DeprecationTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + true, + false, + false, + IssueTrigger::unknown(), + 'stack trace', + ); + + $this->assertTrue($event->wasSuppressed()); + $this->assertSame('Test Triggered Deprecation (FooTest::testBar, unknown if issue was triggered in first-party code or third-party code, suppressed using operator) in file:1' . PHP_EOL . 'message', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/Test/Issue/ErrorTriggeredTest.php b/tests/unit/Event/Events/Test/Issue/ErrorTriggeredTest.php new file mode 100644 index 00000000000..63c5f3eebea --- /dev/null +++ b/tests/unit/Event/Events/Test/Issue/ErrorTriggeredTest.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(ErrorTriggered::class)] +#[Small] +final class ErrorTriggeredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $message = 'message'; + $file = 'file'; + $line = 1; + $suppressed = false; + + $event = new ErrorTriggered( + $telemetryInfo, + $test, + $message, + $file, + $line, + $suppressed, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + $this->assertSame($file, $event->file()); + $this->assertSame($line, $event->line()); + $this->assertSame($suppressed, $event->wasSuppressed()); + $this->assertSame('Test Triggered Error (FooTest::testBar) in file:1' . PHP_EOL . 'message', $event->asString()); + } + + public function testCanBeSuppressed(): void + { + $event = new ErrorTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + true, + ); + + $this->assertTrue($event->wasSuppressed()); + $this->assertSame('Test Triggered Error (FooTest::testBar, suppressed using operator) in file:1' . PHP_EOL . 'message', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/Test/Issue/NoticeTriggeredTest.php b/tests/unit/Event/Events/Test/Issue/NoticeTriggeredTest.php new file mode 100644 index 00000000000..7bab2867d13 --- /dev/null +++ b/tests/unit/Event/Events/Test/Issue/NoticeTriggeredTest.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(NoticeTriggered::class)] +#[Small] +final class NoticeTriggeredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $message = 'message'; + $file = 'file'; + $line = 1; + $suppressed = false; + $ignoredByBaseline = false; + + $event = new NoticeTriggered( + $telemetryInfo, + $test, + $message, + $file, + $line, + $suppressed, + $ignoredByBaseline, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + $this->assertSame($file, $event->file()); + $this->assertSame($line, $event->line()); + $this->assertSame($suppressed, $event->wasSuppressed()); + $this->assertSame($ignoredByBaseline, $event->ignoredByBaseline()); + $this->assertSame('Test Triggered Notice (FooTest::testBar) in file:1' . PHP_EOL . 'message', $event->asString()); + } + + public function testCanBeIgnoredByBaseline(): void + { + $event = new NoticeTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + false, + true, + ); + + $this->assertTrue($event->ignoredByBaseline()); + $this->assertSame('Test Triggered Notice (FooTest::testBar, ignored by baseline) in file:1' . PHP_EOL . 'message', $event->asString()); + } + + public function testCanBeSuppressed(): void + { + $event = new NoticeTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + true, + false, + ); + + $this->assertTrue($event->wasSuppressed()); + $this->assertSame('Test Triggered Notice (FooTest::testBar, suppressed using operator) in file:1' . PHP_EOL . 'message', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/Test/Issue/PhpDeprecationTriggeredTest.php b/tests/unit/Event/Events/Test/Issue/PhpDeprecationTriggeredTest.php new file mode 100644 index 00000000000..b7653146091 --- /dev/null +++ b/tests/unit/Event/Events/Test/Issue/PhpDeprecationTriggeredTest.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code\IssueTrigger\IssueTrigger; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(PhpDeprecationTriggered::class)] +#[Small] +final class PhpDeprecationTriggeredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $message = 'message'; + $file = 'file'; + $line = 1; + $suppressed = false; + $ignoredByBaseline = false; + $ignoredByTest = false; + $trigger = IssueTrigger::unknown(); + + $event = new PhpDeprecationTriggered( + $telemetryInfo, + $test, + $message, + $file, + $line, + $suppressed, + $ignoredByBaseline, + $ignoredByTest, + $trigger, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + $this->assertSame($file, $event->file()); + $this->assertSame($line, $event->line()); + $this->assertSame($suppressed, $event->wasSuppressed()); + $this->assertSame($ignoredByBaseline, $event->ignoredByBaseline()); + $this->assertSame($ignoredByTest, $event->ignoredByTest()); + $this->assertSame('Test Triggered PHP Deprecation (FooTest::testBar, unknown if issue was triggered in first-party code or third-party code) in file:1' . PHP_EOL . 'message', $event->asString()); + $this->assertSame($trigger, $event->trigger()); + } + + public function testCanBeIgnoredByBaseline(): void + { + $event = new PhpDeprecationTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + false, + true, + false, + IssueTrigger::unknown(), + ); + + $this->assertTrue($event->ignoredByBaseline()); + $this->assertSame('Test Triggered PHP Deprecation (FooTest::testBar, unknown if issue was triggered in first-party code or third-party code, ignored by baseline) in file:1' . PHP_EOL . 'message', $event->asString()); + } + + public function testCanBeIgnoredByTest(): void + { + $event = new PhpDeprecationTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + false, + false, + true, + IssueTrigger::unknown(), + ); + + $this->assertTrue($event->ignoredByTest()); + $this->assertSame('Test Triggered PHP Deprecation (FooTest::testBar, unknown if issue was triggered in first-party code or third-party code, ignored by test) in file:1' . PHP_EOL . 'message', $event->asString()); + } + + public function testCanBeSuppressed(): void + { + $event = new PhpDeprecationTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + true, + false, + false, + IssueTrigger::unknown(), + ); + + $this->assertTrue($event->wasSuppressed()); + $this->assertSame('Test Triggered PHP Deprecation (FooTest::testBar, unknown if issue was triggered in first-party code or third-party code, suppressed using operator) in file:1' . PHP_EOL . 'message', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/Test/Issue/PhpNoticeTriggeredTest.php b/tests/unit/Event/Events/Test/Issue/PhpNoticeTriggeredTest.php new file mode 100644 index 00000000000..8140ff3184f --- /dev/null +++ b/tests/unit/Event/Events/Test/Issue/PhpNoticeTriggeredTest.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(PhpNoticeTriggered::class)] +#[Small] +final class PhpNoticeTriggeredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $message = 'message'; + $file = 'file'; + $line = 1; + $suppressed = false; + $ignoredByBaseline = false; + + $event = new PhpNoticeTriggered( + $telemetryInfo, + $test, + $message, + $file, + $line, + $suppressed, + $ignoredByBaseline, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + $this->assertSame($file, $event->file()); + $this->assertSame($line, $event->line()); + $this->assertSame($suppressed, $event->wasSuppressed()); + $this->assertSame($ignoredByBaseline, $event->ignoredByBaseline()); + $this->assertSame('Test Triggered PHP Notice (FooTest::testBar) in file:1' . PHP_EOL . 'message', $event->asString()); + } + + public function testCanBeIgnoredByBaseline(): void + { + $event = new PhpNoticeTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + false, + true, + ); + + $this->assertTrue($event->ignoredByBaseline()); + $this->assertSame('Test Triggered PHP Notice (FooTest::testBar, ignored by baseline) in file:1' . PHP_EOL . 'message', $event->asString()); + } + + public function testCanBeSuppressed(): void + { + $event = new PhpNoticeTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + true, + false, + ); + + $this->assertTrue($event->wasSuppressed()); + $this->assertSame('Test Triggered PHP Notice (FooTest::testBar, suppressed using operator) in file:1' . PHP_EOL . 'message', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/Test/Issue/PhpWarningTriggeredTest.php b/tests/unit/Event/Events/Test/Issue/PhpWarningTriggeredTest.php new file mode 100644 index 00000000000..a6fed8356f4 --- /dev/null +++ b/tests/unit/Event/Events/Test/Issue/PhpWarningTriggeredTest.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(PhpWarningTriggered::class)] +#[Small] +final class PhpWarningTriggeredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $message = 'message'; + $file = 'file'; + $line = 1; + $suppressed = false; + $ignoredByBaseline = false; + + $event = new PhpWarningTriggered( + $telemetryInfo, + $test, + $message, + $file, + $line, + $suppressed, + $ignoredByBaseline, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + $this->assertSame($file, $event->file()); + $this->assertSame($line, $event->line()); + $this->assertSame($suppressed, $event->wasSuppressed()); + $this->assertSame($ignoredByBaseline, $event->ignoredByBaseline()); + $this->assertSame('Test Triggered PHP Warning (FooTest::testBar) in file:1' . PHP_EOL . 'message', $event->asString()); + } + + public function testCanBeIgnoredByBaseline(): void + { + $event = new PhpWarningTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + false, + true, + ); + + $this->assertTrue($event->ignoredByBaseline()); + $this->assertSame('Test Triggered PHP Warning (FooTest::testBar, ignored by baseline) in file:1' . PHP_EOL . 'message', $event->asString()); + } + + public function testCanBeSuppressed(): void + { + $event = new PhpWarningTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + true, + false, + ); + + $this->assertTrue($event->wasSuppressed()); + $this->assertSame('Test Triggered PHP Warning (FooTest::testBar, suppressed using operator) in file:1' . PHP_EOL . 'message', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/Test/Issue/PhpunitDeprecationTriggeredTest.php b/tests/unit/Event/Events/Test/Issue/PhpunitDeprecationTriggeredTest.php new file mode 100644 index 00000000000..e3930bd612d --- /dev/null +++ b/tests/unit/Event/Events/Test/Issue/PhpunitDeprecationTriggeredTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(PhpunitDeprecationTriggered::class)] +#[Small] +final class PhpunitDeprecationTriggeredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $message = 'message'; + + $event = new PhpunitDeprecationTriggered( + $telemetryInfo, + $test, + $message, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + $this->assertSame('Test Triggered PHPUnit Deprecation (FooTest::testBar)' . PHP_EOL . 'message', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/Test/Issue/PhpunitErrorTriggeredTest.php b/tests/unit/Event/Events/Test/Issue/PhpunitErrorTriggeredTest.php new file mode 100644 index 00000000000..36faa267647 --- /dev/null +++ b/tests/unit/Event/Events/Test/Issue/PhpunitErrorTriggeredTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(PhpunitErrorTriggered::class)] +#[Small] +final class PhpunitErrorTriggeredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $message = 'message'; + + $event = new PhpunitErrorTriggered( + $telemetryInfo, + $test, + $message, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + $this->assertSame('Test Triggered PHPUnit Error (FooTest::testBar)' . PHP_EOL . 'message', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/Test/Issue/PhpunitNoticeTriggeredTest.php b/tests/unit/Event/Events/Test/Issue/PhpunitNoticeTriggeredTest.php new file mode 100644 index 00000000000..bb734e46158 --- /dev/null +++ b/tests/unit/Event/Events/Test/Issue/PhpunitNoticeTriggeredTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(PhpunitNoticeTriggered::class)] +#[Small] +final class PhpunitNoticeTriggeredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $message = 'message'; + + $event = new PhpunitNoticeTriggered( + $telemetryInfo, + $test, + $message, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + $this->assertSame('Test Triggered PHPUnit Notice (FooTest::testBar)' . PHP_EOL . 'message', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/Test/Issue/PhpunitWarningTriggeredTest.php b/tests/unit/Event/Events/Test/Issue/PhpunitWarningTriggeredTest.php new file mode 100644 index 00000000000..f6ba1ee24d1 --- /dev/null +++ b/tests/unit/Event/Events/Test/Issue/PhpunitWarningTriggeredTest.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(PhpunitWarningTriggered::class)] +#[Small] +final class PhpunitWarningTriggeredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $message = 'message'; + $ignoredByTest = false; + + $event = new PhpunitWarningTriggered( + $telemetryInfo, + $test, + $message, + $ignoredByTest, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + $this->assertSame($ignoredByTest, $event->ignoredByTest()); + $this->assertSame('Test Triggered PHPUnit Warning (FooTest::testBar)' . PHP_EOL . 'message', $event->asString()); + } + + public function testCanBeIgnoredByTest(): void + { + $event = new PhpunitWarningTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + true, + ); + + $this->assertTrue($event->ignoredByTest()); + $this->assertSame('Test Triggered PHPUnit Warning (FooTest::testBar, ignored by test)' . PHP_EOL . 'message', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/Test/Issue/WarningTriggeredTest.php b/tests/unit/Event/Events/Test/Issue/WarningTriggeredTest.php new file mode 100644 index 00000000000..394af14b2cc --- /dev/null +++ b/tests/unit/Event/Events/Test/Issue/WarningTriggeredTest.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(WarningTriggered::class)] +#[Small] +final class WarningTriggeredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $message = 'message'; + $file = 'file'; + $line = 1; + $suppressed = false; + $ignoredByBaseline = false; + + $event = new WarningTriggered( + $telemetryInfo, + $test, + $message, + $file, + $line, + $suppressed, + $ignoredByBaseline, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + $this->assertSame($file, $event->file()); + $this->assertSame($line, $event->line()); + $this->assertSame($suppressed, $event->wasSuppressed()); + $this->assertSame($ignoredByBaseline, $event->ignoredByBaseline()); + $this->assertSame('Test Triggered Warning (FooTest::testBar) in file:1' . PHP_EOL . 'message', $event->asString()); + } + + public function testCanBeIgnoredByBaseline(): void + { + $event = new WarningTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + false, + true, + ); + + $this->assertTrue($event->ignoredByBaseline()); + $this->assertSame('Test Triggered Warning (FooTest::testBar, ignored by baseline) in file:1' . PHP_EOL . 'message', $event->asString()); + } + + public function testCanBeSuppressed(): void + { + $event = new WarningTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + true, + false, + ); + + $this->assertTrue($event->wasSuppressed()); + $this->assertSame('Test Triggered Warning (FooTest::testBar, suppressed using operator) in file:1' . PHP_EOL . 'message', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/Test/Lifecycle/DataProviderMethodCalledTest.php b/tests/unit/Event/Events/Test/Lifecycle/DataProviderMethodCalledTest.php new file mode 100644 index 00000000000..d8f8acc97a5 --- /dev/null +++ b/tests/unit/Event/Events/Test/Lifecycle/DataProviderMethodCalledTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code\ClassMethod; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(DataProviderMethodCalled::class)] +#[Small] +final class DataProviderMethodCalledTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $testMethod = new ClassMethod('ClassTest', 'testOne'); + $dataProviderMethod = new ClassMethod('ClassTest', 'dataProvider'); + + $event = new DataProviderMethodCalled( + $telemetryInfo, + $testMethod, + $dataProviderMethod, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($testMethod, $event->testMethod()); + $this->assertSame($dataProviderMethod, $event->dataProviderMethod()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new DataProviderMethodCalled( + $this->telemetryInfo(), + new ClassMethod('ClassTest', 'testOne'), + new ClassMethod('ClassTest', 'dataProvider'), + ); + + $this->assertSame('Data Provider Method Called (ClassTest::dataProvider for test method ClassTest::testOne)', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/Test/Lifecycle/DataProviderMethodFinishedTest.php b/tests/unit/Event/Events/Test/Lifecycle/DataProviderMethodFinishedTest.php new file mode 100644 index 00000000000..068207671d9 --- /dev/null +++ b/tests/unit/Event/Events/Test/Lifecycle/DataProviderMethodFinishedTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code\ClassMethod; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(DataProviderMethodFinished::class)] +#[Small] +final class DataProviderMethodFinishedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $testMethod = new ClassMethod('ClassTest', 'testOne'); + $dataProviderMethod = new ClassMethod('ClassTest', 'dataProvider'); + + $event = new DataProviderMethodFinished( + $telemetryInfo, + $testMethod, + $dataProviderMethod, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($testMethod, $event->testMethod()); + $this->assertSame([$dataProviderMethod], $event->calledMethods()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new DataProviderMethodFinished( + $this->telemetryInfo(), + new ClassMethod('ClassTest', 'testOne'), + new ClassMethod('ClassTest', 'dataProvider'), + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +Data Provider Method Finished for ClassTest::testOne: +- ClassTest::dataProvider +EOT + , + $event->asString(), + ); + } +} diff --git a/tests/unit/Event/Events/Test/Lifecycle/FinishedTest.php b/tests/unit/Event/Events/Test/Lifecycle/FinishedTest.php new file mode 100644 index 00000000000..510d1e933bc --- /dev/null +++ b/tests/unit/Event/Events/Test/Lifecycle/FinishedTest.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(Finished::class)] +#[Small] +final class FinishedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + + $event = new Finished( + $telemetryInfo, + $test, + 1, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame(1, $event->numberOfAssertionsPerformed()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new Finished( + $this->telemetryInfo(), + $this->testValueObject(), + 1, + ); + + $this->assertSame('Test Finished (FooTest::testBar)', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/Test/Lifecycle/PreparationErroredTest.php b/tests/unit/Event/Events/Test/Lifecycle/PreparationErroredTest.php new file mode 100644 index 00000000000..9ae9d1154c4 --- /dev/null +++ b/tests/unit/Event/Events/Test/Lifecycle/PreparationErroredTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use Exception; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code\ThrowableBuilder; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(PreparationErrored::class)] +#[Small] +final class PreparationErroredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $throwable = ThrowableBuilder::from(new Exception('message')); + + $event = new PreparationErrored( + $telemetryInfo, + $test, + $throwable, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($throwable, $event->throwable()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new PreparationErrored( + $this->telemetryInfo(), + $this->testValueObject(), + ThrowableBuilder::from(new Exception('message')), + ); + + $this->assertSame('Test Preparation Errored (FooTest::testBar)' . PHP_EOL . 'message', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/Test/Lifecycle/PreparationFailedTest.php b/tests/unit/Event/Events/Test/Lifecycle/PreparationFailedTest.php new file mode 100644 index 00000000000..c85ef9f3799 --- /dev/null +++ b/tests/unit/Event/Events/Test/Lifecycle/PreparationFailedTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use Exception; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code\ThrowableBuilder; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(PreparationFailed::class)] +#[Small] +final class PreparationFailedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $throwable = ThrowableBuilder::from(new Exception('message')); + + $event = new PreparationFailed( + $telemetryInfo, + $test, + $throwable, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($throwable, $event->throwable()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new PreparationFailed( + $this->telemetryInfo(), + $this->testValueObject(), + ThrowableBuilder::from(new Exception('message')), + ); + + $this->assertSame('Test Preparation Failed (FooTest::testBar)' . PHP_EOL . 'message', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/Test/Lifecycle/PreparationStartedTest.php b/tests/unit/Event/Events/Test/Lifecycle/PreparationStartedTest.php new file mode 100644 index 00000000000..3c7a4223091 --- /dev/null +++ b/tests/unit/Event/Events/Test/Lifecycle/PreparationStartedTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(PreparationStarted::class)] +#[Small] +final class PreparationStartedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + + $event = new PreparationStarted( + $telemetryInfo, + $test, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new PreparationStarted( + $this->telemetryInfo(), + $this->testValueObject(), + ); + + $this->assertSame('Test Preparation Started (FooTest::testBar)', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/Test/Lifecycle/PreparedTest.php b/tests/unit/Event/Events/Test/Lifecycle/PreparedTest.php new file mode 100644 index 00000000000..05dc12c8c05 --- /dev/null +++ b/tests/unit/Event/Events/Test/Lifecycle/PreparedTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(Prepared::class)] +#[Small] +final class PreparedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + + $event = new Prepared( + $telemetryInfo, + $test, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new Prepared( + $this->telemetryInfo(), + $this->testValueObject(), + ); + + $this->assertSame('Test Prepared (FooTest::testBar)', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/Test/Outcome/ErroredTest.php b/tests/unit/Event/Events/Test/Outcome/ErroredTest.php new file mode 100644 index 00000000000..a352030810f --- /dev/null +++ b/tests/unit/Event/Events/Test/Outcome/ErroredTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use Exception; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(Errored::class)] +#[Small] +final class ErroredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $throwable = $this->throwable(); + + $event = new Errored( + $telemetryInfo, + $test, + $throwable, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($throwable, $event->throwable()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new Errored( + $this->telemetryInfo(), + $this->testValueObject(), + $this->throwable(), + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +Test Errored (FooTest::testBar) +error +EOT, + $event->asString(), + ); + } + + private function throwable(): Code\Throwable + { + return Code\ThrowableBuilder::from(new Exception('error')); + } +} diff --git a/tests/unit/Event/Events/Test/Outcome/FailedTest.php b/tests/unit/Event/Events/Test/Outcome/FailedTest.php new file mode 100644 index 00000000000..d9a030c67ef --- /dev/null +++ b/tests/unit/Event/Events/Test/Outcome/FailedTest.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use Exception; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\ComparisonFailure; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(Failed::class)] +#[Small] +final class FailedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $throwable = $this->throwable(); + $comparisonFailure = $this->comparisonFailure(); + + $event = new Failed( + $telemetryInfo, + $test, + $throwable, + $comparisonFailure, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($throwable, $event->throwable()); + $this->assertSame($comparisonFailure, $event->comparisonFailure()); + $this->assertTrue($event->hasComparisonFailure()); + } + + public function testThrowsExceptionOnAccessToUnspecifiedComparisonFailure(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $throwable = $this->throwable(); + + $event = new Failed( + $telemetryInfo, + $test, + $throwable, + null, + ); + + $this->assertFalse($event->hasComparisonFailure()); + + $this->expectException(NoComparisonFailureException::class); + + $event->comparisonFailure(); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new Failed( + $this->telemetryInfo(), + $this->testValueObject(), + $this->throwable(), + null, + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +Test Failed (FooTest::testBar) +failure +EOT, + $event->asString(), + ); + } + + private function throwable(): Code\Throwable + { + return Code\ThrowableBuilder::from(new Exception('failure')); + } + + private function comparisonFailure(): ComparisonFailure + { + return new ComparisonFailure('expected', 'actual', 'diff'); + } +} diff --git a/tests/unit/Event/Events/Test/Outcome/MarkedIncompleteTest.php b/tests/unit/Event/Events/Test/Outcome/MarkedIncompleteTest.php new file mode 100644 index 00000000000..dac2fd79fb7 --- /dev/null +++ b/tests/unit/Event/Events/Test/Outcome/MarkedIncompleteTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use Exception; +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Event\Code; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(MarkedIncomplete::class)] +#[Small] +final class MarkedIncompleteTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $throwable = $this->throwable(); + + $event = new MarkedIncomplete( + $telemetryInfo, + $test, + $throwable, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($throwable, $event->throwable()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new MarkedIncomplete( + $this->telemetryInfo(), + $this->testValueObject(), + $this->throwable(), + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +Test Marked Incomplete (FooTest::testBar) +incomplete +EOT, + $event->asString(), + ); + } + + private function throwable(): Code\Throwable + { + return Code\ThrowableBuilder::from(new Exception('incomplete')); + } +} diff --git a/tests/unit/Event/Events/Test/Outcome/PassedTest.php b/tests/unit/Event/Events/Test/Outcome/PassedTest.php new file mode 100644 index 00000000000..c8276522e79 --- /dev/null +++ b/tests/unit/Event/Events/Test/Outcome/PassedTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(Passed::class)] +#[Small] +final class PassedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + + $event = new Passed( + $telemetryInfo, + $test, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new Passed( + $this->telemetryInfo(), + $this->testValueObject(), + ); + + $this->assertSame('Test Passed (FooTest::testBar)', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/Test/Outcome/SkippedTest.php b/tests/unit/Event/Events/Test/Outcome/SkippedTest.php new file mode 100644 index 00000000000..48f09e03ae8 --- /dev/null +++ b/tests/unit/Event/Events/Test/Outcome/SkippedTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(Skipped::class)] +#[Small] +final class SkippedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $message = 'skipped'; + + $event = new Skipped( + $telemetryInfo, + $test, + $message, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($test, $event->test()); + $this->assertSame($message, $event->message()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new Skipped( + $this->telemetryInfo(), + $this->testValueObject(), + 'skipped', + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +Test Skipped (FooTest::testBar) +skipped +EOT, + $event->asString(), + ); + } +} diff --git a/tests/unit/Event/Events/Test/PrintedUnexpectedOutputTest.php b/tests/unit/Event/Events/Test/PrintedUnexpectedOutputTest.php new file mode 100644 index 00000000000..4959cb418ef --- /dev/null +++ b/tests/unit/Event/Events/Test/PrintedUnexpectedOutputTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(PrintedUnexpectedOutput::class)] +#[Small] +final class PrintedUnexpectedOutputTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $output = 'output'; + + $event = new PrintedUnexpectedOutput( + $telemetryInfo, + $output, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($output, $event->output()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new PrintedUnexpectedOutput( + $this->telemetryInfo(), + 'output', + ); + + $this->assertStringEqualsStringIgnoringLineEndings( + <<<'EOT' +Test Printed Unexpected Output +output +EOT + , + $event->asString(), + ); + } +} diff --git a/tests/unit/Event/Events/TestDouble/MockObjectCreatedTest.php b/tests/unit/Event/Events/TestDouble/MockObjectCreatedTest.php new file mode 100644 index 00000000000..f91b9916c79 --- /dev/null +++ b/tests/unit/Event/Events/TestDouble/MockObjectCreatedTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(MockObjectCreated::class)] +#[Small] +final class MockObjectCreatedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $className = 'OriginalType'; + + $event = new MockObjectCreated( + $telemetryInfo, + $className, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($className, $event->className()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new MockObjectCreated( + $this->telemetryInfo(), + 'OriginalType', + ); + + $this->assertSame('Mock Object Created (OriginalType)', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestDouble/MockObjectForIntersectionOfInterfacesCreatedTest.php b/tests/unit/Event/Events/TestDouble/MockObjectForIntersectionOfInterfacesCreatedTest.php new file mode 100644 index 00000000000..63e662542bc --- /dev/null +++ b/tests/unit/Event/Events/TestDouble/MockObjectForIntersectionOfInterfacesCreatedTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(MockObjectForIntersectionOfInterfacesCreated::class)] +#[Small] +final class MockObjectForIntersectionOfInterfacesCreatedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $interfaces = ['AnInterface', 'AnotherInterface']; + + $event = new MockObjectForIntersectionOfInterfacesCreated( + $telemetryInfo, + $interfaces, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($interfaces, $event->interfaces()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new MockObjectForIntersectionOfInterfacesCreated( + $this->telemetryInfo(), + ['AnInterface', 'AnotherInterface'], + ); + + $this->assertSame('Mock Object Created (AnInterface&AnotherInterface)', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestDouble/PartialMockObjectCreatedTest.php b/tests/unit/Event/Events/TestDouble/PartialMockObjectCreatedTest.php new file mode 100644 index 00000000000..6d612c4574a --- /dev/null +++ b/tests/unit/Event/Events/TestDouble/PartialMockObjectCreatedTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(PartialMockObjectCreated::class)] +#[Small] +final class PartialMockObjectCreatedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $className = 'OriginalType'; + $methodNames = [ + 'foo', + 'bar', + 'baz', + ]; + + $event = new PartialMockObjectCreated( + $telemetryInfo, + $className, + ...$methodNames, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($className, $event->className()); + $this->assertSame($methodNames, $event->methodNames()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new PartialMockObjectCreated( + $this->telemetryInfo(), + 'OriginalType', + ); + + $this->assertSame('Partial Mock Object Created (OriginalType)', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestDouble/TestStubCreatedTest.php b/tests/unit/Event/Events/TestDouble/TestStubCreatedTest.php new file mode 100644 index 00000000000..0b4fb3bfe96 --- /dev/null +++ b/tests/unit/Event/Events/TestDouble/TestStubCreatedTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(TestStubCreated::class)] +#[Small] +final class TestStubCreatedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $className = 'OriginalType'; + + $event = new TestStubCreated( + $telemetryInfo, + $className, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($className, $event->className()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new TestStubCreated( + $this->telemetryInfo(), + 'OriginalType', + ); + + $this->assertSame('Test Stub Created (OriginalType)', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestDouble/TestStubForIntersectionOfInterfacesCreatedTest.php b/tests/unit/Event/Events/TestDouble/TestStubForIntersectionOfInterfacesCreatedTest.php new file mode 100644 index 00000000000..c8495b85a9b --- /dev/null +++ b/tests/unit/Event/Events/TestDouble/TestStubForIntersectionOfInterfacesCreatedTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(TestStubForIntersectionOfInterfacesCreated::class)] +#[Small] +final class TestStubForIntersectionOfInterfacesCreatedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $interfaces = ['AnInterface', 'AnotherInterface']; + + $event = new TestStubForIntersectionOfInterfacesCreated( + $telemetryInfo, + $interfaces, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($interfaces, $event->interfaces()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new TestStubForIntersectionOfInterfacesCreated( + $this->telemetryInfo(), + ['AnInterface', 'AnotherInterface'], + ); + + $this->assertSame('Test Stub Created (AnInterface&AnotherInterface)', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestRunner/BootstrapFinishedTest.php b/tests/unit/Event/Events/TestRunner/BootstrapFinishedTest.php new file mode 100644 index 00000000000..cb7cb6fafaf --- /dev/null +++ b/tests/unit/Event/Events/TestRunner/BootstrapFinishedTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(BootstrapFinished::class)] +#[Small] +final class BootstrapFinishedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $filename = 'bootstrap.php'; + + $event = new BootstrapFinished( + $telemetryInfo, + $filename, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($filename, $event->filename()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new BootstrapFinished( + $this->telemetryInfo(), + 'bootstrap.php', + ); + + $this->assertSame('Bootstrap Finished (bootstrap.php)', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestRunner/ChildProcessErroredTest.php b/tests/unit/Event/Events/TestRunner/ChildProcessErroredTest.php new file mode 100644 index 00000000000..2625f9ca512 --- /dev/null +++ b/tests/unit/Event/Events/TestRunner/ChildProcessErroredTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(ChildProcessErrored::class)] +#[Small] +final class ChildProcessErroredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + + $event = new ChildProcessErrored( + $telemetryInfo, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new ChildProcessErrored( + $this->telemetryInfo(), + ); + + $this->assertSame('Child Process Errored', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestRunner/ChildProcessFinishedTest.php b/tests/unit/Event/Events/TestRunner/ChildProcessFinishedTest.php new file mode 100644 index 00000000000..79bfaab0ca8 --- /dev/null +++ b/tests/unit/Event/Events/TestRunner/ChildProcessFinishedTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(ChildProcessFinished::class)] +#[Small] +final class ChildProcessFinishedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $stdout = 'output'; + $stderr = 'error'; + + $event = new ChildProcessFinished( + $telemetryInfo, + $stdout, + $stderr, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($stdout, $event->stdout()); + $this->assertSame($stderr, $event->stderr()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new ChildProcessFinished( + $this->telemetryInfo(), + 'output', + 'error', + ); + + $this->assertSame('Child Process Finished', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestRunner/ChildProcessStartedTest.php b/tests/unit/Event/Events/TestRunner/ChildProcessStartedTest.php new file mode 100644 index 00000000000..80a4864bd90 --- /dev/null +++ b/tests/unit/Event/Events/TestRunner/ChildProcessStartedTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(ChildProcessStarted::class)] +#[Small] +final class ChildProcessStartedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + + $event = new ChildProcessStarted( + $telemetryInfo, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new ChildProcessStarted( + $this->telemetryInfo(), + ); + + $this->assertSame('Child Process Started', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestRunner/ConfiguredTest.php b/tests/unit/Event/Events/TestRunner/ConfiguredTest.php new file mode 100644 index 00000000000..7f169d05f05 --- /dev/null +++ b/tests/unit/Event/Events/TestRunner/ConfiguredTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\TextUI\CliArguments\Builder; +use PHPUnit\TextUI\Configuration\Configuration; +use PHPUnit\TextUI\Configuration\Merger; +use PHPUnit\TextUI\XmlConfiguration\DefaultConfiguration; + +#[CoversClass(Configured::class)] +#[Small] +final class ConfiguredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $configuration = $this->configuration(); + + $event = new Configured( + $telemetryInfo, + $configuration, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($configuration, $event->configuration()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new Configured( + $this->telemetryInfo(), + $this->configuration(), + ); + + $this->assertSame('Test Runner Configured', $event->asString()); + } + + private function configuration(): Configuration + { + return (new Merger)->merge( + (new Builder)->fromParameters([]), + DefaultConfiguration::create(), + ); + } +} diff --git a/tests/unit/Event/Events/TestRunner/DeprecationTriggeredTest.php b/tests/unit/Event/Events/TestRunner/DeprecationTriggeredTest.php new file mode 100644 index 00000000000..ac1bc41718a --- /dev/null +++ b/tests/unit/Event/Events/TestRunner/DeprecationTriggeredTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(DeprecationTriggered::class)] +#[Small] +final class DeprecationTriggeredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $message = 'message'; + + $event = new DeprecationTriggered( + $telemetryInfo, + $message, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($message, $event->message()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new DeprecationTriggered( + $this->telemetryInfo(), + 'message', + ); + + $this->assertSame('Test Runner Triggered Deprecation (message)', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestRunner/EventFacadeSealedTest.php b/tests/unit/Event/Events/TestRunner/EventFacadeSealedTest.php new file mode 100644 index 00000000000..a681ca58d64 --- /dev/null +++ b/tests/unit/Event/Events/TestRunner/EventFacadeSealedTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(EventFacadeSealed::class)] +#[Small] +final class EventFacadeSealedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + + $event = new EventFacadeSealed($telemetryInfo); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new EventFacadeSealed($this->telemetryInfo()); + + $this->assertSame('Event Facade Sealed', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestRunner/ExecutionAbortedTest.php b/tests/unit/Event/Events/TestRunner/ExecutionAbortedTest.php new file mode 100644 index 00000000000..869ae4ce3ed --- /dev/null +++ b/tests/unit/Event/Events/TestRunner/ExecutionAbortedTest.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(ExecutionAborted::class)] +#[Small] +final class ExecutionAbortedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + + $event = new ExecutionAborted($telemetryInfo); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + } + + public function testCanBeRepresentedAsString(): void + { + $telemetryInfo = $this->telemetryInfo(); + + $event = new ExecutionAborted($telemetryInfo); + + $this->assertSame('Test Runner Execution Aborted', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestRunner/ExecutionFinishedTest.php b/tests/unit/Event/Events/TestRunner/ExecutionFinishedTest.php new file mode 100644 index 00000000000..b090a17e786 --- /dev/null +++ b/tests/unit/Event/Events/TestRunner/ExecutionFinishedTest.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(ExecutionFinished::class)] +#[Small] +final class ExecutionFinishedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + + $event = new ExecutionFinished($telemetryInfo); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + } + + public function testCanBeRepresentedAsString(): void + { + $telemetryInfo = $this->telemetryInfo(); + + $event = new ExecutionFinished($telemetryInfo); + + $this->assertSame('Test Runner Execution Finished', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestRunner/ExecutionStartedTest.php b/tests/unit/Event/Events/TestRunner/ExecutionStartedTest.php new file mode 100644 index 00000000000..28373aa9e49 --- /dev/null +++ b/tests/unit/Event/Events/TestRunner/ExecutionStartedTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(ExecutionStarted::class)] +#[Small] +final class ExecutionStartedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $testSuite = $this->testSuiteValueObject(); + + $event = new ExecutionStarted($telemetryInfo, $testSuite); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($testSuite, $event->testSuite()); + } + + public function testCanBeRepresentedAsString(): void + { + $telemetryInfo = $this->telemetryInfo(); + $testSuite = $this->testSuiteValueObject(); + + $event = new ExecutionStarted($telemetryInfo, $testSuite); + + $this->assertSame('Test Runner Execution Started (9001 tests)', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestRunner/ExtensionBootstrappedTest.php b/tests/unit/Event/Events/TestRunner/ExtensionBootstrappedTest.php new file mode 100644 index 00000000000..644f2f47d64 --- /dev/null +++ b/tests/unit/Event/Events/TestRunner/ExtensionBootstrappedTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(ExtensionBootstrapped::class)] +#[Small] +final class ExtensionBootstrappedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $className = 'the-className'; + $parameters = ['foo' => 'bar']; + + $event = new ExtensionBootstrapped( + $telemetryInfo, + $className, + $parameters, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($className, $event->className()); + $this->assertSame($parameters, $event->parameters()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new ExtensionBootstrapped( + $this->telemetryInfo(), + 'the-className', + [], + ); + + $this->assertSame('Extension Bootstrapped (the-className)', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestRunner/ExtensionLoadedFromPharTest.php b/tests/unit/Event/Events/TestRunner/ExtensionLoadedFromPharTest.php new file mode 100644 index 00000000000..d14c4bef4bc --- /dev/null +++ b/tests/unit/Event/Events/TestRunner/ExtensionLoadedFromPharTest.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(ExtensionLoadedFromPhar::class)] +#[Small] +final class ExtensionLoadedFromPharTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $filename = 'extension.phar'; + $name = 'example-extension'; + $version = '1.2.3'; + + $event = new ExtensionLoadedFromPhar( + $telemetryInfo, + $filename, + $name, + $version, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($filename, $event->filename()); + $this->assertSame($name, $event->name()); + $this->assertSame($version, $event->version()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new ExtensionLoadedFromPhar( + $this->telemetryInfo(), + 'extension.phar', + 'example-extension', + '1.2.3', + ); + + $this->assertSame('Extension Loaded from PHAR (example-extension 1.2.3)', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestRunner/FinishedTest.php b/tests/unit/Event/Events/TestRunner/FinishedTest.php new file mode 100644 index 00000000000..a2317a30cda --- /dev/null +++ b/tests/unit/Event/Events/TestRunner/FinishedTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(Finished::class)] +#[Small] +final class FinishedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + + $event = new Finished($telemetryInfo); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new Finished($this->telemetryInfo()); + + $this->assertSame('Test Runner Finished', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestRunner/GarbageCollectionDisabledTest.php b/tests/unit/Event/Events/TestRunner/GarbageCollectionDisabledTest.php new file mode 100644 index 00000000000..e1feadc044f --- /dev/null +++ b/tests/unit/Event/Events/TestRunner/GarbageCollectionDisabledTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(GarbageCollectionDisabled::class)] +#[Small] +final class GarbageCollectionDisabledTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + + $event = new GarbageCollectionDisabled($telemetryInfo); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new GarbageCollectionDisabled($this->telemetryInfo()); + + $this->assertSame('Test Runner Disabled Garbage Collection', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestRunner/GarbageCollectionEnabledTest.php b/tests/unit/Event/Events/TestRunner/GarbageCollectionEnabledTest.php new file mode 100644 index 00000000000..f435b7abb17 --- /dev/null +++ b/tests/unit/Event/Events/TestRunner/GarbageCollectionEnabledTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(GarbageCollectionEnabled::class)] +#[Small] +final class GarbageCollectionEnabledTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + + $event = new GarbageCollectionEnabled($telemetryInfo); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new GarbageCollectionEnabled($this->telemetryInfo()); + + $this->assertSame('Test Runner Enabled Garbage Collection', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestRunner/GarbageCollectionTriggeredTest.php b/tests/unit/Event/Events/TestRunner/GarbageCollectionTriggeredTest.php new file mode 100644 index 00000000000..9fe6b5277e2 --- /dev/null +++ b/tests/unit/Event/Events/TestRunner/GarbageCollectionTriggeredTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(GarbageCollectionTriggered::class)] +#[Small] +final class GarbageCollectionTriggeredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + + $event = new GarbageCollectionTriggered($telemetryInfo); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new GarbageCollectionTriggered($this->telemetryInfo()); + + $this->assertSame('Test Runner Triggered Garbage Collection', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestRunner/NoticeTriggeredTest.php b/tests/unit/Event/Events/TestRunner/NoticeTriggeredTest.php new file mode 100644 index 00000000000..ebd065cbf1e --- /dev/null +++ b/tests/unit/Event/Events/TestRunner/NoticeTriggeredTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(NoticeTriggered::class)] +#[Small] +final class NoticeTriggeredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $message = 'message'; + + $event = new NoticeTriggered( + $telemetryInfo, + $message, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($message, $event->message()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new NoticeTriggered( + $this->telemetryInfo(), + 'message', + ); + + $this->assertSame('Test Runner Triggered Notice (message)', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestRunner/StartedTest.php b/tests/unit/Event/Events/TestRunner/StartedTest.php new file mode 100644 index 00000000000..d4072f6e09e --- /dev/null +++ b/tests/unit/Event/Events/TestRunner/StartedTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(Started::class)] +#[Small] +final class StartedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + + $event = new Started($telemetryInfo); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new Started($this->telemetryInfo()); + + $this->assertSame('Test Runner Started', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestRunner/StaticAnalysisForCodeCoverageFinishedTest.php b/tests/unit/Event/Events/TestRunner/StaticAnalysisForCodeCoverageFinishedTest.php new file mode 100644 index 00000000000..34d66209dbb --- /dev/null +++ b/tests/unit/Event/Events/TestRunner/StaticAnalysisForCodeCoverageFinishedTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(StaticAnalysisForCodeCoverageFinished::class)] +#[Small] +final class StaticAnalysisForCodeCoverageFinishedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $cacheHits = 1; + $cacheMisses = 2; + + $event = new StaticAnalysisForCodeCoverageFinished( + $telemetryInfo, + $cacheHits, + $cacheMisses, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($cacheHits, $event->cacheHits()); + $this->assertSame($cacheMisses, $event->cacheMisses()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new StaticAnalysisForCodeCoverageFinished( + $this->telemetryInfo(), + 1, + 2, + ); + + $this->assertSame('Static Analysis for Code Coverage Finished (1 cache hits, 2 cache misses)', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestRunner/StaticAnalysisForCodeCoverageStartedTest.php b/tests/unit/Event/Events/TestRunner/StaticAnalysisForCodeCoverageStartedTest.php new file mode 100644 index 00000000000..214675385f5 --- /dev/null +++ b/tests/unit/Event/Events/TestRunner/StaticAnalysisForCodeCoverageStartedTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(StaticAnalysisForCodeCoverageStarted::class)] +#[Small] +final class StaticAnalysisForCodeCoverageStartedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + + $event = new StaticAnalysisForCodeCoverageStarted( + $telemetryInfo, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new StaticAnalysisForCodeCoverageStarted( + $this->telemetryInfo(), + ); + + $this->assertSame('Static Analysis for Code Coverage Started', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestRunner/WarningTriggeredTest.php b/tests/unit/Event/Events/TestRunner/WarningTriggeredTest.php new file mode 100644 index 00000000000..b84fae978fa --- /dev/null +++ b/tests/unit/Event/Events/TestRunner/WarningTriggeredTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(WarningTriggered::class)] +#[Small] +final class WarningTriggeredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $message = 'message'; + + $event = new WarningTriggered( + $telemetryInfo, + $message, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($message, $event->message()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new WarningTriggered( + $this->telemetryInfo(), + 'message', + ); + + $this->assertSame('Test Runner Triggered Warning (message)', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestSuite/FilteredTest.php b/tests/unit/Event/Events/TestSuite/FilteredTest.php new file mode 100644 index 00000000000..02a9ab7c021 --- /dev/null +++ b/tests/unit/Event/Events/TestSuite/FilteredTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(Filtered::class)] +#[Small] +final class FilteredTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $testSuite = $this->testSuiteValueObject(); + + $event = new Filtered( + $telemetryInfo, + $testSuite, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($testSuite, $event->testSuite()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new Filtered( + $this->telemetryInfo(), + $this->testSuiteValueObject(), + ); + + $this->assertSame('Test Suite Filtered (9001 tests)', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestSuite/FinishedTest.php b/tests/unit/Event/Events/TestSuite/FinishedTest.php new file mode 100644 index 00000000000..e0d9623b129 --- /dev/null +++ b/tests/unit/Event/Events/TestSuite/FinishedTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(Finished::class)] +#[Small] +final class FinishedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $testSuite = $this->testSuiteValueObject(); + + $event = new Finished( + $telemetryInfo, + $testSuite, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($testSuite, $event->testSuite()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new Finished( + $this->telemetryInfo(), + $this->testSuiteValueObject(), + ); + + $this->assertSame('Test Suite Finished (foo, 9001 tests)', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestSuite/LoadedTest.php b/tests/unit/Event/Events/TestSuite/LoadedTest.php new file mode 100644 index 00000000000..dc899d40860 --- /dev/null +++ b/tests/unit/Event/Events/TestSuite/LoadedTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(Loaded::class)] +#[Small] +final class LoadedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $testSuite = $this->testSuiteValueObject(); + + $event = new Loaded($telemetryInfo, $testSuite); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($testSuite, $event->testSuite()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new Loaded( + $this->telemetryInfo(), + $this->testSuiteValueObject(), + ); + + $this->assertSame('Test Suite Loaded (9001 tests)', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestSuite/SkippedTest.php b/tests/unit/Event/Events/TestSuite/SkippedTest.php new file mode 100644 index 00000000000..84e198779c0 --- /dev/null +++ b/tests/unit/Event/Events/TestSuite/SkippedTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(Skipped::class)] +#[Small] +final class SkippedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $testSuite = $this->testSuiteValueObject(); + $message = 'the-message'; + + $event = new Skipped($telemetryInfo, $testSuite, $message); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($testSuite, $event->testSuite()); + $this->assertSame($message, $event->message()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new Skipped( + $this->telemetryInfo(), + $this->testSuiteValueObject(), + 'the-message', + ); + + $this->assertSame('Test Suite Skipped (foo, the-message)', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestSuite/SortedTest.php b/tests/unit/Event/Events/TestSuite/SortedTest.php new file mode 100644 index 00000000000..a8fe7aad296 --- /dev/null +++ b/tests/unit/Event/Events/TestSuite/SortedTest.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(Sorted::class)] +#[Small] +final class SortedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $executionOrder = 9001; + $executionOrderDefects = 5; + $resolveDependencies = true; + + $event = new Sorted( + $telemetryInfo, + $executionOrder, + $executionOrderDefects, + $resolveDependencies, + ); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($executionOrder, $event->executionOrder()); + $this->assertSame($executionOrderDefects, $event->executionOrderDefects()); + $this->assertSame($resolveDependencies, $event->resolveDependencies()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new Sorted( + $this->telemetryInfo(), + 9001, + 5, + true, + ); + + $this->assertSame('Test Suite Sorted', $event->asString()); + } +} diff --git a/tests/unit/Event/Events/TestSuite/StartedTest.php b/tests/unit/Event/Events/TestSuite/StartedTest.php new file mode 100644 index 00000000000..11a7e2046a6 --- /dev/null +++ b/tests/unit/Event/Events/TestSuite/StartedTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use PHPUnit\Event\AbstractEventTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(Started::class)] +#[Small] +final class StartedTest extends AbstractEventTestCase +{ + public function testConstructorSetsValues(): void + { + $telemetryInfo = $this->telemetryInfo(); + $testSuite = $this->testSuiteValueObject(); + + $event = new Started($telemetryInfo, $testSuite); + + $this->assertSame($telemetryInfo, $event->telemetryInfo()); + $this->assertSame($testSuite, $event->testSuite()); + } + + public function testCanBeRepresentedAsString(): void + { + $event = new Started( + $this->telemetryInfo(), + $this->testSuiteValueObject(), + ); + + $this->assertSame('Test Suite Started (foo, 9001 tests)', $event->asString()); + } +} diff --git a/tests/unit/Event/FacadeTest.php b/tests/unit/Event/FacadeTest.php new file mode 100644 index 00000000000..42d4df6dcb0 --- /dev/null +++ b/tests/unit/Event/FacadeTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use PHPUnit\Event\Tracer\Tracer; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Facade::class)] +#[Small] +final class FacadeTest extends TestCase +{ + public function testSubscriberRegistrationDoesNotWorkWhenEventFacadeIsSealed(): void + { + $this->expectException(EventFacadeIsSealedException::class); + + Facade::instance()->registerSubscriber( + new class implements Subscriber + {}, + ); + } + + public function testTracerRegistrationDoesNotWorkWhenEventFacadeIsSealed(): void + { + $this->expectException(EventFacadeIsSealedException::class); + + Facade::instance()->registerTracer( + new class implements Tracer + { + public function trace(Event $event): void + { + } + }, + ); + } +} diff --git a/tests/unit/Event/TypeMapTest.php b/tests/unit/Event/TypeMapTest.php new file mode 100644 index 00000000000..e641c7835f3 --- /dev/null +++ b/tests/unit/Event/TypeMapTest.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\AnotherDummySubscriber; +use PHPUnit\TestFixture\DummyEvent; +use PHPUnit\TestFixture\DummySubscriber; +use PHPUnit\TestFixture\MockObject\AnInterface; +use PHPUnit\TestFixture\MockObject\FinalClass; + +#[CoversClass(TypeMap::class)] +#[Small] +final class TypeMapTest extends TestCase +{ + public function testMapsKnownSubscriberInterfaceToEventClass(): void + { + $subscriber = $this->createStub(DummySubscriber::class); + $subscriberType = DummySubscriber::class; + $event = new DummyEvent; + $eventType = DummyEvent::class; + + $map = new TypeMap; + + $this->assertFalse($map->isKnownSubscriberType($subscriber)); + $this->assertFalse($map->isKnownEventType($event)); + + $map->addMapping($subscriberType, $eventType); + + $this->assertTrue($map->isKnownSubscriberType($subscriber)); + $this->assertTrue($map->isKnownEventType($event)); + + $this->assertSame($eventType, $map->map($subscriber)); + } + + public function testCannotMapUnknownSubscriberInterface(): void + { + $subscriber = $this->createStub(DummySubscriber::class); + + $map = new TypeMap; + + $this->expectException(MapError::class); + + $map->map($subscriber); + } + + public function testCannotAddMappingFromUnknownSubscriberInterface(): void + { + $subscriberType = 'DoesNotExist'; + $eventType = DummyEvent::class; + + $map = new TypeMap; + + $this->expectException(UnknownSubscriberException::class); + + $map->addMapping($subscriberType, $eventType); + } + + public function testCannotAddMappingFromInvalidSubscriberInterface(): void + { + $subscriberType = AnInterface::class; + $eventType = DummyEvent::class; + + $map = new TypeMap; + + $this->expectException(InvalidSubscriberException::class); + + $map->addMapping($subscriberType, $eventType); + } + + public function testCannotAddMappingToUnknownEventClass(): void + { + $subscriberType = DummySubscriber::class; + $eventType = 'DoesNotExist'; + + $map = new TypeMap; + + $this->expectException(UnknownEventException::class); + + $map->addMapping($subscriberType, $eventType); + } + + public function testCannotAddMappingToInvalidEventClass(): void + { + $subscriberType = DummySubscriber::class; + $eventType = FinalClass::class; + + $map = new TypeMap; + + $this->expectException(InvalidEventException::class); + + $map->addMapping($subscriberType, $eventType); + } + + public function testSubscriberInterfaceCanOnlyBeRegisteredOnce(): void + { + $subscriberType = DummySubscriber::class; + $eventType = DummyEvent::class; + + $map = new TypeMap; + + $map->addMapping($subscriberType, $eventType); + + $this->expectException(SubscriberTypeAlreadyRegisteredException::class); + + $map->addMapping($subscriberType, $eventType); + } + + public function testEventClassCanOnlyBeRegisteredOnce(): void + { + $subscriberType = DummySubscriber::class; + $anotherSubscriberType = AnotherDummySubscriber::class; + $eventType = DummyEvent::class; + + $map = new TypeMap; + + $map->addMapping($subscriberType, $eventType); + + $this->expectException(EventAlreadyAssignedException::class); + + $map->addMapping($anotherSubscriberType, $eventType); + } +} diff --git a/tests/unit/Event/Value/ClassMethodTest.php b/tests/unit/Event/Value/ClassMethodTest.php new file mode 100644 index 00000000000..97594c3d2b5 --- /dev/null +++ b/tests/unit/Event/Value/ClassMethodTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(ClassMethod::class)] +#[Small] +final class ClassMethodTest extends TestCase +{ + public function testConstructorSetsValues(): void + { + $className = self::class; + $methodName = 'foo'; + + $classMethod = new ClassMethod( + $className, + $methodName, + ); + + $this->assertSame($className, $classMethod->className()); + $this->assertSame($methodName, $classMethod->methodName()); + } +} diff --git a/tests/unit/Event/Value/ComparisonFailureBuilderTest.php b/tests/unit/Event/Value/ComparisonFailureBuilderTest.php new file mode 100644 index 00000000000..ff7f2f15b4c --- /dev/null +++ b/tests/unit/Event/Value/ComparisonFailureBuilderTest.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +use Exception; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; +use SebastianBergmann\Comparator\ComparisonFailure; +use stdClass; + +#[CoversClass(ComparisonFailureBuilder::class)] +#[Small] +final class ComparisonFailureBuilderTest extends TestCase +{ + public static function provider(): array + { + return [ + 'type of expected value: string, string representation of expected value: not available, type of actual value: string, string representation of actual value: not available' => [ + 'expected', + 'actual', + '', + new ExpectationFailedException( + 'message', + new ComparisonFailure( + 'expected', + 'actual', + '', + '', + 'message', + ), + ), + ], + + 'type of expected value: true, string representation of expected value: not available, type of actual value: false, string representation of actual value: not available' => [ + 'true', + 'false', + '', + new ExpectationFailedException( + 'message', + new ComparisonFailure( + true, + false, + '', + '', + 'message', + ), + ), + ], + + 'type of expected value: null, string representation of expected value: not available, type of actual value: null, string representation of actual value: not available' => [ + 'null', + 'null', + '', + new ExpectationFailedException( + 'message', + new ComparisonFailure( + null, + null, + '', + '', + 'message', + ), + ), + ], + + 'type of expected value: array, string representation of expected value: not available, type of actual value: object, string representation of actual value: not available' => [ + '', + '', + '', + new ExpectationFailedException( + 'message', + new ComparisonFailure( + [], + new stdClass, + '', + '', + 'message', + ), + ), + ], + + 'type of expected value: string, string representation of expected value: available, type of actual value: string, string representation of actual value: available' => [ + 'expected-string', + 'actual-string', + <<<'EOT' + +--- Expected ++++ Actual +@@ @@ +-expected-string ++actual-string + +EOT, + new ExpectationFailedException( + 'message', + new ComparisonFailure( + 'expected', + 'actual', + 'expected-string', + 'actual-string', + 'message', + ), + ), + ], + ]; + } + + #[TestDox('Maps exception that is not of type ExpectationFailedException to null')] + public function testMapsGenericThrowableToNull(): void + { + $this->assertNull( + ComparisonFailureBuilder::from(new Exception), + ); + } + + #[TestDox('Maps ExpectationFailedException that does not aggregate a ComparisonFailure object to null')] + public function testMapsExpectationFailedExceptionWithoutComparisonFailureToNull(): void + { + $this->assertNull( + ComparisonFailureBuilder::from( + new ExpectationFailedException('message'), + ), + ); + } + + #[DataProvider('provider')] + #[TestDox('Maps ExpectationFailedException that aggregates a ComparisonFailure object to value object')] + public function testMapsExpectationFailedExceptionWithComparisonFailureToValueObject(string $expected, string $actual, string $diff, ExpectationFailedException $exception): void + { + $comparisonFailure = ComparisonFailureBuilder::from($exception); + + $this->assertSame($expected, $comparisonFailure->expected()); + $this->assertSame($actual, $comparisonFailure->actual()); + $this->assertSame($diff, $comparisonFailure->diff()); + } +} diff --git a/tests/unit/Event/Value/ComparisonFailureTest.php b/tests/unit/Event/Value/ComparisonFailureTest.php new file mode 100644 index 00000000000..d7d8ad35ade --- /dev/null +++ b/tests/unit/Event/Value/ComparisonFailureTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(ComparisonFailure::class)] +#[Small] +final class ComparisonFailureTest extends TestCase +{ + public function testConstructorSetsValues(): void + { + $expected = 'expected'; + $actual = 'actual'; + $diff = 'diff'; + + $comparisonFailure = new ComparisonFailure($expected, $actual, $diff); + + $this->assertSame($expected, $comparisonFailure->expected()); + $this->assertSame($actual, $comparisonFailure->actual()); + $this->assertSame($diff, $comparisonFailure->diff()); + } +} diff --git a/tests/unit/Event/Value/Runtime/OperatingSystemTest.php b/tests/unit/Event/Value/Runtime/OperatingSystemTest.php new file mode 100644 index 00000000000..5dc91a33961 --- /dev/null +++ b/tests/unit/Event/Value/Runtime/OperatingSystemTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Runtime; + +use const PHP_OS; +use const PHP_OS_FAMILY; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(OperatingSystem::class)] +#[Small] +final class OperatingSystemTest extends TestCase +{ + public function testCanBeRepresentedAsString(): void + { + $this->assertSame(PHP_OS, (new OperatingSystem)->operatingSystem()); + } + + public function testHasOperatingSystemFamily(): void + { + $this->assertSame(PHP_OS_FAMILY, (new OperatingSystem)->operatingSystemFamily()); + } +} diff --git a/tests/unit/Event/Value/Runtime/PHPTest.php b/tests/unit/Event/Value/Runtime/PHPTest.php new file mode 100644 index 00000000000..061d3d8c2b1 --- /dev/null +++ b/tests/unit/Event/Value/Runtime/PHPTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Runtime; + +use const PHP_EXTRA_VERSION; +use const PHP_MAJOR_VERSION; +use const PHP_MINOR_VERSION; +use const PHP_RELEASE_VERSION; +use const PHP_SAPI; +use const PHP_VERSION; +use const PHP_VERSION_ID; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(PHP::class)] +#[Small] +final class PHPTest extends TestCase +{ + public function testHasVersion(): void + { + $this->assertSame(PHP_VERSION, (new PHP)->version()); + } + + public function testHasVersionId(): void + { + $this->assertSame(PHP_VERSION_ID, (new PHP)->versionId()); + } + + public function testHasMajorVersion(): void + { + $this->assertSame(PHP_MAJOR_VERSION, (new PHP)->majorVersion()); + } + + public function testHasMinorVersion(): void + { + $this->assertSame(PHP_MINOR_VERSION, (new PHP)->minorVersion()); + } + + public function testHasReleaseVersion(): void + { + $this->assertSame(PHP_RELEASE_VERSION, (new PHP)->releaseVersion()); + } + + public function testHasExtraVersion(): void + { + $this->assertSame(PHP_EXTRA_VERSION, (new PHP)->extraVersion()); + } + + public function testHasSapi(): void + { + $this->assertSame(PHP_SAPI, (new PHP)->sapi()); + } + + public function testHasExtensions(): void + { + $this->assertNotEmpty((new PHP)->extensions()); + $this->assertIsList((new PHP)->extensions()); + $this->assertContainsOnlyString((new PHP)->extensions()); + } +} diff --git a/tests/unit/Event/Value/Runtime/PHPUnitTest.php b/tests/unit/Event/Value/Runtime/PHPUnitTest.php new file mode 100644 index 00000000000..b9f479bf2d5 --- /dev/null +++ b/tests/unit/Event/Value/Runtime/PHPUnitTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Runtime; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use PHPUnit\Runner\Version; + +#[CoversClass(PHPUnit::class)] +#[Small] +final class PHPUnitTest extends TestCase +{ + public function testHasVersionId(): void + { + $this->assertSame(Version::id(), (new PHPUnit)->versionId()); + } + + public function testHasReleaseSeries(): void + { + $this->assertSame(Version::series(), (new PHPUnit)->releaseSeries()); + } +} diff --git a/tests/unit/Event/Value/Runtime/RuntimeTest.php b/tests/unit/Event/Value/Runtime/RuntimeTest.php new file mode 100644 index 00000000000..bb28d5b98f5 --- /dev/null +++ b/tests/unit/Event/Value/Runtime/RuntimeTest.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Runtime; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Runtime::class)] +#[UsesClass(OperatingSystem::class)] +#[UsesClass(PHP::class)] +#[UsesClass(PHPUnit::class)] +#[Small] +final class RuntimeTest extends TestCase +{ + public function testHasOperatingSystem(): void + { + $operatingSystem = new OperatingSystem; + + $this->assertSame($operatingSystem->operatingSystem(), (new Runtime)->operatingSystem()->operatingSystem()); + } + + public function test_has_PHP(): void + { + $php = new PHP; + + $this->assertSame($php->version(), (new Runtime)->php()->version()); + } + + public function test_has_PHPUnit(): void + { + $phpunit = new PHPUnit; + + $this->assertSame($phpunit->versionId(), (new Runtime)->phpunit()->versionId()); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertStringMatchesFormat( + 'PHPUnit %s using PHP %s (%s) on %s', + (new Runtime)->asString(), + ); + } +} diff --git a/tests/unit/Event/Value/Telemetry/DurationTest.php b/tests/unit/Event/Value/Telemetry/DurationTest.php new file mode 100644 index 00000000000..5d4a5206d36 --- /dev/null +++ b/tests/unit/Event/Value/Telemetry/DurationTest.php @@ -0,0 +1,201 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Telemetry; + +use InvalidArgumentException; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Duration::class)] +#[Small] +final class DurationTest extends TestCase +{ + /** + * @return array + */ + public static function provideDurationAndStringRepresentation(): array + { + return [ + 'less than a minute' => [ + '00:00:59.000000123', + 59, + 123, + ], + + 'less than an hour' => [ + '00:59:19.000000123', + 3559, + 123, + ], + + 'more than an hour' => [ + '01:00:01.000000123', + 3601, + 123, + ], + ]; + } + + public function testCanBeCreatedFromSecondsAndNanoseconds(): void + { + $seconds = 123; + $nanoseconds = 999999999; + + $duration = Duration::fromSecondsAndNanoseconds($seconds, $nanoseconds); + + $this->assertSame($seconds, $duration->seconds()); + $this->assertSame($nanoseconds, $duration->nanoseconds()); + } + + public function testSecondsMustNotBeNegative(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Value for seconds must not be negative'); + + Duration::fromSecondsAndNanoseconds(-1, 0); + } + + public function testNanosecondsMustNotBeNegative(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Value for nanoseconds must not be negative'); + + Duration::fromSecondsAndNanoseconds(0, -1); + } + + public function testNanosecondsMustNotBeGreaterThan999999999(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Value for nanoseconds must not be greater than 999999999'); + + Duration::fromSecondsAndNanoseconds(0, 1000000000); + } + + #[DataProvider('provideDurationAndStringRepresentation')] + #[TestDox('$seconds seconds and $nanoseconds nanoseconds is represented as "$expected"')] + public function testCanBeRepresentedAsString(string $expected, int $seconds, int $nanoseconds): void + { + $this->assertSame( + $expected, + (Duration::fromSecondsAndNanoseconds($seconds, $nanoseconds))->asString(), + ); + } + + public function testCanBeRepresentedAsFloat(): void + { + $this->assertSame(0.0, Duration::fromSecondsAndNanoseconds(0, 0)->asFloat()); + } + + public function testEqualsReturnsFalseWhenValuesAreDifferent(): void + { + $one = Duration::fromSecondsAndNanoseconds( + 123, + 456, + ); + + $two = Duration::fromSecondsAndNanoseconds( + 456, + 123, + ); + + $this->assertFalse($one->equals($two)); + } + + public function testEqualsReturnsTrueWhenValuesAreSame(): void + { + $one = Duration::fromSecondsAndNanoseconds(123, 456); + $two = Duration::fromSecondsAndNanoseconds(123, 456); + + $this->assertTrue($one->equals($two)); + } + + public function testIsLessThanReturnsFalseWhenSecondsAreGreater(): void + { + $one = Duration::fromSecondsAndNanoseconds(123, 456); + $two = Duration::fromSecondsAndNanoseconds(122, 456); + + $this->assertFalse($one->isLessThan($two)); + } + + public function testIsLessThanReturnsFalseWhenSecondsAreEqualAndNanosecondsAreGreater(): void + { + $one = Duration::fromSecondsAndNanoseconds(123, 456); + $two = Duration::fromSecondsAndNanoseconds(123, 455); + + $this->assertFalse($one->isLessThan($two)); + } + + public function testIsLessThanReturnsFalseWhenValuesAreSame(): void + { + $one = Duration::fromSecondsAndNanoseconds(123, 456); + $two = Duration::fromSecondsAndNanoseconds(123, 456); + + $this->assertFalse($one->isLessThan($two)); + } + + public function testIsLessThanReturnsTrueWhenSecondsAreLess(): void + { + $one = Duration::fromSecondsAndNanoseconds(123, 456); + $two = Duration::fromSecondsAndNanoseconds(124, 456); + + $this->assertTrue($one->isLessThan($two)); + } + + public function testIsLessThanReturnsTrueWhenSecondsAreEqualAndNanosecondsAreLess(): void + { + $one = Duration::fromSecondsAndNanoseconds(123, 456); + $two = Duration::fromSecondsAndNanoseconds(123, 457); + + $this->assertTrue($one->isLessThan($two)); + } + + public function testIsGreaterThanReturnsFalseWhenSecondsAreLess(): void + { + $one = Duration::fromSecondsAndNanoseconds(123, 456); + $two = Duration::fromSecondsAndNanoseconds(124, 456); + + $this->assertFalse($one->isGreaterThan($two)); + } + + public function testIsGreaterThanReturnsFalseWhenSecondsAreEqualAndNanosecondsAreLess(): void + { + $one = Duration::fromSecondsAndNanoseconds(123, 456); + $two = Duration::fromSecondsAndNanoseconds(123, 457); + + $this->assertFalse($one->isGreaterThan($two)); + } + + public function testIsGreaterThanReturnsFalseWhenValuesAreSame(): void + { + $one = Duration::fromSecondsAndNanoseconds(123, 456); + $two = Duration::fromSecondsAndNanoseconds(123, 456); + + $this->assertFalse($one->isGreaterThan($two)); + } + + public function testIsGreaterThanReturnsTrueWhenSecondsAreGreater(): void + { + $one = Duration::fromSecondsAndNanoseconds(123, 456); + $two = Duration::fromSecondsAndNanoseconds(122, 456); + + $this->assertTrue($one->isGreaterThan($two)); + } + + public function testIsGreaterThanReturnsTrueWhenSecondsAreEqualAndNanosecondsAreGreater(): void + { + $one = Duration::fromSecondsAndNanoseconds(123, 456); + $two = Duration::fromSecondsAndNanoseconds(123, 455); + + $this->assertTrue($one->isGreaterThan($two)); + } +} diff --git a/tests/unit/Event/Value/Telemetry/GarbageCollectorStatusTest.php b/tests/unit/Event/Value/Telemetry/GarbageCollectorStatusTest.php new file mode 100644 index 00000000000..608bdaaec55 --- /dev/null +++ b/tests/unit/Event/Value/Telemetry/GarbageCollectorStatusTest.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Telemetry; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(GarbageCollectorStatus::class)] +#[Small] +final class GarbageCollectorStatusTest extends TestCase +{ + public function testHasRuns(): void + { + $this->assertSame(1, $this->garbageCollectorStatus()->runs()); + } + + public function testHasCollected(): void + { + $this->assertSame(2, $this->garbageCollectorStatus()->collected()); + } + + public function testHasThreshold(): void + { + $this->assertSame(3, $this->garbageCollectorStatus()->threshold()); + } + + public function testHasRoots(): void + { + $this->assertSame(4, $this->garbageCollectorStatus()->roots()); + } + + public function testMayHaveRunning(): void + { + $this->assertTrue($this->garbageCollectorStatus()->isRunning()); + } + + public function testMayHaveApplicationTime(): void + { + $this->assertSame(5.0, $this->garbageCollectorStatus()->applicationTime()); + } + + public function testMayHaveCollectorTime(): void + { + $this->assertSame(6.0, $this->garbageCollectorStatus()->collectorTime()); + } + + public function testMayHaveDestructorTime(): void + { + $this->assertSame(7.0, $this->garbageCollectorStatus()->destructorTime()); + } + + public function testMayHaveFreeTime(): void + { + $this->assertSame(8.0, $this->garbageCollectorStatus()->freeTime()); + } + + public function testMayHaveProtected(): void + { + $this->assertTrue($this->garbageCollectorStatus()->isProtected()); + } + + public function testMayHaveFull(): void + { + $this->assertTrue($this->garbageCollectorStatus()->isFull()); + } + + public function testMayHaveBufferSize(): void + { + $this->assertSame(9, $this->garbageCollectorStatus()->bufferSize()); + } + + private function garbageCollectorStatus(): GarbageCollectorStatus + { + return new GarbageCollectorStatus( + 1, + 2, + 3, + 4, + 5.0, + 6.0, + 7.0, + 8.0, + true, + true, + true, + 9, + ); + } +} diff --git a/tests/unit/Event/Value/Telemetry/HRTimeTest.php b/tests/unit/Event/Value/Telemetry/HRTimeTest.php new file mode 100644 index 00000000000..5ac5da9d65e --- /dev/null +++ b/tests/unit/Event/Value/Telemetry/HRTimeTest.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Telemetry; + +use InvalidArgumentException; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(HRTime::class)] +#[Small] +final class HRTimeTest extends TestCase +{ + /** + * @return array + */ + public static function provideStartGreaterThanEnd(): array + { + return [ + 'seconds-greater' => [ + 11, + 1, + 10, + 1, + ], + 'seconds-and-nanoseconds-greater' => [ + 11, + 1, + 10, + 0, + ], + 'nanoseconds-greater' => [ + 10, + 1, + 10, + 0, + ], + ]; + } + + /** + * @return array + */ + public static function provideStartEndAndDuration(): array + { + return [ + 'start-equal-to-end' => [ + 10, + 50, + 10, + 50, + Duration::fromSecondsAndNanoseconds(0, 0), + ], + 'start-smaller-than-end' => [ + 10, + 50, + 12, + 70, + Duration::fromSecondsAndNanoseconds(2, 20), + ], + 'start-nanoseconds-greater-than-end-nanoseconds' => [ + 10, + 50, + 12, + 30, + Duration::fromSecondsAndNanoseconds(1, 999999980), + ], + ]; + } + + public function testFromSecondsAndNanosecondsRejectsNegativeSeconds(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Value for seconds must not be negative'); + + HRTime::fromSecondsAndNanoseconds( + -1, + 0, + ); + } + + public function testFromSecondsAndNanosecondsRejectsNegativeNanoseconds(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Value for nanoseconds must not be negative'); + + HRTime::fromSecondsAndNanoseconds( + 0, + -1, + ); + } + + public function testFromSecondsAndNanosecondsRejectsNanosecondsGreaterThan999999999(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Value for nanoseconds must not be greater than 999999999'); + + HRTime::fromSecondsAndNanoseconds( + 0, + 1000000000, + ); + } + + public function testFromSecondsAndNanosecondsReturnsHRTime(): void + { + $seconds = 123; + $nanoseconds = 456; + + $time = HRTime::fromSecondsAndNanoseconds( + $seconds, + $nanoseconds, + ); + + $this->assertSame($seconds, $time->seconds()); + $this->assertSame($nanoseconds, $time->nanoseconds()); + } + + #[DataProvider('provideStartGreaterThanEnd')] + public function testDurationIgnoresStartGreaterThanEnd(int $startSeconds, int $startNanoseconds, int $endSeconds, int $endNanoseconds): void + { + $start = HRTime::fromSecondsAndNanoseconds( + $startSeconds, + $startNanoseconds, + ); + + $end = HRTime::fromSecondsAndNanoseconds( + $endSeconds, + $endNanoseconds, + ); + + $duration = $end->duration($start); + + $this->assertSame(0, $duration->seconds()); + $this->assertSame(0, $duration->nanoseconds()); + } + + #[DataProvider('provideStartEndAndDuration')] + public function testDurationReturnsDifferenceBetweenEndAndStart(int $startSeconds, int $startNanoseconds, int $endSeconds, int $endNanoseconds, Duration $duration): void + { + $start = HRTime::fromSecondsAndNanoseconds( + $startSeconds, + $startNanoseconds, + ); + + $end = HRTime::fromSecondsAndNanoseconds( + $endSeconds, + $endNanoseconds, + ); + + $this->assertEquals($duration, $end->duration($start)); + } +} diff --git a/tests/unit/Event/Value/Telemetry/InfoTest.php b/tests/unit/Event/Value/Telemetry/InfoTest.php new file mode 100644 index 00000000000..1bec91ff83c --- /dev/null +++ b/tests/unit/Event/Value/Telemetry/InfoTest.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Telemetry; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Info::class)] +#[Small] +final class InfoTest extends TestCase +{ + public function testHasTime(): void + { + $this->assertInstanceOf(HRTime::class, $this->info()->time()); + } + + public function testHasMemoryUsage(): void + { + $this->assertInstanceOf(MemoryUsage::class, $this->info()->memoryUsage()); + } + + public function testHasPeakMemoryUsage(): void + { + $this->assertInstanceOf(MemoryUsage::class, $this->info()->peakMemoryUsage()); + } + + public function testHasDurationSinceStart(): void + { + $this->assertSame(0, $this->info()->durationSinceStart()->nanoseconds()); + } + + public function testHasDurationSincePrevious(): void + { + $this->assertSame(0, $this->info()->durationSincePrevious()->nanoseconds()); + } + + public function testHasMemoryUsageSinceStart(): void + { + $this->assertSame(0, $this->info()->memoryUsageSinceStart()->bytes()); + } + + public function testHasMemoryUsageSincePrevious(): void + { + $this->assertSame(0, $this->info()->memoryUsageSincePrevious()->bytes()); + } + + public function testHasGarbageCollectorStatus(): void + { + $this->assertInstanceOf(GarbageCollectorStatus::class, $this->info()->garbageCollectorStatus()); + } + + public function testCanBeFormattedAsString(): void + { + $this->assertStringMatchesFormat( + '[00:00:00.000000000 / 00:00:00.000000000] [%d bytes]', + $this->info()->asString(), + ); + } + + private function info(): Info + { + $current = $this->telemetrySystem()->snapshot(); + + return new Info( + $current, + $current->time()->duration($current->time()), + $current->memoryUsage()->diff($current->memoryUsage()), + $current->time()->duration($current->time()), + $current->memoryUsage()->diff($current->memoryUsage()), + ); + } + + private function telemetrySystem(): System + { + return new System( + new SystemStopWatch, + new SystemMemoryMeter, + new SystemGarbageCollectorStatusProvider, + ); + } +} diff --git a/tests/unit/Event/Value/Telemetry/MemoryUsageTest.php b/tests/unit/Event/Value/Telemetry/MemoryUsageTest.php new file mode 100644 index 00000000000..89b32e1ae99 --- /dev/null +++ b/tests/unit/Event/Value/Telemetry/MemoryUsageTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Telemetry; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(MemoryUsage::class)] +#[Small] +final class MemoryUsageTest extends TestCase +{ + public static function provideValidBytes(): array + { + return [ + 'int-less-than-zero' => [-1], + 'int-zero' => [0], + 'int-greater-than-zero' => [1], + ]; + } + + #[DataProvider('provideValidBytes')] + public function testFromBytesReturnsMemoryUsage(int $bytes): void + { + $memoryUsage = MemoryUsage::fromBytes($bytes); + + $this->assertSame($bytes, $memoryUsage->bytes()); + } + + public function testDiffReturnsMemoryUsage(): void + { + $one = MemoryUsage::fromBytes(2000); + $two = MemoryUsage::fromBytes(3000); + + $diff = $one->diff($two); + + $this->assertNotSame($one, $diff); + $this->assertNotSame($two, $diff); + $this->assertSame($one->bytes() - $two->bytes(), $diff->bytes()); + } +} diff --git a/tests/unit/Event/Value/Telemetry/SnapshotTest.php b/tests/unit/Event/Value/Telemetry/SnapshotTest.php new file mode 100644 index 00000000000..cb88993b982 --- /dev/null +++ b/tests/unit/Event/Value/Telemetry/SnapshotTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Telemetry; + +use function hrtime; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Snapshot::class)] +#[Small] +final class SnapshotTest extends TestCase +{ + public function testConstructorSetsValues(): void + { + $time = HRTime::fromSecondsAndNanoseconds(...hrtime(false)); + $memoryUsage = MemoryUsage::fromBytes(2000); + $peakMemoryUsage = MemoryUsage::fromBytes(3000); + $garbageCollectorStatus = new GarbageCollectorStatus(0, 0, 0, 0, 0.0, 0.0, 0.0, 0.0, false, false, false, 0); + + $snapshot = new Snapshot( + $time, + $memoryUsage, + $peakMemoryUsage, + $garbageCollectorStatus, + ); + + $this->assertSame($time, $snapshot->time()); + $this->assertSame($memoryUsage, $snapshot->memoryUsage()); + $this->assertSame($peakMemoryUsage, $snapshot->peakMemoryUsage()); + $this->assertSame($garbageCollectorStatus, $snapshot->garbageCollectorStatus()); + } +} diff --git a/tests/unit/Event/Value/Telemetry/SystemStopWatchTest.php b/tests/unit/Event/Value/Telemetry/SystemStopWatchTest.php new file mode 100644 index 00000000000..07efe574f89 --- /dev/null +++ b/tests/unit/Event/Value/Telemetry/SystemStopWatchTest.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Telemetry; + +use function hrtime; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(SystemStopWatch::class)] +#[Small] +final class SystemStopWatchTest extends TestCase +{ + public function testNowReturnsDateTimeImmutable(): void + { + $clock = new SystemStopWatch; + + $before = HRTime::fromSecondsAndNanoseconds(...hrtime(false)); + + $current = $clock->current(); + + $after = HRTime::fromSecondsAndNanoseconds(...hrtime(false)); + + $durationBetweenCurrentAndBefore = $current->duration($before); + + $this->assertSame(0, $durationBetweenCurrentAndBefore->seconds()); + $this->assertGreaterThan(0, $durationBetweenCurrentAndBefore->nanoseconds()); + + $durationBetweenAfterAndCurrent = $after->duration($current); + + $this->assertSame(0, $durationBetweenAfterAndCurrent->seconds()); + $this->assertGreaterThan(0, $durationBetweenAfterAndCurrent->nanoseconds()); + } +} diff --git a/tests/unit/Event/Value/Telemetry/SystemTest.php b/tests/unit/Event/Value/Telemetry/SystemTest.php new file mode 100644 index 00000000000..a3fef4ed9d5 --- /dev/null +++ b/tests/unit/Event/Value/Telemetry/SystemTest.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Telemetry; + +use function hrtime; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(System::class)] +#[Small] +final class SystemTest extends TestCase +{ + public function testSnapshotReturnsSnapshot(): void + { + $time = HRTime::fromSecondsAndNanoseconds(...hrtime(false)); + + $clock = new class($time) implements StopWatch + { + private readonly HRTime $time; + + public function __construct(HRTime $time) + { + $this->time = $time; + } + + public function current(): HRTime + { + return $this->time; + } + }; + + $memoryUsage = MemoryUsage::fromBytes(2000); + $peakMemoryUsage = MemoryUsage::fromBytes(3000); + + $memoryMeter = new class($memoryUsage, $peakMemoryUsage) implements MemoryMeter + { + private readonly MemoryUsage $memoryUsage; + private readonly MemoryUsage $peakMemoryUsage; + + public function __construct(MemoryUsage $memoryUsage, MemoryUsage $peakMemoryUsage) + { + $this->memoryUsage = $memoryUsage; + $this->peakMemoryUsage = $peakMemoryUsage; + } + + public function memoryUsage(): MemoryUsage + { + return $this->memoryUsage; + } + + public function peakMemoryUsage(): MemoryUsage + { + return $this->peakMemoryUsage; + } + }; + + $garbageCollectorStatus = new GarbageCollectorStatus(0, 0, 0, 0, 0.0, 0.0, 0.0, 0.0, false, false, false, 0); + + $garbageCollectorProvider = new class($garbageCollectorStatus) implements GarbageCollectorStatusProvider + { + private readonly GarbageCollectorStatus $status; + + public function __construct(GarbageCollectorStatus $status) + { + $this->status = $status; + } + + public function status(): GarbageCollectorStatus + { + return $this->status; + } + }; + + $snapshot = new System($clock, $memoryMeter, $garbageCollectorProvider)->snapshot(); + + $this->assertSame($time, $snapshot->time()); + $this->assertSame($memoryUsage, $snapshot->memoryUsage()); + $this->assertSame($peakMemoryUsage, $snapshot->peakMemoryUsage()); + $this->assertSame($garbageCollectorStatus, $snapshot->garbageCollectorStatus()); + } +} diff --git a/tests/unit/Event/Value/Test/IssueTriggerTest.php b/tests/unit/Event/Value/Test/IssueTriggerTest.php new file mode 100644 index 00000000000..cbfe4b4ac6d --- /dev/null +++ b/tests/unit/Event/Value/Test/IssueTriggerTest.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code\IssueTrigger; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversClassesThatExtendClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(IssueTrigger::class)] +#[CoversClassesThatExtendClass(IssueTrigger::class)] +#[Small] +final class IssueTriggerTest extends TestCase +{ + public function testCanBeTest(): void + { + $trigger = IssueTrigger::test(); + + $this->assertTrue($trigger->isTest()); + $this->assertFalse($trigger->isSelf()); + $this->assertFalse($trigger->isDirect()); + $this->assertFalse($trigger->isIndirect()); + $this->assertFalse($trigger->isUnknown()); + $this->assertSame('issue triggered by test code', $trigger->asString()); + } + + public function testCanBeSelf(): void + { + $trigger = IssueTrigger::self(); + + $this->assertTrue($trigger->isSelf()); + $this->assertFalse($trigger->isTest()); + $this->assertFalse($trigger->isDirect()); + $this->assertFalse($trigger->isIndirect()); + $this->assertFalse($trigger->isUnknown()); + $this->assertSame('issue triggered by first-party code calling into first-party code', $trigger->asString()); + } + + public function testCanBeDirect(): void + { + $trigger = IssueTrigger::direct(); + + $this->assertTrue($trigger->isDirect()); + $this->assertFalse($trigger->isTest()); + $this->assertFalse($trigger->isSelf()); + $this->assertFalse($trigger->isIndirect()); + $this->assertFalse($trigger->isUnknown()); + $this->assertSame('issue triggered by first-party code calling into third-party code', $trigger->asString()); + } + + public function testCanBeIndirect(): void + { + $trigger = IssueTrigger::indirect(); + + $this->assertTrue($trigger->isIndirect()); + $this->assertFalse($trigger->isTest()); + $this->assertFalse($trigger->isSelf()); + $this->assertFalse($trigger->isDirect()); + $this->assertFalse($trigger->isUnknown()); + $this->assertSame('issue triggered by third-party code', $trigger->asString()); + } + + public function testCanBeUnknown(): void + { + $trigger = IssueTrigger::unknown(); + + $this->assertFalse($trigger->isTest()); + $this->assertFalse($trigger->isSelf()); + $this->assertFalse($trigger->isDirect()); + $this->assertFalse($trigger->isIndirect()); + $this->assertTrue($trigger->isUnknown()); + $this->assertSame('unknown if issue was triggered in first-party code or third-party code', $trigger->asString()); + } +} diff --git a/tests/unit/Event/Value/Test/PhptTest.php b/tests/unit/Event/Value/Test/PhptTest.php new file mode 100644 index 00000000000..484c3ee8ba0 --- /dev/null +++ b/tests/unit/Event/Value/Test/PhptTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Phpt::class)] +#[CoversClass(Test::class)] +#[Small] +final class PhptTest extends TestCase +{ + public function testConstructorSetsValues(): void + { + $file = 'test.phpt'; + + $test = new Phpt($file); + + $this->assertSame($file, $test->file()); + $this->assertSame($file, $test->id()); + $this->assertSame($file, $test->name()); + $this->assertTrue($test->isPhpt()); + $this->assertFalse($test->isTestMethod()); + } +} diff --git a/tests/unit/Event/Value/Test/TestCollectionTest.php b/tests/unit/Event/Value/Test/TestCollectionTest.php new file mode 100644 index 00000000000..3b307b6dc7b --- /dev/null +++ b/tests/unit/Event/Value/Test/TestCollectionTest.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +use PHPUnit\Event\TestData\TestDataCollection; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use PHPUnit\Metadata\MetadataCollection; + +#[CoversClass(TestCollection::class)] +#[CoversClass(TestCollectionIterator::class)] +#[Small] +final class TestCollectionTest extends TestCase +{ + public function testIsCreatedFromArray(): void + { + $test = $this->test(); + $tests = TestCollection::fromArray([$test]); + + $this->assertSame([$test], $tests->asArray()); + } + + public function testIsCountable(): void + { + $test = $this->test(); + $tests = TestCollection::fromArray([$test]); + + $this->assertCount(1, $tests); + } + + public function testIsIterable(): void + { + $test = $this->test(); + $tests = TestCollection::fromArray([$test]); + + foreach ($tests as $index => $_test) { + $this->assertSame(0, $index); + $this->assertSame($test, $_test); + } + } + + private function test(): TestMethod + { + return new TestMethod( + 'FooTest', + 'testBar', + 'FooTest.php', + 1, + TestDoxBuilder::fromClassNameAndMethodName('Foo', 'bar'), + MetadataCollection::fromArray([]), + TestDataCollection::fromArray([]), + ); + } +} diff --git a/tests/unit/Event/Value/Test/TestData/TestDataCollectionTest.php b/tests/unit/Event/Value/Test/TestData/TestDataCollectionTest.php new file mode 100644 index 00000000000..2158c20f95c --- /dev/null +++ b/tests/unit/Event/Value/Test/TestData/TestDataCollectionTest.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestData; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(TestDataCollection::class)] +#[CoversClass(TestDataCollectionIterator::class)] +#[UsesClass(TestData::class)] +#[UsesClass(DataFromDataProvider::class)] +#[UsesClass(DataFromTestDependency::class)] +#[Small] +final class TestDataCollectionTest extends TestCase +{ + public function testMayBeEmpty(): void + { + $collection = TestDataCollection::fromArray([]); + + $this->assertCount(0, $collection); + $this->assertFalse($collection->hasDataFromDataProvider()); + } + + public function testMayContainDataFromDataProvider(): void + { + $data = $this->dataFromDataProvider(); + $collection = TestDataCollection::fromArray([$data]); + + $this->assertTrue($collection->hasDataFromDataProvider()); + $this->assertSame([$data], $collection->asArray()); + $this->assertSame($data, $collection->dataFromDataProvider()); + } + + public function testMayContainDataFromDependedUponTest(): void + { + $data = $this->dataFromDependedUponTest(); + $collection = TestDataCollection::fromArray([$data]); + + $this->assertFalse($collection->hasDataFromDataProvider()); + $this->assertSame([$data], $collection->asArray()); + } + + public function testExceptionIsRaisedWhenDataFromDataProviderIsAccessedButDoesNotExist(): void + { + $collection = TestDataCollection::fromArray([]); + + $this->expectException(NoDataSetFromDataProviderException::class); + + $collection->dataFromDataProvider(); + } + + public function testIsIterable(): void + { + $data = $this->dataFromDataProvider(); + $collection = TestDataCollection::fromArray([$data]); + + foreach ($collection as $index => $element) { + $this->assertSame(0, $index); + $this->assertSame($data, $element); + } + } + + private function dataFromDataProvider(): DataFromDataProvider + { + return DataFromDataProvider::from( + 'data-set-name', + 'data-as-string', + 'data-as-string-for-output', + ); + } + + private function dataFromDependedUponTest(): DataFromTestDependency + { + return DataFromTestDependency::from('data-as-string'); + } +} diff --git a/tests/unit/Event/Value/Test/TestData/TestDataTest.php b/tests/unit/Event/Value/Test/TestData/TestDataTest.php new file mode 100644 index 00000000000..ee1db09b0a0 --- /dev/null +++ b/tests/unit/Event/Value/Test/TestData/TestDataTest.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestData; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversClassesThatExtendClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(TestData::class)] +#[CoversClassesThatExtendClass(TestData::class)] +#[Small] +final class TestDataTest extends TestCase +{ + public function testDataCanBeFromDataProvider(): void + { + $name = 'data-set-name'; + $dataAsString = 'data-as-string'; + $dataAsStringForOutput = 'data-as-string-for-output'; + + $data = DataFromDataProvider::from( + $name, + $dataAsString, + $dataAsStringForOutput, + ); + + $this->assertTrue($data->isFromDataProvider()); + $this->assertFalse($data->isFromTestDependency()); + $this->assertSame($name, $data->dataSetName()); + $this->assertSame($dataAsString, $data->data()); + $this->assertSame($dataAsStringForOutput, $data->dataAsStringForResultOutput()); + } + + public function testDataCanBeFromDependedUponTest(): void + { + $dataAsString = 'data-as-string'; + + $data = DataFromTestDependency::from($dataAsString); + + $this->assertTrue($data->isFromTestDependency()); + $this->assertFalse($data->isFromDataProvider()); + $this->assertSame($dataAsString, $data->data()); + } +} diff --git a/tests/unit/Event/Value/Test/TestMethodTest.php b/tests/unit/Event/Value/Test/TestMethodTest.php new file mode 100644 index 00000000000..e87dc676132 --- /dev/null +++ b/tests/unit/Event/Value/Test/TestMethodTest.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +use function sprintf; +use PHPUnit\Event\TestData\DataFromDataProvider; +use PHPUnit\Event\TestData\TestDataCollection; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use PHPUnit\Metadata\MetadataCollection; + +#[CoversClass(TestMethod::class)] +#[CoversClass(Test::class)] +#[Small] +final class TestMethodTest extends TestCase +{ + public function testConstructorSetsValues(): void + { + $className = 'FooTest'; + $methodName = 'testBar'; + $file = 'FooTest.php'; + $line = 1; + $testDox = TestDoxBuilder::fromClassNameAndMethodName('Foo', 'bar'); + $testData = TestDataCollection::fromArray([]); + $metadata = MetadataCollection::fromArray([]); + + $test = new TestMethod( + $className, + $methodName, + $file, + $line, + $testDox, + $metadata, + $testData, + ); + + $this->assertSame($className, $test->className()); + $this->assertSame($methodName, $test->methodName()); + $this->assertSame($className . '::' . $methodName, $test->nameWithClass()); + $this->assertSame('FooTest::testBar', $test->id()); + $this->assertSame($file, $test->file()); + $this->assertSame($line, $test->line()); + $this->assertSame($testDox, $test->testDox()); + $this->assertSame($metadata, $test->metadata()); + $this->assertSame($testData, $test->testData()); + $this->assertTrue($test->isTestMethod()); + $this->assertFalse($test->isPhpt()); + } + + public function testNameReturnsNameWhenTestDoesNotHaveDataFromDataProvider(): void + { + $test = new TestMethod( + 'FooTest', + 'testBar', + 'FooTest.php', + 1, + TestDoxBuilder::fromClassNameAndMethodName('Foo', 'bar'), + MetadataCollection::fromArray([]), + TestDataCollection::fromArray([]), + ); + + $this->assertSame($test->methodName(), $test->name()); + } + + public function testNameReturnsNameWhenTestHasDataFromDataProviderAndDataSetNameIsInt(): void + { + $dataSetName = 9000; + + $test = new TestMethod( + 'FooTest', + 'testBar', + 'FooTest.php', + 1, + TestDoxBuilder::fromClassNameAndMethodName('Foo', 'bar'), + MetadataCollection::fromArray([]), + TestDataCollection::fromArray( + [ + DataFromDataProvider::from( + $dataSetName, + 'data', + 'data as string for result output', + ), + ], + ), + ); + + $expected = sprintf( + '%s with data set #%d', + $test->methodName(), + $dataSetName, + ); + + $this->assertSame($expected, $test->name()); + $this->assertSame('FooTest::testBar#9000', $test->id()); + $this->assertSame('data', $test->testData()->dataFromDataProvider()->data()); + $this->assertSame('data as string for result output', $test->testData()->dataFromDataProvider()->dataAsStringForResultOutput()); + } + + public function testNameReturnsNameWhenTestHasDataFromDataProviderAndDataSetNameIsString(): void + { + $dataSetName = 'bar-9000'; + + $test = new TestMethod( + 'FooTest', + 'testBar', + 'FooTest.php', + 1, + TestDoxBuilder::fromClassNameAndMethodName('Foo', 'bar'), + MetadataCollection::fromArray([]), + TestDataCollection::fromArray( + [ + DataFromDataProvider::from( + $dataSetName, + 'data', + 'data as string for result output', + ), + ], + ), + ); + + $expected = sprintf( + '%s with data set "%s"', + $test->methodName(), + $dataSetName, + ); + + $this->assertSame($expected, $test->name()); + $this->assertSame('FooTest::testBar#bar-9000', $test->id()); + $this->assertSame('data', $test->testData()->dataFromDataProvider()->data()); + $this->assertSame('data as string for result output', $test->testData()->dataFromDataProvider()->dataAsStringForResultOutput()); + } +} diff --git a/tests/unit/Event/Value/TestSuite/TestSuiteBuilderTest.php b/tests/unit/Event/Value/TestSuite/TestSuiteBuilderTest.php new file mode 100644 index 00000000000..5e795790483 --- /dev/null +++ b/tests/unit/Event/Value/TestSuite/TestSuiteBuilderTest.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestSuite as FrameworkTestSuite; +use PHPUnit\Runner\Filter\Factory; +use PHPUnit\TextUI\CliArguments\Builder as CliArgumentsBuilder; +use PHPUnit\TextUI\Configuration\Merger as ConfigurationMerger; +use PHPUnit\TextUI\XmlConfiguration\Loader as XmlConfigurationLoader; +use PHPUnit\TextUI\XmlConfiguration\TestSuiteMapper; + +#[CoversClass(TestSuiteBuilder::class)] +#[Small] +final class TestSuiteBuilderTest extends TestCase +{ + public function test_Builds_TestSuite_value_object_for_test_suite_loaded_from_XML_configuration_file(): void + { + $testSuite = TestSuiteBuilder::from($this->testSuiteFromXmlConfiguration()); + + $this->assertTrue($testSuite->isWithName()); + $this->assertStringEndsWith('phpunit.xml', $testSuite->name()); + $this->assertSame(3, $testSuite->count()); + $this->assertSame(3, $testSuite->tests()->count()); + $this->assertCount(3, $testSuite->tests()); + } + + public function testBuildCountWithFilter(): void + { + $testSuite = $this->testSuiteFromXmlConfiguration(); + $filterFactory = new Factory; + $filterFactory->addIncludeNameFilter('one'); + $testSuite->injectFilter($filterFactory); + $testSuite = TestSuiteBuilder::from($testSuite); + + $this->assertSame(1, $testSuite->count()); + $this->assertSame(1, $testSuite->tests()->count()); + $this->assertCount(1, $testSuite->tests()); + } + + public function test_Builds_TestSuite_value_object_for_test_case_class(): void + { + $testSuite = TestSuiteBuilder::from($this->testSuiteFromXmlConfiguration()->tests()[0]->tests()[0]); + + $this->assertTrue($testSuite->isForTestClass()); + $this->assertSame('PHPUnit\TestFixture\Groups\FooTest', $testSuite->name()); + $this->assertSame(3, $testSuite->count()); + $this->assertCount(3, $testSuite->tests()); + } + + private function testSuiteFromXmlConfiguration(): FrameworkTestSuite + { + $cliConfiguration = (new CliArgumentsBuilder)->fromParameters([]); + $xmlConfiguration = (new XmlConfigurationLoader)->load(__DIR__ . '/../../../../end-to-end/_files/groups/phpunit.xml'); + $configuration = (new ConfigurationMerger)->merge($cliConfiguration, $xmlConfiguration); + + return (new TestSuiteMapper)->map( + $configuration->configurationFile(), + $configuration->testSuite(), + $configuration->includeTestSuites(), + $configuration->excludeTestSuites(), + ); + } +} diff --git a/tests/unit/Event/Value/TestSuite/TestSuiteTest.php b/tests/unit/Event/Value/TestSuite/TestSuiteTest.php new file mode 100644 index 00000000000..3c780b13899 --- /dev/null +++ b/tests/unit/Event/Value/TestSuite/TestSuiteTest.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use PHPUnit\Event\Code\TestCollection; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversClassesThatExtendClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(TestSuite::class)] +#[CoversClassesThatExtendClass(TestSuite::class)] +#[Small] +final class TestSuiteTest extends TestCase +{ + public function testCanBeTestSuiteForTestClass(): void + { + $className = 'ExampleTest'; + $size = 0; + $tests = TestCollection::fromArray([]); + $file = 'ExampleTest.php'; + $line = 1; + + $testSuite = new TestSuiteForTestClass($className, $size, $tests, $file, $line); + + $this->assertTrue($testSuite->isForTestClass()); + $this->assertFalse($testSuite->isForTestMethodWithDataProvider()); + $this->assertFalse($testSuite->isWithName()); + + $this->assertSame($className, $testSuite->className()); + $this->assertSame($className, $testSuite->name()); + $this->assertSame($size, $testSuite->count()); + $this->assertSame($tests, $testSuite->tests()); + $this->assertSame($file, $testSuite->file()); + $this->assertSame($line, $testSuite->line()); + } + + public function testCanBeTestSuiteForTestMethodWithDataProvider(): void + { + $name = 'ExampleTest::testOne'; + $className = 'ExampleTest'; + $methodName = 'testOne'; + $size = 0; + $tests = TestCollection::fromArray([]); + $file = 'ExampleTest.php'; + $line = 1; + + $testSuite = new TestSuiteForTestMethodWithDataProvider($name, $size, $tests, $className, $methodName, $file, $line); + + $this->assertFalse($testSuite->isForTestClass()); + $this->assertTrue($testSuite->isForTestMethodWithDataProvider()); + $this->assertFalse($testSuite->isWithName()); + + $this->assertSame($name, $testSuite->name()); + $this->assertSame($className, $testSuite->className()); + $this->assertSame($methodName, $testSuite->methodName()); + $this->assertSame($size, $testSuite->count()); + $this->assertSame($tests, $testSuite->tests()); + $this->assertSame($file, $testSuite->file()); + $this->assertSame($line, $testSuite->line()); + } + + public function testCanBeTestSuiteWithName(): void + { + $name = 'the-name'; + $size = 0; + $tests = TestCollection::fromArray([]); + + $testSuite = new TestSuiteWithName($name, $size, $tests); + + $this->assertFalse($testSuite->isForTestClass()); + $this->assertFalse($testSuite->isForTestMethodWithDataProvider()); + $this->assertTrue($testSuite->isWithName()); + + $this->assertSame($name, $testSuite->name()); + $this->assertSame($size, $testSuite->count()); + $this->assertSame($tests, $testSuite->tests()); + } +} diff --git a/tests/unit/Event/Value/ThrowableTest.php b/tests/unit/Event/Value/ThrowableTest.php new file mode 100644 index 00000000000..3dc846331e6 --- /dev/null +++ b/tests/unit/Event/Value/ThrowableTest.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +use Exception; +use PHPUnit\Event\NoPreviousThrowableException; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use PHPUnit\Util\Filter; + +#[CoversClass(Throwable::class)] +#[CoversClass(ThrowableBuilder::class)] +#[Small] +final class ThrowableTest extends TestCase +{ + public function testCanBeCreatedForThrowableWithoutPrevious(): void + { + $e = new Exception('message', 123, null); + $t = ThrowableBuilder::from($e); + + $this->assertSame(Exception::class, $t->className()); + $this->assertSame('message', $t->message()); + $this->assertSame("Exception: message\n", $t->description()); + $this->assertSame(Filter::stackTraceFromThrowableAsString($e), $t->stackTrace()); + $this->assertFalse($t->hasPrevious()); + + $this->expectException(NoPreviousThrowableException::class); + + $t->previous(); + } + + public function testCanBeCreatedForThrowableWithPrevious(): void + { + $first = new Exception('first message', 123, null); + $second = new Exception('second message', 456, $first); + $t = ThrowableBuilder::from($second); + + $this->assertSame(Exception::class, $t->className()); + $this->assertSame('second message', $t->message()); + $this->assertSame("Exception: second message\n", $t->description()); + $this->assertSame(Filter::stackTraceFromThrowableAsString($second, false), $t->stackTrace()); + $this->assertTrue($t->hasPrevious()); + + $previous = $t->previous(); + + $this->assertSame(Exception::class, $previous->className()); + $this->assertSame('first message', $previous->message()); + $this->assertSame("Exception: first message\n", $previous->description()); + $this->assertSame(Filter::stackTraceFromThrowableAsString($first), $previous->stackTrace()); + + $this->assertStringMatchesFormat( + <<<'EOD' +Exception: second message + +%A +Caused by +Exception: first message + +%A +EOD + , + $t->asString(), + ); + } +} diff --git a/tests/unit/Framework/Assert/FunctionsTest.php b/tests/unit/Framework/Assert/FunctionsTest.php index 5df7402d48e..247be881971 100644 --- a/tests/unit/Framework/Assert/FunctionsTest.php +++ b/tests/unit/Framework/Assert/FunctionsTest.php @@ -12,54 +12,59 @@ use function array_reduce; use function file_get_contents; use function preg_match_all; +use PHPUnit\Framework\Attributes\CoversNothing; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestDox; +#[CoversNothing] +#[TestDox('Global Assertion Functions')] final class FunctionsTest extends TestCase { - private static $globalAssertionFunctions = []; + private static array $globalAssertionFunctions = []; - public static function setUpBeforeClass(): void + public static function provideStaticAssertionMethodNames(): array { preg_match_all( - '/function (assert[^ \(]+)/', + '/public static function (assert[^ (]+)/', file_get_contents( - __DIR__ . '/../../../../src/Framework/Assert/Functions.php' + __DIR__ . '/../../../../src/Framework/Assert.php', ), - $matches + $matches, ); - self::$globalAssertionFunctions = $matches[1]; - } + return array_reduce( + $matches[1], + static function (array $functionNames, string $functionName) + { + $functionNames[$functionName] = [$functionName]; - /** - * @dataProvider provideStaticAssertionMethodNames - */ - public function testGlobalFunctionsFileContainsAllStaticAssertions(string $methodName): void - { - Assert::assertContains( - $methodName, - self::$globalAssertionFunctions, - "Mapping for Assert::{$methodName} is missing in Functions.php" + return $functionNames; + }, + [], ); } - public function provideStaticAssertionMethodNames(): array + public static function setUpBeforeClass(): void { preg_match_all( - '/public static function (assert[^ \(]+)/', + '/function (assert[^ (]+)/', file_get_contents( - __DIR__ . '/../../../../src/Framework/Assert.php' + __DIR__ . '/../../../../src/Framework/Assert/Functions.php', ), - $matches + $matches, ); - return array_reduce( - $matches[1], - function (array $functionNames, string $functionName) { - $functionNames[$functionName] = [$functionName]; + self::$globalAssertionFunctions = $matches[1]; + } - return $functionNames; - }, - [] + #[DataProvider('provideStaticAssertionMethodNames')] + #[TestDox('PHPUnit\Framework\Assert::$methodName() is available as global function $methodName()')] + public function testGlobalFunctionsFileContainsAllStaticAssertions(string $methodName): void + { + Assert::assertContains( + $methodName, + self::$globalAssertionFunctions, + "Mapping for Assert::{$methodName} is missing in Functions.php", ); } } diff --git a/tests/unit/Framework/Assert/assertArrayHasKeyTest.php b/tests/unit/Framework/Assert/assertArrayHasKeyTest.php new file mode 100644 index 00000000000..82335a66fe6 --- /dev/null +++ b/tests/unit/Framework/Assert/assertArrayHasKeyTest.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use ArrayAccess; +use ArrayObject; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\TestFixture\SampleArrayAccess; + +#[CoversMethod(Assert::class, 'assertArrayHasKey')] +#[TestDox('assertArrayHasKey()')] +#[Small] +final class assertArrayHasKeyTest extends TestCase +{ + /** + * @return non-empty-list|ArrayAccess}> + */ + public static function successProvider(): array + { + $arrayAccess = new SampleArrayAccess; + $arrayAccess['foo'] = 'bar'; + + $arrayObject = new ArrayObject; + $arrayObject['foo'] = 'bar'; + + return [ + [0, ['foo']], + ['foo', ['foo' => 'bar']], + ['foo', $arrayAccess], + ['foo', $arrayObject], + ]; + } + + /** + * @return non-empty-list|ArrayAccess}> + */ + public static function failureProvider(): array + { + $arrayAccess = new SampleArrayAccess; + $arrayAccess['foo'] = 'bar'; + + $arrayObject = new ArrayObject; + $arrayObject['foo'] = 'bar'; + + return [ + [1, ['foo']], + ['bar', ['foo' => 'bar']], + ['bar', $arrayAccess], + ['bar', $arrayObject], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(int|string $key, array|ArrayAccess $array): void + { + $this->assertArrayHasKey($key, $array); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(int|string $key, array|ArrayAccess $array): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertArrayHasKey($key, $array); + } +} diff --git a/tests/unit/Framework/Assert/assertArrayIsEqualToArrayIgnoringListOfKeysTest.php b/tests/unit/Framework/Assert/assertArrayIsEqualToArrayIgnoringListOfKeysTest.php new file mode 100644 index 00000000000..54249358482 --- /dev/null +++ b/tests/unit/Framework/Assert/assertArrayIsEqualToArrayIgnoringListOfKeysTest.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertArrayIsEqualToArrayIgnoringListOfKeys')] +#[TestDox('assertArrayIsEqualToArrayIgnoringListOfKeys()')] +#[Small] +final class assertArrayIsEqualToArrayIgnoringListOfKeysTest extends TestCase +{ + /** + * @return non-empty-list, 1: array, 2: array}> + */ + public static function successProvider(): array + { + return [ + [ + ['a' => 'b', 'b' => 'c', 0 => 1, 1 => 2], + ['a' => 'b', 'b' => 'b', 0 => 1, 1 => 3], + ['b', 1], + ], + [ + [0 => 1, '1' => 2, 2.0 => 3, '3.0' => 4], + [0 => 1, '1' => 2, 2.0 => 2, '3.0' => 4], + [2.0], + ], + [ + ['a' => 'b', 'b' => 'c', 0 => 1, 1 => 2], + [0 => 1, 1 => 3, 'a' => 'b', 'b' => 'b'], + ['b', 1], + ], + ]; + } + + /** + * @return non-empty-list, 1: array, 2: array}> + */ + public static function failureProvider(): array + { + return [ + [ + ['a' => 'b', 'b' => 'c', 0 => 1, 1 => 2], + ['a' => 'b', 'b' => 'b', 0 => 1, 1 => 3], + ['b'], + ], + [ + [0 => 1, '1' => 2, 2.0 => 3, '3.0' => 4], + [0 => 1, '1' => 2, 2.0 => 2, '3.0' => 4], + ['1'], + ], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(array $expected, array $actual, array $keysToBeIgnored): void + { + $this->assertArrayIsEqualToArrayIgnoringListOfKeys($expected, $actual, $keysToBeIgnored); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(array $expected, array $actual, array $keysToBeIgnored): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertArrayIsEqualToArrayIgnoringListOfKeys($expected, $actual, $keysToBeIgnored); + } +} diff --git a/tests/unit/Framework/Assert/assertArrayIsEqualToArrayOnlyConsideringListOfKeysTest.php b/tests/unit/Framework/Assert/assertArrayIsEqualToArrayOnlyConsideringListOfKeysTest.php new file mode 100644 index 00000000000..bb33eef2514 --- /dev/null +++ b/tests/unit/Framework/Assert/assertArrayIsEqualToArrayOnlyConsideringListOfKeysTest.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertArrayIsEqualToArrayOnlyConsideringListOfKeys')] +#[TestDox('assertArrayIsEqualToArrayOnlyConsideringListOfKeys()')] +#[Small] +final class assertArrayIsEqualToArrayOnlyConsideringListOfKeysTest extends TestCase +{ + /** + * @return non-empty-list, 1: array, 2: array}> + */ + public static function successProvider(): array + { + return [ + [ + ['a' => 'b', 'b' => 'c', 0 => 1, 1 => 2], + ['a' => 'b', 'b' => 'b', 0 => 1, 1 => 3], + ['a', 0], + ], + [ + [0 => 1, '1' => 2, 2.0 => 3, '3.0' => 4], + [0 => 1, '1' => 2, 2.0 => 2, '3.0' => 4], + [0, '1', '3.0'], + ], + [ + ['a' => 'b', 'b' => 'c', 0 => 1, 1 => 2], + [0 => 1, 1 => 3, 'a' => 'b', 'b' => 'b'], + ['a', 0], + ], + ]; + } + + /** + * @return non-empty-list, 1: array, 2: array}> + */ + public static function failureProvider(): array + { + return [ + [ + ['a' => 'b', 'b' => 'c', 0 => 1, 1 => 2], + ['a' => 'b', 'b' => 'b', 0 => 1, 1 => 3], + ['b'], + ], + [ + [0 => 1, '1' => 2, 2.0 => 3, '3.0' => 4], + [0 => 1, '1' => 2, 2.0 => 2, '3.0' => 4], + ['1', 2.0, '3.0'], + ], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(array $expected, array $actual, array $keysToBeConsidered): void + { + $this->assertArrayIsEqualToArrayOnlyConsideringListOfKeys($expected, $actual, $keysToBeConsidered); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(array $expected, array $actual, array $keysToBeConsidered): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertArrayIsEqualToArrayOnlyConsideringListOfKeys($expected, $actual, $keysToBeConsidered); + } +} diff --git a/tests/unit/Framework/Assert/assertArrayIsIdenticalToArrayIgnoringListOfKeysTest.php b/tests/unit/Framework/Assert/assertArrayIsIdenticalToArrayIgnoringListOfKeysTest.php new file mode 100644 index 00000000000..d3701c7c952 --- /dev/null +++ b/tests/unit/Framework/Assert/assertArrayIsIdenticalToArrayIgnoringListOfKeysTest.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertArrayIsIdenticalToArrayIgnoringListOfKeys')] +#[TestDox('assertArrayIsIdenticalToArrayIgnoringListOfKeys()')] +#[Small] +final class assertArrayIsIdenticalToArrayIgnoringListOfKeysTest extends TestCase +{ + /** + * @return non-empty-list, 1: array, 2: array}> + */ + public static function successProvider(): array + { + return [ + [ + ['a' => 'b', 'b' => 'c', 0 => 1, 1 => 2], + ['a' => 'b', 'b' => 'b', 0 => 1, 1 => 3], + ['b', 1], + ], + [ + [0 => 1, '1' => 2, 2.0 => 3, '3.0' => 4], + [0 => 1, '1' => 2, 2.0 => 2, '3.0' => 4], + [2.0], + ], + ]; + } + + /** + * @return non-empty-list, 1: array, 2: array}> + */ + public static function failureProvider(): array + { + return [ + [ + ['a' => 'b', 'b' => 'c', 0 => 1, 1 => 2], + ['a' => 'b', 'b' => 'b', 0 => 1, 1 => 3], + ['b'], + ], + [ + [0 => 1, '1' => 2, 2.0 => 3, '3.0' => 4], + [0 => 1, '1' => 2, 2.0 => 2, '3.0' => 4], + ['1'], + ], + [ + ['a' => 'b', 'b' => 'c', 0 => 1, 1 => 2], + [0 => 1, 1 => 3, 'a' => 'b', 'b' => 'b'], + ['b', 1], + ], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(array $expected, array $actual, array $keysToBeIgnored): void + { + $this->assertArrayIsIdenticalToArrayIgnoringListOfKeys($expected, $actual, $keysToBeIgnored); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(array $expected, array $actual, array $keysToBeIgnored): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertArrayIsIdenticalToArrayIgnoringListOfKeys($expected, $actual, $keysToBeIgnored); + } +} diff --git a/tests/unit/Framework/Assert/assertArrayIsIdenticalToArrayOnlyConsideringListOfKeysTest.php b/tests/unit/Framework/Assert/assertArrayIsIdenticalToArrayOnlyConsideringListOfKeysTest.php new file mode 100644 index 00000000000..7dcdd5c64d5 --- /dev/null +++ b/tests/unit/Framework/Assert/assertArrayIsIdenticalToArrayOnlyConsideringListOfKeysTest.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys')] +#[TestDox('assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys()')] +#[Small] +final class assertArrayIsIdenticalToArrayOnlyConsideringListOfKeysTest extends TestCase +{ + /** + * @return non-empty-list, 1: array, 2: array}> + */ + public static function successProvider(): array + { + return [ + [ + ['a' => 'b', 'b' => 'c', 0 => 1, 1 => 2], + ['a' => 'b', 'b' => 'b', 0 => 1, 1 => 3], + ['a', 0], + ], + [ + [0 => 1, '1' => 2, 2.0 => 3, '3.0' => 4], + [0 => 1, '1' => 2, 2.0 => 2, '3.0' => 4], + [0, '1', '3.0'], + ], + ]; + } + + /** + * @return non-empty-list, 1: array, 2: array}> + */ + public static function failureProvider(): array + { + return [ + [ + ['a' => 'b', 'b' => 'c', 0 => 1, 1 => 2], + ['a' => 'b', 'b' => 'b', 0 => 1, 1 => 3], + ['b'], + ], + [ + [0 => 1, '1' => 2, 2.0 => 3, '3.0' => 4], + [0 => 1, '1' => 2, 2.0 => 2, '3.0' => 4], + ['1', 2.0, '3.0'], + ], + [ + ['a' => 'b', 'b' => 'c', 0 => 1, 1 => 2], + [0 => 1, 1 => 3, 'a' => 'b', 'b' => 'b'], + ['a', 0], + ], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(array $expected, array $actual, array $keysToBeConsidered): void + { + $this->assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys($expected, $actual, $keysToBeConsidered); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(array $expected, array $actual, array $keysToBeConsidered): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys($expected, $actual, $keysToBeConsidered); + } +} diff --git a/tests/unit/Framework/Assert/assertArrayNotHasKeyTest.php b/tests/unit/Framework/Assert/assertArrayNotHasKeyTest.php new file mode 100644 index 00000000000..983bc1948fc --- /dev/null +++ b/tests/unit/Framework/Assert/assertArrayNotHasKeyTest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use ArrayAccess; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertArrayNotHasKey')] +#[TestDox('assertArrayNotHasKey()')] +#[Small] +final class assertArrayNotHasKeyTest extends TestCase +{ + #[DataProviderExternal(assertArrayHasKeyTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(int|string $key, array|ArrayAccess $array): void + { + $this->assertArrayNotHasKey($key, $array); + } + + #[DataProviderExternal(assertArrayHasKeyTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(int|string $key, array|ArrayAccess $array): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertArrayNotHasKey($key, $array); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsEqualsTest.php b/tests/unit/Framework/Assert/assertContainsEqualsTest.php new file mode 100644 index 00000000000..814fa70c342 --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsEqualsTest.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use stdClass; + +#[CoversMethod(Assert::class, 'assertContainsEquals')] +#[TestDox('assertContainsEquals()')] +#[Small] +final class assertContainsEqualsTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + $a = new stdClass; + $a->foo = 'bar'; + + $b = new stdClass; + $b->foo = 'bar'; + + return [ + [0, [0]], + [0, ['0']], + [0, [0.0]], + [0, [false]], + [0, [null]], + ['string', ['string']], + [['string'], [['string']]], + [$a, [$b]], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + $a = new stdClass; + $a->foo = 'bar'; + + $b = new stdClass; + $b->foo = 'baz'; + + return [ + [1, [0]], + [1, [0.0]], + [1, [false]], + [1, [null]], + ['string', ['another-string']], + [['string'], [['another-string']]], + [$a, [$b]], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $needle, iterable $haystack): void + { + $this->assertContainsEquals($needle, $haystack); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $needle, iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsEquals($needle, $haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsNotOnlyArrayTest.php b/tests/unit/Framework/Assert/assertContainsNotOnlyArrayTest.php new file mode 100644 index 00000000000..6bd29ce7cf4 --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsNotOnlyArrayTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsNotOnlyArray')] +#[TestDox('assertContainsNotOnlyArray()')] +#[Small] +final class assertContainsNotOnlyArrayTest extends TestCase +{ + #[DataProviderExternal(assertContainsOnlyArrayTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsNotOnlyArray($haystack); + } + + #[DataProviderExternal(assertContainsOnlyArrayTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsNotOnlyArray($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsNotOnlyBoolTest.php b/tests/unit/Framework/Assert/assertContainsNotOnlyBoolTest.php new file mode 100644 index 00000000000..ad6ad8a079c --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsNotOnlyBoolTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsNotOnlyBool')] +#[TestDox('assertContainsNotOnlyBool()')] +#[Small] +final class assertContainsNotOnlyBoolTest extends TestCase +{ + #[DataProviderExternal(assertContainsOnlyBoolTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsNotOnlyBool($haystack); + } + + #[DataProviderExternal(assertContainsOnlyBoolTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsNotOnlyBool($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsNotOnlyCallableTest.php b/tests/unit/Framework/Assert/assertContainsNotOnlyCallableTest.php new file mode 100644 index 00000000000..7fe565c385a --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsNotOnlyCallableTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsNotOnlyCallable')] +#[TestDox('assertContainsNotOnlyCallable()')] +#[Small] +final class assertContainsNotOnlyCallableTest extends TestCase +{ + #[DataProviderExternal(assertContainsOnlyCallableTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsNotOnlyCallable($haystack); + } + + #[DataProviderExternal(assertContainsOnlyCallableTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsNotOnlyCallable($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsNotOnlyClosedResourceTest.php b/tests/unit/Framework/Assert/assertContainsNotOnlyClosedResourceTest.php new file mode 100644 index 00000000000..f4a503184be --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsNotOnlyClosedResourceTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsNotOnlyClosedResource')] +#[TestDox('assertContainsNotOnlyClosedResource()')] +#[Small] +final class assertContainsNotOnlyClosedResourceTest extends TestCase +{ + #[DataProviderExternal(assertContainsOnlyClosedResourceTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsNotOnlyClosedResource($haystack); + } + + #[DataProviderExternal(assertContainsOnlyClosedResourceTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsNotOnlyClosedResource($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsNotOnlyFloatTest.php b/tests/unit/Framework/Assert/assertContainsNotOnlyFloatTest.php new file mode 100644 index 00000000000..7d49a99eea0 --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsNotOnlyFloatTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsNotOnlyFloat')] +#[TestDox('assertContainsNotOnlyFloat()')] +#[Small] +final class assertContainsNotOnlyFloatTest extends TestCase +{ + #[DataProviderExternal(assertContainsOnlyFloatTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsNotOnlyFloat($haystack); + } + + #[DataProviderExternal(assertContainsOnlyFloatTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsNotOnlyFloat($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsNotOnlyInstancesOfTest.php b/tests/unit/Framework/Assert/assertContainsNotOnlyInstancesOfTest.php new file mode 100644 index 00000000000..f466c8476fb --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsNotOnlyInstancesOfTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsNotOnlyInstancesOf')] +#[TestDox('assertContainsNotOnlyInstancesOf()')] +#[Small] +final class assertContainsNotOnlyInstancesOfTest extends TestCase +{ + #[DataProviderExternal(assertContainsOnlyInstancesOfTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(string $type, iterable $haystack): void + { + $this->assertContainsNotOnlyInstancesOf($type, $haystack); + } + + #[DataProviderExternal(assertContainsOnlyInstancesOfTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(string $type, iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsNotOnlyInstancesOf($type, $haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsNotOnlyIntTest.php b/tests/unit/Framework/Assert/assertContainsNotOnlyIntTest.php new file mode 100644 index 00000000000..c60b24c5e07 --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsNotOnlyIntTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsNotOnlyInt')] +#[TestDox('assertContainsNotOnlyInt()')] +#[Small] +final class assertContainsNotOnlyIntTest extends TestCase +{ + #[DataProviderExternal(assertContainsOnlyIntTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsNotOnlyInt($haystack); + } + + #[DataProviderExternal(assertContainsOnlyIntTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsNotOnlyInt($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsNotOnlyIterableTest.php b/tests/unit/Framework/Assert/assertContainsNotOnlyIterableTest.php new file mode 100644 index 00000000000..fad89055b1c --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsNotOnlyIterableTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsNotOnlyIterable')] +#[TestDox('assertContainsNotOnlyIterable()')] +#[Small] +final class assertContainsNotOnlyIterableTest extends TestCase +{ + #[DataProviderExternal(assertContainsOnlyIterableTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsNotOnlyIterable($haystack); + } + + #[DataProviderExternal(assertContainsOnlyIterableTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsNotOnlyIterable($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsNotOnlyNullTest.php b/tests/unit/Framework/Assert/assertContainsNotOnlyNullTest.php new file mode 100644 index 00000000000..61ca652ce87 --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsNotOnlyNullTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsNotOnlyNull')] +#[TestDox('assertContainsNotOnlyNull()')] +#[Small] +final class assertContainsNotOnlyNullTest extends TestCase +{ + #[DataProviderExternal(assertContainsOnlyNullTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsNotOnlyNull($haystack); + } + + #[DataProviderExternal(assertContainsOnlyNullTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsNotOnlyNull($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsNotOnlyNumericTest.php b/tests/unit/Framework/Assert/assertContainsNotOnlyNumericTest.php new file mode 100644 index 00000000000..e4d302a58dd --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsNotOnlyNumericTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsNotOnlyNumeric')] +#[TestDox('assertContainsNotOnlyNumeric()')] +#[Small] +final class assertContainsNotOnlyNumericTest extends TestCase +{ + #[DataProviderExternal(assertContainsOnlyNumericTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsNotOnlyNumeric($haystack); + } + + #[DataProviderExternal(assertContainsOnlyNumericTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsNotOnlyNumeric($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsNotOnlyObjectTest.php b/tests/unit/Framework/Assert/assertContainsNotOnlyObjectTest.php new file mode 100644 index 00000000000..6931f382686 --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsNotOnlyObjectTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsNotOnlyObject')] +#[TestDox('assertContainsNotOnlyObject()')] +#[Small] +final class assertContainsNotOnlyObjectTest extends TestCase +{ + #[DataProviderExternal(assertContainsOnlyObjectTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsNotOnlyObject($haystack); + } + + #[DataProviderExternal(assertContainsOnlyObjectTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsNotOnlyObject($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsNotOnlyResourceTest.php b/tests/unit/Framework/Assert/assertContainsNotOnlyResourceTest.php new file mode 100644 index 00000000000..075386d4c23 --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsNotOnlyResourceTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsNotOnlyResource')] +#[TestDox('assertContainsNotOnlyResource()')] +#[Small] +final class assertContainsNotOnlyResourceTest extends TestCase +{ + #[DataProviderExternal(assertContainsOnlyResourceTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsNotOnlyResource($haystack); + } + + #[DataProviderExternal(assertContainsOnlyResourceTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsNotOnlyResource($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsNotOnlyScalarTest.php b/tests/unit/Framework/Assert/assertContainsNotOnlyScalarTest.php new file mode 100644 index 00000000000..eb24151adc4 --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsNotOnlyScalarTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsNotOnlyScalar')] +#[TestDox('assertContainsNotOnlyScalar()')] +#[Small] +final class assertContainsNotOnlyScalarTest extends TestCase +{ + #[DataProviderExternal(assertContainsOnlyScalarTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsNotOnlyScalar($haystack); + } + + #[DataProviderExternal(assertContainsOnlyScalarTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsNotOnlyScalar($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsNotOnlyStringTest.php b/tests/unit/Framework/Assert/assertContainsNotOnlyStringTest.php new file mode 100644 index 00000000000..771bf0b092e --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsNotOnlyStringTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsNotOnlyString')] +#[TestDox('assertContainsNotOnlyString()')] +#[Small] +final class assertContainsNotOnlyStringTest extends TestCase +{ + #[DataProviderExternal(assertContainsOnlyStringTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsNotOnlyString($haystack); + } + + #[DataProviderExternal(assertContainsOnlyStringTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsNotOnlyString($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsOnlyArrayTest.php b/tests/unit/Framework/Assert/assertContainsOnlyArrayTest.php new file mode 100644 index 00000000000..adb56c27fdd --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsOnlyArrayTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsOnlyArray')] +#[TestDox('assertContainsOnlyArray()')] +#[Small] +final class assertContainsOnlyArrayTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [[[]]], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + [[null]], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsOnlyArray($haystack); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsOnlyArray($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsOnlyBoolTest.php b/tests/unit/Framework/Assert/assertContainsOnlyBoolTest.php new file mode 100644 index 00000000000..2ef525be2c1 --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsOnlyBoolTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsOnlyBool')] +#[TestDox('assertContainsOnlyBool()')] +#[Small] +final class assertContainsOnlyBoolTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [[true]], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + [[null]], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsOnlyBool($haystack); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsOnlyBool($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsOnlyCallableTest.php b/tests/unit/Framework/Assert/assertContainsOnlyCallableTest.php new file mode 100644 index 00000000000..0344fafbb26 --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsOnlyCallableTest.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsOnlyCallable')] +#[TestDox('assertContainsOnlyCallable()')] +#[Small] +final class assertContainsOnlyCallableTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + $callable = static function (): void + {}; + + return [ + [[$callable]], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + [[null]], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsOnlyCallable($haystack); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsOnlyCallable($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsOnlyClosedResourceTest.php b/tests/unit/Framework/Assert/assertContainsOnlyClosedResourceTest.php new file mode 100644 index 00000000000..2545d1997e2 --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsOnlyClosedResourceTest.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function fclose; +use function fopen; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsOnlyClosedResource')] +#[TestDox('assertContainsOnlyClosedResource()')] +#[Small] +final class assertContainsOnlyClosedResourceTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + $resource = fopen(__FILE__, 'r'); + + fclose($resource); + + return [ + [[$resource]], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + [[null]], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsOnlyClosedResource($haystack); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsOnlyClosedResource($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsOnlyFloatTest.php b/tests/unit/Framework/Assert/assertContainsOnlyFloatTest.php new file mode 100644 index 00000000000..3c51bc740a5 --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsOnlyFloatTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsOnlyFloat')] +#[TestDox('assertContainsOnlyFloat()')] +#[Small] +final class assertContainsOnlyFloatTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [[0.0]], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + [[null]], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsOnlyFloat($haystack); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsOnlyFloat($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsOnlyInstancesOfTest.php b/tests/unit/Framework/Assert/assertContainsOnlyInstancesOfTest.php new file mode 100644 index 00000000000..47a703aaf81 --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsOnlyInstancesOfTest.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use stdClass; + +#[CoversMethod(Assert::class, 'assertContainsOnlyInstancesOf')] +#[TestDox('assertContainsOnlyInstancesOf()')] +#[Small] +final class assertContainsOnlyInstancesOfTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [stdClass::class, [new stdClass]], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + [stdClass::class, [null]], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(string $type, iterable $haystack): void + { + $this->assertContainsOnlyInstancesOf($type, $haystack); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(string $type, iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsOnlyInstancesOf($type, $haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsOnlyIntTest.php b/tests/unit/Framework/Assert/assertContainsOnlyIntTest.php new file mode 100644 index 00000000000..e433ffb9b74 --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsOnlyIntTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsOnlyInt')] +#[TestDox('assertContainsOnlyInt()')] +#[Small] +final class assertContainsOnlyIntTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [[0]], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + [[null]], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsOnlyInt($haystack); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsOnlyInt($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsOnlyIterableTest.php b/tests/unit/Framework/Assert/assertContainsOnlyIterableTest.php new file mode 100644 index 00000000000..d959e0210d8 --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsOnlyIterableTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsOnlyIterable')] +#[TestDox('assertContainsOnlyIterable()')] +#[Small] +final class assertContainsOnlyIterableTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [[[]]], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + [[null]], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsOnlyIterable($haystack); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsOnlyIterable($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsOnlyNullTest.php b/tests/unit/Framework/Assert/assertContainsOnlyNullTest.php new file mode 100644 index 00000000000..6ddcf346b71 --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsOnlyNullTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsOnlyNull')] +#[TestDox('assertContainsOnlyNull()')] +#[Small] +final class assertContainsOnlyNullTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [[null]], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + [[true]], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsOnlyNull($haystack); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsOnlyNull($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsOnlyNumericTest.php b/tests/unit/Framework/Assert/assertContainsOnlyNumericTest.php new file mode 100644 index 00000000000..85d90e006b9 --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsOnlyNumericTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsOnlyNumeric')] +#[TestDox('assertContainsOnlyNumeric()')] +#[Small] +final class assertContainsOnlyNumericTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [['1.0']], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + [[null]], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsOnlyNumeric($haystack); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsOnlyNumeric($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsOnlyObjectTest.php b/tests/unit/Framework/Assert/assertContainsOnlyObjectTest.php new file mode 100644 index 00000000000..875a033881a --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsOnlyObjectTest.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use stdClass; + +#[CoversMethod(Assert::class, 'assertContainsOnlyObject')] +#[TestDox('assertContainsOnlyObject()')] +#[Small] +final class assertContainsOnlyObjectTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [[new stdClass]], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + [[null]], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsOnlyObject($haystack); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsOnlyObject($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsOnlyResourceTest.php b/tests/unit/Framework/Assert/assertContainsOnlyResourceTest.php new file mode 100644 index 00000000000..21ec16022e4 --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsOnlyResourceTest.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function fopen; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsOnlyResource')] +#[TestDox('assertContainsOnlyResource()')] +#[Small] +final class assertContainsOnlyResourceTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + $resource = fopen(__FILE__, 'r'); + + return [ + [[$resource]], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + [[null]], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsOnlyResource($haystack); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsOnlyResource($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsOnlyScalarTest.php b/tests/unit/Framework/Assert/assertContainsOnlyScalarTest.php new file mode 100644 index 00000000000..c5275fc1af3 --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsOnlyScalarTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsOnlyScalar')] +#[TestDox('assertContainsOnlyScalar()')] +#[Small] +final class assertContainsOnlyScalarTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [['string']], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + [[null]], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsOnlyScalar($haystack); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsOnlyScalar($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsOnlyStringTest.php b/tests/unit/Framework/Assert/assertContainsOnlyStringTest.php new file mode 100644 index 00000000000..1ea75ddc544 --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsOnlyStringTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertContainsOnlyString')] +#[TestDox('assertContainsOnlyString()')] +#[Small] +final class assertContainsOnlyStringTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [['string']], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + [[null]], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(iterable $haystack): void + { + $this->assertContainsOnlyString($haystack); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContainsOnlyString($haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertContainsTest.php b/tests/unit/Framework/Assert/assertContainsTest.php new file mode 100644 index 00000000000..c471f00084b --- /dev/null +++ b/tests/unit/Framework/Assert/assertContainsTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use stdClass; + +#[CoversMethod(Assert::class, 'assertContains')] +#[TestDox('assertContains()')] +#[Small] +final class assertContainsTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + $a = new stdClass; + + return [ + [0, [0]], + [0.0, [0.0]], + [false, [false]], + [null, [null]], + ['string', ['string']], + [['string'], [['string']]], + [$a, [$a]], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + [0, ['0']], + [0, [0.0]], + [0, [false]], + [0, [null]], + [new stdClass, [new stdClass]], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $needle, iterable $haystack): void + { + $this->assertContains($needle, $haystack); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $needle, iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertContains($needle, $haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertCountTest.php b/tests/unit/Framework/Assert/assertCountTest.php new file mode 100644 index 00000000000..ba1ab1cc847 --- /dev/null +++ b/tests/unit/Framework/Assert/assertCountTest.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function PHPUnit\TestFixture\Generator\f; +use ArrayIterator; +use Countable; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertCount')] +#[CoversClass(GeneratorNotSupportedException::class)] +#[TestDox('assertCount()')] +#[Small] +final class assertCountTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [2, [1, 2]], + [2, new ArrayIterator([1, 2])], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + [2, [1, 2, 3]], + [2, new ArrayIterator([1, 2, 3])], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(int $expectedCount, Countable|iterable $haystack): void + { + $this->assertCount($expectedCount, $haystack); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(int $expectedCount, Countable|iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertCount($expectedCount, $haystack); + } + + public function testDoesNotSupportGenerators(): void + { + $this->expectException(GeneratorNotSupportedException::class); + $this->expectExceptionMessage('Passing an argument of type Generator for the $haystack parameter is not supported'); + + $this->assertCount(0, f()); + } +} diff --git a/tests/unit/Framework/Assert/assertDirectoryDoesNotExistTest.php b/tests/unit/Framework/Assert/assertDirectoryDoesNotExistTest.php new file mode 100644 index 00000000000..1248bbb65c2 --- /dev/null +++ b/tests/unit/Framework/Assert/assertDirectoryDoesNotExistTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertDirectoryDoesNotExist')] +#[TestDox('assertDirectoryDoesNotExist()')] +#[Small] +final class assertDirectoryDoesNotExistTest extends TestCase +{ + #[DataProviderExternal(assertDirectoryExistsTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(string $directory): void + { + $this->assertDirectoryDoesNotExist($directory); + } + + #[DataProviderExternal(assertDirectoryExistsTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(string $directory): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertDirectoryDoesNotExist($directory); + } +} diff --git a/tests/unit/Framework/Assert/assertDirectoryExistsTest.php b/tests/unit/Framework/Assert/assertDirectoryExistsTest.php new file mode 100644 index 00000000000..9609f7b48fd --- /dev/null +++ b/tests/unit/Framework/Assert/assertDirectoryExistsTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertDirectoryExists')] +#[TestDox('assertDirectoryExists()')] +#[Small] +final class assertDirectoryExistsTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [__DIR__], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + [__DIR__ . '/DoesNotExist'], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(string $directory): void + { + $this->assertDirectoryExists($directory); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(string $directory): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertDirectoryExists($directory); + } +} diff --git a/tests/unit/Framework/Assert/assertDirectoryIsNotReadableTest.php b/tests/unit/Framework/Assert/assertDirectoryIsNotReadableTest.php new file mode 100644 index 00000000000..1a9a869480d --- /dev/null +++ b/tests/unit/Framework/Assert/assertDirectoryIsNotReadableTest.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const DIRECTORY_SEPARATOR; +use const PHP_OS_FAMILY; +use function mkdir; +use function octdec; +use function rmdir; +use function sys_get_temp_dir; +use function uniqid; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertDirectoryIsNotReadable')] +#[TestDox('assertDirectoryIsNotReadable()')] +#[Small] +final class assertDirectoryIsNotReadableTest extends TestCase +{ + private string $directory; + + protected function setUp(): void + { + if (PHP_OS_FAMILY === 'Windows') { + $this->markTestSkipped('Cannot test this behaviour on Windows'); + } + + $this->directory = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(__CLASS__ . '_', true); + } + + protected function tearDown(): void + { + if (!isset($this->directory)) { + return; + } + + @rmdir($this->directory); + } + + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + mkdir($this->directory, octdec('0')); + + $this->assertDirectoryIsNotReadable($this->directory); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertDirectoryIsNotReadable(__DIR__); + } +} diff --git a/tests/unit/Framework/Assert/assertDirectoryIsNotWritableTest.php b/tests/unit/Framework/Assert/assertDirectoryIsNotWritableTest.php new file mode 100644 index 00000000000..d7fd7de6a99 --- /dev/null +++ b/tests/unit/Framework/Assert/assertDirectoryIsNotWritableTest.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const DIRECTORY_SEPARATOR; +use const PHP_OS_FAMILY; +use function mkdir; +use function octdec; +use function rmdir; +use function sys_get_temp_dir; +use function uniqid; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertDirectoryIsNotWritable')] +#[TestDox('assertDirectoryIsNotWritable()')] +#[Small] +final class assertDirectoryIsNotWritableTest extends TestCase +{ + private string $directory; + + protected function setUp(): void + { + $this->directory = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(__CLASS__ . '_', true); + } + + protected function tearDown(): void + { + @rmdir($this->directory); + } + + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + if (PHP_OS_FAMILY === 'Windows') { + $this->markTestSkipped('Cannot test this behaviour on Windows'); + } + + mkdir($this->directory, octdec('0')); + + $this->assertDirectoryIsNotWritable($this->directory); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertDirectoryIsNotWritable(__DIR__); + } +} diff --git a/tests/unit/Framework/Assert/assertDirectoryIsReadableTest.php b/tests/unit/Framework/Assert/assertDirectoryIsReadableTest.php new file mode 100644 index 00000000000..3e41b1146f2 --- /dev/null +++ b/tests/unit/Framework/Assert/assertDirectoryIsReadableTest.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const DIRECTORY_SEPARATOR; +use const PHP_OS_FAMILY; +use function mkdir; +use function octdec; +use function rmdir; +use function sys_get_temp_dir; +use function uniqid; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertDirectoryIsReadable')] +#[TestDox('assertDirectoryIsReadable()')] +#[Small] +final class assertDirectoryIsReadableTest extends TestCase +{ + private string $directory; + + protected function setUp(): void + { + $this->directory = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(__CLASS__ . '_', true); + } + + protected function tearDown(): void + { + @rmdir($this->directory); + } + + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertDirectoryIsReadable(__DIR__); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + if (PHP_OS_FAMILY === 'Windows') { + $this->markTestSkipped('Cannot test this behaviour on Windows'); + } + + mkdir($this->directory, octdec('0')); + + $this->expectException(AssertionFailedError::class); + + $this->assertDirectoryIsReadable($this->directory); + } +} diff --git a/tests/unit/Framework/Assert/assertDirectoryIsWritableTest.php b/tests/unit/Framework/Assert/assertDirectoryIsWritableTest.php new file mode 100644 index 00000000000..9043913b132 --- /dev/null +++ b/tests/unit/Framework/Assert/assertDirectoryIsWritableTest.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const DIRECTORY_SEPARATOR; +use const PHP_OS_FAMILY; +use function mkdir; +use function octdec; +use function rmdir; +use function sys_get_temp_dir; +use function uniqid; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertDirectoryIsWritable')] +#[TestDox('assertDirectoryIsWritable()')] +#[Small] +final class assertDirectoryIsWritableTest extends TestCase +{ + private string $directory; + + protected function setUp(): void + { + $this->directory = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(__CLASS__ . '_', true); + } + + protected function tearDown(): void + { + @rmdir($this->directory); + } + + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertDirectoryIsWritable(__DIR__); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + if (PHP_OS_FAMILY === 'Windows') { + $this->markTestSkipped('Cannot test this behaviour on Windows'); + } + + mkdir($this->directory, octdec('0')); + + $this->expectException(AssertionFailedError::class); + + $this->assertDirectoryIsWritable($this->directory); + } +} diff --git a/tests/unit/Framework/Assert/assertDoesNotMatchRegularExpressionTest.php b/tests/unit/Framework/Assert/assertDoesNotMatchRegularExpressionTest.php new file mode 100644 index 00000000000..953facdba90 --- /dev/null +++ b/tests/unit/Framework/Assert/assertDoesNotMatchRegularExpressionTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertDoesNotMatchRegularExpression')] +#[TestDox('assertDoesNotMatchRegularExpression()')] +#[Small] +final class assertDoesNotMatchRegularExpressionTest extends TestCase +{ + #[DataProviderExternal(assertMatchesRegularExpressionTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(string $pattern, string $string): void + { + $this->assertDoesNotMatchRegularExpression($pattern, $string); + } + + #[DataProviderExternal(assertMatchesRegularExpressionTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(string $pattern, string $string): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertDoesNotMatchRegularExpression($pattern, $string); + } +} diff --git a/tests/unit/Framework/Assert/assertEmptyTest.php b/tests/unit/Framework/Assert/assertEmptyTest.php new file mode 100644 index 00000000000..f90fdf27b84 --- /dev/null +++ b/tests/unit/Framework/Assert/assertEmptyTest.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function PHPUnit\TestFixture\Generator\f; +use Countable; +use EmptyIterator; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertEmpty')] +#[CoversClass(GeneratorNotSupportedException::class)] +#[TestDox('assertEmpty()')] +#[Small] +final class assertEmptyTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [[]], + [''], + [null], + [false], + ['0'], + [0], + [new EmptyIterator], + [ + new class implements Countable + { + public function count(): int + { + return 0; + } + }, + ], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + [[0]], + [true], + ['1'], + [ + new class implements Countable + { + public function count(): int + { + return 1; + } + }, + ], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $actual): void + { + $this->assertEmpty($actual); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertEmpty($actual); + } + + public function testDoesNotSupportGenerators(): void + { + $this->expectException(GeneratorNotSupportedException::class); + $this->expectExceptionMessage('Passing an argument of type Generator for the $actual parameter is not supported'); + + $this->assertEmpty(f()); + } +} diff --git a/tests/unit/Framework/Assert/assertEqualsCanonicalizingTest.php b/tests/unit/Framework/Assert/assertEqualsCanonicalizingTest.php new file mode 100644 index 00000000000..6d6268b146d --- /dev/null +++ b/tests/unit/Framework/Assert/assertEqualsCanonicalizingTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertEqualsCanonicalizing')] +#[TestDox('assertEqualsCanonicalizing()')] +#[Small] +final class assertEqualsCanonicalizingTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [[3, 2, 1], [2, 3, 1]], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + [[3, 2, 1], [2, 3, 4]], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $expected, mixed $actual): void + { + $this->assertEqualsCanonicalizing($expected, $actual); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $expected, mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertEqualsCanonicalizing($expected, $actual); + } +} diff --git a/tests/unit/Framework/Assert/assertEqualsIgnoringCaseTest.php b/tests/unit/Framework/Assert/assertEqualsIgnoringCaseTest.php new file mode 100644 index 00000000000..6809fa5e64c --- /dev/null +++ b/tests/unit/Framework/Assert/assertEqualsIgnoringCaseTest.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertEqualsIgnoringCase')] +#[TestDox('assertEqualsIgnoringCase()')] +#[Small] +final class assertEqualsIgnoringCaseTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + ['a', 'A'], + [['a'], ['A']], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + ['a', 'B'], + [['a'], ['B']], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $expected, mixed $actual): void + { + $this->assertEqualsIgnoringCase($expected, $actual); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $expected, mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertEqualsIgnoringCase($expected, $actual); + } +} diff --git a/tests/unit/Framework/Assert/assertEqualsTest.php b/tests/unit/Framework/Assert/assertEqualsTest.php new file mode 100644 index 00000000000..d6ba5d660ab --- /dev/null +++ b/tests/unit/Framework/Assert/assertEqualsTest.php @@ -0,0 +1,300 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const NAN; +use function acos; +use function array_merge; +use function fopen; +use DateTimeImmutable; +use DateTimeZone; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\TestFixture\Author; +use PHPUnit\TestFixture\Book; +use PHPUnit\TestFixture\ClassWithToString; +use PHPUnit\TestFixture\SampleClass; +use PHPUnit\TestFixture\Struct; +use PHPUnit\Util\Xml\Loader as XmlLoader; +use SplObjectStorage; +use stdClass; + +#[CoversMethod(Assert::class, 'assertEquals')] +#[TestDox('assertEquals()')] +#[Small] +final class assertEqualsTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return array_merge(self::equalValues(), assertSameTest::sameValues()); + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return self::notEqualValues(); + } + + /** + * @return non-empty-list + */ + public static function equalValues(): array + { + // cyclic dependencies + $book1 = new Book; + $book1->author = new Author('Terry Pratchett'); + $book1->author->books[] = $book1; + $book2 = new Book; + $book2->author = new Author('Terry Pratchett'); + $book2->author->books[] = $book2; + + $object1 = new SampleClass(4, 8, 15); + $object2 = new SampleClass(4, 8, 15); + $storage1 = new SplObjectStorage; + $storage1->offsetSet($object1); + $storage2 = new SplObjectStorage; + $storage2->offsetSet($object1); + + return [ + // arrays + [['a' => 1, 'b' => 2], ['b' => 2, 'a' => 1]], + [[1], ['1']], + // objects + [$object1, $object2], + [$book1, $book2], + // SplObjectStorage + [$storage1, $storage2], + // DOMDocument + [ + (new XmlLoader)->load(''), + (new XmlLoader)->load(''), + ], + [ + (new XmlLoader)->load(''), + (new XmlLoader)->load(''), + ], + [ + (new XmlLoader)->load(''), + (new XmlLoader)->load(''), + ], + [ + (new XmlLoader)->load("\n \n"), + (new XmlLoader)->load(''), + ], + [ + new DateTimeImmutable('2013-03-29 04:13:35', new DateTimeZone('America/New_York')), + new DateTimeImmutable('2013-03-29 04:13:35', new DateTimeZone('America/New_York')), + ], + [ + new DateTimeImmutable('2013-03-29', new DateTimeZone('America/New_York')), + new DateTimeImmutable('2013-03-29', new DateTimeZone('America/New_York')), + ], + [ + new DateTimeImmutable('2013-03-29 04:13:35', new DateTimeZone('America/New_York')), + new DateTimeImmutable('2013-03-29 03:13:35', new DateTimeZone('America/Chicago')), + ], + [ + new DateTimeImmutable('2013-03-30', new DateTimeZone('America/New_York')), + new DateTimeImmutable('2013-03-29 23:00:00', new DateTimeZone('America/Chicago')), + ], + [ + new DateTimeImmutable('@1364616000'), + new DateTimeImmutable('2013-03-29 23:00:00', new DateTimeZone('America/Chicago')), + ], + [ + new DateTimeImmutable('2013-03-29T05:13:35-0500'), + new DateTimeImmutable('2013-03-29T04:13:35-0600'), + ], + // Exception + // array(new Exception('Exception 1'), new Exception('Exception 1')), + // mixed types + [0, '0'], + ['0', 0], + [2.3, '2.3'], + ['2.3', 2.3], + [1, 1.0], + [1.0, '1'], + [1 / 3, '0.3333333333333333'], + [1 - 2 / 3, '0.33333333333333337'], + [5.5E+123, '5.5E+123'], + [5.5E-123, '5.5E-123'], + ['string representation', new ClassWithToString], + [new ClassWithToString, 'string representation'], + ]; + } + + /** + * @return non-empty-list + */ + public static function notEqualValues(): array + { + // cyclic dependencies + $book1 = new Book; + $book1->author = new Author('Terry Pratchett'); + $book1->author->books[] = $book1; + $book2 = new Book; + $book2->author = new Author('Terry Pratch'); + $book2->author->books[] = $book2; + + $book3 = new Book; + $book3->author = 'Terry Pratchett'; + $book4 = new stdClass; + $book4->author = 'Terry Pratchett'; + + $object1 = new SampleClass(4, 8, 15); + $object2 = new SampleClass(16, 23, 42); + $object3 = new SampleClass(4, 8, 15); + $storage1 = new SplObjectStorage; + $storage1->offsetSet($object1); + $storage2 = new SplObjectStorage; + $storage2->offsetSet($object3); // same content, different object + + $file = TEST_FILES_PATH . 'foo.xml'; + + return [ + [true, false], + // strings + ['a', 'b'], + ['a', 'A'], + // https://github.com/sebastianbergmann/phpunit/issues/1023 + ['9E6666666', '9E7777777'], + // integers + [1, 2], + [2, 1], + // floats + [2.3, 4.2], + [2.3, 4.2], + [[2.3], [4.2]], + [[[2.3]], [[4.2]]], + [new Struct(2.3), new Struct(4.2)], + [[new Struct(2.3)], [new Struct(4.2)]], + [1 / 3, 1 - 2 / 3], + [1 / 3, '0.33333333333333337'], + [1 - 2 / 3, '3333333333333333'], + [5.5E+123, 5.6E+123], + [5.5E-123, 5.6E-123], + [5.5E+123, 5.5E-123], + // NAN + [NAN, NAN], + // arrays + [[], [0 => 1]], + [[0 => 1], []], + [[0 => null], []], + [[0 => 1, 1 => 2], [0 => 1, 1 => 3]], + [['a', 'b' => [1, 2]], ['a', 'b' => [2, 1]]], + // objects + [new SampleClass(4, 8, 15), new SampleClass(16, 23, 42)], + [$object1, $object2], + [$book1, $book2], + [$book3, $book4], // same content, different class + // resources + [fopen($file, 'r'), fopen($file, 'r')], + // SplObjectStorage + [$storage1, $storage2], + // DOMDocument + [ + (new XmlLoader)->load(''), + (new XmlLoader)->load(''), + ], + [ + (new XmlLoader)->load(''), + (new XmlLoader)->load(''), + ], + [ + (new XmlLoader)->load(' bar '), + (new XmlLoader)->load(''), + ], + [ + (new XmlLoader)->load(''), + (new XmlLoader)->load(''), + ], + [ + (new XmlLoader)->load(' bar '), + (new XmlLoader)->load(' bir '), + ], + [ + new DateTimeImmutable('2013-03-29 04:13:35', new DateTimeZone('America/New_York')), + new DateTimeImmutable('2013-03-29 03:13:35', new DateTimeZone('America/New_York')), + ], + [ + new DateTimeImmutable('2013-03-29 04:13:35', new DateTimeZone('America/New_York')), + new DateTimeImmutable('2013-03-29 03:13:35', new DateTimeZone('America/New_York')), + ], + [ + new DateTimeImmutable('2013-03-29 04:13:35', new DateTimeZone('America/New_York')), + new DateTimeImmutable('2013-03-29 05:13:35', new DateTimeZone('America/New_York')), + ], + [ + new DateTimeImmutable('2013-03-29', new DateTimeZone('America/New_York')), + new DateTimeImmutable('2013-03-30', new DateTimeZone('America/New_York')), + ], + [ + new DateTimeImmutable('2013-03-29', new DateTimeZone('America/New_York')), + new DateTimeImmutable('2013-03-30', new DateTimeZone('America/New_York')), + ], + [ + new DateTimeImmutable('2013-03-29 04:13:35', new DateTimeZone('America/New_York')), + new DateTimeImmutable('2013-03-29 04:13:35', new DateTimeZone('America/Chicago')), + ], + [ + new DateTimeImmutable('2013-03-29 04:13:35', new DateTimeZone('America/New_York')), + new DateTimeImmutable('2013-03-29 04:13:35', new DateTimeZone('America/Chicago')), + ], + [ + new DateTimeImmutable('2013-03-30', new DateTimeZone('America/New_York')), + new DateTimeImmutable('2013-03-30', new DateTimeZone('America/Chicago')), + ], + [ + new DateTimeImmutable('2013-03-29T05:13:35-0600'), + new DateTimeImmutable('2013-03-29T04:13:35-0600'), + ], + [ + new DateTimeImmutable('2013-03-29T05:13:35-0600'), + new DateTimeImmutable('2013-03-29T05:13:35-0500'), + ], + // Exception + // array(new Exception('Exception 1'), new Exception('Exception 2')), + // different types + [new SampleClass(4, 8, 15), false], + [false, new SampleClass(4, 8, 15)], + [[0 => 1, 1 => 2], false], + [false, [0 => 1, 1 => 2]], + [[], new stdClass], + [new stdClass, []], + // PHP: 0 == 'Foobar' => true! + // We want these values to differ + [0, 'Foobar'], + ['Foobar', 0], + [3, acos(8)], + [acos(8), 3], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $expected, mixed $actual): void + { + $this->assertEquals($expected, $actual); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $expected, mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertEquals($expected, $actual); + } +} diff --git a/tests/unit/Framework/Assert/assertEqualsWithDeltaTest.php b/tests/unit/Framework/Assert/assertEqualsWithDeltaTest.php new file mode 100644 index 00000000000..93efad122c0 --- /dev/null +++ b/tests/unit/Framework/Assert/assertEqualsWithDeltaTest.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertEqualsWithDelta')] +#[TestDox('assertEqualsWithDelta()')] +#[Small] +final class assertEqualsWithDeltaTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [2.3, 2.5, 0.5], + [[2.3], [2.5], 0.5], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + [2.3, 3.5, 0.5], + [[2.3], [3.5], 0.5], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $expected, mixed $actual, float $delta): void + { + $this->assertEqualsWithDelta($expected, $actual, $delta); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $expected, mixed $actual, float $delta): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertEqualsWithDelta($expected, $actual, $delta); + } +} diff --git a/tests/unit/Framework/Assert/assertFalseTest.php b/tests/unit/Framework/Assert/assertFalseTest.php new file mode 100644 index 00000000000..caea5451d7b --- /dev/null +++ b/tests/unit/Framework/Assert/assertFalseTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertFalse')] +#[TestDox('assertFalse()')] +#[Small] +final class assertFalseTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertFalse(false); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertFalse(true); + } +} diff --git a/tests/unit/Framework/Assert/assertFileDoesNotExistTest.php b/tests/unit/Framework/Assert/assertFileDoesNotExistTest.php new file mode 100644 index 00000000000..146bcd753e7 --- /dev/null +++ b/tests/unit/Framework/Assert/assertFileDoesNotExistTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertFileDoesNotExist')] +#[TestDox('assertFileDoesNotExist()')] +#[Small] +final class assertFileDoesNotExistTest extends TestCase +{ + #[DataProviderExternal(assertFileExistsTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(string $directory): void + { + $this->assertFileDoesNotExist($directory); + } + + #[DataProviderExternal(assertFileExistsTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(string $directory): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertFileDoesNotExist($directory); + } +} diff --git a/tests/unit/Framework/Assert/assertFileEqualsCanonicalizingTest.php b/tests/unit/Framework/Assert/assertFileEqualsCanonicalizingTest.php new file mode 100644 index 00000000000..64100d31c13 --- /dev/null +++ b/tests/unit/Framework/Assert/assertFileEqualsCanonicalizingTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertFileEqualsCanonicalizing')] +#[TestDox('assertFileEqualsCanonicalizing()')] +#[Small] +final class assertFileEqualsCanonicalizingTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertFileNotEquals(TEST_FILES_PATH . 'foo.txt', TEST_FILES_PATH . 'bar.txt'); + $this->assertFileEqualsCanonicalizing(TEST_FILES_PATH . 'foo.txt', TEST_FILES_PATH . 'foo.txt'); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertFileEqualsCanonicalizing(TEST_FILES_PATH . 'foo.txt', TEST_FILES_PATH . 'foo.xml'); + } +} diff --git a/tests/unit/Framework/Assert/assertFileEqualsIgnoringCaseTest.php b/tests/unit/Framework/Assert/assertFileEqualsIgnoringCaseTest.php new file mode 100644 index 00000000000..462914254de --- /dev/null +++ b/tests/unit/Framework/Assert/assertFileEqualsIgnoringCaseTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertFileEqualsIgnoringCase')] +#[TestDox('assertFileEqualsIgnoringCase()')] +#[Small] +final class assertFileEqualsIgnoringCaseTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertFileEqualsIgnoringCase( + TEST_FILES_PATH . 'foo.xml', + TEST_FILES_PATH . 'fooUppercase.xml', + ); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertFileEqualsIgnoringCase( + TEST_FILES_PATH . 'foo.xml', + TEST_FILES_PATH . 'bar.xml', + ); + } +} diff --git a/tests/unit/Framework/Assert/assertFileEqualsTest.php b/tests/unit/Framework/Assert/assertFileEqualsTest.php new file mode 100644 index 00000000000..2e1d3c36750 --- /dev/null +++ b/tests/unit/Framework/Assert/assertFileEqualsTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertFileEquals')] +#[TestDox('assertFileEquals()')] +#[Small] +final class assertFileEqualsTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertFileEquals( + TEST_FILES_PATH . 'foo.xml', + TEST_FILES_PATH . 'foo.xml', + ); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertFileEquals( + TEST_FILES_PATH . 'foo.xml', + TEST_FILES_PATH . 'bar.xml', + ); + } +} diff --git a/tests/unit/Framework/Assert/assertFileExistsTest.php b/tests/unit/Framework/Assert/assertFileExistsTest.php new file mode 100644 index 00000000000..17c67fb501b --- /dev/null +++ b/tests/unit/Framework/Assert/assertFileExistsTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertFileExists')] +#[TestDox('assertFileExists()')] +#[Small] +final class assertFileExistsTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [__FILE__], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + [__DIR__ . '/DoesNotExist'], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(string $filename): void + { + $this->assertFileExists($filename); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(string $filename): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertFileExists($filename); + } +} diff --git a/tests/unit/Framework/Assert/assertFileIsNotReadableTest.php b/tests/unit/Framework/Assert/assertFileIsNotReadableTest.php new file mode 100644 index 00000000000..4454b7aa3cc --- /dev/null +++ b/tests/unit/Framework/Assert/assertFileIsNotReadableTest.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const PHP_OS_FAMILY; +use function chmod; +use function octdec; +use function sys_get_temp_dir; +use function tempnam; +use function unlink; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertFileIsNotReadable')] +#[TestDox('assertFileIsNotReadable()')] +#[Small] +final class assertFileIsNotReadableTest extends TestCase +{ + private string $file; + + protected function setUp(): void + { + $this->file = tempnam(sys_get_temp_dir(), __CLASS__); + } + + protected function tearDown(): void + { + @unlink($this->file); + } + + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + if (PHP_OS_FAMILY === 'Windows') { + $this->markTestSkipped('Cannot test this behaviour on Windows'); + } + + chmod($this->file, octdec('0')); + + $this->assertFileIsNotReadable($this->file); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertFileIsNotReadable(__FILE__); + } +} diff --git a/tests/unit/Framework/Assert/assertFileIsNotWritableTest.php b/tests/unit/Framework/Assert/assertFileIsNotWritableTest.php new file mode 100644 index 00000000000..06977fd1e55 --- /dev/null +++ b/tests/unit/Framework/Assert/assertFileIsNotWritableTest.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const PHP_OS_FAMILY; +use function chmod; +use function octdec; +use function sys_get_temp_dir; +use function tempnam; +use function unlink; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertFileIsNotWritable')] +#[TestDox('assertFileIsNotWritable()')] +#[Small] +final class assertFileIsNotWritableTest extends TestCase +{ + private string $file; + + protected function setUp(): void + { + $this->file = tempnam(sys_get_temp_dir(), __CLASS__); + } + + protected function tearDown(): void + { + @unlink($this->file); + } + + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + if (PHP_OS_FAMILY === 'Windows') { + $this->markTestSkipped('Cannot test this behaviour on Windows'); + } + + chmod($this->file, octdec('0')); + + $this->assertFileIsNotWritable($this->file); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertFileIsNotWritable(__FILE__); + } +} diff --git a/tests/unit/Framework/Assert/assertFileIsReadableTest.php b/tests/unit/Framework/Assert/assertFileIsReadableTest.php new file mode 100644 index 00000000000..c01fae58a40 --- /dev/null +++ b/tests/unit/Framework/Assert/assertFileIsReadableTest.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const PHP_OS_FAMILY; +use function chmod; +use function octdec; +use function sys_get_temp_dir; +use function tempnam; +use function unlink; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertFileIsReadable')] +#[TestDox('assertFileIsReadable()')] +#[Small] +final class assertFileIsReadableTest extends TestCase +{ + private string $file; + + protected function setUp(): void + { + $this->file = tempnam(sys_get_temp_dir(), __CLASS__); + } + + protected function tearDown(): void + { + @unlink($this->file); + } + + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertFileIsReadable(__FILE__); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + if (PHP_OS_FAMILY === 'Windows') { + $this->markTestSkipped('Cannot test this behaviour on Windows'); + } + + chmod($this->file, octdec('0')); + + $this->expectException(AssertionFailedError::class); + + $this->assertFileIsReadable($this->file); + } +} diff --git a/tests/unit/Framework/Assert/assertFileIsWritableTest.php b/tests/unit/Framework/Assert/assertFileIsWritableTest.php new file mode 100644 index 00000000000..57bf35b75b4 --- /dev/null +++ b/tests/unit/Framework/Assert/assertFileIsWritableTest.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const PHP_OS_FAMILY; +use function chmod; +use function octdec; +use function sys_get_temp_dir; +use function tempnam; +use function unlink; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertFileIsWritable')] +#[TestDox('assertFileIsWritable()')] +#[Small] +final class assertFileIsWritableTest extends TestCase +{ + private string $file; + + protected function setUp(): void + { + $this->file = tempnam(sys_get_temp_dir(), __CLASS__); + } + + protected function tearDown(): void + { + @unlink($this->file); + } + + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertFileIsWritable(__FILE__); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + if (PHP_OS_FAMILY === 'Windows') { + $this->markTestSkipped('Cannot test this behaviour on Windows'); + } + + chmod($this->file, octdec('0')); + + $this->expectException(AssertionFailedError::class); + + $this->assertFileIsWritable($this->file); + } +} diff --git a/tests/unit/Framework/Assert/assertFileMatchesFormatFileTest.php b/tests/unit/Framework/Assert/assertFileMatchesFormatFileTest.php new file mode 100644 index 00000000000..d071bcc187d --- /dev/null +++ b/tests/unit/Framework/Assert/assertFileMatchesFormatFileTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertFileMatchesFormatFile')] +#[TestDox('assertFileMatchesFormatFile()')] +#[Small] +final class assertFileMatchesFormatFileTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertFileMatchesFormatFile( + TEST_FILES_PATH . 'expectedFileFormat.txt', + TEST_FILES_PATH . 'expectedFileFormat.txt', + ); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertFileMatchesFormatFile( + TEST_FILES_PATH . 'expectedFileFormat.txt', + TEST_FILES_PATH . 'actualFileFormat.txt', + ); + } +} diff --git a/tests/unit/Framework/Assert/assertFileMatchesFormatTest.php b/tests/unit/Framework/Assert/assertFileMatchesFormatTest.php new file mode 100644 index 00000000000..a92aeddb02b --- /dev/null +++ b/tests/unit/Framework/Assert/assertFileMatchesFormatTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertFileMatchesFormat')] +#[TestDox('assertFileMatchesFormat()')] +#[Small] +final class assertFileMatchesFormatTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertFileMatchesFormat("FOO\n", TEST_FILES_PATH . 'expectedFileFormat.txt'); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertFileMatchesFormat("BAR\n", TEST_FILES_PATH . 'expectedFileFormat.txt'); + } +} diff --git a/tests/unit/Framework/Assert/assertFileNotEqualsCanonicalizingTest.php b/tests/unit/Framework/Assert/assertFileNotEqualsCanonicalizingTest.php new file mode 100644 index 00000000000..4c93eb9e3a5 --- /dev/null +++ b/tests/unit/Framework/Assert/assertFileNotEqualsCanonicalizingTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertFileNotEqualsCanonicalizing')] +#[TestDox('assertFileNotEqualsCanonicalizing()')] +#[Small] +final class assertFileNotEqualsCanonicalizingTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertFileNotEqualsCanonicalizing( + TEST_FILES_PATH . 'foo.xml', + TEST_FILES_PATH . 'bar.xml', + ); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertFileNotEqualsCanonicalizing( + TEST_FILES_PATH . 'foo.xml', + TEST_FILES_PATH . 'foo.xml', + ); + } +} diff --git a/tests/unit/Framework/Assert/assertFileNotEqualsIgnoringCaseTest.php b/tests/unit/Framework/Assert/assertFileNotEqualsIgnoringCaseTest.php new file mode 100644 index 00000000000..84290d35eb9 --- /dev/null +++ b/tests/unit/Framework/Assert/assertFileNotEqualsIgnoringCaseTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertFileNotEqualsIgnoringCase')] +#[TestDox('assertFileNotEqualsIgnoringCase()')] +#[Small] +final class assertFileNotEqualsIgnoringCaseTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertFileNotEqualsIgnoringCase( + TEST_FILES_PATH . 'foo.xml', + TEST_FILES_PATH . 'bar.xml', + ); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertFileNotEqualsIgnoringCase( + TEST_FILES_PATH . 'foo.xml', + TEST_FILES_PATH . 'fooUppercase.xml', + ); + } +} diff --git a/tests/unit/Framework/Assert/assertFileNotEqualsTest.php b/tests/unit/Framework/Assert/assertFileNotEqualsTest.php new file mode 100644 index 00000000000..d56d641a84a --- /dev/null +++ b/tests/unit/Framework/Assert/assertFileNotEqualsTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertFileNotEquals')] +#[TestDox('assertFileNotEquals()')] +#[Small] +final class assertFileNotEqualsTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertFileNotEquals( + TEST_FILES_PATH . 'foo.xml', + TEST_FILES_PATH . 'bar.xml', + ); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertFileNotEquals( + TEST_FILES_PATH . 'foo.xml', + TEST_FILES_PATH . 'foo.xml', + ); + } +} diff --git a/tests/unit/Framework/Assert/assertFiniteTest.php b/tests/unit/Framework/Assert/assertFiniteTest.php new file mode 100644 index 00000000000..cce2db0fd5c --- /dev/null +++ b/tests/unit/Framework/Assert/assertFiniteTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const INF; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertFinite')] +#[TestDox('assertFinite()')] +#[Small] +final class assertFiniteTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertFinite(1); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertFinite(INF); + } +} diff --git a/tests/unit/Framework/Assert/assertGreaterThanOrEqualTest.php b/tests/unit/Framework/Assert/assertGreaterThanOrEqualTest.php new file mode 100644 index 00000000000..5eddd82f285 --- /dev/null +++ b/tests/unit/Framework/Assert/assertGreaterThanOrEqualTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertGreaterThanOrEqual')] +#[TestDox('assertGreaterThanOrEqual()')] +#[Small] +final class assertGreaterThanOrEqualTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertGreaterThanOrEqual(1, 2); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertGreaterThanOrEqual(2, 1); + } +} diff --git a/tests/unit/Framework/Assert/assertGreaterThanTest.php b/tests/unit/Framework/Assert/assertGreaterThanTest.php new file mode 100644 index 00000000000..b043e57883e --- /dev/null +++ b/tests/unit/Framework/Assert/assertGreaterThanTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertGreaterThan')] +#[TestDox('assertGreaterThan()')] +#[Small] +final class assertGreaterThanTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertGreaterThan(1, 2); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertGreaterThan(2, 1); + } +} diff --git a/tests/unit/Framework/Assert/assertInfiniteTest.php b/tests/unit/Framework/Assert/assertInfiniteTest.php new file mode 100644 index 00000000000..b96f99d83a6 --- /dev/null +++ b/tests/unit/Framework/Assert/assertInfiniteTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const INF; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertInfinite')] +#[TestDox('assertInfinite()')] +#[Small] +final class assertInfiniteTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertInfinite(INF); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertInfinite(1); + } +} diff --git a/tests/unit/Framework/Assert/assertInstanceOfTest.php b/tests/unit/Framework/Assert/assertInstanceOfTest.php new file mode 100644 index 00000000000..e48cc7a135e --- /dev/null +++ b/tests/unit/Framework/Assert/assertInstanceOfTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use stdClass; + +#[CoversMethod(Assert::class, 'assertInstanceOf')] +#[CoversClass(UnknownClassOrInterfaceException::class)] +#[TestDox('assertInstanceOf()')] +#[Small] +final class assertInstanceOfTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [stdClass::class, new stdClass], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + [self::class, new stdClass], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(string $expected, mixed $actual): void + { + $this->assertInstanceOf($expected, $actual); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(string $expected, mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertInstanceOf($expected, $actual); + } + + public function testDoesNotSupportUnknownTypes(): void + { + $this->expectException(UnknownClassOrInterfaceException::class); + $this->expectExceptionMessage('Class or interface "does-not-exist" does not exist'); + + $this->assertInstanceOf('does-not-exist', new stdClass); + } +} diff --git a/tests/unit/Framework/Assert/assertIsArrayTest.php b/tests/unit/Framework/Assert/assertIsArrayTest.php new file mode 100644 index 00000000000..8965b76ed07 --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsArrayTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function fclose; +use function fopen; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use stdClass; + +#[CoversMethod(Assert::class, 'assertIsArray')] +#[TestDox('assertIsArray()')] +#[Small] +final class assertIsArrayTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + $openResource = fopen(__FILE__, 'r'); + + $closedResource = fopen(__FILE__, 'r'); + fclose($closedResource); + + return [ + [true], + [0.0], + [0], + [null], + ['123'], + ['string'], + [new stdClass], + [$openResource], + [$closedResource], + ]; + } + + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertIsArray([]); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsArray($actual); + } +} diff --git a/tests/unit/Framework/Assert/assertIsBoolTest.php b/tests/unit/Framework/Assert/assertIsBoolTest.php new file mode 100644 index 00000000000..d68f0918782 --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsBoolTest.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function fclose; +use function fopen; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use stdClass; + +#[CoversMethod(Assert::class, 'assertIsBool')] +#[TestDox('assertIsBool()')] +#[Small] +final class assertIsBoolTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [true], + [false], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + $openResource = fopen(__FILE__, 'r'); + + $closedResource = fopen(__FILE__, 'r'); + fclose($closedResource); + + return [ + [[]], + [0.0], + [0], + [null], + ['123'], + ['string'], + [new stdClass], + [$openResource], + [$closedResource], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $actual): void + { + $this->assertIsBool($actual); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsBool($actual); + } +} diff --git a/tests/unit/Framework/Assert/assertIsCallableTest.php b/tests/unit/Framework/Assert/assertIsCallableTest.php new file mode 100644 index 00000000000..1d83cd06898 --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsCallableTest.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function fclose; +use function fopen; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use stdClass; + +#[CoversMethod(Assert::class, 'assertIsCallable')] +#[TestDox('assertIsCallable()')] +#[Small] +final class assertIsCallableTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [ + static function (): void + { + }, + ], + ['PHPUnit\Framework\assertIsCallable'], + [[Assert::class, 'assertIsCallable']], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + $openResource = fopen(__FILE__, 'r'); + + $closedResource = fopen(__FILE__, 'r'); + fclose($closedResource); + + return [ + [[]], + [true], + [0.0], + [0], + [null], + ['123'], + ['string'], + [new stdClass], + [$openResource], + [$closedResource], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $actual): void + { + $this->assertIsCallable($actual); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsCallable($actual); + } +} diff --git a/tests/unit/Framework/Assert/assertIsClosedResourceTest.php b/tests/unit/Framework/Assert/assertIsClosedResourceTest.php new file mode 100644 index 00000000000..b3554a2751f --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsClosedResourceTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function fclose; +use function fopen; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use stdClass; + +#[CoversMethod(Assert::class, 'assertIsClosedResource')] +#[TestDox('assertIsClosedResource()')] +#[Small] +final class assertIsClosedResourceTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + $openResource = fopen(__FILE__, 'r'); + + return [ + [[]], + [true], + [0.0], + [0], + [null], + ['123'], + ['string'], + [new stdClass], + [$openResource], + ]; + } + + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $closedResource = fopen(__FILE__, 'r'); + fclose($closedResource); + + $this->assertIsClosedResource($closedResource); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsClosedResource($actual); + } +} diff --git a/tests/unit/Framework/Assert/assertIsFloatTest.php b/tests/unit/Framework/Assert/assertIsFloatTest.php new file mode 100644 index 00000000000..d6ba3e3094d --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsFloatTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function fclose; +use function fopen; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use stdClass; + +#[CoversMethod(Assert::class, 'assertIsFloat')] +#[TestDox('assertIsFloat()')] +#[Small] +final class assertIsFloatTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + $openResource = fopen(__FILE__, 'r'); + + $closedResource = fopen(__FILE__, 'r'); + fclose($closedResource); + + return [ + [[]], + [true], + [0], + [null], + ['123'], + ['string'], + [new stdClass], + [$openResource], + [$closedResource], + ]; + } + + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertIsFloat(0.0); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsFloat($actual); + } +} diff --git a/tests/unit/Framework/Assert/assertIsIntTest.php b/tests/unit/Framework/Assert/assertIsIntTest.php new file mode 100644 index 00000000000..ef4978e4698 --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsIntTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function fclose; +use function fopen; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use stdClass; + +#[CoversMethod(Assert::class, 'assertIsInt')] +#[TestDox('assertIsInt()')] +#[Small] +final class assertIsIntTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + $openResource = fopen(__FILE__, 'r'); + + $closedResource = fopen(__FILE__, 'r'); + fclose($closedResource); + + return [ + [[]], + [true], + [0.0], + [null], + ['123'], + ['string'], + [new stdClass], + [$openResource], + [$closedResource], + ]; + } + + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertIsInt(123); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsInt($actual); + } +} diff --git a/tests/unit/Framework/Assert/assertIsIterableTest.php b/tests/unit/Framework/Assert/assertIsIterableTest.php new file mode 100644 index 00000000000..73374c8b496 --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsIterableTest.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function fclose; +use function fopen; +use ArrayIterator; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use stdClass; + +#[CoversMethod(Assert::class, 'assertIsIterable')] +#[TestDox('assertIsIterable()')] +#[Small] +final class assertIsIterableTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [[]], + [new ArrayIterator([])], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + $openResource = fopen(__FILE__, 'r'); + + $closedResource = fopen(__FILE__, 'r'); + fclose($closedResource); + + return [ + [true], + [0.0], + [0], + [null], + ['123'], + ['string'], + [new stdClass], + [$openResource], + [$closedResource], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $actual): void + { + $this->assertIsIterable($actual); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsIterable($actual); + } +} diff --git a/tests/unit/Framework/Assert/assertIsListTest.php b/tests/unit/Framework/Assert/assertIsListTest.php new file mode 100644 index 00000000000..1773154b5d9 --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsListTest.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function fclose; +use function fopen; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use stdClass; + +#[CoversMethod(Assert::class, 'assertIsList')] +#[TestDox('assertIsList()')] +#[Small] +final class assertIsListTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + $openResource = fopen(__FILE__, 'r'); + + $closedResource = fopen(__FILE__, 'r'); + fclose($closedResource); + + return [ + [['foo' => 'bar']], + [[1 => 'bar', 4 => 'baz']], + [true], + [0.0], + [0], + [null], + ['123'], + ['string'], + [new stdClass], + [$openResource], + [$closedResource], + ]; + } + + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertIsList([1, 2, 3]); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsList($actual); + } +} diff --git a/tests/unit/Framework/Assert/assertIsNotArrayTest.php b/tests/unit/Framework/Assert/assertIsNotArrayTest.php new file mode 100644 index 00000000000..d1c7fcd9074 --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsNotArrayTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertIsNotArray')] +#[TestDox('assertIsNotArray()')] +#[Small] +final class assertIsNotArrayTest extends TestCase +{ + #[DataProviderExternal(assertIsArrayTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $actual): void + { + $this->assertIsNotArray($actual); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsNotArray([]); + } +} diff --git a/tests/unit/Framework/Assert/assertIsNotBoolTest.php b/tests/unit/Framework/Assert/assertIsNotBoolTest.php new file mode 100644 index 00000000000..eab1d98e309 --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsNotBoolTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertIsNotBool')] +#[TestDox('assertIsNotBool()')] +#[Small] +final class assertIsNotBoolTest extends TestCase +{ + #[DataProviderExternal(assertIsBoolTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $actual): void + { + $this->assertIsNotBool($actual); + } + + #[DataProviderExternal(assertIsBoolTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsNotBool($actual); + } +} diff --git a/tests/unit/Framework/Assert/assertIsNotCallableTest.php b/tests/unit/Framework/Assert/assertIsNotCallableTest.php new file mode 100644 index 00000000000..718a8593592 --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsNotCallableTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertIsNotCallable')] +#[TestDox('assertIsNotCallable()')] +#[Small] +final class assertIsNotCallableTest extends TestCase +{ + #[DataProviderExternal(assertIsCallableTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $actual): void + { + $this->assertIsNotCallable($actual); + } + + #[DataProviderExternal(assertIsCallableTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsNotCallable($actual); + } +} diff --git a/tests/unit/Framework/Assert/assertIsNotClosedResourceTest.php b/tests/unit/Framework/Assert/assertIsNotClosedResourceTest.php new file mode 100644 index 00000000000..d49115aaf95 --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsNotClosedResourceTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function fclose; +use function fopen; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertIsNotClosedResource')] +#[TestDox('assertIsNotClosedResource()')] +#[Small] +final class assertIsNotClosedResourceTest extends TestCase +{ + #[DataProviderExternal(assertIsClosedResourceTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $actual): void + { + $this->assertIsNotClosedResource($actual); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $closedResource = fopen(__FILE__, 'r'); + fclose($closedResource); + + $this->expectException(AssertionFailedError::class); + + $this->assertIsNotClosedResource($closedResource); + } +} diff --git a/tests/unit/Framework/Assert/assertIsNotFloatTest.php b/tests/unit/Framework/Assert/assertIsNotFloatTest.php new file mode 100644 index 00000000000..1735512edab --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsNotFloatTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertIsNotFloat')] +#[TestDox('assertIsNotFloat()')] +#[Small] +final class assertIsNotFloatTest extends TestCase +{ + #[DataProviderExternal(assertIsFloatTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $actual): void + { + $this->assertIsNotFloat($actual); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsNotFloat(0.0); + } +} diff --git a/tests/unit/Framework/Assert/assertIsNotIntTest.php b/tests/unit/Framework/Assert/assertIsNotIntTest.php new file mode 100644 index 00000000000..3352a5aa5fb --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsNotIntTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertIsNotInt')] +#[TestDox('assertIsNotInt()')] +#[Small] +final class assertIsNotIntTest extends TestCase +{ + #[DataProviderExternal(assertIsIntTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $actual): void + { + $this->assertIsNotInt($actual); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsNotInt(0); + } +} diff --git a/tests/unit/Framework/Assert/assertIsNotIterableTest.php b/tests/unit/Framework/Assert/assertIsNotIterableTest.php new file mode 100644 index 00000000000..8b5beafeaef --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsNotIterableTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertIsNotIterable')] +#[TestDox('assertIsNotIterable()')] +#[Small] +final class assertIsNotIterableTest extends TestCase +{ + #[DataProviderExternal(assertIsIterableTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $actual): void + { + $this->assertIsNotIterable($actual); + } + + #[DataProviderExternal(assertIsIterableTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsNotIterable($actual); + } +} diff --git a/tests/unit/Framework/Assert/assertIsNotNumericTest.php b/tests/unit/Framework/Assert/assertIsNotNumericTest.php new file mode 100644 index 00000000000..f4ef42f4a43 --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsNotNumericTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertIsNotNumeric')] +#[TestDox('assertIsNotNumeric()')] +#[Small] +final class assertIsNotNumericTest extends TestCase +{ + #[DataProviderExternal(assertIsNumericTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $actual): void + { + $this->assertIsNotNumeric($actual); + } + + #[DataProviderExternal(assertIsNumericTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsNotNumeric($actual); + } +} diff --git a/tests/unit/Framework/Assert/assertIsNotObjectTest.php b/tests/unit/Framework/Assert/assertIsNotObjectTest.php new file mode 100644 index 00000000000..5684723311c --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsNotObjectTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use stdClass; + +#[CoversMethod(Assert::class, 'assertIsNotObject')] +#[TestDox('assertIsNotObject()')] +#[Small] +final class assertIsNotObjectTest extends TestCase +{ + #[DataProviderExternal(assertIsObjectTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $actual): void + { + $this->assertIsNotObject($actual); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsNotObject(new stdClass); + } +} diff --git a/tests/unit/Framework/Assert/assertIsNotReadableTest.php b/tests/unit/Framework/Assert/assertIsNotReadableTest.php new file mode 100644 index 00000000000..effc8d6fd5c --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsNotReadableTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const PHP_OS_FAMILY; +use function chmod; +use function mkdir; +use function octdec; +use function rmdir; +use function unlink; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertIsNotReadable')] +#[TestDox('assertIsNotReadable()')] +#[Small] +final class assertIsNotReadableTest extends TestCase +{ + private ?string $directory = null; + private ?string $file = null; + + protected function tearDown(): void + { + if ($this->directory !== null) { + rmdir($this->directory); + } + + if ($this->file !== null) { + unlink($this->file); + } + } + + #[DataProviderExternal(assertIsReadableTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(string $type, string $path): void + { + if (PHP_OS_FAMILY === 'Windows') { + $this->markTestSkipped('Cannot test this behaviour on Windows'); + } + + if ($type === 'directory') { + mkdir($path, octdec('0')); + + $this->directory = $path; + } else { + chmod($path, octdec('0')); + + $this->file = $path; + } + + $this->assertIsNotReadable($path); + } + + #[DataProviderExternal(assertIsReadableTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(string $type, string $path): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsNotReadable($path); + } +} diff --git a/tests/unit/Framework/Assert/assertIsNotResourceTest.php b/tests/unit/Framework/Assert/assertIsNotResourceTest.php new file mode 100644 index 00000000000..b25b4b8a5f3 --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsNotResourceTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertIsNotResource')] +#[TestDox('assertIsNotResource()')] +#[Small] +final class assertIsNotResourceTest extends TestCase +{ + #[DataProviderExternal(assertIsResourceTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $actual): void + { + $this->assertIsNotResource($actual); + } + + #[DataProviderExternal(assertIsResourceTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsNotResource($actual); + } +} diff --git a/tests/unit/Framework/Assert/assertIsNotScalarTest.php b/tests/unit/Framework/Assert/assertIsNotScalarTest.php new file mode 100644 index 00000000000..0a6e621e6a7 --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsNotScalarTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertIsNotScalar')] +#[TestDox('assertIsNotScalar()')] +#[Small] +final class assertIsNotScalarTest extends TestCase +{ + #[DataProviderExternal(assertIsScalarTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $actual): void + { + $this->assertIsNotScalar($actual); + } + + #[DataProviderExternal(assertIsScalarTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsNotScalar($actual); + } +} diff --git a/tests/unit/Framework/Assert/assertIsNotStringTest.php b/tests/unit/Framework/Assert/assertIsNotStringTest.php new file mode 100644 index 00000000000..5a457a7ec0a --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsNotStringTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertIsNotString')] +#[TestDox('assertIsNotString()')] +#[Small] +final class assertIsNotStringTest extends TestCase +{ + #[DataProviderExternal(assertIsStringTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $actual): void + { + $this->assertIsNotString($actual); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsNotString('string'); + } +} diff --git a/tests/unit/Framework/Assert/assertIsNotWritableTest.php b/tests/unit/Framework/Assert/assertIsNotWritableTest.php new file mode 100644 index 00000000000..ddb2ed508a5 --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsNotWritableTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const PHP_OS_FAMILY; +use function chmod; +use function mkdir; +use function octdec; +use function rmdir; +use function unlink; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertIsNotWritable')] +#[TestDox('assertIsNotWritable()')] +#[Small] +final class assertIsNotWritableTest extends TestCase +{ + private ?string $directory = null; + private ?string $file = null; + + protected function tearDown(): void + { + if ($this->directory !== null) { + rmdir($this->directory); + } + + if ($this->file !== null) { + unlink($this->file); + } + } + + #[DataProviderExternal(assertIsWritableTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(string $type, string $path): void + { + if (PHP_OS_FAMILY === 'Windows') { + $this->markTestSkipped('Cannot test this behaviour on Windows'); + } + + if ($type === 'directory') { + mkdir($path, octdec('0')); + + $this->directory = $path; + } else { + chmod($path, octdec('0')); + + $this->file = $path; + } + + $this->assertIsNotWritable($path); + } + + #[DataProviderExternal(assertIsWritableTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(string $type, string $path): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsNotWritable($path); + } +} diff --git a/tests/unit/Framework/Assert/assertIsNumericTest.php b/tests/unit/Framework/Assert/assertIsNumericTest.php new file mode 100644 index 00000000000..5ff666a3d2f --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsNumericTest.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function fclose; +use function fopen; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use stdClass; + +#[CoversMethod(Assert::class, 'assertIsNumeric')] +#[TestDox('assertIsNumeric()')] +#[Small] +final class assertIsNumericTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + ['123'], + ['123.456'], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + $openResource = fopen(__FILE__, 'r'); + + $closedResource = fopen(__FILE__, 'r'); + fclose($closedResource); + + return [ + [[]], + [true], + [null], + ['string'], + [new stdClass], + [$openResource], + [$closedResource], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $actual): void + { + $this->assertIsNumeric($actual); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsNumeric($actual); + } +} diff --git a/tests/unit/Framework/Assert/assertIsObjectTest.php b/tests/unit/Framework/Assert/assertIsObjectTest.php new file mode 100644 index 00000000000..f5f550440f5 --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsObjectTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function fclose; +use function fopen; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use stdClass; + +#[CoversMethod(Assert::class, 'assertIsObject')] +#[TestDox('assertIsObject()')] +#[Small] +final class assertIsObjectTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + $openResource = fopen(__FILE__, 'r'); + + $closedResource = fopen(__FILE__, 'r'); + fclose($closedResource); + + return [ + [[]], + [true], + [0.0], + [0], + [null], + ['123'], + ['string'], + [$openResource], + [$closedResource], + ]; + } + + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertIsObject(new stdClass); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsObject($actual); + } +} diff --git a/tests/unit/Framework/Assert/assertIsReadableTest.php b/tests/unit/Framework/Assert/assertIsReadableTest.php new file mode 100644 index 00000000000..3e278262308 --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsReadableTest.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const DIRECTORY_SEPARATOR; +use const PHP_OS_FAMILY; +use function chmod; +use function mkdir; +use function octdec; +use function rmdir; +use function sys_get_temp_dir; +use function tempnam; +use function uniqid; +use function unlink; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertIsReadable')] +#[TestDox('assertIsReadable()')] +#[Small] +final class assertIsReadableTest extends TestCase +{ + private ?string $directory = null; + private ?string $file = null; + + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + ['directory', __DIR__], + ['file', __FILE__], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + ['directory', sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(__CLASS__ . '_', true)], + ['file', tempnam(sys_get_temp_dir(), __CLASS__)], + ]; + } + + protected function tearDown(): void + { + if ($this->directory !== null) { + rmdir($this->directory); + } + + if ($this->file !== null) { + unlink($this->file); + } + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(string $type, string $path): void + { + $this->assertIsReadable($path); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(string $type, string $path): void + { + if (PHP_OS_FAMILY === 'Windows') { + $this->markTestSkipped('Cannot test this behaviour on Windows'); + } + + if ($type === 'directory') { + mkdir($path, octdec('0')); + + $this->directory = $path; + } else { + chmod($path, octdec('0')); + + $this->file = $path; + } + + $this->expectException(AssertionFailedError::class); + + $this->assertIsReadable($path); + } +} diff --git a/tests/unit/Framework/Assert/assertIsResourceTest.php b/tests/unit/Framework/Assert/assertIsResourceTest.php new file mode 100644 index 00000000000..37e475debb3 --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsResourceTest.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function fclose; +use function fopen; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use stdClass; + +#[CoversMethod(Assert::class, 'assertIsResource')] +#[TestDox('assertIsResource()')] +#[Small] +final class assertIsResourceTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + $openResource = fopen(__FILE__, 'r'); + + $closedResource = fopen(__FILE__, 'r'); + fclose($closedResource); + + return [ + [$openResource], + [$closedResource], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + [true], + [0.0], + [0], + [null], + ['123'], + ['string'], + [new stdClass], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $actual): void + { + $this->assertIsResource($actual); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsResource($actual); + } +} diff --git a/tests/unit/Framework/Assert/assertIsScalarTest.php b/tests/unit/Framework/Assert/assertIsScalarTest.php new file mode 100644 index 00000000000..4d3164c2752 --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsScalarTest.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function fclose; +use function fopen; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use stdClass; + +#[CoversMethod(Assert::class, 'assertIsScalar')] +#[TestDox('assertIsScalar()')] +#[Small] +final class assertIsScalarTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [true], + [false], + [0], + [0.0], + ['string'], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + $openResource = fopen(__FILE__, 'r'); + + $closedResource = fopen(__FILE__, 'r'); + fclose($closedResource); + + return [ + [[]], + [null], + [new stdClass], + [$openResource], + [$closedResource], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $actual): void + { + $this->assertIsScalar($actual); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsScalar($actual); + } +} diff --git a/tests/unit/Framework/Assert/assertIsStringTest.php b/tests/unit/Framework/Assert/assertIsStringTest.php new file mode 100644 index 00000000000..fb9ef44a051 --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsStringTest.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function fclose; +use function fopen; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use stdClass; + +#[CoversMethod(Assert::class, 'assertIsString')] +#[TestDox('assertIsString()')] +#[Small] +final class assertIsStringTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + $openResource = fopen(__FILE__, 'r'); + + $closedResource = fopen(__FILE__, 'r'); + fclose($closedResource); + + return [ + [[]], + [true], + [0.0], + [0], + [null], + [new stdClass], + [$openResource], + [$closedResource], + ]; + } + + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertIsString('string'); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertIsString($actual); + } +} diff --git a/tests/unit/Framework/Assert/assertIsWritableTest.php b/tests/unit/Framework/Assert/assertIsWritableTest.php new file mode 100644 index 00000000000..00e27b3db29 --- /dev/null +++ b/tests/unit/Framework/Assert/assertIsWritableTest.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const DIRECTORY_SEPARATOR; +use const PHP_OS_FAMILY; +use function chmod; +use function mkdir; +use function octdec; +use function rmdir; +use function sys_get_temp_dir; +use function tempnam; +use function uniqid; +use function unlink; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertIsWritable')] +#[TestDox('assertIsWritable()')] +#[Small] +final class assertIsWritableTest extends TestCase +{ + private ?string $directory = null; + private ?string $file = null; + + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + ['directory', __DIR__], + ['file', __FILE__], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + ['directory', sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(__CLASS__ . '_', true)], + ['file', tempnam(sys_get_temp_dir(), __CLASS__)], + ]; + } + + protected function tearDown(): void + { + if ($this->directory !== null) { + rmdir($this->directory); + } + + if ($this->file !== null) { + unlink($this->file); + } + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(string $type, string $path): void + { + $this->assertIsWritable($path); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(string $type, string $path): void + { + if (PHP_OS_FAMILY === 'Windows') { + $this->markTestSkipped('Cannot test this behaviour on Windows'); + } + + if ($type === 'directory') { + mkdir($path, octdec('0')); + + $this->directory = $path; + } else { + chmod($path, octdec('0')); + + $this->file = $path; + } + + $this->expectException(AssertionFailedError::class); + + $this->assertIsWritable($path); + } +} diff --git a/tests/unit/Framework/Assert/assertJsonFileEqualsJsonFileTest.php b/tests/unit/Framework/Assert/assertJsonFileEqualsJsonFileTest.php new file mode 100644 index 00000000000..ef4c485ee76 --- /dev/null +++ b/tests/unit/Framework/Assert/assertJsonFileEqualsJsonFileTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertJsonFileEqualsJsonFile')] +#[TestDox('assertJsonFileEqualsJsonFile()')] +#[Small] +final class assertJsonFileEqualsJsonFileTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertJsonFileEqualsJsonFile( + TEST_FILES_PATH . 'JsonData/simpleObject.json', + TEST_FILES_PATH . 'JsonData/simpleObject.json', + ); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertJsonFileEqualsJsonFile( + TEST_FILES_PATH . 'JsonData/arrayObject.json', + TEST_FILES_PATH . 'JsonData/simpleObject.json', + ); + } +} diff --git a/tests/unit/Framework/Assert/assertJsonFileNotEqualsJsonFileTest.php b/tests/unit/Framework/Assert/assertJsonFileNotEqualsJsonFileTest.php new file mode 100644 index 00000000000..a9dc84a1a2f --- /dev/null +++ b/tests/unit/Framework/Assert/assertJsonFileNotEqualsJsonFileTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertJsonFileNotEqualsJsonFile')] +#[TestDox('assertJsonFileNotEqualsJsonFile()')] +#[Small] +final class assertJsonFileNotEqualsJsonFileTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertJsonFileNotEqualsJsonFile( + TEST_FILES_PATH . 'JsonData/arrayObject.json', + TEST_FILES_PATH . 'JsonData/simpleObject.json', + ); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertJsonFileNotEqualsJsonFile( + TEST_FILES_PATH . 'JsonData/simpleObject.json', + TEST_FILES_PATH . 'JsonData/simpleObject.json', + ); + } +} diff --git a/tests/unit/Framework/Assert/assertJsonStringEqualsJsonFileTest.php b/tests/unit/Framework/Assert/assertJsonStringEqualsJsonFileTest.php new file mode 100644 index 00000000000..90df937a830 --- /dev/null +++ b/tests/unit/Framework/Assert/assertJsonStringEqualsJsonFileTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function json_encode; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertJsonStringEqualsJsonFile')] +#[TestDox('assertJsonStringEqualsJsonFile()')] +#[Small] +final class assertJsonStringEqualsJsonFileTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertJsonStringEqualsJsonFile( + TEST_FILES_PATH . 'JsonData/simpleObject.json', + json_encode(['Mascott' => 'Tux']), + ); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertJsonStringEqualsJsonFile( + TEST_FILES_PATH . 'JsonData/arrayObject.json', + json_encode(['Mascott' => 'Tux']), + ); + } +} diff --git a/tests/unit/Framework/Assert/assertJsonStringEqualsJsonStringTest.php b/tests/unit/Framework/Assert/assertJsonStringEqualsJsonStringTest.php new file mode 100644 index 00000000000..9c165b1d8e9 --- /dev/null +++ b/tests/unit/Framework/Assert/assertJsonStringEqualsJsonStringTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertJsonStringEqualsJsonString')] +#[TestDox('assertJsonStringEqualsJsonString()')] +#[Small] +final class assertJsonStringEqualsJsonStringTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + ['{"Mascot" : "elePHPant"}', '{"Mascot" : "elePHPant"}'], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + ['{"Mascot" : "elePHPant"}', '{"Mascot" : "Tux"}'], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(string $expectedJson, string $actualJson): void + { + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(string $expectedJson, string $actualJson): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson); + } +} diff --git a/tests/unit/Framework/Assert/assertJsonStringNotEqualsJsonFileTest.php b/tests/unit/Framework/Assert/assertJsonStringNotEqualsJsonFileTest.php new file mode 100644 index 00000000000..e24fcd0baa3 --- /dev/null +++ b/tests/unit/Framework/Assert/assertJsonStringNotEqualsJsonFileTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function json_encode; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertJsonStringNotEqualsJsonFile')] +#[TestDox('assertJsonStringNotEqualsJsonFile()')] +#[Small] +final class assertJsonStringNotEqualsJsonFileTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertJsonStringNotEqualsJsonFile( + TEST_FILES_PATH . 'JsonData/arrayObject.json', + json_encode(['Mascott' => 'Tux']), + ); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertJsonStringNotEqualsJsonFile( + TEST_FILES_PATH . 'JsonData/simpleObject.json', + json_encode(['Mascott' => 'Tux']), + ); + } +} diff --git a/tests/unit/Framework/Assert/assertJsonStringNotEqualsJsonStringTest.php b/tests/unit/Framework/Assert/assertJsonStringNotEqualsJsonStringTest.php new file mode 100644 index 00000000000..58014817069 --- /dev/null +++ b/tests/unit/Framework/Assert/assertJsonStringNotEqualsJsonStringTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertJsonStringNotEqualsJsonString')] +#[TestDox('assertJsonStringNotEqualsJsonString()')] +#[Small] +final class assertJsonStringNotEqualsJsonStringTest extends TestCase +{ + #[DataProviderExternal(assertJsonStringEqualsJsonStringTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(string $expectedJson, string $actualJson): void + { + $this->assertJsonStringNotEqualsJsonString($expectedJson, $actualJson); + } + + #[DataProviderExternal(assertJsonStringEqualsJsonStringTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(string $expectedJson, string $actualJson): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertJsonStringNotEqualsJsonString($expectedJson, $actualJson); + } +} diff --git a/tests/unit/Framework/Assert/assertJsonTest.php b/tests/unit/Framework/Assert/assertJsonTest.php new file mode 100644 index 00000000000..eff3daec61c --- /dev/null +++ b/tests/unit/Framework/Assert/assertJsonTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertJson')] +#[TestDox('assertJson()')] +#[Small] +final class assertJsonTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertJson('{}'); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertJson(''); + } +} diff --git a/tests/unit/Framework/Assert/assertLessThanOrEqualTest.php b/tests/unit/Framework/Assert/assertLessThanOrEqualTest.php new file mode 100644 index 00000000000..faa7e1dbd12 --- /dev/null +++ b/tests/unit/Framework/Assert/assertLessThanOrEqualTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertLessThanOrEqual')] +#[TestDox('assertLessThanOrEqual()')] +#[Small] +final class assertLessThanOrEqualTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertLessThanOrEqual(2, 1); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertLessThanOrEqual(1, 2); + } +} diff --git a/tests/unit/Framework/Assert/assertLessThanTest.php b/tests/unit/Framework/Assert/assertLessThanTest.php new file mode 100644 index 00000000000..ab46dd699d2 --- /dev/null +++ b/tests/unit/Framework/Assert/assertLessThanTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertLessThan')] +#[TestDox('assertLessThan()')] +#[Small] +final class assertLessThanTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertLessThan(2, 1); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertLessThan(1, 2); + } +} diff --git a/tests/unit/Framework/Assert/assertMatchesRegularExpressionTest.php b/tests/unit/Framework/Assert/assertMatchesRegularExpressionTest.php new file mode 100644 index 00000000000..e38ebba7efe --- /dev/null +++ b/tests/unit/Framework/Assert/assertMatchesRegularExpressionTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertMatchesRegularExpression')] +#[TestDox('assertMatchesRegularExpression()')] +#[Small] +final class assertMatchesRegularExpressionTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + ['/foo/', 'foobar'], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + ['/foo/', 'bar'], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(string $pattern, string $string): void + { + $this->assertMatchesRegularExpression($pattern, $string); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(string $pattern, string $string): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertMatchesRegularExpression($pattern, $string); + } +} diff --git a/tests/unit/Framework/Assert/assertNanTest.php b/tests/unit/Framework/Assert/assertNanTest.php new file mode 100644 index 00000000000..663d450daab --- /dev/null +++ b/tests/unit/Framework/Assert/assertNanTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const NAN; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertNan')] +#[TestDox('assertNan()')] +#[Small] +final class assertNanTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertNan(NAN); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertNan(1); + } +} diff --git a/tests/unit/Framework/Assert/assertNotContainsEqualsTest.php b/tests/unit/Framework/Assert/assertNotContainsEqualsTest.php new file mode 100644 index 00000000000..a3cd59ea5d9 --- /dev/null +++ b/tests/unit/Framework/Assert/assertNotContainsEqualsTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertNotContainsEquals')] +#[TestDox('assertNotContainsEquals()')] +#[Small] +final class assertNotContainsEqualsTest extends TestCase +{ + #[DataProviderExternal(assertContainsEqualsTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $needle, iterable $haystack): void + { + $this->assertNotContainsEquals($needle, $haystack); + } + + #[DataProviderExternal(assertContainsEqualsTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $needle, iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertNotContainsEquals($needle, $haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertNotContainsTest.php b/tests/unit/Framework/Assert/assertNotContainsTest.php new file mode 100644 index 00000000000..5db7c369e77 --- /dev/null +++ b/tests/unit/Framework/Assert/assertNotContainsTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertNotContains')] +#[TestDox('assertNotContains()')] +#[Small] +final class assertNotContainsTest extends TestCase +{ + #[DataProviderExternal(assertContainsTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $needle, iterable $haystack): void + { + $this->assertNotContains($needle, $haystack); + } + + #[DataProviderExternal(assertContainsTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $needle, iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertNotContains($needle, $haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertNotCountTest.php b/tests/unit/Framework/Assert/assertNotCountTest.php new file mode 100644 index 00000000000..fa44c0ef5e1 --- /dev/null +++ b/tests/unit/Framework/Assert/assertNotCountTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function PHPUnit\TestFixture\Generator\f; +use Countable; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertNotCount')] +#[CoversClass(GeneratorNotSupportedException::class)] +#[TestDox('assertNotCount()')] +#[Small] +final class assertNotCountTest extends TestCase +{ + #[DataProviderExternal(assertCountTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(int $expectedCount, Countable|iterable $haystack): void + { + $this->assertNotCount($expectedCount, $haystack); + } + + #[DataProviderExternal(assertCountTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(int $expectedCount, Countable|iterable $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertNotCount($expectedCount, $haystack); + } + + public function testDoesNotSupportGenerators(): void + { + $this->expectException(GeneratorNotSupportedException::class); + $this->expectExceptionMessage('Passing an argument of type Generator for the $haystack parameter is not supported'); + + $this->assertNotCount(0, f()); + } +} diff --git a/tests/unit/Framework/Assert/assertNotEmptyTest.php b/tests/unit/Framework/Assert/assertNotEmptyTest.php new file mode 100644 index 00000000000..c2be7c81c49 --- /dev/null +++ b/tests/unit/Framework/Assert/assertNotEmptyTest.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function PHPUnit\TestFixture\Generator\f; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertNotEmpty')] +#[CoversClass(GeneratorNotSupportedException::class)] +#[TestDox('assertNotEmpty()')] +#[Small] +final class assertNotEmptyTest extends TestCase +{ + #[DataProviderExternal(assertEmptyTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $actual): void + { + $this->assertNotEmpty($actual); + } + + #[DataProviderExternal(assertEmptyTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertNotEmpty($actual); + } + + public function testDoesNotSupportGenerators(): void + { + $this->expectException(GeneratorNotSupportedException::class); + $this->expectExceptionMessage('Passing an argument of type Generator for the $actual parameter is not supported'); + + $this->assertNotEmpty(f()); + } +} diff --git a/tests/unit/Framework/Assert/assertNotEqualsCanonicalizingTest.php b/tests/unit/Framework/Assert/assertNotEqualsCanonicalizingTest.php new file mode 100644 index 00000000000..534a9d62e04 --- /dev/null +++ b/tests/unit/Framework/Assert/assertNotEqualsCanonicalizingTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertNotEqualsCanonicalizing')] +#[TestDox('assertNotEqualsCanonicalizing()')] +#[Small] +final class assertNotEqualsCanonicalizingTest extends TestCase +{ + #[DataProviderExternal(assertEqualsCanonicalizingTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $expected, mixed $actual): void + { + $this->assertNotEqualsCanonicalizing($expected, $actual); + } + + #[DataProviderExternal(assertEqualsCanonicalizingTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $expected, mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertNotEqualsCanonicalizing($expected, $actual); + } +} diff --git a/tests/unit/Framework/Assert/assertNotEqualsIgnoringCaseTest.php b/tests/unit/Framework/Assert/assertNotEqualsIgnoringCaseTest.php new file mode 100644 index 00000000000..896515b3dd1 --- /dev/null +++ b/tests/unit/Framework/Assert/assertNotEqualsIgnoringCaseTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertNotEqualsIgnoringCase')] +#[TestDox('assertNotEqualsIgnoringCase()')] +#[Small] +final class assertNotEqualsIgnoringCaseTest extends TestCase +{ + #[DataProviderExternal(assertEqualsIgnoringCaseTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $expected, mixed $actual): void + { + $this->assertNotEqualsIgnoringCase($expected, $actual); + } + + #[DataProviderExternal(assertEqualsIgnoringCaseTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $expected, mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertNotEqualsIgnoringCase($expected, $actual); + } +} diff --git a/tests/unit/Framework/Assert/assertNotEqualsTest.php b/tests/unit/Framework/Assert/assertNotEqualsTest.php new file mode 100644 index 00000000000..051eddf297b --- /dev/null +++ b/tests/unit/Framework/Assert/assertNotEqualsTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertNotEquals')] +#[TestDox('assertNotEquals()')] +#[Small] +final class assertNotEqualsTest extends TestCase +{ + #[DataProviderExternal(assertEqualsTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $expected, mixed $actual): void + { + $this->assertNotEquals($expected, $actual); + } + + #[DataProviderExternal(assertEqualsTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $expected, mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertNotEquals($expected, $actual); + } +} diff --git a/tests/unit/Framework/Assert/assertNotEqualsWithDeltaTest.php b/tests/unit/Framework/Assert/assertNotEqualsWithDeltaTest.php new file mode 100644 index 00000000000..0b7b5026f58 --- /dev/null +++ b/tests/unit/Framework/Assert/assertNotEqualsWithDeltaTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertNotEqualsWithDelta')] +#[TestDox('assertNotEqualsWithDelta()')] +#[Small] +final class assertNotEqualsWithDeltaTest extends TestCase +{ + #[DataProviderExternal(assertEqualsWithDeltaTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $expected, mixed $actual, float $delta): void + { + $this->assertNotEqualsWithDelta($expected, $actual, $delta); + } + + #[DataProviderExternal(assertEqualsWithDeltaTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $expected, mixed $actual, float $delta): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertNotEqualsWithDelta($expected, $actual, $delta); + } +} diff --git a/tests/unit/Framework/Assert/assertNotFalseTest.php b/tests/unit/Framework/Assert/assertNotFalseTest.php new file mode 100644 index 00000000000..e1c8630f9c3 --- /dev/null +++ b/tests/unit/Framework/Assert/assertNotFalseTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertNotFalse')] +#[TestDox('assertNotFalse()')] +#[Small] +final class assertNotFalseTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [true], + [null], + [0], + [1], + ['false'], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $actual): void + { + $this->assertNotFalse($actual); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertNotFalse(false); + } +} diff --git a/tests/unit/Framework/Assert/assertNotInstanceOfTest.php b/tests/unit/Framework/Assert/assertNotInstanceOfTest.php new file mode 100644 index 00000000000..15444700e77 --- /dev/null +++ b/tests/unit/Framework/Assert/assertNotInstanceOfTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use stdClass; + +#[CoversMethod(Assert::class, 'assertNotInstanceOf')] +#[TestDox('assertNotInstanceOf()')] +#[Small] +final class assertNotInstanceOfTest extends TestCase +{ + #[DataProviderExternal(assertInstanceOfTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(string $expected, mixed $actual): void + { + $this->assertNotInstanceOf($expected, $actual); + } + + #[DataProviderExternal(assertInstanceOfTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(string $expected, mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertNotInstanceOf($expected, $actual); + } + + public function testDoesNotSupportUnknownTypes(): void + { + $this->expectException(UnknownClassOrInterfaceException::class); + $this->expectExceptionMessage('Class or interface "does-not-exist" does not exist'); + + $this->assertNotInstanceOf('does-not-exist', new stdClass); + } +} diff --git a/tests/unit/Framework/Assert/assertNotNullTest.php b/tests/unit/Framework/Assert/assertNotNullTest.php new file mode 100644 index 00000000000..bdfd9961252 --- /dev/null +++ b/tests/unit/Framework/Assert/assertNotNullTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertNotNull')] +#[TestDox('assertNotNull()')] +#[Small] +final class assertNotNullTest extends TestCase +{ + #[DataProviderExternal(assertNullTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $actual): void + { + $this->assertNotNull($actual); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertNotNull(null); + } +} diff --git a/tests/unit/Framework/Assert/assertNotSameSizeTest.php b/tests/unit/Framework/Assert/assertNotSameSizeTest.php new file mode 100644 index 00000000000..15c8da95874 --- /dev/null +++ b/tests/unit/Framework/Assert/assertNotSameSizeTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use Countable; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertNotSameSize')] +#[TestDox('assertNotSameSize()')] +#[Small] +final class assertNotSameSizeTest extends TestCase +{ + #[DataProviderExternal(assertSameSizeTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(Countable|iterable $expected, Countable|iterable $actual): void + { + $this->assertNotSameSize($expected, $actual); + } + + #[DataProviderExternal(assertSameSizeTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(Countable|iterable $expected, Countable|iterable $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertNotSameSize($expected, $actual); + } + + #[DataProviderExternal(assertSameSizeTest::class, 'errorProvider')] + public function testDoesNotSupportGenerators(Countable|iterable $expected, Countable|iterable $actual): void + { + $this->expectException(GeneratorNotSupportedException::class); + + $this->assertNotSameSize($expected, $actual); + } +} diff --git a/tests/unit/Framework/Assert/assertNotSameTest.php b/tests/unit/Framework/Assert/assertNotSameTest.php new file mode 100644 index 00000000000..46e38a1c3ad --- /dev/null +++ b/tests/unit/Framework/Assert/assertNotSameTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertNotSame')] +#[TestDox('assertNotSame()')] +#[Small] +final class assertNotSameTest extends TestCase +{ + #[DataProviderExternal(assertSameTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $expected, mixed $actual): void + { + $this->assertNotSame($expected, $actual); + } + + #[DataProviderExternal(assertSameTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $expected, mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertNotSame($expected, $actual); + } +} diff --git a/tests/unit/Framework/Assert/assertNotTrueTest.php b/tests/unit/Framework/Assert/assertNotTrueTest.php new file mode 100644 index 00000000000..8a701859143 --- /dev/null +++ b/tests/unit/Framework/Assert/assertNotTrueTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertNotTrue')] +#[TestDox('assertNotTrue()')] +#[Small] +final class assertNotTrueTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [false], + [null], + [0], + [1], + ['true'], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $actual): void + { + $this->assertNotTrue($actual); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertNotTrue(true); + } +} diff --git a/tests/unit/Framework/Assert/assertNullTest.php b/tests/unit/Framework/Assert/assertNullTest.php new file mode 100644 index 00000000000..69c69920cba --- /dev/null +++ b/tests/unit/Framework/Assert/assertNullTest.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function fclose; +use function fopen; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use stdClass; + +#[CoversMethod(Assert::class, 'assertNull')] +#[TestDox('assertNull()')] +#[Small] +final class assertNullTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + $openResource = fopen(__FILE__, 'r'); + + $closedResource = fopen(__FILE__, 'r'); + fclose($closedResource); + + return [ + [['foo' => 'bar']], + [[1 => 'bar', 4 => 'baz']], + [true], + [0.0], + [0], + ['123'], + ['string'], + [new stdClass], + [$openResource], + [$closedResource], + ]; + } + + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertNull(null); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertNull($actual); + } +} diff --git a/tests/unit/Framework/Assert/assertObjectEqualsTest.php b/tests/unit/Framework/Assert/assertObjectEqualsTest.php new file mode 100644 index 00000000000..a25fd26e541 --- /dev/null +++ b/tests/unit/Framework/Assert/assertObjectEqualsTest.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\TestFixture\ObjectEquals\ValueObject; + +#[CoversMethod(Assert::class, 'assertObjectEquals')] +#[TestDox('assertObjectEquals()')] +#[Small] +final class assertObjectEqualsTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [new ValueObject(1), new ValueObject(1), 'equals'], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + [new ValueObject(1), new ValueObject(2), 'equals'], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(object $expected, object $actual, string $method): void + { + $this->assertObjectEquals($expected, $actual, $method); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(object $expected, object $actual, string $method): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertObjectEquals($expected, $actual, $method); + } +} diff --git a/tests/unit/Framework/Assert/assertObjectHasPropertyTest.php b/tests/unit/Framework/Assert/assertObjectHasPropertyTest.php new file mode 100644 index 00000000000..767e8fc00d8 --- /dev/null +++ b/tests/unit/Framework/Assert/assertObjectHasPropertyTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use stdClass; + +#[CoversMethod(Assert::class, 'assertObjectHasProperty')] +#[TestDox('assertObjectHasProperty()')] +#[Small] +final class assertObjectHasPropertyTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $object = new stdClass; + $object->theProperty = 'value'; + + $this->assertObjectHasProperty('theProperty', $object); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $object = new stdClass; + + $this->expectException(AssertionFailedError::class); + + $this->assertObjectHasProperty('theProperty', $object); + } +} diff --git a/tests/unit/Framework/Assert/assertObjectNotEqualsTest.php b/tests/unit/Framework/Assert/assertObjectNotEqualsTest.php new file mode 100644 index 00000000000..7c65ce0e495 --- /dev/null +++ b/tests/unit/Framework/Assert/assertObjectNotEqualsTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertObjectNotEquals')] +#[TestDox('assertObjectNotEquals()')] +#[Small] +final class assertObjectNotEqualsTest extends TestCase +{ + #[DataProviderExternal(assertObjectEqualsTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(object $expected, object $actual, string $method): void + { + $this->assertObjectNotEquals($expected, $actual, $method); + } + + #[DataProviderExternal(assertObjectEqualsTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(object $expected, object $actual, string $method): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertObjectNotEquals($expected, $actual, $method); + } +} diff --git a/tests/unit/Framework/Assert/assertObjectNotHasPropertyTest.php b/tests/unit/Framework/Assert/assertObjectNotHasPropertyTest.php new file mode 100644 index 00000000000..ef15842ccba --- /dev/null +++ b/tests/unit/Framework/Assert/assertObjectNotHasPropertyTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use stdClass; + +#[CoversMethod(Assert::class, 'assertObjectNotHasProperty')] +#[TestDox('assertObjectNotHasProperty()')] +#[Small] +final class assertObjectNotHasPropertyTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $object = new stdClass; + + $this->assertObjectNotHasProperty('theProperty', $object); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $object = new stdClass; + $object->theProperty = 'value'; + + $this->expectException(AssertionFailedError::class); + + $this->assertObjectNotHasProperty('theProperty', $object); + } +} diff --git a/tests/unit/Framework/Assert/assertSameSizeTest.php b/tests/unit/Framework/Assert/assertSameSizeTest.php new file mode 100644 index 00000000000..671d4d93e3d --- /dev/null +++ b/tests/unit/Framework/Assert/assertSameSizeTest.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function PHPUnit\TestFixture\Generator\f; +use Countable; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertSameSize')] +#[CoversClass(GeneratorNotSupportedException::class)] +#[TestDox('assertSameSize()')] +#[Small] +final class assertSameSizeTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + [[1, 2], [3, 4]], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + [[1, 2], [3]], + ]; + } + + /** + * @return non-empty-list + */ + public static function errorProvider(): array + { + return [ + [f(), []], + [[], f()], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(Countable|iterable $expected, Countable|iterable $actual): void + { + $this->assertSameSize($expected, $actual); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(Countable|iterable $expected, Countable|iterable $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertSameSize($expected, $actual); + } + + #[DataProvider('errorProvider')] + public function testDoesNotSupportGenerators(Countable|iterable $expected, Countable|iterable $actual): void + { + $this->expectException(GeneratorNotSupportedException::class); + + $this->assertSameSize($expected, $actual); + } +} diff --git a/tests/unit/Framework/Assert/assertSameTest.php b/tests/unit/Framework/Assert/assertSameTest.php new file mode 100644 index 00000000000..db0e0c45d77 --- /dev/null +++ b/tests/unit/Framework/Assert/assertSameTest.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const INF; +use function array_merge; +use function fopen; +use function log; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\TestFixture\SampleClass; + +#[CoversMethod(Assert::class, 'assertSame')] +#[TestDox('assertSame()')] +#[Small] +final class assertSameTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return self::sameValues(); + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return array_merge(assertEqualsTest::notEqualValues(), assertEqualsTest::equalValues()); + } + + /** + * @return non-empty-list + */ + public static function sameValues(): array + { + $object = new SampleClass(4, 8, 15); + $file = TEST_FILES_PATH . 'foo.xml'; + $resource = fopen($file, 'r'); + + return [ + // null + [null, null], + // strings + ['a', 'a'], + // integers + [0, 0], + // floats + [1.0, 1.0], + [2.3, 2.3], + [1 / 3, 1 / 3], + [1 - 2 / 3, 1 - 2 / 3], + [5.5E+123, 5.5E+123], + [5.5E-123, 5.5E-123], + [log(0), log(0)], + [INF, INF], + [-INF, -INF], + // arrays + [[], []], + [[0 => 1], [0 => 1]], + [[0 => null], [0 => null]], + [['a', 'b' => [1, 2]], ['a', 'b' => [1, 2]]], + // objects + [$object, $object], + // resources + [$resource, $resource], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(mixed $expected, mixed $actual): void + { + $this->assertSame($expected, $actual); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(mixed $expected, mixed $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertSame($expected, $actual); + } +} diff --git a/tests/unit/Framework/Assert/assertStringContainsStringIgnoringCaseTest.php b/tests/unit/Framework/Assert/assertStringContainsStringIgnoringCaseTest.php new file mode 100644 index 00000000000..ae696ed00fa --- /dev/null +++ b/tests/unit/Framework/Assert/assertStringContainsStringIgnoringCaseTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertStringContainsStringIgnoringCase')] +#[TestDox('assertStringContainsStringIgnoringCase()')] +#[Small] +final class assertStringContainsStringIgnoringCaseTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertStringContainsStringIgnoringCase('BAR', 'foobarbaz'); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertStringContainsStringIgnoringCase('BARBARA', 'foobarbaz'); + } +} diff --git a/tests/unit/Framework/Assert/assertStringContainsStringIgnoringLineEndingsTest.php b/tests/unit/Framework/Assert/assertStringContainsStringIgnoringLineEndingsTest.php new file mode 100644 index 00000000000..b7d2c1a764b --- /dev/null +++ b/tests/unit/Framework/Assert/assertStringContainsStringIgnoringLineEndingsTest.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertStringContainsStringIgnoringLineEndings')] +#[TestDox('assertStringContainsStringIgnoringLineEndings()')] +#[Small] +final class assertStringContainsStringIgnoringLineEndingsTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + ["b\nc", "b\r\nc"], + ["b\nc", "a\r\nb\r\nc\r\nd"], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + ["a\nc", "b\r\nc"], + ["a\nc", "a\r\nb\r\nc\r\nd"], + ["b\nc", "\r\nc\r\n"], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(string $needle, string $haystack): void + { + $this->assertStringContainsStringIgnoringLineEndings($needle, $haystack); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(string $needle, string $haystack): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertStringContainsStringIgnoringLineEndings($needle, $haystack); + } +} diff --git a/tests/unit/Framework/Assert/assertStringContainsStringTest.php b/tests/unit/Framework/Assert/assertStringContainsStringTest.php new file mode 100644 index 00000000000..8b5276e4bb5 --- /dev/null +++ b/tests/unit/Framework/Assert/assertStringContainsStringTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertStringContainsString')] +#[TestDox('assertStringContainsString()')] +#[Small] +final class assertStringContainsStringTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertStringContainsString('bar', 'foobarbaz'); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertStringContainsString('barbara', 'foobarbaz'); + } +} diff --git a/tests/unit/Framework/Assert/assertStringEndsNotWithTest.php b/tests/unit/Framework/Assert/assertStringEndsNotWithTest.php new file mode 100644 index 00000000000..9e3d4e567ba --- /dev/null +++ b/tests/unit/Framework/Assert/assertStringEndsNotWithTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertStringEndsNotWith')] +#[TestDox('assertStringEndsNotWith()')] +#[Small] +final class assertStringEndsNotWithTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertStringEndsNotWith('suffix', 'foo'); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertStringEndsNotWith('suffix', 'foosuffix'); + } +} diff --git a/tests/unit/Framework/Assert/assertStringEndsWithTest.php b/tests/unit/Framework/Assert/assertStringEndsWithTest.php new file mode 100644 index 00000000000..0c8e264dd90 --- /dev/null +++ b/tests/unit/Framework/Assert/assertStringEndsWithTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertStringEndsWith')] +#[TestDox('assertStringEndsWith()')] +#[Small] +final class assertStringEndsWithTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertStringEndsWith('suffix', 'foosuffix'); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertStringEndsWith('suffix', 'foo'); + } +} diff --git a/tests/unit/Framework/Assert/assertStringEqualsFileCanonicalizingTest.php b/tests/unit/Framework/Assert/assertStringEqualsFileCanonicalizingTest.php new file mode 100644 index 00000000000..f4bf4074b49 --- /dev/null +++ b/tests/unit/Framework/Assert/assertStringEqualsFileCanonicalizingTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function file_get_contents; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertStringEqualsFileCanonicalizing')] +#[TestDox('assertStringEqualsFileCanonicalizing()')] +#[Small] +final class assertStringEqualsFileCanonicalizingTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertStringEqualsFileCanonicalizing( + TEST_FILES_PATH . 'foo.txt', + file_get_contents(TEST_FILES_PATH . 'foo.txt'), + ); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertStringEqualsFileCanonicalizing( + TEST_FILES_PATH . 'foo.txt', + file_get_contents(TEST_FILES_PATH . 'foo.txt') . 'BAR', + ); + } +} diff --git a/tests/unit/Framework/Assert/assertStringEqualsFileIgnoringCaseTest.php b/tests/unit/Framework/Assert/assertStringEqualsFileIgnoringCaseTest.php new file mode 100644 index 00000000000..d1db2408ce4 --- /dev/null +++ b/tests/unit/Framework/Assert/assertStringEqualsFileIgnoringCaseTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function file_get_contents; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertStringEqualsFileIgnoringCase')] +#[TestDox('assertStringEqualsFileIgnoringCase()')] +#[Small] +final class assertStringEqualsFileIgnoringCaseTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertStringEqualsFileIgnoringCase( + TEST_FILES_PATH . 'foo.xml', + file_get_contents(TEST_FILES_PATH . 'fooUppercase.xml'), + ); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertStringEqualsFileIgnoringCase( + TEST_FILES_PATH . 'foo.xml', + file_get_contents(TEST_FILES_PATH . 'bar.xml'), + ); + } +} diff --git a/tests/unit/Framework/Assert/assertStringEqualsFileTest.php b/tests/unit/Framework/Assert/assertStringEqualsFileTest.php new file mode 100644 index 00000000000..0b53771844a --- /dev/null +++ b/tests/unit/Framework/Assert/assertStringEqualsFileTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function file_get_contents; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertStringEqualsFile')] +#[TestDox('assertStringEqualsFile()')] +#[Small] +final class assertStringEqualsFileTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertStringEqualsFile( + TEST_FILES_PATH . 'foo.xml', + file_get_contents(TEST_FILES_PATH . 'foo.xml'), + ); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertStringEqualsFile( + TEST_FILES_PATH . 'foo.xml', + file_get_contents(TEST_FILES_PATH . 'bar.xml'), + ); + } +} diff --git a/tests/unit/Framework/Assert/assertStringEqualsStringIgnoringLineEndingsTest.php b/tests/unit/Framework/Assert/assertStringEqualsStringIgnoringLineEndingsTest.php new file mode 100644 index 00000000000..5123272c528 --- /dev/null +++ b/tests/unit/Framework/Assert/assertStringEqualsStringIgnoringLineEndingsTest.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertStringEqualsStringIgnoringLineEndings')] +#[TestDox('assertStringEqualsStringIgnoringLineEndings()')] +#[Small] +final class assertStringEqualsStringIgnoringLineEndingsTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + ["a\nb", "a\r\nb"], + ["a\rb", "a\r\nb"], + ["a\r\nb", "a\r\nb"], + ["a\nb", "a\rb"], + ["a\rb", "a\rb"], + ["a\r\nb", "a\rb"], + ["a\nb", "a\nb"], + ["a\rb", "a\nb"], + ["a\r\nb", "a\nb"], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + ["a\nb", 'ab'], + ["a\rb", 'ab'], + ["a\r\nb", 'ab'], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(string $expected, string $actual): void + { + $this->assertStringEqualsStringIgnoringLineEndings($expected, $actual); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(string $expected, string $actual): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertStringEqualsStringIgnoringLineEndings($expected, $actual); + } +} diff --git a/tests/unit/Framework/Assert/assertStringMatchesFormatFileTest.php b/tests/unit/Framework/Assert/assertStringMatchesFormatFileTest.php new file mode 100644 index 00000000000..a19a60fab42 --- /dev/null +++ b/tests/unit/Framework/Assert/assertStringMatchesFormatFileTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertStringMatchesFormatFile')] +#[TestDox('assertStringMatchesFormatFile()')] +#[Small] +final class assertStringMatchesFormatFileTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertStringMatchesFormatFile(TEST_FILES_PATH . 'expectedFileFormat.txt', "FOO\n"); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertStringMatchesFormatFile(TEST_FILES_PATH . 'expectedFileFormat.txt', "BAR\n"); + } +} diff --git a/tests/unit/Framework/Assert/assertStringMatchesFormatTest.php b/tests/unit/Framework/Assert/assertStringMatchesFormatTest.php new file mode 100644 index 00000000000..5e9d80394e8 --- /dev/null +++ b/tests/unit/Framework/Assert/assertStringMatchesFormatTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertStringMatchesFormat')] +#[TestDox('assertStringMatchesFormat()')] +#[Small] +final class assertStringMatchesFormatTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertStringMatchesFormat('*%s*', '***'); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertStringMatchesFormat('*%s*', '**'); + } +} diff --git a/tests/unit/Framework/Assert/assertStringNotContainsStringIgnoringCaseTest.php b/tests/unit/Framework/Assert/assertStringNotContainsStringIgnoringCaseTest.php new file mode 100644 index 00000000000..00cb4b23ece --- /dev/null +++ b/tests/unit/Framework/Assert/assertStringNotContainsStringIgnoringCaseTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertStringNotContainsStringIgnoringCase')] +#[TestDox('assertStringNotContainsStringIgnoringCase()')] +#[Small] +final class assertStringNotContainsStringIgnoringCaseTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertStringNotContainsStringIgnoringCase('BARBARA', 'foobarbaz'); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertStringNotContainsStringIgnoringCase('BAR', 'foobarbaz'); + } +} diff --git a/tests/unit/Framework/Assert/assertStringNotContainsStringTest.php b/tests/unit/Framework/Assert/assertStringNotContainsStringTest.php new file mode 100644 index 00000000000..9ec048bd002 --- /dev/null +++ b/tests/unit/Framework/Assert/assertStringNotContainsStringTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertStringNotContainsString')] +#[TestDox('assertStringNotContainsString()')] +#[Small] +final class assertStringNotContainsStringTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertStringNotContainsString('barbara', 'foobarbaz'); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertStringNotContainsString('bar', 'foobarbaz'); + } +} diff --git a/tests/unit/Framework/Assert/assertStringNotEqualsFileCanonicalizingTest.php b/tests/unit/Framework/Assert/assertStringNotEqualsFileCanonicalizingTest.php new file mode 100644 index 00000000000..b8cd2f85f80 --- /dev/null +++ b/tests/unit/Framework/Assert/assertStringNotEqualsFileCanonicalizingTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function file_get_contents; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertStringNotEqualsFileCanonicalizing')] +#[TestDox('assertStringNotEqualsFileCanonicalizing()')] +#[Small] +final class assertStringNotEqualsFileCanonicalizingTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertStringNotEqualsFileCanonicalizing( + TEST_FILES_PATH . 'foo.txt', + file_get_contents(TEST_FILES_PATH . 'foo.txt') . 'BAR', + ); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertStringNotEqualsFileCanonicalizing( + TEST_FILES_PATH . 'foo.txt', + file_get_contents(TEST_FILES_PATH . 'foo.txt'), + ); + } +} diff --git a/tests/unit/Framework/Assert/assertStringNotEqualsFileIgnoringCaseTest.php b/tests/unit/Framework/Assert/assertStringNotEqualsFileIgnoringCaseTest.php new file mode 100644 index 00000000000..da141ba288a --- /dev/null +++ b/tests/unit/Framework/Assert/assertStringNotEqualsFileIgnoringCaseTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function file_get_contents; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertStringNotEqualsFileIgnoringCase')] +#[TestDox('assertStringNotEqualsFileIgnoringCase()')] +#[Small] +final class assertStringNotEqualsFileIgnoringCaseTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertStringNotEqualsFileIgnoringCase( + TEST_FILES_PATH . 'foo.xml', + file_get_contents(TEST_FILES_PATH . 'bar.xml'), + ); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertStringNotEqualsFileIgnoringCase( + TEST_FILES_PATH . 'foo.xml', + file_get_contents(TEST_FILES_PATH . 'fooUppercase.xml'), + ); + } +} diff --git a/tests/unit/Framework/Assert/assertStringNotEqualsFileTest.php b/tests/unit/Framework/Assert/assertStringNotEqualsFileTest.php new file mode 100644 index 00000000000..5effdea0155 --- /dev/null +++ b/tests/unit/Framework/Assert/assertStringNotEqualsFileTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function file_get_contents; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertStringNotEqualsFile')] +#[TestDox('assertStringNotEqualsFile()')] +#[Small] +final class assertStringNotEqualsFileTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertStringNotEqualsFile( + TEST_FILES_PATH . 'foo.xml', + file_get_contents(TEST_FILES_PATH . 'bar.xml'), + ); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertStringNotEqualsFile( + TEST_FILES_PATH . 'foo.xml', + file_get_contents(TEST_FILES_PATH . 'foo.xml'), + ); + } +} diff --git a/tests/unit/Framework/Assert/assertStringStartsNotWithTest.php b/tests/unit/Framework/Assert/assertStringStartsNotWithTest.php new file mode 100644 index 00000000000..4646f27d57e --- /dev/null +++ b/tests/unit/Framework/Assert/assertStringStartsNotWithTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertStringStartsNotWith')] +#[TestDox('assertStringStartsNotWith()')] +#[Small] +final class assertStringStartsNotWithTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertStringStartsNotWith('prefix', 'foo'); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertStringStartsNotWith('prefix', 'prefixfoo'); + } +} diff --git a/tests/unit/Framework/Assert/assertStringStartsWithTest.php b/tests/unit/Framework/Assert/assertStringStartsWithTest.php new file mode 100644 index 00000000000..d17898edcf8 --- /dev/null +++ b/tests/unit/Framework/Assert/assertStringStartsWithTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertStringStartsWith')] +#[TestDox('assertStringStartsWith()')] +#[Small] +final class assertStringStartsWithTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertStringStartsWith('prefix', 'prefixfoo'); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertStringStartsWith('prefix', 'foo'); + } +} diff --git a/tests/unit/Framework/Assert/assertThatTest.php b/tests/unit/Framework/Assert/assertThatTest.php new file mode 100644 index 00000000000..cc517bd2cd8 --- /dev/null +++ b/tests/unit/Framework/Assert/assertThatTest.php @@ -0,0 +1,395 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const INF; +use const NAN; +use function fclose; +use function fopen; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\TestFixture\Book; +use PHPUnit\TestFixture\ObjectEquals\ValueObject; +use stdClass; + +#[CoversClass(Assert::class)] +#[TestDox('assertThat()')] +#[Small] +final class assertThatTest extends TestCase +{ + #[DoesNotPerformAssertions] + public function testAssertThatAnything(): void + { + $this->assertThat('anything', $this->anything()); + } + + public function testAssertThatIsTrue(): void + { + $this->assertThat(true, $this->isTrue()); + } + + public function testAssertThatIsFalse(): void + { + $this->assertThat(false, $this->isFalse()); + } + + public function testAssertThatIsJson(): void + { + $this->assertThat('{}', $this->isJson()); + } + + #[DoesNotPerformAssertions] + public function testAssertThatAnythingAndAnything(): void + { + $this->assertThat( + 'anything', + $this->logicalAnd( + $this->anything(), + $this->anything(), + ), + ); + } + + #[DoesNotPerformAssertions] + public function testAssertThatAnythingOrAnything(): void + { + $this->assertThat( + 'anything', + $this->logicalOr( + $this->anything(), + $this->anything(), + ), + ); + } + + #[DoesNotPerformAssertions] + public function testAssertThatAnythingXorNotAnything(): void + { + $this->assertThat( + 'anything', + $this->logicalXor( + $this->anything(), + $this->logicalNot($this->anything()), + ), + ); + } + + public function testAssertThatContainsEqual(): void + { + $this->assertThat(['foo'], $this->containsEqual('foo')); + } + + public function testAssertThatContainsIdentical(): void + { + $this->assertThat(['foo'], $this->containsIdentical('foo')); + } + + public function testAssertThatStringMatchesFormatDescription(): void + { + $this->assertThat('foo', $this->matches('%s')); + } + + public function testAssertThatStringContains(): void + { + $this->assertThat('barfoobar', $this->stringContains('foo')); + } + + public function testAssertThatStringStartsWith(): void + { + $this->assertThat('foobar', $this->stringStartsWith('foo')); + } + + public function testAssertThatStringEndsWith(): void + { + $this->assertThat('foobar', $this->stringEndsWith('bar')); + } + + public function testAssertThatStringEqualsStringIgnoringLineEndings(): void + { + $this->assertThat('foo' . "\r\n", $this->stringEqualsStringIgnoringLineEndings('foo' . "\n")); + } + + public function testAssertThatContainsOnlyArray(): void + { + $this->assertThat([[]], $this->containsOnlyArray()); + } + + public function testAssertThatContainsOnlyBool(): void + { + $this->assertThat([true], $this->containsOnlyBool()); + } + + public function testAssertThatContainsOnlyCallable(): void + { + $callable = static function (): void + {}; + + $this->assertThat([$callable], $this->containsOnlyCallable()); + } + + public function testAssertThatContainsOnlyFloat(): void + { + $this->assertThat([0.0], $this->containsOnlyFloat()); + } + + public function testAssertThatContainsOnlyInt(): void + { + $this->assertThat([0], $this->containsOnlyInt()); + } + + public function testAssertThatContainsOnlyIterable(): void + { + $this->assertThat([[]], $this->containsOnlyIterable()); + } + + public function testAssertThatContainsOnlyNull(): void + { + $this->assertThat([null], $this->containsOnlyNull()); + } + + public function testAssertThatContainsOnlyNumeric(): void + { + $this->assertThat(['0.0'], $this->containsOnlyNumeric()); + } + + public function testAssertThatContainsOnlyObject(): void + { + $this->assertThat([new stdClass], $this->containsOnlyObject()); + } + + public function testAssertThatContainsOnlyResource(): void + { + $resource = fopen(__FILE__, 'r'); + + $this->assertThat([$resource], $this->containsOnlyResource()); + } + + public function testAssertThatContainsOnlyClosedResource(): void + { + $resource = fopen(__FILE__, 'r'); + + fclose($resource); + + $this->assertThat([$resource], $this->containsOnlyClosedResource()); + } + + public function testAssertThatContainsOnlyScalar(): void + { + $this->assertThat(['string'], $this->containsOnlyScalar()); + } + + public function testAssertThatContainsOnlyString(): void + { + $this->assertThat(['string'], $this->containsOnlyString()); + } + + public function testAssertThatContainsOnlyInstancesOf(): void + { + $this->assertThat([new Book], $this->containsOnlyInstancesOf(Book::class)); + } + + public function testAssertThatArrayHasKey(): void + { + $this->assertThat(['foo' => 'bar'], $this->arrayHasKey('foo')); + } + + public function testAssertThatArrayIsList(): void + { + $this->assertThat([0, 1, 2], $this->isList()); + } + + public function testAssertThatEqualTo(): void + { + $this->assertThat('foo', $this->equalTo('foo')); + } + + public function testAssertThatEqualToCanonicalizing(): void + { + $this->assertThat(['foo', 'bar'], $this->equalToCanonicalizing(['bar', 'foo'])); + } + + public function testAssertThatEqualToIgnoringCase(): void + { + $this->assertThat('foo', $this->equalToIgnoringCase('FOO')); + } + + public function testAssertThatEqualToWithDelta(): void + { + $this->assertThat(1.0, $this->equalToWithDelta(1.09, 0.1)); + } + + public function testAssertThatIdenticalTo(): void + { + $value = new stdClass; + $constraint = $this->identicalTo($value); + + $this->assertThat($value, $constraint); + } + + public function testAssertThatIsInstanceOf(): void + { + $this->assertThat(new stdClass, $this->isInstanceOf(stdClass::class)); + } + + public function testAssertThatIsArray(): void + { + $this->assertThat([], $this->isArray()); + } + + public function testAssertThatIsBool(): void + { + $this->assertThat(true, $this->isBool()); + } + + public function testAssertThatIsCallable(): void + { + $this->assertThat(static function (): void + {}, $this->isCallable()); + } + + public function testAssertThatIsFloat(): void + { + $this->assertThat(0.0, $this->isFloat()); + } + + public function testAssertThatIsInt(): void + { + $this->assertThat(0, $this->isInt()); + } + + public function testAssertThatIsIterable(): void + { + $this->assertThat([], $this->isIterable()); + } + + public function testAssertThatIsNumeric(): void + { + $this->assertThat('0.0', $this->isNumeric()); + } + + public function testAssertThatIsObject(): void + { + $this->assertThat(new stdClass, $this->isObject()); + } + + public function testAssertThatIsResource(): void + { + $this->assertThat(fopen(__FILE__, 'r'), $this->isResource()); + } + + public function testAssertThatIsClosedResource(): void + { + $resource = fopen(__FILE__, 'r'); + + fclose($resource); + + $this->assertThat($resource, $this->isClosedResource()); + } + + public function testAssertThatIsScalar(): void + { + $this->assertThat('string', $this->isScalar()); + } + + public function testAssertThatIsString(): void + { + $this->assertThat('string', $this->isString()); + } + + public function testAssertThatIsEmpty(): void + { + $this->assertThat([], $this->isEmpty()); + } + + public function testAssertThatFileIsReadable(): void + { + $this->assertThat(__FILE__, $this->isReadable()); + } + + public function testAssertThatFileIsWritable(): void + { + $this->assertThat(__FILE__, $this->isWritable()); + } + + public function testAssertThatDirectoryExists(): void + { + $this->assertThat(__DIR__, $this->directoryExists()); + } + + public function testAssertThatFileExists(): void + { + $this->assertThat(__FILE__, $this->fileExists()); + } + + public function testAssertThatGreaterThan(): void + { + $this->assertThat(2, $this->greaterThan(1)); + } + + public function testAssertThatGreaterThanOrEqual(): void + { + $this->assertThat(2, $this->greaterThanOrEqual(1)); + } + + public function testAssertThatLessThan(): void + { + $this->assertThat(1, $this->lessThan(2)); + } + + public function testAssertThatLessThanOrEqual(): void + { + $this->assertThat(1, $this->lessThanOrEqual(2)); + } + + public function testAssertThatMatchesRegularExpression(): void + { + $this->assertThat('foobar', $this->matchesRegularExpression('/foo/')); + } + + public function testAssertThatCallback(): void + { + $this->assertThat( + null, + $this->callback(static fn ($other) => true), + ); + } + + public function testAssertThatCountOf(): void + { + $this->assertThat([1], $this->countOf(1)); + } + + public function testAssertThatNull(): void + { + $this->assertThat(null, $this->isNull()); + } + + public function testAssertThatIsFinite(): void + { + $this->assertThat(0, $this->isFinite()); + } + + public function testAssertThatIsInfinite(): void + { + $this->assertThat(INF, $this->isInfinite()); + } + + public function testAssertThatIsNan(): void + { + $this->assertThat(NAN, $this->isNan()); + } + + public function testAssertThatObjectEquals(): void + { + $this->assertObjectEquals(new ValueObject(1), new ValueObject(1)); + } +} diff --git a/tests/unit/Framework/Assert/assertTrueTest.php b/tests/unit/Framework/Assert/assertTrueTest.php new file mode 100644 index 00000000000..15694f81a43 --- /dev/null +++ b/tests/unit/Framework/Assert/assertTrueTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertTrue')] +#[TestDox('assertTrue()')] +#[Small] +final class assertTrueTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertTrue(true); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + /* @noinspection PhpUnitAssertCanBeReplacedWithFailInspection */ + $this->assertTrue(false); + } +} diff --git a/tests/unit/Framework/Assert/assertXmlFileEqualsXmlFileTest.php b/tests/unit/Framework/Assert/assertXmlFileEqualsXmlFileTest.php new file mode 100644 index 00000000000..9c0c8273681 --- /dev/null +++ b/tests/unit/Framework/Assert/assertXmlFileEqualsXmlFileTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertXmlFileEqualsXmlFile')] +#[TestDox('assertXmlFileEqualsXmlFile()')] +#[Small] +final class assertXmlFileEqualsXmlFileTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertXmlFileEqualsXmlFile( + TEST_FILES_PATH . 'foo.xml', + TEST_FILES_PATH . 'foo.xml', + ); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertXmlFileEqualsXmlFile( + TEST_FILES_PATH . 'foo.xml', + TEST_FILES_PATH . 'bar.xml', + ); + } +} diff --git a/tests/unit/Framework/Assert/assertXmlFileNotEqualsXmlFileTest.php b/tests/unit/Framework/Assert/assertXmlFileNotEqualsXmlFileTest.php new file mode 100644 index 00000000000..7a3c46e3b1c --- /dev/null +++ b/tests/unit/Framework/Assert/assertXmlFileNotEqualsXmlFileTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertXmlFileNotEqualsXmlFile')] +#[TestDox('assertXmlFileNotEqualsXmlFile()')] +#[Small] +final class assertXmlFileNotEqualsXmlFileTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertXmlFileNotEqualsXmlFile( + TEST_FILES_PATH . 'foo.xml', + TEST_FILES_PATH . 'bar.xml', + ); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertXmlFileNotEqualsXmlFile( + TEST_FILES_PATH . 'foo.xml', + TEST_FILES_PATH . 'foo.xml', + ); + } +} diff --git a/tests/unit/Framework/Assert/assertXmlStringEqualsXmlFileTest.php b/tests/unit/Framework/Assert/assertXmlStringEqualsXmlFileTest.php new file mode 100644 index 00000000000..074f0e8778c --- /dev/null +++ b/tests/unit/Framework/Assert/assertXmlStringEqualsXmlFileTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function file_get_contents; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertXmlStringEqualsXmlFile')] +#[TestDox('assertXmlStringEqualsXmlFile()')] +#[Small] +final class assertXmlStringEqualsXmlFileTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertXmlStringEqualsXmlFile( + TEST_FILES_PATH . 'foo.xml', + file_get_contents(TEST_FILES_PATH . 'foo.xml'), + ); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertXmlStringEqualsXmlFile( + TEST_FILES_PATH . 'foo.xml', + file_get_contents(TEST_FILES_PATH . 'bar.xml'), + ); + } +} diff --git a/tests/unit/Framework/Assert/assertXmlStringEqualsXmlStringTest.php b/tests/unit/Framework/Assert/assertXmlStringEqualsXmlStringTest.php new file mode 100644 index 00000000000..b729fd31a1c --- /dev/null +++ b/tests/unit/Framework/Assert/assertXmlStringEqualsXmlStringTest.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertXmlStringEqualsXmlString')] +#[TestDox('assertXmlStringEqualsXmlString()')] +#[Small] +final class assertXmlStringEqualsXmlStringTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function successProvider(): array + { + return [ + ['', ''], + ['', ''], + [ + <<<'XML' + + + + +XML, + <<<'XML' + + + + +XML + ], + ]; + } + + /** + * @return non-empty-list + */ + public static function failureProvider(): array + { + return [ + ['', ''], + ]; + } + + #[DataProvider('successProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(string $expectedXml, string $actualXml): void + { + $this->assertXmlStringEqualsXmlString($expectedXml, $actualXml); + } + + #[DataProvider('failureProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(string $expectedXml, string $actualXml): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertXmlStringEqualsXmlString($expectedXml, $actualXml); + } +} diff --git a/tests/unit/Framework/Assert/assertXmlStringNotEqualsXmlFileTest.php b/tests/unit/Framework/Assert/assertXmlStringNotEqualsXmlFileTest.php new file mode 100644 index 00000000000..2e8a7721e66 --- /dev/null +++ b/tests/unit/Framework/Assert/assertXmlStringNotEqualsXmlFileTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function file_get_contents; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertXmlStringNotEqualsXmlFile')] +#[TestDox('assertXmlStringNotEqualsXmlFile()')] +#[Small] +final class assertXmlStringNotEqualsXmlFileTest extends TestCase +{ + public function testSucceedsWhenConstraintEvaluatesToTrue(): void + { + $this->assertXmlStringNotEqualsXmlFile( + TEST_FILES_PATH . 'foo.xml', + file_get_contents(TEST_FILES_PATH . 'bar.xml'), + ); + } + + public function testFailsWhenConstraintEvaluatesToFalse(): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertXmlStringNotEqualsXmlFile( + TEST_FILES_PATH . 'foo.xml', + file_get_contents(TEST_FILES_PATH . 'foo.xml'), + ); + } +} diff --git a/tests/unit/Framework/Assert/assertXmlStringNotEqualsXmlStringTest.php b/tests/unit/Framework/Assert/assertXmlStringNotEqualsXmlStringTest.php new file mode 100644 index 00000000000..f00a7d0fcf4 --- /dev/null +++ b/tests/unit/Framework/Assert/assertXmlStringNotEqualsXmlStringTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'assertXmlStringNotEqualsXmlString')] +#[TestDox('assertXmlStringNotEqualsXmlString()')] +#[Small] +final class assertXmlStringNotEqualsXmlStringTest extends TestCase +{ + #[DataProviderExternal(assertXmlStringEqualsXmlStringTest::class, 'failureProvider')] + public function testSucceedsWhenConstraintEvaluatesToTrue(string $expectedXml, string $actualXml): void + { + $this->assertXmlStringNotEqualsXmlString($expectedXml, $actualXml); + } + + #[DataProviderExternal(assertXmlStringEqualsXmlStringTest::class, 'successProvider')] + public function testFailsWhenConstraintEvaluatesToFalse(string $expectedXml, string $actualXml): void + { + $this->expectException(AssertionFailedError::class); + + $this->assertXmlStringNotEqualsXmlString($expectedXml, $actualXml); + } +} diff --git a/tests/unit/Framework/Assert/failTest.php b/tests/unit/Framework/Assert/failTest.php new file mode 100644 index 00000000000..07d15b9680c --- /dev/null +++ b/tests/unit/Framework/Assert/failTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'fail')] +#[TestDox('fail()')] +#[Small] +final class failTest extends TestCase +{ + public function testFailsTest(): void + { + $message = 'message'; + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage($message); + + $this->fail($message); + } +} diff --git a/tests/unit/Framework/Assert/markTestIncompleteTest.php b/tests/unit/Framework/Assert/markTestIncompleteTest.php new file mode 100644 index 00000000000..05878f58d8a --- /dev/null +++ b/tests/unit/Framework/Assert/markTestIncompleteTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'markTestIncomplete')] +#[TestDox('markTestIncomplete()')] +#[Small] +final class markTestIncompleteTest extends TestCase +{ + public function testMarksTestAsIncomplete(): void + { + $message = 'message'; + + $this->expectException(IncompleteTestError::class); + $this->expectExceptionMessage($message); + + $this->markTestIncomplete($message); + } +} diff --git a/tests/unit/Framework/Assert/markTestSkippedTest.php b/tests/unit/Framework/Assert/markTestSkippedTest.php new file mode 100644 index 00000000000..bccf8534e8a --- /dev/null +++ b/tests/unit/Framework/Assert/markTestSkippedTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversMethod(Assert::class, 'markTestSkipped')] +#[TestDox('markTestSkipped()')] +#[Small] +final class markTestSkippedTest extends TestCase +{ + public function testMarksTestAsSkipped(): void + { + $message = 'message'; + + $this->expectException(SkippedWithMessageException::class); + $this->expectExceptionMessage($message); + + $this->markTestSkipped($message); + } +} diff --git a/tests/unit/Framework/AssertTest.php b/tests/unit/Framework/AssertTest.php deleted file mode 100644 index fc0c43258de..00000000000 --- a/tests/unit/Framework/AssertTest.php +++ /dev/null @@ -1,2615 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use const DIRECTORY_SEPARATOR; -use const INF; -use const NAN; -use const PHP_OS_FAMILY; -use function acos; -use function array_merge; -use function chmod; -use function file_get_contents; -use function fopen; -use function json_encode; -use function log; -use function mkdir; -use function octdec; -use function rmdir; -use function sys_get_temp_dir; -use function tempnam; -use function uniqid; -use function unlink; -use ArrayIterator; -use ArrayObject; -use DateTime; -use DateTimeZone; -use PHPUnit\TestFixture\Author; -use PHPUnit\TestFixture\Book; -use PHPUnit\TestFixture\ClassWithNonPublicAttributes; -use PHPUnit\TestFixture\ClassWithToString; -use PHPUnit\TestFixture\SampleArrayAccess; -use PHPUnit\TestFixture\SampleClass; -use PHPUnit\TestFixture\Struct; -use PHPUnit\Util\Xml; -use SplObjectStorage; -use stdClass; - -/** - * @small - */ -final class AssertTest extends TestCase -{ - public static function validInvalidJsonDataprovider(): array - { - return [ - 'error syntax in expected JSON' => ['{"Mascott"::}', '{"Mascott" : "Tux"}'], - 'error UTF-8 in actual JSON' => ['{"Mascott" : "Tux"}', '{"Mascott" : :}'], - ]; - } - - public function testFail(): void - { - try { - $this->fail(); - } catch (AssertionFailedError $e) { - return; - } - - throw new AssertionFailedError('Fail did not throw fail exception'); - } - - public function testAssertContainsOnlyInstancesOf(): void - { - $test = [new Book, new Book]; - - $this->assertContainsOnlyInstancesOf(Book::class, $test); - $this->assertContainsOnlyInstancesOf(stdClass::class, [new stdClass]); - - $test2 = [new Author('Test')]; - - $this->expectException(AssertionFailedError::class); - - $this->assertContainsOnlyInstancesOf(Book::class, $test2); - } - - public function testAssertArrayHasKeyThrowsExceptionForInvalidFirstArgument(): void - { - $this->expectException(Exception::class); - - $this->assertArrayHasKey(null, []); - } - - public function testAssertArrayHasKeyThrowsExceptionForInvalidSecondArgument(): void - { - $this->expectException(Exception::class); - - $this->assertArrayHasKey(0, null); - } - - public function testAssertArrayHasIntegerKey(): void - { - $this->assertArrayHasKey(0, ['foo']); - - $this->expectException(AssertionFailedError::class); - - $this->assertArrayHasKey(1, ['foo']); - } - - public function testAssertArrayNotHasKeyThrowsExceptionForInvalidFirstArgument(): void - { - $this->expectException(Exception::class); - - $this->assertArrayNotHasKey(null, []); - } - - public function testAssertArrayNotHasKeyThrowsExceptionForInvalidSecondArgument(): void - { - $this->expectException(Exception::class); - - $this->assertArrayNotHasKey(0, null); - } - - public function testAssertArrayNotHasIntegerKey(): void - { - $this->assertArrayNotHasKey(1, ['foo']); - - $this->expectException(AssertionFailedError::class); - - $this->assertArrayNotHasKey(0, ['foo']); - } - - public function testAssertArrayHasStringKey(): void - { - $this->assertArrayHasKey('foo', ['foo' => 'bar']); - - $this->expectException(AssertionFailedError::class); - - $this->assertArrayHasKey('bar', ['foo' => 'bar']); - } - - public function testAssertArrayNotHasStringKey(): void - { - $this->assertArrayNotHasKey('bar', ['foo' => 'bar']); - - $this->expectException(AssertionFailedError::class); - - $this->assertArrayNotHasKey('foo', ['foo' => 'bar']); - } - - public function testAssertArrayHasKeyAcceptsArrayObjectValue(): void - { - $array = new ArrayObject; - $array['foo'] = 'bar'; - - $this->assertArrayHasKey('foo', $array); - } - - public function testAssertArrayHasKeyProperlyFailsWithArrayObjectValue(): void - { - $array = new ArrayObject; - $array['bar'] = 'bar'; - - $this->expectException(AssertionFailedError::class); - - $this->assertArrayHasKey('foo', $array); - } - - public function testAssertArrayHasKeyAcceptsArrayAccessValue(): void - { - $array = new SampleArrayAccess; - $array['foo'] = 'bar'; - - $this->assertArrayHasKey('foo', $array); - } - - public function testAssertArrayHasKeyProperlyFailsWithArrayAccessValue(): void - { - $array = new SampleArrayAccess; - $array['bar'] = 'bar'; - - $this->expectException(AssertionFailedError::class); - - $this->assertArrayHasKey('foo', $array); - } - - public function testAssertArrayNotHasKeyAcceptsArrayAccessValue(): void - { - $array = new ArrayObject; - $array['foo'] = 'bar'; - - $this->assertArrayNotHasKey('bar', $array); - } - - public function testAssertArrayNotHasKeyPropertlyFailsWithArrayAccessValue(): void - { - $array = new ArrayObject; - $array['bar'] = 'bar'; - - $this->expectException(AssertionFailedError::class); - - $this->assertArrayNotHasKey('bar', $array); - } - - public function testAssertArrayContainsOnlyIntegers(): void - { - $this->assertContainsOnly('integer', [1, 2, 3]); - - $this->expectException(AssertionFailedError::class); - - $this->assertContainsOnly('integer', ['1', 2, 3]); - } - - public function testAssertArrayNotContainsOnlyIntegers(): void - { - $this->assertNotContainsOnly('integer', ['1', 2, 3]); - - $this->expectException(AssertionFailedError::class); - - $this->assertNotContainsOnly('integer', [1, 2, 3]); - } - - public function testAssertArrayContainsOnlyStdClass(): void - { - $this->assertContainsOnly('StdClass', [new stdClass]); - - $this->expectException(AssertionFailedError::class); - - $this->assertContainsOnly('StdClass', ['StdClass']); - } - - public function testAssertArrayNotContainsOnlyStdClass(): void - { - $this->assertNotContainsOnly('StdClass', ['StdClass']); - - $this->expectException(AssertionFailedError::class); - - $this->assertNotContainsOnly('StdClass', [new stdClass]); - } - - public function equalProvider(): array - { - // same |= equal - return array_merge($this->equalValues(), $this->sameValues()); - } - - public function notEqualProvider() - { - return $this->notEqualValues(); - } - - public function sameProvider(): array - { - return $this->sameValues(); - } - - public function notSameProvider(): array - { - // not equal |= not same - // equal, ¬same |= not same - return array_merge($this->notEqualValues(), $this->equalValues()); - } - - /** - * @dataProvider equalProvider - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function testAssertEqualsSucceeds($a, $b): void - { - $this->assertEquals($a, $b); - } - - /** - * @dataProvider notEqualProvider - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function testAssertEqualsFails($a, $b): void - { - $this->expectException(AssertionFailedError::class); - - $this->assertEquals($a, $b); - } - - /** - * @dataProvider notEqualProvider - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function testAssertNotEqualsSucceeds($a, $b): void - { - $this->assertNotEquals($a, $b); - } - - /** - * @testdox assertNotEquals($a, $b) with delta $delta, canoicalize $canonicalize, ignoreCase $ignoreCase - * @dataProvider equalProvider - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function testAssertNotEqualsFails($a, $b): void - { - $this->expectException(AssertionFailedError::class); - - $this->assertNotEquals($a, $b); - } - - /** - * @testdox assertNotSame($a, $b) fails - * @dataProvider sameProvider - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function testAssertSameSucceeds($a, $b): void - { - $this->assertSame($a, $b); - } - - /** - * @testdox assertNotSame($a, $b) - * @dataProvider notSameProvider - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function testAssertSameFails($a, $b): void - { - $this->expectException(AssertionFailedError::class); - - $this->assertSame($a, $b); - } - - /** - * @testdox assertSame($a, $b) fails - * @dataProvider notSameProvider - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function testAssertNotSameSucceeds($a, $b): void - { - $this->assertNotSame($a, $b); - } - - /** - * @testdox assertSame($a, $b) - * @dataProvider sameProvider - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function testAssertNotSameFails($a, $b): void - { - $this->expectException(AssertionFailedError::class); - - $this->assertNotSame($a, $b); - } - - public function testAssertXmlFileEqualsXmlFile(): void - { - $this->assertXmlFileEqualsXmlFile( - TEST_FILES_PATH . 'foo.xml', - TEST_FILES_PATH . 'foo.xml' - ); - - $this->expectException(AssertionFailedError::class); - - $this->assertXmlFileEqualsXmlFile( - TEST_FILES_PATH . 'foo.xml', - TEST_FILES_PATH . 'bar.xml' - ); - } - - public function testAssertXmlFileNotEqualsXmlFile(): void - { - $this->assertXmlFileNotEqualsXmlFile( - TEST_FILES_PATH . 'foo.xml', - TEST_FILES_PATH . 'bar.xml' - ); - - $this->expectException(AssertionFailedError::class); - - $this->assertXmlFileNotEqualsXmlFile( - TEST_FILES_PATH . 'foo.xml', - TEST_FILES_PATH . 'foo.xml' - ); - } - - public function testAssertXmlStringEqualsXmlFile(): void - { - $this->assertXmlStringEqualsXmlFile( - TEST_FILES_PATH . 'foo.xml', - file_get_contents(TEST_FILES_PATH . 'foo.xml') - ); - - $this->expectException(AssertionFailedError::class); - - $this->assertXmlStringEqualsXmlFile( - TEST_FILES_PATH . 'foo.xml', - file_get_contents(TEST_FILES_PATH . 'bar.xml') - ); - } - - public function testXmlStringNotEqualsXmlFile(): void - { - $this->assertXmlStringNotEqualsXmlFile( - TEST_FILES_PATH . 'foo.xml', - file_get_contents(TEST_FILES_PATH . 'bar.xml') - ); - - $this->expectException(AssertionFailedError::class); - - $this->assertXmlStringNotEqualsXmlFile( - TEST_FILES_PATH . 'foo.xml', - file_get_contents(TEST_FILES_PATH . 'foo.xml') - ); - } - - public function testAssertXmlStringEqualsXmlString(): void - { - $this->assertXmlStringEqualsXmlString('', ''); - - $this->expectException(AssertionFailedError::class); - - $this->assertXmlStringEqualsXmlString('', ''); - } - - /** - * @ticket 1860 - */ - public function testAssertXmlStringEqualsXmlString2(): void - { - $this->expectException(Exception::class); - - $this->assertXmlStringEqualsXmlString('', ''); - } - - /** - * @ticket 1860 - */ - public function testAssertXmlStringEqualsXmlString3(): void - { - $expected = <<<'XML' - - - - -XML; - - $actual = <<<'XML' - - - - -XML; - - $this->assertXmlStringEqualsXmlString($expected, $actual); - } - - public function testAssertXmlStringNotEqualsXmlString(): void - { - $this->assertXmlStringNotEqualsXmlString('', ''); - - $this->expectException(AssertionFailedError::class); - - $this->assertXmlStringNotEqualsXmlString('', ''); - } - - public function testAssertStringEqualsNumeric(): void - { - $this->assertEquals('0', 0); - - $this->expectException(AssertionFailedError::class); - - $this->assertEquals('0', 1); - } - - public function testAssertStringEqualsNumeric2(): void - { - $this->assertNotEquals('A', 0); - } - - public function testAssertIsReadable(): void - { - $this->assertIsReadable(__FILE__); - - $this->expectException(AssertionFailedError::class); - - $this->assertIsReadable(__DIR__ . DIRECTORY_SEPARATOR . 'NotExisting'); - } - - public function testAssertIsNotReadable(): void - { - $this->assertIsNotReadable(__DIR__ . DIRECTORY_SEPARATOR . 'NotExisting'); - - $this->expectException(AssertionFailedError::class); - - $this->assertIsNotReadable(__FILE__); - } - - public function testAssertIsWritable(): void - { - $this->assertIsWritable(__FILE__); - - $this->expectException(AssertionFailedError::class); - - $this->assertIsWritable(__DIR__ . DIRECTORY_SEPARATOR . 'NotExisting'); - } - - public function testAssertNotIsWritable(): void - { - $this->assertIsNotWritable(__DIR__ . DIRECTORY_SEPARATOR . 'NotExisting'); - - $this->expectException(AssertionFailedError::class); - - $this->assertIsNotWritable(__FILE__); - } - - public function testAssertDirectoryExists(): void - { - $this->assertDirectoryExists(__DIR__); - - $this->expectException(AssertionFailedError::class); - - $this->assertDirectoryExists(__DIR__ . DIRECTORY_SEPARATOR . 'NotExisting'); - } - - public function testAssertDirectoryNotExists(): void - { - $this->assertDirectoryDoesNotExist(__DIR__ . DIRECTORY_SEPARATOR . 'NotExisting'); - - $this->expectException(AssertionFailedError::class); - - $this->assertDirectoryDoesNotExist(__DIR__); - } - - public function testAssertDirectoryIsReadable(): void - { - $this->assertDirectoryIsReadable(__DIR__); - - $this->expectException(AssertionFailedError::class); - - $this->assertDirectoryIsReadable(__DIR__ . DIRECTORY_SEPARATOR . 'NotExisting'); - } - - public function testAssertDirectoryIsNotReadable(): void - { - if (PHP_OS_FAMILY === 'Windows') { - self::markTestSkipped('Cannot test this behaviour on Windows'); - } - - $dirName = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('unreadable_dir_', true); - mkdir($dirName, octdec('0')); - - $this->assertDirectoryIsNotReadable($dirName); - - chmod($dirName, octdec('444')); - - try { - $this->assertDirectoryIsNotReadable($dirName); - } catch (AssertionFailedError $e) { - } - - rmdir($dirName); - } - - public function testAssertDirectoryIsWritable(): void - { - $this->assertDirectoryIsWritable(__DIR__); - - $this->expectException(AssertionFailedError::class); - - $this->assertDirectoryIsWritable(__DIR__ . DIRECTORY_SEPARATOR . 'NotExisting'); - } - - public function testAssertDirectoryIsNotWritable(): void - { - if (PHP_OS_FAMILY === 'Windows') { - self::markTestSkipped('Cannot test this behaviour on Windows'); - } - - $dirName = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('unwritable_dir_', true); - mkdir($dirName, octdec('444')); - - $this->assertDirectoryIsNotWritable($dirName); - - chmod($dirName, octdec('755')); - - try { - $this->assertDirectoryIsNotWritable($dirName); - } catch (AssertionFailedError $e) { - } - - rmdir($dirName); - } - - public function testAssertFileExists(): void - { - $this->assertFileExists(__FILE__); - - $this->expectException(AssertionFailedError::class); - - $this->assertFileExists(__DIR__ . DIRECTORY_SEPARATOR . 'NotExisting'); - } - - public function testAssertFileNotExists(): void - { - $this->assertFileDoesNotExist(__DIR__ . DIRECTORY_SEPARATOR . 'NotExisting'); - - $this->expectException(AssertionFailedError::class); - - $this->assertFileDoesNotExist(__FILE__); - } - - public function testAssertFileIsReadable(): void - { - $this->assertFileIsReadable(__FILE__); - - $this->expectException(AssertionFailedError::class); - - $this->assertFileIsReadable(__DIR__ . DIRECTORY_SEPARATOR . 'NotExisting'); - } - - public function testAssertFileIsNotReadable(): void - { - if (PHP_OS_FAMILY === 'Windows') { - self::markTestSkipped('Cannot test this behaviour on Windows'); - } - - $tempFile = tempnam( - sys_get_temp_dir(), - 'unreadable' - ); - - chmod($tempFile, octdec('0')); - - $this->assertFileIsNotReadable($tempFile); - - chmod($tempFile, octdec('755')); - - try { - $this->assertFileIsNotReadable($tempFile); - } catch (AssertionFailedError $e) { - } - - unlink($tempFile); - } - - public function testAssertFileIsNotWritable(): void - { - $tempFile = tempnam(sys_get_temp_dir(), 'unwriteable'); - - chmod($tempFile, octdec('0')); - - $this->assertFileIsNotWritable($tempFile); - - chmod($tempFile, octdec('755')); - - $this->expectException(AssertionFailedError::class); - - $this->assertFileIsNotWritable($tempFile); - - unlink($tempFile); - } - - public function testAssertFileIsWritable(): void - { - $this->assertFileIsWritable(__FILE__); - - $this->expectException(AssertionFailedError::class); - - $this->assertFileIsWritable(__DIR__ . DIRECTORY_SEPARATOR . 'NotExisting'); - } - - public function testAssertObjectHasAttribute(): void - { - $o = new Author('Terry Pratchett'); - - $this->assertObjectHasAttribute('name', $o); - - $this->expectException(AssertionFailedError::class); - - $this->assertObjectHasAttribute('foo', $o); - } - - public function testAssertObjectHasAttributeNumericAttribute(): void - { - $object = new stdClass; - $object->{'2020'} = 'Tokyo'; - - $this->assertObjectHasAttribute('2020', $object); - - $this->expectException(AssertionFailedError::class); - - $this->assertObjectHasAttribute('2018', $object); - } - - public function testAssertObjectHasAttributeMultiByteAttribute(): void - { - $object = new stdClass; - $object->{'東京'} = 2020; - - $this->assertObjectHasAttribute('東京', $object); - - $this->expectException(AssertionFailedError::class); - - $this->assertObjectHasAttribute('長野', $object); - } - - public function testAssertObjectNotHasAttribute(): void - { - $o = new Author('Terry Pratchett'); - - $this->assertObjectNotHasAttribute('foo', $o); - - $this->expectException(AssertionFailedError::class); - - $this->assertObjectNotHasAttribute('name', $o); - } - - public function testAssertObjectNotHasAttributeNumericAttribute(): void - { - $object = new stdClass; - $object->{'2020'} = 'Tokyo'; - - $this->assertObjectNotHasAttribute('2018', $object); - - $this->expectException(AssertionFailedError::class); - - $this->assertObjectNotHasAttribute('2020', $object); - } - - public function testAssertObjectNotHasAttributeMultiByteAttribute(): void - { - $object = new stdClass; - $object->{'東京'} = 2020; - - $this->assertObjectNotHasAttribute('長野', $object); - - $this->expectException(AssertionFailedError::class); - - $this->assertObjectNotHasAttribute('東京', $object); - } - - public function testAssertFinite(): void - { - $this->assertFinite(1); - - $this->expectException(AssertionFailedError::class); - - $this->assertFinite(INF); - } - - public function testAssertInfinite(): void - { - $this->assertInfinite(INF); - - $this->expectException(AssertionFailedError::class); - - $this->assertInfinite(1); - } - - public function testAssertNan(): void - { - $this->assertNan(NAN); - - $this->expectException(AssertionFailedError::class); - - $this->assertNan(1); - } - - public function testAssertNull(): void - { - $this->assertNull(null); - - $this->expectException(AssertionFailedError::class); - - $this->assertNull(new stdClass); - } - - public function testAssertNotNull(): void - { - $this->assertNotNull(new stdClass); - - $this->expectException(AssertionFailedError::class); - - $this->assertNotNull(null); - } - - public function testAssertTrue(): void - { - $this->assertTrue(true); - - $this->expectException(AssertionFailedError::class); - - $this->assertTrue(false); - } - - public function testAssertNotTrue(): void - { - $this->assertNotTrue(false); - $this->assertNotTrue(1); - $this->assertNotTrue('true'); - - $this->expectException(AssertionFailedError::class); - - $this->assertNotTrue(true); - } - - public function testAssertFalse(): void - { - $this->assertFalse(false); - - $this->expectException(AssertionFailedError::class); - - $this->assertFalse(true); - } - - public function testAssertNotFalse(): void - { - $this->assertNotFalse(true); - $this->assertNotFalse(0); - $this->assertNotFalse(''); - - $this->expectException(AssertionFailedError::class); - - $this->assertNotFalse(false); - } - - public function testAssertMatchesRegularExpression(): void - { - $this->assertMatchesRegularExpression('/foo/', 'foobar'); - - $this->expectException(AssertionFailedError::class); - - $this->assertMatchesRegularExpression('/foo/', 'bar'); - } - - public function testAssertDoesNotMatchRegularExpression(): void - { - $this->assertDoesNotMatchRegularExpression('/foo/', 'bar'); - - $this->expectException(AssertionFailedError::class); - - $this->assertDoesNotMatchRegularExpression('/foo/', 'foobar'); - } - - public function testAssertSame(): void - { - $o = new stdClass; - - $this->assertSame($o, $o); - - $this->expectException(AssertionFailedError::class); - - $this->assertSame(new stdClass, new stdClass); - } - - public function testAssertSame2(): void - { - $this->assertSame(true, true); - $this->assertSame(false, false); - - $this->expectException(AssertionFailedError::class); - - $this->assertSame(true, false); - } - - public function testAssertNotSame(): void - { - $this->assertNotSame( - new stdClass, - null - ); - - $this->assertNotSame( - null, - new stdClass - ); - - $this->assertNotSame( - new stdClass, - new stdClass - ); - - $o = new stdClass; - - $this->expectException(AssertionFailedError::class); - - $this->assertNotSame($o, $o); - } - - public function testAssertNotSame2(): void - { - $this->assertNotSame(true, false); - $this->assertNotSame(false, true); - - $this->expectException(AssertionFailedError::class); - - $this->assertNotSame(true, true); - } - - public function testAssertNotSameFailsNull(): void - { - $this->expectException(AssertionFailedError::class); - - $this->assertNotSame(null, null); - } - - public function testGreaterThan(): void - { - $this->assertGreaterThan(1, 2); - - $this->expectException(AssertionFailedError::class); - - $this->assertGreaterThan(2, 1); - } - - public function testGreaterThanOrEqual(): void - { - $this->assertGreaterThanOrEqual(1, 2); - - $this->expectException(AssertionFailedError::class); - - $this->assertGreaterThanOrEqual(2, 1); - } - - public function testLessThan(): void - { - $this->assertLessThan(2, 1); - - try { - $this->assertLessThan(1, 2); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testLessThanOrEqual(): void - { - $this->assertLessThanOrEqual(2, 1); - - $this->expectException(AssertionFailedError::class); - - $this->assertLessThanOrEqual(1, 2); - } - - public function testAssertClassHasAttributeThrowsExceptionIfAttributeNameIsNotValid(): void - { - $this->expectException(Exception::class); - - $this->assertClassHasAttribute('1', ClassWithNonPublicAttributes::class); - } - - public function testAssertClassHasAttributeThrowsExceptionIfClassDoesNotExist(): void - { - $this->expectException(Exception::class); - - $this->assertClassHasAttribute('attribute', 'ClassThatDoesNotExist'); - } - - public function testAssertClassNotHasAttributeThrowsExceptionIfAttributeNameIsNotValid(): void - { - $this->expectException(Exception::class); - - $this->assertClassNotHasAttribute('1', ClassWithNonPublicAttributes::class); - } - - public function testAssertClassNotHasAttributeThrowsExceptionIfClassDoesNotExist(): void - { - $this->expectException(Exception::class); - - $this->assertClassNotHasAttribute('attribute', 'ClassThatDoesNotExist'); - } - - public function testAssertClassHasStaticAttributeThrowsExceptionIfAttributeNameIsNotValid(): void - { - $this->expectException(Exception::class); - - $this->assertClassHasStaticAttribute('1', ClassWithNonPublicAttributes::class); - } - - public function testAssertClassHasStaticAttributeThrowsExceptionIfClassDoesNotExist(): void - { - $this->expectException(Exception::class); - - $this->assertClassHasStaticAttribute('attribute', 'ClassThatDoesNotExist'); - } - - public function testAssertClassNotHasStaticAttributeThrowsExceptionIfAttributeNameIsNotValid(): void - { - $this->expectException(Exception::class); - - $this->assertClassNotHasStaticAttribute('1', ClassWithNonPublicAttributes::class); - } - - public function testAssertClassNotHasStaticAttributeThrowsExceptionIfClassDoesNotExist(): void - { - $this->expectException(Exception::class); - - $this->assertClassNotHasStaticAttribute('attribute', 'ClassThatDoesNotExist'); - } - - public function testAssertObjectHasAttributeThrowsException2(): void - { - $this->expectException(Exception::class); - - $this->assertObjectHasAttribute('foo', null); - } - - public function testAssertObjectHasAttributeThrowsExceptionIfAttributeNameIsNotValid(): void - { - $this->expectException(Exception::class); - - $this->assertObjectHasAttribute('1', ClassWithNonPublicAttributes::class); - } - - public function testAssertObjectNotHasAttributeThrowsException2(): void - { - $this->expectException(Exception::class); - - $this->assertObjectNotHasAttribute('foo', null); - } - - public function testAssertObjectNotHasAttributeThrowsExceptionIfAttributeNameIsNotValid(): void - { - $this->expectException(Exception::class); - - $this->assertObjectNotHasAttribute('1', ClassWithNonPublicAttributes::class); - } - - public function testClassHasPublicAttribute(): void - { - $this->assertClassHasAttribute('publicAttribute', ClassWithNonPublicAttributes::class); - - $this->expectException(AssertionFailedError::class); - - $this->assertClassHasAttribute('attribute', ClassWithNonPublicAttributes::class); - } - - public function testClassNotHasPublicAttribute(): void - { - $this->assertClassNotHasAttribute('attribute', ClassWithNonPublicAttributes::class); - - $this->expectException(AssertionFailedError::class); - - $this->assertClassNotHasAttribute('publicAttribute', ClassWithNonPublicAttributes::class); - } - - public function testClassHasPublicStaticAttribute(): void - { - $this->assertClassHasStaticAttribute('publicStaticAttribute', ClassWithNonPublicAttributes::class); - - $this->expectException(AssertionFailedError::class); - - $this->assertClassHasStaticAttribute('attribute', ClassWithNonPublicAttributes::class); - } - - public function testClassNotHasPublicStaticAttribute(): void - { - $this->assertClassNotHasStaticAttribute('attribute', ClassWithNonPublicAttributes::class); - - $this->expectException(AssertionFailedError::class); - - $this->assertClassNotHasStaticAttribute('publicStaticAttribute', ClassWithNonPublicAttributes::class); - } - - public function testObjectHasPublicAttribute(): void - { - $obj = new ClassWithNonPublicAttributes; - - $this->assertObjectHasAttribute('publicAttribute', $obj); - - $this->expectException(AssertionFailedError::class); - - $this->assertObjectHasAttribute('attribute', $obj); - } - - public function testObjectNotHasPublicAttribute(): void - { - $obj = new ClassWithNonPublicAttributes; - - $this->assertObjectNotHasAttribute('attribute', $obj); - - $this->expectException(AssertionFailedError::class); - - $this->assertObjectNotHasAttribute('publicAttribute', $obj); - } - - public function testObjectHasOnTheFlyAttribute(): void - { - $obj = new stdClass; - $obj->foo = 'bar'; - - $this->assertObjectHasAttribute('foo', $obj); - - $this->expectException(AssertionFailedError::class); - - $this->assertObjectHasAttribute('bar', $obj); - } - - public function testObjectNotHasOnTheFlyAttribute(): void - { - $obj = new stdClass; - $obj->foo = 'bar'; - - $this->assertObjectNotHasAttribute('bar', $obj); - - $this->expectException(AssertionFailedError::class); - - $this->assertObjectNotHasAttribute('foo', $obj); - } - - public function testObjectHasProtectedAttribute(): void - { - $obj = new ClassWithNonPublicAttributes; - - $this->assertObjectHasAttribute('protectedAttribute', $obj); - - $this->expectException(AssertionFailedError::class); - - $this->assertObjectHasAttribute('attribute', $obj); - } - - public function testObjectNotHasProtectedAttribute(): void - { - $obj = new ClassWithNonPublicAttributes; - - $this->assertObjectNotHasAttribute('attribute', $obj); - - $this->expectException(AssertionFailedError::class); - - $this->assertObjectNotHasAttribute('protectedAttribute', $obj); - } - - public function testObjectHasPrivateAttribute(): void - { - $obj = new ClassWithNonPublicAttributes; - - $this->assertObjectHasAttribute('privateAttribute', $obj); - - $this->expectException(AssertionFailedError::class); - - $this->assertObjectHasAttribute('attribute', $obj); - } - - public function testObjectNotHasPrivateAttribute(): void - { - $obj = new ClassWithNonPublicAttributes; - - $this->assertObjectNotHasAttribute('attribute', $obj); - - $this->expectException(AssertionFailedError::class); - - $this->assertObjectNotHasAttribute('privateAttribute', $obj); - } - - /** - * @doesNotPerformAssertions - */ - public function testAssertThatAnything(): void - { - $this->assertThat('anything', $this->anything()); - } - - public function testAssertThatIsTrue(): void - { - $this->assertThat(true, $this->isTrue()); - } - - public function testAssertThatIsFalse(): void - { - $this->assertThat(false, $this->isFalse()); - } - - public function testAssertThatIsJson(): void - { - $this->assertThat('{}', $this->isJson()); - } - - /** - * @doesNotPerformAssertions - */ - public function testAssertThatAnythingAndAnything(): void - { - $this->assertThat( - 'anything', - $this->logicalAnd( - $this->anything(), - $this->anything() - ) - ); - } - - /** - * @doesNotPerformAssertions - */ - public function testAssertThatAnythingOrAnything(): void - { - $this->assertThat( - 'anything', - $this->logicalOr( - $this->anything(), - $this->anything() - ) - ); - } - - /** - * @doesNotPerformAssertions - */ - public function testAssertThatAnythingXorNotAnything(): void - { - $this->assertThat( - 'anything', - $this->logicalXor( - $this->anything(), - $this->logicalNot($this->anything()) - ) - ); - } - - public function testAssertThatContains(): void - { - $this->assertThat(['foo'], $this->containsIdentical('foo')); - } - - public function testAssertThatStringContains(): void - { - $this->assertThat('barfoobar', $this->stringContains('foo')); - } - - public function testAssertThatContainsOnly(): void - { - $this->assertThat(['foo'], $this->containsOnly('string')); - } - - public function testAssertThatContainsOnlyInstancesOf(): void - { - $this->assertThat([new Book], $this->containsOnlyInstancesOf(Book::class)); - } - - public function testAssertThatArrayHasKey(): void - { - $this->assertThat(['foo' => 'bar'], $this->arrayHasKey('foo')); - } - - public function testAssertThatClassHasAttribute(): void - { - $this->assertThat( - new ClassWithNonPublicAttributes, - $this->classHasAttribute('publicAttribute') - ); - } - - public function testAssertThatClassHasStaticAttribute(): void - { - $this->assertThat( - new ClassWithNonPublicAttributes, - $this->classHasStaticAttribute('publicStaticAttribute') - ); - } - - public function testAssertThatObjectHasAttribute(): void - { - $this->assertThat( - new ClassWithNonPublicAttributes, - $this->objectHasAttribute('publicAttribute') - ); - } - - public function testAssertThatEqualTo(): void - { - $this->assertThat('foo', $this->equalTo('foo')); - } - - public function testAssertThatIdenticalTo(): void - { - $value = new stdClass; - $constraint = $this->identicalTo($value); - - $this->assertThat($value, $constraint); - } - - public function testAssertThatIsInstanceOf(): void - { - $this->assertThat(new stdClass, $this->isInstanceOf('StdClass')); - } - - public function testAssertThatIsType(): void - { - $this->assertThat('string', $this->isType('string')); - } - - public function testAssertThatIsEmpty(): void - { - $this->assertThat([], $this->isEmpty()); - } - - public function testAssertThatFileExists(): void - { - $this->assertThat(__FILE__, $this->fileExists()); - } - - public function testAssertThatGreaterThan(): void - { - $this->assertThat(2, $this->greaterThan(1)); - } - - public function testAssertThatGreaterThanOrEqual(): void - { - $this->assertThat(2, $this->greaterThanOrEqual(1)); - } - - public function testAssertThatLessThan(): void - { - $this->assertThat(1, $this->lessThan(2)); - } - - public function testAssertThatLessThanOrEqual(): void - { - $this->assertThat(1, $this->lessThanOrEqual(2)); - } - - public function testAssertThatMatchesRegularExpression(): void - { - $this->assertThat('foobar', $this->matchesRegularExpression('/foo/')); - } - - public function testAssertThatCallback(): void - { - $this->assertThat( - null, - $this->callback(function ($other) { - return true; - }) - ); - } - - public function testAssertThatCountOf(): void - { - $this->assertThat([1], $this->countOf(1)); - } - - public function testAssertFileEquals(): void - { - $this->assertFileEquals( - TEST_FILES_PATH . 'foo.xml', - TEST_FILES_PATH . 'foo.xml' - ); - - $this->expectException(AssertionFailedError::class); - - $this->assertFileEquals( - TEST_FILES_PATH . 'foo.xml', - TEST_FILES_PATH . 'bar.xml' - ); - } - - public function testAssertFileNotEquals(): void - { - $this->assertFileNotEquals( - TEST_FILES_PATH . 'foo.xml', - TEST_FILES_PATH . 'bar.xml' - ); - - $this->expectException(AssertionFailedError::class); - - $this->assertFileNotEquals( - TEST_FILES_PATH . 'foo.xml', - TEST_FILES_PATH . 'foo.xml' - ); - } - - public function testAssertStringEqualsFile(): void - { - $this->assertStringEqualsFile( - TEST_FILES_PATH . 'foo.xml', - file_get_contents(TEST_FILES_PATH . 'foo.xml') - ); - - $this->expectException(AssertionFailedError::class); - - $this->assertStringEqualsFile( - TEST_FILES_PATH . 'foo.xml', - file_get_contents(TEST_FILES_PATH . 'bar.xml') - ); - } - - public function testAssertStringEqualsFileIgnoringCase(): void - { - $this->assertStringEqualsFileIgnoringCase( - TEST_FILES_PATH . 'foo.xml', - file_get_contents(TEST_FILES_PATH . 'fooUppercase.xml') - ); - - $this->expectException(AssertionFailedError::class); - - $this->assertStringEqualsFileIgnoringCase( - TEST_FILES_PATH . 'foo.xml', - file_get_contents(TEST_FILES_PATH . 'bar.xml') - ); - } - - public function testAssertStringNotEqualsFile(): void - { - $this->assertStringNotEqualsFile( - TEST_FILES_PATH . 'foo.xml', - file_get_contents(TEST_FILES_PATH . 'bar.xml') - ); - - $this->expectException(AssertionFailedError::class); - - $this->assertStringNotEqualsFile( - TEST_FILES_PATH . 'foo.xml', - file_get_contents(TEST_FILES_PATH . 'foo.xml') - ); - } - - public function testAssertStringNotEqualsFileIgnoringCase(): void - { - $this->assertStringNotEqualsFileIgnoringCase( - TEST_FILES_PATH . 'foo.xml', - file_get_contents(TEST_FILES_PATH . 'bar.xml') - ); - - $this->expectException(AssertionFailedError::class); - - $this->assertStringNotEqualsFileIgnoringCase( - TEST_FILES_PATH . 'foo.xml', - file_get_contents(TEST_FILES_PATH . 'fooUppercase.xml') - ); - } - - public function testAssertFileEqualsIgnoringCase(): void - { - $this->assertFileEqualsIgnoringCase( - TEST_FILES_PATH . 'foo.xml', - TEST_FILES_PATH . 'fooUppercase.xml' - ); - - $this->expectException(AssertionFailedError::class); - - $this->assertFileEqualsIgnoringCase( - TEST_FILES_PATH . 'foo.xml', - TEST_FILES_PATH . 'bar.xml' - ); - } - - public function testAssertFileNotEqualsIgnoringCase(): void - { - $this->assertFileNotEqualsIgnoringCase( - TEST_FILES_PATH . 'foo.xml', - TEST_FILES_PATH . 'bar.xml' - ); - - $this->expectException(AssertionFailedError::class); - - $this->assertFileNotEqualsIgnoringCase( - TEST_FILES_PATH . 'foo.xml', - TEST_FILES_PATH . 'fooUppercase.xml' - ); - } - - public function testAssertStringStartsNotWithThrowsException2(): void - { - $this->expectException(Exception::class); - - $this->assertStringStartsNotWith('', null); - } - - public function testAssertStringStartsWith(): void - { - $this->assertStringStartsWith('prefix', 'prefixfoo'); - - $this->expectException(AssertionFailedError::class); - - $this->assertStringStartsWith('prefix', 'foo'); - } - - public function testAssertStringStartsNotWith(): void - { - $this->assertStringStartsNotWith('prefix', 'foo'); - - $this->expectException(AssertionFailedError::class); - - $this->assertStringStartsNotWith('prefix', 'prefixfoo'); - } - - public function testAssertStringEndsWith(): void - { - $this->assertStringEndsWith('suffix', 'foosuffix'); - - $this->expectException(AssertionFailedError::class); - - $this->assertStringEndsWith('suffix', 'foo'); - } - - public function testAssertStringEndsNotWith(): void - { - $this->assertStringEndsNotWith('suffix', 'foo'); - - $this->expectException(AssertionFailedError::class); - - $this->assertStringEndsNotWith('suffix', 'foosuffix'); - } - - public function testAssertStringMatchesFormat(): void - { - $this->assertStringMatchesFormat('*%s*', '***'); - } - - public function testAssertStringMatchesFormatFailure(): void - { - $this->expectException(AssertionFailedError::class); - - $this->assertStringMatchesFormat('*%s*', '**'); - } - - public function testAssertStringNotMatchesFormat(): void - { - $this->assertStringNotMatchesFormat('*%s*', '**'); - - $this->expectException(AssertionFailedError::class); - - $this->assertStringMatchesFormat('*%s*', '**'); - } - - public function testAssertEmpty(): void - { - $this->assertEmpty([]); - - $this->expectException(AssertionFailedError::class); - - $this->assertEmpty(['foo']); - } - - public function testAssertNotEmpty(): void - { - $this->assertNotEmpty(['foo']); - - $this->expectException(AssertionFailedError::class); - - $this->assertNotEmpty([]); - } - - public function testMarkTestIncomplete(): void - { - try { - $this->markTestIncomplete('incomplete'); - } catch (IncompleteTestError $e) { - $this->assertEquals('incomplete', $e->getMessage()); - - return; - } - - $this->fail(); - } - - public function testMarkTestSkipped(): void - { - try { - $this->markTestSkipped('skipped'); - } catch (SkippedTestError $e) { - $this->assertEquals('skipped', $e->getMessage()); - - return; - } - - $this->fail(); - } - - public function testAssertCount(): void - { - $this->assertCount(2, [1, 2]); - - $this->expectException(AssertionFailedError::class); - - $this->assertCount(2, [1, 2, 3]); - } - - public function testAssertCountTraversable(): void - { - $this->assertCount(2, new ArrayIterator([1, 2])); - - $this->expectException(AssertionFailedError::class); - - $this->assertCount(2, new ArrayIterator([1, 2, 3])); - } - - public function testAssertCountThrowsExceptionIfElementIsNotCountable(): void - { - try { - $this->assertCount(2, ''); - } catch (Exception $e) { - $this->assertEquals('Argument #2 of PHPUnit\Framework\Assert::assertCount() must be a countable or iterable', $e->getMessage()); - - return; - } - - $this->fail(); - } - - public function testAssertNotCount(): void - { - $this->assertNotCount(2, [1, 2, 3]); - - $this->expectException(AssertionFailedError::class); - - $this->assertNotCount(2, [1, 2]); - } - - public function testAssertNotCountThrowsExceptionIfElementIsNotCountable(): void - { - $this->expectException(Exception::class); - - $this->assertNotCount(2, ''); - } - - public function testAssertSameSize(): void - { - $this->assertSameSize([1, 2], [3, 4]); - - $this->expectException(AssertionFailedError::class); - - $this->assertSameSize([1, 2], [1, 2, 3]); - } - - public function testAssertSameSizeThrowsExceptionIfExpectedIsNotCountable(): void - { - try { - $this->assertSameSize('a', []); - } catch (Exception $e) { - $this->assertEquals('Argument #1 of PHPUnit\Framework\Assert::assertSameSize() must be a countable or iterable', $e->getMessage()); - - return; - } - - $this->fail(); - } - - public function testAssertSameSizeThrowsExceptionIfActualIsNotCountable(): void - { - try { - $this->assertSameSize([], ''); - } catch (Exception $e) { - $this->assertEquals('Argument #2 of PHPUnit\Framework\Assert::assertSameSize() must be a countable or iterable', $e->getMessage()); - - return; - } - - $this->fail(); - } - - public function testAssertNotSameSize(): void - { - $this->assertNotSameSize([1, 2], [1, 2, 3]); - - $this->expectException(AssertionFailedError::class); - - $this->assertNotSameSize([1, 2], [3, 4]); - } - - public function testAssertNotSameSizeThrowsExceptionIfExpectedIsNotCountable(): void - { - $this->expectException(Exception::class); - - $this->assertNotSameSize('a', []); - } - - public function testAssertNotSameSizeThrowsExceptionIfActualIsNotCountable(): void - { - $this->expectException(Exception::class); - - $this->assertNotSameSize([], ''); - } - - /** - * @testdox Assert JSON - */ - public function testAssertJson(): void - { - $this->assertJson('{}'); - } - - /** - * @testdox Assert JSON string equals JSON string - */ - public function testAssertJsonStringEqualsJsonString(): void - { - $expected = '{"Mascott" : "Tux"}'; - $actual = '{"Mascott" : "Tux"}'; - $message = 'Given Json strings do not match'; - - $this->assertJsonStringEqualsJsonString($expected, $actual, $message); - } - - /** - * @dataProvider validInvalidJsonDataprovider - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function testAssertJsonStringEqualsJsonStringErrorRaised($expected, $actual): void - { - $this->expectException(AssertionFailedError::class); - - $this->assertJsonStringEqualsJsonString($expected, $actual); - } - - public function testAssertJsonStringNotEqualsJsonString(): void - { - $expected = '{"Mascott" : "Beastie"}'; - $actual = '{"Mascott" : "Tux"}'; - $message = 'Given Json strings do match'; - - $this->assertJsonStringNotEqualsJsonString($expected, $actual, $message); - } - - /** - * @testdox Assert JSON string equals equals JSON string raised $_dataName - * @dataProvider validInvalidJsonDataprovider - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function testAssertJsonStringNotEqualsJsonStringErrorRaised($expected, $actual): void - { - $this->expectException(AssertionFailedError::class); - - $this->assertJsonStringNotEqualsJsonString($expected, $actual); - } - - public function testAssertJsonStringEqualsJsonFile(): void - { - $file = TEST_FILES_PATH . 'JsonData/simpleObject.json'; - $actual = json_encode(['Mascott' => 'Tux']); - $message = ''; - - $this->assertJsonStringEqualsJsonFile($file, $actual, $message); - } - - public function testAssertJsonStringEqualsJsonFileExpectingExpectationFailedException(): void - { - $file = TEST_FILES_PATH . 'JsonData/simpleObject.json'; - $actual = json_encode(['Mascott' => 'Beastie']); - $message = ''; - - try { - $this->assertJsonStringEqualsJsonFile($file, $actual, $message); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - 'Failed asserting that \'{"Mascott":"Beastie"}\' matches JSON string "{"Mascott":"Tux"}".', - $e->getMessage() - ); - - return; - } - - $this->fail('Expected Exception not thrown.'); - } - - public function testAssertJsonStringNotEqualsJsonFile(): void - { - $file = TEST_FILES_PATH . 'JsonData/simpleObject.json'; - $actual = json_encode(['Mascott' => 'Beastie']); - $message = ''; - - $this->assertJsonStringNotEqualsJsonFile($file, $actual, $message); - } - - public function testAssertJsonFileNotEqualsJsonFile(): void - { - $fileExpected = TEST_FILES_PATH . 'JsonData/simpleObject.json'; - $fileActual = TEST_FILES_PATH . 'JsonData/arrayObject.json'; - $message = ''; - - $this->assertJsonFileNotEqualsJsonFile($fileExpected, $fileActual, $message); - } - - public function testAssertJsonFileEqualsJsonFile(): void - { - $file = TEST_FILES_PATH . 'JsonData/simpleObject.json'; - $message = ''; - - $this->assertJsonFileEqualsJsonFile($file, $file, $message); - } - - public function testAssertInstanceOfThrowsExceptionIfTypeDoesNotExist(): void - { - $this->expectException(Exception::class); - - $this->assertInstanceOf('ClassThatDoesNotExist', new stdClass); - } - - public function testAssertInstanceOf(): void - { - $this->assertInstanceOf(stdClass::class, new stdClass); - - $this->expectException(AssertionFailedError::class); - - $this->assertInstanceOf(\Exception::class, new stdClass); - } - - public function testAssertNotInstanceOfThrowsExceptionIfTypeDoesNotExist(): void - { - $this->expectException(Exception::class); - - $this->assertNotInstanceOf('ClassThatDoesNotExist', new stdClass); - } - - public function testAssertNotInstanceOf(): void - { - $this->assertNotInstanceOf(\Exception::class, new stdClass); - - $this->expectException(AssertionFailedError::class); - - $this->assertNotInstanceOf(stdClass::class, new stdClass); - } - - public function testAssertStringMatchesFormatFileThrowsExceptionForInvalidArgument(): void - { - $this->expectException(Exception::class); - - $this->assertStringMatchesFormatFile('not_existing_file', ''); - } - - public function testAssertStringMatchesFormatFile(): void - { - $this->assertStringMatchesFormatFile(TEST_FILES_PATH . 'expectedFileFormat.txt', "FOO\n"); - - $this->expectException(AssertionFailedError::class); - - $this->assertStringMatchesFormatFile(TEST_FILES_PATH . 'expectedFileFormat.txt', "BAR\n"); - } - - public function testAssertStringNotMatchesFormatFileThrowsExceptionForInvalidArgument(): void - { - $this->expectException(Exception::class); - - $this->assertStringNotMatchesFormatFile('not_existing_file', ''); - } - - public function testAssertStringNotMatchesFormatFile(): void - { - $this->assertStringNotMatchesFormatFile(TEST_FILES_PATH . 'expectedFileFormat.txt', "BAR\n"); - - $this->expectException(AssertionFailedError::class); - - $this->assertStringNotMatchesFormatFile(TEST_FILES_PATH . 'expectedFileFormat.txt', "FOO\n"); - } - - public function testStringsCanBeComparedForEqualityIgnoringCase(): void - { - $this->assertEqualsIgnoringCase('a', 'A'); - - $this->assertNotEqualsIgnoringCase('a', 'B'); - } - - public function testArraysOfStringsCanBeComparedForEqualityIgnoringCase(): void - { - $this->assertEqualsIgnoringCase(['a'], ['A']); - - $this->assertNotEqualsIgnoringCase(['a'], ['B']); - } - - public function testStringsCanBeComparedForEqualityWithDelta(): void - { - $this->assertEqualsWithDelta(2.3, 2.5, 0.5); - - $this->assertNotEqualsWithDelta(2.3, 3.5, 0.5); - } - - public function testArraysOfStringsCanBeComparedForEqualityWithDelta(): void - { - $this->assertEqualsWithDelta([2.3], [2.5], 0.5); - - $this->assertNotEqualsWithDelta([2.3], [3.5], 0.5); - } - - public function testArraysCanBeComparedForEqualityWithCanonicalization(): void - { - $this->assertEqualsCanonicalizing([3, 2, 1], [2, 3, 1]); - - $this->assertNotEqualsCanonicalizing([3, 2, 1], [2, 3, 4]); - } - - public function testArrayTypeCanBeAsserted(): void - { - $this->assertIsArray([]); - - try { - $this->assertIsArray(null); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testBoolTypeCanBeAsserted(): void - { - $this->assertIsBool(true); - - try { - $this->assertIsBool(null); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testFloatTypeCanBeAsserted(): void - { - $this->assertIsFloat(0.0); - - try { - $this->assertIsFloat(null); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testIntTypeCanBeAsserted(): void - { - $this->assertIsInt(1); - - try { - $this->assertIsInt(null); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testNumericTypeCanBeAsserted(): void - { - $this->assertIsNumeric('1.0'); - - try { - $this->assertIsNumeric('abc'); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testObjectTypeCanBeAsserted(): void - { - $this->assertIsObject(new stdClass); - - try { - $this->assertIsObject(null); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testResourceTypeCanBeAsserted(): void - { - $this->assertIsResource(fopen(__FILE__, 'r')); - - try { - $this->assertIsResource(null); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testStringTypeCanBeAsserted(): void - { - $this->assertIsString(''); - - try { - $this->assertIsString(null); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testScalarTypeCanBeAsserted(): void - { - $this->assertIsScalar(true); - - try { - $this->assertIsScalar(new stdClass); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testCallableTypeCanBeAsserted(): void - { - $this->assertIsCallable(function (): void { - }); - - try { - $this->assertIsCallable(null); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testIterableTypeCanBeAsserted(): void - { - $this->assertIsIterable([]); - - try { - $this->assertIsIterable(null); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testNotArrayTypeCanBeAsserted(): void - { - $this->assertIsNotArray(null); - - try { - $this->assertIsNotArray([]); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testNotBoolTypeCanBeAsserted(): void - { - $this->assertIsNotBool(null); - - try { - $this->assertIsNotBool(true); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testNotFloatTypeCanBeAsserted(): void - { - $this->assertIsNotFloat(null); - - try { - $this->assertIsNotFloat(0.0); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testNotIntTypeCanBeAsserted(): void - { - $this->assertIsNotInt(null); - - try { - $this->assertIsNotInt(1); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testNotNumericTypeCanBeAsserted(): void - { - $this->assertIsNotNumeric('abc'); - - try { - $this->assertIsNotNumeric('1.0'); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testNotObjectTypeCanBeAsserted(): void - { - $this->assertIsNotObject(null); - - try { - $this->assertIsNotObject(new stdClass); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testNotResourceTypeCanBeAsserted(): void - { - $this->assertIsNotResource(null); - - try { - $this->assertIsNotResource(fopen(__FILE__, 'r')); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testNotScalarTypeCanBeAsserted(): void - { - $this->assertIsNotScalar(new stdClass); - - try { - $this->assertIsNotScalar(true); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testNotStringTypeCanBeAsserted(): void - { - $this->assertIsNotString(null); - - try { - $this->assertIsNotString(''); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testNotCallableTypeCanBeAsserted(): void - { - $this->assertIsNotCallable(null); - - try { - $this->assertIsNotCallable(function (): void { - }); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testNotIterableTypeCanBeAsserted(): void - { - $this->assertIsNotIterable(null); - - try { - $this->assertIsNotIterable([]); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testLogicalAnd(): void - { - $this->assertThat( - true, - $this->logicalAnd( - $this->isTrue(), - $this->isTrue() - ) - ); - - $this->expectException(AssertionFailedError::class); - - $this->assertThat( - true, - $this->logicalAnd( - $this->isTrue(), - $this->isFalse() - ) - ); - } - - public function testLogicalOr(): void - { - $this->assertThat( - true, - $this->logicalOr( - $this->isTrue(), - $this->isFalse() - ) - ); - - $this->expectException(AssertionFailedError::class); - - $this->assertThat( - true, - $this->logicalOr( - $this->isFalse(), - $this->isFalse() - ) - ); - } - - public function testLogicalXor(): void - { - $this->assertThat( - true, - $this->logicalXor( - $this->isTrue(), - $this->isFalse() - ) - ); - - $this->expectException(AssertionFailedError::class); - - $this->assertThat( - true, - $this->logicalXor( - $this->isTrue(), - $this->isTrue() - ) - ); - } - - public function testStringContainsStringCanBeAsserted(): void - { - $this->assertStringContainsString('bar', 'foobarbaz'); - - try { - $this->assertStringContainsString('barbara', 'foobarbaz'); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testStringNotContainsStringCanBeAsserted(): void - { - $this->assertStringNotContainsString('barbara', 'foobarbaz'); - - try { - $this->assertStringNotContainsString('bar', 'foobarbaz'); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testStringContainsStringCanBeAssertedIgnoringCase(): void - { - $this->assertStringContainsStringIgnoringCase('BAR', 'foobarbaz'); - - try { - $this->assertStringContainsStringIgnoringCase('BARBARA', 'foobarbaz'); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testStringNotContainsStringCanBeAssertedIgnoringCase(): void - { - $this->assertStringNotContainsStringIgnoringCase('BARBARA', 'foobarbaz'); - - try { - $this->assertStringNotContainsStringIgnoringCase('BAR', 'foobarbaz'); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testIterableContainsSameObjectCanBeAsserted(): void - { - $object = new stdClass; - $iterable = [$object]; - - $this->assertContains($object, $iterable); - - try { - $this->assertContains(new stdClass, $iterable); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testIterableNotContainsSameObjectCanBeAsserted(): void - { - $object = new stdClass; - $iterable = [$object]; - - $this->assertNotContains(new stdClass, $iterable); - - try { - $this->assertNotContains($object, $iterable); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testIterableContainsEqualObjectCanBeAsserted(): void - { - $a = new stdClass; - $a->foo = 'bar'; - - $b = new stdClass; - $b->foo = 'baz'; - - $this->assertContainsEquals($a, [$a]); - - try { - $this->assertContainsEquals($b, [$a]); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - public function testIterableNotContainsEqualObjectCanBeAsserted(): void - { - $a = new stdClass; - $a->foo = 'bar'; - - $b = new stdClass; - $b->foo = 'baz'; - - $this->assertNotContainsEquals($b, [$a]); - - try { - $this->assertNotContainsEquals($a, [$a]); - } catch (AssertionFailedError $e) { - return; - } - - $this->fail(); - } - - protected function sameValues(): array - { - $object = new SampleClass(4, 8, 15); - $file = TEST_FILES_PATH . 'foo.xml'; - $resource = fopen($file, 'r'); - - return [ - // null - [null, null], - // strings - ['a', 'a'], - // integers - [0, 0], - // floats - [2.3, 2.3], - [1 / 3, 1 - 2 / 3], - [log(0), log(0)], - // arrays - [[], []], - [[0 => 1], [0 => 1]], - [[0 => null], [0 => null]], - [['a', 'b' => [1, 2]], ['a', 'b' => [1, 2]]], - // objects - [$object, $object], - // resources - [$resource, $resource], - ]; - } - - protected function notEqualValues(): array - { - // cyclic dependencies - $book1 = new Book; - $book1->author = new Author('Terry Pratchett'); - $book1->author->books[] = $book1; - $book2 = new Book; - $book2->author = new Author('Terry Pratch'); - $book2->author->books[] = $book2; - - $book3 = new Book; - $book3->author = 'Terry Pratchett'; - $book4 = new stdClass; - $book4->author = 'Terry Pratchett'; - - $object1 = new SampleClass(4, 8, 15); - $object2 = new SampleClass(16, 23, 42); - $object3 = new SampleClass(4, 8, 15); - $storage1 = new SplObjectStorage; - $storage1->attach($object1); - $storage2 = new SplObjectStorage; - $storage2->attach($object3); // same content, different object - - $file = TEST_FILES_PATH . 'foo.xml'; - - return [ - // strings - ['a', 'b'], - ['a', 'A'], - // https://github.com/sebastianbergmann/phpunit/issues/1023 - ['9E6666666', '9E7777777'], - // integers - [1, 2], - [2, 1], - // floats - [2.3, 4.2], - [2.3, 4.2, 0.5], - [[2.3], [4.2], 0.5], - [[[2.3]], [[4.2]], 0.5], - [new Struct(2.3), new Struct(4.2), 0.5], - [[new Struct(2.3)], [new Struct(4.2)], 0.5], - // NAN - [NAN, NAN], - // arrays - [[], [0 => 1]], - [[0 => 1], []], - [[0 => null], []], - [[0 => 1, 1 => 2], [0 => 1, 1 => 3]], - [['a', 'b' => [1, 2]], ['a', 'b' => [2, 1]]], - // objects - [new SampleClass(4, 8, 15), new SampleClass(16, 23, 42)], - [$object1, $object2], - [$book1, $book2], - [$book3, $book4], // same content, different class - // resources - [fopen($file, 'r'), fopen($file, 'r')], - // SplObjectStorage - [$storage1, $storage2], - // DOMDocument - [ - Xml::load(''), - Xml::load(''), - ], - [ - Xml::load(''), - Xml::load(''), - ], - [ - Xml::load(' bar '), - Xml::load(''), - ], - [ - Xml::load(''), - Xml::load(''), - ], - [ - Xml::load(' bar '), - Xml::load(' bir '), - ], - [ - new DateTime('2013-03-29 04:13:35', new DateTimeZone('America/New_York')), - new DateTime('2013-03-29 03:13:35', new DateTimeZone('America/New_York')), - ], - [ - new DateTime('2013-03-29 04:13:35', new DateTimeZone('America/New_York')), - new DateTime('2013-03-29 03:13:35', new DateTimeZone('America/New_York')), - 3500, - ], - [ - new DateTime('2013-03-29 04:13:35', new DateTimeZone('America/New_York')), - new DateTime('2013-03-29 05:13:35', new DateTimeZone('America/New_York')), - 3500, - ], - [ - new DateTime('2013-03-29', new DateTimeZone('America/New_York')), - new DateTime('2013-03-30', new DateTimeZone('America/New_York')), - ], - [ - new DateTime('2013-03-29', new DateTimeZone('America/New_York')), - new DateTime('2013-03-30', new DateTimeZone('America/New_York')), - 43200, - ], - [ - new DateTime('2013-03-29 04:13:35', new DateTimeZone('America/New_York')), - new DateTime('2013-03-29 04:13:35', new DateTimeZone('America/Chicago')), - ], - [ - new DateTime('2013-03-29 04:13:35', new DateTimeZone('America/New_York')), - new DateTime('2013-03-29 04:13:35', new DateTimeZone('America/Chicago')), - 3500, - ], - [ - new DateTime('2013-03-30', new DateTimeZone('America/New_York')), - new DateTime('2013-03-30', new DateTimeZone('America/Chicago')), - ], - [ - new DateTime('2013-03-29T05:13:35-0600'), - new DateTime('2013-03-29T04:13:35-0600'), - ], - [ - new DateTime('2013-03-29T05:13:35-0600'), - new DateTime('2013-03-29T05:13:35-0500'), - ], - // Exception - //array(new Exception('Exception 1'), new Exception('Exception 2')), - // different types - [new SampleClass(4, 8, 15), false], - [false, new SampleClass(4, 8, 15)], - [[0 => 1, 1 => 2], false], - [false, [0 => 1, 1 => 2]], - [[], new stdClass], - [new stdClass, []], - // PHP: 0 == 'Foobar' => true! - // We want these values to differ - [0, 'Foobar'], - ['Foobar', 0], - [3, acos(8)], - [acos(8), 3], - ]; - } - - protected function equalValues(): array - { - // cyclic dependencies - $book1 = new Book; - $book1->author = new Author('Terry Pratchett'); - $book1->author->books[] = $book1; - $book2 = new Book; - $book2->author = new Author('Terry Pratchett'); - $book2->author->books[] = $book2; - - $object1 = new SampleClass(4, 8, 15); - $object2 = new SampleClass(4, 8, 15); - $storage1 = new SplObjectStorage; - $storage1->attach($object1); - $storage2 = new SplObjectStorage; - $storage2->attach($object1); - - return [ - // arrays - [['a' => 1, 'b' => 2], ['b' => 2, 'a' => 1]], - [[1], ['1']], - // objects - [$object1, $object2], - [$book1, $book2], - // SplObjectStorage - [$storage1, $storage2], - // DOMDocument - [ - Xml::load(''), - Xml::load(''), - ], - [ - Xml::load(''), - Xml::load(''), - ], - [ - Xml::load(''), - Xml::load(''), - ], - [ - Xml::load("\n \n"), - Xml::load(''), - ], - [ - new DateTime('2013-03-29 04:13:35', new DateTimeZone('America/New_York')), - new DateTime('2013-03-29 04:13:35', new DateTimeZone('America/New_York')), - ], - [ - new DateTime('2013-03-29', new DateTimeZone('America/New_York')), - new DateTime('2013-03-29', new DateTimeZone('America/New_York')), - ], - [ - new DateTime('2013-03-29 04:13:35', new DateTimeZone('America/New_York')), - new DateTime('2013-03-29 03:13:35', new DateTimeZone('America/Chicago')), - ], - [ - new DateTime('2013-03-30', new DateTimeZone('America/New_York')), - new DateTime('2013-03-29 23:00:00', new DateTimeZone('America/Chicago')), - ], - [ - new DateTime('@1364616000'), - new DateTime('2013-03-29 23:00:00', new DateTimeZone('America/Chicago')), - ], - [ - new DateTime('2013-03-29T05:13:35-0500'), - new DateTime('2013-03-29T04:13:35-0600'), - ], - // Exception - //array(new Exception('Exception 1'), new Exception('Exception 1')), - // mixed types - [0, '0'], - ['0', 0], - [2.3, '2.3'], - ['2.3', 2.3], - [(string) (1 / 3), 1 - 2 / 3], - [1 / 3, (string) (1 - 2 / 3)], - ['string representation', new ClassWithToString], - [new ClassWithToString, 'string representation'], - ]; - } -} diff --git a/tests/unit/Framework/Constraint/ArrayHasKeyTest.php b/tests/unit/Framework/Constraint/ArrayHasKeyTest.php deleted file mode 100644 index 0301867e60f..00000000000 --- a/tests/unit/Framework/Constraint/ArrayHasKeyTest.php +++ /dev/null @@ -1,85 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestFailure; - -/** - * @small - */ -final class ArrayHasKeyTest extends ConstraintTestCase -{ - public function testConstraintArrayHasKey(): void - { - $constraint = new ArrayHasKey(0); - - $this->assertFalse($constraint->evaluate([], '', true)); - $this->assertEquals('has the key 0', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate([]); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that an array has the key 0. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintArrayHasKey2(): void - { - $constraint = new ArrayHasKey(0); - - try { - $constraint->evaluate([], 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<fail(); - } - - public function testConstraintArrayHasKey0(): void - { - $constraint = new ArrayHasKey(0); - - try { - $constraint->evaluate(0, ''); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that an array has the key 0. - -EOF - , - TestFailure::exceptionToString($e) - ); - } - } -} diff --git a/tests/unit/Framework/Constraint/BinaryOperatorTestCase.php b/tests/unit/Framework/Constraint/BinaryOperatorTestCase.php deleted file mode 100644 index db9c1b8bcd2..00000000000 --- a/tests/unit/Framework/Constraint/BinaryOperatorTestCase.php +++ /dev/null @@ -1,583 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use function array_fill; -use function array_map; -use function array_slice; -use function array_sum; -use function count; -use function decbin; -use function implode; -use function sprintf; -use function str_split; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\TestFixture\BooleanConstraint; -use PHPUnit\TestFixture\CountConstraint; -use PHPUnit\TestFixture\NamedConstraint; -use ReflectionClass; -use ReflectionMethod; - -abstract class BinaryOperatorTestCase extends OperatorTestCase -{ - /** - * Shall return the name of the operator under test. - */ - abstract public static function getOperatorName(): string; - - /** - * Shall return the precedence of the operator under test. - */ - abstract public static function getOperatorPrecedence(): int; - - /** - * Takes an array or boolean values and returns the expected evaluation - * result for the logical operator under test. - */ - abstract public function evaluateExpectedResult(array $input): bool; - - final public function testIsSubclassOfOperator(): void - { - $className = $this->className(); - - $reflection = new ReflectionClass($className); - - $this->assertTrue($reflection->isSubclassOf(Operator::class), sprintf( - 'Failed to assert that "%s" is subclass of "%s".', - $className, - Operator::class - )); - } - - final public function testOperatorName(): void - { - $className = $this->className(); - $constraint = new $className; - $this->assertSame($this->getOperatorName(), $constraint->operator()); - } - - final public function testOperatorPrecedence(): void - { - $className = $this->className(); - $constraint = new $className; - $this->assertSame($this->getOperatorPrecedence(), $constraint->precedence()); - } - - final public function testOperatorCount(): void - { - $counts = [ - 3, - 5, - 8, - ]; - - $constraints = array_map(function ($count) { - return CountConstraint::fromCount($count); - }, $counts); - - $className = $this->className(); - - $constraint = new $className; - - $constraint->setConstraints($constraints); - - $expected = array_sum($counts); - - $this->assertSame($expected, $constraint->count()); - } - - final public function testOperatorArity(): void - { - $constraints = [ - CountConstraint::fromCount(3), - CountConstraint::fromCount(5), - CountConstraint::fromCount(8), - ]; - - $className = $this->className(); - - $constraint = new $className; - - $constraint->setConstraints($constraints); - - $expected = count($constraints); - - $this->assertSame($expected, $constraint->arity()); - } - - public function testFromConstraints(): void - { - $operand = $this->getMockBuilder(Constraint::class)->getMock(); - $className = $this->className(); - - for ($arity = 0; $arity <= 3; $arity++) { - $constraints = array_fill(0, $arity, $operand); - $constraint = $className::fromConstraints(...$constraints); - - $this->assertInstanceOf($className, $constraint); - - $this->assertSame($arity, $constraint->arity()); - } - } - - public function testSetConstraintsHandlesNonConstraintArguments(): void - { - $className = $this->className(); - - $constraint = new $className; - - $constraint->setConstraints(['whatever']); - - $this->assertTrue($constraint->evaluate('whatever', '', true)); - - $this->assertSame("is equal to 'whatever'", $constraint->toString()); - } - - final public function providerConnectiveTruthTable() - { - $inputs = self::getBooleanTuples(0, 5); - - return array_map(function (array $input) { - return [$input, $this->evaluateExpectedResult($input)]; - }, $inputs); - } - - /** - * @dataProvider providerConnectiveTruthTable - */ - final public function testEvaluateReturnsCorrectBooleanResult(array $inputs, bool $expected): void - { - $constraints = array_map(function (bool $input) { - return BooleanConstraint::fromBool($input); - }, $inputs); - - $className = $this->className(); - - $constraint = $className::fromConstraints(...$constraints); - - $message = 'Failed asserting that ' . $constraint->toString() . ' is ' . ($expected ? 'true' : 'false'); - $this->assertSame($expected, $constraint->evaluate(null, '', true), $message); - } - - /** - * @dataProvider providerConnectiveTruthTable - */ - final public function testEvaluateReturnsNullOnSuccessAndThrowsExceptionOnFailure(array $inputs, bool $expected): void - { - $constraints = array_map(function (bool $input) { - return BooleanConstraint::fromBool($input); - }, $inputs); - - $className = $this->className(); - - $constraint = $className::fromConstraints(...$constraints); - - if ($expected) { - $this->assertNull($constraint->evaluate(null)); - } else { - $expectedString = self::operatorJoinStrings( - array_map( - function (Constraint $operand) { - return $operand->toString(); - }, - $constraints - ) - ); - $message = "Failed asserting that 'the following expression is true' " . $expectedString; - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage($message); - $constraint->evaluate('the following expression is true'); - } - } - - public function providerToStringWithNamedConstraints(): array - { - return [ - [ - ], - [ - 'is healthy', - ], - [ - 'is healthy', - 'is rich in amino acids', - ], - [ - 'is healthy', - 'is rich in amino acids', - 'is rich in unsaturated fats', - ], - ]; - } - - /** - * @dataProvider providerToStringWithNamedConstraints - */ - public function testToStringWithNamedConstraints(string ...$names): void - { - $constraints = array_map(function (string $name) { - return NamedConstraint::fromName($name); - }, $names); - - $className = $this->className(); - - $constraint = $className::fromConstraints(...$constraints); - - $expected = static::operatorJoinStrings($names); - - $this->assertSame($expected, $constraint->toString()); - } - - public function testToStringWithoutOperands(): void - { - $className = $this->className(); - - $operator = $className::fromConstraints(); - - $this->assertSame('', $operator->toString()); - } - - public function testToStringWithSingleOperand(): void - { - $methods = [ - 'arity', - 'precedence', - 'toStringInContext', - 'toString', - ]; - - $operand = $this->getMockBuilder(Operator::class) - ->setMethods($methods) - ->getMockForAbstractClass(); - - $className = $this->className(); - - $operator = $className::fromConstraints($operand); - - // A non-contextual operator - $operand->expects($this->never()) - ->method('arity'); - $operand->expects($this->never()) - ->method('precedence'); - $operand->expects($this->never()) - ->method('toStringInContext'); - $operand->expects($this->once()) - ->method('toString') - ->with() - ->willReturn('is the only'); - - $this->assertSame('is the only', $operator->toString()); - } - - public function testToStringWithMultipleOperands(): void - { - $constraintMethods = [ - 'toStringInContext', - 'toString', - ]; - - $operatorMethods = [ - 'arity', - 'precedence', - 'toStringInContext', - 'toString', - ]; - - $constraints = [ - $this->getMockBuilder(Constraint::class) - ->setMethods($constraintMethods) - ->getMockForAbstractClass(), - $this->getMockBuilder(Constraint::class) - ->setMethods($constraintMethods) - ->getMockForAbstractClass(), - $this->getMockBuilder(Operator::class) - ->setMethods($operatorMethods) - ->getMockForAbstractClass(), - $this->getMockBuilder(Operator::class) - ->setMethods($operatorMethods) - ->getMockForAbstractClass(), - $this->getMockBuilder(Operator::class) - ->setMethods($operatorMethods) - ->getMockForAbstractClass(), - ]; - - $className = $this->className(); - - $constraint = $className::fromConstraints(...$constraints); - - // A non-contextual non-operator constraint - $constraints[0]->expects($this->once()) - ->method('toStringInContext') - ->with($this->identicalTo($constraint), 0) - ->willReturn(''); - $constraints[0]->expects($this->once()) - ->method('toString') - ->with() - ->willReturn('is first'); - - // A contextual non-operator constraint - $constraints[1]->expects($this->once()) - ->method('toStringInContext') - ->with($this->identicalTo($constraint), 1) - ->willReturn('is second'); - $constraints[1]->expects($this->never()) - ->method('toString'); - - // An non-contextual operator constraint with arity = 2 and high precedence (no braces needed) - $constraints[2]->expects($this->once()) - ->method('arity') - ->with() - ->willReturn(2); - $constraints[2]->expects($this->once()) - ->method('precedence') - ->with() - ->willReturn(-1); - $constraints[2]->expects($this->once()) - ->method('toStringInContext') - ->with($this->identicalTo($constraint), 2) - ->willReturn(''); - $constraints[2]->expects($this->once()) - ->method('toString') - ->with() - ->willReturn('is third'); - - // A contextual operator constraint with arity = 1 (no braces needed) - $constraints[3]->expects($this->once()) - ->method('arity') - ->with() - ->willReturn(1); - $constraints[3]->expects($this->never()) - ->method('precedence'); - $constraints[3]->expects($this->once()) - ->method('toStringInContext') - ->with($this->identicalTo($constraint), 3) - ->willReturn('is fourth'); - $constraints[3]->expects($this->never()) - ->method('toString'); - - // An operator constraint with arity = 2 and low precedence (braces needed) - $constraints[4]->expects($this->once()) - ->method('arity') - ->with() - ->willReturn(2); - $constraints[4]->expects($this->once()) - ->method('precedence') - ->with() - ->willReturn(10000); - $constraints[4]->expects($this->never()) - ->method('toStringInContext'); - $constraints[4]->expects($this->once()) - ->method('toString') - ->with() - ->willReturn('is fifth or later'); - - $expected = self::operatorJoinStrings([ - 'is first', - 'is second', - 'is third', - 'is fourth', - '( is fifth or later )', - ]); - - $this->assertSame($expected, $constraint->toString()); - } - - public function testFailureDescriptionWithSingleOperand(): void - { - $methods = [ - 'arity', - 'precedence', - 'toStringInContext', - 'toString', - ]; - - $operand = $this->getMockBuilder(Operator::class) - ->setMethods($methods) - ->getMockForAbstractClass(); - - $className = $this->className(); - - $operator = $className::fromConstraints($operand); - - // A non-contextual operator with toString() - $operand->expects($this->never()) - ->method('arity'); - $operand->expects($this->never()) - ->method('precedence'); - $operand->expects($this->never()) - ->method('toStringInContext'); - $operand->expects($this->once()) - ->method('toString') - ->with() - ->willReturn('is the only'); - - $method = new ReflectionMethod($className, 'failureDescription'); - $method->setAccessible(true); - - $this->assertSame("'whatever' is the only", $method->invoke($operator, 'whatever')); - } - - public function testFailureDescriptionWithMultipleOperands(): void - { - $constraintMethods = [ - 'toStringInContext', - 'toString', - ]; - - $operatorMethods = [ - 'arity', - 'precedence', - 'toStringInContext', - 'toString', - ]; - - $constraints = [ - $this->getMockBuilder(Constraint::class) - ->setMethods($constraintMethods) - ->getMockForAbstractClass(), - $this->getMockBuilder(Constraint::class) - ->setMethods($constraintMethods) - ->getMockForAbstractClass(), - $this->getMockBuilder(Operator::class) - ->setMethods($operatorMethods) - ->getMockForAbstractClass(), - $this->getMockBuilder(Operator::class) - ->setMethods($operatorMethods) - ->getMockForAbstractClass(), - $this->getMockBuilder(Operator::class) - ->setMethods($operatorMethods) - ->getMockForAbstractClass(), - ]; - - $className = $this->className(); - - $constraint = $className::fromConstraints(...$constraints); - - // A non-contextual non-operator constraint - $constraints[0]->expects($this->once()) - ->method('toStringInContext') - ->with($this->identicalTo($constraint), 0) - ->willReturn(''); - $constraints[0]->expects($this->once()) - ->method('toString') - ->with() - ->willReturn('is first'); - - // A contextual non-operator constraint - $constraints[1]->expects($this->once()) - ->method('toStringInContext') - ->with($this->identicalTo($constraint), 1) - ->willReturn('is second'); - $constraints[1]->expects($this->never()) - ->method('toString'); - - // An non-contextual operator constraint with arity = 2 and high precedence (no braces needed) - $constraints[2]->expects($this->once()) - ->method('arity') - ->with() - ->willReturn(2); - $constraints[2]->expects($this->once()) - ->method('precedence') - ->with() - ->willReturn(-1); - $constraints[2]->expects($this->once()) - ->method('toStringInContext') - ->with($this->identicalTo($constraint), 2) - ->willReturn(''); - $constraints[2]->expects($this->once()) - ->method('toString') - ->with() - ->willReturn('is third'); - - // A contextual operator constraint with arity = 1 (no braces needed) - $constraints[3]->expects($this->once()) - ->method('arity') - ->with() - ->willReturn(1); - $constraints[3]->expects($this->never()) - ->method('precedence'); - $constraints[3]->expects($this->once()) - ->method('toStringInContext') - ->with($this->identicalTo($constraint), 3) - ->willReturn('is fourth'); - $constraints[3]->expects($this->never()) - ->method('toString'); - - // An operator constraint with arity = 2 and low precedence (braces needed) - $constraints[4]->expects($this->once()) - ->method('arity') - ->with() - ->willReturn(2); - $constraints[4]->expects($this->once()) - ->method('precedence') - ->with() - ->willReturn(10000); - $constraints[4]->expects($this->never()) - ->method('toStringInContext'); - $constraints[4]->expects($this->once()) - ->method('toString') - ->with() - ->willReturn('is fifth or later'); - - $method = new ReflectionMethod($className, 'failureDescription'); - $method->setAccessible(true); - - $expectedToString = self::operatorJoinStrings([ - 'is first', - 'is second', - 'is third', - 'is fourth', - '( is fifth or later )', - ]); - $expected = "'whatever' " . $expectedToString; - $this->assertSame($expected, $method->invokeArgs($constraint, ['whatever'])); - } - - /** - * Generates an array of "binary tuples" of size $minSize up to (and - * including) $maxSize. - * - * A "binary tuple" is an array of 0s an 1s. The method generates all - * possible combinations of 0s and 1s of size $minSize up to $maxSize. - */ - final protected static function getBinaryTuples(int $minSize, int $maxSize): array - { - $tuples = []; - - for ($size = $minSize; $size <= $maxSize; $size++) { - for ($num = 0; $num < 2 ** $size; $num++) { - $str = decbin($num | 2 ** $size); // "1xyz" (extra "1" on the left) - $bits = array_map('intval', str_split($str)); // ["1", "x", "y", "z"] - $tuple = array_slice($bits, 1); // ["x", "y", "z"] - $tuples[] = $tuple; - } - } - - return $tuples; - } - - /** - * Same as getBinaryTuples(), but returns tuples of boolean values - * instead of integers. - */ - final protected static function getBooleanTuples(int $minSize, int $maxSize): array - { - $tuples = self::getBinaryTuples($minSize, $maxSize); - - return array_map(function ($tuple) { - return array_map('boolval', $tuple); - }, $tuples); - } - - protected static function operatorJoinStrings(array $strings): string - { - return implode(' ' . static::getOperatorName() . ' ', $strings); - } -} diff --git a/tests/unit/Framework/Constraint/Boolean/IsFalseTest.php b/tests/unit/Framework/Constraint/Boolean/IsFalseTest.php new file mode 100644 index 00000000000..16625a2417b --- /dev/null +++ b/tests/unit/Framework/Constraint/Boolean/IsFalseTest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(IsFalse::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class IsFalseTest extends TestCase +{ + public function testCanBeEvaluated(): void + { + $this->assertTrue((new IsFalse)->evaluate(false, returnResult: true)); + $this->assertFalse((new IsFalse)->evaluate(true, returnResult: true)); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('is false', (new IsFalse)->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new IsFalse)); + } +} diff --git a/tests/unit/Framework/Constraint/Boolean/IsTrueTest.php b/tests/unit/Framework/Constraint/Boolean/IsTrueTest.php new file mode 100644 index 00000000000..38b8ec46303 --- /dev/null +++ b/tests/unit/Framework/Constraint/Boolean/IsTrueTest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(IsTrue::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class IsTrueTest extends TestCase +{ + public function testCanBeEvaluated(): void + { + $this->assertTrue((new IsTrue)->evaluate(true, returnResult: true)); + $this->assertFalse((new IsTrue)->evaluate(false, returnResult: true)); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('is true', (new IsTrue)->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new IsTrue)); + } +} diff --git a/tests/unit/Framework/Constraint/CallbackTest.php b/tests/unit/Framework/Constraint/CallbackTest.php index 3add01293f7..978d549659c 100644 --- a/tests/unit/Framework/Constraint/CallbackTest.php +++ b/tests/unit/Framework/Constraint/CallbackTest.php @@ -9,60 +9,73 @@ */ namespace PHPUnit\Framework\Constraint; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; -/** - * @small - */ -final class CallbackTest extends ConstraintTestCase +#[CoversClass(Callback::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class CallbackTest extends TestCase { - public static function staticCallbackReturningTrue() + public function testCanBeEvaluated(): void { - return true; + $this->assertTrue($this->acceptingCallbackConstraint()->evaluate('actual', returnResult: true)); + $this->assertFalse($this->rejectingCallbackConstraint()->evaluate('actual', returnResult: true)); + + try { + $this->rejectingCallbackConstraint()->evaluate('actual'); + } catch (ExpectationFailedException $e) { + $this->assertSame('Failed asserting that \'actual\' is accepted by specified callback.', $e->getMessage()); + + return; + } + + $this->fail(); } - public function callbackReturningTrue() + public function testCanBeRepresentedAsString(): void { - return true; + $this->assertSame('is accepted by specified callback', $this->acceptingCallbackConstraint()->toString()); } - public function testConstraintCallback(): void + public function testIsCountable(): void { - $closureReflect = function ($parameter) { - return $parameter; - }; + $this->assertCount(1, $this->acceptingCallbackConstraint()); + } - $closureWithoutParameter = function () { - return true; + public function testIsVariadic(): void + { + $class = new class + { + public function __invoke(string ...$values): void + { + } }; - $constraint = new Callback($closureWithoutParameter); - $this->assertTrue($constraint->evaluate('', '', true)); - - $constraint = new Callback($closureReflect); - $this->assertTrue($constraint->evaluate(true, '', true)); - $this->assertFalse($constraint->evaluate(false, '', true)); - - $callback = [$this, 'callbackReturningTrue']; - $constraint = new Callback($callback); - $this->assertTrue($constraint->evaluate(false, '', true)); + $this->assertTrue(new Callback($class)->isVariadic()); + } - $callback = [self::class, 'staticCallbackReturningTrue']; - $constraint = new Callback($callback); - $this->assertTrue($constraint->evaluate(null, '', true)); + public function testIsNotVariadic(): void + { + $class = new class + { + public function __invoke(string $value): void + { + } + }; - $this->assertEquals('is accepted by specified callback', $constraint->toString()); + $this->assertFalse(new Callback($class)->isVariadic()); } - public function testConstraintCallbackFailure(): void + private function acceptingCallbackConstraint(): Callback { - $constraint = new Callback(function () { - return false; - }); - - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage('Failed asserting that \'This fails\' is accepted by specified callback.'); + return new Callback(static fn (): bool => true); + } - $constraint->evaluate('This fails'); + private function rejectingCallbackConstraint(): Callback + { + return new Callback(static fn (): bool => false); } } diff --git a/tests/unit/Framework/Constraint/Cardinality/CountTest.php b/tests/unit/Framework/Constraint/Cardinality/CountTest.php new file mode 100644 index 00000000000..8a890af4213 --- /dev/null +++ b/tests/unit/Framework/Constraint/Cardinality/CountTest.php @@ -0,0 +1,218 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use ArrayIterator; +use ArrayObject; +use EmptyIterator; +use Generator; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Exception; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\GeneratorNotSupportedException; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\ExceptionThrowingIteratorAggregate; +use PHPUnit\TestFixture\TestIterator; +use PHPUnit\TestFixture\TestIterator2; +use PHPUnit\TestFixture\TestIteratorAggregate; +use PHPUnit\TestFixture\TestIteratorAggregate2; + +#[CoversClass(Count::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class CountTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + '', + 0, + [], + ], + + [ + true, + '', + 1, + ['value'], + ], + + [ + true, + '', + 0, + new EmptyIterator, + ], + + [ + true, + '', + 5, + new ArrayObject([1, 2, 3, 4, 5]), + ], + + [ + true, + '', + 1, + new ArrayIterator(['value']), + ], + + [ + true, + '', + 2, + new TestIterator(['value', 'value']), + ], + + [ + true, + '', + 2, + new TestIteratorAggregate(new TestIterator(['value', 'value'])), + ], + + [ + true, + '', + 2, + new TestIteratorAggregate2(new TestIteratorAggregate(new TestIterator(['value', 'value']))), + ], + + [ + false, + 'Failed asserting that actual size 0 matches expected size 1.', + 1, + 1, + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $failureDescription, int $expected, mixed $actual): void + { + $constraint = new Count($expected); + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('count matches 1', new Count(1)->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new Count(1))); + } + + public function testCountDoesNotChangeIteratorKey(): void + { + $countConstraint = new Count(2); + + // test with 1st implementation of Iterator + $it = new TestIterator([1, 2]); + + $countConstraint->evaluate($it, returnResult: true); + $this->assertSame(1, $it->current()); + + $it->next(); + $countConstraint->evaluate($it, returnResult: true); + $this->assertSame(2, $it->current()); + + $it->next(); + $countConstraint->evaluate($it, returnResult: true); + $this->assertFalse($it->valid()); + + // test with 2nd implementation of Iterator + $it = new TestIterator2([1, 2]); + + $countConstraint = new Count(2); + $countConstraint->evaluate($it, returnResult: true); + $this->assertSame(1, $it->current()); + + $it->next(); + $countConstraint->evaluate($it, returnResult: true); + $this->assertSame(2, $it->current()); + + $it->next(); + $countConstraint->evaluate($it, returnResult: true); + $this->assertFalse($it->valid()); + + // test with IteratorAggregate + $it = new TestIterator([1, 2]); + $ia = new TestIteratorAggregate($it); + + $countConstraint = new Count(2); + $countConstraint->evaluate($ia, returnResult: true); + $this->assertSame(1, $it->current()); + + $it->next(); + $countConstraint->evaluate($ia, returnResult: true); + $this->assertSame(2, $it->current()); + + $it->next(); + $countConstraint->evaluate($ia, returnResult: true); + $this->assertFalse($it->valid()); + + // test with nested IteratorAggregate + $it = new TestIterator([1, 2]); + $ia = new TestIteratorAggregate($it); + $ia2 = new TestIteratorAggregate2($ia); + + $countConstraint = new Count(2); + $countConstraint->evaluate($ia2, returnResult: true); + $this->assertSame(1, $it->current()); + + $it->next(); + $countConstraint->evaluate($ia2, returnResult: true); + $this->assertSame(2, $it->current()); + + $it->next(); + $countConstraint->evaluate($ia2, returnResult: true); + $this->assertFalse($it->valid()); + } + + public function testDoesNotAcceptGenerators(): void + { + $constraint = new Count(0); + + $this->expectException(GeneratorNotSupportedException::class); + + $constraint->evaluate($this->generator()); + } + + public function testWrapsExceptionFromIteratorAggregate(): void + { + $constraint = new Count(0); + + $this->expectException(Exception::class); + + $constraint->evaluate(new ExceptionThrowingIteratorAggregate); + } + + private function generator(): Generator + { + yield 1; + } +} diff --git a/tests/unit/Framework/Constraint/Cardinality/GreaterThanTest.php b/tests/unit/Framework/Constraint/Cardinality/GreaterThanTest.php new file mode 100644 index 00000000000..9a6f287fbd7 --- /dev/null +++ b/tests/unit/Framework/Constraint/Cardinality/GreaterThanTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; + +#[CoversClass(GreaterThan::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class GreaterThanTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + '', + 0, + 1, + ], + + [ + false, + 'Failed asserting that 0 is greater than 1.', + 1, + 0, + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $failureDescription, mixed $expected, mixed $actual): void + { + $constraint = new GreaterThan($expected); + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('is greater than 0', new GreaterThan(0)->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new GreaterThan(1))); + } +} diff --git a/tests/unit/Framework/Constraint/Cardinality/IsEmptyTest.php b/tests/unit/Framework/Constraint/Cardinality/IsEmptyTest.php new file mode 100644 index 00000000000..a1c2b932a78 --- /dev/null +++ b/tests/unit/Framework/Constraint/Cardinality/IsEmptyTest.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use ArrayIterator; +use ArrayObject; +use EmptyIterator; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; + +#[CoversClass(IsEmpty::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class IsEmptyTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + '', + [], + ], + + [ + true, + '', + new EmptyIterator, + ], + + [ + true, + '', + new ArrayObject, + ], + + [ + true, + '', + new ArrayIterator([]), + ], + + [ + false, + 'Failed asserting that an array is empty.', + [0], + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $failureDescription, mixed $actual): void + { + $constraint = new IsEmpty; + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('is empty', (new IsEmpty)->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, new IsEmpty); + } +} diff --git a/tests/unit/Framework/Constraint/Cardinality/LessThanTest.php b/tests/unit/Framework/Constraint/Cardinality/LessThanTest.php new file mode 100644 index 00000000000..c14e962744b --- /dev/null +++ b/tests/unit/Framework/Constraint/Cardinality/LessThanTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; + +#[CoversClass(LessThan::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class LessThanTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + '', + 1, + 0, + ], + + [ + false, + 'Failed asserting that 1 is less than 0.', + 0, + 1, + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $failureDescription, mixed $expected, mixed $actual): void + { + $constraint = new LessThan($expected); + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('is less than 0', new LessThan(0)->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new LessThan(1))); + } +} diff --git a/tests/unit/Framework/Constraint/Cardinality/SameSizeTest.php b/tests/unit/Framework/Constraint/Cardinality/SameSizeTest.php new file mode 100644 index 00000000000..f11e53f7428 --- /dev/null +++ b/tests/unit/Framework/Constraint/Cardinality/SameSizeTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(SameSize::class)] +#[CoversClass(Count::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class SameSizeTest extends TestCase +{ + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('count matches 0', new SameSize([])->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new SameSize([]))); + } +} diff --git a/tests/unit/Framework/Constraint/ClassHasAttributeTest.php b/tests/unit/Framework/Constraint/ClassHasAttributeTest.php deleted file mode 100644 index ce52a81feb0..00000000000 --- a/tests/unit/Framework/Constraint/ClassHasAttributeTest.php +++ /dev/null @@ -1,75 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestFailure; -use PHPUnit\TestFixture\ClassWithNonPublicAttributes; -use stdClass; - -/** - * @small - */ -final class ClassHasAttributeTest extends ConstraintTestCase -{ - public function testConstraintClassHasAttribute(): void - { - $constraint = new ClassHasAttribute( - 'privateAttribute' - ); - - $this->assertTrue($constraint->evaluate(ClassWithNonPublicAttributes::class, '', true)); - $this->assertFalse($constraint->evaluate(stdClass::class, '', true)); - $this->assertEquals('has attribute "privateAttribute"', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate(stdClass::class); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that class "stdClass" has attribute "privateAttribute". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintClassHasAttribute2(): void - { - $constraint = new ClassHasAttribute( - 'privateAttribute' - ); - - try { - $constraint->evaluate(stdClass::class, 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that class "stdClass" has attribute "privateAttribute". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } -} diff --git a/tests/unit/Framework/Constraint/ClassHasStaticAttributeTest.php b/tests/unit/Framework/Constraint/ClassHasStaticAttributeTest.php deleted file mode 100644 index 832b4fdca77..00000000000 --- a/tests/unit/Framework/Constraint/ClassHasStaticAttributeTest.php +++ /dev/null @@ -1,71 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestFailure; -use PHPUnit\TestFixture\ClassWithNonPublicAttributes; -use stdClass; - -/** - * @small - */ -final class ClassHasStaticAttributeTest extends ConstraintTestCase -{ - public function testConstraintClassHasStaticAttribute(): void - { - $constraint = new ClassHasStaticAttribute('privateStaticAttribute'); - - $this->assertTrue($constraint->evaluate(ClassWithNonPublicAttributes::class, '', true)); - $this->assertFalse($constraint->evaluate(stdClass::class, '', true)); - $this->assertEquals('has static attribute "privateStaticAttribute"', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate(stdClass::class); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that class "stdClass" has static attribute "privateStaticAttribute". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintClassHasStaticAttribute2(): void - { - $constraint = new ClassHasStaticAttribute('foo'); - - try { - $constraint->evaluate(stdClass::class, 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that class "stdClass" has static attribute "foo". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } -} diff --git a/tests/unit/Framework/Constraint/ConstraintTest.php b/tests/unit/Framework/Constraint/ConstraintTest.php deleted file mode 100644 index bed4f80631e..00000000000 --- a/tests/unit/Framework/Constraint/ConstraintTest.php +++ /dev/null @@ -1,223 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use PHPUnit\Framework\ExpectationFailedException; -use SebastianBergmann\Comparator\ComparisonFailure; -use SebastianBergmann\Exporter\Exporter; -use stdClass; - -/** - * @small - */ -final class ConstraintTest extends ConstraintTestCase -{ - public static function getDummyConstraintInstance(): Constraint - { - return new class extends Constraint { - final public function toString(): string - { - return 'is ok'; - } - - final protected function matches($other): bool - { - return parent::matches($other); - } - - final protected function exporter(): Exporter - { - return parent::exporter(); - } - - final protected function reduce(): Constraint - { - return parent::reduce(); - } - - final protected function fail($other, $description, ComparisonFailure $comparisonFailure = null): void - { - parent::fail($other, $description, $comparisonFailure); - } - - final protected function additionalFailureDescription($other): string - { - return parent::additionalFailureDescription($other); - } - - final protected function failureDescription($other): string - { - return parent::failureDescription($other); - } - - final protected function toStringInContext(Operator $operator, $role): string - { - return parent::toStringInContext($operator, $role); - } - - final protected function failureDescriptionInContext(Operator $operator, $role, $other): string - { - return parent::failureDescriptionInContext($operator, $role, $other); - } - - final public function exposedMatches($other): bool - { - return $this->matches($other); - } - - final public function exposedReduce(): Constraint - { - return $this->reduce(); - } - - final public function exposedExporter(): Exporter - { - return $this->exporter(); - } - - final public function exposedFail($other, $description, ComparisonFailure $comparisonFailure = null): void - { - $this->fail($other, $description, $comparisonFailure); - } - - final public function exposedAdditionalFailureDescription($other): string - { - return $this->additionalFailureDescription($other); - } - - final public function exposedFailureDescription($other): string - { - return $this->failureDescription($other); - } - - final public function exposedToStringInContext(Operator $operator, $role): string - { - return $this->toStringInContext($operator, $role); - } - - final public function exposedFailureDescriptionInContext(Operator $operator, $role, $other): string - { - return $this->failureDescriptionInContext($operator, $role, $other); - } - }; - } - - public function testEvaluateReturnsFalse(): void - { - $constraint = $this->getDummyConstraintInstance(); - - $this->assertFalse($constraint->evaluate('whatever', '', true)); - $this->assertFalse($constraint->evaluate(null, '', true)); - $this->assertFalse($constraint->evaluate(new stdClass, '', true)); - } - - public function testEvaluateFailsWithExpectationFailedException(): void - { - $constraint = $this->getDummyConstraintInstance(); - - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage("Failed asserting that 'whatever' is ok"); - - $constraint->evaluate('whatever', ''); - } - - public function testEvaluateFailsWithExpectationFailedExceptionAndCustomMessage(): void - { - $constraint = $this->getDummyConstraintInstance(); - - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage("Failed asserting that 'whatever' is fine"); - - $constraint->evaluate('whatever', "Failed asserting that 'whatever' is fine"); - } - - public function testCountIsOne(): void - { - $constraint = $this->getDummyConstraintInstance(); - - $this->assertCount(1, $constraint); - } - - public function testExporterReturnsMemoizedExporter(): void - { - $constraint = $this->getDummyConstraintInstance(); - - $exporter = $constraint->exposedExporter(); - $this->assertInstanceOf(Exporter::class, $exporter); - $this->assertSame($exporter, $constraint->exposedExporter()); - } - - public function testMatchesReturnsFalse(): void - { - $constraint = $this->getDummyConstraintInstance(); - - $this->assertFalse($constraint->exposedMatches('whatever')); - $this->assertFalse($constraint->exposedMatches(null)); - $this->assertFalse($constraint->exposedMatches(true)); - $this->assertFalse($constraint->exposedMatches(new StdClass)); - } - - public function testFailThrowsExpectationFailureExceptionWithDefaultMessage(): void - { - $constraint = $this->getDummyConstraintInstance(); - - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage("Failed asserting that 'whatever' is ok"); - - $constraint->exposedFail('whatever', ''); - } - - public function testFailThrowsExpectationFailureExceptionWithCustomMessage(): void - { - $constraint = $this->getDummyConstraintInstance(); - - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage("Custom message.\nFailed asserting that 'whatever' is ok"); - - $constraint->exposedFail('whatever', 'Custom message.'); - } - - public function testAdditionalFailureDescriptionReturnsEmptyString(): void - { - $constraint = $this->getDummyConstraintInstance(); - - $this->assertSame('', $constraint->exposedAdditionalFailureDescription('whatever')); - } - - public function testFailureDescriptionReturnsString(): void - { - $constraint = $this->getDummyConstraintInstance(); - - $this->assertSame("'whatever' is ok", $constraint->exposedFailureDescription('whatever')); - } - - public function testToStringInContextReturnsEmptyString(): void - { - $constraint = $this->getDummyConstraintInstance(); - $operator = $this->getMockBuilder(Operator::class)->getMockForAbstractClass(); - - $this->assertSame('', $constraint->exposedToStringInContext($operator, 0)); - } - - public function testFailureDescriptionInContextReturnsEmptyString(): void - { - $constraint = $this->getDummyConstraintInstance(); - $operator = $this->getMockBuilder(Operator::class)->getMockForAbstractClass(); - - $this->assertSame('', $constraint->exposedFailureDescriptionInContext($operator, 0, 'whatever')); - } - - public function testReduceReturnsThis(): void - { - $constraint = $this->getDummyConstraintInstance(); - - $this->assertSame($constraint, $constraint->exposedReduce()); - } -} diff --git a/tests/unit/Framework/Constraint/ConstraintTestCase.php b/tests/unit/Framework/Constraint/ConstraintTestCase.php deleted file mode 100644 index 82e997c9878..00000000000 --- a/tests/unit/Framework/Constraint/ConstraintTestCase.php +++ /dev/null @@ -1,61 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use function preg_replace; -use function sprintf; -use Countable; -use PHPUnit\Framework\SelfDescribing; -use PHPUnit\Framework\TestCase; -use ReflectionClass; - -/** - * @small - */ -abstract class ConstraintTestCase extends TestCase -{ - final public function testIsCountable(): void - { - $className = $this->className(); - - $reflection = new ReflectionClass($className); - - $this->assertTrue($reflection->implementsInterface(Countable::class), sprintf( - 'Failed to assert that "%s" implements "%s".', - $className, - Countable::class - )); - } - - final public function testIsSelfDescribing(): void - { - $className = $this->className(); - - $reflection = new ReflectionClass($className); - - $this->assertTrue($reflection->implementsInterface(SelfDescribing::class), sprintf( - 'Failed to assert that "%s" implements "%s".', - $className, - SelfDescribing::class - )); - } - - /** - * Returns the class name of the constraint. - */ - final protected function className(): string - { - return preg_replace( - '/Test$/', - '', - static::class - ); - } -} diff --git a/tests/unit/Framework/Constraint/CountTest.php b/tests/unit/Framework/Constraint/CountTest.php deleted file mode 100644 index b03089e1c34..00000000000 --- a/tests/unit/Framework/Constraint/CountTest.php +++ /dev/null @@ -1,201 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use DatePeriod; -use EmptyIterator; -use Iterator; -use IteratorAggregate; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestFailure; -use PHPUnit\TestFixture\TestGeneratorMaker; -use PHPUnit\TestFixture\TestIterator; -use PHPUnit\TestFixture\TestIterator2; -use PHPUnit\TestFixture\TestIteratorAggregate; -use PHPUnit\TestFixture\TestIteratorAggregate2; -use Traversable; - -/** - * @small - */ -final class CountTest extends ConstraintTestCase -{ - public function testCount(): void - { - $countConstraint = new Count(3); - $this->assertTrue($countConstraint->evaluate([1, 2, 3], '', true)); - - $countConstraint = new Count(0); - $this->assertTrue($countConstraint->evaluate([], '', true)); - - $countConstraint = new Count(2); - $it = new TestIterator([1, 2]); - $ia = new TestIteratorAggregate($it); - $ia2 = new TestIteratorAggregate2($ia); - - $this->assertTrue($countConstraint->evaluate($it, '', true)); - $this->assertTrue($countConstraint->evaluate($ia, '', true)); - $this->assertTrue($countConstraint->evaluate($ia2, '', true)); - } - - public function testCountDoesNotChangeIteratorKey(): void - { - $countConstraint = new Count(2); - - // test with 1st implementation of Iterator - $it = new TestIterator([1, 2]); - - $countConstraint->evaluate($it, '', true); - $this->assertEquals(1, $it->current()); - - $it->next(); - $countConstraint->evaluate($it, '', true); - $this->assertEquals(2, $it->current()); - - $it->next(); - $countConstraint->evaluate($it, '', true); - $this->assertFalse($it->valid()); - - // test with 2nd implementation of Iterator - $it = new TestIterator2([1, 2]); - - $countConstraint = new Count(2); - $countConstraint->evaluate($it, '', true); - $this->assertEquals(1, $it->current()); - - $it->next(); - $countConstraint->evaluate($it, '', true); - $this->assertEquals(2, $it->current()); - - $it->next(); - $countConstraint->evaluate($it, '', true); - $this->assertFalse($it->valid()); - - // test with IteratorAggregate - $it = new TestIterator([1, 2]); - $ia = new TestIteratorAggregate($it); - - $countConstraint = new Count(2); - $countConstraint->evaluate($ia, '', true); - $this->assertEquals(1, $it->current()); - - $it->next(); - $countConstraint->evaluate($ia, '', true); - $this->assertEquals(2, $it->current()); - - $it->next(); - $countConstraint->evaluate($ia, '', true); - $this->assertFalse($it->valid()); - - // test with nested IteratorAggregate - $it = new TestIterator([1, 2]); - $ia = new TestIteratorAggregate($it); - $ia2 = new TestIteratorAggregate2($ia); - - $countConstraint = new Count(2); - $countConstraint->evaluate($ia2, '', true); - $this->assertEquals(1, $it->current()); - - $it->next(); - $countConstraint->evaluate($ia2, '', true); - $this->assertEquals(2, $it->current()); - - $it->next(); - $countConstraint->evaluate($ia2, '', true); - $this->assertFalse($it->valid()); - } - - public function testCountGeneratorsDoNotRewind(): void - { - $generatorMaker = new TestGeneratorMaker; - - $countConstraint = new Count(3); - - $generator = $generatorMaker->create([1, 2, 3]); - $this->assertEquals(1, $generator->current()); - $countConstraint->evaluate($generator, '', true); - $this->assertEquals(null, $generator->current()); - - $countConstraint = new Count(2); - - $generator = $generatorMaker->create([1, 2, 3]); - $this->assertEquals(1, $generator->current()); - $generator->next(); - $this->assertEquals(2, $generator->current()); - $countConstraint->evaluate($generator, '', true); - $this->assertEquals(null, $generator->current()); - - $countConstraint = new Count(1); - - $generator = $generatorMaker->create([1, 2, 3]); - $this->assertEquals(1, $generator->current()); - $generator->next(); - $this->assertEquals(2, $generator->current()); - $generator->next(); - $this->assertEquals(3, $generator->current()); - $countConstraint->evaluate($generator, '', true); - $this->assertEquals(null, $generator->current()); - } - - /** - * Since PHP8, Traversable cannot be implemented directly. - * - * @requires PHP < 8.0 - */ - public function testCountTraversable(): void - { - $countConstraint = new Count(5); - - // DatePeriod is used as an object that is Traversable but does not - // implement Iterator or IteratorAggregate. The following ISO 8601 - // recurring time interval will yield five total DateTime objects. - $datePeriod = new DatePeriod('R4/2017-05-01T00:00:00Z/P1D'); - - $this->assertInstanceOf(Traversable::class, $datePeriod); - $this->assertNotInstanceOf(Iterator::class, $datePeriod); - $this->assertNotInstanceOf(IteratorAggregate::class, $datePeriod); - $this->assertTrue($countConstraint->evaluate($datePeriod, '', true)); - } - - public function testCountCanBeExportedToString(): void - { - $countConstraint = new Count(1); - - $this->assertEquals('count matches 1', $countConstraint->toString()); - } - - public function testCountEvaluateReturnsNullWithNonCountableAndNonTraversableOther(): void - { - $countConstraint = new Count(1); - - try { - $this->assertNull($countConstraint->evaluate(1)); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that actual size 0 matches expected size 1. - -EOF - , - TestFailure::exceptionToString($e) - ); - } - } - - /** - * @ticket https://github.com/sebastianbergmann/phpunit/issues/3743 - */ - public function test_EmptyIterator_is_handled_correctly(): void - { - $constraint = new Count(0); - - $this->assertTrue($constraint->evaluate(new EmptyIterator, '', true)); - } -} diff --git a/tests/unit/Framework/Constraint/DirectoryExistsTest.php b/tests/unit/Framework/Constraint/DirectoryExistsTest.php deleted file mode 100644 index bcbb9e7792d..00000000000 --- a/tests/unit/Framework/Constraint/DirectoryExistsTest.php +++ /dev/null @@ -1,69 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestFailure; - -/** - * @small - */ -final class DirectoryExistsTest extends ConstraintTestCase -{ - public function testDefaults(): void - { - $constraint = new DirectoryExists; - - $this->assertCount(1, $constraint); - $this->assertSame('directory exists', $constraint->toString()); - } - - public function testEvaluateReturnsFalseWhenDirectoryDoesNotExist(): void - { - $directory = __DIR__ . '/NonExistentDirectory'; - - $constraint = new DirectoryExists; - - $this->assertFalse($constraint->evaluate($directory, '', true)); - } - - public function testEvaluateReturnsTrueWhenDirectoryExists(): void - { - $directory = __DIR__; - - $constraint = new DirectoryExists; - - $this->assertTrue($constraint->evaluate($directory, '', true)); - } - - public function testEvaluateThrowsExpectationFailedExceptionWhenDirectoryDoesNotExist(): void - { - $directory = __DIR__ . '/NonExistentDirectory'; - - $constraint = new DirectoryExists; - - try { - $constraint->evaluate($directory); - } catch (ExpectationFailedException $e) { - $this->assertSame( - <<fail(); - } -} diff --git a/tests/unit/Framework/Constraint/Equality/IsEqualCanonicalizingTest.php b/tests/unit/Framework/Constraint/Equality/IsEqualCanonicalizingTest.php new file mode 100644 index 00000000000..72ba288bb35 --- /dev/null +++ b/tests/unit/Framework/Constraint/Equality/IsEqualCanonicalizingTest.php @@ -0,0 +1,331 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use const PHP_EOL; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; +use stdClass; + +#[CoversClass(IsEqualCanonicalizing::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class IsEqualCanonicalizingTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + <<<'EOT' +is equal to Array &%d [ + 0 => 'value', +] +EOT, + '', + '', + ['value'], + ['value'], + ], + + [ + true, + <<<'EOT' +is equal to Array &%d [ + 0 => 'value', + 1 => 'another-value', +] +EOT, + '', + '', + ['value', 'another-value'], + ['another-value', 'value'], + ], + + [ + true, + <<<'EOT' +is equal to stdClass Object #%d ( + 'foo' => 'bar', +) +EOT, + '', + '', + self::stdClass('foo', 'bar'), + self::stdClass('foo', 'bar'), + ], + + [ + true, + 'is equal to true', + '', + '', + true, + true, + ], + + [ + true, + 'is equal to true', + '', + '', + true, + 'true', + ], + + [ + true, + 'is equal to 1.0', + '', + '', + 1.0, + 1.0, + ], + + [ + true, + 'is equal to 1.0', + '', + '', + 1.0, + 1, + ], + + [ + true, + 'is equal to 1', + '', + '', + 1, + 1, + ], + + [ + true, + 'is equal to 1', + '', + '', + 1, + 1.0, + ], + + [ + true, + 'is equal to 1', + '', + '', + 1, + '1', + ], + + [ + true, + 'is equal to \'1\'', + '', + '', + '1', + 1, + ], + + [ + true, + 'is equal to \'string\'', + '', + '', + 'string', + 'string', + ], + + [ + true, + 'is equal to ', + '', + '', + 'string' . PHP_EOL . 'string', + 'string' . PHP_EOL . 'string', + ], + + [ + false, + <<<'EOT' +is equal to Array &%d [ + 0 => 'value', +] +EOT, + 'Failed asserting that two arrays are equal.', + <<<'EOT' +Failed asserting that two arrays are equal. +--- Expected ++++ Actual +@@ @@ + Array ( +- 0 => 'value' ++ 0 => 'another-value' + ) + +EOT, + ['value'], + ['another-value'], + ], + + [ + false, + <<<'EOT' +is equal to stdClass Object #%d ( + 'foo' => 'bar', +) +EOT, + 'Failed asserting that two objects are equal.', + <<<'EOT' +Failed asserting that two objects are equal. +--- Expected ++++ Actual +@@ @@ + stdClass Object ( +- 0 => 'bar' ++ 0 => 'foo' + ) + +EOT, + self::stdClass('foo', 'bar'), + self::stdClass('bar', 'foo'), + ], + + [ + false, + 'is equal to true', + 'Failed asserting that false matches expected true.', + 'Failed asserting that false matches expected true.', + true, + false, + ], + + [ + false, + 'is equal to 1.0', + 'Failed asserting that 1.01 matches expected 1.0.', + 'Failed asserting that 1.01 matches expected 1.0.', + 1.0, + 1.01, + ], + + [ + false, + 'is equal to 1.01', + 'Failed asserting that 1 matches expected 1.01.', + 'Failed asserting that 1 matches expected 1.01.', + 1.01, + 1, + ], + + [ + false, + 'is equal to 1', + 'Failed asserting that \'2\' matches expected 1.', + 'Failed asserting that \'2\' matches expected 1.', + 1, + '2', + ], + + [ + false, + 'is equal to \'1\'', + 'Failed asserting that 2 matches expected \'1\'.', + 'Failed asserting that 2 matches expected \'1\'.', + '1', + 2, + ], + + [ + false, + 'is equal to \'string\'', + 'Failed asserting that two strings are equal.', + <<<'EOT' +Failed asserting that two strings are equal. +--- Expected ++++ Actual +@@ @@ +-'string' ++'another-string' + +EOT, + 'string', + 'another-string', + ], + + [ + false, + 'is equal to ', + 'Failed asserting that two strings are equal.', + <<<'EOT' +Failed asserting that two strings are equal. +--- Expected ++++ Actual +@@ @@ +-'string\n +-string' ++'another-string\n ++another-string' + +EOT, + "string\nstring", + "another-string\nanother-string", + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $constraintAsString, string $failureDescription, string $comparisonFailureAsString, mixed $expected, mixed $actual): void + { + $constraint = new IsEqualCanonicalizing($expected); + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + try { + $constraint->evaluate($actual); + } catch (ExpectationFailedException $e) { + $this->assertSame($failureDescription, $e->getMessage()); + $this->assertSame($comparisonFailureAsString, $e->getComparisonFailure() ? $e->getComparisonFailure()->toString() : ''); + + return; + } + + $this->fail(); + } + + #[DataProvider('provider')] + public function testCanBeRepresentedAsString(bool $result, string $constraintAsString, string $failureDescription, string $comparisonFailureAsString, mixed $expected, mixed $actual): void + { + $constraint = new IsEqualCanonicalizing($expected); + + $this->assertStringMatchesFormat($constraintAsString, $constraint->toString(true)); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new IsEqualCanonicalizing(true))); + } + + private static function stdClass(string $key, string $value): stdClass + { + $o = new stdClass; + + $o->{$key} = $value; + + return $o; + } +} diff --git a/tests/unit/Framework/Constraint/Equality/IsEqualIgnoringCaseTest.php b/tests/unit/Framework/Constraint/Equality/IsEqualIgnoringCaseTest.php new file mode 100644 index 00000000000..bb09d49f9c5 --- /dev/null +++ b/tests/unit/Framework/Constraint/Equality/IsEqualIgnoringCaseTest.php @@ -0,0 +1,308 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use const PHP_EOL; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; +use stdClass; + +#[CoversClass(IsEqualIgnoringCase::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class IsEqualIgnoringCaseTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + <<<'EOT' +is equal to Array &%d [ + 0 => 'value', +] +EOT, + '', + '', + ['value'], + ['VALUE'], + ], + + [ + true, + <<<'EOT' +is equal to Array &%d [ + 0 => 'value', + 1 => 'another-value', +] +EOT, + '', + '', + ['value', 'another-value'], + ['VALUE', 'ANOTHER-VALUE'], + ], + + [ + true, + <<<'EOT' +is equal to stdClass Object #%d ( + 'foo' => 'bar', +) +EOT, + '', + '', + self::stdClass('foo', 'bar'), + self::stdClass('foo', 'BAR'), + ], + + [ + true, + 'is equal to true', + '', + '', + true, + true, + ], + + [ + true, + 'is equal to true', + '', + '', + true, + 'true', + ], + + [ + true, + 'is equal to 1.0', + '', + '', + 1.0, + 1.0, + ], + + [ + true, + 'is equal to 1.0', + '', + '', + 1.0, + 1, + ], + + [ + true, + 'is equal to 1', + '', + '', + 1, + 1, + ], + + [ + true, + 'is equal to 1', + '', + '', + 1, + 1.0, + ], + + [ + true, + 'is equal to 1', + '', + '', + 1, + '1', + ], + + [ + true, + 'is equal to \'1\'', + '', + '', + '1', + 1, + ], + + [ + true, + 'is equal to \'string\'', + '', + '', + 'string', + 'STRING', + ], + + [ + true, + 'is equal to ', + '', + '', + 'string' . PHP_EOL . 'string', + 'STRING' . PHP_EOL . 'STRING', + ], + + [ + false, + <<<'EOT' +is equal to Array &%d [ + 0 => 'value', +] +EOT, + 'Failed asserting that two arrays are equal.', + <<<'EOT' +Failed asserting that two arrays are equal. +--- Expected ++++ Actual +@@ @@ + Array ( +- 0 => 'value' ++ 0 => 'another-value' + ) + +EOT, + ['value'], + ['another-value'], + ], + + [ + false, + 'is equal to true', + 'Failed asserting that false matches expected true.', + 'Failed asserting that false matches expected true.', + true, + false, + ], + + [ + false, + 'is equal to 1.0', + 'Failed asserting that 1.01 matches expected 1.0.', + 'Failed asserting that 1.01 matches expected 1.0.', + 1.0, + 1.01, + ], + + [ + false, + 'is equal to 1.01', + 'Failed asserting that 1 matches expected 1.01.', + 'Failed asserting that 1 matches expected 1.01.', + 1.01, + 1, + ], + + [ + false, + 'is equal to 1', + 'Failed asserting that \'2\' matches expected 1.', + 'Failed asserting that \'2\' matches expected 1.', + 1, + '2', + ], + + [ + false, + 'is equal to \'1\'', + 'Failed asserting that 2 matches expected \'1\'.', + 'Failed asserting that 2 matches expected \'1\'.', + '1', + 2, + ], + + [ + false, + 'is equal to \'string\'', + 'Failed asserting that two strings are equal.', + <<<'EOT' +Failed asserting that two strings are equal. +--- Expected ++++ Actual +@@ @@ +-'string' ++'another-string' + +EOT, + 'string', + 'another-string', + ], + + [ + false, + 'is equal to ', + 'Failed asserting that two strings are equal.', + <<<'EOT' +Failed asserting that two strings are equal. +--- Expected ++++ Actual +@@ @@ +-'string\n +-string' ++'another-string\n ++another-string' + +EOT, + "string\nstring", + "another-string\nanother-string", + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $constraintAsString, string $failureDescription, string $comparisonFailureAsString, mixed $expected, mixed $actual): void + { + $constraint = new IsEqualIgnoringCase($expected); + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + try { + $constraint->evaluate($actual); + } catch (ExpectationFailedException $e) { + $this->assertSame($failureDescription, $e->getMessage()); + $this->assertSame($comparisonFailureAsString, $e->getComparisonFailure() ? $e->getComparisonFailure()->toString() : ''); + + return; + } + + $this->fail(); + } + + #[DataProvider('provider')] + public function testCanBeRepresentedAsString(bool $result, string $constraintAsString, string $failureDescription, string $comparisonFailureAsString, mixed $expected, mixed $actual): void + { + $constraint = new IsEqualIgnoringCase($expected); + + $this->assertStringMatchesFormat($constraintAsString, $constraint->toString(true)); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new IsEqualIgnoringCase(true))); + } + + private static function stdClass(string $key, string $value): stdClass + { + $o = new stdClass; + + $o->{$key} = $value; + + return $o; + } +} diff --git a/tests/unit/Framework/Constraint/Equality/IsEqualTest.php b/tests/unit/Framework/Constraint/Equality/IsEqualTest.php new file mode 100644 index 00000000000..82f5b8f20d9 --- /dev/null +++ b/tests/unit/Framework/Constraint/Equality/IsEqualTest.php @@ -0,0 +1,400 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use const PHP_EOL; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\EnumerationEquals\Example; +use PHPUnit\TestFixture\EnumerationEquals\ExampleInt; +use PHPUnit\TestFixture\EnumerationEquals\ExampleString; +use stdClass; + +#[CoversClass(IsEqual::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class IsEqualTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + <<<'EOT' +is equal to Array &%d [ + 0 => 'value', +] +EOT, + '', + '', + ['value'], + ['value'], + ], + + [ + true, + <<<'EOT' +is equal to stdClass Object #%d ( + 'foo' => 'bar', +) +EOT, + '', + '', + self::stdClass('foo', 'bar'), + self::stdClass('foo', 'bar'), + ], + + [ + true, + 'is equal to true', + '', + '', + true, + true, + ], + + [ + true, + 'is equal to true', + '', + '', + true, + 'true', + ], + + [ + true, + 'is equal to 1.0', + '', + '', + 1.0, + 1.0, + ], + + [ + true, + 'is equal to 1.0', + '', + '', + 1.0, + 1, + ], + + [ + true, + 'is equal to 1', + '', + '', + 1, + 1, + ], + + [ + true, + 'is equal to 1', + '', + '', + 1, + 1.0, + ], + + [ + true, + 'is equal to 1', + '', + '', + 1, + '1', + ], + + [ + true, + 'is equal to \'1\'', + '', + '', + '1', + 1, + ], + + [ + true, + 'is equal to \'string\'', + '', + '', + 'string', + 'string', + ], + + [ + true, + 'is equal to ', + '', + '', + 'string' . PHP_EOL . 'string', + 'string' . PHP_EOL . 'string', + ], + + [ + true, + 'is equal to PHPUnit\TestFixture\EnumerationEquals\Example Enum %s (Foo)', + '', + '', + Example::Foo, + Example::Foo, + ], + + [ + true, + 'is equal to PHPUnit\TestFixture\EnumerationEquals\ExampleString Enum %s (Foo, \'foo\')', + '', + '', + ExampleString::Foo, + ExampleString::Foo, + ], + + [ + true, + 'is equal to PHPUnit\TestFixture\EnumerationEquals\ExampleInt Enum %s (Foo, 0)', + '', + '', + ExampleInt::Foo, + ExampleInt::Foo, + ], + + [ + false, + <<<'EOT' +is equal to Array &%d [ + 0 => 'value', +] +EOT, + 'Failed asserting that two arrays are equal.', + <<<'EOT' +Failed asserting that two arrays are equal. +--- Expected ++++ Actual +@@ @@ + Array ( +- 0 => 'value' ++ 0 => 'another-value' + ) + +EOT, + ['value'], + ['another-value'], + ], + + [ + false, + <<<'EOT' +is equal to Array &%d [ + 0 => 'value', + 1 => 'another-value', +] +EOT, + 'Failed asserting that two arrays are equal.', + <<<'EOT' +Failed asserting that two arrays are equal. +--- Expected ++++ Actual +@@ @@ + Array ( +- 0 => 'value' +- 1 => 'another-value' ++ 0 => 'another-value' ++ 1 => 'value' + ) + +EOT, + ['value', 'another-value'], + ['another-value', 'value'], + ], + + [ + false, + <<<'EOT' +is equal to stdClass Object #%d ( + 'foo' => 'bar', +) +EOT, + 'Failed asserting that two objects are equal.', + <<<'EOT' +Failed asserting that two objects are equal. +--- Expected ++++ Actual +@@ @@ + stdClass Object ( +- 'foo' => 'bar' ++ 'bar' => 'foo' + ) + +EOT, + self::stdClass('foo', 'bar'), + self::stdClass('bar', 'foo'), + ], + + [ + false, + 'is equal to true', + 'Failed asserting that false matches expected true.', + 'Failed asserting that false matches expected true.', + true, + false, + ], + + [ + false, + 'is equal to 1.0', + 'Failed asserting that 1.01 matches expected 1.0.', + 'Failed asserting that 1.01 matches expected 1.0.', + 1.0, + 1.01, + ], + + [ + false, + 'is equal to 1.01', + 'Failed asserting that 1 matches expected 1.01.', + 'Failed asserting that 1 matches expected 1.01.', + 1.01, + 1, + ], + + [ + false, + 'is equal to 1', + 'Failed asserting that \'2\' matches expected 1.', + 'Failed asserting that \'2\' matches expected 1.', + 1, + '2', + ], + + [ + false, + 'is equal to \'1\'', + 'Failed asserting that 2 matches expected \'1\'.', + 'Failed asserting that 2 matches expected \'1\'.', + '1', + 2, + ], + + [ + false, + 'is equal to \'string\'', + 'Failed asserting that two strings are equal.', + <<<'EOT' +Failed asserting that two strings are equal. +--- Expected ++++ Actual +@@ @@ +-'string' ++'another-string' + +EOT, + 'string', + 'another-string', + ], + + [ + false, + 'is equal to ', + 'Failed asserting that two strings are equal.', + <<<'EOT' +Failed asserting that two strings are equal. +--- Expected ++++ Actual +@@ @@ +-'string\n +-string' ++'another-string\n ++another-string' + +EOT, + "string\nstring", + "another-string\nanother-string", + ], + + [ + false, + 'is equal to PHPUnit\TestFixture\EnumerationEquals\Example Enum %s (Foo)', + 'Failed asserting that two values of enumeration PHPUnit\TestFixture\EnumerationEquals\Example are equal, Bar does not match expected Foo.', + 'Failed asserting that two values of enumeration PHPUnit\TestFixture\EnumerationEquals\Example are equal, Bar does not match expected Foo.', + Example::Foo, + Example::Bar, + ], + + [ + false, + 'is equal to PHPUnit\TestFixture\EnumerationEquals\ExampleString Enum %s (Foo, \'foo\')', + 'Failed asserting that two values of enumeration PHPUnit\TestFixture\EnumerationEquals\ExampleString are equal, Bar does not match expected Foo.', + 'Failed asserting that two values of enumeration PHPUnit\TestFixture\EnumerationEquals\ExampleString are equal, Bar does not match expected Foo.', + ExampleString::Foo, + ExampleString::Bar, + ], + + [ + false, + 'is equal to PHPUnit\TestFixture\EnumerationEquals\ExampleInt Enum %s (Foo, 0)', + 'Failed asserting that two values of enumeration PHPUnit\TestFixture\EnumerationEquals\ExampleInt are equal, Bar does not match expected Foo.', + 'Failed asserting that two values of enumeration PHPUnit\TestFixture\EnumerationEquals\ExampleInt are equal, Bar does not match expected Foo.', + ExampleInt::Foo, + ExampleInt::Bar, + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $constraintAsString, string $failureDescription, string $comparisonFailureAsString, mixed $expected, mixed $actual): void + { + $constraint = new IsEqual($expected); + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + try { + $constraint->evaluate($actual); + } catch (ExpectationFailedException $e) { + $this->assertSame($failureDescription, $e->getMessage()); + $this->assertSame($comparisonFailureAsString, $e->getComparisonFailure() ? $e->getComparisonFailure()->toString() : ''); + + return; + } + + $this->fail(); + } + + #[DataProvider('provider')] + public function testCanBeRepresentedAsString(bool $result, string $constraintAsString, string $failureDescription, string $comparisonFailureAsString, mixed $expected, mixed $actual): void + { + $constraint = new IsEqual($expected); + + $this->assertStringMatchesFormat($constraintAsString, $constraint->toString(true)); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new IsEqual(true))); + } + + private static function stdClass(string $key, string $value): stdClass + { + $o = new stdClass; + + $o->{$key} = $value; + + return $o; + } +} diff --git a/tests/unit/Framework/Constraint/Equality/IsEqualWithDeltaTest.php b/tests/unit/Framework/Constraint/Equality/IsEqualWithDeltaTest.php new file mode 100644 index 00000000000..6d7ef6917a7 --- /dev/null +++ b/tests/unit/Framework/Constraint/Equality/IsEqualWithDeltaTest.php @@ -0,0 +1,207 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; +use stdClass; + +#[CoversClass(IsEqualWithDelta::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class IsEqualWithDeltaTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + 'is equal to 1.0 with delta <0.000000>', + '', + '', + 1.0, + 0.0, + 1.0, + ], + + [ + true, + 'is equal to 1.0 with delta <0.100000>', + '', + '', + 1.0, + 0.1, + 1.09, + ], + + [ + true, + <<<'EOT' +is equal to Array &%d [ + 0 => 1.0, +] with delta <0.000000> +EOT, + '', + '', + [1.0], + 0.0, + [1.0], + ], + + [ + true, + <<<'EOT' +is equal to Array &%d [ + 0 => 1.0, +] with delta <0.100000> +EOT, + '', + '', + [1.0], + 0.1, + [1.09], + ], + + [ + true, + <<<'EOT' +is equal to stdClass Object #%d ( + 'property' => 1.0, +) with delta <0.000000> +EOT, + '', + '', + self::stdClass('property', 1.0), + 0.0, + self::stdClass('property', 1.0), + ], + + [ + true, + <<<'EOT' +is equal to stdClass Object #%d ( + 'property' => 1.0, +) with delta <0.100000> +EOT, + '', + '', + self::stdClass('property', 1.0), + 0.1, + self::stdClass('property', 1.09), + ], + + [ + false, + 'is equal to 1.0 with delta <0.100000>', + 'Failed asserting that 1.1 matches expected 1.0.', + 'Failed asserting that 1.1 matches expected 1.0.', + 1.0, + 0.1, + 1.1, + ], + + [ + false, + <<<'EOT' +is equal to Array &%d [ + 0 => 1.0, +] with delta <0.000000> +EOT, + 'Failed asserting that two arrays are equal.', + <<<'EOT' +Failed asserting that two arrays are equal. +--- Expected ++++ Actual +@@ @@ + Array ( +- 0 => 1.0 ++ 0 => 1.01 + ) + +EOT, + [1.0], + 0.0, + [1.01], + ], + + [ + false, + <<<'EOT' +is equal to stdClass Object #%d ( + 'property' => 1.0, +) with delta <0.000000> +EOT, + 'Failed asserting that two objects are equal.', + <<<'EOT' +Failed asserting that two objects are equal. +--- Expected ++++ Actual +@@ @@ + stdClass Object ( +- 'property' => 1.0 ++ 'property' => 1.01 + ) + +EOT, + self::stdClass('property', 1.0), + 0.0, + self::stdClass('property', 1.01), + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $constraintAsString, string $failureDescription, string $comparisonFailureAsString, mixed $expected, float $delta, mixed $actual): void + { + $constraint = new IsEqualWithDelta($expected, $delta); + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + try { + $constraint->evaluate($actual); + } catch (ExpectationFailedException $e) { + $this->assertSame($failureDescription, $e->getMessage()); + $this->assertSame($comparisonFailureAsString, $e->getComparisonFailure() ? $e->getComparisonFailure()->toString() : ''); + + return; + } + + $this->fail(); + } + + #[DataProvider('provider')] + public function testCanBeRepresentedAsString(bool $result, string $constraintAsString, string $failureDescription, string $comparisonFailureAsString, mixed $expected, float $delta, mixed $actual): void + { + $constraint = new IsEqualWithDelta($expected, $delta); + + $this->assertStringMatchesFormat($constraintAsString, $constraint->toString(true)); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new IsEqualWithDelta(1.0, 0.0))); + } + + private static function stdClass(string $key, float $value): stdClass + { + $o = new stdClass; + + $o->{$key} = $value; + + return $o; + } +} diff --git a/tests/unit/Framework/Constraint/Exception/ExceptionCodeTest.php b/tests/unit/Framework/Constraint/Exception/ExceptionCodeTest.php new file mode 100644 index 00000000000..1fd4922e291 --- /dev/null +++ b/tests/unit/Framework/Constraint/Exception/ExceptionCodeTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; + +#[CoversClass(ExceptionCode::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class ExceptionCodeTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + '', + 1234, + 1234, + ], + + [ + false, + 'Failed asserting that 4567 is equal to expected exception code 1234.', + 1234, + 4567, + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $failureDescription, int $expected, mixed $actual): void + { + $constraint = new ExceptionCode($expected); + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('exception code is 1234', new ExceptionCode(1234)->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new ExceptionCode(1234))); + } +} diff --git a/tests/unit/Framework/Constraint/Exception/ExceptionMessageIsOrContainsTest.php b/tests/unit/Framework/Constraint/Exception/ExceptionMessageIsOrContainsTest.php new file mode 100644 index 00000000000..44bfc7db747 --- /dev/null +++ b/tests/unit/Framework/Constraint/Exception/ExceptionMessageIsOrContainsTest.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; + +#[CoversClass(ExceptionMessageIsOrContains::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class ExceptionMessageIsOrContainsTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + '', + 'expected-message', + 'expected-message', + ], + + [ + true, + '', + '', + '', + ], + + [ + false, + 'Failed asserting that exception message is empty but is \'actual-message\'.', + '', + 'actual-message', + ], + + [ + false, + 'Failed asserting that exception message \'actual-message\' contains \'expected-message\'.', + 'expected-message', + 'actual-message', + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $failureDescription, string $expected, mixed $actual): void + { + $constraint = new ExceptionMessageIsOrContains($expected); + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('exception message contains \'message\'', new ExceptionMessageIsOrContains('message')->toString()); + $this->assertSame('exception message is empty', new ExceptionMessageIsOrContains('')->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new ExceptionMessageIsOrContains('message'))); + } +} diff --git a/tests/unit/Framework/Constraint/Exception/ExceptionMessageMatchesRegularExpressionTest.php b/tests/unit/Framework/Constraint/Exception/ExceptionMessageMatchesRegularExpressionTest.php new file mode 100644 index 00000000000..6b5668c6456 --- /dev/null +++ b/tests/unit/Framework/Constraint/Exception/ExceptionMessageMatchesRegularExpressionTest.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Exception; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; + +#[CoversClass(ExceptionMessageMatchesRegularExpression::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class ExceptionMessageMatchesRegularExpressionTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + '', + '/[0-9]/', + '1234', + ], + + [ + false, + 'Failed asserting that exception message \'abcd\' matches \'/[0-9]/\'.', + '/[0-9]/', + 'abcd', + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $failureDescription, string $expected, mixed $actual): void + { + $constraint = new ExceptionMessageMatchesRegularExpression($expected); + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + public function testRejectsInvalidRegularExpression(): void + { + $constraint = new ExceptionMessageMatchesRegularExpression('invalid regular expression'); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('Invalid expected exception message regular expression given: invalid regular expression'); + + $constraint->evaluate('abcd'); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('exception message matches \'/.*/\'', new ExceptionMessageMatchesRegularExpression('/.*/')->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new ExceptionMessageMatchesRegularExpression('/.*/'))); + } +} diff --git a/tests/unit/Framework/Constraint/Exception/ExceptionTest.php b/tests/unit/Framework/Constraint/Exception/ExceptionTest.php new file mode 100644 index 00000000000..2b8d7895d41 --- /dev/null +++ b/tests/unit/Framework/Constraint/Exception/ExceptionTest.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use InvalidArgumentException; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; +use RuntimeException; + +#[CoversClass(Exception::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class ExceptionTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + '', + RuntimeException::class, + new RuntimeException, + ], + + [ + false, + 'Failed asserting that exception of type "RuntimeException" is thrown.', + RuntimeException::class, + null, + ], + + [ + false, + 'Failed asserting that exception of type "InvalidArgumentException" matches expected exception "RuntimeException". Message was: "message" at', + RuntimeException::class, + new InvalidArgumentException('message'), + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $failureDescription, string $expected, mixed $actual): void + { + $constraint = new Exception($expected); + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('exception of type "Exception"', new Exception(\Exception::class)->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new Exception(\Exception::class))); + } +} diff --git a/tests/unit/Framework/Constraint/ExceptionCodeTest.php b/tests/unit/Framework/Constraint/ExceptionCodeTest.php deleted file mode 100644 index c58a0387db2..00000000000 --- a/tests/unit/Framework/Constraint/ExceptionCodeTest.php +++ /dev/null @@ -1,47 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use Exception; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestFailure; - -class ExceptionCodeTest extends TestCase -{ - public function testExceptionCodeCanEvaluateExceptions(): void - { - $exceptionCode = new ExceptionCode(123); - - $other = new Exception('bla', 456); - - try { - $exceptionCode->evaluate($other); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that 456 is equal to expected exception code 123. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - } - - public function testExceptionCodeCanBeExportedAsString(): void - { - $exceptionCode = new ExceptionCode(ExceptionCode::class); - - $this->assertSame('exception code is ', $exceptionCode->toString()); - } -} diff --git a/tests/unit/Framework/Constraint/ExceptionMessageRegExpTest.php b/tests/unit/Framework/Constraint/ExceptionMessageRegExpTest.php deleted file mode 100644 index 05b0d9d88ff..00000000000 --- a/tests/unit/Framework/Constraint/ExceptionMessageRegExpTest.php +++ /dev/null @@ -1,57 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use function ini_set; -use Exception; -use PHPUnit\Framework\TestCase; - -/** - * @small - */ -final class ExceptionMessageRegExpTest extends TestCase -{ - public function testRegexMessage(): void - { - $this->expectException(Exception::class); - $this->expectExceptionMessageMatches('/^A polymorphic \w+ message/'); - - throw new Exception('A polymorphic exception message'); - } - - public function testRegexMessageExtreme(): void - { - $this->expectException(Exception::class); - $this->expectExceptionMessageMatches('/^a poly[a-z]+ [a-zA-Z0-9_]+ me(s){2}age$/i'); - - throw new Exception('A polymorphic exception message'); - } - - /** - * @runInSeparateProcess - * @requires extension xdebug - */ - public function testMessageXdebugScreamCompatibility(): void - { - ini_set('xdebug.scream', '1'); - - $this->expectException(Exception::class); - $this->expectExceptionMessageMatches('#Screaming preg_match#'); - - throw new Exception('Screaming preg_match'); - } - - public function testRegExMessageCanBeExportedAsString(): void - { - $exceptionMessageReExp = new ExceptionMessageRegularExpression('/^a poly[a-z]+ [a-zA-Z0-9_]+ me(s){2}age$/i'); - - $this->assertSame('exception message matches ', $exceptionMessageReExp->toString()); - } -} diff --git a/tests/unit/Framework/Constraint/ExceptionMessageTest.php b/tests/unit/Framework/Constraint/ExceptionMessageTest.php deleted file mode 100644 index 60373a83859..00000000000 --- a/tests/unit/Framework/Constraint/ExceptionMessageTest.php +++ /dev/null @@ -1,65 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use Exception; -use PHPUnit\Framework\TestCase; - -/** - * @small - */ -final class ExceptionMessageTest extends TestCase -{ - public function testLiteralMessage(): void - { - $this->expectException(Exception::class); - $this->expectExceptionMessage('A literal exception message'); - - throw new Exception('A literal exception message'); - } - - public function testPartialMessageBegin(): void - { - $this->expectException(Exception::class); - $this->expectExceptionMessage('A partial'); - - throw new Exception('A partial exception message'); - } - - public function testPartialMessageMiddle(): void - { - $this->expectException(Exception::class); - $this->expectExceptionMessage('partial exception'); - - throw new Exception('A partial exception message'); - } - - public function testPartialMessageEnd(): void - { - $this->expectException(Exception::class); - $this->expectExceptionMessage('exception message'); - - throw new Exception('A partial exception message'); - } - - public function testEmptyMessageExportToString(): void - { - $exceptionMessage = new ExceptionMessage(''); - - $this->assertSame('exception message is empty', $exceptionMessage->toString()); - } - - public function testMessageExportToString(): void - { - $exceptionMessage = new ExceptionMessage('test'); - - $this->assertSame('exception message contains ', $exceptionMessage->toString()); - } -} diff --git a/tests/unit/Framework/Constraint/ExceptionTest.php b/tests/unit/Framework/Constraint/ExceptionTest.php deleted file mode 100644 index 6a92f8240c6..00000000000 --- a/tests/unit/Framework/Constraint/ExceptionTest.php +++ /dev/null @@ -1,22 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use PHPUnit\Framework\TestCase; - -class ExceptionTest extends TestCase -{ - public function testExceptionCanBeExportedAsString(): void - { - $exception = new Exception(Exception::class); - - $this->assertSame('exception of type "' . Exception::class . '"', $exception->toString()); - } -} diff --git a/tests/unit/Framework/Constraint/FileExistsTest.php b/tests/unit/Framework/Constraint/FileExistsTest.php deleted file mode 100644 index d7979368b43..00000000000 --- a/tests/unit/Framework/Constraint/FileExistsTest.php +++ /dev/null @@ -1,68 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestFailure; - -/** - * @small - */ -final class FileExistsTest extends ConstraintTestCase -{ - public function testConstraintFileExists(): void - { - $constraint = new FileExists; - - $this->assertFalse($constraint->evaluate('foo', '', true)); - $this->assertEquals('file exists', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate('foo'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that file "foo" exists. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintFileExists2(): void - { - $constraint = new FileExists; - - try { - $constraint->evaluate('foo', 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that file "foo" exists. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } -} diff --git a/tests/unit/Framework/Constraint/Filesystem/DirectoryExistsTest.php b/tests/unit/Framework/Constraint/Filesystem/DirectoryExistsTest.php new file mode 100644 index 00000000000..9734f29d1bd --- /dev/null +++ b/tests/unit/Framework/Constraint/Filesystem/DirectoryExistsTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; + +#[CoversClass(DirectoryExists::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class DirectoryExistsTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + '', + __DIR__, + ], + + [ + false, + 'Failed asserting that directory "/does/not/exist" exists.', + '/does/not/exist', + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $failureDescription, string $actual): void + { + $constraint = new DirectoryExists; + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('directory exists', (new DirectoryExists)->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new DirectoryExists)); + } +} diff --git a/tests/unit/Framework/Constraint/Filesystem/FileExistsTest.php b/tests/unit/Framework/Constraint/Filesystem/FileExistsTest.php new file mode 100644 index 00000000000..a6e5b977565 --- /dev/null +++ b/tests/unit/Framework/Constraint/Filesystem/FileExistsTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; + +#[CoversClass(FileExists::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class FileExistsTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + '', + __FILE__, + ], + + [ + false, + 'Failed asserting that file "/does/not/exist" exists.', + '/does/not/exist', + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $failureDescription, string $actual): void + { + $constraint = new FileExists; + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('file exists', (new FileExists)->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new FileExists)); + } +} diff --git a/tests/unit/Framework/Constraint/Filesystem/IsReadableTest.php b/tests/unit/Framework/Constraint/Filesystem/IsReadableTest.php new file mode 100644 index 00000000000..6c73081caf6 --- /dev/null +++ b/tests/unit/Framework/Constraint/Filesystem/IsReadableTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; + +#[CoversClass(IsReadable::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class IsReadableTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + '', + __FILE__, + ], + + [ + false, + 'Failed asserting that "/is/not/readable" is readable.', + '/is/not/readable', + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $failureDescription, string $actual): void + { + $constraint = new IsReadable; + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('is readable', (new IsReadable)->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new IsReadable)); + } +} diff --git a/tests/unit/Framework/Constraint/Filesystem/IsWritableTest.php b/tests/unit/Framework/Constraint/Filesystem/IsWritableTest.php new file mode 100644 index 00000000000..b042ec448d5 --- /dev/null +++ b/tests/unit/Framework/Constraint/Filesystem/IsWritableTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; + +#[CoversClass(IsWritable::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class IsWritableTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + '', + __FILE__, + ], + + [ + false, + 'Failed asserting that "/is/not/writable" is writable.', + '/is/not/writable', + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $failureDescription, string $actual): void + { + $constraint = new IsWritable; + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('is writable', (new IsWritable)->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new IsWritable)); + } +} diff --git a/tests/unit/Framework/Constraint/GreaterThanTest.php b/tests/unit/Framework/Constraint/GreaterThanTest.php deleted file mode 100644 index 2d189ff6fd4..00000000000 --- a/tests/unit/Framework/Constraint/GreaterThanTest.php +++ /dev/null @@ -1,69 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestFailure; - -/** - * @small - */ -final class GreaterThanTest extends ConstraintTestCase -{ - public function testConstraintGreaterThan(): void - { - $constraint = new GreaterThan(1); - - $this->assertFalse($constraint->evaluate(0, '', true)); - $this->assertTrue($constraint->evaluate(2, '', true)); - $this->assertEquals('is greater than 1', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate(0); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that 0 is greater than 1. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintGreaterThan2(): void - { - $constraint = new GreaterThan(1); - - try { - $constraint->evaluate(0, 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that 0 is greater than 1. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } -} diff --git a/tests/unit/Framework/Constraint/IsAnythingTest.php b/tests/unit/Framework/Constraint/IsAnythingTest.php new file mode 100644 index 00000000000..daa5ffc0020 --- /dev/null +++ b/tests/unit/Framework/Constraint/IsAnythingTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(IsAnything::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class IsAnythingTest extends TestCase +{ + public function testCanBeEvaluated(): void + { + $this->assertTrue((new IsAnything)->evaluate(true, returnResult: true)); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('is anything', (new IsAnything)->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(0, (new IsAnything)); + } +} diff --git a/tests/unit/Framework/Constraint/IsEmptyTest.php b/tests/unit/Framework/Constraint/IsEmptyTest.php deleted file mode 100644 index afb3ff919a0..00000000000 --- a/tests/unit/Framework/Constraint/IsEmptyTest.php +++ /dev/null @@ -1,82 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use ArrayObject; -use EmptyIterator; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestFailure; - -/** - * @small - */ -final class IsEmptyTest extends ConstraintTestCase -{ - public function testConstraintIsEmpty(): void - { - $constraint = new IsEmpty; - - $this->assertFalse($constraint->evaluate(['foo'], '', true)); - $this->assertTrue($constraint->evaluate([], '', true)); - $this->assertFalse($constraint->evaluate(new ArrayObject(['foo']), '', true)); - $this->assertTrue($constraint->evaluate(new ArrayObject([]), '', true)); - $this->assertEquals('is empty', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate(['foo']); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that an array is empty. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintIsEmpty2(): void - { - $constraint = new IsEmpty; - - try { - $constraint->evaluate(['foo'], 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<fail(); - } - - /** - * @ticket https://github.com/sebastianbergmann/phpunit/issues/3743 - */ - public function test_EmptyIterator_is_handled_correctly(): void - { - $constraint = new IsEmpty; - - $this->assertTrue($constraint->evaluate(new EmptyIterator, '', true)); - } -} diff --git a/tests/unit/Framework/Constraint/IsEqualTest.php b/tests/unit/Framework/Constraint/IsEqualTest.php deleted file mode 100644 index 27723a4de37..00000000000 --- a/tests/unit/Framework/Constraint/IsEqualTest.php +++ /dev/null @@ -1,338 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use function preg_replace; -use function spl_object_hash; -use DateTime; -use DateTimeZone; -use DOMDocument; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestFailure; -use SplObjectStorage; -use stdClass; - -/** - * @small - */ -final class IsEqualTest extends ConstraintTestCase -{ - public function testConstraintIsEqual(): void - { - $constraint = new IsEqual(1); - - $this->assertTrue($constraint->evaluate(1, '', true)); - $this->assertFalse($constraint->evaluate(0, '', true)); - $this->assertEquals('is equal to 1', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate(0); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that 0 matches expected 1. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - /** - * @dataProvider isEqualProvider - */ - public function testConstraintIsEqual2($expected, $actual, $message): void - { - $constraint = new IsEqual($expected); - - try { - $constraint->evaluate($actual, 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - "custom message\n{$message}", - $this->trimnl(TestFailure::exceptionToString($e)) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintDeltaIsNotZero(): void - { - $constraint = new IsEqual(15, 1); - - $this->assertSame('is equal to 15 with delta <1.000000>', $constraint->toString()); - } - - public function isEqualProvider(): array - { - $a = new stdClass; - $a->foo = 'bar'; - $b = new stdClass; - $ahash = spl_object_hash($a); - $bhash = spl_object_hash($b); - - $c = new stdClass; - $c->foo = 'bar'; - $c->int = 1; - $c->array = [0, [1], [2], 3]; - $c->related = new stdClass; - $c->related->foo = "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk"; - $c->self = $c; - $c->c = $c; - $d = new stdClass; - $d->foo = 'bar'; - $d->int = 2; - $d->array = [0, [4], [2], 3]; - $d->related = new stdClass; - $d->related->foo = "a\np\nc\nd\ne\nf\ng\nh\ni\nw\nk"; - $d->self = $d; - $d->c = $c; - - $storage1 = new SplObjectStorage; - $storage1->attach($a); - $storage1->attach($b); - $storage2 = new SplObjectStorage; - $storage2->attach($b); - $storage1hash = spl_object_hash($storage1); - $storage2hash = spl_object_hash($storage2); - - $dom1 = new DOMDocument; - $dom1->preserveWhiteSpace = false; - $dom1->loadXML(''); - $dom2 = new DOMDocument; - $dom2->preserveWhiteSpace = false; - $dom2->loadXML(''); - - return [ - [1, 0, <<<'EOF' -Failed asserting that 0 matches expected 1. - -EOF - ], - [1.1, 0, <<<'EOF' -Failed asserting that 0 matches expected 1.1. - -EOF - ], - ['a', 'b', <<<'EOF' -Failed asserting that two strings are equal. ---- Expected -+++ Actual -@@ @@ --'a' -+'b' - -EOF - ], - ["a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk", "a\np\nc\nd\ne\nf\ng\nh\ni\nw\nk", <<<'EOF' -Failed asserting that two strings are equal. ---- Expected -+++ Actual -@@ @@ - 'a\n --b\n -+p\n - c\n - d\n - e\n -@@ @@ - g\n - h\n - i\n --j\n -+w\n - k' - -EOF - ], - [1, [0], <<<'EOF' -Array (...) does not match expected type "integer". - -EOF - ], - [[0], 1, <<<'EOF' -1 does not match expected type "array". - -EOF - ], - [[0], [1], <<<'EOF' -Failed asserting that two arrays are equal. ---- Expected -+++ Actual -@@ @@ - Array ( -- 0 => 0 -+ 0 => 1 - ) - -EOF - ], - [[true], ['true'], <<<'EOF' -Failed asserting that two arrays are equal. ---- Expected -+++ Actual -@@ @@ - Array ( -- 0 => true -+ 0 => 'true' - ) - -EOF - ], - [[0, [1], [2], 3], [0, [4], [2], 3], <<<'EOF' -Failed asserting that two arrays are equal. ---- Expected -+++ Actual -@@ @@ - Array ( - 0 => 0 - 1 => Array ( -- 0 => 1 -+ 0 => 4 - ) - 2 => Array (...) - 3 => 3 - ) - -EOF - ], - [$a, [0], <<<'EOF' -Array (...) does not match expected type "object". - -EOF - ], - [[0], $a, <<<'EOF' -stdClass Object (...) does not match expected type "array". - -EOF - ], - [$a, $b, <<<'EOF' -Failed asserting that two objects are equal. ---- Expected -+++ Actual -@@ @@ - stdClass Object ( -- 'foo' => 'bar' - ) - -EOF - ], - [$c, $d, <<<'EOF' -Failed asserting that two objects are equal. ---- Expected -+++ Actual -@@ @@ - stdClass Object ( - 'foo' => 'bar' -- 'int' => 1 -+ 'int' => 2 - 'array' => Array ( - 0 => 0 - 1 => Array ( -- 0 => 1 -+ 0 => 4 - ) - 2 => Array (...) - 3 => 3 -@@ @@ - ) - 'related' => stdClass Object ( - 'foo' => 'a\n -- b\n -+ p\n - c\n - d\n - e\n -@@ @@ - g\n - h\n - i\n -- j\n -+ w\n - k' - ) - 'self' => stdClass Object (...) - 'c' => stdClass Object (...) - ) - -EOF - ], - [$dom1, $dom2, <<<'EOF' -Failed asserting that two DOM documents are equal. ---- Expected -+++ Actual -@@ @@ - -- -+ -+ -+ - -EOF - ], - [ - new DateTime('2013-03-29 04:13:35', new DateTimeZone('America/New_York')), - new DateTime('2013-03-29 04:13:35', new DateTimeZone('America/Chicago')), - <<<'EOF' -Failed asserting that two DateTime objects are equal. ---- Expected -+++ Actual -@@ @@ --2013-03-29T04:13:35.000000-0400 -+2013-03-29T04:13:35.000000-0500 - -EOF - ], - [$storage1, $storage2, << Array &0 ( -- 'obj' => stdClass Object &{$ahash} ( -- 'foo' => 'bar' -- ) -- 'inf' => null -- ) -- '{$bhash}' => Array &1 ( -+SplObjectStorage Object &{$storage2hash} ( -+ '{$bhash}' => Array &0 ( - 'obj' => stdClass Object &{$bhash} () - 'inf' => null - ) - ) - -EOF - ], - ]; - } - - /** - * Removes spaces in front of newlines. - * - * @param string $string - * - * @return string - */ - private function trimnl($string) - { - return preg_replace('/[ ]*\n/', "\n", $string); - } -} diff --git a/tests/unit/Framework/Constraint/IsIdenticalTest.php b/tests/unit/Framework/Constraint/IsIdenticalTest.php index 5ee4cddd857..a77e655ed40 100644 --- a/tests/unit/Framework/Constraint/IsIdenticalTest.php +++ b/tests/unit/Framework/Constraint/IsIdenticalTest.php @@ -9,130 +9,157 @@ */ namespace PHPUnit\Framework\Constraint; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestFailure; +use PHPUnit\Framework\TestCase; use stdClass; -/** - * @small - */ -final class IsIdenticalTest extends ConstraintTestCase +#[CoversClass(IsIdentical::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class IsIdenticalTest extends TestCase { - public function testConstraintIsIdentical(): void - { - $a = new stdClass; - $b = new stdClass; - - $constraint = new IsIdentical($a); - - $this->assertFalse($constraint->evaluate($b, '', true)); - $this->assertTrue($constraint->evaluate($a, '', true)); - $this->assertEquals('is identical to an object of class "stdClass"', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate($b); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that two variables reference the same object. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintIsIdentical2(): void + public static function provider(): array { - $a = new stdClass; - $b = new stdClass; - - $constraint = new IsIdentical($a); + return [ + [ + 'is identical to 0', + 'Failed asserting that 1 is identical to 0.', + '', + 0, + 1, + ], - try { - $constraint->evaluate($b, 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that two variables reference the same object. + [ + 'is identical to an object of class "stdClass"', + 'Failed asserting that two variables reference the same object.', + '', + new stdClass, + new stdClass, + ], -EOF - , - TestFailure::exceptionToString($e) - ); + [ + 'is identical to \'expected\'', + 'Failed asserting that two strings are identical.', + <<<'EOT' - return; - } +--- Expected ++++ Actual +@@ @@ +-'expected' ++'actual' - $this->fail(); - } +EOT, + 'expected', + 'actual', + ], - public function testConstraintIsIdentical3(): void - { - $constraint = new IsIdentical('a'); + [ + <<<'EOT' +is identical to Array &0 [ + 0 => 1, + 1 => 2, + 2 => 3, + 3 => 4, + 4 => 5, + 5 => 6, +] +EOT, + 'Failed asserting that two arrays are identical.', + <<<'EOT' - try { - $constraint->evaluate('b', 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that two strings are identical. --- Expected +++ Actual @@ @@ --'a' -+'b' - -EOF - , - TestFailure::exceptionToString($e) - ); + Array &0 [ + 0 => 1, + 1 => 2, +- 2 => 3, ++ 2 => 33, + 3 => 4, + 4 => 5, + 5 => 6, + ] + +EOT, + [1, 2, 3, 4, 5, 6], + [1, 2, 33, 4, 5, 6], + ], - return; - } + [ + <<<'EOT' +is identical to Array &0 [ + 0 => Array &1 [ + 'A' => 'B', + ], + 1 => Array &2 [ + 'C' => Array &3 [ + 0 => 'D', + 1 => 'E', + ], + ], +] +EOT, + 'Failed asserting that two arrays are identical.', + <<<'EOT' - $this->fail(); +--- Expected ++++ Actual +@@ @@ + Array &0 [ + 0 => Array &1 [ +- 'A' => 'B', ++ 'A' => 'C', + ], + 1 => Array &2 [ + 'C' => Array &3 [ +- 0 => 'D', ++ 0 => 'C', + 1 => 'E', ++ 2 => 'F', + ], + ], + ] + +EOT, + [ + ['A' => 'B'], + [ + 'C' => [ + 'D', + 'E', + ], + ], + ], + [ + ['A' => 'C'], + [ + 'C' => [ + 'C', + 'E', + 'F', + ], + ], + ], + ], + ]; } - public function testConstraintIsIdenticalArrayDiff(): void + #[DataProvider('provider')] + public function testCanBeEvaluated(string $constraintAsString, string $failureDescription, string $comparisonFailureAsString, mixed $expected, mixed $actual): void { - $expected = [1, 2, 3, 4, 5, 6]; - $actual = [1, 2, 33, 4, 5, 6]; - $constraint = new IsIdentical($expected); + $this->assertTrue($constraint->evaluate($expected, returnResult: true)); + $this->assertFalse($constraint->evaluate($actual, returnResult: true)); + try { - $constraint->evaluate($actual, 'custom message'); + $constraint->evaluate($actual); } catch (ExpectationFailedException $e) { - $this->assertSame( - <<<'EOF' -custom message -Failed asserting that two arrays are identical. ---- Expected -+++ Actual -@@ @@ - Array &0 ( - 0 => 1 - 1 => 2 -- 2 => 3 -+ 2 => 33 - 3 => 4 - 4 => 5 - 5 => 6 - ) - -EOF - , - TestFailure::exceptionToString($e) - ); + $this->assertSame($failureDescription, $e->getMessage()); + $this->assertSame($comparisonFailureAsString, $e->getComparisonFailure() ? $e->getComparisonFailure()->toString() : ''); return; } @@ -140,62 +167,16 @@ public function testConstraintIsIdenticalArrayDiff(): void $this->fail(); } - public function testConstraintIsIdenticalNestedArrayDiff(): void + #[DataProvider('provider')] + public function testCanBeRepresentedAsString(string $constraintAsString, string $failureDescription, string $comparisonFailureAsString, mixed $expected, mixed $actual): void { - $expected = [ - ['A' => 'B'], - [ - 'C' => [ - 'D', - 'E', - ], - ], - ]; - $actual = [ - ['A' => 'C'], - [ - 'C' => [ - 'C', - 'E', - 'F', - ], - ], - ]; $constraint = new IsIdentical($expected); - try { - $constraint->evaluate($actual, 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that two arrays are identical. ---- Expected -+++ Actual -@@ @@ - Array &0 ( - 0 => Array &1 ( -- 'A' => 'B' -+ 'A' => 'C' - ) - 1 => Array &2 ( - 'C' => Array &3 ( -- 0 => 'D' -+ 0 => 'C' - 1 => 'E' -+ 2 => 'F' - ) - ) - ) - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } + $this->assertSame($constraintAsString, $constraint->toString()); + } - $this->fail(); + public function testIsCountable(): void + { + $this->assertCount(1, (new IsIdentical(true))); } } diff --git a/tests/unit/Framework/Constraint/IsInstanceOfTest.php b/tests/unit/Framework/Constraint/IsInstanceOfTest.php deleted file mode 100644 index 50039570c1c..00000000000 --- a/tests/unit/Framework/Constraint/IsInstanceOfTest.php +++ /dev/null @@ -1,58 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestFailure; -use ReflectionException; -use stdClass; - -/** - * @small - */ -final class IsInstanceOfTest extends ConstraintTestCase -{ - public function testConstraintInstanceOf(): void - { - $constraint = new IsInstanceOf(stdClass::class); - - self::assertTrue($constraint->evaluate(new stdClass, '', true)); - } - - public function testConstraintFailsOnString(): void - { - $constraint = new IsInstanceOf(stdClass::class); - - try { - $constraint->evaluate('stdClass'); - } catch (ExpectationFailedException $e) { - self::assertSame( - <<<'EOT' -Failed asserting that 'stdClass' is an instance of class "stdClass". - -EOT - , - TestFailure::exceptionToString($e) - ); - } - } - - public function testCronstraintsThrowsReflectionException(): void - { - $this->throwException(new ReflectionException); - - $constraint = new IsInstanceOf(NotExistingClass::class); - - self::assertSame( - 'is instance of class "PHPUnit\Framework\Constraint\NotExistingClass"', - $constraint->toString() - ); - } -} diff --git a/tests/unit/Framework/Constraint/IsJsonTest.php b/tests/unit/Framework/Constraint/IsJsonTest.php deleted file mode 100644 index 97e2e29f12c..00000000000 --- a/tests/unit/Framework/Constraint/IsJsonTest.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestFailure; - -/** - * @small - */ -final class IsJsonTest extends ConstraintTestCase -{ - public static function evaluateDataprovider(): array - { - return [ - 'valid JSON' => [true, '{}'], - 'empty string should be treated as invalid JSON' => [false, ''], - ]; - } - - /** - * @testdox Evaluate $_dataName - * @dataProvider evaluateDataprovider - * - * @throws \PHPUnit\Framework\ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function testEvaluate($expected, $jsonOther): void - { - $constraint = new IsJson; - - $this->assertEquals($expected, $constraint->evaluate($jsonOther, '', true)); - } - - public function testIsJsonCanBeExportedAsString(): void - { - $isJson = new IsJson; - - $this->assertSame('is valid JSON', $isJson->toString()); - } - - public function testIsJsonCanBeEmptyString(): void - { - $isJson = new IsJson; - - try { - $isJson->evaluate(''); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that an empty string is valid JSON. - -EOF - , - TestFailure::exceptionToString($e) - ); - } - } -} diff --git a/tests/unit/Framework/Constraint/IsNullTest.php b/tests/unit/Framework/Constraint/IsNullTest.php deleted file mode 100644 index cb1c4a76429..00000000000 --- a/tests/unit/Framework/Constraint/IsNullTest.php +++ /dev/null @@ -1,69 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestFailure; - -/** - * @small - */ -final class IsNullTest extends ConstraintTestCase -{ - public function testConstraintIsNull(): void - { - $constraint = new IsNull; - - $this->assertFalse($constraint->evaluate(0, '', true)); - $this->assertTrue($constraint->evaluate(null, '', true)); - $this->assertEquals('is null', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate(0); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that 0 is null. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintIsNull2(): void - { - $constraint = new IsNull; - - try { - $constraint->evaluate(0, 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that 0 is null. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } -} diff --git a/tests/unit/Framework/Constraint/IsReadableTest.php b/tests/unit/Framework/Constraint/IsReadableTest.php deleted file mode 100644 index 0f2665fb6fb..00000000000 --- a/tests/unit/Framework/Constraint/IsReadableTest.php +++ /dev/null @@ -1,45 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestFailure; - -/** - * @small - */ -final class IsReadableTest extends ConstraintTestCase -{ - public function testConstraintIsReadable(): void - { - $constraint = new IsReadable; - - $this->assertFalse($constraint->evaluate('foo', '', true)); - $this->assertEquals('is readable', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate('foo'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that "foo" is readable. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } -} diff --git a/tests/unit/Framework/Constraint/IsTypeTest.php b/tests/unit/Framework/Constraint/IsTypeTest.php deleted file mode 100644 index 0aebdefe495..00000000000 --- a/tests/unit/Framework/Constraint/IsTypeTest.php +++ /dev/null @@ -1,145 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use function fclose; -use function fopen; -use function is_resource; -use function preg_replace; -use PHPUnit\Framework\Assert; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestFailure; -use stdClass; - -/** - * @small - */ -final class IsTypeTest extends ConstraintTestCase -{ - public function testConstraintIsType(): void - { - $constraint = Assert::isType('string'); - - $this->assertFalse($constraint->evaluate(0, '', true)); - $this->assertTrue($constraint->evaluate('', '', true)); - $this->assertEquals('is of type "string"', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate(new stdClass); - } catch (ExpectationFailedException $e) { - $this->assertStringMatchesFormat( - <<<'EOF' -Failed asserting that stdClass Object &%x () is of type "string". - -EOF - , - $this->trimnl(TestFailure::exceptionToString($e)) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintIsType2(): void - { - $constraint = Assert::isType('string'); - - try { - $constraint->evaluate(new stdClass, 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertStringMatchesFormat( - <<<'EOF' -custom message -Failed asserting that stdClass Object &%x () is of type "string". - -EOF - , - $this->trimnl(TestFailure::exceptionToString($e)) - ); - - return; - } - - $this->fail(); - } - - /** - * @dataProvider resources - */ - public function testConstraintIsResourceTypeEvaluatesCorrectlyWithResources($resource): void - { - $constraint = Assert::isType('resource'); - - $this->assertTrue($constraint->evaluate($resource, '', true)); - - if (is_resource($resource)) { - @fclose($resource); - } - } - - public function resources() - { - $fh = fopen(__FILE__, 'r'); - fclose($fh); - - return [ - 'open resource' => [fopen(__FILE__, 'r')], - 'closed resource' => [$fh], - ]; - } - - public function testIterableTypeIsSupported(): void - { - $constraint = Assert::isType('iterable'); - - $this->assertFalse($constraint->evaluate('', '', true)); - $this->assertTrue($constraint->evaluate([], '', true)); - $this->assertEquals('is of type "iterable"', $constraint->toString()); - } - - public function testTypeCanBeNull(): void - { - $constraint = Assert::isType('null'); - - $this->assertNull($constraint->evaluate(null)); - $this->assertEquals('is of type "null"', $constraint->toString()); - } - - public function testTypeCanNotBeAnUndefinedOne(): void - { - try { - Assert::isType('diverse'); - } catch (\PHPUnit\Framework\Exception $e) { - $this->assertEquals( - << is not a valid type. - -EOF - , - TestFailure::exceptionToString($e) - ); - } - } - - /** - * Removes spaces in front of newlines. - * - * @param string $string - * - * @return string - */ - private function trimnl($string) - { - return preg_replace('/[ ]*\n/', "\n", $string); - } -} diff --git a/tests/unit/Framework/Constraint/IsWritableTest.php b/tests/unit/Framework/Constraint/IsWritableTest.php deleted file mode 100644 index c0ad01d3085..00000000000 --- a/tests/unit/Framework/Constraint/IsWritableTest.php +++ /dev/null @@ -1,45 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestFailure; - -/** - * @small - */ -final class IsWritableTest extends ConstraintTestCase -{ - public function testConstraintIsWritable(): void - { - $constraint = new IsWritable; - - $this->assertFalse($constraint->evaluate('foo', '', true)); - $this->assertEquals('is writable', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate('foo'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that "foo" is writable. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } -} diff --git a/tests/unit/Framework/Constraint/JsonMatchesErrorMessageProviderTest.php b/tests/unit/Framework/Constraint/JsonMatchesErrorMessageProviderTest.php deleted file mode 100644 index 5211e17571a..00000000000 --- a/tests/unit/Framework/Constraint/JsonMatchesErrorMessageProviderTest.php +++ /dev/null @@ -1,96 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use const JSON_ERROR_CTRL_CHAR; -use const JSON_ERROR_DEPTH; -use const JSON_ERROR_STATE_MISMATCH; -use const JSON_ERROR_SYNTAX; -use const JSON_ERROR_UTF8; -use PHPUnit\Framework\TestCase; - -/** - * @small - */ -final class JsonMatchesErrorMessageProviderTest extends TestCase -{ - public static function determineJsonErrorDataprovider(): array - { - return [ - 'JSON_ERROR_NONE' => [ - null, 'json_error_none', '', - ], - 'JSON_ERROR_DEPTH' => [ - 'Maximum stack depth exceeded', JSON_ERROR_DEPTH, '', - ], - 'prefixed JSON_ERROR_DEPTH' => [ - 'TUX: Maximum stack depth exceeded', JSON_ERROR_DEPTH, 'TUX: ', - ], - 'JSON_ERROR_STATE_MISMatch' => [ - 'Underflow or the modes mismatch', JSON_ERROR_STATE_MISMATCH, '', - ], - 'JSON_ERROR_CTRL_CHAR' => [ - 'Unexpected control character found', JSON_ERROR_CTRL_CHAR, '', - ], - 'JSON_ERROR_SYNTAX' => [ - 'Syntax error, malformed JSON', JSON_ERROR_SYNTAX, '', - ], - 'JSON_ERROR_UTF8`' => [ - 'Malformed UTF-8 characters, possibly incorrectly encoded', - JSON_ERROR_UTF8, - '', - ], - 'Invalid error indicator' => [ - 'Unknown error', 55, '', - ], - ]; - } - - public static function translateTypeToPrefixDataprovider(): array - { - return [ - 'expected' => ['Expected value JSON decode error - ', 'expected'], - 'actual' => ['Actual value JSON decode error - ', 'actual'], - 'default' => ['', ''], - ]; - } - - /** - * @dataProvider translateTypeToPrefixDataprovider - * - * @throws \PHPUnit\Framework\ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function testTranslateTypeToPrefix($expected, $type): void - { - $this->assertEquals( - $expected, - JsonMatchesErrorMessageProvider::translateTypeToPrefix($type) - ); - } - - /** - * @testdox Determine JSON error $_dataName - * @dataProvider determineJsonErrorDataprovider - * - * @throws \PHPUnit\Framework\ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function testDetermineJsonError($expected, $error, $prefix): void - { - $this->assertEquals( - $expected, - JsonMatchesErrorMessageProvider::determineJsonError( - (string) $error, - $prefix - ) - ); - } -} diff --git a/tests/unit/Framework/Constraint/JsonMatchesTest.php b/tests/unit/Framework/Constraint/JsonMatchesTest.php index 544b129dbb3..adbab49aead 100644 --- a/tests/unit/Framework/Constraint/JsonMatchesTest.php +++ b/tests/unit/Framework/Constraint/JsonMatchesTest.php @@ -10,186 +10,409 @@ namespace PHPUnit\Framework\Constraint; use function json_encode; -use function sprintf; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestFailure; -use PHPUnit\Util\Json; +use PHPUnit\Framework\TestCase; -/** - * @small - */ -final class JsonMatchesTest extends ConstraintTestCase +#[CoversClass(JsonMatches::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class JsonMatchesTest extends TestCase { - public static function evaluateDataprovider(): array + public static function provider(): array { return [ - 'valid JSON' => [true, json_encode(['Mascott' => 'Tux']), json_encode(['Mascott' => 'Tux'])], - 'error syntax' => [false, '{"Mascott"::}', json_encode(['Mascott' => 'Tux'])], - 'error UTF-8' => [false, json_encode('\xB1\x31'), json_encode(['Mascott' => 'Tux'])], - 'invalid JSON in class instantiation' => [false, json_encode(['Mascott' => 'Tux']), '{"Mascott"::}'], - 'string type not equals number' => [false, '{"age": "5"}', '{"age": 5}'], - 'string type not equals boolean' => [false, '{"age": "true"}', '{"age": true}'], - 'string type not equals null' => [false, '{"age": "null"}', '{"age": null}'], - 'object fields are unordered' => [true, '{"first":1, "second":"2"}', '{"second":"2", "first":1}'], - 'child object fields are unordered' => [true, '{"Mascott": {"name":"Tux", "age":5}}', '{"Mascott": {"age":5, "name":"Tux"}}'], - 'null field different from missing field' => [false, '{"present": true, "missing": null}', '{"present": true}'], - 'array elements are ordered' => [false, '["first", "second"]', '["second", "first"]'], - 'single boolean valid json' => [true, 'true', 'true'], - 'single number valid json' => [true, '5.3', '5.3'], - 'single null valid json' => [true, 'null', 'null'], - 'objects are not arrays' => [false, '{}', '[]'], - ]; - } + 'valid JSON' => [ + true, + '', + '', + json_encode(['Mascott' => 'Tux']), + json_encode(['Mascott' => 'Tux']), + ], - public static function evaluateThrowsExpectationFailedExceptionWhenJsonIsValidButDoesNotMatchDataprovider(): array - { - return [ - 'error UTF-8' => [json_encode('\xB1\x31'), json_encode(['Mascott' => 'Tux'])], - 'string type not equals number' => ['{"age": "5"}', '{"age": 5}'], - 'string type not equals boolean' => ['{"age": "true"}', '{"age": true}'], - 'string type not equals null' => ['{"age": "null"}', '{"age": null}'], - 'null field different from missing field' => ['{"missing": null, "present": true}', '{"present": true}'], - 'array elements are ordered' => ['["first", "second"]', '["second", "first"]'], - ]; - } + 'object fields are unordered' => [ + true, + '', + '', + '{"second":"2", "first":1}', + '{"first":1, "second":"2"}', + ], - /** - * @dataProvider evaluateDataprovider - * - * @throws ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function testEvaluate($expected, $jsonOther, $jsonValue): void - { - $constraint = new JsonMatches($jsonValue); + 'object fields with numeric keys are unordered' => [ + true, + '', + '', + '{"0":null,"a":{},"b":[],"c":"1","d":1,"e":-1,"f":[1,2],"g":[2,1],"h":{"0":"0","1":"1","2":"2"}}', + '{"a":{},"d":1,"b":[],"e":-1,"0":null,"c":"1","f":[1,2],"h":{"2":"2","1":"1","0":"0"},"g":[2,1]}', + ], - $this->assertEquals($expected, $constraint->evaluate($jsonOther, '', true)); - } + 'child object fields are unordered' => [ + true, + '', + '', + '{"Mascott": {"age":5, "name":"Tux"}}', + '{"Mascott": {"name":"Tux", "age":5}}', + ], - /** - * @dataProvider evaluateThrowsExpectationFailedExceptionWhenJsonIsValidButDoesNotMatchDataprovider - * - * @throws ExpectationFailedException - * @throws \PHPUnit\Framework\AssertionFailedError - * @throws \PHPUnit\Framework\Exception - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function testEvaluateThrowsExpectationFailedExceptionWhenJsonIsValidButDoesNotMatch($jsonOther, $jsonValue): void - { - $constraint = new JsonMatches($jsonValue); + 'single boolean valid json' => [ + true, + '', + '', + 'true', + 'true', + ], - try { - $constraint->evaluate($jsonOther, '', false); - $this->fail(sprintf('Expected %s to be thrown.', ExpectationFailedException::class)); - } catch (ExpectationFailedException $expectedException) { - $comparisonFailure = $expectedException->getComparisonFailure(); - $this->assertNotNull($comparisonFailure); - - [$error, $jsonOtherCanonicalized] = Json::canonicalize($jsonOther); - [$error, $jsonValueCanonicalized] = Json::canonicalize($jsonValue); - - $this->assertSame(Json::prettify($jsonOtherCanonicalized), $comparisonFailure->getActualAsString()); - $this->assertSame(Json::prettify($jsonValueCanonicalized), $comparisonFailure->getExpectedAsString()); - $this->assertSame('Failed asserting that two json values are equal.', $comparisonFailure->getMessage()); - } - } + 'single number valid json' => [ + true, + '', + '', + '5.3', + '5.3', + ], - public function testToString(): void - { - $jsonValue = json_encode(['Mascott' => 'Tux']); - $constraint = new JsonMatches($jsonValue); + 'single null valid json' => [ + true, + '', + '', + 'null', + 'null', + ], - $this->assertEquals('matches JSON string "' . $jsonValue . '"', $constraint->toString()); - } + 'invalid JSON in class instantiation' => [ + false, + 'Failed asserting that \'{"Mascott":"Tux"}\' matches JSON string "{"Mascott"::}".', + '', + '{"Mascott"::}', + json_encode(['Mascott' => 'Tux']), + ], - public function testFailErrorWithInvalidValueAndOther(): void - { - $constraint = new JsonMatches('{"Mascott"::}'); + 'error syntax' => [ + false, + 'Failed asserting that \'{"Mascott"::}\' matches JSON string "{"Mascott":"Tux"}".', + '', + json_encode(['Mascott' => 'Tux']), + '{"Mascott"::}', + ], - try { - $constraint->evaluate('{"Mascott"::}', '', false); - $this->fail(sprintf('Expected %s to be thrown.', ExpectationFailedException::class)); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that '{"Mascott"::}' matches JSON string "{"Mascott"::}". - -EOF - , - TestFailure::exceptionToString($e) - ); - } - } + 'error UTF-8' => [ + false, + 'Failed asserting that \'' . json_encode('\xB1\x31') . '\' matches JSON string "{"Mascott":"Tux"}".', + <<<'EOT' +Failed asserting that two json values are equal. +--- Expected ++++ Actual +@@ @@ +-{ +- "Mascott": "Tux" +-} ++"\\xB1\\x31" - public function testFailErrorWithValidValueAndInvalidOther(): void - { - $constraint = new JsonMatches('{"Mascott"::}'); +EOT, + json_encode(['Mascott' => 'Tux']), + json_encode('\xB1\x31'), + ], - try { - $constraint->evaluate('{"Mascott":"Tux"}', '', false); - $this->fail(sprintf('Expected %s to be thrown.', ExpectationFailedException::class)); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that '{"Mascott":"Tux"}' matches JSON string "{"Mascott"::}". - -EOF - , - TestFailure::exceptionToString($e) - ); - } - } + 'string type not equals number' => [ + false, + 'Failed asserting that \'{"age": "5"}\' matches JSON string "{"age": 5}".', + <<<'EOT' +Failed asserting that two json values are equal. +--- Expected ++++ Actual +@@ @@ + { +- "age": 5 ++ "age": "5" + } - public function testEmptyObjectNotConvertedToArrayInDiff(): void - { - $constraint = new JsonMatches('{"obj": {}, "val": 1}'); +EOT, + '{"age": 5}', + '{"age": "5"}', + ], - try { - $constraint->evaluate('{"obj": {}, "val": 2}', '', false); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that '{"obj": {}, "val": 2}' matches JSON string "{"obj": {}, "val": 1}". + 'string type not equals boolean' => [ + false, + 'Failed asserting that \'{"age": "true"}\' matches JSON string "{"age": true}".', + <<<'EOT' +Failed asserting that two json values are equal. --- Expected +++ Actual @@ @@ { - "obj": {}, -- "val": 1 -+ "val": 2 +- "age": true ++ "age": "true" } -EOF - , - TestFailure::exceptionToString($e) - ); - } - } +EOT, + '{"age": true}', + '{"age": "true"}', + ], - public function testObjectAreCanonicalizedInDiff(): void - { - $constraint = new JsonMatches('{"obj": {"x": 1, "y": 2}, "val": 1}'); + 'string type not equals null' => [ + false, + 'Failed asserting that \'{"age": "null"}\' matches JSON string "{"age": null}".', + <<<'EOT' +Failed asserting that two json values are equal. +--- Expected ++++ Actual +@@ @@ + { +- "age": null ++ "age": "null" + } - try { - $constraint->evaluate('{"obj": {"y": 2, "x": 1}, "val": 2}', '', false); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that '{"obj": {"y": 2, "x": 1}, "val": 2}' matches JSON string "{"obj": {"x": 1, "y": 2}, "val": 1}". +EOT, + '{"age": null}', + '{"age": "null"}', + ], + + 'null field different from missing field' => [ + false, + 'Failed asserting that \'{"present": true, "missing": null}\' matches JSON string "{"present": true}".', + <<<'EOT' +Failed asserting that two json values are equal. +--- Expected ++++ Actual +@@ @@ + { ++ "missing": null, + "present": true + } + +EOT, + '{"present": true}', + '{"present": true, "missing": null}', + ], + + 'array elements are ordered' => [ + false, + 'Failed asserting that \'["first", "second"]\' matches JSON string "["second", "first"]".', + <<<'EOT' +Failed asserting that two json values are equal. +--- Expected ++++ Actual +@@ @@ + [ +- "second", +- "first" ++ "first", ++ "second" + ] + +EOT, + '["second", "first"]', + '["first", "second"]', + ], + + 'objects with numeric keys are not arrays' => [ + false, + 'Failed asserting that \'[{}]\' matches JSON string "{"0":{}}".', + <<<'EOT' +Failed asserting that two json values are equal. +--- Expected ++++ Actual +@@ @@ +-{ +- "0": {} +-} ++[ ++ {} ++] + +EOT, + '{"0":{}}', + '[{}]', + ], + + 'child array elements are ordered' => [ + false, + 'Failed asserting that \'{"a":{},"d":1,"b":[],"e":-1,"0":null,"c":"1","f":[2,1],"h":{"2":"2","1":"1","0":"0"},"g":[2,1]}\' matches JSON string "{"0":null,"a":{},"b":[],"c":"1","d":1,"e":-1,"f":[1,2],"g":[2,1],"h":{"0":"0","1":"1","2":"2"}}".', + <<<'EOT' +Failed asserting that two json values are equal. +--- Expected ++++ Actual +@@ @@ + "d": 1, + "e": -1, + "f": [ +- 1, +- 2 ++ 2, ++ 1 + ], + "g": [ + 2, + +EOT, + '{"0":null,"a":{},"b":[],"c":"1","d":1,"e":-1,"f":[1,2],"g":[2,1],"h":{"0":"0","1":"1","2":"2"}}', + '{"a":{},"d":1,"b":[],"e":-1,"0":null,"c":"1","f":[2,1],"h":{"2":"2","1":"1","0":"0"},"g":[2,1]}', + ], + + 'child object with numeric fields stay as object' => [ + false, + 'Failed asserting that \'{"a":{},"d":1,"b":[],"e":-1,"0":null,"c":"1","f":[1,2],"h":["0","1","2"],"g":[2,1]}\' matches JSON string "{"0":null,"a":{},"b":[],"c":"1","d":1,"e":-1,"f":[1,2],"g":[2,1],"h":{"0":"0","1":"1","2":"2"}}".', + <<<'EOT' +Failed asserting that two json values are equal. --- Expected +++ Actual @@ @@ - "x": 1, - "y": 2 - }, -- "val": 1 -+ "val": 2 + 2, + 1 + ], +- "h": { +- "0": "0", +- "1": "1", +- "2": "2" +- } ++ "h": [ ++ "0", ++ "1", ++ "2" ++ ] } -EOF - , - TestFailure::exceptionToString($e) - ); +EOT, + '{"0":null,"a":{},"b":[],"c":"1","d":1,"e":-1,"f":[1,2],"g":[2,1],"h":{"0":"0","1":"1","2":"2"}}', + '{"a":{},"d":1,"b":[],"e":-1,"0":null,"c":"1","f":[1,2],"h":["0","1","2"],"g":[2,1]}', + ], + + 'nested arrays are ordered' => [ + false, + 'Failed asserting that \'[{"1":"1","0":"0"},{"2":"2","3":"3"}]\' matches JSON string "[[1,0],[2,3]]".', + <<<'EOT' +Failed asserting that two json values are equal. +--- Expected ++++ Actual +@@ @@ + [ +- [ +- 1, +- 0 +- ], +- [ +- 2, +- 3 +- ] ++ { ++ "0": "0", ++ "1": "1" ++ }, ++ { ++ "2": "2", ++ "3": "3" ++ } + ] + +EOT, + '[[1,0],[2,3]]', + '[{"1":"1","0":"0"},{"2":"2","3":"3"}]', + ], + + 'child objects in arrays stay in order' => [ + false, + 'Failed asserting that \'[{"2":"2","3":"3"},{"1":"1","0":"0"}]\' matches JSON string "[{"0":"0","1":"1"},{"2":"2","3":"3"}]".', + <<<'EOT' +Failed asserting that two json values are equal. +--- Expected ++++ Actual +@@ @@ + [ + { ++ "2": "2", ++ "3": "3" ++ }, ++ { + "0": "0", + "1": "1" +- }, +- { +- "2": "2", +- "3": "3" + } + ] + +EOT, + + '[{"0":"0","1":"1"},{"2":"2","3":"3"}]', + '[{"2":"2","3":"3"},{"1":"1","0":"0"}]', + ], + + 'objects are not arrays' => [ + false, + 'Failed asserting that \'{}\' matches JSON string "[]".', + <<<'EOT' +Failed asserting that two json values are equal. +--- Expected ++++ Actual +@@ @@ +-[] ++{} + +EOT, + '[]', + '{}', + ], + + 'arrays are not objects' => [ + false, + 'Failed asserting that \'{}\' matches JSON string "[]".', + <<<'EOT' +Failed asserting that two json values are equal. +--- Expected ++++ Actual +@@ @@ +-[] ++{} + +EOT, + '[]', + '{}', + ], + + 'objects in arrays are unordered' => [ + true, + '', + '', + '[{"0":"0","1":"1"},{"2":"2","3":"3"}]', + '[{"1":"1","0":"0"},{"2":"2","3":"3"}]', + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $failureDescription, string $comparisonFailureAsString, mixed $expected, mixed $actual): void + { + $constraint = new JsonMatches($expected); + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; } + + try { + $constraint->evaluate($actual); + } catch (ExpectationFailedException $e) { + $this->assertSame($failureDescription, $e->getMessage()); + $this->assertSame($comparisonFailureAsString, $e->getComparisonFailure() ? $e->getComparisonFailure()->toString() : ''); + + return; + } + + $this->fail(); + } + + public function testCanBeRepresentedAsString(): void + { + $constraint = new JsonMatches(json_encode(['key' => 'value'])); + + $this->assertSame('matches JSON string "{"key":"value"}"', $constraint->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new JsonMatches(json_encode(['key' => 'value'])))); } } diff --git a/tests/unit/Framework/Constraint/LessThanTest.php b/tests/unit/Framework/Constraint/LessThanTest.php deleted file mode 100644 index 114d5553d7d..00000000000 --- a/tests/unit/Framework/Constraint/LessThanTest.php +++ /dev/null @@ -1,69 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestFailure; - -/** - * @small - */ -final class LessThanTest extends ConstraintTestCase -{ - public function testConstraintLessThan(): void - { - $constraint = new LessThan(1); - - $this->assertTrue($constraint->evaluate(0, '', true)); - $this->assertFalse($constraint->evaluate(1, '', true)); - $this->assertEquals('is less than 1', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate(1); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that 1 is less than 1. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintLessThan2(): void - { - $constraint = new LessThan(1); - - try { - $constraint->evaluate(1, 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that 1 is less than 1. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } -} diff --git a/tests/unit/Framework/Constraint/LogicalAndTest.php b/tests/unit/Framework/Constraint/LogicalAndTest.php deleted file mode 100644 index 7476d9e5719..00000000000 --- a/tests/unit/Framework/Constraint/LogicalAndTest.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use function array_reduce; -use function array_shift; - -/** - * @small - */ -final class LogicalAndTest extends BinaryOperatorTestCase -{ - public static function getOperatorName(): string - { - return 'and'; - } - - public static function getOperatorPrecedence(): int - { - return 22; - } - - public function evaluateExpectedResult(array $input): bool - { - $initial = (bool) array_shift($input); - - return array_reduce($input, static function ($carry, bool $item): bool { - return $carry && $item; - }, $initial); - } -} diff --git a/tests/unit/Framework/Constraint/LogicalExpressionsTest.php b/tests/unit/Framework/Constraint/LogicalExpressionsTest.php deleted file mode 100644 index ac8871d3d6e..00000000000 --- a/tests/unit/Framework/Constraint/LogicalExpressionsTest.php +++ /dev/null @@ -1,513 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestCase; - -/** - * @small - */ -final class LogicalExpressionsTest extends TestCase -{ - public function testLogicalNotOfDegenerateLogicalAnd(): void - { - $constraint = new LogicalNot( - LogicalAnd::fromConstraints( - new IsNull(), - ) - ); - - $this->assertTrue($constraint->evaluate('string', '', true)); - $this->assertFalse($constraint->evaluate(null, '', true)); - - $string = 'is not null'; - $this->assertSame($string, $constraint->toString()); - - $message = 'Failed asserting that null is not null'; - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage($message); - - $this->assertThat(null, $constraint); - } - - public function testLogicalNotOfLogicalAndWithThreeArguments(): void - { - $constraint = new LogicalNot( - LogicalAnd::fromConstraints( - new IsType('int'), - new GreaterThan(5), - new LessThan(10) - ) - ); - - $this->assertTrue($constraint->evaluate('string', '', true)); - $this->assertTrue($constraint->evaluate(7.7, '', true)); - $this->assertTrue($constraint->evaluate(2, '', true)); - $this->assertTrue($constraint->evaluate(13, '', true)); - $this->assertFalse($constraint->evaluate(7, '', true)); - - $string = 'not( is of type "int" and is greater than 5 and is less than 10 )'; - $this->assertSame($string, $constraint->toString()); - - $message = 'Failed asserting that ' . - 'not( 7 is of type "int" and is greater than 5 and is less than 10 )' . - '.'; - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage($message); - - $this->assertThat(7, $constraint); - } - - public function testLogicalNotOfDegenerateLogicalOr(): void - { - $constraint = new LogicalNot( - LogicalOr::fromConstraints( - new IsNull(), - ) - ); - - $this->assertTrue($constraint->evaluate('string', '', true)); - $this->assertFalse($constraint->evaluate(null, '', true)); - - $string = 'is not null'; - $this->assertSame($string, $constraint->toString()); - - $message = 'Failed asserting that null is not null'; - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage($message); - - $this->assertThat(null, $constraint); - } - - public function testLogicalNotOfLogicalOrWithThreeArguments(): void - { - $constraint = new LogicalNot( - LogicalOr::fromConstraints( - new IsNull(), - new IsType('int'), - new IsType('array') - ) - ); - - $this->assertTrue($constraint->evaluate('string', '', true)); - $this->assertFalse($constraint->evaluate([], '', true)); - $this->assertFalse($constraint->evaluate(2, '', true)); - $this->assertFalse($constraint->evaluate(null, '', true)); - - $string = 'not( is null or is of type "int" or is of type "array" )'; - $this->assertSame($string, $constraint->toString()); - - $message = 'Failed asserting that ' . - 'not( 2 is null or is of type "int" or is of type "array" )' . - '.'; - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage($message); - - $this->assertThat(2, $constraint); - } - - public function testLogicalNotOfDegenerateLogicalXor(): void - { - $constraint = new LogicalNot( - LogicalXor::fromConstraints( - new IsNull(), - ) - ); - - $this->assertTrue($constraint->evaluate('string', '', true)); - $this->assertFalse($constraint->evaluate(null, '', true)); - - $string = 'is not null'; - $this->assertSame($string, $constraint->toString()); - - $message = 'Failed asserting that null is not null'; - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage($message); - - $this->assertThat(null, $constraint); - } - - public function testLogicalNotOfLogicalXorWithTwoArguments(): void - { - $constraint = new LogicalNot( - LogicalXor::fromConstraints( - new IsType('int'), - new IsEqual(false), - ) - ); - - $this->assertTrue($constraint->evaluate(0, '', true)); - $this->assertFalse($constraint->evaluate('', '', true)); - $this->assertFalse($constraint->evaluate(1, '', true)); - - $string = 'not( is of type "int" xor is equal to false )'; - $this->assertSame($string, $constraint->toString()); - - $message = 'Failed asserting that ' . - 'not( 1 is of type "int" xor is equal to false )' . - '.'; - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage($message); - - $this->assertThat(1, $constraint); - } - - public function testTwoLevelNestedLogicalNotOfTerminalConstraint(): void - { - $terminal = new GreaterThan(5); - $constraint = new LogicalNot(new LogicalNot($terminal)); - - $this->assertTrue($constraint->evaluate(6, '', true)); - $this->assertFalse($constraint->evaluate(5, '', true)); - $this->assertSame('is greater than 5', $constraint->toString()); - - $message = 'Failed asserting that 5 is greater than 5'; - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage($message); - - $this->assertThat(5, $constraint); - } - - public function testThreeLevelNestedLogicalNotOfTerminalConstraint(): void - { - $terminal = new GreaterThan(5); - $constraint = new LogicalNot(new LogicalNot(new LogicalNot($terminal))); - - $this->assertFalse($constraint->evaluate(6, '', true)); - $this->assertTrue($constraint->evaluate(5, '', true)); - $this->assertSame('is not greater than 5', $constraint->toString()); - - $message = 'Failed asserting that 6 is not greater than 5'; - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage($message); - - $this->assertThat(6, $constraint); - } - - public function testFourLevelNestedLogicalNotOfTerminalConstraint(): void - { - $terminal = new GreaterThan(5); - $constraint = new LogicalNot(new LogicalNot(new LogicalNot(new LogicalNot($terminal)))); - - $this->assertTrue($constraint->evaluate(6, '', true)); - $this->assertFalse($constraint->evaluate(5, '', true)); - $this->assertSame('is greater than 5', $constraint->toString()); - - $message = 'Failed asserting that 5 is greater than 5'; - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage($message); - - $this->assertThat(5, $constraint); - } - - public function testTwoLevelNestedLogicalNotOfLogicalAndWithSingleOperand(): void - { - $subexpr = LogicalAnd::fromConstraints(new IsEqual(5)); - $constraint = new LogicalNot(new LogicalNot($subexpr)); - - $this->assertTrue($constraint->evaluate(5, '', true)); - $this->assertFalse($constraint->evaluate(3, '', true)); - - $string = 'is equal to 5'; - $this->assertSame($string, $constraint->toString()); - - $message = 'Failed asserting that 3 is equal to 5.'; - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage($message); - - $this->assertThat(3, $constraint); - } - - public function testTwoLevelNestedLogicalNotOfLogicalOrWithSingleOperand(): void - { - $subexpr = LogicalOr::fromConstraints(new IsEqual(5)); - $constraint = new LogicalNot(new LogicalNot($subexpr)); - - $this->assertTrue($constraint->evaluate(5, '', true)); - $this->assertFalse($constraint->evaluate(3, '', true)); - - $string = 'is equal to 5'; - $this->assertSame($string, $constraint->toString()); - - $message = 'Failed asserting that 3 is equal to 5.'; - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage($message); - - $this->assertThat(3, $constraint); - } - - public function testTwoLevelNestedLogicalNotOfLogicalXorWithSingleOperand(): void - { - $subexpr = LogicalXor::fromConstraints(new IsEqual(5)); - $constraint = new LogicalNot(new LogicalNot($subexpr)); - - $this->assertTrue($constraint->evaluate(5, '', true)); - $this->assertFalse($constraint->evaluate(3, '', true)); - - $string = 'is equal to 5'; - $this->assertSame($string, $constraint->toString()); - - $message = 'Failed asserting that 3 is equal to 5.'; - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage($message); - - $this->assertThat(3, $constraint); - } - - public function testTwoLevelNestedLogicalNotOfLogicalOrWithThreeOperands(): void - { - $subexpr = LogicalOr::fromConstraints(new LessThan(5), new IsEqual(10), new GreaterThan(15)); - $constraint = new LogicalNot(new LogicalNot($subexpr)); - - $this->assertTrue($constraint->evaluate(4, '', true)); - $this->assertTrue($constraint->evaluate(10, '', true)); - $this->assertTrue($constraint->evaluate(16, '', true)); - $this->assertFalse($constraint->evaluate(5, '', true)); - $this->assertFalse($constraint->evaluate(15, '', true)); - - $string = 'is less than 5 or is equal to 10 or is greater than 15'; - $this->assertSame($string, $constraint->toString()); - - $message = 'Failed asserting that ' . - '7 is less than 5 or is equal to 10 or is greater than 15' . - '.'; - - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage($message); - - $this->assertThat(7, $constraint); - } - - public function testThreeLevelNestedLogicalNotOfLogicalOrWithThreeOperands(): void - { - $subexpr = LogicalOr::fromConstraints(new LessThan(5), new IsEqual(10), new GreaterThan(15)); - $constraint = new LogicalNot(new LogicalNot(new LogicalNot($subexpr))); - - $this->assertFalse($constraint->evaluate(4, '', true)); - $this->assertFalse($constraint->evaluate(10, '', true)); - $this->assertFalse($constraint->evaluate(16, '', true)); - $this->assertTrue($constraint->evaluate(5, '', true)); - $this->assertTrue($constraint->evaluate(15, '', true)); - - $string = 'not( is less than 5 or is equal to 10 or is greater than 15 )'; - $this->assertSame($string, $constraint->toString()); - - $message = 'Failed asserting that ' . - 'not( 10 is less than 5 or is equal to 10 or is greater than 15 )' . - '.'; - - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage($message); - - $this->assertThat(10, $constraint); - } - - public function testFourLevelNestedLogicalNotOfLogicalOrWithThreeOperands(): void - { - $subexpr = LogicalOr::fromConstraints(new LessThan(5), new IsEqual(10), new GreaterThan(15)); - $constraint = new LogicalNot(new LogicalNot(new LogicalNot(new LogicalNot($subexpr)))); - - $this->assertTrue($constraint->evaluate(4, '', true)); - $this->assertTrue($constraint->evaluate(10, '', true)); - $this->assertTrue($constraint->evaluate(16, '', true)); - $this->assertFalse($constraint->evaluate(5, '', true)); - $this->assertFalse($constraint->evaluate(15, '', true)); - - $string = 'is less than 5 or is equal to 10 or is greater than 15'; - $this->assertSame($string, $constraint->toString()); - - $message = 'Failed asserting that ' . - '7 is less than 5 or is equal to 10 or is greater than 15' . - '.'; - - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage($message); - - $this->assertThat(7, $constraint); - } - - public function testOneLevelDegenerateLogicaOrOfTerminalConstraint(): void - { - $terminal = new GreaterThan(5); - $constraint = LogicalOr::fromConstraints($terminal); - - $this->assertTrue($constraint->evaluate(6, '', true)); - $this->assertFalse($constraint->evaluate(5, '', true)); - $this->assertSame('is greater than 5', $constraint->toString()); - - $message = 'Failed asserting that 5 is greater than 5'; - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage($message); - - $this->assertThat(5, $constraint); - } - - public function testTwoLevelDegenerateLogicaOrOfTerminalConstraint(): void - { - $terminal = new GreaterThan(5); - $constraint = LogicalOr::fromConstraints(LogicalOr::fromConstraints($terminal)); - - $this->assertTrue($constraint->evaluate(6, '', true)); - $this->assertFalse($constraint->evaluate(5, '', true)); - $this->assertSame('is greater than 5', $constraint->toString()); - - $message = 'Failed asserting that 5 is greater than 5'; - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage($message); - - $this->assertThat(5, $constraint); - } - - public function testThreeLevelDegenerateLogicaOrOfTerminalConstraint(): void - { - $terminal = new GreaterThan(5); - $constraint = LogicalOr::fromConstraints(LogicalOr::fromConstraints(LogicalOr::fromConstraints($terminal))); - - $this->assertTrue($constraint->evaluate(6, '', true)); - $this->assertFalse($constraint->evaluate(5, '', true)); - $this->assertSame('is greater than 5', $constraint->toString()); - - $message = 'Failed asserting that 5 is greater than 5'; - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage($message); - - $this->assertThat(5, $constraint); - } - - public function testLogicalAndOfTwoLogicalOr(): void - { - $constraint = LogicalAnd::fromConstraints( - LogicalOr::fromConstraints( - new IsEqual(false), - new GreaterThan(5) - ), - LogicalOr::fromConstraints( - new IsType('int'), - new IsType('bool') - ), - ); - - $this->assertTrue($constraint->evaluate(0, '', true)); - $this->assertTrue($constraint->evaluate(false, '', true)); - $this->assertTrue($constraint->evaluate(6, '', true)); - $this->assertFalse($constraint->evaluate(5, '', true)); - $this->assertFalse($constraint->evaluate('', '', true)); - $this->assertFalse($constraint->evaluate(true, '', true)); - - $string = '( is equal to false or is greater than 5 ) and ( is of type "int" or is of type "bool" )'; - $this->assertSame($string, $constraint->toString()); - - $message = 'Failed asserting that ' . - '5 ( is equal to false or is greater than 5 ) and ( is of type "int" or is of type "bool" )' . - '.'; - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage($message); - - $this->assertThat(5, $constraint); - } - - public function testLogicalOrOfTwoLogicalAnd(): void - { - $constraint = LogicalOr::fromConstraints( - LogicalAnd::fromConstraints( - new IsType('bool'), - new IsEqual(false) - ), - LogicalAnd::fromConstraints( - new IsType('int'), - new GreaterThan(5) - ) - ); - - $this->assertTrue($constraint->evaluate(false, '', true)); - $this->assertTrue($constraint->evaluate(6, '', true)); - $this->assertFalse($constraint->evaluate(5, '', true)); - $this->assertFalse($constraint->evaluate('', '', true)); - $this->assertFalse($constraint->evaluate(true, '', true)); - - $string = 'is of type "bool" and is equal to false or is of type "int" and is greater than 5'; - $this->assertSame($string, $constraint->toString()); - - $message = 'Failed asserting that ' . - '5 is of type "bool" and is equal to false or is of type "int" and is greater than 5' . - '.'; - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage($message); - - $this->assertThat(5, $constraint); - } - - public function testLogicalOrOfLogicalAndAndLogicalNotOfLogicalAnd(): void - { - $constraint = LogicalOr::fromConstraints( - LogicalAnd::fromConstraints( - new IsType('bool'), - new IsEqual(false) - ), - new LogicalNot( - LogicalAnd::fromConstraints( - new IsType('int'), - new GreaterThan(5) - ) - ) - ); - - $this->assertTrue($constraint->evaluate(false, '', true)); - $this->assertTrue($constraint->evaluate(true, '', true)); - $this->assertTrue($constraint->evaluate(5, '', true)); - $this->assertTrue($constraint->evaluate('', '', true)); - $this->assertFalse($constraint->evaluate(6, '', true)); - - $string = 'is of type "bool" and is equal to false or not( is of type "int" and is greater than 5 )'; - $this->assertSame($string, $constraint->toString()); - - $message = 'Failed asserting that ' . - '6 is of type "bool" and is equal to false or not( is of type "int" and is greater than 5 )' . - '.'; - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage($message); - - $this->assertThat(6, $constraint); - } - - public function testLogicalOrOfLogicalAndAndTwoLevelNestedLogicalNotOfLogicalAnd(): void - { - $constraint = LogicalOr::fromConstraints( - LogicalAnd::fromConstraints( - new IsType('bool'), - new IsEqual(false) - ), - new LogicalNot(new LogicalNot( - LogicalAnd::fromConstraints( - new IsType('int'), - new GreaterThan(5) - ) - )) - ); - - $this->assertTrue($constraint->evaluate(false, '', true)); - $this->assertTrue($constraint->evaluate(6, '', true)); - $this->assertFalse($constraint->evaluate(5, '', true)); - $this->assertFalse($constraint->evaluate('', '', true)); - $this->assertFalse($constraint->evaluate(true, '', true)); - - $string = 'is of type "bool" and is equal to false or is of type "int" and is greater than 5'; - $this->assertSame($string, $constraint->toString()); - - $message = 'Failed asserting that ' . - '5 is of type "bool" and is equal to false or is of type "int" and is greater than 5' . - '.'; - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage($message); - - $this->assertThat(5, $constraint); - } -} diff --git a/tests/unit/Framework/Constraint/LogicalNotTest.php b/tests/unit/Framework/Constraint/LogicalNotTest.php deleted file mode 100644 index 875897b48ed..00000000000 --- a/tests/unit/Framework/Constraint/LogicalNotTest.php +++ /dev/null @@ -1,74 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -/** - * @small - */ -final class LogicalNotTest extends UnaryOperatorTestCase -{ - public static function getOperatorName(): string - { - return 'not'; - } - - public static function getOperatorPrecedence(): int - { - return 5; - } - - public function providerToStringWithNativeTransformations() - { - return $this->providerNegate(); - } - - public function evaluateExpectedResult(bool $input): bool - { - return !$input; - } - - public function providerNegate() - { - return [ - ['ocean contains water', 'ocean does not contain water'], - [ - '\'this is water\' contains "water" and contains "is"', - '\'this is water\' does not contain "water" and does not contain "is"', - ], - ['what it contains', 'what it contains'], - ['life exists in outer space', 'life does not exist in outer space'], - ['alien exists', 'alien does not exist'], - ['it coexists', 'it coexists'], - ['the dog has a bone', 'the dog does not have a bone'], - ['whatever it has', 'whatever it has'], - ['apple is red', 'apple is not red'], - ['yes, it is', 'yes, it is'], - ['this is clock', 'this is not clock'], - ['how are you?', 'how are not you?'], - ['how dare you!', 'how dare you!'], - ['what they are', 'what they are'], - ['that matches my preferences', 'that does not match my preferences'], - ['dinner starts with desert', 'dinner starts not with desert'], - ['it starts with', 'it starts with'], - ['dinner ends with desert', 'dinner ends not with desert'], - ['it ends with', 'it ends with'], - ['you reference me', 'you don\'t reference me'], - ['it\'s not not false', 'it\'s not false'], - ]; - } - - /** - * @dataProvider providerNegate - */ - public function testNegate(string $input, string $expected): void - { - $this->assertSame($expected, LogicalNot::negate($input)); - } -} diff --git a/tests/unit/Framework/Constraint/LogicalOrTest.php b/tests/unit/Framework/Constraint/LogicalOrTest.php deleted file mode 100644 index 44c11b43de2..00000000000 --- a/tests/unit/Framework/Constraint/LogicalOrTest.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use function array_reduce; -use function array_shift; - -/** - * @small - */ -final class LogicalOrTest extends BinaryOperatorTestCase -{ - public static function getOperatorName(): string - { - return 'or'; - } - - public static function getOperatorPrecedence(): int - { - return 24; - } - - public function evaluateExpectedResult(array $input): bool - { - $initial = (bool) array_shift($input); - - return array_reduce($input, static function ($carry, bool $item): bool { - return $carry || $item; - }, $initial); - } -} diff --git a/tests/unit/Framework/Constraint/LogicalXorTest.php b/tests/unit/Framework/Constraint/LogicalXorTest.php deleted file mode 100644 index d24fb4d392e..00000000000 --- a/tests/unit/Framework/Constraint/LogicalXorTest.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use function array_reduce; -use function array_shift; - -/** - * @small - */ -final class LogicalXorTest extends BinaryOperatorTestCase -{ - public static function getOperatorName(): string - { - return 'xor'; - } - - public static function getOperatorPrecedence(): int - { - return 23; - } - - public function evaluateExpectedResult(array $input): bool - { - $initial = (bool) array_shift($input); - - return array_reduce($input, static function ($carry, bool $item): bool { - return $carry xor $item; - }, $initial); - } -} diff --git a/tests/unit/Framework/Constraint/Math/IsFiniteTest.php b/tests/unit/Framework/Constraint/Math/IsFiniteTest.php new file mode 100644 index 00000000000..1371d30ea5b --- /dev/null +++ b/tests/unit/Framework/Constraint/Math/IsFiniteTest.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function acos; +use function log; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(IsFinite::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class IsFiniteTest extends TestCase +{ + public function testCanBeEvaluated(): void + { + $constraint = new IsFinite; + + $this->assertTrue($constraint->evaluate(1, returnResult: true)); + $this->assertFalse($constraint->evaluate(log(0), returnResult: true)); + $this->assertFalse($constraint->evaluate(acos(2), returnResult: true)); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('is finite', (new IsFinite)->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new IsFinite)); + } +} diff --git a/tests/unit/Framework/Constraint/Math/IsInfiniteTest.php b/tests/unit/Framework/Constraint/Math/IsInfiniteTest.php new file mode 100644 index 00000000000..9b891c32071 --- /dev/null +++ b/tests/unit/Framework/Constraint/Math/IsInfiniteTest.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function acos; +use function log; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(IsInfinite::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class IsInfiniteTest extends TestCase +{ + public function testCanBeEvaluated(): void + { + $constraint = new IsInfinite; + + $this->assertTrue($constraint->evaluate(log(0), returnResult: true)); + $this->assertFalse($constraint->evaluate(1, returnResult: true)); + $this->assertFalse($constraint->evaluate(acos(2), returnResult: true)); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('is infinite', (new IsInfinite)->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new IsInfinite)); + } +} diff --git a/tests/unit/Framework/Constraint/Math/IsNanTest.php b/tests/unit/Framework/Constraint/Math/IsNanTest.php new file mode 100644 index 00000000000..d2038ebc8ea --- /dev/null +++ b/tests/unit/Framework/Constraint/Math/IsNanTest.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function acos; +use function log; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(IsNan::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class IsNanTest extends TestCase +{ + public function testCanBeEvaluated(): void + { + $constraint = new IsNan; + + $this->assertTrue($constraint->evaluate(acos(2), returnResult: true)); + $this->assertFalse($constraint->evaluate(log(0), returnResult: true)); + $this->assertFalse($constraint->evaluate(1, returnResult: true)); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('is nan', (new IsNan)->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new IsNan)); + } +} diff --git a/tests/unit/Framework/Constraint/Object/ObjectEqualsTest.php b/tests/unit/Framework/Constraint/Object/ObjectEqualsTest.php new file mode 100644 index 00000000000..9d4d9f61241 --- /dev/null +++ b/tests/unit/Framework/Constraint/Object/ObjectEqualsTest.php @@ -0,0 +1,154 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\ActualValueIsNotAnObjectException; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ComparisonMethodDoesNotAcceptParameterTypeException; +use PHPUnit\Framework\ComparisonMethodDoesNotDeclareBoolReturnTypeException; +use PHPUnit\Framework\ComparisonMethodDoesNotDeclareExactlyOneParameterException; +use PHPUnit\Framework\ComparisonMethodDoesNotDeclareParameterTypeException; +use PHPUnit\Framework\ComparisonMethodDoesNotExistException; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\ObjectEquals\ValueObject; +use PHPUnit\TestFixture\ObjectEquals\ValueObjectWithEqualsMethodThatAcceptsTooManyArguments; +use PHPUnit\TestFixture\ObjectEquals\ValueObjectWithEqualsMethodThatDoesNotAcceptArguments; +use PHPUnit\TestFixture\ObjectEquals\ValueObjectWithEqualsMethodThatDoesNotDeclareParameterType; +use PHPUnit\TestFixture\ObjectEquals\ValueObjectWithEqualsMethodThatHasIncompatibleParameterType; +use PHPUnit\TestFixture\ObjectEquals\ValueObjectWithEqualsMethodThatHasUnionParameterType; +use PHPUnit\TestFixture\ObjectEquals\ValueObjectWithEqualsMethodWithNullableReturnType; +use PHPUnit\TestFixture\ObjectEquals\ValueObjectWithEqualsMethodWithoutReturnType; +use PHPUnit\TestFixture\ObjectEquals\ValueObjectWithEqualsMethodWithUnionReturnType; +use PHPUnit\TestFixture\ObjectEquals\ValueObjectWithEqualsMethodWithVoidReturnType; +use PHPUnit\TestFixture\ObjectEquals\ValueObjectWithoutEqualsMethod; + +#[CoversClass(ObjectEquals::class)] +#[CoversClass(ActualValueIsNotAnObjectException::class)] +#[CoversClass(ComparisonMethodDoesNotExistException::class)] +#[CoversClass(ComparisonMethodDoesNotDeclareBoolReturnTypeException::class)] +#[CoversClass(ComparisonMethodDoesNotDeclareExactlyOneParameterException::class)] +#[CoversClass(ComparisonMethodDoesNotDeclareParameterTypeException::class)] +#[CoversClass(ComparisonMethodDoesNotAcceptParameterTypeException::class)] +#[Small] +final class ObjectEqualsTest extends TestCase +{ + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('two objects are equal', new ObjectEquals(new ValueObject(1))->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new ObjectEquals(new ValueObject(1)))); + } + + public function testAcceptsActualObjectWhenMethodSaysTheyAreEqual(): void + { + $this->assertTrue(new ObjectEquals(new ValueObject(1))->evaluate(new ValueObject(1), '', true)); + } + + public function testRejectsActualValueThatIsNotAnObject(): void + { + $this->expectException(ActualValueIsNotAnObjectException::class); + $this->expectExceptionMessage('Actual value is not an object'); + + new ObjectEquals(new ValueObject(1))->evaluate(null); + } + + public function testRejectsActualObjectThatDoesNotHaveTheSpecifiedMethod(): void + { + $this->expectException(ComparisonMethodDoesNotExistException::class); + $this->expectExceptionMessage('Comparison method PHPUnit\TestFixture\ObjectEquals\ValueObjectWithoutEqualsMethod::equals() does not exist.'); + + new ObjectEquals(new ValueObjectWithoutEqualsMethod(1))->evaluate(new ValueObjectWithoutEqualsMethod(1)); + } + + public function testRejectsActualObjectWhenTheSpecifiedMethodExistsButIsNotDeclaredToReturnBool(): void + { + $this->expectException(ComparisonMethodDoesNotDeclareBoolReturnTypeException::class); + $this->expectExceptionMessage('Comparison method PHPUnit\TestFixture\ObjectEquals\ValueObjectWithEqualsMethodWithoutReturnType::equals() does not declare bool return type.'); + + new ObjectEquals(new ValueObjectWithEqualsMethodWithoutReturnType(1))->evaluate(new ValueObjectWithEqualsMethodWithoutReturnType(1)); + } + + public function testRejectsActualObjectWhenTheSpecifiedMethodExistsButIsDeclaredToReturnUnion(): void + { + $this->expectException(ComparisonMethodDoesNotDeclareBoolReturnTypeException::class); + $this->expectExceptionMessage('Comparison method PHPUnit\TestFixture\ObjectEquals\ValueObjectWithEqualsMethodWithUnionReturnType::equals() does not declare bool return type.'); + + new ObjectEquals(new ValueObjectWithEqualsMethodWithUnionReturnType(1))->evaluate(new ValueObjectWithEqualsMethodWithUnionReturnType(1)); + } + + public function testRejectsActualObjectWhenTheSpecifiedMethodExistsButIsDeclaredVoid(): void + { + $this->expectException(ComparisonMethodDoesNotDeclareBoolReturnTypeException::class); + $this->expectExceptionMessage('Comparison method PHPUnit\TestFixture\ObjectEquals\ValueObjectWithEqualsMethodWithVoidReturnType::equals() does not declare bool return type.'); + + new ObjectEquals(new ValueObjectWithEqualsMethodWithVoidReturnType(1))->evaluate(new ValueObjectWithEqualsMethodWithVoidReturnType(1)); + } + + public function testRejectsActualObjectWhenTheSpecifiedMethodExistsButIsDeclaredNullable(): void + { + $this->expectException(ComparisonMethodDoesNotDeclareBoolReturnTypeException::class); + $this->expectExceptionMessage('Comparison method PHPUnit\TestFixture\ObjectEquals\ValueObjectWithEqualsMethodWithNullableReturnType::equals() does not declare bool return type.'); + + new ObjectEquals(new ValueObjectWithEqualsMethodWithNullableReturnType(1))->evaluate(new ValueObjectWithEqualsMethodWithNullableReturnType(1)); + } + + public function testRejectsActualObjectWhenTheSpecifiedMethodDoesNotAcceptArguments(): void + { + $this->expectException(ComparisonMethodDoesNotDeclareExactlyOneParameterException::class); + $this->expectExceptionMessage('Comparison method PHPUnit\TestFixture\ObjectEquals\ValueObjectWithEqualsMethodThatDoesNotAcceptArguments::equals() does not declare exactly one parameter.'); + + new ObjectEquals(new ValueObjectWithEqualsMethodThatDoesNotAcceptArguments(1))->evaluate(new ValueObjectWithEqualsMethodThatDoesNotAcceptArguments(1)); + } + + public function testRejectsActualObjectWhenTheSpecifiedMethodAcceptsTooManyArguments(): void + { + $this->expectException(ComparisonMethodDoesNotDeclareExactlyOneParameterException::class); + $this->expectExceptionMessage('Comparison method PHPUnit\TestFixture\ObjectEquals\ValueObjectWithEqualsMethodThatAcceptsTooManyArguments::equals() does not declare exactly one parameter.'); + + new ObjectEquals(new ValueObjectWithEqualsMethodThatAcceptsTooManyArguments(1))->evaluate(new ValueObjectWithEqualsMethodThatAcceptsTooManyArguments(1)); + } + + public function testRejectsActualObjectWhenTheSpecifiedMethodDoesNotDeclareParameterType(): void + { + $this->expectException(ComparisonMethodDoesNotDeclareParameterTypeException::class); + $this->expectExceptionMessage('Parameter of comparison method PHPUnit\TestFixture\ObjectEquals\ValueObjectWithEqualsMethodThatDoesNotDeclareParameterType::equals() does not have a declared type.'); + + new ObjectEquals(new ValueObjectWithEqualsMethodThatDoesNotDeclareParameterType(1))->evaluate(new ValueObjectWithEqualsMethodThatDoesNotDeclareParameterType(1)); + } + + public function testRejectsActualObjectWhenTheSpecifiedMethodHasUnionParameterType(): void + { + $this->expectException(ComparisonMethodDoesNotDeclareParameterTypeException::class); + $this->expectExceptionMessage('Parameter of comparison method PHPUnit\TestFixture\ObjectEquals\ValueObjectWithEqualsMethodThatHasUnionParameterType::equals() does not have a declared type.'); + + new ObjectEquals(new ValueObjectWithEqualsMethodThatHasUnionParameterType(1))->evaluate(new ValueObjectWithEqualsMethodThatHasUnionParameterType(1)); + } + + public function testRejectsActualObjectWhenTheSpecifiedMethodHasIncompatibleParameterType(): void + { + $this->expectException(ComparisonMethodDoesNotAcceptParameterTypeException::class); + $this->expectExceptionMessage('PHPUnit\TestFixture\ObjectEquals\ValueObjectWithEqualsMethodThatHasIncompatibleParameterType is not an accepted argument type for comparison method PHPUnit\TestFixture\ObjectEquals\ValueObjectWithEqualsMethodThatHasIncompatibleParameterType::equals().'); + + new ObjectEquals(new ValueObjectWithEqualsMethodThatHasIncompatibleParameterType(1))->evaluate(new ValueObjectWithEqualsMethodThatHasIncompatibleParameterType(1)); + } + + public function testRejectsActualObjectWhenMethodSaysTheyAreNotEqual(): void + { + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Failed asserting that two objects are equal.'); + + new ObjectEquals(new ValueObject(1))->evaluate(new ValueObject(2)); + } +} diff --git a/tests/unit/Framework/Constraint/Object/ObjectHasPropertyTest.php b/tests/unit/Framework/Constraint/Object/ObjectHasPropertyTest.php new file mode 100644 index 00000000000..03344baa107 --- /dev/null +++ b/tests/unit/Framework/Constraint/Object/ObjectHasPropertyTest.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; +use stdClass; + +#[CoversClass(ObjectHasProperty::class)] +#[Small] +final class ObjectHasPropertyTest extends TestCase +{ + public function testCanBeEvaluated(): void + { + $constraint = new ObjectHasProperty('theProperty'); + + $objectWithProperty = new stdClass; + $objectWithProperty->theProperty = 'value'; + + $this->assertTrue($constraint->evaluate($objectWithProperty, returnResult: true)); + $this->assertFalse($constraint->evaluate(new stdClass, returnResult: true)); + $this->assertFalse($constraint->evaluate(null, returnResult: true)); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Failed asserting that object of class "stdClass" has property "theProperty".'); + + $constraint->evaluate(new stdClass); + } + + public function testHandlesNonObjectsGracefully(): void + { + $constraint = new ObjectHasProperty('theProperty'); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Failed asserting that "non-object" (string) has property "theProperty".'); + + $constraint->evaluate('non-object'); + } + + public function testCanBeRepresentedAsString(): void + { + $constraint = new ObjectHasProperty('theProperty'); + + $this->assertSame('has property "theProperty"', $constraint->toString()); + } + + public function testIsCountable(): void + { + $constraint = new ObjectHasProperty('theProperty'); + + $this->assertCount(1, $constraint); + } +} diff --git a/tests/unit/Framework/Constraint/ObjectHasAttributeTest.php b/tests/unit/Framework/Constraint/ObjectHasAttributeTest.php deleted file mode 100644 index 4501ffe2183..00000000000 --- a/tests/unit/Framework/Constraint/ObjectHasAttributeTest.php +++ /dev/null @@ -1,71 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestFailure; -use PHPUnit\TestFixture\ClassWithNonPublicAttributes; -use stdClass; - -/** - * @small - */ -final class ObjectHasAttributeTest extends ConstraintTestCase -{ - public function testConstraintObjectHasAttribute(): void - { - $constraint = new ObjectHasAttribute('privateAttribute'); - - $this->assertTrue($constraint->evaluate(new ClassWithNonPublicAttributes, '', true)); - $this->assertFalse($constraint->evaluate(new stdClass, '', true)); - $this->assertEquals('has attribute "privateAttribute"', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate(new stdClass); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that object of class "stdClass" has attribute "privateAttribute". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintObjectHasAttribute2(): void - { - $constraint = new ObjectHasAttribute('privateAttribute'); - - try { - $constraint->evaluate(new stdClass, 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that object of class "stdClass" has attribute "privateAttribute". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } -} diff --git a/tests/unit/Framework/Constraint/Operator/LogicalAndTest.php b/tests/unit/Framework/Constraint/Operator/LogicalAndTest.php new file mode 100644 index 00000000000..51d2421017c --- /dev/null +++ b/tests/unit/Framework/Constraint/Operator/LogicalAndTest.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; + +#[CoversClass(LogicalAnd::class)] +#[CoversClass(BinaryOperator::class)] +#[CoversClass(Operator::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class LogicalAndTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + 'is of type bool and is true', + '', + self::logicalAnd( + self::isBool(), + self::isTrue(), + ), + true, + ], + + [ + true, + 'is of type bool and is equal to true', + '', + self::logicalAnd( + self::isBool(), + true, + ), + true, + ], + + [ + true, + 'is of type bool and ( is true or is false )', + '', + self::logicalAnd( + self::isBool(), + self::logicalOr( + self::isTrue(), + self::isFalse(), + ), + ), + true, + ], + + [ + false, + 'is of type bool and is true', + 'Failed asserting that false is of type bool and is true.', + self::logicalAnd( + self::isBool(), + self::isTrue(), + ), + false, + ], + + [ + false, + 'is of type bool and ( is true or is false )', + 'Failed asserting that \'string\' is of type bool and ( is true or is false ).', + self::logicalAnd( + self::isBool(), + self::logicalOr( + self::isTrue(), + self::isFalse(), + ), + ), + 'string', + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $constraintAsString, string $failureDescription, LogicalAnd $constraint, mixed $actual): void + { + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + #[DataProvider('provider')] + public function testCanBeRepresentedAsString(bool $result, string $constraintAsString, string $failureDescription, LogicalAnd $constraint, mixed $actual): void + { + $this->assertSame($constraintAsString, $constraint->toString()); + } + + public function testIsCountable(): void + { + $constraint = $this->logicalAnd( + $this->isBool(), + true, + ); + + $this->assertCount(2, $constraint); + } +} diff --git a/tests/unit/Framework/Constraint/Operator/LogicalNotTest.php b/tests/unit/Framework/Constraint/Operator/LogicalNotTest.php new file mode 100644 index 00000000000..882f81ed799 --- /dev/null +++ b/tests/unit/Framework/Constraint/Operator/LogicalNotTest.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Assert; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\Attributes\Ticket; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; + +#[CoversClass(LogicalNot::class)] +#[CoversClass(UnaryOperator::class)] +#[CoversClass(Operator::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class LogicalNotTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + '', + self::logicalNot(self::isTrue()), + false, + ], + + [ + false, + 'Failed asserting that true is not true.', + self::logicalNot(self::isTrue()), + true, + ], + + [ + false, + 'Failed asserting that not( true is true or is true ).', + self::logicalNot( + self::logicalOr( + self::isTrue(), + self::isTrue(), + ), + ), + true, + ], + ]; + } + + public static function negateProvider(): array + { + return [ + ['ocean contains water', 'ocean does not contain water'], + [ + '\'this is water\' contains "water" and contains "is"', + '\'this is water\' does not contain "water" and does not contain "is"', + ], + ['what it contains', 'what it contains'], + ['life exists in outer space', 'life does not exist in outer space'], + ['alien exists', 'alien does not exist'], + ['it coexists', 'it coexists'], + ['the dog has a bone', 'the dog does not have a bone'], + ['whatever it has', 'whatever it has'], + ['apple is red', 'apple is not red'], + ['yes, it is', 'yes, it is'], + ['this is clock', 'this is not clock'], + ['how are you?', 'how are not you?'], + ['how dare you!', 'how dare you!'], + ['what they are', 'what they are'], + ['that matches my preferences', 'that does not match my preferences'], + ['dinner starts with desert', 'dinner starts not with desert'], + ['it starts with', 'it starts with'], + ['dinner ends with desert', 'dinner ends not with desert'], + ['it ends with', 'it ends with'], + ['you reference me', 'you don\'t reference me'], + ['it\'s not not false', 'it\'s not false'], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $failureDescription, LogicalNot $constraint, mixed $actual): void + { + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + #[DataProvider('negateProvider')] + public function testCanNegateStatement(string $input, string $expected): void + { + $this->assertSame($expected, LogicalNot::negate($input)); + } + + public function testCanBeRepresentedAsString(): void + { + $constraint = $this->logicalNot( + $this->logicalOr( + $this->isTrue(), + $this->isFalse(), + ), + ); + + $this->assertSame('not( is true or is false )', $constraint->toString()); + } + + public function testIsCountable(): void + { + $constraint = $this->logicalNot( + $this->logicalOr( + $this->isTrue(), + $this->isFalse(), + ), + ); + + $this->assertCount(2, $constraint); + } + + #[TestDox('LogicalNot(IsEqual(\'test contains something\')) is handled correctly')] + #[Ticket('https://github.com/sebastianbergmann/phpunit/issues/5516')] + public function testForNotEqualsWithStringThatContainsContains(): void + { + $constraint = new LogicalNot(new IsEqual('test contains something')); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage("Failed asserting that 'test contains something' is not equal to 'test contains something'."); + + Assert::assertThat('test contains something', $constraint); + } +} diff --git a/tests/unit/Framework/Constraint/Operator/LogicalOrTest.php b/tests/unit/Framework/Constraint/Operator/LogicalOrTest.php new file mode 100644 index 00000000000..48bf53166ac --- /dev/null +++ b/tests/unit/Framework/Constraint/Operator/LogicalOrTest.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; + +#[CoversClass(LogicalOr::class)] +#[CoversClass(BinaryOperator::class)] +#[CoversClass(Operator::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class LogicalOrTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + 'is of type bool or is true', + '', + self::logicalOr( + self::isBool(), + self::isTrue(), + ), + true, + ], + + [ + true, + 'is of type bool or is equal to true', + '', + self::logicalOr( + self::isBool(), + true, + ), + true, + ], + + [ + true, + 'is true or is of type bool and is false', + '', + self::logicalOr( + self::isTrue(), + self::logicalAnd( + self::isBool(), + self::isFalse(), + ), + ), + true, + ], + + [ + false, + 'is of type bool or is of type string', + 'Failed asserting that 0 is of type bool or is of type string.', + self::logicalOr( + self::isBool(), + self::isString(), + ), + 0, + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $constraintAsString, string $failureDescription, LogicalOr $constraint, mixed $actual): void + { + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + #[DataProvider('provider')] + public function testCanBeRepresentedAsString(bool $result, string $constraintAsString, string $failureDescription, LogicalOr $constraint, mixed $actual): void + { + $this->assertSame($constraintAsString, $constraint->toString()); + } + + public function testIsCountable(): void + { + $constraint = $this->logicalOr( + $this->isBool(), + true, + ); + + $this->assertCount(2, $constraint); + } +} diff --git a/tests/unit/Framework/Constraint/Operator/LogicalXorTest.php b/tests/unit/Framework/Constraint/Operator/LogicalXorTest.php new file mode 100644 index 00000000000..f9fa2dd65f6 --- /dev/null +++ b/tests/unit/Framework/Constraint/Operator/LogicalXorTest.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; + +#[CoversClass(LogicalXor::class)] +#[CoversClass(BinaryOperator::class)] +#[CoversClass(Operator::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class LogicalXorTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + 'is false xor is true', + '', + self::logicalXor( + self::isFalse(), + self::isTrue(), + ), + true, + ], + + [ + true, + 'is false xor is equal to true', + '', + self::logicalXor( + self::isFalse(), + true, + ), + true, + ], + + [ + true, + 'is of type bool xor is true', + '', + self::logicalXor( + self::isBool(), + self::isTrue(), + ), + false, + ], + + [ + false, + 'is of type bool xor is true', + 'Failed asserting that true is of type bool xor is true.', + self::logicalXor( + self::isBool(), + self::isTrue(), + ), + true, + ], + + [ + false, + 'is of type bool and is true xor is of type bool and is true', + 'Failed asserting that true is of type bool and is true xor is of type bool and is true.', + self::logicalXor( + self::logicalAnd( + self::isBool(), + self::isTrue(), + ), + self::logicalAnd( + self::isBool(), + self::isTrue(), + ), + ), + true, + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $constraintAsString, string $failureDescription, LogicalXor $constraint, mixed $actual): void + { + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + #[DataProvider('provider')] + public function testCanBeRepresentedAsString(bool $result, string $constraintAsString, string $failureDescription, LogicalXor $constraint, mixed $actual): void + { + $this->assertSame($constraintAsString, $constraint->toString()); + } + + public function testIsCountable(): void + { + $constraint = $this->logicalXor( + $this->isBool(), + true, + ); + + $this->assertCount(2, $constraint); + } +} diff --git a/tests/unit/Framework/Constraint/OperatorTestCase.php b/tests/unit/Framework/Constraint/OperatorTestCase.php deleted file mode 100644 index 5bf17855cfa..00000000000 --- a/tests/unit/Framework/Constraint/OperatorTestCase.php +++ /dev/null @@ -1,37 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use function sprintf; -use ReflectionClass; - -abstract class OperatorTestCase extends ConstraintTestCase -{ - final public function testIsSubclassOfConstraint(): void - { - $className = $this->className(); - - $reflection = new ReflectionClass($className); - - $this->assertTrue($reflection->isSubclassOf(Constraint::class), sprintf( - 'Failed to assert that "%s" is subclass of "%s".', - $className, - Constraint::class - )); - } - - abstract public function testOperatorName(): void; - - abstract public function testOperatorPrecedence(): void; - - abstract public function testOperatorArity(): void; - - abstract public function testOperatorCount(): void; -} diff --git a/tests/unit/Framework/Constraint/RegularExpressionTest.php b/tests/unit/Framework/Constraint/RegularExpressionTest.php deleted file mode 100644 index 99be0fc358c..00000000000 --- a/tests/unit/Framework/Constraint/RegularExpressionTest.php +++ /dev/null @@ -1,69 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestFailure; - -/** - * @small - */ -final class RegularExpressionTest extends ConstraintTestCase -{ - public function testConstraintRegularExpression(): void - { - $constraint = new RegularExpression('/foo/'); - - $this->assertFalse($constraint->evaluate('barbazbar', '', true)); - $this->assertTrue($constraint->evaluate('barfoobar', '', true)); - $this->assertEquals('matches PCRE pattern "/foo/"', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate('barbazbar'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that 'barbazbar' matches PCRE pattern "/foo/". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintRegularExpression2(): void - { - $constraint = new RegularExpression('/foo/'); - - try { - $constraint->evaluate('barbazbar', 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that 'barbazbar' matches PCRE pattern "/foo/". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } -} diff --git a/tests/unit/Framework/Constraint/SameSizeTest.php b/tests/unit/Framework/Constraint/SameSizeTest.php deleted file mode 100644 index 05ee7469948..00000000000 --- a/tests/unit/Framework/Constraint/SameSizeTest.php +++ /dev/null @@ -1,67 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use ArrayObject; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestFailure; -use PHPUnit\TestFixture\TestIterator; - -/** - * @small - */ -final class SameSizeTest extends ConstraintTestCase -{ - public function testConstraintSameSizeWithAnArray(): void - { - $constraint = new SameSize([1, 2, 3, 4, 5]); - - $this->assertTrue($constraint->evaluate([6, 7, 8, 9, 10], '', true)); - $this->assertFalse($constraint->evaluate([1, 2, 3, 4], '', true)); - } - - public function testConstraintSameSizeWithAnIteratorWhichDoesNotImplementCountable(): void - { - $constraint = new SameSize(new TestIterator([1, 2, 3, 4, 5])); - - $this->assertTrue($constraint->evaluate(new TestIterator([6, 7, 8, 9, 10]), '', true)); - $this->assertFalse($constraint->evaluate(new TestIterator([1, 2, 3, 4]), '', true)); - } - - public function testConstraintSameSizeWithAnObjectImplementingCountable(): void - { - $constraint = new SameSize(new ArrayObject([1, 2, 3, 4, 5])); - - $this->assertTrue($constraint->evaluate(new ArrayObject([6, 7, 8, 9, 10]), '', true)); - $this->assertFalse($constraint->evaluate(new ArrayObject([1, 2, 3, 4]), '', true)); - } - - public function testConstraintSameSizeFailing(): void - { - $constraint = new SameSize([1, 2, 3, 4, 5]); - - try { - $constraint->evaluate([1, 2]); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that actual size 2 matches expected size 5. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } -} diff --git a/tests/unit/Framework/Constraint/String/IsJsonTest.php b/tests/unit/Framework/Constraint/String/IsJsonTest.php new file mode 100644 index 00000000000..a21e0b2b569 --- /dev/null +++ b/tests/unit/Framework/Constraint/String/IsJsonTest.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function json_encode; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; + +#[CoversClass(IsJson::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class IsJsonTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + '', + json_encode(['key' => 'value']), + ], + + [ + false, + 'Failed asserting that an empty string is valid JSON.', + '', + ], + + [ + false, + 'Failed asserting that a string is valid JSON (Syntax error, malformed JSON).', + 'invalid json', + ], + + [ + false, + 'Failed asserting that an array is valid JSON.', + [], + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $failureDescription, mixed $actual): void + { + $constraint = new IsJson; + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('is valid JSON', (new IsJson)->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new IsJson)); + } +} diff --git a/tests/unit/Framework/Constraint/String/RegularExpressionTest.php b/tests/unit/Framework/Constraint/String/RegularExpressionTest.php new file mode 100644 index 00000000000..9d78096e07b --- /dev/null +++ b/tests/unit/Framework/Constraint/String/RegularExpressionTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; + +#[CoversClass(RegularExpression::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class RegularExpressionTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + '', + '/.*/', + 'string', + ], + + [ + false, + 'Failed asserting that \'string\' matches PCRE pattern "/[0-9]/".', + '/[0-9]/', + 'string', + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $failureDescription, string $expected, string $actual): void + { + $constraint = new RegularExpression($expected); + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('matches PCRE pattern "/.*/"', new RegularExpression('/.*/')->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new RegularExpression('/.*/'))); + } +} diff --git a/tests/unit/Framework/Constraint/String/StringContainsTest.php b/tests/unit/Framework/Constraint/String/StringContainsTest.php new file mode 100644 index 00000000000..20c96b81d5f --- /dev/null +++ b/tests/unit/Framework/Constraint/String/StringContainsTest.php @@ -0,0 +1,235 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; + +#[CoversClass(StringContains::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class StringContainsTest extends TestCase +{ + public static function providesEvaluationCases(): array + { + return [ + 'It finds the needle with default options given' => [ + true, + '', + false, + false, + 'needle', + 'prefix needle suffix', + ], + + 'Empty needles are supported with default options given' => [ + true, + '', + false, + false, + '', + 'prefix needle suffix', + ], + + 'It finds the needle with default options given but different encodings are used' => [ + true, + '', + false, + false, + 'character', // Example ASCII needle string + 'Example character encoding string', // Example UTF-8 haystack string + ], + + 'It finds the needle given letter casing is ignored and both haystack and needle are in the same case' => [ + true, + '', + true, + false, + 'needle', + 'prefix needle suffix', + ], + + 'It finds the needle given letter casing is ignored and needle is in a different case to haystack' => [ + true, + '', + true, + false, + 'needle', + 'prefix NEEDLE suffix', + ], + + 'It finds the needle given letter casing is ignored and haystack is in a different case to needle' => [ + true, + '', + true, + false, + 'NEEDLE', + 'prefix needle suffix', + ], + + 'Needles containing only line endings are supported given line endings are set up to be ignored' => [ + true, + '', + false, + true, + "\n", + "prefix needle\r\n suffix", + ], + + 'It supports the needle and haystack using different line endings given line endings are ignored' => [ + true, + '', + false, + true, + "needle\r suffix", + "prefix needle\n suffix", + ], + + '\r\n line endings will be ignored in the needle given line endings are set up to be ignored' => [ + true, + '', + false, + true, + "needle\r\n suffix", + "prefix needle\r suffix", + ], + + '\r\n line endings will be ignored in the haystack given line endings are set up to be ignored' => [ + true, + '', + true, + true, + "needle\n", + "prefix NEEDLE\r\n suffix", + ], + + 'It fails to find the needle given the haystack is null' => [ + false, + 'Failed asserting that null [Encoding detection failed](length: 0) contains "needle" [ASCII](length: 6).', + false, + false, + 'needle', + null, + ], + + 'It fails to find the needle given the haystack does not contain it' => [ + false, + 'Failed asserting that \'prefix ... suffix\' [ASCII](length: 17) contains "needle" [ASCII](length: 6).', + false, + false, + 'needle', + 'prefix ... suffix', + ], + + 'Encoding is ignored given letter casing is ignored' => [ + false, + 'Failed asserting that \'Example UTF-8 encoded string £$\' [Encoding ignored](length: 32) contains "example ascii encoded string that is not a needle of the utf-8 one" [Encoding ignored](length: 66).', + true, + false, + 'Example ASCII encoded string that is not a needle of the UTF-8 one', + 'Example UTF-8 encoded string £$', + ], + + 'The length and detecting encoding is included in the failure message' => [ + false, + 'Failed asserting that \'Example character encoding\' [UTF-8](length: 30) contains "Example character encoding" [ASCII](length: 26).', + false, + false, + /** + * Below is an ASCII string using a 'blank space' character (code 32 in https://smartwebworker.com/ascii-codes) + * between each word. + */ + 'Example character encoding', + /** + * Below is a UTF-8 string using a 'thin-space' character (https://www.compart.com/en/unicode/U+2009) + * between each word instead of usual 'space' character (https://www.compart.com/en/unicode/U+0020). + */ + 'Example character encoding', + ], + + 'Both the needle and haystack length in the failure message partly account for \r line endings given line endings are ignored' => [ + false, + "Failed asserting that 'Some haystack with\\r\n line\\n\n endings \\n\\r\n' [ASCII](length: 36) contains \"Some needle with\n line\n endings \n\n\" [ASCII](length: 34).", + false, + true, + /** + * See StringContains::normalizeLineEndings() to + * see how "\r" are mapped to "\n". + */ + "Some needle with\r line\n endings \n\r", // 38 characters long + "Some haystack with\r line\n endings \n\r", // 39 characters long + ], + ]; + } + + public static function providesToStringRepresentationCases(): array + { + return [ + 'It contains the needle\'s string, length, and encoding information' => [ + 'contains "needle" [ASCII](length: 6)', + 'needle', + false, + false, + ], + + 'It contains the needle\'s string, length, and encoding information when using a non-ASCII encoding' => [ + 'contains "example UTF-8 needle £$" [UTF-8](length: 24)', + 'example UTF-8 needle £$', + false, + false, + ], + + 'It contains the converted-to-lower-case needle string given letter casing is ignored' => [ + 'contains "needle" [Encoding ignored](length: 6)', + 'NEEDLE', + true, + false, + ], + + 'It maps out the \r line endings from needle string given line endings are ignored' => [ + 'contains "NEEDLE' . "\n" . '" [ASCII](length: 7)', + "NEEDLE\r\n", + false, + true, + ], + ]; + } + + #[DataProvider('providesEvaluationCases')] + public function testCanBeEvaluated(bool $result, string $failureDescription, bool $ignoreCase, bool $ignoreLineEndings, string $needle, mixed $haystack): void + { + $constraint = new StringContains($needle, $ignoreCase, $ignoreLineEndings); + + $this->assertSame($result, $constraint->evaluate($haystack, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($haystack); + } + + #[DataProvider('providesToStringRepresentationCases')] + public function testCanBeRepresentedAsString(string $expected, string $needle, bool $ignoreCase, bool $ignoreLineEndings): void + { + $this->assertSame($expected, new StringContains($needle, $ignoreCase, $ignoreLineEndings)->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new StringContains('needle'))); + } +} diff --git a/tests/unit/Framework/Constraint/String/StringEndsWithTest.php b/tests/unit/Framework/Constraint/String/StringEndsWithTest.php new file mode 100644 index 00000000000..42e8d74b0b1 --- /dev/null +++ b/tests/unit/Framework/Constraint/String/StringEndsWithTest.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\EmptyStringException; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; + +#[CoversClass(StringEndsWith::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class StringEndsWithTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + '', + 'suffix', + 'prefix substring suffix', + ], + + [ + false, + 'Failed asserting that \'prefix substring\' ends with "suffix".', + 'suffix', + 'prefix substring', + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $failureDescription, string $expected, string $actual): void + { + $constraint = new StringEndsWith($expected); + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('ends with "suffix"', new StringEndsWith('suffix')->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new StringEndsWith('suffix'))); + } + + public function testRejectsEmptySuffix(): void + { + $this->expectException(EmptyStringException::class); + + new StringEndsWith(''); + } +} diff --git a/tests/unit/Framework/Constraint/String/StringEqualsStringIgnoringLineEndingsTest.php b/tests/unit/Framework/Constraint/String/StringEqualsStringIgnoringLineEndingsTest.php new file mode 100644 index 00000000000..e0fbc979bf4 --- /dev/null +++ b/tests/unit/Framework/Constraint/String/StringEqualsStringIgnoringLineEndingsTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; + +#[CoversClass(StringEqualsStringIgnoringLineEndings::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class StringEqualsStringIgnoringLineEndingsTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + '', + "string\r", + "string\r\n", + ], + + [ + false, + 'Failed asserting that \'another string\' is equal to "string" ignoring line endings.', + 'string', + 'another string', + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $failureDescription, string $expected, string $actual): void + { + $constraint = new StringEqualsStringIgnoringLineEndings($expected); + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('is equal to "string" ignoring line endings', new StringEqualsStringIgnoringLineEndings('string')->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new StringEqualsStringIgnoringLineEndings('string'))); + } +} diff --git a/tests/unit/Framework/Constraint/String/StringMatchesFormatDescriptionTest.php b/tests/unit/Framework/Constraint/String/StringMatchesFormatDescriptionTest.php new file mode 100644 index 00000000000..9447f286462 --- /dev/null +++ b/tests/unit/Framework/Constraint/String/StringMatchesFormatDescriptionTest.php @@ -0,0 +1,347 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use const DIRECTORY_SEPARATOR; +use const PHP_EOL; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; + +#[CoversClass(StringMatchesFormatDescription::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class StringMatchesFormatDescriptionTest extends TestCase +{ + public function testConstraintStringMatchesDirectorySeparator(): void + { + $constraint = new StringMatchesFormatDescription('*%e*'); + + $this->assertFalse($constraint->evaluate('**', '', true)); + $this->assertFalse($constraint->evaluate('*a*', '', true)); + + $this->assertTrue($constraint->evaluate('*' . DIRECTORY_SEPARATOR . '*', '', true)); + } + + public function testConstraintStringMatchesString(): void + { + $constraint = new StringMatchesFormatDescription('*%s*'); + + $this->assertFalse($constraint->evaluate('**', '', true)); + $this->assertFalse($constraint->evaluate("*\n*", '', true)); + + $this->assertTrue($constraint->evaluate('***', '', true)); + $this->assertTrue($constraint->evaluate('*foo 123 bar*', '', true)); + } + + public function testConstraintStringMatchesOptionalString(): void + { + $constraint = new StringMatchesFormatDescription('*%S*'); + + $this->assertFalse($constraint->evaluate('*', '', true)); + $this->assertFalse($constraint->evaluate("*\n*", '', true)); + + $this->assertTrue($constraint->evaluate('***', '', true)); + $this->assertTrue($constraint->evaluate('*foo 123 bar*', '', true)); + $this->assertTrue($constraint->evaluate('**', '', true)); + } + + public function testConstraintStringMatchesAnything(): void + { + $constraint = new StringMatchesFormatDescription('*%a*'); + + $this->assertFalse($constraint->evaluate('**', '', true)); + + $this->assertTrue($constraint->evaluate('***', '', true)); + $this->assertTrue($constraint->evaluate('*foo 123 bar*', '', true)); + $this->assertTrue($constraint->evaluate("*\n*", '', true)); + } + + public function testConstraintStringMatchesOptionalAnything(): void + { + $constraint = new StringMatchesFormatDescription('*%A*'); + + $this->assertFalse($constraint->evaluate('*', '', true)); + + $this->assertTrue($constraint->evaluate('***', '', true)); + $this->assertTrue($constraint->evaluate('*foo 123 bar*', '', true)); + $this->assertTrue($constraint->evaluate("*\n*", '', true)); + $this->assertTrue($constraint->evaluate('**', '', true)); + } + + public function testConstraintStringMatchesWhitespace(): void + { + $constraint = new StringMatchesFormatDescription('*%w*'); + + $this->assertFalse($constraint->evaluate('*', '', true)); + $this->assertFalse($constraint->evaluate('*a*', '', true)); + + $this->assertTrue($constraint->evaluate('* *', '', true)); + $this->assertTrue($constraint->evaluate("*\t\n*", '', true)); + $this->assertTrue($constraint->evaluate('**', '', true)); + } + + public function testConstraintStringMatchesInteger(): void + { + $constraint = new StringMatchesFormatDescription('*%i*'); + + $this->assertFalse($constraint->evaluate('**', '', true)); + $this->assertFalse($constraint->evaluate('*a*', '', true)); + $this->assertFalse($constraint->evaluate('*1.0*', '', true)); + + $this->assertTrue($constraint->evaluate('*0*', '', true)); + $this->assertTrue($constraint->evaluate('*12*', '', true)); + $this->assertTrue($constraint->evaluate('*-1*', '', true)); + $this->assertTrue($constraint->evaluate('*+2*', '', true)); + } + + public function testConstraintStringMatchesUnsignedInt(): void + { + $constraint = new StringMatchesFormatDescription('*%d*'); + + $this->assertFalse($constraint->evaluate('**', '', true)); + $this->assertFalse($constraint->evaluate('*a*', '', true)); + $this->assertFalse($constraint->evaluate('*1.0*', '', true)); + $this->assertFalse($constraint->evaluate('*-1*', '', true)); + $this->assertFalse($constraint->evaluate('*+2*', '', true)); + + $this->assertTrue($constraint->evaluate('*0*', '', true)); + $this->assertTrue($constraint->evaluate('*12*', '', true)); + } + + public function testConstraintStringMatchesHexadecimal(): void + { + $constraint = new StringMatchesFormatDescription('*%x*'); + + $this->assertFalse($constraint->evaluate('**', '', true)); + $this->assertFalse($constraint->evaluate('***', '', true)); + $this->assertFalse($constraint->evaluate('*g*', '', true)); + $this->assertFalse($constraint->evaluate('*1.0*', '', true)); + $this->assertFalse($constraint->evaluate('*-1*', '', true)); + $this->assertFalse($constraint->evaluate('*+2*', '', true)); + + $this->assertTrue($constraint->evaluate('*0f0f0f*', '', true)); + $this->assertTrue($constraint->evaluate('*0*', '', true)); + $this->assertTrue($constraint->evaluate('*12*', '', true)); + $this->assertTrue($constraint->evaluate('*a*', '', true)); + } + + public function testConstraintStringMatchesFloat(): void + { + $constraint = new StringMatchesFormatDescription('*%f*'); + + $this->assertFalse($constraint->evaluate('**', '', true)); + $this->assertFalse($constraint->evaluate('***', '', true)); + $this->assertFalse($constraint->evaluate('*a*', '', true)); + $this->assertFalse($constraint->evaluate('*1.*', '', true)); + + $this->assertTrue($constraint->evaluate('*1.0*', '', true)); + $this->assertTrue($constraint->evaluate('*0*', '', true)); + $this->assertTrue($constraint->evaluate('*12*', '', true)); + $this->assertTrue($constraint->evaluate('*.1*', '', true)); + $this->assertTrue($constraint->evaluate('*2e3*', '', true)); + $this->assertTrue($constraint->evaluate('*-2.34e-56*', '', true)); + $this->assertTrue($constraint->evaluate('*+2.34e+56*', '', true)); + } + + public function testConstraintStringMatchesCharacter(): void + { + $constraint = new StringMatchesFormatDescription('*%c*'); + + $this->assertFalse($constraint->evaluate('**', '', true)); + $this->assertFalse($constraint->evaluate('*ab*', '', true)); + + $this->assertTrue($constraint->evaluate('***', '', true)); + $this->assertTrue($constraint->evaluate('*a*', '', true)); + $this->assertTrue($constraint->evaluate('*g*', '', true)); + $this->assertTrue($constraint->evaluate('*0*', '', true)); + $this->assertTrue($constraint->evaluate('*2*', '', true)); + $this->assertTrue($constraint->evaluate('* *', '', true)); + $this->assertTrue($constraint->evaluate("*\n*", '', true)); + } + + public function testConstraintStringMatchesEscapedPercent(): void + { + $constraint = new StringMatchesFormatDescription('%%,%%e,%%s,%%S,%%a,%%A,%%w,%%i,%%d,%%x,%%f,%%c,%%Z,%%%%,%%'); + + $this->assertFalse($constraint->evaluate('%%,%' . DIRECTORY_SEPARATOR . ',%*,%*,%*,%*,% ,%0,%0,%0f0f0f,%1.0,%*,%%Z,%%%%,%%', '', true)); + $this->assertTrue($constraint->evaluate('%,%e,%s,%S,%a,%A,%w,%i,%d,%x,%f,%c,%Z,%%,%', '', true)); + } + + public function testConstraintStringMatchesEscapedPercentThenPlaceholder(): void + { + $constraint = new StringMatchesFormatDescription('%%%e,%%%s,%%%S,%%%a,%%%A,%%%w,%%%i,%%%d,%%%x,%%%f,%%%c'); + + $this->assertFalse($constraint->evaluate('%%e,%%s,%%S,%%a,%%A,%%w,%%i,%%d,%%x,%%f,%%c', '', true)); + $this->assertTrue($constraint->evaluate('%' . DIRECTORY_SEPARATOR . ',%*,%*,%*,%*,% ,%0,%0,%0f0f0f,%1.0,%*', '', true)); + } + + public function testConstraintStringMatchesSlash(): void + { + $constraint = new StringMatchesFormatDescription('/'); + + $this->assertFalse($constraint->evaluate('\\/', '', true)); + $this->assertTrue($constraint->evaluate('/', '', true)); + } + + public function testConstraintStringMatchesBackslash(): void + { + $constraint = new StringMatchesFormatDescription('\\'); + + $this->assertFalse($constraint->evaluate('\\\\', '', true)); + $this->assertTrue($constraint->evaluate('\\', '', true)); + } + + public function testConstraintStringMatchesBackslashSlash(): void + { + $constraint = new StringMatchesFormatDescription('\\/'); + + $this->assertFalse($constraint->evaluate('/', '', true)); + $this->assertTrue($constraint->evaluate('\\/', '', true)); + } + + public function testConstraintStringMatchesNewline(): void + { + $constraint = new StringMatchesFormatDescription("\r\n"); + + $this->assertFalse($constraint->evaluate("*\r\n", '', true)); + $this->assertTrue($constraint->evaluate("\r\n", '', true)); + } + + public function testConstraintStringMatchesAnythingMultiline(): void + { + $constraint = new StringMatchesFormatDescription("*\n%a\nbar\nbaz"); + + $this->assertFalse($constraint->evaluate("*\n*", '', true)); + } + + public function testFailureMessageWithNewlines(): void + { + $constraint = new StringMatchesFormatDescription("%c\nfoo\n%c"); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage( + <<<'EOD' +Failed asserting that string matches format description. +--- Expected ++++ Actual +@@ @@ + * +-foo ++bar + * + +EOD + ); + + $constraint->evaluate("*\nbar\n*"); + } + + public function testFailureMessageWithNewlinesAndAnythingMatcher(): void + { + $constraint = new StringMatchesFormatDescription("%a\nfoo\n%s\nbar"); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage( + <<<'EOD' +Failed asserting that string matches format description. +--- Expected ++++ Actual +@@ @@ + * + foo + * +-bar ++mismatch + +EOD + ); + + $constraint->evaluate("*\nfoo\n*\nmismatch"); + } + + public function testFailureMessageWithNewlinesAndAnythingMatcherMultilineMatches(): void + { + $constraint = new StringMatchesFormatDescription( + <<<'EOD' +## before first A +%A +## after first A +* +## before second A +%A +## after second A +* +Foo: %s + +EOD + ); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage( + <<<'EOD' +Failed asserting that string matches format description. +--- Expected ++++ Actual +@@ @@ + ## before first A + some multiline ++text for ++A to match + ## after first A + * + ## before second A ++more multiline text ++for A to match ++## after second A + * +-## after second A ++Foo: s match + * +-Foo: %s ++Additional Text that is not matched + +EOD + ); + + $constraint->evaluate( + <<<'EOD' +## before first A +some multiline +text for +A to match +## after first A +* +## before second A +more multiline text +for A to match +## after second A +* +Foo: s match +* +Additional Text that is not matched +EOD + ); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame( + 'matches format description:' . PHP_EOL . 'string', + new StringMatchesFormatDescription('string')->toString(), + ); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new StringMatchesFormatDescription('string'))); + } +} diff --git a/tests/unit/Framework/Constraint/String/StringStartsWithTest.php b/tests/unit/Framework/Constraint/String/StringStartsWithTest.php new file mode 100644 index 00000000000..c60d3104b49 --- /dev/null +++ b/tests/unit/Framework/Constraint/String/StringStartsWithTest.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\EmptyStringException; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; + +#[CoversClass(StringStartsWith::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class StringStartsWithTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + '', + 'prefix', + 'prefix substring suffix', + ], + + [ + false, + 'Failed asserting that \'substring suffix\' starts with "prefix".', + 'prefix', + 'substring suffix', + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $failureDescription, string $expected, string $actual): void + { + $constraint = new StringStartsWith($expected); + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('starts with "prefix"', new StringStartsWith('prefix')->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new StringStartsWith('prefix'))); + } + + public function testRejectsEmptyPrefix(): void + { + $this->expectException(EmptyStringException::class); + + new StringStartsWith(''); + } +} diff --git a/tests/unit/Framework/Constraint/StringContainsTest.php b/tests/unit/Framework/Constraint/StringContainsTest.php deleted file mode 100644 index c9a484f1915..00000000000 --- a/tests/unit/Framework/Constraint/StringContainsTest.php +++ /dev/null @@ -1,108 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestFailure; - -/** - * @small - */ -final class StringContainsTest extends ConstraintTestCase -{ - public function testConstraintStringContains(): void - { - $constraint = new StringContains('foo'); - - $this->assertFalse($constraint->evaluate('barbazbar', '', true)); - $this->assertTrue($constraint->evaluate('barfoobar', '', true)); - $this->assertEquals('contains "foo"', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate('barbazbar'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that 'barbazbar' contains "foo". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintStringContainsWhenIgnoreCase(): void - { - $constraint = new StringContains('oryginał', true); - - $this->assertFalse($constraint->evaluate('oryginal', '', true)); - $this->assertTrue($constraint->evaluate('ORYGINAŁ', '', true)); - $this->assertTrue($constraint->evaluate('oryginał', '', true)); - $this->assertEquals('contains "oryginał"', $constraint->toString()); - $this->assertCount(1, $constraint); - - $this->expectException(ExpectationFailedException::class); - - $constraint->evaluate('oryginal'); - } - - public function testConstraintStringContainsForUtf8StringWhenNotIgnoreCase(): void - { - $constraint = new StringContains('oryginał', false); - - $this->assertFalse($constraint->evaluate('oryginal', '', true)); - $this->assertFalse($constraint->evaluate('ORYGINAŁ', '', true)); - $this->assertTrue($constraint->evaluate('oryginał', '', true)); - $this->assertEquals('contains "oryginał"', $constraint->toString()); - $this->assertCount(1, $constraint); - - $this->expectException(ExpectationFailedException::class); - - $constraint->evaluate('oryginal'); - } - - public function testConstraintStringContains2(): void - { - $constraint = new StringContains('foo'); - - try { - $constraint->evaluate('barbazbar', 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that 'barbazbar' contains "foo". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testEvaluateEmptyStringInFoo(): void - { - $stringContains = new StringContains(''); - - $stringContains->evaluate('foo'); - - $this->assertSame('contains ""', $stringContains->toString()); - } -} diff --git a/tests/unit/Framework/Constraint/StringEndsWithTest.php b/tests/unit/Framework/Constraint/StringEndsWithTest.php deleted file mode 100644 index f3e8a5a8d3d..00000000000 --- a/tests/unit/Framework/Constraint/StringEndsWithTest.php +++ /dev/null @@ -1,106 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestFailure; - -/** - * @small - */ -final class StringEndsWithTest extends ConstraintTestCase -{ - public function testConstraintStringEndsWithCorrectValueAndReturnResult(): void - { - $constraint = new StringEndsWith('suffix'); - - $this->assertTrue($constraint->evaluate('foosuffix', '', true)); - } - - public function testConstraintStringEndsWithNotCorrectValueAndReturnResult(): void - { - $constraint = new StringEndsWith('suffix'); - - $this->assertFalse($constraint->evaluate('suffixerror', '', true)); - } - - public function testConstraintStringEndsWithCorrectNumericValueAndReturnResult(): void - { - $constraint = new StringEndsWith('0E1'); - - $this->assertTrue($constraint->evaluate('zzz0E1', '', true)); - } - - public function testConstraintStringEndsWithNotCorrectNumericValueAndReturnResult(): void - { - $constraint = new StringEndsWith('0E1'); - - $this->assertFalse($constraint->evaluate('zzz0E2', '', true)); - } - - public function testConstraintStringEndsWithToStringMethod(): void - { - $constraint = new StringEndsWith('suffix'); - - $this->assertEquals('ends with "suffix"', $constraint->toString()); - } - - public function testConstraintStringEndsWithCountMethod(): void - { - $constraint = new StringEndsWith('suffix'); - - $this->assertCount(1, $constraint); - } - - public function testConstraintStringEndsWithNotCorrectValueAndExpectation(): void - { - $constraint = new StringEndsWith('suffix'); - - try { - $constraint->evaluate('error'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that 'error' ends with "suffix". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintStringEndsWithNotCorrectValueExceptionAndCustomMessage(): void - { - $constraint = new StringEndsWith('suffix'); - - try { - $constraint->evaluate('error', 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that 'error' ends with "suffix". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } -} diff --git a/tests/unit/Framework/Constraint/StringMatchesFormatDescriptionTest.php b/tests/unit/Framework/Constraint/StringMatchesFormatDescriptionTest.php deleted file mode 100644 index 91bc69dc1ae..00000000000 --- a/tests/unit/Framework/Constraint/StringMatchesFormatDescriptionTest.php +++ /dev/null @@ -1,282 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use const DIRECTORY_SEPARATOR; -use PHPUnit\Framework\ExpectationFailedException; - -/** - * @small - */ -final class StringMatchesFormatDescriptionTest extends ConstraintTestCase -{ - public function testConstraintStringMatchesDirectorySeparator(): void - { - $constraint = new StringMatchesFormatDescription('*%e*'); - - $this->assertFalse($constraint->evaluate('**', '', true)); - $this->assertFalse($constraint->evaluate('*a*', '', true)); - - $this->assertTrue($constraint->evaluate('*' . DIRECTORY_SEPARATOR . '*', '', true)); - - $this->assertEquals('matches PCRE pattern "/^\*\\' . DIRECTORY_SEPARATOR . '\*$/s"', $constraint->toString()); - $this->assertCount(1, $constraint); - } - - public function testConstraintStringMatchesString(): void - { - $constraint = new StringMatchesFormatDescription('*%s*'); - - $this->assertFalse($constraint->evaluate('**', '', true)); - $this->assertFalse($constraint->evaluate("*\n*", '', true)); - - $this->assertTrue($constraint->evaluate('***', '', true)); - $this->assertTrue($constraint->evaluate('*foo 123 bar*', '', true)); - - $this->assertEquals('matches PCRE pattern "/^\*[^\r\n]+\*$/s"', $constraint->toString()); - $this->assertCount(1, $constraint); - } - - public function testConstraintStringMatchesOptionalString(): void - { - $constraint = new StringMatchesFormatDescription('*%S*'); - - $this->assertFalse($constraint->evaluate('*', '', true)); - $this->assertFalse($constraint->evaluate("*\n*", '', true)); - - $this->assertTrue($constraint->evaluate('***', '', true)); - $this->assertTrue($constraint->evaluate('*foo 123 bar*', '', true)); - $this->assertTrue($constraint->evaluate('**', '', true)); - - $this->assertEquals('matches PCRE pattern "/^\*[^\r\n]*\*$/s"', $constraint->toString()); - $this->assertCount(1, $constraint); - } - - public function testConstraintStringMatchesAnything(): void - { - $constraint = new StringMatchesFormatDescription('*%a*'); - - $this->assertFalse($constraint->evaluate('**', '', true)); - - $this->assertTrue($constraint->evaluate('***', '', true)); - $this->assertTrue($constraint->evaluate('*foo 123 bar*', '', true)); - $this->assertTrue($constraint->evaluate("*\n*", '', true)); - - $this->assertEquals('matches PCRE pattern "/^\*.+\*$/s"', $constraint->toString()); - $this->assertCount(1, $constraint); - } - - public function testConstraintStringMatchesOptionalAnything(): void - { - $constraint = new StringMatchesFormatDescription('*%A*'); - - $this->assertFalse($constraint->evaluate('*', '', true)); - - $this->assertTrue($constraint->evaluate('***', '', true)); - $this->assertTrue($constraint->evaluate('*foo 123 bar*', '', true)); - $this->assertTrue($constraint->evaluate("*\n*", '', true)); - $this->assertTrue($constraint->evaluate('**', '', true)); - - $this->assertEquals('matches PCRE pattern "/^\*.*\*$/s"', $constraint->toString()); - $this->assertCount(1, $constraint); - } - - public function testConstraintStringMatchesWhitespace(): void - { - $constraint = new StringMatchesFormatDescription('*%w*'); - - $this->assertFalse($constraint->evaluate('*', '', true)); - $this->assertFalse($constraint->evaluate('*a*', '', true)); - - $this->assertTrue($constraint->evaluate('* *', '', true)); - $this->assertTrue($constraint->evaluate("*\t\n*", '', true)); - $this->assertTrue($constraint->evaluate('**', '', true)); - - $this->assertEquals('matches PCRE pattern "/^\*\s*\*$/s"', $constraint->toString()); - $this->assertCount(1, $constraint); - } - - public function testConstraintStringMatchesInteger(): void - { - $constraint = new StringMatchesFormatDescription('*%i*'); - - $this->assertFalse($constraint->evaluate('**', '', true)); - $this->assertFalse($constraint->evaluate('*a*', '', true)); - $this->assertFalse($constraint->evaluate('*1.0*', '', true)); - - $this->assertTrue($constraint->evaluate('*0*', '', true)); - $this->assertTrue($constraint->evaluate('*12*', '', true)); - $this->assertTrue($constraint->evaluate('*-1*', '', true)); - $this->assertTrue($constraint->evaluate('*+2*', '', true)); - - $this->assertEquals('matches PCRE pattern "/^\*[+-]?\d+\*$/s"', $constraint->toString()); - $this->assertCount(1, $constraint); - } - - public function testConstraintStringMatchesUnsignedInt(): void - { - $constraint = new StringMatchesFormatDescription('*%d*'); - - $this->assertFalse($constraint->evaluate('**', '', true)); - $this->assertFalse($constraint->evaluate('*a*', '', true)); - $this->assertFalse($constraint->evaluate('*1.0*', '', true)); - $this->assertFalse($constraint->evaluate('*-1*', '', true)); - $this->assertFalse($constraint->evaluate('*+2*', '', true)); - - $this->assertTrue($constraint->evaluate('*0*', '', true)); - $this->assertTrue($constraint->evaluate('*12*', '', true)); - - $this->assertEquals('matches PCRE pattern "/^\*\d+\*$/s"', $constraint->toString()); - $this->assertCount(1, $constraint); - } - - public function testConstraintStringMatchesHexadecimal(): void - { - $constraint = new StringMatchesFormatDescription('*%x*'); - - $this->assertFalse($constraint->evaluate('**', '', true)); - $this->assertFalse($constraint->evaluate('***', '', true)); - $this->assertFalse($constraint->evaluate('*g*', '', true)); - $this->assertFalse($constraint->evaluate('*1.0*', '', true)); - $this->assertFalse($constraint->evaluate('*-1*', '', true)); - $this->assertFalse($constraint->evaluate('*+2*', '', true)); - - $this->assertTrue($constraint->evaluate('*0f0f0f*', '', true)); - $this->assertTrue($constraint->evaluate('*0*', '', true)); - $this->assertTrue($constraint->evaluate('*12*', '', true)); - $this->assertTrue($constraint->evaluate('*a*', '', true)); - - $this->assertEquals('matches PCRE pattern "/^\*[0-9a-fA-F]+\*$/s"', $constraint->toString()); - $this->assertCount(1, $constraint); - } - - public function testConstraintStringMatchesFloat(): void - { - $constraint = new StringMatchesFormatDescription('*%f*'); - - $this->assertFalse($constraint->evaluate('**', '', true)); - $this->assertFalse($constraint->evaluate('***', '', true)); - $this->assertFalse($constraint->evaluate('*a*', '', true)); - - $this->assertTrue($constraint->evaluate('*1.0*', '', true)); - $this->assertTrue($constraint->evaluate('*0*', '', true)); - $this->assertTrue($constraint->evaluate('*12*', '', true)); - $this->assertTrue($constraint->evaluate('*.1*', '', true)); - $this->assertTrue($constraint->evaluate('*1.*', '', true)); - $this->assertTrue($constraint->evaluate('*2e3*', '', true)); - $this->assertTrue($constraint->evaluate('*-2.34e-56*', '', true)); - $this->assertTrue($constraint->evaluate('*+2.34e+56*', '', true)); - - $this->assertEquals('matches PCRE pattern "/^\*[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?\*$/s"', $constraint->toString()); - $this->assertCount(1, $constraint); - } - - public function testConstraintStringMatchesCharacter(): void - { - $constraint = new StringMatchesFormatDescription('*%c*'); - - $this->assertFalse($constraint->evaluate('**', '', true)); - $this->assertFalse($constraint->evaluate('*ab*', '', true)); - - $this->assertTrue($constraint->evaluate('***', '', true)); - $this->assertTrue($constraint->evaluate('*a*', '', true)); - $this->assertTrue($constraint->evaluate('*g*', '', true)); - $this->assertTrue($constraint->evaluate('*0*', '', true)); - $this->assertTrue($constraint->evaluate('*2*', '', true)); - $this->assertTrue($constraint->evaluate('* *', '', true)); - $this->assertTrue($constraint->evaluate("*\n*", '', true)); - - $this->assertEquals('matches PCRE pattern "/^\*.\*$/s"', $constraint->toString()); - $this->assertCount(1, $constraint); - } - - public function testConstraintStringMatchesEscapedPercent(): void - { - $constraint = new StringMatchesFormatDescription('%%,%%e,%%s,%%S,%%a,%%A,%%w,%%i,%%d,%%x,%%f,%%c,%%Z,%%%%,%%'); - - $this->assertFalse($constraint->evaluate('%%,%' . DIRECTORY_SEPARATOR . ',%*,%*,%*,%*,% ,%0,%0,%0f0f0f,%1.0,%*,%%Z,%%%%,%%', '', true)); - $this->assertTrue($constraint->evaluate('%,%e,%s,%S,%a,%A,%w,%i,%d,%x,%f,%c,%Z,%%,%', '', true)); - $this->assertEquals('matches PCRE pattern "/^%,%e,%s,%S,%a,%A,%w,%i,%d,%x,%f,%c,%Z,%%,%$/s"', $constraint->toString()); - $this->assertCount(1, $constraint); - } - - public function testConstraintStringMatchesEscapedPercentThenPlaceholder(): void - { - $constraint = new StringMatchesFormatDescription('%%%e,%%%s,%%%S,%%%a,%%%A,%%%w,%%%i,%%%d,%%%x,%%%f,%%%c'); - - $this->assertFalse($constraint->evaluate('%%e,%%s,%%S,%%a,%%A,%%w,%%i,%%d,%%x,%%f,%%c', '', true)); - $this->assertTrue($constraint->evaluate('%' . DIRECTORY_SEPARATOR . ',%*,%*,%*,%*,% ,%0,%0,%0f0f0f,%1.0,%*', '', true)); - $this->assertEquals('matches PCRE pattern "/^%\\' . DIRECTORY_SEPARATOR . ',%[^\r\n]+,%[^\r\n]*,%.+,%.*,%\s*,%[+-]?\d+,%\d+,%[0-9a-fA-F]+,%[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?,%.$/s"', $constraint->toString()); - $this->assertCount(1, $constraint); - } - - public function testConstraintStringMatchesSlash(): void - { - $constraint = new StringMatchesFormatDescription('/'); - - $this->assertFalse($constraint->evaluate('\\/', '', true)); - $this->assertTrue($constraint->evaluate('/', '', true)); - $this->assertEquals('matches PCRE pattern "/^\\/$/s"', $constraint->toString()); - $this->assertCount(1, $constraint); - } - - public function testConstraintStringMatchesBackslash(): void - { - $constraint = new StringMatchesFormatDescription('\\'); - - $this->assertFalse($constraint->evaluate('\\\\', '', true)); - $this->assertTrue($constraint->evaluate('\\', '', true)); - $this->assertEquals('matches PCRE pattern "/^\\\\$/s"', $constraint->toString()); - $this->assertCount(1, $constraint); - } - - public function testConstraintStringMatchesBackslashSlash(): void - { - $constraint = new StringMatchesFormatDescription('\\/'); - - $this->assertFalse($constraint->evaluate('/', '', true)); - $this->assertTrue($constraint->evaluate('\\/', '', true)); - $this->assertEquals('matches PCRE pattern "/^\\\\\\/$/s"', $constraint->toString()); - $this->assertCount(1, $constraint); - } - - public function testConstraintStringMatchesNewline(): void - { - $constraint = new StringMatchesFormatDescription("\r\n"); - - $this->assertFalse($constraint->evaluate("*\r\n", '', true)); - $this->assertTrue($constraint->evaluate("\r\n", '', true)); - $this->assertEquals("matches PCRE pattern \"/^\n$/s\"", $constraint->toString()); - $this->assertCount(1, $constraint); - } - - public function testFailureMessageWithNewlines(): void - { - $constraint = new StringMatchesFormatDescription("%c\nfoo\n%c"); - - try { - $constraint->evaluate("*\nbar\n*"); - $this->fail('Expected ExpectationFailedException, but it was not thrown.'); - } catch (ExpectationFailedException $e) { - $expected = <<<'EOD' -Failed asserting that string matches format description. ---- Expected -+++ Actual -@@ @@ - * --foo -+bar - * - -EOD; - $this->assertEquals($expected, $e->getMessage()); - } - } -} diff --git a/tests/unit/Framework/Constraint/StringStartsWithTest.php b/tests/unit/Framework/Constraint/StringStartsWithTest.php deleted file mode 100644 index 9607a434446..00000000000 --- a/tests/unit/Framework/Constraint/StringStartsWithTest.php +++ /dev/null @@ -1,113 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\TestFailure; - -/** - * @small - */ -final class StringStartsWithTest extends ConstraintTestCase -{ - public function testConstraintStringStartsWithCorrectValueAndReturnResult(): void - { - $constraint = new StringStartsWith('prefix'); - - $this->assertTrue($constraint->evaluate('prefixfoo', '', true)); - } - - public function testConstraintStringStartsWithNotCorrectValueAndReturnResult(): void - { - $constraint = new StringStartsWith('prefix'); - - $this->assertFalse($constraint->evaluate('error', '', true)); - } - - public function testConstraintStringStartsWithCorrectNumericValueAndReturnResult(): void - { - $constraint = new StringStartsWith('0E1'); - - $this->assertTrue($constraint->evaluate('0E1zzz', '', true)); - } - - public function testConstraintStringStartsWithCorrectSingleZeroAndReturnResult(): void - { - $constraint = new StringStartsWith('0'); - - $this->assertTrue($constraint->evaluate('0ABC', '', true)); - } - - public function testConstraintStringStartsWithNotCorrectNumericValueAndReturnResult(): void - { - $constraint = new StringStartsWith('0E1'); - - $this->assertFalse($constraint->evaluate('0E2zzz', '', true)); - } - - public function testConstraintStringStartsWithToStringMethod(): void - { - $constraint = new StringStartsWith('prefix'); - - $this->assertEquals('starts with "prefix"', $constraint->toString()); - } - - public function testConstraintStringStartsWitCountMethod(): void - { - $constraint = new StringStartsWith('prefix'); - - $this->assertCount(1, $constraint); - } - - public function testConstraintStringStartsWithNotCorrectValueAndExpectation(): void - { - $constraint = new StringStartsWith('prefix'); - - try { - $constraint->evaluate('error'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that 'error' starts with "prefix". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintStringStartsWithNotCorrectValueExceptionAndCustomMessage(): void - { - $constraint = new StringStartsWith('prefix'); - - try { - $constraint->evaluate('error', 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that 'error' starts with "prefix". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } -} diff --git a/tests/unit/Framework/Constraint/Traversable/ArrayHasKeyTest.php b/tests/unit/Framework/Constraint/Traversable/ArrayHasKeyTest.php new file mode 100644 index 00000000000..5dbccaa9bb3 --- /dev/null +++ b/tests/unit/Framework/Constraint/Traversable/ArrayHasKeyTest.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use ArrayObject; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; + +#[CoversClass(ArrayHasKey::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class ArrayHasKeyTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + '', + 0, + [0 => 'value'], + ], + + [ + true, + '', + 'key', + ['key' => 'value'], + ], + + [ + true, + '', + 'key', + new ArrayObject(['key' => 'value']), + ], + + [ + false, + 'Failed asserting that an array has the key 1.', + 1, + [0 => 'value'], + ], + + [ + false, + 'Failed asserting that an array has the key \'another-key\'.', + 'another-key', + ['key' => 'value'], + ], + + [ + false, + 'Failed asserting that an array has the key \'another-key\'.', + 'another-key', + new ArrayObject(['key' => 'value']), + ], + + [ + false, + 'Failed asserting that an array has the key \'key\'.', + 'key', + null, + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $failureDescription, int|string $expected, mixed $actual): void + { + $constraint = new ArrayHasKey($expected); + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('has the key 0', new ArrayHasKey(0)->toString()); + $this->assertSame('has the key \'key\'', new ArrayHasKey('key')->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new ArrayHasKey(0))); + } +} diff --git a/tests/unit/Framework/Constraint/Traversable/IsListTest.php b/tests/unit/Framework/Constraint/Traversable/IsListTest.php new file mode 100644 index 00000000000..1e734aee999 --- /dev/null +++ b/tests/unit/Framework/Constraint/Traversable/IsListTest.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function fclose; +use function fopen; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; +use stdClass; + +#[CoversClass(IsList::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class IsListTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + '', + [0 => 'value', 1 => 'another-value'], + ], + + [ + false, + 'Failed asserting that an array is a list.', + ['key' => 'value'], + ], + + [ + false, + 'Failed asserting that an integer is a list.', + 0, + ], + + [ + false, + 'Failed asserting that an instance of class stdClass is a list.', + new stdClass, + ], + + [ + false, + 'Failed asserting that a boolean is a list.', + false, + ], + + [ + false, + 'Failed asserting that a float is a list.', + 0.0, + ], + + [ + false, + 'Failed asserting that a resource is a list.', + fopen(__FILE__, 'r'), + ], + + [ + false, + 'Failed asserting that a closed resource is a list.', + self::closedResource(), + ], + + [ + false, + 'Failed asserting that null is a list.', + null, + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $failureDescription, mixed $actual): void + { + $constraint = new IsList; + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('is a list', (new IsList)->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new IsList)); + } + + private static function closedResource() + { + $resource = fopen(__FILE__, 'r'); + + fclose($resource); + + return $resource; + } +} diff --git a/tests/unit/Framework/Constraint/Traversable/TraversableContainsEqualTest.php b/tests/unit/Framework/Constraint/Traversable/TraversableContainsEqualTest.php new file mode 100644 index 00000000000..c261db93e80 --- /dev/null +++ b/tests/unit/Framework/Constraint/Traversable/TraversableContainsEqualTest.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; +use SplObjectStorage; +use stdClass; + +#[CoversClass(TraversableContainsEqual::class)] +#[CoversClass(TraversableContains::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class TraversableContainsEqualTest extends TestCase +{ + public static function provider(): array + { + $o = new stdClass; + + $s = new SplObjectStorage; + $s->offsetSet($o); + + return [ + [ + true, + '', + 0, + [0], + ], + + [ + true, + '', + 'value', + ['value'], + ], + + [ + true, + '', + $o, + [$o], + ], + + [ + true, + '', + $o, + $s, + ], + + [ + true, + '', + '0', + [0], + ], + + [ + true, + '', + 0, + ['0'], + ], + + [ + false, + 'Failed asserting that an array contains stdClass Object', + $o, + [], + ], + + [ + false, + 'Failed asserting that a traversable contains stdClass Object', + $o, + new SplObjectStorage, + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $failureDescription, mixed $expected, mixed $actual): void + { + $constraint = new TraversableContainsEqual($expected); + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('contains \'value\'', new TraversableContainsEqual('value')->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new TraversableContainsEqual('value'))); + } +} diff --git a/tests/unit/Framework/Constraint/Traversable/TraversableContainsIdenticalTest.php b/tests/unit/Framework/Constraint/Traversable/TraversableContainsIdenticalTest.php new file mode 100644 index 00000000000..59938248045 --- /dev/null +++ b/tests/unit/Framework/Constraint/Traversable/TraversableContainsIdenticalTest.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; +use SplObjectStorage; +use stdClass; + +#[CoversClass(TraversableContainsIdentical::class)] +#[CoversClass(TraversableContains::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class TraversableContainsIdenticalTest extends TestCase +{ + public static function provider(): array + { + $o = new stdClass; + + $s = new SplObjectStorage; + $s->offsetSet($o); + + return [ + [ + true, + '', + 0, + [0], + ], + + [ + true, + '', + 'value', + ['value'], + ], + + [ + true, + '', + $o, + [$o], + ], + + [ + true, + '', + $o, + $s, + ], + + [ + false, + 'Failed asserting that an array contains stdClass Object', + $o, + [], + ], + + [ + false, + 'Failed asserting that a traversable contains stdClass Object', + $o, + new SplObjectStorage, + ], + + [ + false, + 'Failed asserting that an array contains \'0\'.', + '0', + [0], + ], + + [ + false, + 'Failed asserting that an array contains 0.', + 0, + ['0'], + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $failureDescription, mixed $expected, mixed $actual): void + { + $constraint = new TraversableContainsIdentical($expected); + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('contains \'value\'', new TraversableContainsIdentical('value')->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, (new TraversableContainsIdentical('value'))); + } +} diff --git a/tests/unit/Framework/Constraint/Traversable/TraversableContainsOnlyTest.php b/tests/unit/Framework/Constraint/Traversable/TraversableContainsOnlyTest.php new file mode 100644 index 00000000000..0d272f2f4ce --- /dev/null +++ b/tests/unit/Framework/Constraint/Traversable/TraversableContainsOnlyTest.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\NativeType; +use PHPUnit\Framework\TestCase; +use stdClass; + +#[CoversClass(TraversableContainsOnly::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class TraversableContainsOnlyTest extends TestCase +{ + public static function nativeTypeProvider(): array + { + return [ + [ + true, + '', + NativeType::Int, + [0, 1, 2], + ], + + [ + false, + <<<'EOT' +Failed asserting that Array &0 [ + 0 => 0, + 1 => '1', + 2 => 2, +] contains only values of type "int". +EOT, + NativeType::Int, + [0, '1', 2], + ], + ]; + } + + public static function classOrInterfaceProvider(): array + { + return [ + [ + true, + '', + stdClass::class, + [new stdClass, new stdClass], + ], + + [ + false, + <<<'EOT' +Failed asserting that Array &0 [ + 0 => null, +] contains only values of type "stdClass". +EOT, + stdClass::class, + [null], + ], + ]; + } + + #[DataProvider('nativeTypeProvider')] + public function testCanBeEvaluatedForNativeType(bool $result, string $failureDescription, NativeType $expected, mixed $actual): void + { + $constraint = TraversableContainsOnly::forNativeType($expected); + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + /** + * @param class-string $expected + */ + #[DataProvider('classOrInterfaceProvider')] + public function testCanBeEvaluatedForClassOrInterface(bool $result, string $failureDescription, string $expected, mixed $actual): void + { + $constraint = TraversableContainsOnly::forClassOrInterface($expected); + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + public function testCanBeRepresentedAsStringForNativeType(): void + { + $this->assertSame('contains only values of type "int"', TraversableContainsOnly::forNativeType(NativeType::Int)->toString()); + } + + public function testCanBeRepresentedAsStringForClassOrInterface(): void + { + $this->assertSame('contains only values of type "stdClass"', TraversableContainsOnly::forClassOrInterface(stdClass::class)->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, TraversableContainsOnly::forNativeType(NativeType::Int)); + } +} diff --git a/tests/unit/Framework/Constraint/TraversableContainsEqualTest.php b/tests/unit/Framework/Constraint/TraversableContainsEqualTest.php deleted file mode 100644 index 9fc4007a682..00000000000 --- a/tests/unit/Framework/Constraint/TraversableContainsEqualTest.php +++ /dev/null @@ -1,87 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use SplObjectStorage; -use stdClass; - -/** - * @small - */ -final class TraversableContainsEqualTest extends ConstraintTestCase -{ - public function testArrayContainsFloat(): void - { - $constraint = new TraversableContainsEqual(22.04); - - $this->assertTrue($constraint->evaluate([22.04], '', true)); - $this->assertTrue($constraint->evaluate(['22.04'], '', true)); - $this->assertFalse($constraint->evaluate([19.78], '', true)); - $this->assertFalse($constraint->evaluate(['19.78'], '', true)); - } - - public function testArrayContainsInteger(): void - { - $constraint = new TraversableContainsEqual(2204); - - $this->assertTrue($constraint->evaluate([2204], '', true)); - $this->assertTrue($constraint->evaluate(['2204'], '', true)); - $this->assertFalse($constraint->evaluate([1978], '', true)); - $this->assertFalse($constraint->evaluate(['1978'], '', true)); - } - - public function testArrayContainsString(): void - { - $constraint = new TraversableContainsEqual('foo'); - - $this->assertTrue($constraint->evaluate(['foo'], '', true)); - $this->assertFalse($constraint->evaluate(['bar'], '', true)); - } - - public function testArrayContainsObject(): void - { - $a = new stdClass; - $a->foo = 'bar'; - - $b = new stdClass; - $b->foo = 'bar'; - - $c = new stdClass; - $c->foo = 'baz'; - - $constraint = new TraversableContainsEqual($a); - - $this->assertTrue($constraint->evaluate([$b], '', true)); - $this->assertFalse($constraint->evaluate([$c], '', true)); - } - - public function test_SplObjectStorage_ContainsObject(): void - { - $a = new stdClass; - $a->foo = 'bar'; - - $b = new stdClass; - $b->foo = 'bar'; - - $c = new stdClass; - $c->foo = 'baz'; - - $storageWithA = new SplObjectStorage; - $storageWithA->attach($a); - - $storageWithoutA = new SplObjectStorage; - $storageWithoutA->attach($b); - - $constraint = new TraversableContainsEqual($a); - - $this->assertTrue($constraint->evaluate($storageWithA, '', true)); - $this->assertFalse($constraint->evaluate($storageWithoutA, '', true)); - } -} diff --git a/tests/unit/Framework/Constraint/TraversableContainsIdenticalTest.php b/tests/unit/Framework/Constraint/TraversableContainsIdenticalTest.php deleted file mode 100644 index bbcd2335818..00000000000 --- a/tests/unit/Framework/Constraint/TraversableContainsIdenticalTest.php +++ /dev/null @@ -1,88 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use SplObjectStorage; -use stdClass; - -/** - * @small - */ -final class TraversableContainsIdenticalTest extends ConstraintTestCase -{ - public function testArrayContainsFloat(): void - { - $constraint = new TraversableContainsIdentical(22.04); - - $this->assertTrue($constraint->evaluate([22.04], '', true)); - $this->assertFalse($constraint->evaluate(['22.04'], '', true)); - $this->assertFalse($constraint->evaluate([19.78], '', true)); - $this->assertFalse($constraint->evaluate(['19.78'], '', true)); - } - - public function testArrayContainsInteger(): void - { - $constraint = new TraversableContainsIdentical(2204); - - $this->assertTrue($constraint->evaluate([2204], '', true)); - $this->assertFalse($constraint->evaluate(['2204'], '', true)); - $this->assertFalse($constraint->evaluate([1978], '', true)); - $this->assertFalse($constraint->evaluate(['1978'], '', true)); - } - - public function testArrayContainsString(): void - { - $constraint = new TraversableContainsIdentical('foo'); - - $this->assertTrue($constraint->evaluate(['foo'], '', true)); - $this->assertFalse($constraint->evaluate(['bar'], '', true)); - } - - public function testArrayContainsObject(): void - { - $a = new stdClass; - $a->foo = 'bar'; - - $b = new stdClass; - $b->foo = 'bar'; - - $c = new stdClass; - $c->foo = 'baz'; - - $constraint = new TraversableContainsIdentical($a); - - $this->assertTrue($constraint->evaluate([$a], '', true)); - $this->assertFalse($constraint->evaluate([$b], '', true)); - $this->assertFalse($constraint->evaluate([$c], '', true)); - } - - public function test_SplObjectStorage_ContainsObject(): void - { - $a = new stdClass; - $a->foo = 'bar'; - - $b = new stdClass; - $b->foo = 'bar'; - - $c = new stdClass; - $c->foo = 'baz'; - - $storageWithA = new SplObjectStorage; - $storageWithA->attach($a); - - $storageWithoutA = new SplObjectStorage; - $storageWithoutA->attach($b); - - $constraint = new TraversableContainsIdentical($a); - - $this->assertTrue($constraint->evaluate($storageWithA, '', true)); - $this->assertFalse($constraint->evaluate($storageWithoutA, '', true)); - } -} diff --git a/tests/unit/Framework/Constraint/Type/IsInstanceOfTest.php b/tests/unit/Framework/Constraint/Type/IsInstanceOfTest.php new file mode 100644 index 00000000000..924fabc559e --- /dev/null +++ b/tests/unit/Framework/Constraint/Type/IsInstanceOfTest.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use Exception; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\UnknownClassOrInterfaceException; +use stdClass; +use Throwable; + +#[CoversClass(IsInstanceOf::class)] +#[CoversClass(Constraint::class)] +#[CoversClass(UnknownClassOrInterfaceException::class)] +#[Small] +final class IsInstanceOfTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + '', + stdClass::class, + new stdClass, + ], + + [ + false, + 'Failed asserting that an instance of anonymous class created at', + stdClass::class, + new class + {}, + ], + + [ + false, + 'Failed asserting that an instance of class Exception is an instance of class stdClass.', + stdClass::class, + new Exception, + ], + + [ + false, + 'Failed asserting that an instance of class stdClass is an instance of interface Throwable.', + Throwable::class, + new stdClass, + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $failureDescription, string $expected, mixed $actual): void + { + $constraint = new IsInstanceOf($expected); + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('is an instance of class stdClass', new IsInstanceOf(stdClass::class)->toString()); + $this->assertSame('is an instance of interface Throwable', new IsInstanceOf(Throwable::class)->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, new IsInstanceOf(stdClass::class)); + } + + public function testRejectsUnknownTypes(): void + { + $this->expectException(UnknownClassOrInterfaceException::class); + + new IsInstanceOf('Does\Not\Exist'); + } +} diff --git a/tests/unit/Framework/Constraint/Type/IsNullTest.php b/tests/unit/Framework/Constraint/Type/IsNullTest.php new file mode 100644 index 00000000000..f5008a9b2c3 --- /dev/null +++ b/tests/unit/Framework/Constraint/Type/IsNullTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(IsNull::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class IsNullTest extends TestCase +{ + public function testCanBeEvaluated(): void + { + $constraint = new IsNull; + + $this->assertTrue($constraint->evaluate(null, returnResult: true)); + $this->assertFalse($constraint->evaluate(false, returnResult: true)); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('is null', (new IsNull)->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, new IsNull); + } +} diff --git a/tests/unit/Framework/Constraint/Type/IsTypeTest.php b/tests/unit/Framework/Constraint/Type/IsTypeTest.php new file mode 100644 index 00000000000..e71f8c7bcd2 --- /dev/null +++ b/tests/unit/Framework/Constraint/Type/IsTypeTest.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function fclose; +use function fopen; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\NativeType; +use PHPUnit\Framework\TestCase; +use stdClass; + +#[CoversClass(IsType::class)] +#[CoversClass(Constraint::class)] +#[Small] +final class IsTypeTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + true, + '', + NativeType::Numeric, + 0, + ], + + [ + true, + '', + NativeType::Int, + 0, + ], + + [ + true, + '', + NativeType::Float, + 0.0, + ], + + [ + true, + '', + NativeType::String, + 'string', + ], + + [ + true, + '', + NativeType::Bool, + false, + ], + + [ + true, + '', + NativeType::Null, + null, + ], + + [ + true, + '', + NativeType::Array, + [], + ], + + [ + true, + '', + NativeType::Object, + new stdClass, + ], + + [ + true, + '', + NativeType::Resource, + fopen(__FILE__, 'r'), + ], + + [ + true, + '', + NativeType::ClosedResource, + self::closedResource(), + ], + + [ + true, + '', + NativeType::Scalar, + 0, + ], + + [ + true, + '', + NativeType::Callable, + static fn () => true, + ], + + [ + true, + '', + NativeType::Iterable, + [], + ], + ]; + } + + #[DataProvider('provider')] + public function testCanBeEvaluated(bool $result, string $failureDescription, NativeType $expected, mixed $actual): void + { + $constraint = new IsType($expected); + + $this->assertSame($result, $constraint->evaluate($actual, returnResult: true)); + + if ($result) { + return; + } + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage($failureDescription); + + $constraint->evaluate($actual); + } + + public function testCanBeRepresentedAsString(): void + { + $this->assertSame('is of type array', new IsType(NativeType::Array)->toString()); + } + + public function testIsCountable(): void + { + $this->assertCount(1, new IsType(NativeType::Array)); + } + + private static function closedResource() + { + $resource = fopen(__FILE__, 'r'); + + fclose($resource); + + return $resource; + } +} diff --git a/tests/unit/Framework/Constraint/UnaryOperatorTestCase.php b/tests/unit/Framework/Constraint/UnaryOperatorTestCase.php deleted file mode 100644 index 633a7f48819..00000000000 --- a/tests/unit/Framework/Constraint/UnaryOperatorTestCase.php +++ /dev/null @@ -1,635 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use function array_map; -use function sprintf; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\TestFixture\BooleanConstraint; -use PHPUnit\TestFixture\CountConstraint; -use PHPUnit\TestFixture\NamedConstraint; -use ReflectionClass; -use ReflectionMethod; - -abstract class UnaryOperatorTestCase extends OperatorTestCase -{ - /** - * Shall return the name of the operator under test. - */ - abstract public static function getOperatorName(): string; - - /** - * Shall return the precedence of the operator under test. - */ - abstract public static function getOperatorPrecedence(): int; - - /** - * Shall return series of two-element arrays [$input, $expected]. - */ - abstract public function providerToStringWithNativeTransformations(); - - /** - * Takes a boolean values and returns the expected evaluation result for - * the logical operator under test. - */ - abstract public function evaluateExpectedResult(bool $input): bool; - - final public function testIsSubclassOfOperator(): void - { - $className = $this->className(); - - $reflection = new ReflectionClass($className); - - $this->assertTrue($reflection->isSubclassOf(Operator::class), sprintf( - 'Failed to assert that "%s" is subclass of "%s".', - $className, - Operator::class - )); - } - - final public function testOperatorName(): void - { - $className = $this->className(); - $constraint = new $className($this->getMockBuilder(Constraint::class)->getMock()); - $this->assertSame($this->getOperatorName(), $constraint->operator()); - } - - final public function testOperatorPrecedence(): void - { - $className = $this->className(); - $constraint = new $className($this->getMockBuilder(Constraint::class)->getMock()); - $this->assertSame($this->getOperatorPrecedence(), $constraint->precedence()); - } - - final public function testOperatorCount(): void - { - $className = $this->className(); - - $constraint = new $className(CountConstraint::fromCount(3)); - - $this->assertSame(3, $constraint->count()); - } - - final public function testOperatorArity(): void - { - $className = $this->className(); - - $constraint = new $className(CountConstraint::fromCount(3)); - - $this->assertSame(1, $constraint->arity()); - } - - final public function testConstructorAcceptsConstraintArgument(): void - { - $className = $this->className(); - - $nice = $this->getMockBuilder(Constraint::class) - ->setMethods(['toStringInContext']) - ->getMockForAbstractClass(); - - $constraint = new $className($nice); - - $string = 'is ' . $this->getOperatorName() . ' nice'; - - $nice->expects($this->once()) - ->method('toStringInContext') - ->with($this->identicalTo($constraint), 0) - ->willReturn($string); - - $this->assertSame($string, $constraint->toString()); - } - - public function testNonRestrictedConstructParameterIsHandled(): void - { - $className = $this->className(); - - $constraint = new $className('test'); - - $withIsEqual = new $className(new IsEqual('test')); - - $this->assertSame($withIsEqual->toString(), $constraint->toString()); - } - - final public function providerUnaryTruthTable() - { - return array_map(function (bool $input): array { - return [$input, $this->evaluateExpectedResult($input)]; - }, [false, true]); - } - - /** - * @dataProvider providerUnaryTruthTable - */ - final public function testEvaluateReturnsCorrectBooleanResult(bool $input, bool $expected): void - { - $operand = BooleanConstraint::fromBool($input); - - $className = $this->className(); - - $constraint = new $className($operand); - - $message = 'Failed asserting that ' . $constraint->toString() . ' is ' . ($expected ? 'true' : 'false'); - $this->assertSame($expected, $constraint->evaluate(null, '', true), $message); - } - - /** - * @dataProvider providerUnaryTruthTable - */ - final public function testEvaluateReturnsNullOnSuccessAndThrowsExceptionOnFailure(bool $input, bool $expected): void - { - $operand = BooleanConstraint::fromBool($input); - - $className = $this->className(); - - $constraint = new $className($operand); - - if ($expected) { - $this->assertNull($constraint->evaluate(null)); - - return; - } - - $expectedString = $operand->toString(); - $message = "Failed asserting that 'the following expression is not true' " . $expectedString; - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage($message); - $constraint->evaluate('the following expression is true'); - } - - /** - * @dataProvider providerToStringWithNativeTransformations - */ - final public function testToStringWithNativeTransformations(string $input, string $expected): void - { - $operand = NamedConstraint::fromName($input); - - $className = $this->className(); - - $constraint = new $className($operand); - - $this->assertSame($expected, $constraint->toString()); - } - - public function testToStringWithNonContextualTerminalConstraint(): void - { - $methods = [ - 'toStringInContext', - 'toString', - ]; - - $operand = $this->getMockBuilder(Constraint::class) - ->setMethods($methods) - ->getMockForAbstractClass(); - - $className = $this->className(); - - $operator = new $className($operand); - - $string = 'John Smith'; - - $operand->expects($this->once()) - ->method('toStringInContext') - ->with($this->identicalTo($operator), 0) - ->willReturn(''); - $operand->expects($this->once()) - ->method('toString') - ->with() - ->willReturn($string); - - $this->assertSame($string, $operator->toString()); - } - - public function testToStringWithContextualTerminalConstraint(): void - { - $methods = [ - 'toStringInContext', - 'toString', - ]; - - $operand = $this->getMockBuilder(Constraint::class) - ->setMethods($methods) - ->getMockForAbstractClass(); - - $className = $this->className(); - - $operator = new $className($operand); - - $string = 'John ' . $this->getOperatorName() . ' Smith'; - - $operand->expects($this->once()) - ->method('toStringInContext') - ->with($this->identicalTo($operator), 0) - ->willReturn($string); - $operand->expects($this->never()) - ->method('toString'); - - $this->assertSame($string, $operator->toString()); - } - - public function testToStringWithContextualUnaryOperator(): void - { - $methods = [ - 'toStringInContext', - 'toString', - 'arity', - 'precedence', - ]; - - $operand = $this->getMockBuilder(Operator::class) - ->setMethods($methods) - ->getMockForAbstractClass(); - - $className = $this->className(); - - $operator = new $className($operand); - - $string = 'John ' . $this->getOperatorName() . ' Smith'; - - $operand->expects($this->once()) - ->method('arity') - ->with() - ->willReturn(1); - $operand->expects($this->never()) - ->method('precedence'); - $operand->expects($this->once()) - ->method('toStringInContext') - ->with($this->identicalTo($operator), 0) - ->willReturn($string); - $operand->expects($this->never()) - ->method('toString'); - - $this->assertSame($string, $operator->toString()); - } - - public function testToStringWithNonContextualBinaryOperatorOfHigherPrecedence(): void - { - $methods = [ - 'toStringInContext', - 'toString', - 'arity', - 'precedence', - ]; - - $operand = $this->getMockBuilder(Operator::class) - ->setMethods($methods) - ->getMockForAbstractClass(); - - $className = $this->className(); - - $operator = new $className($operand); - - $string = 'tree of apples'; - - $operand->expects($this->once()) - ->method('arity') - ->with() - ->willReturn(2); - $operand->expects($this->once()) - ->method('precedence') - ->with() - ->willReturn(-1); - $operand->expects($this->once()) - ->method('toStringInContext') - ->with($this->identicalTo($operator), 0) - ->willReturn(''); - $operand->expects($this->once()) - ->method('toString') - ->with() - ->willReturn($string); - - $this->assertSame($string, $operator->toString()); - } - - public function testToStringWithContextualBinaryOperatorOfHigherPrecedence(): void - { - $methods = [ - 'toStringInContext', - 'toString', - 'arity', - 'precedence', - ]; - - $operand = $this->getMockBuilder(Operator::class) - ->setMethods($methods) - ->getMockForAbstractClass(); - - $className = $this->className(); - - $operator = new $className($operand); - - $string = 'tree of apples'; - - $operand->expects($this->once()) - ->method('arity') - ->with() - ->willReturn(2); - $operand->expects($this->once()) - ->method('precedence') - ->with() - ->willReturn(-1); - $operand->expects($this->once()) - ->method('toStringInContext') - ->with($this->identicalTo($operator), 0) - ->willReturn($string); - $operand->expects($this->never()) - ->method('toString'); - - $this->assertSame($string, $operator->toString()); - } - - public function testToStringWithBinaryOperatorOfLowerPrecedence(): void - { - $methods = [ - 'toStringInContext', - 'toString', - 'arity', - 'precedence', - ]; - - $operand = $this->getMockBuilder(Operator::class) - ->setMethods($methods) - ->getMockForAbstractClass(); - - $className = $this->className(); - - $operator = new $className($operand); - - $string = 'apple or banana'; - - $operand->expects($this->once()) - ->method('arity') - ->with() - ->willReturn(2); - $operand->expects($this->once()) - ->method('precedence') - ->with() - ->willReturn(10000); - $operand->expects($this->never()) - ->method('toStringInContext'); - $operand->expects($this->once()) - ->method('toString') - ->with() - ->willReturn($string); - - $expected = $this->getOperatorName() . '( ' . $string . ' )'; - $this->assertSame($expected, $operator->toString()); - } - - /** - * @dataProvider providerToStringWithNativeTransformations - */ - public function testFailureDescriptionWithNativeTransformations(string $input, string $expected): void - { - $operand = NamedConstraint::fromName($input); - - $className = $this->className(); - - $constraint = new $className($operand); - - $method = (new ReflectionMethod($className, 'failureDescription')); - $method->setAccessible(true); - - $this->assertSame("'whatever' " . $expected, $method->invokeArgs($constraint, ['whatever'])); - } - - public function testFailureDescriptionWithNonContextualTerminalConstraint(): void - { - $methods = [ - 'toStringInContext', - 'toString', - ]; - - $operand = $this->getMockBuilder(Constraint::class) - ->setMethods($methods) - ->getMockForAbstractClass(); - - $className = $this->className(); - - $operator = new $className($operand); - - $string = 'John Smith'; - - $operand->expects($this->once()) - ->method('toStringInContext') - ->with($this->identicalTo($operator), 0) - ->willReturn(''); - $operand->expects($this->once()) - ->method('toString') - ->with() - ->willReturn($string); - - $method = (new ReflectionMethod($className, 'failureDescription')); - $method->setAccessible(true); - - $expected = "'whatever' " . $string; - - $this->assertSame($expected, $method->invokeArgs($operator, ['whatever'])); - } - - public function testFailureDescriptionWithContextualTerminalConstraint(): void - { - $methods = [ - 'toStringInContext', - 'toString', - ]; - - $operand = $this->getMockBuilder(Constraint::class) - ->setMethods($methods) - ->getMockForAbstractClass(); - - $className = $this->className(); - - $operator = new $className($operand); - - $string = 'John ' . $this->getOperatorName() . ' Smith'; - - $operand->expects($this->once()) - ->method('toStringInContext') - ->with($this->identicalTo($operator), 0) - ->willReturn($string); - $operand->expects($this->never()) - ->method('toString'); - - $method = (new ReflectionMethod($className, 'failureDescription')); - $method->setAccessible(true); - - $expected = "'whatever' " . $string; - - $this->assertSame($expected, $method->invokeArgs($operator, ['whatever'])); - } - - public function testFailureDescriptionWithContextualUnaryOperator(): void - { - $methods = [ - 'toStringInContext', - 'toString', - 'arity', - 'precedence', - ]; - - $operand = $this->getMockBuilder(Operator::class) - ->setMethods($methods) - ->getMockForAbstractClass(); - - $className = $this->className(); - - $operator = new $className($operand); - - $string = 'John ' . $this->getOperatorName() . ' Smith'; - - $operand->expects($this->once()) - ->method('arity') - ->with() - ->willReturn(1); - $operand->expects($this->never()) - ->method('precedence'); - $operand->expects($this->once()) - ->method('toStringInContext') - ->with($this->identicalTo($operator), 0) - ->willReturn($string); - $operand->expects($this->never()) - ->method('toString'); - - $method = (new ReflectionMethod($className, 'failureDescription')); - $method->setAccessible(true); - - $expected = "'whatever' " . $string; - - $this->assertSame($expected, $method->invokeArgs($operator, ['whatever'])); - } - - public function testFailureDescriptionWithNonContextualBinaryOperatorOfHigherPrecedence(): void - { - $methods = [ - 'toStringInContext', - 'toString', - 'arity', - 'precedence', - ]; - - $operand = $this->getMockBuilder(Operator::class) - ->setMethods($methods) - ->getMockForAbstractClass(); - - $className = $this->className(); - - $operator = new $className($operand); - - $string = 'tree of apples'; - - $operand->expects($this->once()) - ->method('arity') - ->with() - ->willReturn(2); - $operand->expects($this->once()) - ->method('precedence') - ->with() - ->willReturn(-1); - $operand->expects($this->once()) - ->method('toStringInContext') - ->with($this->identicalTo($operator), 0) - ->willReturn(''); - $operand->expects($this->once()) - ->method('toString') - ->with() - ->willReturn($string); - - $method = (new ReflectionMethod($className, 'failureDescription')); - $method->setAccessible(true); - - $expected = "'whatever' " . $string; - - $this->assertSame($expected, $method->invokeArgs($operator, ['whatever'])); - } - - public function testFailureDescriptionWithContextualBinaryOperatorOfHigherPrecedence(): void - { - $methods = [ - 'toStringInContext', - 'toString', - 'arity', - 'precedence', - ]; - - $operand = $this->getMockBuilder(Operator::class) - ->setMethods($methods) - ->getMockForAbstractClass(); - - $className = $this->className(); - - $operator = new $className($operand); - - $string = 'tree of apples'; - - $operand->expects($this->once()) - ->method('arity') - ->with() - ->willReturn(2); - $operand->expects($this->once()) - ->method('precedence') - ->with() - ->willReturn(-1); - $operand->expects($this->once()) - ->method('toStringInContext') - ->with($this->identicalTo($operator), 0) - ->willReturn($string); - $operand->expects($this->never()) - ->method('toString'); - - $method = (new ReflectionMethod($className, 'failureDescription')); - $method->setAccessible(true); - - $expected = "'whatever' " . $string; - - $this->assertSame($expected, $method->invokeArgs($operator, ['whatever'])); - } - - public function testFailureDescriptionWithBinaryOperatorOfLowerPrecedence(): void - { - $methods = [ - 'toStringInContext', - 'toString', - 'arity', - 'precedence', - ]; - - $operand = $this->getMockBuilder(Operator::class) - ->setMethods($methods) - ->getMockForAbstractClass(); - - $className = $this->className(); - - $operator = new $className($operand); - - $string = 'apple or banana'; - - $operand->expects($this->once()) - ->method('arity') - ->with() - ->willReturn(2); - $operand->expects($this->once()) - ->method('precedence') - ->with() - ->willReturn(10000); - $operand->expects($this->never()) - ->method('toStringInContext'); - $operand->expects($this->once()) - ->method('toString') - ->with() - ->willReturn($string); - - $method = (new ReflectionMethod($className, 'failureDescription')); - $method->setAccessible(true); - - $expected = $this->getOperatorName() . "( 'whatever' " . $string . ' )'; - - $this->assertSame($expected, $method->invokeArgs($operator, ['whatever'])); - } -} diff --git a/tests/unit/Framework/ConstraintTest.php b/tests/unit/Framework/ConstraintTest.php deleted file mode 100644 index 6bb3b87101a..00000000000 --- a/tests/unit/Framework/ConstraintTest.php +++ /dev/null @@ -1,1393 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use function preg_replace; -use ArrayObject; -use Countable; -use PHPUnit\Framework\Constraint\Count; -use PHPUnit\Framework\Constraint\SameSize; -use PHPUnit\TestFixture\ClassWithNonPublicAttributes; -use PHPUnit\TestFixture\DummyException; -use PHPUnit\TestFixture\TestIterator; -use PHPUnit\Util\Filter; -use stdClass; - -/** - * @small - */ -final class ConstraintTest extends TestCase -{ - public function testConstraintArrayNotHasKey(): void - { - $constraint = Assert::logicalNot( - Assert::arrayHasKey(0) - ); - - $this->assertFalse($constraint->evaluate([0 => 1], '', true)); - $this->assertEquals('does not have the key 0', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate([0 => 1]); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that an array does not have the key 0. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintArrayNotHasKey2(): void - { - $constraint = Assert::logicalNot( - Assert::arrayHasKey(0) - ); - - try { - $constraint->evaluate([0], 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that an array does not have the key 0. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintFileNotExists(): void - { - $file = TEST_FILES_PATH . 'ClassWithNonPublicAttributes.php'; - - $constraint = Assert::logicalNot( - Assert::fileExists() - ); - - $this->assertFalse($constraint->evaluate($file, '', true)); - $this->assertEquals('file does not exist', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate($file); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<fail(); - } - - public function testConstraintFileNotExists2(): void - { - $file = TEST_FILES_PATH . 'ClassWithNonPublicAttributes.php'; - - $constraint = Assert::logicalNot( - Assert::fileExists() - ); - - try { - $constraint->evaluate($file, 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<fail(); - } - - public function testConstraintNotGreaterThan(): void - { - $constraint = Assert::logicalNot( - Assert::greaterThan(1) - ); - - $this->assertTrue($constraint->evaluate(1, '', true)); - $this->assertEquals('is not greater than 1', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate(2); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that 2 is not greater than 1. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintNotGreaterThan2(): void - { - $constraint = Assert::logicalNot( - Assert::greaterThan(1) - ); - - try { - $constraint->evaluate(2, 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that 2 is not greater than 1. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintGreaterThanOrEqual(): void - { - $constraint = Assert::greaterThanOrEqual(1); - - $this->assertTrue($constraint->evaluate(1, '', true)); - $this->assertFalse($constraint->evaluate(0, '', true)); - $this->assertEquals('is equal to 1 or is greater than 1', $constraint->toString()); - $this->assertCount(2, $constraint); - - try { - $constraint->evaluate(0); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that 0 is equal to 1 or is greater than 1. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintGreaterThanOrEqual2(): void - { - $constraint = Assert::greaterThanOrEqual(1); - - try { - $constraint->evaluate(0, 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that 0 is equal to 1 or is greater than 1. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintNotGreaterThanOrEqual(): void - { - $constraint = Assert::logicalNot( - Assert::greaterThanOrEqual(1) - ); - - $this->assertFalse($constraint->evaluate(1, '', true)); - $this->assertEquals('not( is equal to 1 or is greater than 1 )', $constraint->toString()); - $this->assertCount(2, $constraint); - - try { - $constraint->evaluate(1); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that not( 1 is equal to 1 or is greater than 1 ). - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintNotGreaterThanOrEqual2(): void - { - $constraint = Assert::logicalNot( - Assert::greaterThanOrEqual(1) - ); - - try { - $constraint->evaluate(1, 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that not( 1 is equal to 1 or is greater than 1 ). - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintIsAnything(): void - { - $constraint = Assert::anything(); - - $this->assertTrue($constraint->evaluate(null, '', true)); - $this->assertNull($constraint->evaluate(null)); - $this->assertEquals('is anything', $constraint->toString()); - $this->assertCount(0, $constraint); - } - - public function testConstraintNotIsAnything(): void - { - $constraint = Assert::logicalNot( - Assert::anything() - ); - - $this->assertFalse($constraint->evaluate(null, '', true)); - $this->assertEquals('is not anything', $constraint->toString()); - $this->assertCount(0, $constraint); - - try { - $constraint->evaluate(null); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that null is not anything. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintIsNotEqual(): void - { - $constraint = Assert::logicalNot( - Assert::equalTo(1) - ); - - $this->assertTrue($constraint->evaluate(0, '', true)); - $this->assertFalse($constraint->evaluate(1, '', true)); - $this->assertEquals('is not equal to 1', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate(1); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that 1 is not equal to 1. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintIsNotEqual2(): void - { - $constraint = Assert::logicalNot( - Assert::equalTo(1) - ); - - try { - $constraint->evaluate(1, 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that 1 is not equal to 1. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintIsNotIdentical(): void - { - $a = new stdClass; - $b = new stdClass; - - $constraint = Assert::logicalNot( - Assert::identicalTo($a) - ); - - $this->assertTrue($constraint->evaluate($b, '', true)); - $this->assertFalse($constraint->evaluate($a, '', true)); - $this->assertEquals('is not identical to an object of class "stdClass"', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate($a); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that two variables don't reference the same object. - -EOF - , - $this->trimnl(TestFailure::exceptionToString($e)) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintIsNotIdentical2(): void - { - $a = new stdClass; - - $constraint = Assert::logicalNot( - Assert::identicalTo($a) - ); - - try { - $constraint->evaluate($a, 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that two variables don't reference the same object. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintIsNotIdentical3(): void - { - $constraint = Assert::logicalNot( - Assert::identicalTo('a') - ); - - try { - $constraint->evaluate('a', 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that two strings are not identical. - -EOF - , - $this->trimnl(TestFailure::exceptionToString($e)) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintIsInstanceOf(): void - { - $constraint = Assert::isInstanceOf(\Exception::class); - - $this->assertFalse($constraint->evaluate(new stdClass, '', true)); - $this->assertTrue($constraint->evaluate(new \Exception, '', true)); - $this->assertEquals('is instance of class "Exception"', $constraint->toString()); - $this->assertCount(1, $constraint); - - $interfaceConstraint = Assert::isInstanceOf(Countable::class); - $this->assertFalse($interfaceConstraint->evaluate(new stdClass, '', true)); - $this->assertTrue($interfaceConstraint->evaluate(new ArrayObject, '', true)); - $this->assertEquals('is instance of interface "Countable"', $interfaceConstraint->toString()); - - try { - $constraint->evaluate(new stdClass); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that stdClass Object () is an instance of class "Exception". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintIsInstanceOf2(): void - { - $constraint = Assert::isInstanceOf(\Exception::class); - - try { - $constraint->evaluate(new stdClass, 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that stdClass Object () is an instance of class "Exception". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintIsNotInstanceOf(): void - { - $constraint = Assert::logicalNot( - Assert::isInstanceOf(stdClass::class) - ); - - $this->assertFalse($constraint->evaluate(new stdClass, '', true)); - $this->assertTrue($constraint->evaluate(new Exception, '', true)); - $this->assertEquals('is not instance of class "stdClass"', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate(new stdClass); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that stdClass Object () is not an instance of class "stdClass". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintIsNotInstanceOf2(): void - { - $constraint = Assert::logicalNot( - Assert::isInstanceOf(stdClass::class) - ); - - try { - $constraint->evaluate(new stdClass, 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that stdClass Object () is not an instance of class "stdClass". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintIsNotType(): void - { - $constraint = Assert::logicalNot( - Assert::isType('string') - ); - - $this->assertTrue($constraint->evaluate(0, '', true)); - $this->assertFalse($constraint->evaluate('', '', true)); - $this->assertEquals('is not of type "string"', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate(''); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that '' is not of type "string". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintIsNotType2(): void - { - $constraint = Assert::logicalNot( - Assert::isType('string') - ); - - try { - $constraint->evaluate('', 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that '' is not of type "string". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintIsNotNull(): void - { - $constraint = Assert::logicalNot( - Assert::isNull() - ); - - $this->assertFalse($constraint->evaluate(null, '', true)); - $this->assertTrue($constraint->evaluate(0, '', true)); - $this->assertEquals('is not null', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate(null); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that null is not null. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintIsNotNull2(): void - { - $constraint = Assert::logicalNot( - Assert::isNull() - ); - - try { - $constraint->evaluate(null, 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that null is not null. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintNotLessThan(): void - { - $constraint = Assert::logicalNot( - Assert::lessThan(1) - ); - - $this->assertTrue($constraint->evaluate(1, '', true)); - $this->assertFalse($constraint->evaluate(0, '', true)); - $this->assertEquals('is not less than 1', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate(0); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that 0 is not less than 1. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintNotLessThan2(): void - { - $constraint = Assert::logicalNot( - Assert::lessThan(1) - ); - - try { - $constraint->evaluate(0, 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that 0 is not less than 1. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintLessThanOrEqual(): void - { - $constraint = Assert::lessThanOrEqual(1); - - $this->assertTrue($constraint->evaluate(1, '', true)); - $this->assertFalse($constraint->evaluate(2, '', true)); - $this->assertEquals('is equal to 1 or is less than 1', $constraint->toString()); - $this->assertCount(2, $constraint); - - try { - $constraint->evaluate(2); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that 2 is equal to 1 or is less than 1. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintLessThanOrEqual2(): void - { - $constraint = Assert::lessThanOrEqual(1); - - try { - $constraint->evaluate(2, 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that 2 is equal to 1 or is less than 1. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintNotLessThanOrEqual(): void - { - $constraint = Assert::logicalNot( - Assert::lessThanOrEqual(1) - ); - - $this->assertTrue($constraint->evaluate(2, '', true)); - $this->assertFalse($constraint->evaluate(1, '', true)); - $this->assertEquals('not( is equal to 1 or is less than 1 )', $constraint->toString()); - $this->assertCount(2, $constraint); - - try { - $constraint->evaluate(1); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that not( 1 is equal to 1 or is less than 1 ). - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintNotLessThanOrEqual2(): void - { - $constraint = Assert::logicalNot( - Assert::lessThanOrEqual(1) - ); - - try { - $constraint->evaluate(1, 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that not( 1 is equal to 1 or is less than 1 ). - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintClassNotHasAttribute(): void - { - $constraint = Assert::logicalNot( - Assert::classHasAttribute('privateAttribute') - ); - - $this->assertTrue($constraint->evaluate(stdClass::class, '', true)); - $this->assertFalse($constraint->evaluate(ClassWithNonPublicAttributes::class, '', true)); - $this->assertEquals('does not have attribute "privateAttribute"', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate(ClassWithNonPublicAttributes::class); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<fail(); - } - - public function testConstraintClassNotHasAttribute2(): void - { - $constraint = Assert::logicalNot( - Assert::classHasAttribute('privateAttribute') - ); - - try { - $constraint->evaluate(ClassWithNonPublicAttributes::class, 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<fail(); - } - - public function testConstraintClassNotHasStaticAttribute(): void - { - $constraint = Assert::logicalNot( - Assert::classHasStaticAttribute('privateStaticAttribute') - ); - - $this->assertTrue($constraint->evaluate(stdClass::class, '', true)); - $this->assertFalse($constraint->evaluate(ClassWithNonPublicAttributes::class, '', true)); - $this->assertEquals('does not have static attribute "privateStaticAttribute"', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate(ClassWithNonPublicAttributes::class); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<fail(); - } - - public function testConstraintClassNotHasStaticAttribute2(): void - { - $constraint = Assert::logicalNot( - Assert::classHasStaticAttribute('privateStaticAttribute') - ); - - try { - $constraint->evaluate(ClassWithNonPublicAttributes::class, 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<fail(); - } - - public function testConstraintObjectNotHasAttribute(): void - { - $constraint = Assert::logicalNot( - Assert::objectHasAttribute('privateAttribute') - ); - - $this->assertTrue($constraint->evaluate(new stdClass, '', true)); - $this->assertFalse($constraint->evaluate(new ClassWithNonPublicAttributes, '', true)); - $this->assertEquals('does not have attribute "privateAttribute"', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate(new ClassWithNonPublicAttributes); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<fail(); - } - - public function testConstraintObjectNotHasAttribute2(): void - { - $constraint = Assert::logicalNot( - Assert::objectHasAttribute('privateAttribute') - ); - - try { - $constraint->evaluate(new ClassWithNonPublicAttributes, 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<fail(); - } - - /** - * @testdox Constraint PCRE not match - */ - public function testConstraintPCRENotMatch(): void - { - $constraint = Assert::logicalNot( - Assert::matchesRegularExpression('/foo/') - ); - - $this->assertTrue($constraint->evaluate('barbazbar', '', true)); - $this->assertFalse($constraint->evaluate('barfoobar', '', true)); - $this->assertEquals('does not match PCRE pattern "/foo/"', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate('barfoobar'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that 'barfoobar' does not match PCRE pattern "/foo/". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - /** - * @testdox Constraint PCRE not match with custom message - */ - public function testConstraintPCRENotMatch2(): void - { - $constraint = Assert::logicalNot( - Assert::matchesRegularExpression('/foo/') - ); - - try { - $constraint->evaluate('barfoobar', 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that 'barfoobar' does not match PCRE pattern "/foo/". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintStringStartsNotWith(): void - { - $constraint = Assert::logicalNot( - Assert::stringStartsWith('prefix') - ); - - $this->assertTrue($constraint->evaluate('foo', '', true)); - $this->assertFalse($constraint->evaluate('prefixfoo', '', true)); - $this->assertEquals('starts not with "prefix"', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate('prefixfoo'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that 'prefixfoo' starts not with "prefix". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintStringStartsNotWith2(): void - { - $constraint = Assert::logicalNot( - Assert::stringStartsWith('prefix') - ); - - try { - $constraint->evaluate('prefixfoo', 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that 'prefixfoo' starts not with "prefix". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintStringNotContains(): void - { - $constraint = Assert::logicalNot( - Assert::stringContains('foo') - ); - - $this->assertTrue($constraint->evaluate('barbazbar', '', true)); - $this->assertFalse($constraint->evaluate('barfoobar', '', true)); - $this->assertEquals('does not contain "foo"', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate('barfoobar'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that 'barfoobar' does not contain "foo". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintStringNotContainsWhenIgnoreCase(): void - { - $constraint = Assert::logicalNot( - Assert::stringContains('oryginał') - ); - - $this->assertTrue($constraint->evaluate('original', '', true)); - $this->assertFalse($constraint->evaluate('ORYGINAŁ', '', true)); - $this->assertFalse($constraint->evaluate('oryginał', '', true)); - $this->assertEquals('does not contain "oryginał"', $constraint->toString()); - $this->assertCount(1, $constraint); - - $this->expectException(ExpectationFailedException::class); - - $constraint->evaluate('ORYGINAŁ'); - } - - public function testConstraintStringNotContainsForUtf8StringWhenNotIgnoreCase(): void - { - $constraint = Assert::logicalNot( - Assert::stringContains('oryginał', false) - ); - - $this->assertTrue($constraint->evaluate('original', '', true)); - $this->assertTrue($constraint->evaluate('ORYGINAŁ', '', true)); - $this->assertFalse($constraint->evaluate('oryginał', '', true)); - $this->assertEquals('does not contain "oryginał"', $constraint->toString()); - $this->assertCount(1, $constraint); - - $this->expectException(ExpectationFailedException::class); - - $constraint->evaluate('oryginał'); - } - - public function testConstraintStringNotContains2(): void - { - $constraint = Assert::logicalNot( - Assert::stringContains('foo') - ); - - try { - $constraint->evaluate('barfoobar', 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that 'barfoobar' does not contain "foo". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintStringEndsNotWith(): void - { - $constraint = Assert::logicalNot( - Assert::stringEndsWith('suffix') - ); - - $this->assertTrue($constraint->evaluate('foo', '', true)); - $this->assertFalse($constraint->evaluate('foosuffix', '', true)); - $this->assertEquals('ends not with "suffix"', $constraint->toString()); - $this->assertCount(1, $constraint); - - try { - $constraint->evaluate('foosuffix'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that 'foosuffix' ends not with "suffix". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintStringEndsNotWith2(): void - { - $constraint = Assert::logicalNot( - Assert::stringEndsWith('suffix') - ); - - try { - $constraint->evaluate('foosuffix', 'custom message'); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -custom message -Failed asserting that 'foosuffix' ends not with "suffix". - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintCountWithAnArray(): void - { - $constraint = new Count(5); - - $this->assertTrue($constraint->evaluate([1, 2, 3, 4, 5], '', true)); - $this->assertFalse($constraint->evaluate([1, 2, 3, 4], '', true)); - } - - public function testConstraintCountWithAnIteratorWhichDoesNotImplementCountable(): void - { - $constraint = new Count(5); - - $this->assertTrue($constraint->evaluate(new TestIterator([1, 2, 3, 4, 5]), '', true)); - $this->assertFalse($constraint->evaluate(new TestIterator([1, 2, 3, 4]), '', true)); - } - - public function testConstraintCountWithAnObjectImplementingCountable(): void - { - $constraint = new Count(5); - - $this->assertTrue($constraint->evaluate(new ArrayObject([1, 2, 3, 4, 5]), '', true)); - $this->assertFalse($constraint->evaluate(new ArrayObject([1, 2, 3, 4]), '', true)); - } - - public function testConstraintCountFailing(): void - { - $constraint = new Count(5); - - try { - $constraint->evaluate([1, 2]); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that actual size 2 matches expected size 5. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintNotCountFailing(): void - { - $constraint = Assert::logicalNot( - new Count(2) - ); - - try { - $constraint->evaluate([1, 2]); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that actual size 2 does not match expected size 2. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintNotSameSizeFailing(): void - { - $constraint = Assert::logicalNot( - new SameSize([1, 2]) - ); - - try { - $constraint->evaluate([3, 4]); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<<'EOF' -Failed asserting that actual size 2 does not match expected size 2. - -EOF - , - TestFailure::exceptionToString($e) - ); - - return; - } - - $this->fail(); - } - - public function testConstraintException(): void - { - $constraint = new Constraint\Exception('FoobarException'); - $exception = new DummyException('Test'); - $stackTrace = Filter::getFilteredStacktrace($exception); - - try { - $constraint->evaluate($exception); - } catch (ExpectationFailedException $e) { - $this->assertEquals( - <<fail(); - } - - /** - * Removes spaces in front of newlines. - * - * @param string $string - * - * @return string - */ - private function trimnl($string) - { - return preg_replace('/[ ]*\n/', "\n", $string); - } -} diff --git a/tests/unit/Framework/Exception/ExceptionTest.php b/tests/unit/Framework/Exception/ExceptionTest.php index 64ae6cb616b..c19badaede8 100644 --- a/tests/unit/Framework/Exception/ExceptionTest.php +++ b/tests/unit/Framework/Exception/ExceptionTest.php @@ -9,20 +9,22 @@ */ namespace PHPUnit\Framework; -class ExceptionTest extends TestCase +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(Exception::class)] +#[Small] +final class ExceptionTest extends TestCase { - public function testExceptionSleep(): void + public function testExceptionSerialize(): void { - $exception = new Exception(); - - $expectedArray = [ - 'serializableTrace', - 'message', - 'code', - 'file', - 'line', - ]; + $actual = (new Exception)->__serialize(); - $this->assertSame($expectedArray, $exception->__sleep()); + $this->assertCount(5, $actual); + $this->assertArrayHasKey('serializableTrace', $actual); + $this->assertArrayHasKey('message', $actual); + $this->assertArrayHasKey('code', $actual); + $this->assertArrayHasKey('file', $actual); + $this->assertArrayHasKey('line', $actual); } } diff --git a/tests/unit/Framework/Exception/InvalidArgumentExceptionTest.php b/tests/unit/Framework/Exception/InvalidArgumentExceptionTest.php deleted file mode 100644 index 0d7f317819b..00000000000 --- a/tests/unit/Framework/Exception/InvalidArgumentExceptionTest.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @small - */ -final class InvalidArgumentExceptionTest extends TestCase -{ - /** - * @dataProvider provider - */ - public function testUsesCorrectArticleInErrorMessage(string $expected, $type): void - { - $e = InvalidArgumentException::create(1, $type); - - $this->assertStringMatchesFormat($expected, $e->getMessage()); - } - - public function provider(): array - { - return [ - 'an array' => ['Argument #1 of %s must be an array', 'array'], - 'a boolean' => ['Argument #1 of %s must be a boolean', 'boolean'], - ]; - } -} diff --git a/tests/unit/Framework/ExceptionWrapperTest.php b/tests/unit/Framework/ExceptionWrapperTest.php deleted file mode 100644 index b14a3bdc8b0..00000000000 --- a/tests/unit/Framework/ExceptionWrapperTest.php +++ /dev/null @@ -1,62 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use function print_r; -use BadFunctionCallException; -use Exception; - -/** - * @small - */ -final class ExceptionWrapperTest extends TestCase -{ - /** - * @runInSeparateProcess - */ - public function testGetOriginalException(): void - { - $e = new BadFunctionCallException('custom class exception'); - $wrapper = new ExceptionWrapper($e); - - $this->assertInstanceOf(BadFunctionCallException::class, $wrapper->getOriginalException()); - } - - /** - * @runInSeparateProcess - */ - public function testGetOriginalExceptionWithPrevious(): void - { - $e = new BadFunctionCallException('custom class exception', 0, new Exception('previous')); - $wrapper = new ExceptionWrapper($e); - - $this->assertInstanceOf(BadFunctionCallException::class, $wrapper->getOriginalException()); - } - - /** - * @runInSeparateProcess - */ - public function testNoOriginalExceptionInStacktrace(): void - { - $e = new BadFunctionCallException('custom class exception'); - $wrapper = new ExceptionWrapper($e); - - // Replace the only mention of "BadFunctionCallException" in wrapper - $wrapper->setClassName('MyException'); - - $data = print_r($wrapper, true); - - $this->assertStringNotContainsString( - 'BadFunctionCallException', - $data, - 'Assert there is s no other BadFunctionCallException mention in stacktrace' - ); - } -} diff --git a/tests/unit/Framework/ExecutionOrderDependencyTest.php b/tests/unit/Framework/ExecutionOrderDependencyTest.php index 381fc349428..95932d172ba 100644 --- a/tests/unit/Framework/ExecutionOrderDependencyTest.php +++ b/tests/unit/Framework/ExecutionOrderDependencyTest.php @@ -7,12 +7,12 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -use PHPUnit\Framework\ExecutionOrderDependency; -use PHPUnit\Framework\TestCase; +namespace PHPUnit\Framework; -/** - * @covers \PHPUnit\Framework\ExecutionOrderDependency - */ +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; + +#[CoversClass(ExecutionOrderDependency::class)] class ExecutionOrderDependencyTest extends TestCase { public static function createFromParametersProvider(): array @@ -34,11 +34,9 @@ public static function createFromParametersProvider(): array public static function createWithCloneOptionProvider(): array { return [ - // Cloning option values - ['clone', false, true], - ['!clone', false, false], - ['shallowClone', true, false], - ['!shallowClone', false, false], + 'no clone' => [false, false, false, false], + 'deep clone' => [true, false, false, true], + 'shallow clone' => [false, true, true, false], ]; } @@ -51,18 +49,7 @@ public function testCreateDependencyOnClassFromClassNameOnly(): void $this->assertSame('ClassDependency', $dependency->getTargetClassName()); } - public function testDependsAnnotationRequireATargetToBeValid(): void - { - $dependency = ExecutionOrderDependency::createFromDependsAnnotation('SomeClass', ''); - - $this->assertFalse($dependency->isValid()); - $this->assertSame('', $dependency->getTarget()); - } - - /** - * @testdox Create valid dependency from [$className, $methodName] - * @dataProvider createFromParametersProvider - */ + #[DataProvider('createFromParametersProvider')] public function testCreateDependencyFromParameters( string $className, ?string $methodName, @@ -74,55 +61,34 @@ public function testCreateDependencyFromParameters( $this->assertSame( $expectedTarget, $dependency->getTarget(), - 'Incorrect dependency class::method target' + 'Incorrect dependency class::method target', ); $this->assertSame( $expectedTargetIsClass, $dependency->targetIsClass(), - 'Incorrect targetIsClass' + 'Incorrect targetIsClass', ); } - /** - * @testdox Create valid dependency with clone option $option - * @dataProvider createWithCloneOptionProvider - */ - public function testCreateDependencyWithCloneOption( - ?string $option, - bool $expectedShallowClone, - bool $expectedDeepClone - ): void { - $dependency = new ExecutionOrderDependency('ClassName', 'methodName', $option); + #[DataProvider('createWithCloneOptionProvider')] + public function testCreateDependencyWithCloneOption(bool $deepClone, bool $shallowClone, bool $expectedShallowClone, bool $expectedDeepClone): void + { + $dependency = new ExecutionOrderDependency('ClassName', 'methodName', $deepClone, $shallowClone); $this->assertSame( $expectedShallowClone, - $dependency->useShallowClone(), - 'Incorrect shallowClone option' + $dependency->shallowClone(), + 'Incorrect shallowClone option', ); $this->assertSame( $expectedDeepClone, - $dependency->useDeepClone(), - 'Incorrect clone option' + $dependency->deepClone(), + 'Incorrect clone option', ); } - public function testCreateDependencyFromAnnotation(): void - { - $dependency = ExecutionOrderDependency::createFromDependsAnnotation('ClassOne', 'ClassOne::methodOne'); - - $this->assertSame('ClassOne::methodOne', $dependency->getTarget()); - } - - public function testCreateDependencyFromAnnotationWithCloneOption(): void - { - $dependency = ExecutionOrderDependency::createFromDependsAnnotation('ClassOne', 'clone methodOne'); - - $this->assertSame('ClassOne::methodOne', $dependency->getTarget()); - $this->assertTrue($dependency->useDeepClone()); - } - public function testMergeHandlesEmptyDependencyLists(): void { $depOne = new ExecutionOrderDependency('classOne'); @@ -132,76 +98,27 @@ public function testMergeHandlesEmptyDependencyLists(): void [$depOne, $depTwo], ExecutionOrderDependency::mergeUnique( [], - [$depOne, $depTwo] + [$depOne, $depTwo], ), - 'Left side of merge could be empty' + 'Left side of merge could be empty', ); $this->assertSame( [$depOne, $depTwo], ExecutionOrderDependency::mergeUnique( [$depOne, $depTwo], - [] + [], ), - 'Right side of merge could be empty' - ); - } - - public function testMergeUniqueDependencies(): void - { - $depOne = new ExecutionOrderDependency('classOne'); - $depTwo = new ExecutionOrderDependency('classTwo::methodTwo'); - $depThree = ExecutionOrderDependency::createFromDependsAnnotation('classThree', 'clone methodThree'); - - $this->assertSame( - [$depOne, $depTwo, $depThree], - ExecutionOrderDependency::mergeUnique( - [$depOne, $depTwo], - [$depTwo, $depThree] - ) + 'Right side of merge could be empty', ); } - public function testDiffDependencies(): void + public function testEmptyClassOrCallable(): void { - $depOne = new ExecutionOrderDependency('classOne'); - $depTwo = new ExecutionOrderDependency('classTwo::methodTwo'); - $depThree = new ExecutionOrderDependency('classThree::methodThree'); - $depThreeClone = ExecutionOrderDependency::createFromDependsAnnotation('classThree', 'clone methodThree'); - $depFour = new ExecutionOrderDependency('classFour::methodFour'); - - $this->assertSame( - [], - ExecutionOrderDependency::diff( - [$depOne, $depTwo], - [$depOne, $depTwo, $depThree, $depFour] - ) - ); - - $this->assertSame( - [$depOne, $depTwo], - ExecutionOrderDependency::diff( - [$depOne, $depTwo], - [$depThree, $depFour] - ) - ); - - $this->assertSame( - [$depOne, $depFour], - ExecutionOrderDependency::diff( - [$depOne, $depTwo, $depThree, $depFour], - [$depTwo, $depThreeClone] - ) - ); - - $this->assertSame( - [$depOne, $depTwo, $depThree, $depFour], - ExecutionOrderDependency::diff( - [$depOne, $depTwo, $depThree, $depFour], - [] - ) - ); - - $this->assertSame([], ExecutionOrderDependency::diff([], [])); + $empty = new ExecutionOrderDependency(''); + $this->assertFalse($empty->shallowClone()); + $this->assertFalse($empty->deepClone()); + $this->assertFalse($empty->targetIsClass()); + $this->assertSame('', $empty->getTargetClassName()); } } diff --git a/tests/unit/Framework/IncompleteTestCaseTest.php b/tests/unit/Framework/IncompleteTestCaseTest.php deleted file mode 100644 index cb000932fcb..00000000000 --- a/tests/unit/Framework/IncompleteTestCaseTest.php +++ /dev/null @@ -1,90 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use function array_shift; -use function sprintf; -use PHPUnit\Runner\BaseTestRunner; - -final class IncompleteTestCaseTest extends TestCase -{ - public function testDefaults(): void - { - $testCase = new IncompleteTestCase( - 'Foo', - 'testThatBars' - ); - - $this->assertSame('', $testCase->getMessage()); - } - - public function testGetNameReturnsClassAndMethodName(): void - { - $className = 'Foo'; - $methodName = 'testThatBars'; - - $testCase = new IncompleteTestCase( - $className, - $methodName - ); - - $name = sprintf( - '%s::%s', - $className, - $methodName - ); - - $this->assertSame($name, $testCase->getName()); - } - - public function testGetMessageReturnsMessage(): void - { - $message = 'Somehow incomplete, right?'; - - $testCase = new IncompleteTestCase( - 'Foo', - 'testThatBars', - $message - ); - - $this->assertSame($message, $testCase->getMessage()); - } - - public function testRunMarksTestAsIncomplete(): void - { - $className = 'Foo'; - $methodName = 'testThatBars'; - $message = 'Somehow incomplete, right?'; - - $testCase = new IncompleteTestCase( - $className, - $methodName, - $message - ); - - $result = $testCase->run(); - - $this->assertSame(BaseTestRunner::STATUS_INCOMPLETE, $testCase->getStatus()); - $this->assertSame(1, $result->notImplementedCount()); - - $failures = $result->notImplemented(); - - $failure = array_shift($failures); - - $name = sprintf( - '%s::%s', - $className, - $methodName - ); - - $this->assertSame($name, $failure->getTestName()); - $this->assertSame($message, $failure->exceptionMessage()); - } -} diff --git a/tests/unit/Framework/MockObject/Builder/InvocationMockerTest.php b/tests/unit/Framework/MockObject/Builder/InvocationMockerTest.php deleted file mode 100644 index 58f78d5f3ac..00000000000 --- a/tests/unit/Framework/MockObject/Builder/InvocationMockerTest.php +++ /dev/null @@ -1,252 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\Constraint\IsEqual; -use PHPUnit\Framework\MockObject\Builder\InvocationMocker; -use PHPUnit\Framework\MockObject\IncompatibleReturnValueException; -use PHPUnit\Framework\MockObject\InvocationHandler; -use PHPUnit\Framework\MockObject\Matcher; -use PHPUnit\Framework\MockObject\Stub\ReturnSelf; -use PHPUnit\Framework\MockObject\Stub\ReturnStub; -use PHPUnit\Framework\TestCase; -use PHPUnit\TestFixture\ClassWithAllPossibleReturnTypes; -use PHPUnit\TestFixture\MockObject\ClassWithImplicitProtocol; - -/** - * @covers \PHPUnit\Framework\MockObject\Builder\InvocationMocker - * @small - */ -final class InvocationMockerTest extends TestCase -{ - public function testWillReturnWithOneValue(): void - { - $mock = $this->getMockBuilder(stdClass::class) - ->setMethods(['foo']) - ->getMock(); - - $mock->method('foo') - ->willReturn(1); - - $this->assertEquals(1, $mock->foo()); - } - - public function testWillReturnWithMultipleValues(): void - { - $mock = $this->getMockBuilder(stdClass::class) - ->setMethods(['foo']) - ->getMock(); - - $mock->method('foo') - ->willReturn(1, 2, 3); - - $this->assertEquals(1, $mock->foo()); - $this->assertEquals(2, $mock->foo()); - $this->assertEquals(3, $mock->foo()); - } - - public function testWillReturnOnConsecutiveCalls(): void - { - $mock = $this->getMockBuilder(stdClass::class) - ->setMethods(['foo']) - ->getMock(); - - $mock->method('foo') - ->willReturnOnConsecutiveCalls(1, 2, 3); - - $this->assertEquals(1, $mock->foo()); - $this->assertEquals(2, $mock->foo()); - $this->assertEquals(3, $mock->foo()); - } - - public function testWillReturnByReference(): void - { - $mock = $this->getMockBuilder(stdClass::class) - ->setMethods(['foo']) - ->getMock(); - - $mock->method('foo') - ->willReturnReference($value); - - $this->assertNull($mock->foo()); - $value = 'foo'; - $this->assertSame('foo', $mock->foo()); - $value = 'bar'; - $this->assertSame('bar', $mock->foo()); - } - - public function testWillFailWhenTryingToPerformExpectationUnconfigurableMethod(): void - { - $matcherCollection = new InvocationHandler([], false); - $invocationMocker = new InvocationMocker( - $matcherCollection, - new Matcher($this->any()) - ); - - $this->expectException(RuntimeException::class); - $invocationMocker->method('someMethod'); - } - - public function testWillReturnFailsWhenTryingToReturnSingleIncompatibleValue(): void - { - $mock = $this->getMockBuilder(ClassWithAllPossibleReturnTypes::class) - ->getMock(); - - $invocationMocker = $mock->method('methodWithBoolReturnTypeDeclaration'); - - $this->expectException(IncompatibleReturnValueException::class); - $this->expectExceptionMessage('Method methodWithBoolReturnTypeDeclaration may not return value of type integer, its return declaration is "bool"'); - $invocationMocker->willReturn(1); - } - - public function testWillReturnFailsWhenTryingToReturnIncompatibleValueByConstraint(): void - { - $mock = $this->getMockBuilder(ClassWithAllPossibleReturnTypes::class) - ->getMock(); - - $invocationMocker = $mock->method(new IsEqual('methodWithBoolReturnTypeDeclaration')); - - $this->expectException(IncompatibleReturnValueException::class); - $this->expectExceptionMessage('Method methodWithBoolReturnTypeDeclaration may not return value of type integer, its return declaration is "bool"'); - $invocationMocker->willReturn(1); - } - - public function testWillReturnFailsWhenTryingToReturnAtLeastOneIncompatibleValue(): void - { - $mock = $this->getMockBuilder(ClassWithAllPossibleReturnTypes::class) - ->getMock(); - - $invocationMocker = $mock->method('methodWithBoolReturnTypeDeclaration'); - - $this->expectException(IncompatibleReturnValueException::class); - $this->expectExceptionMessage('Method methodWithBoolReturnTypeDeclaration may not return value of type integer, its return declaration is "bool"'); - $invocationMocker->willReturn(true, 1); - } - - public function testWillReturnFailsWhenTryingToReturnSingleIncompatibleClass(): void - { - $mock = $this->getMockBuilder(ClassWithAllPossibleReturnTypes::class) - ->getMock(); - - $invocationMocker = $mock->method('methodWithClassReturnTypeDeclaration'); - - $this->expectException(IncompatibleReturnValueException::class); - $this->expectExceptionMessage('Method methodWithClassReturnTypeDeclaration may not return value of type Foo, its return declaration is "stdClass"'); - $invocationMocker->willReturn(new Foo()); - } - - public function testWillReturnAllowsMatchersForMultipleMethodsWithDifferentReturnTypes(): void - { - /** @var ClassWithAllPossibleReturnTypes|\PHPUnit\Framework\MockObject\MockObject $mock */ - $mock = $this->getMockBuilder(ClassWithAllPossibleReturnTypes::class) - ->getMock(); - - $invocationMocker = $mock->method(new \PHPUnit\Framework\Constraint\IsAnything()); - $invocationMocker->willReturn(true, 1); - - $this->assertEquals(true, $mock->methodWithBoolReturnTypeDeclaration()); - $this->assertEquals(1, $mock->methodWithIntReturnTypeDeclaration()); - } - - public function testWillReturnValidType(): void - { - $mock = $this->getMockBuilder(ClassWithAllPossibleReturnTypes::class) - ->getMock(); - - $mock->method('methodWithBoolReturnTypeDeclaration') - ->willReturn(true); - - $this->assertEquals(true, $mock->methodWithBoolReturnTypeDeclaration()); - } - - public function testWillReturnValidTypeForLowercaseCall(): void - { - $mock = $this->getMockBuilder(ClassWithAllPossibleReturnTypes::class) - ->getMock(); - - $mock->method('methodWithBoolReturnTypeDeclaration') - ->willReturn(true); - - $this->assertEquals(true, $mock->methodwithboolreturntypedeclaration()); - } - - public function testWillReturnValidTypeForLowercaseMethod(): void - { - $mock = $this->getMockBuilder(ClassWithAllPossibleReturnTypes::class) - ->getMock(); - - $mock->method('methodwithboolreturntypedeclaration') - ->willReturn(true); - - $this->assertEquals(true, $mock->methodWithBoolReturnTypeDeclaration()); - } - - /** - * @see https://github.com/sebastianbergmann/phpunit/issues/3602 - */ - public function testWillReturnFailsWhenTryingToReturnValueFromVoidMethod(): void - { - /** @var ClassWithAllPossibleReturnTypes|\PHPUnit\Framework\MockObject\MockObject $out */ - $out = $this->createMock(ClassWithAllPossibleReturnTypes::class); - $method = $out->method('methodWithVoidReturnTypeDeclaration'); - - $this->expectException(IncompatibleReturnValueException::class); - $this->expectExceptionMessage('Method methodWithVoidReturnTypeDeclaration may not return value of type boolean, its return declaration is "void"'); - $method->willReturn(true); - } - - public function testExpectationsAreEnabledByPreviousMethodCallWhenChainedWithAfter(): void - { - $mock = $this->createMock(ClassWithImplicitProtocol::class); - - $mock->expects($this->once()) - ->method('firstCall') - ->id($fristCallId = 'first-call-id'); - - $mock->expects($this->once()) - ->method('secondCall') - ->after($fristCallId); - - $mock->firstCall(); - $mock->secondCall(); - } - - public function testExpectationsAreNotTriggeredUntilPreviousMethodWasCalled(): void - { - $mock = $this->createMock(ClassWithImplicitProtocol::class); - - $mock->expects($this->once()) - ->method('firstCall') - ->id($firstCallId = 'first-call-id'); - - $mock->expects($this->once()) - ->method('secondCall') - ->after($firstCallId); - - $mock->secondCall(); - $mock->firstCall(); - $mock->secondCall(); - } - - public function testWillReturnAlreadyInstantiatedStubs(): void - { - $mock = $this->getMockBuilder(stdClass::class) - ->setMethods(['foo', 'bar']) - ->getMock(); - - $mock->method('foo') - ->willReturn(new ReturnStub('foo')); - - $mock->method('bar') - ->willReturn(new ReturnSelf()); - - $this->assertSame('foo', $mock->foo()); - $this->assertSame($mock, $mock->bar()); - } -} diff --git a/tests/unit/Framework/MockObject/ConfigurableMethodTest.php b/tests/unit/Framework/MockObject/ConfigurableMethodTest.php deleted file mode 100644 index efe2b443eae..00000000000 --- a/tests/unit/Framework/MockObject/ConfigurableMethodTest.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use PHPUnit\Framework\TestCase; -use SebastianBergmann\Type\Type; - -final class ConfigurableMethodTest extends TestCase -{ - public function testMethodMayReturnAssignableValue(): void - { - $assignableType = $this->createMock(Type::class); - $assignableType->method('isAssignable') - ->willReturn(true); - $configurable = new ConfigurableMethod('foo', $assignableType); - $this->assertTrue($configurable->mayReturn('everything-is-valid')); - } - - public function testMethodMayNotReturnUnassignableValue(): void - { - $unassignableType = $this->createMock(Type::class); - $unassignableType->method('isAssignable') - ->willReturn(false); - $configurable = new ConfigurableMethod('foo', $unassignableType); - $this->assertFalse($configurable->mayReturn('everything-is-invalid')); - } -} diff --git a/tests/unit/Framework/MockObject/ConfigurableMethodsTest.php b/tests/unit/Framework/MockObject/ConfigurableMethodsTest.php deleted file mode 100644 index 1bfcefcfe66..00000000000 --- a/tests/unit/Framework/MockObject/ConfigurableMethodsTest.php +++ /dev/null @@ -1,37 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use PHPUnit\Framework\TestCase; -use PHPUnit\TestFixture\MockObject\AnotherClassUsingConfigurableMethods; -use PHPUnit\TestFixture\MockObject\ClassUsingConfigurableMethods; -use PHPUnit\TestFixture\MockObject\ReinitializeConfigurableMethods; -use SebastianBergmann\Type\SimpleType; - -final class ConfigurableMethodsTest extends TestCase -{ - public function testTwoClassesUsingConfigurableMethodsDontInterfere(): void - { - $configurableMethodsA = [new ConfigurableMethod('foo', SimpleType::fromValue('boolean', false))]; - $configurableMethodsB = []; - ClassUsingConfigurableMethods::__phpunit_initConfigurableMethods(...$configurableMethodsA); - AnotherClassUsingConfigurableMethods::__phpunit_initConfigurableMethods(...$configurableMethodsB); - - $this->assertSame($configurableMethodsA, ClassUsingConfigurableMethods::getConfigurableMethods()); - $this->assertSame($configurableMethodsB, AnotherClassUsingConfigurableMethods::getConfigurableMethods()); - } - - public function testConfigurableMethodsAreImmutable(): void - { - ReinitializeConfigurableMethods::__phpunit_initConfigurableMethods(); - $this->expectException(ConfigurableMethodsAlreadyInitializedException::class); - ReinitializeConfigurableMethods::__phpunit_initConfigurableMethods(); - } -} diff --git a/tests/unit/Framework/MockObject/Creation/CreateConfiguredMockTest.php b/tests/unit/Framework/MockObject/Creation/CreateConfiguredMockTest.php new file mode 100644 index 00000000000..51ba60a16ce --- /dev/null +++ b/tests/unit/Framework/MockObject/Creation/CreateConfiguredMockTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\MockObject\InterfaceWithReturnTypeDeclaration; + +#[Group('test-doubles')] +#[Group('test-doubles/creation')] +#[Group('test-doubles/mock-object')] +#[Medium] +#[TestDox('createConfiguredMock()')] +final class CreateConfiguredMockTest extends TestCase +{ + public function testCreatesMockObjectForInterfaceOrExtendableClassWithReturnValueConfigurationForMultipleMethods(): void + { + $double = $this->createConfiguredMock( + InterfaceWithReturnTypeDeclaration::class, + [ + 'doSomething' => true, + 'doSomethingElse' => 1, + ], + ); + + $this->assertTrue($double->doSomething()); + $this->assertSame(1, $double->doSomethingElse(0)); + } +} diff --git a/tests/unit/Framework/MockObject/Creation/CreateConfiguredStubTest.php b/tests/unit/Framework/MockObject/Creation/CreateConfiguredStubTest.php new file mode 100644 index 00000000000..445947fd291 --- /dev/null +++ b/tests/unit/Framework/MockObject/Creation/CreateConfiguredStubTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\MockObject\InterfaceWithReturnTypeDeclaration; + +#[Group('test-doubles')] +#[Group('test-doubles/creation')] +#[Group('test-doubles/test-stub')] +#[Medium] +#[TestDox('createConfiguredStub()')] +final class CreateConfiguredStubTest extends TestCase +{ + public function testCreatesTestStubForInterfaceOrExtendableClassWithReturnValueConfigurationForMultipleMethods(): void + { + $double = $this->createConfiguredStub( + InterfaceWithReturnTypeDeclaration::class, + [ + 'doSomething' => true, + 'doSomethingElse' => 1, + ], + ); + + $this->assertTrue($double->doSomething()); + $this->assertSame(1, $double->doSomethingElse(0)); + } +} diff --git a/tests/unit/Framework/MockObject/Creation/CreateMockForIntersectionOfInterfacesTest.php b/tests/unit/Framework/MockObject/Creation/CreateMockForIntersectionOfInterfacesTest.php new file mode 100644 index 00000000000..b7a09cfed5d --- /dev/null +++ b/tests/unit/Framework/MockObject/Creation/CreateMockForIntersectionOfInterfacesTest.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\MockObject\Generator\RuntimeException as GeneratorRuntimeException; +use PHPUnit\Framework\MockObject\Generator\UnknownInterfaceException; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\MockObject\AnInterface; +use PHPUnit\TestFixture\MockObject\AnotherInterface; +use PHPUnit\TestFixture\MockObject\AnotherInterfaceThatDoesSomething; + +#[Group('test-doubles')] +#[Group('test-doubles/creation')] +#[Group('test-doubles/mock-object')] +#[Medium] +#[TestDox('createMockForIntersectionOfInterfaces()')] +final class CreateMockForIntersectionOfInterfacesTest extends TestCase +{ + public function testCreatesMockObjectForIntersectionOfInterfaces(): void + { + $double = $this->createMockForIntersectionOfInterfaces([AnInterface::class, AnotherInterface::class]); + + $this->assertInstanceOf(AnInterface::class, $double); + $this->assertInstanceOf(AnotherInterface::class, $double); + $this->assertInstanceOf(Stub::class, $double); + + $double->method('doSomething')->willReturn(true); + $double->method('doSomethingElse')->willReturn(true); + + $this->assertTrue($double->doSomething()); + $this->assertTrue($double->doSomethingElse()); + } + + public function testReturnValueGenerationIsEnabledByDefault(): void + { + $double = $this->createMockForIntersectionOfInterfaces([AnInterface::class, AnotherInterface::class]); + + $this->assertFalse($double->doSomething()); + $this->assertNull($double->doSomethingElse()); + } + + public function testCannotCreateMockObjectForIntersectionOfInterfacesWhenLessThanTwoInterfacesAreSpecified(): void + { + $this->expectException(GeneratorRuntimeException::class); + $this->expectExceptionMessage('At least two interfaces must be specified'); + + $this->createMockForIntersectionOfInterfaces([AnInterface::class]); + } + + public function testCannotCreateMockObjectForIntersectionOfUnknownInterfaces(): void + { + $this->expectException(UnknownInterfaceException::class); + + $this->createMockForIntersectionOfInterfaces(['DoesNotExist', 'DoesNotExist']); + } + + public function testCannotCreateMockObjectForIntersectionOfInterfacesThatDeclareTheSameMethod(): void + { + $this->expectException(GeneratorRuntimeException::class); + $this->expectExceptionMessage('Interfaces must not declare the same method'); + + $this->createMockForIntersectionOfInterfaces([AnInterface::class, AnotherInterfaceThatDoesSomething::class]); + } +} diff --git a/tests/unit/Framework/MockObject/Creation/CreateMockForIntersectionOfInterfacesWithDisabledReturnValueGenerationTest.php b/tests/unit/Framework/MockObject/Creation/CreateMockForIntersectionOfInterfacesWithDisabledReturnValueGenerationTest.php new file mode 100644 index 00000000000..a72711110b3 --- /dev/null +++ b/tests/unit/Framework/MockObject/Creation/CreateMockForIntersectionOfInterfacesWithDisabledReturnValueGenerationTest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\Attributes\DisableReturnValueGenerationForTestDoubles; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\MockObject\AnInterface; +use PHPUnit\TestFixture\MockObject\AnotherInterface; + +#[Group('test-doubles')] +#[Group('test-doubles/creation')] +#[Group('test-doubles/mock-object')] +#[Medium] +#[TestDox('createMockForIntersectionOfInterfaces()')] +#[DisableReturnValueGenerationForTestDoubles] +final class CreateMockForIntersectionOfInterfacesWithDisabledReturnValueGenerationTest extends TestCase +{ + public function testReturnValueGenerationCanBeDisabledWithAttribute(): void + { + $double = $this->createMockForIntersectionOfInterfaces([AnInterface::class, AnotherInterface::class]); + + $this->expectException(ReturnValueNotConfiguredException::class); + + $double->doSomething(); + } +} diff --git a/tests/unit/Framework/MockObject/Creation/CreateMockTest.php b/tests/unit/Framework/MockObject/Creation/CreateMockTest.php new file mode 100644 index 00000000000..4bb54486f74 --- /dev/null +++ b/tests/unit/Framework/MockObject/Creation/CreateMockTest.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\MockObject\Generator\ClassIsEnumerationException; +use PHPUnit\Framework\MockObject\Generator\ClassIsFinalException; +use PHPUnit\Framework\MockObject\Generator\UnknownTypeException; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\MockObject\AnInterface; +use PHPUnit\TestFixture\MockObject\Enumeration; +use PHPUnit\TestFixture\MockObject\ExtendableClass; +use PHPUnit\TestFixture\MockObject\ExtendableReadonlyClass; +use PHPUnit\TestFixture\MockObject\FinalClass; + +#[Group('test-doubles')] +#[Group('test-doubles/creation')] +#[Group('test-doubles/mock-object')] +#[Medium] +#[TestDox('createMock()')] +final class CreateMockTest extends TestCase +{ + public function testCreatesMockObjectForInterface(): void + { + $double = $this->createMock(AnInterface::class); + + $this->assertInstanceOf(AnInterface::class, $double); + $this->assertInstanceOf(Stub::class, $double); + $this->assertInstanceOf(MockObject::class, $double); + } + + public function testCreatesMockObjectForExtendableClass(): void + { + $double = $this->createMock(ExtendableClass::class); + + $this->assertInstanceOf(ExtendableClass::class, $double); + $this->assertInstanceOf(Stub::class, $double); + $this->assertInstanceOf(MockObject::class, $double); + } + + public function testCreatesMockObjectForExtendableReadonlyClass(): void + { + $double = $this->createMock(ExtendableReadonlyClass::class); + + $this->assertInstanceOf(ExtendableReadonlyClass::class, $double); + $this->assertInstanceOf(Stub::class, $double); + $this->assertInstanceOf(MockObject::class, $double); + } + + public function testReturnValueGenerationIsEnabledByDefault(): void + { + $double = $this->createMock(AnInterface::class); + + $this->assertFalse($double->doSomething()); + } + + public function testCannotCreateMockObjectForFinalClass(): void + { + $this->expectException(ClassIsFinalException::class); + + $this->createMock(FinalClass::class); + } + + public function testCannotCreateMockObjectForEnumeration(): void + { + $this->expectException(ClassIsEnumerationException::class); + + $this->createMock(Enumeration::class); + } + + public function testCannotCreateMockObjectForUnknownType(): void + { + $this->expectException(UnknownTypeException::class); + + $this->createMock('this\does\not\exist'); + } +} diff --git a/tests/unit/Framework/MockObject/Creation/CreateMockWithDisabledReturnValueGenerationTest.php b/tests/unit/Framework/MockObject/Creation/CreateMockWithDisabledReturnValueGenerationTest.php new file mode 100644 index 00000000000..41b69a7d555 --- /dev/null +++ b/tests/unit/Framework/MockObject/Creation/CreateMockWithDisabledReturnValueGenerationTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\Attributes\DisableReturnValueGenerationForTestDoubles; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\MockObject\AnInterface; + +#[Group('test-doubles')] +#[Group('test-doubles/creation')] +#[Group('test-doubles/mock-object')] +#[Medium] +#[TestDox('createMock()')] +#[DisableReturnValueGenerationForTestDoubles] +final class CreateMockWithDisabledReturnValueGenerationTest extends TestCase +{ + public function testReturnValueGenerationCanBeDisabledWithAttribute(): void + { + $double = $this->createMock(AnInterface::class); + + $this->expectException(ReturnValueNotConfiguredException::class); + + $double->doSomething(); + } +} diff --git a/tests/unit/Framework/MockObject/Creation/CreatePartialMockTest.php b/tests/unit/Framework/MockObject/Creation/CreatePartialMockTest.php new file mode 100644 index 00000000000..10644401e3b --- /dev/null +++ b/tests/unit/Framework/MockObject/Creation/CreatePartialMockTest.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\MockObject\Generator\DuplicateMethodException; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\MockObject\ExtendableClass; +use ReflectionProperty; + +#[Group('test-doubles')] +#[Group('test-doubles/creation')] +#[Group('test-doubles/mock-object')] +#[Medium] +#[TestDox('createPartialMock()')] +final class CreatePartialMockTest extends TestCase +{ + public function testCreatesPartialMockObjectForExtendableClass(): void + { + $double = $this->createPartialMock(ExtendableClass::class, ['doSomethingElse']); + + $double->expects($this->once())->method('doSomethingElse')->willReturn(true); + + $this->assertTrue($double->doSomething()); + } + + public function testCannotCreatePartialMockObjectForExtendableClassWithDuplicateMethods(): void + { + $this->expectException(DuplicateMethodException::class); + $this->expectExceptionMessage('Cannot double using a method list that contains duplicates: "doSomethingElse, doSomethingElse" (duplicate: "doSomethingElse")'); + + $this->createPartialMock(ExtendableClass::class, ['doSomethingElse', 'doSomethingElse']); + } + + public function testMethodOfPartialMockThatIsNotConfigurableCannotBeConfigured(): void + { + $double = $this->createPartialMock(ExtendableClass::class, ['doSomethingElse']); + + try { + $double->expects($this->once())->method('doSomething')->willReturn(true); + } catch (MethodCannotBeConfiguredException $e) { + $this->assertSame('Trying to configure method "doSomething" which cannot be configured because it does not exist, has not been specified, is final, or is static', $e->getMessage()); + + return; + } finally { + $this->resetMockObjects(); + } + + $this->fail(); + } + + public function testMethodOfPartialMockThatDoesNotExistCannotBeConfigured(): void + { + $this->expectException(CannotUseOnlyMethodsException::class); + $this->expectExceptionMessage('Trying to configure method "doesNotExist" with onlyMethods(), but it does not exist in class "PHPUnit\TestFixture\MockObject\ExtendableClass"'); + + $this->createPartialMock(ExtendableClass::class, ['doesNotExist']); + } + + private function resetMockObjects(): void + { + new ReflectionProperty(TestCase::class, 'mockObjects')->setValue($this, []); + } +} diff --git a/tests/unit/Framework/MockObject/Creation/CreatePartialMockWithDisabledReturnValueGenerationTest.php b/tests/unit/Framework/MockObject/Creation/CreatePartialMockWithDisabledReturnValueGenerationTest.php new file mode 100644 index 00000000000..34672c3bd1e --- /dev/null +++ b/tests/unit/Framework/MockObject/Creation/CreatePartialMockWithDisabledReturnValueGenerationTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\Attributes\DisableReturnValueGenerationForTestDoubles; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\MockObject\ExtendableClass; + +#[Group('test-doubles')] +#[Group('test-doubles/creation')] +#[Group('test-doubles/mock-object')] +#[Medium] +#[TestDox('createPartialMock()')] +#[DisableReturnValueGenerationForTestDoubles] +final class CreatePartialMockWithDisabledReturnValueGenerationTest extends TestCase +{ + public function testReturnValueGenerationCanBeDisabledWithAttribute(): void + { + $double = $this->createPartialMock(ExtendableClass::class, ['doSomething']); + + $this->expectException(ReturnValueNotConfiguredException::class); + + $double->doSomething(); + } +} diff --git a/tests/unit/Framework/MockObject/Creation/CreateStubForIntersectionOfInterfacesTest.php b/tests/unit/Framework/MockObject/Creation/CreateStubForIntersectionOfInterfacesTest.php new file mode 100644 index 00000000000..758425209a7 --- /dev/null +++ b/tests/unit/Framework/MockObject/Creation/CreateStubForIntersectionOfInterfacesTest.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\MockObject\Generator\RuntimeException as GeneratorRuntimeException; +use PHPUnit\Framework\MockObject\Generator\UnknownInterfaceException; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\MockObject\AnInterface; +use PHPUnit\TestFixture\MockObject\AnotherInterface; +use PHPUnit\TestFixture\MockObject\AnotherInterfaceThatDoesSomething; +use PHPUnit\TestFixture\MockObject\ExtendableClass; + +#[Group('test-doubles')] +#[Group('test-doubles/creation')] +#[Group('test-doubles/test-stub')] +#[Medium] +#[TestDox('createStubForIntersectionOfInterfaces()')] +final class CreateStubForIntersectionOfInterfacesTest extends TestCase +{ + public function testCreatesTestStubForIntersectionOfInterfaces(): void + { + $double = $this->createStubForIntersectionOfInterfaces([AnInterface::class, AnotherInterface::class]); + + $this->assertInstanceOf(AnInterface::class, $double); + $this->assertInstanceOf(AnotherInterface::class, $double); + $this->assertInstanceOf(Stub::class, $double); + + $double->method('doSomething')->willReturn(true); + $double->method('doSomethingElse')->willReturn(true); + + $this->assertTrue($double->doSomething()); + $this->assertTrue($double->doSomethingElse()); + } + + public function testReturnValueGenerationIsEnabledByDefault(): void + { + $double = $this->createStubForIntersectionOfInterfaces([AnInterface::class, AnotherInterface::class]); + + $this->assertFalse($double->doSomething()); + $this->assertNull($double->doSomethingElse()); + } + + public function testCannotCreateTestStubForIntersectionOfInterfacesWhenLessThanTwoInterfacesAreSpecified(): void + { + $this->expectException(GeneratorRuntimeException::class); + $this->expectExceptionMessage('At least two interfaces must be specified'); + + $this->createStubForIntersectionOfInterfaces([AnInterface::class]); + } + + public function testCannotCreateTestStubForIntersectionOfUnknownInterfaces(): void + { + $this->expectException(UnknownInterfaceException::class); + + $this->createStubForIntersectionOfInterfaces(['DoesNotExist', 'DoesNotExist']); + } + + public function testCannotCreateTestStubForIntersectionOfInterfacesThatDeclareTheSameMethod(): void + { + $this->expectException(GeneratorRuntimeException::class); + $this->expectExceptionMessage('Interfaces must not declare the same method'); + + $this->createStubForIntersectionOfInterfaces([AnInterface::class, AnotherInterfaceThatDoesSomething::class]); + } + + public function testCannotCreateTestStubForIntersectionOfInterfacesWhenClassIsUsed(): void + { + $this->expectException(UnknownInterfaceException::class); + $this->expectExceptionMessage('Interface "PHPUnit\TestFixture\MockObject\ExtendableClass" does not exist'); + + $this->createStubForIntersectionOfInterfaces([AnInterface::class, ExtendableClass::class]); + } +} diff --git a/tests/unit/Framework/MockObject/Creation/CreateStubForIntersectionOfInterfacesWithDisabledReturnValueGenerationTest.php b/tests/unit/Framework/MockObject/Creation/CreateStubForIntersectionOfInterfacesWithDisabledReturnValueGenerationTest.php new file mode 100644 index 00000000000..4049f87e452 --- /dev/null +++ b/tests/unit/Framework/MockObject/Creation/CreateStubForIntersectionOfInterfacesWithDisabledReturnValueGenerationTest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\Attributes\DisableReturnValueGenerationForTestDoubles; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\MockObject\AnInterface; +use PHPUnit\TestFixture\MockObject\AnotherInterface; + +#[Group('test-doubles')] +#[Group('test-doubles/creation')] +#[Group('test-doubles/test-stub')] +#[Medium] +#[TestDox('createStubForIntersectionOfInterfaces()')] +#[DisableReturnValueGenerationForTestDoubles] +final class CreateStubForIntersectionOfInterfacesWithDisabledReturnValueGenerationTest extends TestCase +{ + public function testReturnValueGenerationCanBeDisabledWithAttribute(): void + { + $double = $this->createStubForIntersectionOfInterfaces([AnInterface::class, AnotherInterface::class]); + + $this->expectException(ReturnValueNotConfiguredException::class); + + $double->doSomething(); + } +} diff --git a/tests/unit/Framework/MockObject/Creation/CreateStubTest.php b/tests/unit/Framework/MockObject/Creation/CreateStubTest.php new file mode 100644 index 00000000000..0226009ab78 --- /dev/null +++ b/tests/unit/Framework/MockObject/Creation/CreateStubTest.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\MockObject\Generator\ClassIsEnumerationException; +use PHPUnit\Framework\MockObject\Generator\ClassIsFinalException; +use PHPUnit\Framework\MockObject\Generator\UnknownTypeException; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\MockObject\AnInterface; +use PHPUnit\TestFixture\MockObject\Enumeration; +use PHPUnit\TestFixture\MockObject\ExtendableClass; +use PHPUnit\TestFixture\MockObject\ExtendableReadonlyClass; +use PHPUnit\TestFixture\MockObject\FinalClass; + +#[Group('test-doubles')] +#[Group('test-doubles/creation')] +#[Group('test-doubles/test-stub')] +#[Medium] +#[TestDox('createStub()')] +final class CreateStubTest extends TestCase +{ + public function testCreatesTestStubForInterface(): void + { + $double = $this->createStub(AnInterface::class); + + $this->assertInstanceOf(AnInterface::class, $double); + $this->assertInstanceOf(Stub::class, $double); + } + + public function testCreatesTestStubForExtendableClass(): void + { + $double = $this->createStub(ExtendableClass::class); + + $this->assertInstanceOf(ExtendableClass::class, $double); + $this->assertInstanceOf(Stub::class, $double); + } + + public function testCreatesTestStubForExtendableReadonlyClass(): void + { + $double = $this->createStub(ExtendableReadonlyClass::class); + + $this->assertInstanceOf(ExtendableReadonlyClass::class, $double); + $this->assertInstanceOf(Stub::class, $double); + } + + public function testReturnValueGenerationIsEnabledByDefault(): void + { + $double = $this->createStub(AnInterface::class); + + $this->assertFalse($double->doSomething()); + } + + public function testCannotCreateTestStubForFinalClass(): void + { + $this->expectException(ClassIsFinalException::class); + + $this->createStub(FinalClass::class); + } + + public function testCannotCreateTestStubForEnumeration(): void + { + $this->expectException(ClassIsEnumerationException::class); + + $this->createStub(Enumeration::class); + } + + public function testCannotCreateTestStubForUnknownType(): void + { + $this->expectException(UnknownTypeException::class); + + $this->createStub('this\does\not\exist'); + } +} diff --git a/tests/unit/Framework/MockObject/Creation/CreateStubWithDisabledReturnValueGenerationTest.php b/tests/unit/Framework/MockObject/Creation/CreateStubWithDisabledReturnValueGenerationTest.php new file mode 100644 index 00000000000..eff448ae4f7 --- /dev/null +++ b/tests/unit/Framework/MockObject/Creation/CreateStubWithDisabledReturnValueGenerationTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\Attributes\DisableReturnValueGenerationForTestDoubles; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\MockObject\AnInterface; + +#[Group('test-doubles')] +#[Group('test-doubles/creation')] +#[Group('test-doubles/test-stub')] +#[Medium] +#[TestDox('createStub()')] +#[DisableReturnValueGenerationForTestDoubles] +final class CreateStubWithDisabledReturnValueGenerationTest extends TestCase +{ + public function testReturnValueGenerationCanBeDisabledWithAttribute(): void + { + $double = $this->createStub(AnInterface::class); + + $this->expectException(ReturnValueNotConfiguredException::class); + + $double->doSomething(); + } +} diff --git a/tests/unit/Framework/MockObject/Creation/MockBuilderTest.php b/tests/unit/Framework/MockObject/Creation/MockBuilderTest.php new file mode 100644 index 00000000000..8edcb05c500 --- /dev/null +++ b/tests/unit/Framework/MockObject/Creation/MockBuilderTest.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function md5; +use function mt_rand; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\IgnorePhpunitDeprecations; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\MockObject\Generator\DuplicateMethodException; +use PHPUnit\Framework\MockObject\Generator\InvalidMethodNameException; +use PHPUnit\Framework\MockObject\Generator\NameAlreadyInUseException; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\MockObject\ExtendableClass; +use PHPUnit\TestFixture\MockObject\ExtendableClassCallingMethodInConstructor; +use PHPUnit\TestFixture\MockObject\ExtendableClassWithConstructorArguments; +use PHPUnit\TestFixture\MockObject\InterfaceWithReturnTypeDeclaration; + +#[CoversClass(MockBuilder::class)] +#[CoversClass(DuplicateMethodException::class)] +#[CoversClass(InvalidMethodNameException::class)] +#[CoversClass(NameAlreadyInUseException::class)] +#[Group('test-doubles')] +#[Group('test-doubles/creation')] +#[Group('test-doubles/mock-object')] +#[Medium] +final class MockBuilderTest extends TestCase +{ + #[TestDox('setMockClassName() can be used to configure the name of the mock object class')] + public function testCanCreateMockObjectWithSpecifiedClassName(): void + { + $className = 'random_' . md5((string) mt_rand()); + + $double = $this->getMockBuilder(InterfaceWithReturnTypeDeclaration::class) + ->setMockClassName($className) + ->getMock(); + + $this->assertSame($className, $double::class); + } + + #[TestDox('setMockClassName() cannot be used to configure the name of the mock object class when a class with that name already exists')] + public function testCannotCreateMockObjectWithSpecifiedClassNameWhenClassWithThatNameAlreadyExists(): void + { + $this->expectException(NameAlreadyInUseException::class); + + $this->getMockBuilder(InterfaceWithReturnTypeDeclaration::class) + ->setMockClassName(__CLASS__) + ->getMock(); + } + + #[TestDox('setConstructorArgs() can be used to configure constructor arguments for a partially mocked class')] + public function testConstructorArgumentsCanBeConfiguredForPartiallyMockedClass(): void + { + $value = 'string'; + + $double = $this->getMockBuilder(ExtendableClassWithConstructorArguments::class) + ->enableOriginalConstructor() + ->setConstructorArgs([$value]) + ->onlyMethods([]) + ->getMock(); + + $this->assertSame($value, $double->value()); + } + + #[TestDox('onlyMethods() can be used to configure which methods should be doubled')] + public function testCreatesPartialMockObjectForExtendableClass(): void + { + $double = $this->getMockBuilder(ExtendableClass::class) + ->onlyMethods(['doSomethingElse']) + ->getMock(); + + $double->expects($this->once())->method('doSomethingElse')->willReturn(true); + + $this->assertTrue($double->doSomething()); + } + + #[IgnorePhpunitDeprecations] + public function testDefaultBehaviourCanBeConfiguredExplicitly(): void + { + $double = $this->getMockBuilder(ExtendableClass::class) + ->enableOriginalConstructor() + ->enableOriginalClone() + ->enableAutoReturnValueGeneration() + ->getMock(); + + $this->assertTrue($double->constructorCalled); + } + + #[TestDox('Mocked methods can be called from the original constructor of a partially mocked class')] + public function testOnlyMethodCalledInConstructorWorks(): void + { + $double = $this->getMockBuilder(ExtendableClassCallingMethodInConstructor::class) + ->onlyMethods(['reset']) + ->getMock(); + + $double->expects($this->once())->method('reset'); + + $double->second(); + } +} diff --git a/tests/unit/Framework/MockObject/Generator/PropertyTest.php b/tests/unit/Framework/MockObject/Generator/PropertyTest.php new file mode 100644 index 00000000000..1beab059452 --- /dev/null +++ b/tests/unit/Framework/MockObject/Generator/PropertyTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use SebastianBergmann\Type\Type; + +#[CoversClass(HookedProperty::class)] +#[Group('test-doubles')] +#[Small] +final class PropertyTest extends TestCase +{ + public function testHasName(): void + { + $name = 'property-name'; + + $property = new HookedProperty($name, Type::fromName('string', false), false, false); + + $this->assertSame($name, $property->name()); + } + + public function testHasType(): void + { + $type = Type::fromName('string', false); + + $property = new HookedProperty('property-name', $type, false, false); + + $this->assertSame($type, $property->type()); + } + + public function testMayHaveGetHook(): void + { + $property = new HookedProperty('property-name', Type::fromName('string', false), true, false); + + $this->assertTrue($property->hasGetHook()); + } + + public function testMayNotHaveGetHook(): void + { + $property = new HookedProperty('property-name', Type::fromName('string', false), false, false); + + $this->assertFalse($property->hasGetHook()); + } + + public function testMayHaveSetHook(): void + { + $property = new HookedProperty('property-name', Type::fromName('string', false), false, true); + + $this->assertTrue($property->hasSetHook()); + } + + public function testMayNotHaveSetHook(): void + { + $property = new HookedProperty('property-name', Type::fromName('string', false), false, false); + + $this->assertFalse($property->hasSetHook()); + } +} diff --git a/tests/unit/Framework/MockObject/GeneratorTest.php b/tests/unit/Framework/MockObject/GeneratorTest.php deleted file mode 100644 index ab82ad75bdd..00000000000 --- a/tests/unit/Framework/MockObject/GeneratorTest.php +++ /dev/null @@ -1,314 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\MockObject\Generator; -use PHPUnit\Framework\MockObject\MockObject; -use PHPUnit\Framework\TestCase; -use PHPUnit\TestFixture\AbstractTrait; -use PHPUnit\TestFixture\AnInterfaceWithReturnType; -use PHPUnit\TestFixture\ClassWithVariadicArgumentMethod; -use PHPUnit\TestFixture\ExceptionWithThrowable; -use PHPUnit\TestFixture\InterfaceWithSemiReservedMethodName; -use PHPUnit\TestFixture\MockObject\AbstractMockTestClass; -use PHPUnit\TestFixture\SingletonClass; - -/** - * @covers \PHPUnit\Framework\MockObject\Generator - * - * @uses \PHPUnit\Framework\MockObject\InvocationHandler - * @uses \PHPUnit\Framework\MockObject\Builder\InvocationMocker - * @uses \PHPUnit\Framework\MockObject\Invocation - * @uses \PHPUnit\Framework\MockObject\Matcher - * @uses \PHPUnit\Framework\MockObject\Rule\InvocationOrder - * @uses \PHPUnit\Framework\MockObject\Rule\MethodName - * @uses \PHPUnit\Framework\MockObject\Stub\ReturnStub - * @uses \PHPUnit\Framework\MockObject\Rule\InvokedCount - * - * @small - */ -final class GeneratorTest extends TestCase -{ - /** - * @var Generator - */ - private $generator; - - protected function setUp(): void - { - $this->generator = new Generator; - } - - public function testGetMockThrowsExceptionWhenInvalidFunctionNameIsPassedInAsAFunctionToMock(): void - { - $this->expectException(\PHPUnit\Framework\MockObject\RuntimeException::class); - - $this->generator->getMock(stdClass::class, [0]); - } - - public function testGetMockThrowsExceptionWithInvalidMethods(): void - { - $this->expectException(\PHPUnit\Framework\InvalidArgumentException::class); - - $this->generator->getMock(stdClass::class, false); - } - - public function testGetMockThrowsExceptionWithNonExistingClass(): void - { - $this->expectException(\PHPUnit\Framework\MockObject\RuntimeException::class); - - $this->assertFalse(\class_exists('Tux')); - - $this->generator->getMock('Tux', [], [], '', true, true, false, true, false, null, false); - } - - public function testGetMockThrowsExceptionWithNonExistingClasses(): void - { - $this->expectException(\PHPUnit\Framework\MockObject\RuntimeException::class); - - $this->assertFalse(\class_exists('Tux')); - - $this->generator->getMock('Tux', [], [], '', true, true, false, true, false, null, false); - } - - public function testGetMockThrowsExceptionWithExistingClassAsMockName(): void - { - $this->expectException(\PHPUnit\Framework\MockObject\RuntimeException::class); - - $this->generator->getMock(stdClass::class, [], [], RuntimeException::class); - } - - public function testGetMockCanCreateNonExistingFunctions(): void - { - $mock = $this->generator->getMock(stdClass::class, ['testFunction']); - - $this->assertTrue(\method_exists($mock, 'testFunction')); - } - - public function testGetMockGeneratorThrowsException(): void - { - $this->expectException(\PHPUnit\Framework\MockObject\RuntimeException::class); - $this->expectExceptionMessage('duplicates: "foo, bar, foo" (duplicate: "foo")'); - - $this->generator->getMock(stdClass::class, ['foo', 'bar', 'foo']); - } - - public function testGetMockBlacklistedMethodNamesPhp7(): void - { - $mock = $this->generator->getMock(InterfaceWithSemiReservedMethodName::class); - - $this->assertTrue(\method_exists($mock, 'unset')); - $this->assertInstanceOf(InterfaceWithSemiReservedMethodName::class, $mock); - } - - public function testGetMockForAbstractClassDoesNotFailWhenFakingInterfaces(): void - { - $mock = $this->generator->getMockForAbstractClass(Countable::class); - - $this->assertTrue(\method_exists($mock, 'count')); - } - - public function testGetMockForAbstractClassStubbingAbstractClass(): void - { - $mock = $this->generator->getMockForAbstractClass(AbstractMockTestClass::class); - - $this->assertTrue(\method_exists($mock, 'doSomething')); - } - - public function testGetMockForAbstractClassWithNonExistentMethods(): void - { - $mock = $this->generator->getMockForAbstractClass( - AbstractMockTestClass::class, - [], - '', - true, - true, - true, - ['nonexistentMethod'] - ); - - $this->assertTrue(\method_exists($mock, 'nonexistentMethod')); - $this->assertTrue(\method_exists($mock, 'doSomething')); - } - - public function testGetMockForAbstractClassShouldCreateStubsOnlyForAbstractMethodWhenNoMethodsWereInformed(): void - { - $mock = $this->generator->getMockForAbstractClass(AbstractMockTestClass::class); - - $mock->method('doSomething') - ->willReturn('testing'); - - $this->assertEquals('testing', $mock->doSomething()); - $this->assertEquals(1, $mock->returnAnything()); - } - - public function testGetMockForAbstractClassAbstractClassDoesNotExist(): void - { - $this->expectException(\PHPUnit\Framework\MockObject\RuntimeException::class); - - $this->generator->getMockForAbstractClass('Tux'); - } - - public function testGetMockForTraitWithNonExistentMethodsAndNonAbstractMethods(): void - { - $mock = $this->generator->getMockForTrait( - AbstractTrait::class, - [], - '', - true, - true, - true, - ['nonexistentMethod'] - ); - - $this->assertTrue(\method_exists($mock, 'nonexistentMethod')); - $this->assertTrue(\method_exists($mock, 'doSomething')); - $this->assertTrue($mock->mockableMethod()); - $this->assertTrue($mock->anotherMockableMethod()); - } - - public function testGetMockForTraitStubbingAbstractMethod(): void - { - $mock = $this->generator->getMockForTrait(AbstractTrait::class); - - $this->assertTrue(\method_exists($mock, 'doSomething')); - } - - public function testGetMockForTraitWithNonExistantTrait(): void - { - $this->expectException(\PHPUnit\Framework\MockObject\RuntimeException::class); - - $mock = $this->generator->getMockForTrait('Tux'); - } - - public function testGetObjectForTraitWithNonExistantTrait(): void - { - $this->expectException(\PHPUnit\Framework\MockObject\RuntimeException::class); - - $mock = $this->generator->getObjectForTrait('Tux'); - } - - public function testGetMockClassMethodsForNonExistantClass(): void - { - $this->expectException(\PHPUnit\Framework\MockObject\RuntimeException::class); - - $mock = $this->generator->mockClassMethods('Tux', true, true); - } - - public function testGetMockForSingletonWithReflectionSuccess(): void - { - $mock = $this->generator->getMock(SingletonClass::class, ['doSomething'], [], '', false); - - $this->assertInstanceOf(SingletonClass::class, $mock); - } - - public function testExceptionIsRaisedForMutuallyExclusiveOptions(): void - { - $this->expectException(\PHPUnit\Framework\MockObject\RuntimeException::class); - - $this->generator->getMock(stdClass::class, [], [], '', false, true, true, true, true); - } - - public function testCanImplementInterfacesThatHaveMethodsWithReturnTypes(): void - { - $stub = $this->generator->getMock(AnInterfaceWithReturnType::class); - - $this->assertInstanceOf(AnInterfaceWithReturnType::class, $stub); - $this->assertInstanceOf(MockObject::class, $stub); - } - - public function testCanConfigureMethodsForDoubleOfNonExistentClass(): void - { - $className = 'X' . \md5(\microtime()); - - $mock = $this->generator->getMock($className, ['someMethod']); - - $this->assertInstanceOf($className, $mock); - } - - public function testCanInvokeMethodsOfNonExistentClass(): void - { - $className = 'X' . \md5(\microtime()); - - $mock = $this->generator->getMock($className, ['someMethod']); - - $mock->expects($this->once())->method('someMethod'); - - $this->assertNull($mock->someMethod()); - } - - public function testMockingOfExceptionWithThrowable(): void - { - $stub = $this->generator->getMock(ExceptionWithThrowable::class); - - $this->assertInstanceOf(ExceptionWithThrowable::class, $stub); - $this->assertInstanceOf(Exception::class, $stub); - $this->assertInstanceOf(MockObject::class, $stub); - } - - public function testMockingOfThrowable(): void - { - $stub = $this->generator->getMock(Throwable::class); - - $this->assertInstanceOf(Throwable::class, $stub); - $this->assertInstanceOf(Exception::class, $stub); - $this->assertInstanceOf(MockObject::class, $stub); - } - - public function testMockingOfThrowableConstructorArguments(): void - { - $mock = $this->generator->getMock(Throwable::class, null, ['It works']); - $this->assertSame('It works', $mock->getMessage()); - } - - public function testVariadicArgumentsArePassedToOriginalMethod(): void - { - /** @var ClassWithVariadicArgumentMethod|MockObject $mock */ - $mock = $this->generator->getMock( - ClassWithVariadicArgumentMethod::class, - [], - [], - '', - true, - false, - true, - false, - true - ); - - $arguments = [1, 'foo', false]; - $this->assertSame($arguments, $mock->foo(...$arguments)); - } - - public function testVariadicArgumentsArePassedToMockedMethod(): void - { - /** @var ClassWithVariadicArgumentMethod|MockObject $mock */ - $mock = $this->createMock(ClassWithVariadicArgumentMethod::class); - - $arguments = [1, 'foo', false]; - $mock->expects($this->once()) - ->method('foo') - ->with(...$arguments); - - $mock->foo(...$arguments); - } - - public function testGetClassMethodsWithNonExistingClass(): void - { - $this->expectException(\PHPUnit\Framework\MockObject\RuntimeException::class); - - $this->generator->getClassMethods('Tux'); - } - - public function testCannotMockFinalClass(): void - { - $this->expectException(\PHPUnit\Framework\MockObject\RuntimeException::class); - - $mock = $this->createMock(FinalClass::class); - } -} diff --git a/tests/unit/Framework/MockObject/InvocationHandlerTest.php b/tests/unit/Framework/MockObject/InvocationHandlerTest.php deleted file mode 100644 index adefa66ac23..00000000000 --- a/tests/unit/Framework/MockObject/InvocationHandlerTest.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use PHPUnit\Framework\TestCase; -use PHPUnit\TestFixture\StringableClass; -use RuntimeException; - -class InvocationHandlerTest extends TestCase -{ - public function testExceptionThrownIn__ToStringIsDeferred(): void - { - $mock = $this->createMock(StringableClass::class); - $mock->method('__toString') - ->willThrowException(new RuntimeException('planned error')); - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('planned error'); - $mock->__toString(); - } -} diff --git a/tests/unit/Framework/MockObject/Matcher/ConsecutiveParametersTest.php b/tests/unit/Framework/MockObject/Matcher/ConsecutiveParametersTest.php deleted file mode 100644 index 2885ffd5714..00000000000 --- a/tests/unit/Framework/MockObject/Matcher/ConsecutiveParametersTest.php +++ /dev/null @@ -1,88 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\InvalidParameterGroupException; -use PHPUnit\Framework\TestCase; - -/** - * @small - */ -final class ConsecutiveParametersTest extends TestCase -{ - public function testIntegration(): void - { - $mock = $this->getMockBuilder(stdClass::class) - ->setMethods(['foo']) - ->getMock(); - - $mock->expects($this->any()) - ->method('foo') - ->withConsecutive( - ['bar'], - [21, 42] - ); - - $this->assertNull($mock->foo('bar')); - $this->assertNull($mock->foo(21, 42)); - } - - public function testIntegrationWithLessAssertionsThanMethodCalls(): void - { - $mock = $this->getMockBuilder(stdClass::class) - ->setMethods(['foo']) - ->getMock(); - - $mock->expects($this->any()) - ->method('foo') - ->withConsecutive( - ['bar'] - ); - - $this->assertNull($mock->foo('bar')); - $this->assertNull($mock->foo(21, 42)); - } - - public function testIntegrationExpectingException(): void - { - $mock = $this->getMockBuilder(stdClass::class) - ->setMethods(['foo']) - ->getMock(); - - $mock->expects($this->any()) - ->method('foo') - ->withConsecutive( - ['bar'], - [21, 42] - ); - - $mock->foo('bar'); - - $this->expectException(ExpectationFailedException::class); - - $mock->foo('invalid'); - } - - public function testIntegrationFailsWithNonIterableParameterGroup(): void - { - $mock = $this->getMockBuilder(stdClass::class) - ->setMethods(['foo']) - ->getMock(); - - $this->expectException(InvalidParameterGroupException::class); - $this->expectExceptionMessage('Parameter group #1 must be an array or Traversable, got object'); - - $mock->expects($this->any()) - ->method('foo') - ->withConsecutive( - ['bar'], - $this->identicalTo([21, 42]) // this is not an array - ); - } -} diff --git a/tests/unit/Framework/MockObject/MatcherTest.php b/tests/unit/Framework/MockObject/MatcherTest.php deleted file mode 100644 index 57e943260a6..00000000000 --- a/tests/unit/Framework/MockObject/MatcherTest.php +++ /dev/null @@ -1,120 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use Exception; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\MockObject\Rule\InvocationOrder; -use PHPUnit\Framework\MockObject\Rule\MethodName; -use PHPUnit\Framework\MockObject\Rule\ParametersRule; -use PHPUnit\Framework\TestCase; -use stdClass; - -/** - * @covers \PHPUnit\Framework\MockObject\Matcher - */ -class MatcherTest extends TestCase -{ - public function testParameterRuleIsAppliedToInvocation(): void - { - $invocationMatcher = $this->createStub(InvocationOrder::class); - $invocation = new Invocation('Foo', 'bar', [], 'void', new stdClass); - - $parameterRule = $this->createMock(ParametersRule::class); - $parameterRule->expects($this->once()) - ->method('apply') - ->with($invocation); - - $matcher = new Matcher($invocationMatcher); - $matcher->setMethodNameRule(new MethodName('bar')); - $matcher->setParametersRule($parameterRule); - - $matcher->invoked($invocation); - } - - public function testParametersRuleTriggersFailOfInvocation(): void - { - $invocationMatcher = $this->createStub(InvocationOrder::class); - $invocation = new Invocation('Foo', 'bar', [], 'void', new stdClass); - - $parameterRule = $this->createStub(ParametersRule::class); - $parameterRule->method('apply') - ->willThrowException(new ExpectationFailedException('rule is always violated.')); - - $matcher = new Matcher($invocationMatcher); - $matcher->setMethodNameRule(new MethodName('bar')); - $matcher->setParametersRule($parameterRule); - - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage("Expectation failed for method name is \"bar\" when \nrule is always violated."); - $matcher->invoked($invocation); - } - - public function testParameterRuleDoesNotInfluenceMatches(): void - { - $invocationMatcher = $this->createStub(InvocationOrder::class); - $invocationMatcher->method('matches') - ->willReturn(true); - $invocation = new Invocation('Foo', 'bar', [], 'void', new stdClass); - $matcher = new Matcher($invocationMatcher); - $matcher->setMethodNameRule(new MethodName('bar')); - - $parameterRule = $this->createStub(ParametersRule::class); - $parameterRule->method('apply') - ->willThrowException(new Exception('This method should not have been called.')); - $matcher->setParametersRule($parameterRule); - - $this->assertTrue($matcher->matches($invocation)); - } - - public function testStubIsNotInvokedIfParametersRuleIsViolated(): void - { - $invocationMatcher = $this->createStub(InvocationOrder::class); - $invocation = new Invocation('Foo', 'bar', [], 'void', new stdClass); - - $stub = $this->createMock(Stub\Stub::class); - $stub->expects($this->never()) - ->method('invoke'); - - $parameterRule = $this->createStub(ParametersRule::class); - $parameterRule->method('apply') - ->willThrowException(new ExpectationFailedException('rule is always violated.')); - - $matcher = new Matcher($invocationMatcher); - $matcher->setMethodNameRule(new MethodName('bar')); - $matcher->setParametersRule($parameterRule); - $matcher->setStub($stub); - - try { - $matcher->invoked($invocation); - } catch (ExpectationFailedException $e) { - } - } - - public function testStubIsInvokedIfAllMatchersAndRulesApply(): void - { - $invocationMatcher = $this->createStub(InvocationOrder::class); - $invocation = new Invocation('Foo', 'bar', [], 'void', new stdClass); - - $stub = $this->createMock(Stub\Stub::class); - $stub->expects($this->once()) - ->method('invoke') - ->with($invocation); - - $parameterRule = $this->createStub(ParametersRule::class); - - $matcher = new Matcher($invocationMatcher); - $matcher->setMethodNameRule(new MethodName('bar')); - $matcher->setParametersRule($parameterRule); - $matcher->setStub($stub); - - $matcher->invoked($invocation); - } -} diff --git a/tests/unit/Framework/MockObject/MockBuilderTest.php b/tests/unit/Framework/MockObject/MockBuilderTest.php deleted file mode 100644 index e3964379cb6..00000000000 --- a/tests/unit/Framework/MockObject/MockBuilderTest.php +++ /dev/null @@ -1,273 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\MockObject\MockBuilder; -use PHPUnit\Framework\TestCase; -use PHPUnit\TestFixture\Mockable; - -/** - * @small - */ -final class MockBuilderTest extends TestCase -{ - public function testMockBuilderRequiresClassName(): void - { - $mock = $this->getMockBuilder(Mockable::class)->getMock(); - - $this->assertInstanceOf(Mockable::class, $mock); - } - - public function testByDefaultMocksAllMethods(): void - { - $mock = $this->getMockBuilder(Mockable::class)->getMock(); - - $this->assertNull($mock->mockableMethod()); - $this->assertNull($mock->anotherMockableMethod()); - } - - public function testMethodsToMockCanBeSpecified(): void - { - $mock = $this->getMockBuilder(Mockable::class) - ->setMethods(['mockableMethod']) - ->getMock(); - - $this->assertNull($mock->mockableMethod()); - $this->assertTrue($mock->anotherMockableMethod()); - } - - public function testMethodExceptionsToMockCanBeSpecified(): void - { - $mock = $this->getMockBuilder(Mockable::class) - ->setMethodsExcept(['mockableMethod']) - ->getMock(); - - $this->assertTrue($mock->mockableMethod()); - $this->assertNull($mock->anotherMockableMethod()); - } - - public function testSetMethodsAllowsNonExistentMethodNames(): void - { - $mock = $this->getMockBuilder(Mockable::class) - ->setMethods(['mockableMethodWithCrazyName']) - ->getMock(); - - $this->assertNull($mock->mockableMethodWithCrazyName()); - } - - public function testOnlyMethodsWithNonExistentMethodNames(): void - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Trying to set mock method "mockableMethodWithCrazyName" with onlyMethods, but it does not exist in class "PHPUnit\TestFixture\Mockable". Use addMethods() for methods that don\'t exist in the class.'); - - $this->getMockBuilder(Mockable::class) - ->onlyMethods(['mockableMethodWithCrazyName']) - ->getMock(); - } - - public function testOnlyMethodsWithExistingMethodNames(): void - { - $mock = $this->getMockBuilder(Mockable::class) - ->onlyMethods(['mockableMethod']) - ->getMock(); - - $this->assertNull($mock->mockableMethod()); - $this->assertTrue($mock->anotherMockableMethod()); - } - - public function testOnlyMethodsWithEmptyArray(): void - { - $mock = $this->getMockBuilder(Mockable::class) - ->onlyMethods([]) - ->getMock(); - - $this->assertTrue($mock->mockableMethod()); - } - - public function testAddMethodsWithNonExistentMethodNames(): void - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Trying to set mock method "mockableMethod" with addMethods(), but it exists in class "PHPUnit\TestFixture\Mockable". Use onlyMethods() for methods that exist in the class.'); - - $this->getMockBuilder(Mockable::class) - ->addMethods(['mockableMethod']) - ->getMock(); - } - - public function testAddMethodsWithExistingMethodNames(): void - { - $mock = $this->getMockBuilder(Mockable::class) - ->addMethods(['mockableMethodWithFakeMethod']) - ->getMock(); - - $this->assertNull($mock->mockableMethodWithFakeMethod()); - $this->assertTrue($mock->anotherMockableMethod()); - } - - public function testAddMethodsWithEmptyArray(): void - { - $mock = $this->getMockBuilder(Mockable::class) - ->addMethods([]) - ->getMock(); - - $this->assertTrue($mock->mockableMethod()); - } - - public function testEmptyMethodExceptionsToMockCanBeSpecified(): void - { - $mock = $this->getMockBuilder(Mockable::class) - ->setMethodsExcept() - ->getMock(); - - $this->assertNull($mock->mockableMethod()); - $this->assertNull($mock->anotherMockableMethod()); - } - - public function testAbleToUseAddMethodsAfterOnlyMethods(): void - { - $mock = $this->getMockBuilder(Mockable::class) - ->onlyMethods(['mockableMethod']) - ->addMethods(['mockableMethodWithFakeMethod']) - ->getMock(); - - $this->assertNull($mock->mockableMethod()); - $this->assertNull($mock->mockableMethodWithFakeMethod()); - } - - public function testAbleToUseOnlyMethodsAfterAddMethods(): void - { - $mock = $this->getMockBuilder(Mockable::class) - ->addMethods(['mockableMethodWithFakeMethod']) - ->onlyMethods(['mockableMethod']) - ->getMock(); - - $this->assertNull($mock->mockableMethodWithFakeMethod()); - $this->assertNull($mock->mockableMethod()); - } - - public function testAbleToUseSetMethodsAfterOnlyMethods(): void - { - $mock = $this->getMockBuilder(Mockable::class) - ->onlyMethods(['mockableMethod']) - ->setMethods(['mockableMethodWithCrazyName']) - ->getMock(); - - $this->assertNull($mock->mockableMethodWithCrazyName()); - } - - public function testAbleToUseSetMethodsAfterAddMethods(): void - { - $mock = $this->getMockBuilder(Mockable::class) - ->addMethods(['notAMethod']) - ->setMethods(['mockableMethodWithCrazyName']) - ->getMock(); - - $this->assertNull($mock->mockableMethodWithCrazyName()); - } - - public function testAbleToUseAddMethodsAfterSetMethods(): void - { - $mock = $this->getMockBuilder(Mockable::class) - ->setMethods(['mockableMethod']) - ->addMethods(['mockableMethodWithFakeMethod']) - ->getMock(); - - $this->assertNull($mock->mockableMethod()); - $this->assertNull($mock->mockableMethodWithFakeMethod()); - } - - public function testAbleToUseOnlyMethodsAfterSetMethods(): void - { - $mock = $this->getMockBuilder(Mockable::class) - ->setMethods(['mockableMethodWithFakeMethod']) - ->onlyMethods(['mockableMethod']) - ->getMock(); - - $this->assertNull($mock->mockableMethod()); - $this->assertNull($mock->mockableMethodWithFakeMethod()); - } - - public function testAbleToUseAddMethodsAfterSetMethodsWithNull(): void - { - $mock = $this->getMockBuilder(Mockable::class) - ->setMethods() - ->addMethods(['mockableMethodWithFakeMethod']) - ->getMock(); - - $this->assertNull($mock->mockableMethodWithFakeMethod()); - } - - public function testByDefaultDoesNotPassArgumentsToTheConstructor(): void - { - $mock = $this->getMockBuilder(Mockable::class)->getMock(); - - $this->assertEquals([null, null], $mock->constructorArgs); - } - - public function testMockClassNameCanBeSpecified(): void - { - $mock = $this->getMockBuilder(Mockable::class) - ->setMockClassName('ACustomClassName') - ->getMock(); - - $this->assertInstanceOf(ACustomClassName::class, $mock); - } - - public function testConstructorArgumentsCanBeSpecified(): void - { - $mock = $this->getMockBuilder(Mockable::class) - ->setConstructorArgs([23, 42]) - ->getMock(); - - $this->assertEquals([23, 42], $mock->constructorArgs); - } - - public function testOriginalConstructorCanBeDisabled(): void - { - $mock = $this->getMockBuilder(Mockable::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->assertNull($mock->constructorArgs); - } - - public function testByDefaultOriginalCloneIsPreserved(): void - { - $mock = $this->getMockBuilder(Mockable::class) - ->getMock(); - - $cloned = clone $mock; - - $this->assertTrue($cloned->cloned); - } - - public function testOriginalCloneCanBeDisabled(): void - { - $mock = $this->getMockBuilder(Mockable::class) - ->disableOriginalClone() - ->getMock(); - - $mock->cloned = false; - $cloned = clone $mock; - - $this->assertFalse($cloned->cloned); - } - - public function testProvidesAFluentInterface(): void - { - $spec = $this->getMockBuilder(Mockable::class) - ->setMethods(['mockableMethod']) - ->setConstructorArgs([]) - ->setMockClassName('DummyClassName') - ->disableOriginalConstructor() - ->disableOriginalClone() - ->disableAutoload(); - - $this->assertInstanceOf(MockBuilder::class, $spec); - } -} diff --git a/tests/unit/Framework/MockObject/MockClassTest.php b/tests/unit/Framework/MockObject/MockClassTest.php deleted file mode 100644 index 9c7c2e6f2f6..00000000000 --- a/tests/unit/Framework/MockObject/MockClassTest.php +++ /dev/null @@ -1,49 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function class_exists; -use function file_get_contents; -use PHPUnit\Framework\TestCase; -use PHPUnit\TestFixture\MockObject\MockClassWithConfigurableMethods; -use SebastianBergmann\Type\Type; - -final class MockClassTest extends TestCase -{ - public function testGenerateClassFromSource(): void - { - $mockName = 'PHPUnit\TestFixture\MockObject\MockClassGenerated'; - - $file = __DIR__ . '/../../../_files/mock-object/MockClassGenerated.tpl'; - - $mockClass = new MockClass(file_get_contents($file), $mockName, []); - $mockClass->generate(); - - $this->assertTrue(class_exists($mockName)); - } - - public function testGenerateReturnsNameOfGeneratedClass(): void - { - $mockName = 'PHPUnit\TestFixture\MockObject\MockClassGenerated'; - - $mockClass = new MockClass('', $mockName, []); - - $this->assertEquals($mockName, $mockClass->generate()); - } - - public function testConfigurableMethodsAreInitalized(): void - { - $configurableMethods = [new ConfigurableMethod('foo', Type::fromName('void', false))]; - $mockClass = new MockClass('', MockClassWithConfigurableMethods::class, $configurableMethods); - $mockClass->generate(); - - $this->assertSame($configurableMethods, MockClassWithConfigurableMethods::getConfigurableMethods()); - } -} diff --git a/tests/unit/Framework/MockObject/MockMethodTest.php b/tests/unit/Framework/MockObject/MockMethodTest.php deleted file mode 100644 index 351aacb05d6..00000000000 --- a/tests/unit/Framework/MockObject/MockMethodTest.php +++ /dev/null @@ -1,52 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use PHPUnit\Framework\TestCase; -use PHPUnit\TestFixture\MockObject\ClassWithoutParentButParentReturnType; -use ReflectionClass; -use RuntimeException; -use SebastianBergmann\Type\UnknownType; - -/** - * @small - */ -final class MockMethodTest extends TestCase -{ - public function testGetNameReturnsMethodName(): void - { - $method = new MockMethod( - 'ClassName', - 'methodName', - false, - '', - '', - '', - new UnknownType, - '', - false, - false, - null, - false - ); - $this->assertEquals('methodName', $method->getName()); - } - - /** - * @requires PHP < 7.4 - */ - public function testFailWhenReturnTypeIsParentButThereIsNoParentClass(): void - { - $class = new ReflectionClass(ClassWithoutParentButParentReturnType::class); - - $this->expectException(RuntimeException::class); - MockMethod::fromReflection($class->getMethod('foo'), false, false); - } -} diff --git a/tests/unit/Framework/MockObject/MockObjectTest.php b/tests/unit/Framework/MockObject/MockObjectTest.php index 240abebdfbd..9b604ba2106 100644 --- a/tests/unit/Framework/MockObject/MockObjectTest.php +++ b/tests/unit/Framework/MockObject/MockObjectTest.php @@ -7,1154 +7,544 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +namespace PHPUnit\Framework\MockObject; + +use function call_user_func_array; +use Exception; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\Attributes\RequiresMethod; +use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\MockObject\Runtime\PropertyHook; use PHPUnit\Framework\TestCase; -use PHPUnit\TestFixture\AbstractTrait; -use PHPUnit\TestFixture\AnInterface; -use PHPUnit\TestFixture\ClassThatImplementsSerializable; -use PHPUnit\TestFixture\ClassWithAllPossibleReturnTypes; -use PHPUnit\TestFixture\ClassWithSelfTypeHint; -use PHPUnit\TestFixture\ClassWithStaticMethod; -use PHPUnit\TestFixture\ClassWithUnionReturnTypes; -use PHPUnit\TestFixture\ExampleTrait; -use PHPUnit\TestFixture\InterfaceWithStaticMethod; -use PHPUnit\TestFixture\MethodCallback; -use PHPUnit\TestFixture\MethodCallbackByReference; -use PHPUnit\TestFixture\MockObject\AbstractMockTestClass; -use PHPUnit\TestFixture\PartialMockTestClass; -use PHPUnit\TestFixture\SomeClass; -use PHPUnit\TestFixture\StringableClass; -use PHPUnit\TestFixture\TraitWithConstructor; -use PHPUnit\TestFixture\TraversableMockTestInterface; - -/** - * @small - */ -final class MockObjectTest extends TestCase +use PHPUnit\TestFixture\MockObject\AnInterface; +use PHPUnit\TestFixture\MockObject\ExtendableClassWithCloneMethod; +use PHPUnit\TestFixture\MockObject\ExtendableClassWithPropertyWithSetHook; +use PHPUnit\TestFixture\MockObject\ExtendableReadonlyClassWithCloneMethod; +use PHPUnit\TestFixture\MockObject\InterfaceWithImplicitProtocol; +use PHPUnit\TestFixture\MockObject\InterfaceWithPropertyWithSetHook; +use PHPUnit\TestFixture\MockObject\InterfaceWithReturnTypeDeclaration; +use PHPUnit\TestFixture\MockObject\MethodWIthVariadicVariables; +use ReflectionProperty; + +#[Group('test-doubles')] +#[Group('test-doubles/mock-object')] +#[TestDox('Mock Object')] +#[Medium] +final class MockObjectTest extends TestDoubleTestCase { - public function testMockedMethodIsNeverCalled(): void - { - $mock = $this->getMockBuilder(AnInterface::class) - ->getMock(); - - $mock->expects($this->never()) - ->method('doSomething'); - } - - public function testMockedMethodIsNeverCalledWithParameter(): void + public function testExpectationThatMethodIsNeverCalledSucceedsWhenMethodIsNotCalled(): void { - $mock = $this->getMockBuilder(SomeClass::class) - ->getMock(); + $double = $this->createMock(AnInterface::class); - $mock->expects($this->never()) - ->method('doSomething') - ->with('someArg'); + $double->expects($this->never())->method('doSomething'); } - /** - * @doesNotPerformAssertions - */ - public function testMockedMethodIsNotCalledWhenExpectsAnyWithParameter(): void + public function testExpectationThatMethodIsNeverCalledFailsWhenMethodIsCalled(): void { - $mock = $this->getMockBuilder(SomeClass::class) - ->getMock(); + $double = $this->createMock(AnInterface::class); - $mock->method('doSomethingElse') - ->with('someArg'); - } + $double->expects($this->never())->method('doSomething'); - /** - * @doesNotPerformAssertions - */ - public function testMockedMethodIsNotCalledWhenMethodSpecifiedDirectlyWithParameter(): void - { - $mock = $this->getMockBuilder(SomeClass::class) - ->getMock(); - - $mock->method('doSomethingElse') - ->with('someArg'); + $this->assertThatMockObjectExpectationFails( + AnInterface::class . '::doSomething(): bool was not expected to be called.', + $double, + 'doSomething', + ); } - public function testMockedMethodIsCalledAtLeastOnce(): void + #[DoesNotPerformAssertions] + public function testExpectationThatMethodIsCalledZeroOrMoreTimesSucceedsWhenMethodIsNotCalled(): void { - $mock = $this->getMockBuilder(AnInterface::class) - ->getMock(); - - $mock->expects($this->atLeastOnce()) - ->method('doSomething'); + $double = $this->createMock(AnInterface::class); - $mock->doSomething(); + $double->expects($this->any())->method('doSomething'); } - public function testMockedMethodIsCalledAtLeastOnce2(): void + #[DoesNotPerformAssertions] + public function testExpectationThatMethodIsCalledZeroOrMoreTimesSucceedsWhenMethodIsCalledOnce(): void { - $mock = $this->getMockBuilder(AnInterface::class) - ->getMock(); + $double = $this->createMock(AnInterface::class); - $mock->expects($this->atLeastOnce()) - ->method('doSomething'); + $double->expects($this->any())->method('doSomething'); - $mock->doSomething(); - $mock->doSomething(); + $double->doSomething(); } - public function testMockedMethodIsCalledAtLeastTwice(): void + public function testExpectationThatMethodIsCalledOnceSucceedsWhenMethodIsCalledOnce(): void { - $mock = $this->getMockBuilder(AnInterface::class) - ->getMock(); + $double = $this->createMock(AnInterface::class); - $mock->expects($this->atLeast(2)) - ->method('doSomething'); + $double->expects($this->once())->method('doSomething'); - $mock->doSomething(); - $mock->doSomething(); + $double->doSomething(); } - public function testMockedMethodIsCalledAtLeastTwice2(): void + public function testExpectationThatMethodIsCalledOnceFailsWhenMethodIsNeverCalled(): void { - $mock = $this->getMockBuilder(AnInterface::class) - ->getMock(); - - $mock->expects($this->atLeast(2)) - ->method('doSomething'); + $double = $this->createMock(AnInterface::class); - $mock->doSomething(); - $mock->doSomething(); - $mock->doSomething(); - } - - public function testMockedMethodIsCalledAtMostTwice(): void - { - $mock = $this->getMockBuilder(AnInterface::class) - ->getMock(); + $double->expects($this->once())->method('doSomething'); - $mock->expects($this->atMost(2)) - ->method('doSomething'); + $this->assertThatMockObjectExpectationFails( + <<<'EOT' +Expectation failed for method name is "doSomething" when invoked 1 time. +Method was expected to be called 1 time, actually called 0 times. - $mock->doSomething(); - $mock->doSomething(); +EOT, + $double, + ); } - public function testMockedMethodIsCalledAtMosttTwice2(): void + public function testExpectationThatMethodIsCalledOnceFailsWhenMethodIsCalledMoreThanOnce(): void { - $mock = $this->getMockBuilder(AnInterface::class) - ->getMock(); + $double = $this->createMock(AnInterface::class); - $mock->expects($this->atMost(2)) - ->method('doSomething'); - - $mock->doSomething(); - } - - public function testMockedMethodIsCalledOnce(): void - { - $mock = $this->getMockBuilder(AnInterface::class) - ->getMock(); + $double->expects($this->once())->method('doSomething'); - $mock->expects($this->once()) - ->method('doSomething'); + $double->doSomething(); - $mock->doSomething(); + $this->assertThatMockObjectExpectationFails( + AnInterface::class . '::doSomething(): bool was not expected to be called more than once.', + $double, + 'doSomething', + ); } - public function testMockedMethodIsCalledOnceWithParameter(): void + public function testExpectationThatMethodIsCalledAtLeastOnceSucceedsWhenMethodIsCalledOnce(): void { - $mock = $this->getMockBuilder(SomeClass::class) - ->getMock(); + $double = $this->createMock(AnInterface::class); - $mock->expects($this->once()) - ->method('doSomethingElse') - ->with($this->equalTo('something')); + $double->expects($this->atLeastOnce())->method('doSomething'); - $mock->doSomethingElse('something'); + $double->doSomething(); } - public function testMockedMethodIsCalledExactly(): void + public function testExpectationThatMethodIsCalledAtLeastOnceSucceedsWhenMethodIsCalledTwice(): void { - $mock = $this->getMockBuilder(AnInterface::class) - ->getMock(); + $double = $this->createMock(AnInterface::class); - $mock->expects($this->exactly(2)) - ->method('doSomething'); + $double->expects($this->atLeastOnce())->method('doSomething'); - $mock->doSomething(); - $mock->doSomething(); + $double->doSomething(); + $double->doSomething(); } - public function testStubbedException(): void + public function testExpectationThatMethodIsCalledAtLeastTwiceSucceedsWhenMethodIsCalledTwice(): void { - $mock = $this->getMockBuilder(AnInterface::class) - ->getMock(); - - $mock->method('doSomething') - ->will($this->throwException(new \Exception)); + $double = $this->createMock(AnInterface::class); - $this->expectException(\Exception::class); + $double->expects($this->atLeast(2))->method('doSomething'); - $mock->doSomething(); + $double->doSomething(); + $double->doSomething(); } - public function testStubbedWillThrowException(): void + public function testExpectationThatMethodIsCalledAtLeastTwiceSucceedsWhenMethodIsCalledThreeTimes(): void { - $mock = $this->getMockBuilder(AnInterface::class) - ->getMock(); + $double = $this->createMock(AnInterface::class); - $mock->method('doSomething') - ->willThrowException(new \Exception); + $double->expects($this->atLeast(2))->method('doSomething'); - $this->expectException(\Exception::class); - - $mock->doSomething(); + $double->doSomething(); + $double->doSomething(); + $double->doSomething(); } - public function testStubbedReturnValue(): void + public function testExpectationThatMethodIsCalledAtLeastOnceFailsWhenMethodIsNotCalled(): void { - $mock = $this->getMockBuilder(AnInterface::class) - ->getMock(); - - $mock->method('doSomething') - ->will($this->returnValue('something')); + $double = $this->createMock(AnInterface::class); - $this->assertEquals('something', $mock->doSomething()); + $double->expects($this->atLeastOnce())->method('doSomething'); - $mock = $this->getMockBuilder(AnInterface::class) - ->getMock(); + $this->assertThatMockObjectExpectationFails( + <<<'EOT' +Expectation failed for method name is "doSomething" when invoked at least once. +Expected invocation at least once but it never occurred. - $mock->method('doSomething') - ->willReturn('something'); - - $this->assertEquals('something', $mock->doSomething()); +EOT, + $double, + ); } - public function testStubbedReturnValueMap(): void + public function testExpectationThatMethodIsCalledAtLeastTwiceFailsWhenMethodIsCalledOnce(): void { - $map = [ - ['a', 'b', 'c', 'd'], - ['e', 'f', 'g', 'h'], - ]; - - $mock = $this->getMockBuilder(AnInterface::class) - ->getMock(); + $double = $this->createMock(AnInterface::class); - $mock->method('doSomething') - ->will($this->returnValueMap($map)); + $double->expects($this->atLeast(2))->method('doSomething'); - $this->assertEquals('d', $mock->doSomething('a', 'b', 'c')); - $this->assertEquals('h', $mock->doSomething('e', 'f', 'g')); - $this->assertNull($mock->doSomething('foo', 'bar')); + $double->doSomething(); - $mock = $this->getMockBuilder(AnInterface::class) - ->getMock(); + $this->assertThatMockObjectExpectationFails( + <<<'EOT' +Expectation failed for method name is "doSomething" when invoked at least 2 times. +Expected invocation at least 2 times but it occurred 1 time. - $mock->method('doSomething') - ->willReturnMap($map); - - $this->assertEquals('d', $mock->doSomething('a', 'b', 'c')); - $this->assertEquals('h', $mock->doSomething('e', 'f', 'g')); - $this->assertNull($mock->doSomething('foo', 'bar')); +EOT, + $double, + ); } - public function testStubbedReturnArgument(): void + public function testExpectationThatMethodIsCalledTwiceSucceedsWhenMethodIsCalledTwice(): void { - $mock = $this->getMockBuilder(AnInterface::class) - ->getMock(); - - $mock->method('doSomething') - ->will($this->returnArgument(1)); - - $this->assertEquals('b', $mock->doSomething('a', 'b')); + $double = $this->createMock(AnInterface::class); - $mock = $this->getMockBuilder(AnInterface::class) - ->getMock(); + $double->expects($this->exactly(2))->method('doSomething'); - $mock->method('doSomething') - ->willReturnArgument(1); - - $this->assertEquals('b', $mock->doSomething('a', 'b')); + $double->doSomething(); + $double->doSomething(); } - public function testFunctionCallback(): void + public function testExpectationThatMethodIsCalledTwiceFailsWhenMethodIsNeverCalled(): void { - $mock = $this->getMockBuilder(SomeClass::class) - ->setMethods(['doSomething']) - ->getMock(); - - $mock->expects($this->once()) - ->method('doSomething') - ->willReturnCallback('PHPUnit\TestFixture\FunctionCallbackWrapper::functionCallback'); + $double = $this->createMock(AnInterface::class); - $this->assertEquals('pass', $mock->doSomething('foo', 'bar')); + $double->expects($this->exactly(2))->method('doSomething'); - $mock = $this->getMockBuilder(SomeClass::class) - ->setMethods(['doSomething']) - ->getMock(); + $this->assertThatMockObjectExpectationFails( + <<<'EOT' +Expectation failed for method name is "doSomething" when invoked 2 times. +Method was expected to be called 2 times, actually called 0 times. - $mock->expects($this->once()) - ->method('doSomething') - ->willReturnCallback('PHPUnit\TestFixture\FunctionCallbackWrapper::functionCallback'); - - $this->assertEquals('pass', $mock->doSomething('foo', 'bar')); +EOT, + $double, + ); } - public function testStubbedReturnSelf(): void + public function testExpectationThatMethodIsCalledTwiceFailsWhenMethodIsCalledOnce(): void { - $mock = $this->getMockBuilder(AnInterface::class) - ->getMock(); + $double = $this->createMock(AnInterface::class); - $mock->method('doSomething') - ->will($this->returnSelf()); + $double->expects($this->exactly(2))->method('doSomething'); - $this->assertEquals($mock, $mock->doSomething()); + $double->doSomething(); - $mock = $this->getMockBuilder(AnInterface::class) - ->getMock(); + $this->assertThatMockObjectExpectationFails( + <<<'EOT' +Expectation failed for method name is "doSomething" when invoked 2 times. +Method was expected to be called 2 times, actually called 1 time. - $mock->method('doSomething') - ->willReturnSelf(); - - $this->assertEquals($mock, $mock->doSomething()); +EOT, + $double, + ); } - public function testStubbedReturnOnConsecutiveCalls(): void + public function testExpectationThatMethodIsCalledTwiceFailsWhenMethodIsCalledThreeTimes(): void { - $mock = $this->getMockBuilder(AnInterface::class) - ->getMock(); - - $mock->method('doSomething') - ->will($this->onConsecutiveCalls('a', 'b', 'c')); - - $this->assertEquals('a', $mock->doSomething()); - $this->assertEquals('b', $mock->doSomething()); - $this->assertEquals('c', $mock->doSomething()); + $double = $this->createMock(AnInterface::class); - $mock = $this->getMockBuilder(AnInterface::class) - ->getMock(); + $double->expects($this->exactly(2))->method('doSomething'); - $mock->method('doSomething') - ->willReturnOnConsecutiveCalls('a', 'b', 'c'); + $double->doSomething(); + $double->doSomething(); - $this->assertEquals('a', $mock->doSomething()); - $this->assertEquals('b', $mock->doSomething()); - $this->assertEquals('c', $mock->doSomething()); + $this->assertThatMockObjectExpectationFails( + AnInterface::class . '::doSomething(): bool was not expected to be called more than 2 times.', + $double, + 'doSomething', + ); } - public function testStaticMethodCallback(): void + public function testExpectationThatMethodIsCalledAtMostOnceSucceedsWhenMethodIsNeverCalled(): void { - $mock = $this->getMockBuilder(SomeClass::class) - ->setMethods(['doSomething']) - ->getMock(); - - $mock->expects($this->once()) - ->method('doSomething') - ->will($this->returnCallback([MethodCallback::class, 'staticCallback'])); + $double = $this->createMock(AnInterface::class); - $this->assertEquals('pass', $mock->doSomething('foo', 'bar')); + $double->expects($this->atMost(1))->method('doSomething'); } - public function testPublicMethodCallback(): void + public function testExpectationThatMethodIsCalledAtMostOnceSucceedsWhenMethodIsCalledOnce(): void { - $mock = $this->getMockBuilder(SomeClass::class) - ->setMethods(['doSomething']) - ->getMock(); + $double = $this->createMock(AnInterface::class); - $mock->expects($this->once()) - ->method('doSomething') - ->will($this->returnCallback([new MethodCallback, 'nonStaticCallback'])); + $double->expects($this->atMost(1))->method('doSomething'); - $this->assertEquals('pass', $mock->doSomething('foo', 'bar')); + $double->doSomething(); } - public function testMockClassOnlyGeneratedOnce(): void + public function testExpectationThatMethodIsCalledAtMostOnceFailsWhenMethodIsCalledTwice(): void { - $mock1 = $this->getMockBuilder(AnInterface::class) - ->getMock(); - - $mock2 = $this->getMockBuilder(AnInterface::class) - ->getMock(); + $double = $this->createMock(AnInterface::class); - $this->assertEquals(\get_class($mock1), \get_class($mock2)); - } + $double->expects($this->atMost(1))->method('doSomething'); - public function testMockClassDifferentForPartialMocks(): void - { - $mock1 = $this->getMockBuilder(PartialMockTestClass::class) - ->getMock(); - - $mock2 = $this->getMockBuilder(PartialMockTestClass::class) - ->setMethods(['doSomething']) - ->getMock(); - - $mock3 = $this->getMockBuilder(PartialMockTestClass::class) - ->setMethods(['doSomething']) - ->getMock(); - - $mock4 = $this->getMockBuilder(PartialMockTestClass::class) - ->setMethods(['doAnotherThing']) - ->getMock(); - - $mock5 = $this->getMockBuilder(PartialMockTestClass::class) - ->setMethods(['doAnotherThing']) - ->getMock(); - - $this->assertNotEquals(\get_class($mock1), \get_class($mock2)); - $this->assertNotEquals(\get_class($mock1), \get_class($mock3)); - $this->assertNotEquals(\get_class($mock1), \get_class($mock4)); - $this->assertNotEquals(\get_class($mock1), \get_class($mock5)); - $this->assertEquals(\get_class($mock2), \get_class($mock3)); - $this->assertNotEquals(\get_class($mock2), \get_class($mock4)); - $this->assertNotEquals(\get_class($mock2), \get_class($mock5)); - $this->assertEquals(\get_class($mock4), \get_class($mock5)); - } + $double->doSomething(); + $double->doSomething(); - public function testMockClassStoreOverrulable(): void - { - $mock1 = $this->getMockBuilder(PartialMockTestClass::class) - ->getMock(); - - $mock2 = $this->getMockBuilder(PartialMockTestClass::class) - ->setMockClassName('MyMockClassNameForPartialMockTestClass1') - ->getMock(); - - $mock3 = $this->getMockBuilder(PartialMockTestClass::class) - ->getMock(); - - $mock4 = $this->getMockBuilder(PartialMockTestClass::class) - ->setMethods(['doSomething']) - ->setMockClassName('AnotherMockClassNameForPartialMockTestClass') - ->getMock(); - - $mock5 = $this->getMockBuilder(PartialMockTestClass::class) - ->setMockClassName('MyMockClassNameForPartialMockTestClass2') - ->getMock(); - - $this->assertNotEquals(\get_class($mock1), \get_class($mock2)); - $this->assertEquals(\get_class($mock1), \get_class($mock3)); - $this->assertNotEquals(\get_class($mock1), \get_class($mock4)); - $this->assertNotEquals(\get_class($mock2), \get_class($mock3)); - $this->assertNotEquals(\get_class($mock2), \get_class($mock4)); - $this->assertNotEquals(\get_class($mock2), \get_class($mock5)); - $this->assertNotEquals(\get_class($mock3), \get_class($mock4)); - $this->assertNotEquals(\get_class($mock3), \get_class($mock5)); - $this->assertNotEquals(\get_class($mock4), \get_class($mock5)); - } + $this->assertThatMockObjectExpectationFails( + <<<'EOT' +Expectation failed for method name is "doSomething" when invoked at most 1 time. +Expected invocation at most 1 time but it occurred 2 times. - public function testGetMockWithFixedClassNameCanProduceTheSameMockTwice(): void - { - $mock = $this->getMockBuilder(stdClass::class)->setMockClassName('FixedName')->getMock(); - $this->assertInstanceOf(stdClass::class, $mock); +EOT, + $double, + ); } - public function testOriginalConstructorSettingConsidered(): void + public function testExpectationThatMethodIsCalledWithAnyParameterSucceedsWhenMethodIsCalledWithParameter(): void { - $mock1 = $this->getMockBuilder(PartialMockTestClass::class) - ->getMock(); + $double = $this->createMock(InterfaceWithReturnTypeDeclaration::class); - $mock2 = $this->getMockBuilder(PartialMockTestClass::class) - ->disableOriginalConstructor() - ->getMock(); + $double->expects($this->once())->method('doSomethingElse')->withAnyParameters(); - $this->assertTrue($mock1->constructorCalled); - $this->assertFalse($mock2->constructorCalled); + $double->doSomethingElse(1); } - public function testOriginalCloneSettingConsidered(): void + public function testExpectationThatMethodIsCalledWithParameterSucceedsWhenMethodIsCalledWithExpectedParameter(): void { - $mock1 = $this->getMockBuilder(PartialMockTestClass::class) - ->getMock(); + $double = $this->createMock(InterfaceWithReturnTypeDeclaration::class); - $mock2 = $this->getMockBuilder(PartialMockTestClass::class) - ->disableOriginalClone() - ->getMock(); + $double->expects($this->once())->method('doSomethingElse')->with(1); - $this->assertNotEquals(\get_class($mock1), \get_class($mock2)); + $double->doSomethingElse(1); } - /** - * @testdox getMock() for abstract class - */ - public function testGetMockForAbstractClass(): void + public function testExpectationThatMethodIsCalledWithParameterFailsWhenMethodIsCalledButWithUnexpectedParameter(): void { - $mock = $this->getMockBuilder(AbstractMockTestClass::class) - ->getMock(); + $double = $this->createMock(InterfaceWithReturnTypeDeclaration::class); - $mock->expects($this->never()) - ->method('doSomething'); - } + $double->expects($this->once())->method('doSomethingElse')->with(1); - /** - * @testdox getMock() for Traversable $_dataName - * @dataProvider traversableProvider - */ - public function testGetMockForTraversable($type): void - { - $mock = $this->getMockBuilder($type) - ->getMock(); - - $this->assertInstanceOf(Traversable::class, $mock); + $this->assertThatMockObjectExpectationFails( + <<<'EOT' +Expectation failed for method name is "doSomethingElse" when invoked 1 time +Parameter 0 for invocation PHPUnit\TestFixture\MockObject\InterfaceWithReturnTypeDeclaration::doSomethingElse(0): int does not match expected value. +Failed asserting that 0 matches expected 1. +EOT, + $double, + 'doSomethingElse', + [0], + ); } /** - * @testdox getMockForTrait() + * With $double->expects($this->once())->method('one')->id($id);, + * we configure an expectation that one() is called once. This expectation is given the ID $id. + * + * With $double->expects($this->once())->method('two')->after($id);, + * we configure an expectation that two() is called once. However, this expectation will only be verified + * if/after one() has been called. */ - public function testGetMockForTrait(): void + public function testMethodCallCanBeExpectedContingentOnWhetherAnotherMethodWasPreviouslyCalled(): void { - $mock = $this->getMockForTrait(AbstractTrait::class); + $id = 'the-id'; + $double = $this->createMock(InterfaceWithImplicitProtocol::class); - $mock->expects($this->never()) - ->method('doSomething'); + $double->expects($this->once()) + ->method('one') + ->id($id); - $parent = \get_parent_class($mock); - $traits = \class_uses($parent, false); + $double->expects($this->once()) + ->method('two') + ->after($id); - $this->assertContains(AbstractTrait::class, $traits); + $double->one(); + $double->two(); } - public function testClonedMockObjectShouldStillEqualTheOriginal(): void + public function testContingentExpectationsAreNotEvaluatedUntilTheirConditionIsMet(): void { - $a = $this->getMockBuilder(stdClass::class) - ->getMock(); + $id = 'the-id'; + $double = $this->createMock(InterfaceWithImplicitProtocol::class); - $b = clone $a; + $double->expects($this->once()) + ->method('one') + ->id($id); - $this->assertEquals($a, $b); - } - - public function testMockObjectsConstructedIndepentantlyShouldBeEqual(): void - { - $a = $this->getMockBuilder(stdClass::class) - ->getMock(); - - $b = $this->getMockBuilder(stdClass::class) - ->getMock(); - - $this->assertEquals($a, $b); - } - - public function testMockObjectsConstructedIndepentantlyShouldNotBeTheSame(): void - { - $a = $this->getMockBuilder(stdClass::class) - ->getMock(); - - $b = $this->getMockBuilder(stdClass::class) - ->getMock(); - - $this->assertNotSame($a, $b); - } - - public function testClonedMockObjectCanBeUsedInPlaceOfOriginalOne(): void - { - $x = $this->getMockBuilder(stdClass::class) - ->getMock(); - - $y = clone $x; - - $mock = $this->getMockBuilder(stdClass::class) - ->setMethods(['foo']) - ->getMock(); - - $mock->expects($this->once()) - ->method('foo') - ->with($this->equalTo($x)); - - $mock->foo($y); - } - - public function testClonedMockObjectIsNotIdenticalToOriginalOne(): void - { - $x = $this->getMockBuilder(stdClass::class) - ->getMock(); - - $y = clone $x; - - $mock = $this->getMockBuilder(stdClass::class) - ->setMethods(['foo']) - ->getMock(); - - $mock->expects($this->once()) - ->method('foo') - ->with($this->logicalNot($this->identicalTo($x))); - - $mock->foo($y); - } - - public function testObjectMethodCallWithArgumentCloningEnabled(): void - { - $expectedObject = new stdClass; - - $mock = $this->getMockBuilder('SomeClass') - ->setMethods(['doSomethingElse']) - ->enableArgumentCloning() - ->getMock(); - - $actualArguments = []; - - $mock->method('doSomethingElse') - ->will( - $this->returnCallback( - function () use (&$actualArguments): void { - $actualArguments = \func_get_args(); - } - ) - ); + $double->expects($this->once()) + ->method('two') + ->after($id); - $mock->doSomethingElse($expectedObject); - - $this->assertCount(1, $actualArguments); - $this->assertEquals($expectedObject, $actualArguments[0]); - $this->assertNotSame($expectedObject, $actualArguments[0]); - } - - public function testObjectMethodCallWithArgumentCloningDisabled(): void - { - $expectedObject = new stdClass; - - $mock = $this->getMockBuilder('SomeClass') - ->setMethods(['doSomethingElse']) - ->disableArgumentCloning() - ->getMock(); - - $actualArguments = []; - - $mock->method('doSomethingElse') - ->will( - $this->returnCallback( - function () use (&$actualArguments): void { - $actualArguments = \func_get_args(); - } - ) - ); - - $mock->doSomethingElse($expectedObject); - - $this->assertCount(1, $actualArguments); - $this->assertSame($expectedObject, $actualArguments[0]); - } - - public function testArgumentCloningOptionGeneratesUniqueMock(): void - { - $mockWithCloning = $this->getMockBuilder('SomeClass') - ->setMethods(['doSomethingElse']) - ->enableArgumentCloning() - ->getMock(); - - $mockWithoutCloning = $this->getMockBuilder('SomeClass') - ->setMethods(['doSomethingElse']) - ->disableArgumentCloning() - ->getMock(); - - $this->assertNotEquals($mockWithCloning, $mockWithoutCloning); - } - - public function testVerificationOfMethodNameFailsWithoutParameters(): void - { - $mock = $this->getMockBuilder(SomeClass::class) - ->setMethods(['right', 'wrong']) - ->getMock(); - - $mock->expects($this->once()) - ->method('right'); - - $mock->wrong(); - - try { - $mock->__phpunit_verify(); - $this->fail('Expected exception'); - } catch (ExpectationFailedException $e) { - $this->assertSame( - "Expectation failed for method name is \"right\" when invoked 1 time(s).\n" . - 'Method was expected to be called 1 times, actually called 0 times.' . "\n", - $e->getMessage() - ); - } - - $this->resetMockObjects(); + $double->two(); + $double->one(); + $double->two(); } - public function testVerificationOfMethodNameFailsWithParameters(): void + public function testContingentExpectationsAreEvaluatedWhenTheirConditionIsMet(): void { - $mock = $this->getMockBuilder(SomeClass::class) - ->setMethods(['right', 'wrong']) - ->getMock(); + $id = 'the-id'; + $double = $this->createMock(InterfaceWithImplicitProtocol::class); - $mock->expects($this->once()) - ->method('right'); + $double->expects($this->once()) + ->method('one') + ->id($id); - $mock->wrong(); + $double->expects($this->once()) + ->method('two') + ->after($id); - try { - $mock->__phpunit_verify(); - $this->fail('Expected exception'); - } catch (ExpectationFailedException $e) { - $this->assertSame( - "Expectation failed for method name is \"right\" when invoked 1 time(s).\n" . - 'Method was expected to be called 1 times, actually called 0 times.' . "\n", - $e->getMessage() - ); - } + $double->two(); + $double->one(); - $this->resetMockObjects(); - } + $this->assertThatMockObjectExpectationFails( + <<<'EOT' +Expectation failed for method name is "two" when invoked 1 time. +Method was expected to be called 1 time, actually called 0 times. - public function testVerificationOfMethodNameFailsWithWrongParameters(): void - { - $mock = $this->getMockBuilder(SomeClass::class) - ->setMethods(['right', 'wrong']) - ->getMock(); - - $mock->expects($this->once()) - ->method('right') - ->with(['first', 'second']); - - try { - $mock->right(['second']); - } catch (ExpectationFailedException $e) { - $this->assertSame( - "Expectation failed for method name is \"right\" when invoked 1 time(s)\n" . - 'Parameter 0 for invocation PHPUnit\TestFixture\SomeClass::right(Array (...)) does not match expected value.' . "\n" . - 'Failed asserting that two arrays are equal.', - $e->getMessage() - ); - } - - try { - $mock->__phpunit_verify(); - - // CHECKOUT THIS MORE CAREFULLY -// $this->fail('Expected exception'); - } catch (ExpectationFailedException $e) { - $this->assertSame( - "Expectation failed for method name is \"right\" when invoked 1 time(s).\n" . - 'Parameter 0 for invocation PHPUnit\TestFixture\SomeClass::right(Array (...)) does not match expected value.' . "\n" . - 'Failed asserting that two arrays are equal.' . "\n" . - '--- Expected' . "\n" . - '+++ Actual' . "\n" . - '@@ @@' . "\n" . - ' Array (' . "\n" . - '- 0 => \'first\'' . "\n" . - '- 1 => \'second\'' . "\n" . - '+ 0 => \'second\'' . "\n" . - ' )' . "\n", - $e->getMessage() - ); - } - - $this->resetMockObjects(); - } - - public function testVerificationOfNeverFailsWithEmptyParameters(): void - { - $mock = $this->getMockBuilder(SomeClass::class) - ->setMethods(['right', 'wrong']) - ->getMock(); - - $mock->expects($this->never()) - ->method('right') - ->with(); - - try { - $mock->right(); - $this->fail('Expected exception'); - } catch (ExpectationFailedException $e) { - $this->assertSame( - 'PHPUnit\TestFixture\SomeClass::right() was not expected to be called.', - $e->getMessage() - ); - } - - $this->resetMockObjects(); +EOT, + $double, + ); } - public function testVerificationOfNeverFailsWithAnyParameters(): void + public function testExpectationCannotBeContingentOnExpectationThatHasNotBeenConfigured(): void { - $mock = $this->getMockBuilder(SomeClass::class) - ->setMethods(['right', 'wrong']) - ->getMock(); - - $mock->expects($this->never()) - ->method('right') - ->withAnyParameters(); + $double = $this->createMock(InterfaceWithImplicitProtocol::class); - try { - $mock->right(); - $this->fail('Expected exception'); - } catch (ExpectationFailedException $e) { - $this->assertSame( - 'PHPUnit\TestFixture\SomeClass::right() was not expected to be called.', - $e->getMessage() - ); - } + $double->expects($this->once()) + ->method('two') + ->after('the-id'); - $this->resetMockObjects(); + $this->assertThatMockObjectExpectationFails( + 'No builder found for match builder identification ', + $double, + 'two', + ); } - public function testWithAnythingInsteadOfWithAnyParameters(): void + public function testExpectationsCannotHaveDuplicateIds(): void { - $mock = $this->getMockBuilder(SomeClass::class) - ->setMethods(['right', 'wrong']) - ->getMock(); + $id = 'the-id'; + $double = $this->createMock(InterfaceWithImplicitProtocol::class); - $mock->expects($this->once()) - ->method('right') - ->with($this->anything()); + $double->expects($this->once()) + ->method('one') + ->id($id); try { - $mock->right(); - $this->fail('Expected exception'); - } catch (ExpectationFailedException $e) { - $this->assertSame( - "Expectation failed for method name is \"right\" when invoked 1 time(s)\n" . - 'Parameter count for invocation PHPUnit\TestFixture\SomeClass::right() is too low.' . "\n" . - 'To allow 0 or more parameters with any value, omit ->with() or use ->withAnyParameters() instead.', - $e->getMessage() - ); + $double->expects($this->once()) + ->method('one') + ->id($id); + } catch (MatcherAlreadyRegisteredException $e) { + $this->assertSame('Matcher with id is already registered', $e->getMessage()); + + return; + } finally { + $this->resetMockObjects(); } - $this->resetMockObjects(); + $this->fail(); } - /** - * @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/81 - */ - public function testMockArgumentsPassedByReference(): void + public function testWillReturnCallbackWithVariadicVariables(): void { - $foo = $this->getMockBuilder(MethodCallbackByReference::class) - ->setMethods(['bar']) - ->disableOriginalConstructor() - ->disableArgumentCloning() - ->getMock(); - - $foo->method('bar') - ->will($this->returnCallback([$foo, 'callback'])); - - $a = $b = $c = 0; - - $foo->bar($a, $b, $c); - - $this->assertEquals(1, $b); - } - - /** - * @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/81 - */ - public function testMockArgumentsPassedByReference2(): void - { - $foo = $this->getMockBuilder(MethodCallbackByReference::class) - ->disableOriginalConstructor() - ->disableArgumentCloning() - ->getMock(); - - $foo->method('bar') - ->will($this->returnCallback( - function (&$a, &$b, $c): void { - $b = 1; - } - )); + $double = $this->createMock(MethodWIthVariadicVariables::class); + $double->expects($this->once())->method('testVariadic') + ->withAnyParameters() + ->willReturnCallback(static fn (string $string, string ...$arguments) => [$string, ...$arguments]); - $a = $b = $c = 0; + $testData = ['foo', 'bar', 'biz' => 'kuz']; + $actual = $double->testVariadic(...$testData); - $foo->bar($a, $b, $c); - - $this->assertEquals(1, $b); + $this->assertSame($testData, $actual); } - /** - * @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/116 - */ - public function testMockArgumentsPassedByReference3(): void + public function testExpectationsAreClonedWhenTestDoubleIsCloned(): void { - $foo = $this->getMockBuilder(MethodCallbackByReference::class) - ->setMethods(['bar']) - ->disableOriginalConstructor() - ->disableArgumentCloning() - ->getMock(); - - $a = new stdClass; - $b = $c = 0; + $double = $this->createMock(InterfaceWithReturnTypeDeclaration::class); - $foo->method('bar') - ->with($a, $b, $c) - ->will($this->returnCallback([$foo, 'callback'])); + $double->expects($this->exactly(2))->method('doSomething'); - $this->assertNull($foo->bar($a, $b, $c)); - } + $clone = clone $double; - /** - * @see https://github.com/sebastianbergmann/phpunit/issues/796 - */ - public function testMockArgumentsPassedByReference4(): void - { - $foo = $this->getMockBuilder(MethodCallbackByReference::class) - ->setMethods(['bar']) - ->disableOriginalConstructor() - ->disableArgumentCloning() - ->getMock(); + $double->expects($this->once())->method('doSomethingElse')->willReturn(1); + $clone->expects($this->once())->method('doSomethingElse')->willReturn(2); - $a = new stdClass; - $b = $c = 0; - - $foo->method('bar') - ->with($this->isInstanceOf(stdClass::class), $b, $c) - ->will($this->returnCallback([$foo, 'callback'])); - - $this->assertNull($foo->bar($a, $b, $c)); + $this->assertFalse($double->doSomething()); + $this->assertFalse($clone->doSomething()); + $this->assertSame(1, $double->doSomethingElse(0)); + $this->assertSame(2, $clone->doSomethingElse(0)); } - /** - * @requires extension soap - */ - public function testCreateMockFromWsdl(): void + #[RequiresMethod(ReflectionProperty::class, 'isFinal')] + public function testExpectationCanBeConfiguredForSetHookForPropertyOfInterface(): void { - $mock = $this->getMockFromWsdl(TEST_FILES_PATH . 'GoogleSearch.wsdl', 'WsdlMock'); - - $this->assertStringStartsWith( - 'Mock_WsdlMock_', - \get_class($mock) - ); - } + $double = $this->createTestDouble(InterfaceWithPropertyWithSetHook::class); - /** - * @requires extension soap - */ - public function testCreateNamespacedMockFromWsdl(): void - { - $mock = $this->getMockFromWsdl(TEST_FILES_PATH . 'GoogleSearch.wsdl', 'My\\Space\\WsdlMock'); + $double->expects($this->once())->method(PropertyHook::set('property'))->with('value'); - $this->assertStringStartsWith( - 'Mock_WsdlMock_', - \get_class($mock) - ); + $double->property = 'value'; } - /** - * @requires extension soap - */ - public function testCreateTwoMocksOfOneWsdlFile(): void + #[RequiresMethod(ReflectionProperty::class, 'isFinal')] + public function testExpectationCanBeConfiguredForSetHookForPropertyOfExtendableClass(): void { - $a = $this->getMockFromWsdl(TEST_FILES_PATH . 'GoogleSearch.wsdl'); - $b = $this->getMockFromWsdl(TEST_FILES_PATH . 'GoogleSearch.wsdl'); + $double = $this->createTestDouble(ExtendableClassWithPropertyWithSetHook::class); - $this->assertStringStartsWith('Mock_GoogleSearch_', \get_class($a)); - $this->assertEquals(\get_class($a), \get_class($b)); - } + $double->expects($this->once())->method(PropertyHook::set('property'))->with('value'); - /** - * @see https://github.com/sebastianbergmann/phpunit/issues/2573 - * @ticket 2573 - * @requires extension soap - */ - public function testCreateMockOfWsdlFileWithSpecialChars(): void - { - $mock = $this->getMockFromWsdl(TEST_FILES_PATH . 'Go ogle-Sea.rch.wsdl'); - - $this->assertStringStartsWith('Mock_GoogleSearch_', \get_class($mock)); + $double->property = 'value'; } - /** - * @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/156 - * @ticket 156 - */ - public function testInterfaceWithStaticMethodCanBeStubbed(): void + #[TestDox('__toString() method returns empty string when return value generation is disabled and no return value is configured')] + public function testToStringMethodReturnsEmptyStringWhenReturnValueGenerationIsDisabledAndNoReturnValueIsConfigured(): void { - $this->assertInstanceOf( - InterfaceWithStaticMethod::class, - $this->getMockBuilder(InterfaceWithStaticMethod::class)->getMock() - ); - } - - public function testInvokingStubbedStaticMethodRaisesException(): void - { - $mock = $this->getMockBuilder(ClassWithStaticMethod::class)->getMock(); - - $this->expectException(\PHPUnit\Framework\MockObject\BadMethodCallException::class); - - $mock->staticMethod(); - } - - /** - * @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/171 - * @ticket 171 - */ - public function testStubForClassThatImplementsSerializableCanBeCreatedWithoutInvokingTheConstructor(): void - { - $this->assertInstanceOf( - ClassThatImplementsSerializable::class, - $this->getMockBuilder(ClassThatImplementsSerializable::class) - ->disableOriginalConstructor() - ->getMock() - ); - } - - public function testGetMockForClassWithSelfTypeHint(): void - { - $this->assertInstanceOf( - ClassWithSelfTypeHint::class, - $this->getMockBuilder(ClassWithSelfTypeHint::class)->getMock() - ); - } - - public function testStringableClassDoesNotThrow(): void - { - /** @var PHPUnit\Framework\MockObject\MockObject|StringableClass $mock */ - $mock = $this->getMockBuilder(StringableClass::class)->getMock(); - - $this->assertIsString((string) $mock); - } - - public function testStringableClassCanBeMocked(): void - { - /** @var PHPUnit\Framework\MockObject\MockObject|StringableClass $mock */ - $mock = $this->getMockBuilder(StringableClass::class)->getMock(); - - $mock->method('__toString')->willReturn('foo'); - - $this->assertSame('foo', (string) $mock); - } - - public function traversableProvider(): array - { - return [ - Traversable::class => [Traversable::class], - '\Traversable' => [Traversable::class], - TraversableMockTestInterface::class => [TraversableMockTestInterface::class], - ]; - } - - public function testParameterCallbackConstraintOnlyEvaluatedOnce(): void - { - $mock = $this->getMockBuilder(Foo::class)->setMethods(['bar'])->getMock(); - $expectedNumberOfCalls = 1; - $callCount = 0; - - $mock->expects($this->exactly($expectedNumberOfCalls))->method('bar') - ->with($this->callback(function ($argument) use (&$callCount) { - return $argument === 'call_' . $callCount++; - })); - - for ($i = 0; $i < $expectedNumberOfCalls; $i++) { - $mock->bar('call_' . $i); - } - } - - public function testReturnTypesAreMockedCorrectly(): void - { - /** @var ClassWithAllPossibleReturnTypes|MockObject $stub */ - $stub = $this->createMock(ClassWithAllPossibleReturnTypes::class); - - $this->assertNull($stub->methodWithNoReturnTypeDeclaration()); - $this->assertSame('', $stub->methodWithStringReturnTypeDeclaration()); - $this->assertSame(0.0, $stub->methodWithFloatReturnTypeDeclaration()); - $this->assertSame(0, $stub->methodWithIntReturnTypeDeclaration()); - $this->assertFalse($stub->methodWithBoolReturnTypeDeclaration()); - $this->assertSame([], $stub->methodWithArrayReturnTypeDeclaration()); - $this->assertInstanceOf(MockObject::class, $stub->methodWithClassReturnTypeDeclaration()); - } - - public function testDisableAutomaticReturnValueGeneration(): void - { - $mock = $this->getMockBuilder(SomeClass::class) + $double = $this->getMockBuilder(InterfaceWithReturnTypeDeclaration::class) ->disableAutoReturnValueGeneration() ->getMock(); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage( - 'Return value inference disabled and no expectation set up for PHPUnit\TestFixture\SomeClass::doSomethingElse()' - ); - - $mock->doSomethingElse(1); + $this->assertSame('', $double->__toString()); } - public function testDisableAutomaticReturnValueGenerationWithToString(): void + public function testMethodDoesNotReturnValueWhenReturnValueGenerationIsDisabledAndNoReturnValueIsConfigured(): void { - /** @var PHPUnit\Framework\MockObject\MockObject|StringableClass $mock */ - $mock = $this->getMockBuilder(StringableClass::class) + $double = $this->getMockBuilder(InterfaceWithReturnTypeDeclaration::class) ->disableAutoReturnValueGeneration() ->getMock(); - (string) $mock; + $this->expectException(ReturnValueNotConfiguredException::class); + $this->expectExceptionMessage('No return value is configured for ' . InterfaceWithReturnTypeDeclaration::class . '::doSomething() and return value generation is disabled'); - try { - $mock->__phpunit_verify(); - $this->fail('Exception expected'); - } catch (RuntimeException $e) { - $this->assertSame( - 'Return value inference disabled and no expectation set up for PHPUnit\TestFixture\StringableClass::__toString()', - $e->getMessage() - ); - } - - $this->resetMockObjects(); + $double->doSomething(); } - public function testVoidReturnTypeIsMockedCorrectly(): void + #[TestDox('Original __clone() method can optionally be called when test double object is cloned')] + public function testOriginalCloneMethodCanOptionallyBeCalledWhenTestDoubleObjectIsCloned(): void { - /** @var ClassWithAllPossibleReturnTypes|MockObject $stub */ - $stub = $this->createMock(ClassWithAllPossibleReturnTypes::class); + $double = $this->getMockBuilder(ExtendableClassWithCloneMethod::class)->enableOriginalClone()->getMock(); - $this->assertNull($stub->methodWithVoidReturnTypeDeclaration()); - } + $this->expectException(Exception::class); + $this->expectExceptionMessage(ExtendableClassWithCloneMethod::class . '::__clone'); - public function testObjectReturnTypeIsMockedCorrectly(): void - { - /** @var ClassWithAllPossibleReturnTypes|MockObject $stub */ - $stub = $this->createMock(ClassWithAllPossibleReturnTypes::class); - - $this->assertInstanceOf(stdClass::class, $stub->methodWithObjectReturnTypeDeclaration()); + clone $double; } - /** - * @requires PHP > 8.0 - */ - public function testUnionReturnTypeIsDoubledCorrectly(): void + #[TestDox('Original __clone() method can optionally be called when test double object is cloned (readonly class)')] + public function testOriginalCloneMethodCanOptionallyBeCalledWhenTestDoubleObjectOfReadonlyClassIsCloned(): void { - /** @var ClassWithUnionReturnTypes|MockObject $stub */ - $stub = $this->createMock(ClassWithUnionReturnTypes::class); + $double = $this->getMockBuilder(ExtendableReadonlyClassWithCloneMethod::class)->enableOriginalClone()->getMock(); - $this->assertFalse($stub->returnsBoolOrInt()); - } - - /** - * @requires PHP > 8.0 - */ - public function testNullableUnionReturnTypeIsDoubledCorrectly(): void - { - /** @var ClassWithUnionReturnTypes|MockObject $stub */ - $stub = $this->createMock(ClassWithUnionReturnTypes::class); + $this->expectException(Exception::class); + $this->expectExceptionMessage(ExtendableReadonlyClassWithCloneMethod::class . '::__clone'); - $this->assertNull($stub->returnsBoolOrIntOrNull()); + clone $double; } /** - * @requires PHP > 8.0 + * @param class-string $type */ - public function testMixedReturnTypeIsDoubledCorrectly(): void + protected function createTestDouble(string $type): object { - /** @var ClassWithUnionReturnTypes|MockObject $stub */ - $stub = $this->createMock(ClassWithUnionReturnTypes::class); - - $this->assertNull($stub->returnsMixed()); + return $this->createMock($type); } - public function testTraitCanBeDoubled(): void + private function assertThatMockObjectExpectationFails(string $expectationFailureMessage, MockObject $double, string $methodName = '__phpunit_verify', array $arguments = []): void { - $object = $this->getObjectForTrait(ExampleTrait::class); - - $this->assertSame('ohHai', $object->ohHai()); - } + try { + call_user_func_array([$double, $methodName], $arguments); + } catch (ExpectationFailedException|MatchBuilderNotFoundException $e) { + $this->assertSame($expectationFailureMessage, $e->getMessage()); - public function testTraitWithConstructorCanBeDoubled(): void - { - $object = $this->getObjectForTrait(TraitWithConstructor::class, ['value']); + return; + } finally { + $this->resetMockObjects(); + } - $this->assertSame('value', $object->value()); + $this->fail(); } private function resetMockObjects(): void { - $refl = new ReflectionObject($this); - $refl = $refl->getParentClass(); - $prop = $refl->getProperty('mockObjects'); - $prop->setAccessible(true); - $prop->setValue($this, []); + new ReflectionProperty(TestCase::class, 'mockObjects')->setValue($this, []); } } diff --git a/tests/unit/Framework/MockObject/MockTraitTest.php b/tests/unit/Framework/MockObject/MockTraitTest.php deleted file mode 100644 index bd88ba68619..00000000000 --- a/tests/unit/Framework/MockObject/MockTraitTest.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function file_get_contents; -use function trait_exists; -use PHPUnit\Framework\TestCase; - -final class MockTraitTest extends TestCase -{ - public function testGenerateClassFromSource(): void - { - $mockName = 'PHPUnit\TestFixture\MockObject\MockTraitGenerated'; - - $file = __DIR__ . '/../../../_files/mock-object/MockTraitGenerated.tpl'; - - $mockTrait = new MockTrait(file_get_contents($file), $mockName); - $mockTrait->generate(); - - $this->assertTrue(trait_exists($mockName)); - } - - public function testGenerateReturnsNameOfGeneratedClass(): void - { - $mockName = 'PHPUnit\TestFixture\MockObject\MockTraitGenerated'; - - $mockTrait = new MockTrait('', $mockName); - - $this->assertEquals($mockName, $mockTrait->generate()); - } -} diff --git a/tests/unit/Framework/MockObject/ProxyObjectTest.php b/tests/unit/Framework/MockObject/ProxyObjectTest.php deleted file mode 100644 index 34002e5208d..00000000000 --- a/tests/unit/Framework/MockObject/ProxyObjectTest.php +++ /dev/null @@ -1,96 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\MockObject\MockObject; -use PHPUnit\Framework\TestCase; -use PHPUnit\TestFixture\TestProxyFixture; - -/** - * @small - */ -final class ProxyObjectTest extends TestCase -{ - public function testProxyingWorksForMethodThatReturnsUndeclaredScalarValue(): void - { - $proxy = $this->createTestProxy(TestProxyFixture::class); - - $proxy->expects($this->once()) - ->method('returnString'); - - \assert($proxy instanceof MockObject); - \assert($proxy instanceof TestProxyFixture); - - $this->assertSame('result', $proxy->returnString()); - } - - public function testProxyingWorksForMethodThatReturnsDeclaredScalarValue(): void - { - $proxy = $this->createTestProxy(TestProxyFixture::class); - - $proxy->expects($this->once()) - ->method('returnTypedString'); - - \assert($proxy instanceof MockObject); - \assert($proxy instanceof TestProxyFixture); - - $this->assertSame('result', $proxy->returnTypedString()); - } - - public function testProxyingWorksForMethodThatReturnsUndeclaredObject(): void - { - $proxy = $this->createTestProxy(TestProxyFixture::class); - - $proxy->expects($this->once()) - ->method('returnObject'); - - \assert($proxy instanceof MockObject); - \assert($proxy instanceof TestProxyFixture); - - $this->assertSame('bar', $proxy->returnObject()->foo); - } - - public function testProxyingWorksForMethodThatReturnsDeclaredObject(): void - { - $proxy = $this->createTestProxy(TestProxyFixture::class); - - $proxy->expects($this->once()) - ->method('returnTypedObject'); - - \assert($proxy instanceof MockObject); - \assert($proxy instanceof TestProxyFixture); - - $this->assertSame('bar', $proxy->returnTypedObject()->foo); - } - - public function testProxyingWorksForMethodThatReturnsUndeclaredObjectOfFinalClass(): void - { - $proxy = $this->createTestProxy(TestProxyFixture::class); - - $proxy->expects($this->once()) - ->method('returnObjectOfFinalClass'); - - \assert($proxy instanceof MockObject); - \assert($proxy instanceof TestProxyFixture); - - $this->assertSame('value', $proxy->returnObjectOfFinalClass()->value()); - } - - public function testProxyingWorksForMethodThatReturnsDeclaredObjectOfFinalClass(): void - { - $proxy = $this->createTestProxy(TestProxyFixture::class); - - $proxy->expects($this->once()) - ->method('returnTypedObjectOfFinalClass'); - - \assert($proxy instanceof MockObject); - \assert($proxy instanceof TestProxyFixture); - - $this->assertSame('value', $proxy->returnTypedObjectOfFinalClass()->value()); - } -} diff --git a/tests/unit/Framework/MockObject/ReturnValueGeneratorTest.php b/tests/unit/Framework/MockObject/ReturnValueGeneratorTest.php new file mode 100644 index 00000000000..98691117cce --- /dev/null +++ b/tests/unit/Framework/MockObject/ReturnValueGeneratorTest.php @@ -0,0 +1,270 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; +use Generator; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\Attributes\Ticket; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\MockObject\AnInterface; +use PHPUnit\TestFixture\MockObject\AnInterfaceForIssue5593; +use PHPUnit\TestFixture\MockObject\AnotherInterface; +use PHPUnit\TestFixture\MockObject\AnotherInterfaceForIssue5593; +use PHPUnit\TestFixture\MockObject\ExtendableClass; +use PHPUnit\TestFixture\MockObject\InterfaceWithMethodThatReturnsSelf; +use PHPUnit\TestFixture\MockObject\InterfaceWithMethodThatReturnsStatic; +use PHPUnit\TestFixture\MockObject\YetAnotherInterface; +use stdClass; + +#[CoversClass(ReturnValueGenerator::class)] +#[Group('test-doubles')] +#[Group('test-doubles/test-stub')] +#[Small] +final class ReturnValueGeneratorTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function unionProvider(): array + { + return [ + [null, 'null|true|float|int|string|array|object'], + [null, 'null|false|float|int|string|array|object'], + [null, 'null|bool|float|int|string|array|object'], + [true, 'true|float|int|string|array|object'], + [false, 'false|float|int|string|array|object'], + [false, 'bool|float|int|string|array|object'], + [0, 'int|string|array|object'], + ['', 'string|array|object'], + [[], 'array|object'], + ]; + } + + public function test_Generates_null_for_missing_return_type_declaration(): void + { + $this->assertNull($this->generate('')); + } + + public function test_Generates_null_for_null(): void + { + $this->assertNull($this->generate('null')); + } + + public function test_Generates_null_for_mixed(): void + { + $this->assertNull($this->generate('mixed')); + } + + public function test_Generates_null_for_void(): void + { + $this->assertNull($this->generate('void')); + } + + public function test_Generates_true_for_true(): void + { + $this->assertTrue($this->generate('true')); + } + + public function test_Generates_false_for_bool(): void + { + $this->assertFalse($this->generate('bool')); + } + + public function test_Generates_false_for_false(): void + { + $this->assertFalse($this->generate('false')); + } + + #[TestDox('Generates 0.0 for float')] + public function test_Generates_00_for_float(): void + { + $this->assertSame(0.0, $this->generate('float')); + } + + public function test_Generates_0_for_int(): void + { + $this->assertSame(0, $this->generate('int')); + } + + public function test_Generates_empty_string_for_string(): void + { + $this->assertSame('', $this->generate('string')); + } + + public function test_Generates_empty_array_for_array(): void + { + $this->assertSame([], $this->generate('array')); + } + + public function test_Generates_stdClass_object_for_object(): void + { + $this->assertInstanceOf(stdClass::class, $this->generate('object')); + } + + public function test_Generates_callable_for_callable(): void + { + $this->assertIsCallable($this->generate('callable')); + } + + public function test_Generates_callable_for_Closure(): void + { + $this->assertIsCallable($this->generate('Closure')); + } + + public function test_Generates_Generator_for_Generator(): void + { + $value = $this->generate('Generator'); + + $this->assertInstanceOf(Generator::class, $value); + + foreach ($value as $element) { + $this->assertSame([], $element); + } + } + + public function test_Generates_Generator_for_Traversable(): void + { + $value = $this->generate('Traversable'); + + $this->assertInstanceOf(Generator::class, $value); + + foreach ($value as $element) { + $this->assertSame([], $element); + } + } + + public function test_Generates_Generator_for_iterable(): void + { + $value = $this->generate('iterable'); + + $this->assertInstanceOf(Generator::class, $value); + + foreach ($value as $element) { + $this->assertSame([], $element); + } + } + + public function test_Generates_test_stub_for_class_or_interface_name(): void + { + $value = $this->generate(AnInterface::class); + + $this->assertInstanceOf(Stub::class, $value); + $this->assertInstanceOf(AnInterface::class, $value); + } + + public function test_Generates_test_stub_for_intersection_of_interfaces(): void + { + $value = $this->generate(AnInterface::class . '&' . AnotherInterface::class); + + $this->assertInstanceOf(Stub::class, $value); + $this->assertInstanceOf(AnInterface::class, $value); + $this->assertInstanceOf(AnotherInterface::class, $value); + } + + public function test_Generates_new_instance_of_test_stub_for_self(): void + { + $stub = $this->createStub(InterfaceWithMethodThatReturnsSelf::class); + + $returnValue = $stub->doSomething(); + + $this->assertNotInstanceOf($stub::class, $returnValue); + $this->assertNotSame($stub, $returnValue); + } + + public function test_Generates_new_instance_of_test_stub_for_static(): void + { + $stub = $this->createStub(InterfaceWithMethodThatReturnsStatic::class); + + $returnValue = $stub->doSomething(); + + $this->assertInstanceOf($stub::class, $returnValue); + $this->assertNotSame($stub, $returnValue); + } + + #[Ticket('https://github.com/sebastianbergmann/phpunit/issues/5593')] + public function test_Generates_new_instance_of_test_stub_for_static_when_used_recursively(): void + { + $a = $this->createStub(AnInterfaceForIssue5593::class); + + $this->assertInstanceOf(AnInterfaceForIssue5593::class, $a); + + $b = $a->doSomething(); + + $this->assertInstanceOf(AnotherInterfaceForIssue5593::class, $b); + + $c = $b->doSomethingElse(); + + $this->assertInstanceOf(AnotherInterfaceForIssue5593::class, $c); + } + + #[DataProvider('unionProvider')] + #[TestDox('Generates $expected for $union')] + public function test_Generates_return_value_for_union(mixed $expected, string $union): void + { + $this->assertSame($expected, $this->generate($union)); + } + + public function test_Generates_stdClass_object_for_union_that_contains_object_and_unknown_type(): void + { + $this->assertInstanceOf(stdClass::class, $this->generate('object|ThisDoesNotExist')); + } + + public function test_Generates_test_stub_for_first_intersection_of_interfaces_found_in_union_of_intersections(): void + { + $value = $this->generate( + sprintf( + '(%s&%s)|(%s&%s)', + AnInterface::class, + AnotherInterface::class, + AnInterface::class, + YetAnotherInterface::class, + ), + ); + + $this->assertInstanceOf(Stub::class, $value); + $this->assertInstanceOf(AnInterface::class, $value); + $this->assertInstanceOf(AnotherInterface::class, $value); + } + + public function test_Does_not_handle_union_of_extendable_class_and_interface(): void + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Return value for OriginalClassName::methodName() cannot be generated because the declared return type is a union, please configure a return value for this method'); + + $this->generate(ExtendableClass::class . '|' . AnInterface::class); + } + + public function test_Does_not_handle_intersection_of_extendable_class_and_interface(): void + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Return value for OriginalClassName::methodName() cannot be generated because the declared return type is an intersection, please configure a return value for this method'); + + $this->generate(ExtendableClass::class . '&' . AnInterface::class); + } + + private function generate(string $typeDeclaration, ?StubInternal $testStub = null): mixed + { + if ($testStub === null) { + $testStub = $this->createStub(AnInterface::class); + } + + return (new ReturnValueGenerator)->generate( + 'OriginalClassName', + 'methodName', + $testStub, + $typeDeclaration, + ); + } +} diff --git a/tests/unit/Framework/MockObject/Runtime/PropertyGetHookTest.php b/tests/unit/Framework/MockObject/Runtime/PropertyGetHookTest.php new file mode 100644 index 00000000000..3ef9b5616fb --- /dev/null +++ b/tests/unit/Framework/MockObject/Runtime/PropertyGetHookTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Runtime; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(PropertyHook::class)] +#[CoversClass(PropertyGetHook::class)] +#[Small] +#[Group('test-doubles')] +final class PropertyGetHookTest extends TestCase +{ + public function testHasPropertyName(): void + { + $propertyName = 'property'; + + $propertyHook = PropertyHook::get($propertyName); + + $this->assertSame($propertyName, $propertyHook->propertyName()); + } + + public function testCanBeRepresentedAsString(): void + { + $propertyName = 'property'; + + $propertyHook = PropertyHook::get($propertyName); + + $this->assertSame('$' . $propertyName . '::get', $propertyHook->asString()); + } +} diff --git a/tests/unit/Framework/MockObject/Runtime/PropertySetHookTest.php b/tests/unit/Framework/MockObject/Runtime/PropertySetHookTest.php new file mode 100644 index 00000000000..57c9c49961d --- /dev/null +++ b/tests/unit/Framework/MockObject/Runtime/PropertySetHookTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Runtime; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(PropertyHook::class)] +#[CoversClass(PropertySetHook::class)] +#[Small] +#[Group('test-doubles')] +final class PropertySetHookTest extends TestCase +{ + public function testHasPropertyName(): void + { + $propertyName = 'property'; + + $propertyHook = PropertyHook::set($propertyName); + + $this->assertSame($propertyName, $propertyHook->propertyName()); + } + + public function testCanBeRepresentedAsString(): void + { + $propertyName = 'property'; + + $propertyHook = PropertyHook::set($propertyName); + + $this->assertSame('$' . $propertyName . '::set', $propertyHook->asString()); + } +} diff --git a/tests/unit/Framework/MockObject/StubTest.php b/tests/unit/Framework/MockObject/StubTest.php new file mode 100644 index 00000000000..747fcd0e392 --- /dev/null +++ b/tests/unit/Framework/MockObject/StubTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\Attributes\TestDox; + +#[Group('test-doubles')] +#[Group('test-doubles/test-stub')] +#[Medium] +#[TestDox('Test Stub')] +final class StubTest extends TestDoubleTestCase +{ + /** + * @param class-string $type + */ + protected function createTestDouble(string $type): object + { + return $this->createStub($type); + } +} diff --git a/tests/unit/Framework/MockObject/TestDoubleTestCase.php b/tests/unit/Framework/MockObject/TestDoubleTestCase.php new file mode 100644 index 00000000000..ba465c40df4 --- /dev/null +++ b/tests/unit/Framework/MockObject/TestDoubleTestCase.php @@ -0,0 +1,324 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use Exception; +use PHPUnit\Framework\Attributes\RequiresPhp; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\Attributes\Ticket; +use PHPUnit\Framework\MockObject\Runtime\PropertyHook; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\MockObject\ExtendableClassCallingMethodInDestructor; +use PHPUnit\TestFixture\MockObject\ExtendableClassWithCloneMethod; +use PHPUnit\TestFixture\MockObject\ExtendableClassWithPropertyWithGetHook; +use PHPUnit\TestFixture\MockObject\ExtendableReadonlyClassWithCloneMethod; +use PHPUnit\TestFixture\MockObject\InterfaceWithMethodThatHasDefaultParameterValues; +use PHPUnit\TestFixture\MockObject\InterfaceWithNeverReturningMethod; +use PHPUnit\TestFixture\MockObject\InterfaceWithPropertyWithGetHook; +use PHPUnit\TestFixture\MockObject\InterfaceWithReturnTypeDeclaration; +use PHPUnit\TestFixture\MockObject\Issue6174; + +abstract class TestDoubleTestCase extends TestCase +{ + final public function testMethodReturnsNullWhenReturnValueIsNullableAndNoReturnValueIsConfigured(): void + { + $double = $this->createTestDouble(InterfaceWithReturnTypeDeclaration::class); + + $this->assertNull($double->returnsNullOrString()); + } + + final public function testMethodReturnsGeneratedValueWhenReturnValueGenerationIsEnabledAndNoReturnValueIsConfigured(): void + { + $double = $this->createTestDouble(InterfaceWithReturnTypeDeclaration::class); + + $this->assertFalse($double->doSomething()); + } + + final public function testMethodReturnsConfiguredValueWhenReturnValueIsConfigured(): void + { + $double = $this->createTestDouble(InterfaceWithReturnTypeDeclaration::class); + + $double->method('doSomething')->willReturn(true); + + $this->assertTrue($double->doSomething()); + } + + final public function testConfiguredReturnValueMustBeCompatibleWithReturnTypeDeclaration(): void + { + $double = $this->createTestDouble(InterfaceWithReturnTypeDeclaration::class); + + $this->expectException(IncompatibleReturnValueException::class); + + $double->method('doSomething')->willReturn(null); + } + + final public function testMethodCanBeConfiguredToReturnOneOfItsArguments(): void + { + $double = $this->createTestDouble(InterfaceWithReturnTypeDeclaration::class); + + $double->method('doSomethingElse')->willReturnArgument(0); + + $this->assertSame(123, $double->doSomethingElse(123)); + } + + final public function testMethodCanBeConfiguredToReturnSelfReference(): void + { + $double = $this->createTestDouble(InterfaceWithReturnTypeDeclaration::class); + + $double->method('selfReference')->willReturnSelf(); + + $this->assertSame($double, $double->selfReference()); + } + + final public function testMethodCanBeConfiguredToReturnReference(): void + { + $double = $this->createTestDouble(InterfaceWithReturnTypeDeclaration::class); + + $double->method('doSomething')->willReturnReference($value); + + $value = true; + + $this->assertSame($value, $double->doSomething()); + } + + final public function testMethodCanBeConfiguredToReturnValuesBasedOnArgumentMapping(): void + { + $double = $this->createTestDouble(InterfaceWithReturnTypeDeclaration::class); + + $double->method('doSomethingElse')->willReturnMap([[1, 2], [3, 4]]); + + $this->assertSame(2, $double->doSomethingElse(1)); + $this->assertSame(4, $double->doSomethingElse(3)); + } + + final public function testMethodWithDefaultParameterValuesCanBeConfiguredToReturnValuesBasedOnArgumentMapping(): void + { + $double = $this->createTestDouble(InterfaceWithMethodThatHasDefaultParameterValues::class); + + $double->method('doSomething')->willReturnMap([[1, 2, 3], [4, 5, 6]]); + + $this->assertSame(3, $double->doSomething(1, 2)); + $this->assertSame(6, $double->doSomething(4, 5)); + } + + final public function testMethodWithDefaultParameterValuesCanBeConfiguredToReturnValuesBasedOnArgumentMappingThatOmitsDefaultValues(): void + { + $double = $this->createTestDouble(InterfaceWithMethodThatHasDefaultParameterValues::class); + + $double->method('doSomething')->willReturnMap([[1, 2], [3, 4]]); + + $this->assertSame(2, $double->doSomething(1)); + $this->assertSame(4, $double->doSomething(3)); + } + + #[Ticket('https://github.com/sebastianbergmann/phpunit/issues/6174')] + final public function testIssue6174(): void + { + $double = $this->createTestDouble(Issue6174::class); + + $double->method('methodNullDefault')->willReturnMap( + [ + ['A', null, 'result'], + ], + ); + + $this->assertSame('result', $double->methodNullDefault('A')); + + $double = $this->createTestDouble(Issue6174::class); + + $double->method('methodNullDefault')->willReturnMap( + [ + ['A', 'result'], + ], + ); + + $this->assertSame('result', $double->methodNullDefault('A')); + + $double = $this->createTestDouble(Issue6174::class); + + $double->method('methodStringDefault')->willReturnMap( + [ + ['A', 'result'], + ], + ); + + $this->assertSame('result', $double->methodStringDefault('A')); + + $double = $this->createTestDouble(Issue6174::class); + + $double->method('methodStringDefault')->willReturnMap( + [ + [null, 'result'], + ], + ); + + $this->assertSame('result', $double->methodStringDefault(null)); + } + + final public function testMethodCanBeConfiguredToReturnValuesUsingCallback(): void + { + $double = $this->createTestDouble(InterfaceWithReturnTypeDeclaration::class); + + $double->method('doSomethingElse')->willReturnCallback( + static function (int $x) + { + return match ($x) { + 1 => 2, + 3 => 4, + }; + }, + ); + + $this->assertSame(2, $double->doSomethingElse(1)); + $this->assertSame(4, $double->doSomethingElse(3)); + } + + final public function testMethodCanBeConfiguredToReturnDifferentValuesOnConsecutiveCalls(): void + { + $double = $this->createTestDouble(InterfaceWithReturnTypeDeclaration::class); + + $double->method('doSomething')->willReturn(false, true, false, true); + + $this->assertFalse($double->doSomething()); + $this->assertTrue($double->doSomething()); + $this->assertFalse($double->doSomething()); + $this->assertTrue($double->doSomething()); + } + + final public function testMethodConfiguredToReturnDifferentValuesOnConsecutiveCallsCannotBeCalledMoreOftenThanReturnValuesHaveBeenConfigured(): void + { + $double = $this->createTestDouble(InterfaceWithReturnTypeDeclaration::class); + + $double->method('doSomething')->willReturn(false, true); + + $this->assertFalse($double->doSomething()); + $this->assertTrue($double->doSomething()); + + $this->expectException(NoMoreReturnValuesConfiguredException::class); + $this->expectExceptionMessage('Only 2 return values have been configured for PHPUnit\TestFixture\MockObject\InterfaceWithReturnTypeDeclaration::doSomething()'); + + $double->doSomething(); + } + + final public function testMethodCanBeConfiguredToReturnDifferentValuesAndThrowExceptionsOnConsecutiveCalls(): void + { + $double = $this->createTestDouble(InterfaceWithReturnTypeDeclaration::class); + + $double->method('doSomething')->willReturnOnConsecutiveCalls( + false, + true, + $this->throwException(new Exception), + ); + + $this->assertFalse($double->doSomething()); + $this->assertTrue($double->doSomething()); + + $this->expectException(Exception::class); + + $double->doSomething(); + } + + final public function testMethodCanBeConfiguredToThrowAnException(): void + { + $expectedException = new Exception('exception configured using throwException()'); + + $double = $this->createTestDouble(InterfaceWithReturnTypeDeclaration::class); + + $double->method('doSomething')->willThrowException($expectedException); + + try { + $double->doSomething(); + } catch (Exception $actualException) { + $this->assertSame($expectedException, $actualException); + + return; + } + + $this->fail(); + } + + final public function testMethodWithNeverReturnTypeDeclarationThrowsException(): void + { + $double = $this->createTestDouble(InterfaceWithNeverReturningMethod::class); + + $this->expectException(NeverReturningMethodException::class); + $this->expectExceptionMessage('Method PHPUnit\TestFixture\MockObject\InterfaceWithNeverReturningMethod::m() is declared to never return'); + + $double->m(); + } + + #[TestDox('Original __clone() method is not called by default when test double object is cloned')] + final public function testOriginalCloneMethodIsNotCalledByDefaultWhenTestDoubleObjectIsCloned(): void + { + $double = clone $this->createTestDouble(ExtendableClassWithCloneMethod::class); + + $this->assertFalse($double->doSomething()); + } + + #[TestDox('Original __clone() method is not called by default when test double object is cloned (readonly class)')] + final public function testOriginalCloneMethodIsNotCalledByDefaultWhenTestDoubleObjectOfReadonlyClassIsCloned(): void + { + $double = clone $this->createTestDouble(ExtendableReadonlyClassWithCloneMethod::class); + + $this->assertFalse($double->doSomething()); + } + + public function testMethodNameCanOnlyBeConfiguredOnce(): void + { + $double = $this->createTestDouble(InterfaceWithReturnTypeDeclaration::class); + + $this->expectException(MethodNameAlreadyConfiguredException::class); + + $double + ->method('doSomething') + ->method('doSomething') + ->willReturn(true); + } + + #[Ticket('https://github.com/sebastianbergmann/phpunit/issues/5874')] + public function testDoubledMethodsCanBeCalledFromDestructorOnTestDoubleCreatedByTheReturnValueGenerator(): void + { + $double = $this->createTestDouble(ExtendableClassCallingMethodInDestructor::class); + + $this->assertInstanceOf( + ExtendableClassCallingMethodInDestructor::class, + $double->doSomething(), + ); + } + + #[RequiresPhp('^8.4')] + public function testGetHookForPropertyOfInterfaceCanBeConfigured(): void + { + $double = $this->createTestDouble(InterfaceWithPropertyWithGetHook::class); + + $double->method(PropertyHook::get('property'))->willReturn('value'); + + $this->assertSame('value', $double->property); + } + + #[RequiresPhp('^8.4')] + public function testGetHookForPropertyOfExtendableClassCanBeConfigured(): void + { + $double = $this->createTestDouble(ExtendableClassWithPropertyWithGetHook::class); + + $double->method(PropertyHook::get('property'))->willReturn('value'); + + $this->assertSame('value', $double->property); + } + + /** + * @template RealInstanceType of object + * + * @param class-string $type + * + * @return (MockObject|Stub)&RealInstanceType + */ + abstract protected function createTestDouble(string $type): object; +} diff --git a/tests/unit/Framework/SkippedTestCaseTest.php b/tests/unit/Framework/SkippedTestCaseTest.php deleted file mode 100644 index d4017667c6f..00000000000 --- a/tests/unit/Framework/SkippedTestCaseTest.php +++ /dev/null @@ -1,90 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use function array_shift; -use function sprintf; -use PHPUnit\Runner\BaseTestRunner; - -final class SkippedTestCaseTest extends TestCase -{ - public function testDefaults(): void - { - $testCase = new SkippedTestCase( - 'Foo', - 'testThatBars' - ); - - $this->assertSame('', $testCase->getMessage()); - } - - public function testGetNameReturnsClassAndMethodName(): void - { - $className = 'Foo'; - $methodName = 'testThatBars'; - - $testCase = new SkippedTestCase( - $className, - $methodName - ); - - $name = sprintf( - '%s::%s', - $className, - $methodName - ); - - $this->assertSame($name, $testCase->getName()); - } - - public function testGetMessageReturnsMessage(): void - { - $message = 'Somehow skipped, right?'; - - $testCase = new SkippedTestCase( - 'Foo', - 'testThatBars', - $message - ); - - $this->assertSame($message, $testCase->getMessage()); - } - - public function testRunMarksTestAsSkipped(): void - { - $className = 'Foo'; - $methodName = 'testThatBars'; - $message = 'Somehow skipped, right?'; - - $testCase = new SkippedTestCase( - $className, - $methodName, - $message - ); - - $result = $testCase->run(); - - $this->assertSame(BaseTestRunner::STATUS_SKIPPED, $testCase->getStatus()); - $this->assertSame(1, $result->skippedCount()); - - $failures = $result->skipped(); - - $failure = array_shift($failures); - - $name = sprintf( - '%s::%s', - $className, - $methodName - ); - - $this->assertSame($name, $failure->getTestName()); - $this->assertSame($message, $failure->exceptionMessage()); - } -} diff --git a/tests/unit/Framework/TestBuilderTest.php b/tests/unit/Framework/TestBuilderTest.php index ae962074555..61e55b40863 100644 --- a/tests/unit/Framework/TestBuilderTest.php +++ b/tests/unit/Framework/TestBuilderTest.php @@ -9,115 +9,90 @@ */ namespace PHPUnit\Framework; -use function assert; -use PHPUnit\Framework\MockObject\MockObject; -use PHPUnit\TestFixture\EmptyDataProviderTest; -use PHPUnit\TestFixture\ModifiedConstructorTestCase; -use PHPUnit\TestFixture\TestWithAnnotations; +use function iterator_to_array; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\TestFixture\TestBuilder\TestWithClassLevelIsolationAttributes; +use PHPUnit\TestFixture\TestBuilder\TestWithDataProvider; +use PHPUnit\TestFixture\TestBuilder\TestWithMethodLevelIsolationAttributes; +use PHPUnit\TestFixture\TestBuilder\TestWithoutIsolationAttributes; use ReflectionClass; -/** - * @covers \PHPUnit\Framework\TestBuilder - */ +#[CoversClass(TestBuilder::class)] +#[Small] final class TestBuilderTest extends TestCase { - public function testCreateTestForConstructorlessTestClass(): void + public function testBuildsTestWithoutMetadataForIsolation(): void { - $reflector = $this->getMockBuilder(ReflectionClass::class) - ->setConstructorArgs([$this]) - ->getMock(); + $test = (new TestBuilder)->build( + new ReflectionClass(TestWithoutIsolationAttributes::class), + 'testOne', + ); + + $this->assertInstanceOf(TestWithoutIsolationAttributes::class, $test); - assert($reflector instanceof MockObject); - assert($reflector instanceof ReflectionClass); + $test = $test->valueObjectForEvents(); - $reflector->expects($this->once()) - ->method('getConstructor') - ->willReturn(null); + $this->assertSame(TestWithoutIsolationAttributes::class, $test->className()); + $this->assertSame('testOne', $test->methodName()); + $this->assertTrue($test->metadata()->isBackupGlobals()->isEmpty()); + $this->assertTrue($test->metadata()->isBackupStaticProperties()->isEmpty()); + $this->assertTrue($test->metadata()->isRunTestsInSeparateProcesses()->isEmpty()); + } - $reflector->expects($this->once()) - ->method('isInstantiable') - ->willReturn(true); + public function testBuildsTestWithClassLevelMetadataForIsolation(): void + { + $test = (new TestBuilder)->build( + new ReflectionClass(TestWithClassLevelIsolationAttributes::class), + 'testOne', + ); - $reflector->expects($this->once()) - ->method('getName') - ->willReturn(__CLASS__); + $this->assertInstanceOf(TestWithClassLevelIsolationAttributes::class, $test); - $this->expectException(Exception::class); - $this->expectExceptionMessage('No valid test provided.'); + $test = $test->valueObjectForEvents(); - (new TestBuilder)->build($reflector, 'TestForConstructorlessTestClass'); + $this->assertSame(TestWithClassLevelIsolationAttributes::class, $test->className()); + $this->assertSame('testOne', $test->methodName()); + $this->assertTrue($test->metadata()->isBackupGlobals()->asArray()[0]->enabled()); + $this->assertTrue($test->metadata()->isBackupStaticProperties()->asArray()[0]->enabled()); + $this->assertTrue($test->metadata()->isRunTestsInSeparateProcesses()->isNotEmpty()); } - public function testCreateTestForNotInstantiableTestClass(): void + public function testBuildsTestWithMethodLevelMetadataForIsolation(): void { - $reflector = $this->getMockBuilder(ReflectionClass::class) - ->setConstructorArgs([$this]) - ->getMock(); + $test = (new TestBuilder)->build( + new ReflectionClass(TestWithMethodLevelIsolationAttributes::class), + 'testOne', + ); - assert($reflector instanceof MockObject); - assert($reflector instanceof ReflectionClass); + $this->assertInstanceOf(TestWithMethodLevelIsolationAttributes::class, $test); - $reflector->expects($this->once()) - ->method('isInstantiable') - ->willReturn(false); + $test = $test->valueObjectForEvents(); - $reflector->expects($this->once()) - ->method('getName') - ->willReturn('foo'); - - $test = (new TestBuilder)->build($reflector, 'TestForNonInstantiableTestClass'); - $this->assertInstanceOf(WarningTestCase::class, $test); - /* @var WarningTestCase $test */ - $this->assertSame('Cannot instantiate class "foo".', $test->getMessage()); + $this->assertSame(TestWithMethodLevelIsolationAttributes::class, $test->className()); + $this->assertSame('testOne', $test->methodName()); + $this->assertTrue($test->metadata()->isBackupGlobals()->asArray()[0]->enabled()); + $this->assertTrue($test->metadata()->isBackupStaticProperties()->asArray()[0]->enabled()); + $this->assertTrue($test->metadata()->isRunInSeparateProcess()->isNotEmpty()); } - public function testCreateTestForTestClassWithModifiedConstructor(): void + public function testBuildsTestWithDataProvider(): void { - $test = (new TestBuilder)->build(new ReflectionClass(ModifiedConstructorTestCase::class), 'testCase'); - $this->assertInstanceOf(ModifiedConstructorTestCase::class, $test); - } + $test = (new TestBuilder)->build( + new ReflectionClass(TestWithDataProvider::class), + 'testOne', + ); - public function testCreateWithEmptyData(): void - { - $test = (new TestBuilder)->build(new ReflectionClass(EmptyDataProviderTest::class), 'testCase'); $this->assertInstanceOf(DataProviderTestSuite::class, $test); - /* @var DataProviderTestSuite $test */ - $this->assertInstanceOf(SkippedTestCase::class, $test->getGroupDetails()['default'][0]); - } - /** - * @dataProvider provideWithAnnotations - */ - public function testWithAnnotations(string $methodName): void - { - $test = (new TestBuilder)->build(new ReflectionClass(TestWithAnnotations::class), $methodName); - $this->assertInstanceOf(TestWithAnnotations::class, $test); - } + $test = iterator_to_array($test)[0]; - public function provideWithAnnotations(): array - { - return [ - ['testThatInteractsWithGlobalVariables'], - ['testThatInteractsWithStaticAttributes'], - ['testInSeparateProcess'], - ]; - } + $this->assertInstanceOf(TestWithDataProvider::class, $test); - /** - * @dataProvider provideWithAnnotationsAndDataProvider - */ - public function testWithAnnotationsAndDataProvider(string $methodName): void - { - $test = (new TestBuilder)->build(new ReflectionClass(TestWithAnnotations::class), $methodName); - $this->assertInstanceOf(DataProviderTestSuite::class, $test); - } + $test = $test->valueObjectForEvents(); - public function provideWithAnnotationsAndDataProvider(): array - { - return [ - ['testThatInteractsWithGlobalVariablesWithDataProvider'], - ['testThatInteractsWithStaticAttributesWithDataProvider'], - ['testInSeparateProcessWithDataProvider'], - ]; + $this->assertSame(TestWithDataProvider::class, $test->className()); + $this->assertSame('testOne', $test->methodName()); + $this->assertTrue($test->testData()->hasDataFromDataProvider()); } } diff --git a/tests/unit/Framework/TestCaseTest.php b/tests/unit/Framework/TestCaseTest.php index 65381396afd..ad8631e0199 100644 --- a/tests/unit/Framework/TestCaseTest.php +++ b/tests/unit/Framework/TestCaseTest.php @@ -9,62 +9,27 @@ */ namespace PHPUnit\Framework; -use const E_USER_DEPRECATED; -use const E_USER_ERROR; -use const E_USER_NOTICE; -use const E_USER_WARNING; -use const PHP_EOL; -use function array_map; -use function get_class; -use function getcwd; -use function ini_get; -use function ini_set; -use function trigger_error; -use DependencyFailureTest; -use DependencyInputTest; -use DependencyOnClassTest; -use DependencySuccessTest; -use InvalidArgumentException; -use PHPUnit\Framework\MockObject\MockObject; -use PHPUnit\Framework\MockObject\Stub; -use PHPUnit\Runner\BaseTestRunner; -use PHPUnit\TestFixture\ChangeCurrentWorkingDirectoryTest; -use PHPUnit\TestFixture\ClassWithScalarTypeDeclarations; -use PHPUnit\TestFixture\DoNoAssertionTestCase; -use PHPUnit\TestFixture\ExceptionInAssertPostConditionsTest; -use PHPUnit\TestFixture\ExceptionInAssertPreConditionsTest; -use PHPUnit\TestFixture\ExceptionInSetUpTest; -use PHPUnit\TestFixture\ExceptionInTearDownTest; -use PHPUnit\TestFixture\ExceptionInTest; -use PHPUnit\TestFixture\ExceptionInTestDetectedInTeardown; -use PHPUnit\TestFixture\Failure; -use PHPUnit\TestFixture\IsolationTest; -use PHPUnit\TestFixture\Mockable; -use PHPUnit\TestFixture\NoArgTestCaseTest; -use PHPUnit\TestFixture\OutputTestCase; -use PHPUnit\TestFixture\RequirementsTest; -use PHPUnit\TestFixture\Singleton; -use PHPUnit\TestFixture\Success; -use PHPUnit\TestFixture\TestAutoreferenced; -use PHPUnit\TestFixture\TestError; -use PHPUnit\TestFixture\TestIncomplete; -use PHPUnit\TestFixture\TestSkipped; +use function sprintf; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\ExcludeGlobalVariableFromBackup; use PHPUnit\TestFixture\TestWithDifferentNames; -use PHPUnit\TestFixture\TestWithDifferentOutput; -use PHPUnit\TestFixture\TestWithDifferentSizes; -use PHPUnit\TestFixture\TestWithDifferentStatuses; -use PHPUnit\TestFixture\ThrowExceptionTestCase; -use PHPUnit\TestFixture\ThrowNoExceptionTestCase; -use PHPUnit\TestFixture\WasRun; -use PHPUnit\Util\Test as TestUtil; -use RuntimeException; -use TypeError; +#[CoversClass(TestCase::class)] +#[ExcludeGlobalVariableFromBackup('i')] +#[ExcludeGlobalVariableFromBackup('singleton')] class TestCaseTest extends TestCase { - protected static $testStatic = 456; + protected static int $testStatic = 456; - protected $backupGlobalsExcludeList = ['i', 'singleton']; + public static function provideDataSetAsStringWithDataProvider(): iterable + { + yield ['', 'dataSet', []]; + + yield ["#0 with data ('foo', 'bar')", 0, ['foo', 'bar']]; + + yield ["@dataSet with data ('foo', 'bar')", 'dataSet', ['foo', 'bar']]; + } public static function setUpBeforeClass(): void { @@ -90,1238 +55,51 @@ public static function tearDownAfterClass(): void $_SERVER['f'], $_FILES['g'], $_REQUEST['h'], - $GLOBALS['i'] + $GLOBALS['i'], ); } - /** - * @testdox TestCase::toSring() - */ public function testCaseToString(): void { $this->assertEquals( - 'PHPUnit\Framework\TestCaseTest::testCaseToString', - $this->toString() + sprintf( + '%s::testCaseToString', + self::class, + ), + $this->toString(), ); } - /** - * @testdox TestCase has sensible defaults for execution reordering - */ public function testCaseDefaultExecutionOrderDependencies(): void { $this->assertInstanceOf(Reorderable::class, $this); $this->assertEquals( - [new ExecutionOrderDependency(get_class($this), 'testCaseDefaultExecutionOrderDependencies')], - $this->provides() + [new ExecutionOrderDependency(static::class, 'testCaseDefaultExecutionOrderDependencies')], + $this->provides(), ); $this->assertEquals( [], - $this->requires() - ); - } - - public function testSuccess(): void - { - $test = new Success; - $result = $test->run(); - - $this->assertEquals(BaseTestRunner::STATUS_PASSED, $test->getStatus()); - $this->assertEquals(0, $result->errorCount()); - $this->assertEquals(0, $result->failureCount()); - $this->assertEquals(0, $result->skippedCount()); - $this->assertCount(1, $result); - } - - public function testFailure(): void - { - $test = new Failure; - $result = $test->run(); - - $this->assertEquals(BaseTestRunner::STATUS_FAILURE, $test->getStatus()); - $this->assertEquals(0, $result->errorCount()); - $this->assertEquals(1, $result->failureCount()); - $this->assertEquals(0, $result->skippedCount()); - $this->assertCount(1, $result); - } - - public function testError(): void - { - $test = new TestError; - $result = $test->run(); - - $this->assertEquals(BaseTestRunner::STATUS_ERROR, $test->getStatus()); - $this->assertEquals(1, $result->errorCount()); - $this->assertEquals(0, $result->failureCount()); - $this->assertEquals(0, $result->skippedCount()); - $this->assertCount(1, $result); - } - - public function testSkipped(): void - { - $test = new TestSkipped; - $result = $test->run(); - - $this->assertEquals(BaseTestRunner::STATUS_SKIPPED, $test->getStatus()); - $this->assertEquals('Skipped test', $test->getStatusMessage()); - $this->assertEquals(0, $result->errorCount()); - $this->assertEquals(0, $result->failureCount()); - $this->assertEquals(1, $result->skippedCount()); - $this->assertCount(1, $result); - } - - public function testIncomplete(): void - { - $test = new TestIncomplete; - $result = $test->run(); - - $this->assertEquals(BaseTestRunner::STATUS_INCOMPLETE, $test->getStatus()); - $this->assertEquals('Incomplete test', $test->getStatusMessage()); - $this->assertEquals(0, $result->errorCount()); - $this->assertEquals(0, $result->failureCount()); - $this->assertEquals(0, $result->skippedCount()); - $this->assertCount(1, $result); - } - - public function testExceptionInSetUp(): void - { - $test = new ExceptionInSetUpTest('testSomething'); - $test->run(); - - $this->assertTrue($test->setUp); - $this->assertFalse($test->assertPreConditions); - $this->assertFalse($test->testSomething); - $this->assertFalse($test->assertPostConditions); - $this->assertTrue($test->tearDown); - } - - public function testExceptionInAssertPreConditions(): void - { - $test = new ExceptionInAssertPreConditionsTest('testSomething'); - $test->run(); - - $this->assertTrue($test->setUp); - $this->assertTrue($test->assertPreConditions); - $this->assertFalse($test->testSomething); - $this->assertFalse($test->assertPostConditions); - $this->assertTrue($test->tearDown); - } - - public function testExceptionInTest(): void - { - $test = new ExceptionInTest('testSomething'); - $test->run(); - - $this->assertTrue($test->setUp); - $this->assertTrue($test->assertPreConditions); - $this->assertTrue($test->testSomething); - $this->assertFalse($test->assertPostConditions); - $this->assertTrue($test->tearDown); - } - - public function testExceptionInAssertPostConditions(): void - { - $test = new ExceptionInAssertPostConditionsTest('testSomething'); - $test->run(); - - $this->assertTrue($test->setUp); - $this->assertTrue($test->assertPreConditions); - $this->assertTrue($test->testSomething); - $this->assertTrue($test->assertPostConditions); - $this->assertTrue($test->tearDown); - } - - public function testExceptionInTearDown(): void - { - $test = new ExceptionInTearDownTest('testSomething'); - $test->run(); - - $this->assertTrue($test->setUp); - $this->assertTrue($test->assertPreConditions); - $this->assertTrue($test->testSomething); - $this->assertTrue($test->assertPostConditions); - $this->assertTrue($test->tearDown); - $this->assertEquals(BaseTestRunner::STATUS_ERROR, $test->getStatus()); - $this->assertSame('throw Exception in tearDown()', $test->getStatusMessage()); - } - - public function testExceptionInTestIsDetectedInTeardown(): void - { - $test = new ExceptionInTestDetectedInTeardown('testSomething'); - $test->run(); - - $this->assertTrue($test->exceptionDetected); - } - - public function testNoArgTestCasePasses(): void - { - $result = new TestResult; - $t = new TestSuite(NoArgTestCaseTest::class); - - $t->run($result); - - $this->assertCount(1, $result); - $this->assertEquals(0, $result->failureCount()); - $this->assertEquals(0, $result->errorCount()); - } - - public function testWasRun(): void - { - $test = new WasRun; - $test->run(); - - $this->assertTrue($test->wasRun); - } - - public function testException(): void - { - $test = new ThrowExceptionTestCase('test'); - $test->expectException(RuntimeException::class); - - $result = $test->run(); - - $this->assertCount(1, $result); - $this->assertTrue($result->wasSuccessful()); - } - - public function testExpectExceptionAllowsAccessingExpectedException(): void - { - $exception = RuntimeException::class; - - $test = new ThrowExceptionTestCase('test'); - - $test->expectException($exception); - - $this->assertSame($exception, $test->getExpectedException()); - } - - public function testExpectExceptionCodeWithSameCode(): void - { - $test = new ThrowExceptionTestCase('test'); - - $test->expectExceptionCode(0); - - $result = $test->run(); - - $this->assertCount(1, $result); - $this->assertTrue($result->wasSuccessful()); - } - - public function testExpectExceptionCodeWithDifferentCode(): void - { - $test = new ThrowExceptionTestCase('test'); - - $test->expectExceptionCode(9000); - - $result = $test->run(); - - $this->assertCount(1, $result); - $this->assertFalse($result->wasSuccessful()); - } - - public function testExpectExceptionCodeAllowsAccessingExpectedExceptionCode(): void - { - $code = 9000; - - $test = new ThrowExceptionTestCase('test'); - - $test->expectExceptionCode($code); - - $this->assertSame($code, $test->getExpectedExceptionCode()); - } - - public function testExceptionWithEmptyMessage(): void - { - $test = new ThrowExceptionTestCase('test'); - $test->expectException(RuntimeException::class); - - $result = $test->run(); - - $this->assertCount(1, $result); - $this->assertTrue($result->wasSuccessful()); - } - - public function testExceptionWithNullMessage(): void - { - $test = new ThrowExceptionTestCase('test'); - $test->expectException(RuntimeException::class); - - $result = $test->run(); - - $this->assertCount(1, $result); - $this->assertTrue($result->wasSuccessful()); - } - - public function testExceptionWithMessage(): void - { - $test = new ThrowExceptionTestCase('test'); - $test->expectException(RuntimeException::class); - $test->expectExceptionMessage('A runtime error occurred'); - - $result = $test->run(); - - $this->assertCount(1, $result); - $this->assertTrue($result->wasSuccessful()); - } - - public function testExceptionWithWrongMessage(): void - { - $test = new ThrowExceptionTestCase('test'); - $test->expectException(RuntimeException::class); - $test->expectExceptionMessage('A logic error occurred'); - - $result = $test->run(); - - $this->assertEquals(1, $result->failureCount()); - $this->assertCount(1, $result); - $this->assertEquals( - "Failed asserting that exception message 'A runtime error occurred' contains 'A logic error occurred'.", - $test->getStatusMessage() - ); - } - - public function testExpectExceptionMessageAllowsAccessingExpectedExceptionMessage(): void - { - $message = 'A runtime error occurred'; - - $test = new ThrowExceptionTestCase('test'); - - $test->expectExceptionMessage($message); - - $this->assertSame($message, $test->getExpectedExceptionMessage()); - } - - public function testExceptionWithRegexpMessage(): void - { - $test = new ThrowExceptionTestCase('test'); - $test->expectException(RuntimeException::class); - $test->expectExceptionMessageMatches('/runtime .*? occurred/'); - - $result = $test->run(); - - $this->assertCount(1, $result); - $this->assertTrue($result->wasSuccessful()); - } - - public function testexpectExceptionMessageMatchesAllowsAccessingExpectedExceptionRegExp(): void - { - $messageRegExp = '/runtime .*? occurred/'; - - $test = new ThrowExceptionTestCase('test'); - - $test->expectExceptionMessageMatches($messageRegExp); - - $this->assertSame($messageRegExp, $test->getExpectedExceptionMessageRegExp()); - } - - public function testExceptionWithWrongRegexpMessage(): void - { - $test = new ThrowExceptionTestCase('test'); - $test->expectException(RuntimeException::class); - $test->expectExceptionMessageMatches('/logic .*? occurred/'); - - $result = $test->run(); - - $this->assertEquals(1, $result->failureCount()); - $this->assertCount(1, $result); - $this->assertEquals( - "Failed asserting that exception message 'A runtime error occurred' matches '/logic .*? occurred/'.", - $test->getStatusMessage() - ); - } - - public function testExceptionWithInvalidRegexpMessage(): void - { - $test = new ThrowExceptionTestCase('test'); - $test->expectException(RuntimeException::class); - $test->expectExceptionMessageMatches('#runtime .*? occurred/'); - - $test->run(); - - $this->assertEquals( - "Invalid expected exception message regex given: '#runtime .*? occurred/'", - $test->getStatusMessage() - ); - } - - public function testExpectExceptionObjectWithDifferentExceptionClass(): void - { - $exception = new InvalidArgumentException( - 'Cannot compute at this time.', - 9000 - ); - - $test = new ThrowExceptionTestCase('testWithExpectExceptionObject'); - - $test->expectExceptionObject($exception); - - $result = $test->run(); - - $this->assertCount(1, $result); - $this->assertFalse($result->wasSuccessful()); - } - - public function testExpectExceptionObjectWithDifferentExceptionMessage(): void - { - $exception = new RuntimeException( - 'This is fine!', - 9000 - ); - - $test = new ThrowExceptionTestCase('testWithExpectExceptionObject'); - - $test->expectExceptionObject($exception); - - $result = $test->run(); - - $this->assertCount(1, $result); - $this->assertFalse($result->wasSuccessful()); - } - - public function testExpectExceptionObjectWithDifferentExceptionCode(): void - { - $exception = new RuntimeException( - 'Cannot compute at this time.', - 9001 - ); - - $test = new ThrowExceptionTestCase('testWithExpectExceptionObject'); - - $test->expectExceptionObject($exception); - - $result = $test->run(); - - $this->assertCount(1, $result); - $this->assertFalse($result->wasSuccessful()); - } - - public function testExpectExceptionObjectWithEqualException(): void - { - $exception = new RuntimeException( - 'Cannot compute at this time', - 9000 - ); - - $test = new ThrowExceptionTestCase('testWithExpectExceptionObject'); - - $test->expectExceptionObject($exception); - - $result = $test->run(); - - $this->assertCount(1, $result); - $this->assertTrue($result->wasSuccessful()); - } - - public function testExpectExceptionObjectAllowsAccessingExpectedExceptionDetails(): void - { - $exception = new RuntimeException( - 'Cannot compute at this time', - 9000 - ); - - $test = new ThrowExceptionTestCase('testWithExpectExceptionObject'); - - $test->expectExceptionObject($exception); - - $this->assertSame(get_class($exception), $test->getExpectedException()); - $this->assertSame($exception->getCode(), $test->getExpectedExceptionCode()); - $this->assertSame($exception->getMessage(), $test->getExpectedExceptionMessage()); - } - - public function testNoException(): void - { - $test = new ThrowNoExceptionTestCase('test'); - $test->expectException(RuntimeException::class); - - $result = $test->run(); - - $this->assertEquals(1, $result->failureCount()); - $this->assertCount(1, $result); - } - - public function testWrongException(): void - { - $test = new ThrowExceptionTestCase('test'); - $test->expectException(InvalidArgumentException::class); - - $result = $test->run(); - - $this->assertEquals(1, $result->failureCount()); - $this->assertCount(1, $result); - } - - public function testDoesNotPerformAssertions(): void - { - $test = new DoNoAssertionTestCase('testNothing'); - $test->expectNotToPerformAssertions(); - - $result = $test->run(); - - $this->assertEquals(0, $result->riskyCount()); - $this->assertCount(1, $result); - } - - /** - * @backupGlobals enabled - */ - public function testGlobalsBackupPre(): void - { - global $a; - global $i; - - $this->assertEquals('a', $a); - $this->assertEquals('a', $GLOBALS['a']); - $this->assertEquals('b', $_ENV['b']); - $this->assertEquals('c', $_POST['c']); - $this->assertEquals('d', $_GET['d']); - $this->assertEquals('e', $_COOKIE['e']); - $this->assertEquals('f', $_SERVER['f']); - $this->assertEquals('g', $_FILES['g']); - $this->assertEquals('h', $_REQUEST['h']); - $this->assertEquals('i', $i); - $this->assertEquals('i', $GLOBALS['i']); - - $GLOBALS['a'] = 'aa'; - $GLOBALS['foo'] = 'bar'; - $_ENV['b'] = 'bb'; - $_POST['c'] = 'cc'; - $_GET['d'] = 'dd'; - $_COOKIE['e'] = 'ee'; - $_SERVER['f'] = 'ff'; - $_FILES['g'] = 'gg'; - $_REQUEST['h'] = 'hh'; - $GLOBALS['i'] = 'ii'; - - $this->assertEquals('aa', $a); - $this->assertEquals('aa', $GLOBALS['a']); - $this->assertEquals('bar', $GLOBALS['foo']); - $this->assertEquals('bb', $_ENV['b']); - $this->assertEquals('cc', $_POST['c']); - $this->assertEquals('dd', $_GET['d']); - $this->assertEquals('ee', $_COOKIE['e']); - $this->assertEquals('ff', $_SERVER['f']); - $this->assertEquals('gg', $_FILES['g']); - $this->assertEquals('hh', $_REQUEST['h']); - $this->assertEquals('ii', $i); - $this->assertEquals('ii', $GLOBALS['i']); - } - - /** - * @depends testGlobalsBackupPre - */ - public function testGlobalsBackupPost(): void - { - global $a; - global $i; - - $this->assertEquals('a', $a); - $this->assertEquals('a', $GLOBALS['a']); - $this->assertEquals('b', $_ENV['b']); - $this->assertEquals('c', $_POST['c']); - $this->assertEquals('d', $_GET['d']); - $this->assertEquals('e', $_COOKIE['e']); - $this->assertEquals('f', $_SERVER['f']); - $this->assertEquals('g', $_FILES['g']); - $this->assertEquals('h', $_REQUEST['h']); - $this->assertEquals('ii', $i); - $this->assertEquals('ii', $GLOBALS['i']); - - $this->assertArrayNotHasKey('foo', $GLOBALS); - } - - /** - * @backupGlobals enabled - * @backupStaticAttributes enabled - * @depends testGlobalsBackupPost - * - * @doesNotPerformAssertions - */ - public function testStaticAttributesBackupPre(): void - { - $GLOBALS['singleton'] = Singleton::getInstance(); - $GLOBALS['i'] = 'set by testStaticAttributesBackupPre'; - - $GLOBALS['j'] = 'reset by backup'; - self::$testStatic = 123; - } - - /** - * @depends testStaticAttributesBackupPre - */ - public function testStaticAttributesBackupPost(): void - { - // Snapshots made by @backupGlobals - $this->assertSame(Singleton::getInstance(), $GLOBALS['singleton']); - $this->assertSame('set by testStaticAttributesBackupPre', $GLOBALS['i']); - - // Reset global - $this->assertArrayNotHasKey('j', $GLOBALS); - - // Static reset to original state by @backupStaticAttributes - $this->assertSame(456, self::$testStatic); - } - - public function testIsInIsolationReturnsFalse(): void - { - $test = new IsolationTest('testIsInIsolationReturnsFalse'); - $result = $test->run(); - - $this->assertCount(1, $result); - $this->assertTrue($result->wasSuccessful()); - } - - public function testIsInIsolationReturnsTrue(): void - { - $test = new IsolationTest('testIsInIsolationReturnsTrue'); - $test->setRunTestInSeparateProcess(true); - $result = $test->run(); - - $this->assertCount(1, $result); - $this->assertTrue($result->wasSuccessful()); - } - - public function testExpectOutputStringFooActualFoo(): void - { - $test = new OutputTestCase('testExpectOutputStringFooActualFoo'); - $result = $test->run(); - - $this->assertCount(1, $result); - $this->assertTrue($result->wasSuccessful()); - } - - public function testExpectOutputStringFooActualBar(): void - { - $test = new OutputTestCase('testExpectOutputStringFooActualBar'); - $result = $test->run(); - - $this->assertCount(1, $result); - $this->assertFalse($result->wasSuccessful()); - } - - public function testExpectOutputRegexFooActualFoo(): void - { - $test = new OutputTestCase('testExpectOutputRegexFooActualFoo'); - $result = $test->run(); - - $this->assertCount(1, $result); - $this->assertTrue($result->wasSuccessful()); - } - - public function testExpectOutputRegexFooActualBar(): void - { - $test = new OutputTestCase('testExpectOutputRegexFooActualBar'); - $result = $test->run(); - - $this->assertCount(1, $result); - $this->assertFalse($result->wasSuccessful()); - } - - public function testSkipsIfRequiresHigherVersionOfPHPUnit(): void - { - $test = new RequirementsTest('testAlwaysSkip'); - $result = $test->run(); - - $this->assertEquals(1, $result->skippedCount()); - $this->assertEquals( - 'PHPUnit >= 1111111 is required.', - $test->getStatusMessage() + $this->requires(), ); } - public function testSkipsIfRequiresHigherVersionOfPHP(): void - { - $test = new RequirementsTest('testAlwaysSkip2'); - $result = $test->run(); - - $this->assertEquals(1, $result->skippedCount()); - $this->assertEquals( - 'PHP >= 9999999 is required.', - $test->getStatusMessage() - ); - } - - public function testSkipsIfRequiresNonExistingOs(): void - { - $test = new RequirementsTest('testAlwaysSkip3'); - $result = $test->run(); - - $this->assertEquals(1, $result->skippedCount()); - $this->assertEquals( - 'Operating system matching /DOESNOTEXIST/i is required.', - $test->getStatusMessage() - ); - } - - public function testSkipsIfRequiresNonExistingOsFamily(): void - { - $test = new RequirementsTest('testAlwaysSkip4'); - $result = $test->run(); - - $this->assertEquals(1, $result->skippedCount()); - $this->assertEquals( - 'Operating system DOESNOTEXIST is required.', - $test->getStatusMessage() - ); - } - - public function testSkipsIfRequiresNonExistingFunction(): void - { - $test = new RequirementsTest('testNine'); - $result = $test->run(); - - $this->assertEquals(1, $result->skippedCount()); - $this->assertEquals( - 'Function testFunc is required.', - $test->getStatusMessage() - ); - } - - public function testSkipsIfRequiresNonExistingExtension(): void - { - $test = new RequirementsTest('testTen'); - $test->run(); - - $this->assertEquals( - 'Extension testExt is required.', - $test->getStatusMessage() - ); - } - - public function testSkipsIfRequiresExtensionWithAMinimumVersion(): void - { - $test = new RequirementsTest('testSpecificExtensionVersion'); - $test->run(); - - $this->assertEquals( - 'Extension testExt >= 1.8.0 is required.', - $test->getStatusMessage() - ); - } - - public function testSkipsProvidesMessagesForAllSkippingReasons(): void - { - $test = new RequirementsTest('testAllPossibleRequirements'); - $test->run(); - - $this->assertEquals( - 'PHP >= 99-dev is required.' . PHP_EOL . - 'PHPUnit >= 99-dev is required.' . PHP_EOL . - 'Operating system matching /DOESNOTEXIST/i is required.' . PHP_EOL . - 'Function testFuncOne is required.' . PHP_EOL . - 'Function testFunc2 is required.' . PHP_EOL . - 'Setting "not_a_setting" must be "Off".' . PHP_EOL . - 'Extension testExtOne is required.' . PHP_EOL . - 'Extension testExt2 is required.' . PHP_EOL . - 'Extension testExtThree >= 2.0 is required.', - $test->getStatusMessage() - ); - } - - public function testRequiringAnExistingMethodDoesNotSkip(): void - { - $test = new RequirementsTest('testExistingMethod'); - $result = $test->run(); - $this->assertEquals(0, $result->skippedCount()); - } - - public function testRequiringAnExistingFunctionDoesNotSkip(): void - { - $test = new RequirementsTest('testExistingFunction'); - $result = $test->run(); - $this->assertEquals(0, $result->skippedCount()); - } - - public function testRequiringAnExistingExtensionDoesNotSkip(): void - { - $test = new RequirementsTest('testExistingExtension'); - $result = $test->run(); - $this->assertEquals(0, $result->skippedCount()); - } - - public function testRequiringAnExistingOsDoesNotSkip(): void - { - $test = new RequirementsTest('testExistingOs'); - $result = $test->run(); - $this->assertEquals(0, $result->skippedCount()); - } - - public function testRequiringASetting(): void - { - $test = new RequirementsTest('testSettingDisplayErrorsOn'); - - // Get this so we can return it to whatever it was before the test. - $displayErrorsVal = ini_get('display_errors'); - - ini_set('display_errors', 'On'); - $result = $test->run(); - $this->assertEquals(0, $result->skippedCount()); - - ini_set('display_errors', 'Off'); - $result = $test->run(); - $this->assertEquals(1, $result->skippedCount()); - - ini_set('display_errors', $displayErrorsVal); - } - - public function testCurrentWorkingDirectoryIsRestored(): void - { - $expectedCwd = getcwd(); - - $test = new ChangeCurrentWorkingDirectoryTest('testSomethingThatChangesTheCwd'); - $test->run(); - - $this->assertSame($expectedCwd, getcwd()); - } - - /** - * @requires PHP 7 - */ - public function testTypeErrorCanBeExpected(): void - { - $o = new ClassWithScalarTypeDeclarations; - - $this->expectException(TypeError::class); - - $o->foo(null, null); - } - - public function testCreateMockFromClassName(): void - { - $mock = $this->createMock(Mockable::class); - - $this->assertInstanceOf(Mockable::class, $mock); - $this->assertInstanceOf(MockObject::class, $mock); - } - - public function testCreateMockMocksAllMethods(): void - { - $mock = $this->createMock(Mockable::class); - - $this->assertNull($mock->mockableMethod()); - $this->assertNull($mock->anotherMockableMethod()); - } - - public function testCreateStubFromClassName(): void - { - $mock = $this->createStub(Mockable::class); - - $this->assertInstanceOf(Mockable::class, $mock); - $this->assertInstanceOf(Stub::class, $mock); - } - - public function testCreateStubStubsAllMethods(): void - { - $mock = $this->createStub(Mockable::class); - - $this->assertNull($mock->mockableMethod()); - $this->assertNull($mock->anotherMockableMethod()); - } - - public function testCreatePartialMockDoesNotMockAllMethods(): void - { - /** @var Mockable $mock */ - $mock = $this->createPartialMock(Mockable::class, ['mockableMethod']); - - $this->assertNull($mock->mockableMethod()); - $this->assertTrue($mock->anotherMockableMethod()); - } - - public function testCreatePartialMockCanMockNoMethods(): void - { - /** @var Mockable $mock */ - $mock = $this->createPartialMock(Mockable::class, []); - - $this->assertTrue($mock->mockableMethod()); - $this->assertTrue($mock->anotherMockableMethod()); - } - - public function testCreatePartialMockWithFakeMethods(): void - { - $test = new TestWithDifferentStatuses('testWithCreatePartialMockWarning'); - - $test->run(); - - $this->assertSame(BaseTestRunner::STATUS_WARNING, $test->getStatus()); - $this->assertFalse($test->hasFailed()); - } - - public function testCreatePartialMockWithRealMethods(): void - { - $test = new TestWithDifferentStatuses('testWithCreatePartialMockPassesNoWarning'); - - $test->run(); - - $this->assertSame(BaseTestRunner::STATUS_PASSED, $test->getStatus()); - $this->assertFalse($test->hasFailed()); - } - - public function testCreateMockSkipsConstructor(): void - { - $mock = $this->createMock(Mockable::class); - - $this->assertNull($mock->constructorArgs); - } - - public function testCreateMockDisablesOriginalClone(): void - { - $mock = $this->createMock(Mockable::class); - - $cloned = clone $mock; - $this->assertNull($cloned->cloned); - } - - public function testCreateStubSkipsConstructor(): void - { - $mock = $this->createStub(Mockable::class); - - $this->assertNull($mock->constructorArgs); - } - - public function testCreateStubDisablesOriginalClone(): void - { - $mock = $this->createStub(Mockable::class); - - $cloned = clone $mock; - $this->assertNull($cloned->cloned); - } - - public function testConfiguredMockCanBeCreated(): void - { - /** @var Mockable $mock */ - $mock = $this->createConfiguredMock( - Mockable::class, - [ - 'mockableMethod' => false, - ] - ); - - $this->assertFalse($mock->mockableMethod()); - $this->assertNull($mock->anotherMockableMethod()); - } - - public function testProvidingOfAutoreferencedArray(): void - { - $test = new TestAutoreferenced('testJsonEncodeException', $this->getAutoreferencedArray()); - $test->runBare(); - - $this->assertIsArray($test->myTestData); - $this->assertArrayHasKey('data', $test->myTestData); - $this->assertEquals($test->myTestData['data'][0], $test->myTestData['data']); - } - - public function testProvidingArrayThatMixesObjectsAndScalars(): void - { - $data = [ - [123], - ['foo'], - [$this->createMock(Mockable::class)], - [$this->createStub(Mockable::class)], - ]; - - $test = new TestAutoreferenced('testJsonEncodeException', [$data]); - $test->runBare(); - - $this->assertIsArray($test->myTestData); - $this->assertSame($data, $test->myTestData); - } - - public function testGettingNullTestResultObject(): void - { - $test = new Success; - $this->assertNull($test->getTestResultObject()); - } - - public function testSizeUnknown(): void - { - $test = new TestWithDifferentSizes('testWithSizeUnknown'); - - $this->assertFalse($test->hasSize()); - - $this->assertSame(TestUtil::UNKNOWN, $test->getSize()); - - $this->assertFalse($test->isLarge()); - $this->assertFalse($test->isMedium()); - $this->assertFalse($test->isSmall()); - } - - public function testSizeLarge(): void - { - $test = new TestWithDifferentSizes('testWithSizeLarge'); - - $this->assertTrue($test->hasSize()); - - $this->assertSame(TestUtil::LARGE, $test->getSize()); - - $this->assertTrue($test->isLarge()); - $this->assertFalse($test->isMedium()); - $this->assertFalse($test->isSmall()); - } - - public function testSizeMedium(): void - { - $test = new TestWithDifferentSizes('testWithSizeMedium'); - - $this->assertTrue($test->hasSize()); - - $this->assertSame(TestUtil::MEDIUM, $test->getSize()); - - $this->assertFalse($test->isLarge()); - $this->assertTrue($test->isMedium()); - $this->assertFalse($test->isSmall()); - } - - public function testSizeSmall(): void - { - $test = new TestWithDifferentSizes('testWithSizeSmall'); - - $this->assertTrue($test->hasSize()); - - $this->assertSame(TestUtil::SMALL, $test->getSize()); - - $this->assertFalse($test->isLarge()); - $this->assertFalse($test->isMedium()); - $this->assertTrue($test->isSmall()); - } - - public function testCanUseDependsToDependOnSuccessfulClass(): void - { - $result = new TestResult(); - $suite = new TestSuite(); - $suite->addTestSuite(DependencySuccessTest::class); - $suite->addTestSuite(DependencyFailureTest::class); - $suite->addTestSuite(DependencyOnClassTest::class); - $suite->run($result); - - // Confirm only the passing TestSuite::class has passed - $this->assertContains(DependencySuccessTest::class, $result->passedClasses()); - $this->assertNotContains(DependencyFailureTest::class, $result->passedClasses()); - - // Confirm the test depending on the passing TestSuite::class did run and has passed - $this->assertArrayHasKey(DependencyOnClassTest::class . '::testThatDependsOnASuccessfulClass', $result->passed()); - - // Confirm the test depending on the failing TestSuite::class has been warn-skipped - $skipped = array_map(function (TestFailure $t) { - return $t->getTestName(); - }, $result->skipped()); - $this->assertContains(DependencyOnClassTest::class . '::testThatDependsOnAFailingClass', $skipped); - } - public function testGetNameReturnsMethodName(): void { $methodName = 'testWithName'; $testCase = new TestWithDifferentNames($methodName); - $this->assertSame($methodName, $testCase->getName()); - } - - public function testGetNameReturnsEmptyStringAsDefault(): void - { - $testCase = new TestWithDifferentNames(); - - $this->assertSame('', $testCase->getName()); - } - - /** - * @dataProvider providerInvalidName - */ - public function testRunBareThrowsExceptionWhenTestHasInvalidName($name): void - { - $testCase = new TestWithDifferentNames($name); - - $this->expectException(Exception::class); - $this->expectExceptionMessage('PHPUnit\Framework\TestCase::$name must be a non-blank string.'); - - $testCase->runBare(); - } - - public function providerInvalidName(): array - { - return [ - 'null' => [null], - 'string-empty' => [''], - 'string-blank' => [' '], - ]; - } - - public function testHasFailedReturnsFalseWhenTestHasNotRunYet(): void - { - $test = new TestWithDifferentStatuses(); - - $this->assertSame(BaseTestRunner::STATUS_UNKNOWN, $test->getStatus()); - $this->assertFalse($test->hasFailed()); - } - - public function testHasFailedReturnsTrueWhenTestHasFailed(): void - { - $test = new TestWithDifferentStatuses('testThatFails'); - - $test->run(); - - $this->assertSame(BaseTestRunner::STATUS_FAILURE, $test->getStatus()); - $this->assertTrue($test->hasFailed()); - } - - public function testHasFailedReturnsTrueWhenTestHasErrored(): void - { - $test = new TestWithDifferentStatuses('testThatErrors'); - - $test->run(); - - $this->assertSame(BaseTestRunner::STATUS_ERROR, $test->getStatus()); - $this->assertTrue($test->hasFailed()); - } - - public function testHasFailedReturnsFalseWhenTestHasPassed(): void - { - $test = new TestWithDifferentStatuses('testThatPasses'); - - $test->run(); - - $this->assertSame(BaseTestRunner::STATUS_PASSED, $test->getStatus()); - $this->assertFalse($test->hasFailed()); - } - - public function testHasFailedReturnsFalseWhenTestHasBeenMarkedAsIncomplete(): void - { - $test = new TestWithDifferentStatuses('testThatIsMarkedAsIncomplete'); - - $test->run(); - - $this->assertSame(BaseTestRunner::STATUS_INCOMPLETE, $test->getStatus()); - $this->assertFalse($test->hasFailed()); - } - - public function testHasFailedReturnsFalseWhenTestHasBeenMarkedAsRisky(): void - { - $test = new TestWithDifferentStatuses('testThatIsMarkedAsRisky'); - - $test->run(); - - $this->assertSame(BaseTestRunner::STATUS_RISKY, $test->getStatus()); - $this->assertFalse($test->hasFailed()); - } - - public function testHasFailedReturnsFalseWhenTestHasBeenMarkedAsSkipped(): void - { - $test = new TestWithDifferentStatuses('testThatIsMarkedAsSkipped'); - - $test->run(); - - $this->assertSame(BaseTestRunner::STATUS_SKIPPED, $test->getStatus()); - $this->assertFalse($test->hasFailed()); - } - - public function testHasFailedReturnsFalseWhenTestHasEmittedWarning(): void - { - $test = new TestWithDifferentStatuses('testThatAddsAWarning'); - - $test->run(); - - $this->assertSame(BaseTestRunner::STATUS_WARNING, $test->getStatus()); - $this->assertFalse($test->hasFailed()); - } - - public function testHasOutputReturnsFalseWhenTestDoesNotGenerateOutput(): void - { - $test = new TestWithDifferentOutput('testThatDoesNotGenerateOutput'); - - $test->run(); - - $this->assertFalse($test->hasOutput()); - } - - public function testHasOutputReturnsFalseWhenTestExpectsOutputRegex(): void - { - $test = new TestWithDifferentOutput('testThatExpectsOutputRegex'); - - $test->run(); - - $this->assertFalse($test->hasOutput()); - } - - public function testHasOutputReturnsFalseWhenTestExpectsOutputString(): void - { - $test = new TestWithDifferentOutput('testThatExpectsOutputString'); - - $test->run(); - - $this->assertFalse($test->hasOutput()); - } - - public function testHasOutputReturnsTrueWhenTestGeneratesOutput(): void - { - $test = new TestWithDifferentOutput('testThatGeneratesOutput'); - - $test->run(); - - $this->assertTrue($test->hasOutput()); - } - - public function testDeprecationCanBeExpected(): void - { - $this->expectDeprecation(); - $this->expectDeprecationMessage('foo'); - $this->expectDeprecationMessageMatches('/foo/'); - - trigger_error('foo', E_USER_DEPRECATED); - } - - public function testNoticeCanBeExpected(): void - { - $this->expectNotice(); - $this->expectNoticeMessage('foo'); - $this->expectNoticeMessageMatches('/foo/'); - - trigger_error('foo', E_USER_NOTICE); - } - - public function testWarningCanBeExpected(): void - { - $this->expectWarning(); - $this->expectWarningMessage('foo'); - $this->expectWarningMessageMatches('/foo/'); - - trigger_error('foo', E_USER_WARNING); - } - - public function testErrorCanBeExpected(): void - { - $this->expectError(); - $this->expectErrorMessage('foo'); - $this->expectErrorMessageMatches('/foo/'); - - trigger_error('foo', E_USER_ERROR); - } - - public function testSetDependencyInput(): void - { - $test = new DependencyInputTest('testDependencyInputAsParameter'); - $test->setDependencyInput(['value from TestCaseTest']); - - $result = $test->run(); - - $this->assertEquals(BaseTestRunner::STATUS_PASSED, $test->getStatus()); - $this->assertEquals(0, $result->errorCount()); - $this->assertEquals(0, $result->failureCount()); - $this->assertEquals(0, $result->skippedCount()); - $this->assertCount(1, $result); + $this->assertSame($methodName, $testCase->nameWithDataSet()); } - /** - * @return array - */ - private function getAutoreferencedArray() + #[DataProvider('provideDataSetAsStringWithDataProvider')] + public function testDataSetAsStringWithData(string $expectedData, int|string $dataName, array $data): void { - $recursionData = []; - $recursionData[] = &$recursionData; + $testCase = new TestWithDifferentNames('testWithName'); + $testCase->setData($dataName, $data); - return [ - 'RECURSION' => [ - 'data' => $recursionData, - ], - ]; + $this->assertSame($expectedData, $testCase->dataSetAsStringWithData()); } } diff --git a/tests/unit/Framework/TestFailureTest.php b/tests/unit/Framework/TestFailureTest.php deleted file mode 100644 index 92025dfc0d4..00000000000 --- a/tests/unit/Framework/TestFailureTest.php +++ /dev/null @@ -1,145 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use PHPUnit\Framework\Error\Error; -use PHPUnit\TestFixture\NotSelfDescribingTest; -use SebastianBergmann\Comparator\ComparisonFailure; - -/** - * @small - */ -final class TestFailureTest extends TestCase -{ - public function testToString(): void - { - $test = new self(__FUNCTION__); - $exception = new Exception('message'); - $failure = new TestFailure($test, $exception); - - $this->assertEquals(__METHOD__ . ': message', $failure->toString()); - } - - public function testToStringForError(): void - { - $test = new self(__FUNCTION__); - $exception = new \Error('message'); - $failure = new TestFailure($test, $exception); - - $this->assertEquals(__METHOD__ . ': message', $failure->toString()); - } - - public function testToStringForNonSelfDescribing(): void - { - $test = new NotSelfDescribingTest(); - $exception = new Exception('message'); - $failure = new TestFailure($test, $exception); - - $this->assertEquals('PHPUnit\TestFixture\NotSelfDescribingTest: message', $failure->toString()); - } - - public function testgetExceptionAsString(): void - { - $test = new self(__FUNCTION__); - $exception = new \Error('message'); - $failure = new TestFailure($test, $exception); - - $this->assertEquals("Error: message\n", $failure->getExceptionAsString()); - } - - public function testExceptionToString(): void - { - $exception = new AssertionFailedError('message'); - - $this->assertEquals("message\n", TestFailure::exceptionToString($exception)); - } - - public function testExceptionToStringForExpectationFailedException(): void - { - $exception = new ExpectationFailedException('message'); - - $this->assertEquals("message\n", TestFailure::exceptionToString($exception)); - } - - public function testExceptionToStringForExpectationFailedExceptionWithComparisonFailure(): void - { - $exception = new ExpectationFailedException('message', new ComparisonFailure('expected', 'actual', 'expected', 'actual')); - - $this->assertEquals("message\n--- Expected\n+++ Actual\n@@ @@\n-expected\n+actual\n", TestFailure::exceptionToString($exception)); - } - - public function testExceptionToStringForFrameworkError(): void - { - $exception = new Error('message', 0, 'file', 1); - - $this->assertEquals("message\n", TestFailure::exceptionToString($exception)); - } - - public function testExceptionToStringForExceptionWrapper(): void - { - $exception = new ExceptionWrapper(new \Error('message')); - - $this->assertEquals("Error: message\n", TestFailure::exceptionToString($exception)); - } - - public function testGetTestName(): void - { - $test = new self(__FUNCTION__); - $exception = new Exception('message'); - $failure = new TestFailure($test, $exception); - - $this->assertEquals($this->toString(), $failure->getTestName()); - } - - public function testFailedTest(): void - { - $test = new self(__FUNCTION__); - $exception = new Exception('message'); - $failure = new TestFailure($test, $exception); - - $this->assertEquals($test, $failure->failedTest()); - } - - public function testThrownException(): void - { - $test = new self(__FUNCTION__); - $exception = new Exception('message'); - $failure = new TestFailure($test, $exception); - - $this->assertEquals($exception, $failure->thrownException()); - } - - public function testExceptionMessage(): void - { - $test = new self(__FUNCTION__); - $exception = new Exception('message'); - $failure = new TestFailure($test, $exception); - - $this->assertEquals('message', $failure->exceptionMessage()); - } - - public function testIsFailure(): void - { - $test = new self(__FUNCTION__); - $exception = new ExpectationFailedException('message'); - $failure = new TestFailure($test, $exception); - - $this->assertTrue($failure->isFailure()); - } - - public function testIsFailureFalse(): void - { - $test = new self(__FUNCTION__); - $exception = new Warning('message'); - $failure = new TestFailure($test, $exception); - - $this->assertFalse($failure->isFailure()); - } -} diff --git a/tests/unit/Framework/TestImplementorTest.php b/tests/unit/Framework/TestImplementorTest.php deleted file mode 100644 index cffad5ad34c..00000000000 --- a/tests/unit/Framework/TestImplementorTest.php +++ /dev/null @@ -1,32 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use function count; -use PHPUnit\TestFixture\DoubleTestCase; -use PHPUnit\TestFixture\Success; - -/** - * @small - */ -final class TestImplementorTest extends TestCase -{ - public function testSuccessfulRun(): void - { - $result = new TestResult; - - $test = new DoubleTestCase(new Success); - $test->run($result); - - $this->assertCount(count($test), $result); - $this->assertEquals(0, $result->errorCount()); - $this->assertEquals(0, $result->failureCount()); - } -} diff --git a/tests/unit/Framework/TestListenerTest.php b/tests/unit/Framework/TestListenerTest.php deleted file mode 100644 index 8f5aad0c7a0..00000000000 --- a/tests/unit/Framework/TestListenerTest.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use PHPUnit\TestFixture\Failure; -use PHPUnit\TestFixture\MyTestListener; -use PHPUnit\TestFixture\Success; -use PHPUnit\TestFixture\TestError; - -/** - * @small - */ -final class TestListenerTest extends TestCase -{ - /** - * @var TestResult - */ - private $result; - - /** - * @var MyTestListener - */ - private $listener; - - protected function setUp(): void - { - $this->result = new TestResult; - $this->listener = new MyTestListener; - - $this->result->addListener($this->listener); - } - - public function testError(): void - { - $test = new TestError; - $test->run($this->result); - - $this->assertEquals(1, $this->listener->errorCount()); - $this->assertEquals(1, $this->listener->endCount()); - } - - public function testFailure(): void - { - $test = new Failure; - $test->run($this->result); - - $this->assertEquals(1, $this->listener->failureCount()); - $this->assertEquals(1, $this->listener->endCount()); - } - - public function testStartStop(): void - { - $test = new Success; - $test->run($this->result); - - $this->assertEquals(1, $this->listener->startCount()); - $this->assertEquals(1, $this->listener->endCount()); - } -} diff --git a/tests/unit/Framework/TestRunner/ChildProcessResultProcessorTest.php b/tests/unit/Framework/TestRunner/ChildProcessResultProcessorTest.php new file mode 100644 index 00000000000..ee64fd34aab --- /dev/null +++ b/tests/unit/Framework/TestRunner/ChildProcessResultProcessorTest.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use PHPUnit\Event\Emitter; +use PHPUnit\Event\Facade; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Runner\CodeCoverage; +use PHPUnit\TestFixture\Success; +use PHPUnit\TestRunner\TestResult\PassedTests; + +#[CoversClass(ChildProcessResultProcessor::class)] +#[Small] +final class ChildProcessResultProcessorTest extends TestCase +{ + #[TestDox('Emits Test\Errored event when standard output is not empty')] + public function testEmitsErrorEventWhenStderrIsNotEmpty(): void + { + $emitter = $this->createMock(Emitter::class); + + $emitter + ->expects($this->once()) + ->method('testErrored'); + + $this->processor($emitter)->process(new Success('testOne'), '', 'error'); + } + + #[TestDox('Emits Test\Errored event when process result cannot be unserialized')] + public function testEmitsErrorEventWhenProcessResultCannotBeUnserialized(): void + { + $emitter = $this->createMock(Emitter::class); + + $emitter + ->expects($this->once()) + ->method('testErrored'); + + $this->processor($emitter)->process(new Success('testOne'), '', ''); + } + + private function processor(Emitter $emitter): ChildProcessResultProcessor + { + return new ChildProcessResultProcessor( + new Facade, + $emitter, + new PassedTests, + new CodeCoverage, + ); + } +} diff --git a/tests/unit/Framework/TestSizeTest.php b/tests/unit/Framework/TestSizeTest.php new file mode 100644 index 00000000000..4632c66bb2b --- /dev/null +++ b/tests/unit/Framework/TestSizeTest.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestSize; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversClassesThatExtendClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(TestSize::class)] +#[CoversClassesThatExtendClass(TestSize::class)] +#[Small] +final class TestSizeTest extends TestCase +{ + public static function comparisonProvider(): array + { + return [ + 'small test is not greater than small test' => [ + false, + TestSize::small(), + TestSize::small(), + ], + + 'small test is not greater than medium test' => [ + false, + TestSize::small(), + TestSize::medium(), + ], + + 'small test is not greater than large test' => [ + false, + TestSize::small(), + TestSize::large(), + ], + + 'medium test is greater than small test' => [ + true, + TestSize::medium(), + TestSize::small(), + ], + + 'medium test is not greater than medium test' => [ + false, + TestSize::medium(), + TestSize::medium(), + ], + + 'medium test is not greater than large test' => [ + false, + TestSize::medium(), + TestSize::large(), + ], + + 'large test is greater than small test' => [ + true, + TestSize::large(), + TestSize::small(), + ], + + 'large test is greater than medium test' => [ + true, + TestSize::large(), + TestSize::medium(), + ], + + 'large test is not greater than large test' => [ + false, + TestSize::large(), + TestSize::large(), + ], + ]; + } + + public function testCanBeUnknown(): void + { + $testSize = TestSize::unknown(); + + $this->assertFalse($testSize->isKnown()); + $this->assertTrue($testSize->isUnknown()); + $this->assertFalse($testSize->isSmall()); + $this->assertFalse($testSize->isMedium()); + $this->assertFalse($testSize->isLarge()); + + $this->assertSame('unknown', $testSize->asString()); + } + + public function testCanBeSmall(): void + { + $testSize = TestSize::small(); + + $this->assertTrue($testSize->isKnown()); + $this->assertFalse($testSize->isUnknown()); + $this->assertTrue($testSize->isSmall()); + $this->assertFalse($testSize->isMedium()); + $this->assertFalse($testSize->isLarge()); + + $this->assertSame('small', $testSize->asString()); + } + + public function testCanBeMedium(): void + { + $testSize = TestSize::medium(); + + $this->assertTrue($testSize->isKnown()); + $this->assertFalse($testSize->isUnknown()); + $this->assertFalse($testSize->isSmall()); + $this->assertTrue($testSize->isMedium()); + $this->assertFalse($testSize->isLarge()); + + $this->assertSame('medium', $testSize->asString()); + } + + public function testCanBeLarge(): void + { + $testSize = TestSize::large(); + + $this->assertTrue($testSize->isKnown()); + $this->assertFalse($testSize->isUnknown()); + $this->assertFalse($testSize->isSmall()); + $this->assertFalse($testSize->isMedium()); + $this->assertTrue($testSize->isLarge()); + + $this->assertSame('large', $testSize->asString()); + } + + #[DataProvider('comparisonProvider')] + public function testTwoKnownSizesCanBeCompared(bool $expected, Known $a, Known $b): void + { + $this->assertSame($expected, $a->isGreaterThan($b)); + } +} diff --git a/tests/unit/Framework/TestStatusTest.php b/tests/unit/Framework/TestStatusTest.php new file mode 100644 index 00000000000..583c71adf90 --- /dev/null +++ b/tests/unit/Framework/TestStatusTest.php @@ -0,0 +1,289 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestStatus; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversClassesThatExtendClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(TestStatus::class)] +#[CoversClassesThatExtendClass(TestStatus::class)] +#[Small] +final class TestStatusTest extends TestCase +{ + public function testCanBeUnknown(): void + { + $status = TestStatus::unknown(); + + $this->assertFalse($status->isKnown()); + $this->assertTrue($status->isUnknown()); + $this->assertFalse($status->isSuccess()); + $this->assertFalse($status->isFailure()); + $this->assertFalse($status->isError()); + $this->assertFalse($status->isWarning()); + $this->assertFalse($status->isRisky()); + $this->assertFalse($status->isIncomplete()); + $this->assertFalse($status->isSkipped()); + $this->assertFalse($status->isDeprecation()); + $this->assertFalse($status->isNotice()); + + $this->assertSame('unknown', $status->asString()); + } + + public function testCanBeSuccess(): void + { + $status = TestStatus::success(); + + $this->assertTrue($status->isKnown()); + $this->assertFalse($status->isUnknown()); + $this->assertTrue($status->isSuccess()); + $this->assertFalse($status->isFailure()); + $this->assertFalse($status->isError()); + $this->assertFalse($status->isWarning()); + $this->assertFalse($status->isRisky()); + $this->assertFalse($status->isIncomplete()); + $this->assertFalse($status->isSkipped()); + $this->assertFalse($status->isDeprecation()); + $this->assertFalse($status->isNotice()); + + $this->assertSame('success', $status->asString()); + } + + public function testCanBeFailure(): void + { + $status = TestStatus::failure('message'); + + $this->assertTrue($status->isKnown()); + $this->assertFalse($status->isUnknown()); + $this->assertFalse($status->isSuccess()); + $this->assertTrue($status->isFailure()); + $this->assertFalse($status->isError()); + $this->assertFalse($status->isWarning()); + $this->assertFalse($status->isRisky()); + $this->assertFalse($status->isIncomplete()); + $this->assertFalse($status->isSkipped()); + $this->assertFalse($status->isDeprecation()); + $this->assertFalse($status->isNotice()); + + $this->assertSame('failure', $status->asString()); + $this->assertSame('message', $status->message()); + } + + public function testCanBeError(): void + { + $status = TestStatus::error('message'); + + $this->assertTrue($status->isKnown()); + $this->assertFalse($status->isUnknown()); + $this->assertFalse($status->isSuccess()); + $this->assertFalse($status->isFailure()); + $this->assertTrue($status->isError()); + $this->assertFalse($status->isWarning()); + $this->assertFalse($status->isRisky()); + $this->assertFalse($status->isIncomplete()); + $this->assertFalse($status->isSkipped()); + $this->assertFalse($status->isDeprecation()); + $this->assertFalse($status->isNotice()); + + $this->assertSame('error', $status->asString()); + $this->assertSame('message', $status->message()); + } + + public function testCanBeWarning(): void + { + $status = TestStatus::warning('message'); + + $this->assertTrue($status->isKnown()); + $this->assertFalse($status->isUnknown()); + $this->assertFalse($status->isSuccess()); + $this->assertFalse($status->isFailure()); + $this->assertFalse($status->isError()); + $this->assertTrue($status->isWarning()); + $this->assertFalse($status->isRisky()); + $this->assertFalse($status->isIncomplete()); + $this->assertFalse($status->isSkipped()); + $this->assertFalse($status->isDeprecation()); + $this->assertFalse($status->isNotice()); + + $this->assertSame('warning', $status->asString()); + $this->assertSame('message', $status->message()); + } + + public function testCanBeRisky(): void + { + $status = TestStatus::risky('message'); + + $this->assertTrue($status->isKnown()); + $this->assertFalse($status->isUnknown()); + $this->assertFalse($status->isSuccess()); + $this->assertFalse($status->isFailure()); + $this->assertFalse($status->isError()); + $this->assertFalse($status->isWarning()); + $this->assertTrue($status->isRisky()); + $this->assertFalse($status->isIncomplete()); + $this->assertFalse($status->isSkipped()); + $this->assertFalse($status->isDeprecation()); + $this->assertFalse($status->isNotice()); + + $this->assertSame('risky', $status->asString()); + $this->assertSame('message', $status->message()); + } + + public function testCanBeIncomplete(): void + { + $status = TestStatus::incomplete('message'); + + $this->assertTrue($status->isKnown()); + $this->assertFalse($status->isUnknown()); + $this->assertFalse($status->isSuccess()); + $this->assertFalse($status->isFailure()); + $this->assertFalse($status->isError()); + $this->assertFalse($status->isWarning()); + $this->assertFalse($status->isRisky()); + $this->assertTrue($status->isIncomplete()); + $this->assertFalse($status->isSkipped()); + $this->assertFalse($status->isDeprecation()); + $this->assertFalse($status->isNotice()); + + $this->assertSame('incomplete', $status->asString()); + $this->assertSame('message', $status->message()); + } + + public function testCanBeSkipped(): void + { + $status = TestStatus::skipped('message'); + + $this->assertTrue($status->isKnown()); + $this->assertFalse($status->isUnknown()); + $this->assertFalse($status->isSuccess()); + $this->assertFalse($status->isFailure()); + $this->assertFalse($status->isError()); + $this->assertFalse($status->isWarning()); + $this->assertFalse($status->isRisky()); + $this->assertFalse($status->isIncomplete()); + $this->assertTrue($status->isSkipped()); + $this->assertFalse($status->isDeprecation()); + $this->assertFalse($status->isNotice()); + + $this->assertSame('skipped', $status->asString()); + $this->assertSame('message', $status->message()); + } + + public function testCanBeDeprecation(): void + { + $status = TestStatus::deprecation('message'); + + $this->assertTrue($status->isKnown()); + $this->assertFalse($status->isUnknown()); + $this->assertFalse($status->isSuccess()); + $this->assertFalse($status->isFailure()); + $this->assertFalse($status->isError()); + $this->assertFalse($status->isWarning()); + $this->assertFalse($status->isRisky()); + $this->assertFalse($status->isIncomplete()); + $this->assertFalse($status->isSkipped()); + $this->assertTrue($status->isDeprecation()); + $this->assertFalse($status->isNotice()); + + $this->assertSame('deprecation', $status->asString()); + $this->assertSame('message', $status->message()); + } + + public function testCanBeNotice(): void + { + $status = TestStatus::notice('message'); + + $this->assertTrue($status->isKnown()); + $this->assertFalse($status->isUnknown()); + $this->assertFalse($status->isSuccess()); + $this->assertFalse($status->isFailure()); + $this->assertFalse($status->isError()); + $this->assertFalse($status->isWarning()); + $this->assertFalse($status->isRisky()); + $this->assertFalse($status->isIncomplete()); + $this->assertFalse($status->isSkipped()); + $this->assertFalse($status->isDeprecation()); + $this->assertTrue($status->isNotice()); + + $this->assertSame('notice', $status->asString()); + $this->assertSame('message', $status->message()); + } + + public function testCanBeRepresentedAsIntegerValue(): void + { + $this->assertSame(-1, TestStatus::unknown()->asInt()); + $this->assertSame(0, TestStatus::success()->asInt()); + $this->assertSame(1, TestStatus::skipped()->asInt()); + $this->assertSame(2, TestStatus::incomplete()->asInt()); + $this->assertSame(3, TestStatus::notice()->asInt()); + $this->assertSame(4, TestStatus::deprecation()->asInt()); + $this->assertSame(5, TestStatus::risky()->asInt()); + $this->assertSame(6, TestStatus::warning()->asInt()); + $this->assertSame(7, TestStatus::failure()->asInt()); + $this->assertSame(8, TestStatus::error()->asInt()); + } + + public function testCanBeCreatedFromIntegerValue(): void + { + $this->assertInstanceOf(Unknown::class, TestStatus::from(-1)); + $this->assertInstanceOf(Success::class, TestStatus::from(0)); + $this->assertInstanceOf(Skipped::class, TestStatus::from(1)); + $this->assertInstanceOf(Incomplete::class, TestStatus::from(2)); + $this->assertInstanceOf(Notice::class, TestStatus::from(3)); + $this->assertInstanceOf(Deprecation::class, TestStatus::from(4)); + $this->assertInstanceOf(Risky::class, TestStatus::from(5)); + $this->assertInstanceOf(Warning::class, TestStatus::from(6)); + $this->assertInstanceOf(Failure::class, TestStatus::from(7)); + $this->assertInstanceOf(Error::class, TestStatus::from(8)); + } + + public function testSuccessIsMoreImportantThanUnknown(): void + { + $this->assertTrue(TestStatus::success()->isMoreImportantThan(TestStatus::unknown())); + $this->assertFalse(TestStatus::unknown()->isMoreImportantThan(TestStatus::success())); + } + + public function testSkippedIsMoreImportantThanSuccess(): void + { + $this->assertTrue(TestStatus::skipped()->isMoreImportantThan(TestStatus::success())); + $this->assertFalse(TestStatus::success()->isMoreImportantThan(TestStatus::skipped())); + } + + public function testIncompleteIsMoreImportantThanSkipped(): void + { + $this->assertTrue(TestStatus::incomplete()->isMoreImportantThan(TestStatus::skipped())); + $this->assertFalse(TestStatus::skipped()->isMoreImportantThan(TestStatus::incomplete())); + } + + public function testRiskyIsMoreImportantThanIncomplete(): void + { + $this->assertTrue(TestStatus::risky()->isMoreImportantThan(TestStatus::incomplete())); + $this->assertFalse(TestStatus::incomplete()->isMoreImportantThan(TestStatus::risky())); + } + + public function testWarningIsMoreImportantThanRisky(): void + { + $this->assertTrue(TestStatus::warning()->isMoreImportantThan(TestStatus::risky())); + $this->assertFalse(TestStatus::risky()->isMoreImportantThan(TestStatus::warning())); + } + + public function testFailureIsMoreImportantThanWarning(): void + { + $this->assertTrue(TestStatus::failure()->isMoreImportantThan(TestStatus::warning())); + $this->assertFalse(TestStatus::warning()->isMoreImportantThan(TestStatus::failure())); + } + + public function testErrorIsMoreImportantThanFailure(): void + { + $this->assertTrue(TestStatus::error()->isMoreImportantThan(TestStatus::failure())); + $this->assertFalse(TestStatus::failure()->isMoreImportantThan(TestStatus::error())); + } +} diff --git a/tests/unit/Framework/TestSuiteIteratorTest.php b/tests/unit/Framework/TestSuiteIteratorTest.php index bbe1a519e61..813a82a020d 100644 --- a/tests/unit/Framework/TestSuiteIteratorTest.php +++ b/tests/unit/Framework/TestSuiteIteratorTest.php @@ -9,16 +9,17 @@ */ namespace PHPUnit\Framework; -use PHPUnit\TestFixture\EmptyTestCaseTest; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\TestFixture\Success; -/** - * @small - */ +#[CoversClass(TestSuiteIterator::class)] +#[Small] final class TestSuiteIteratorTest extends TestCase { public function testKeyForEmptyTestSuiteInitiallyReturnsZero(): void { - $testSuite = new TestSuite; + $testSuite = TestSuite::empty('test suite name'); $subject = new TestSuiteIterator($testSuite); $this->assertSame(0, $subject->key()); @@ -26,7 +27,7 @@ public function testKeyForEmptyTestSuiteInitiallyReturnsZero(): void public function testValidForEmptyTestSuiteInitiallyReturnsFalse(): void { - $testSuite = new TestSuite; + $testSuite = TestSuite::empty('test suite name'); $subject = new TestSuiteIterator($testSuite); $this->assertFalse($subject->valid()); @@ -48,8 +49,8 @@ public function testValidForNonEmptyTestSuiteInitiallyReturnsTrue(): void public function testCurrentForNonEmptyTestSuiteInitiallyReturnsFirstTest(): void { - $test = new EmptyTestCaseTest; - $testSuite = new TestSuite; + $test = new Success('testOne'); + $testSuite = TestSuite::empty('test suite name'); $testSuite->addTest($test); $subject = new TestSuiteIterator($testSuite); @@ -68,8 +69,8 @@ public function testRewindResetsKeyToZero(): void public function testRewindResetsCurrentToFirstElement(): void { - $testSuite = new TestSuite; - $test = new EmptyTestCaseTest; + $testSuite = TestSuite::empty('test suite name'); + $test = new Success('testOne'); $testSuite->addTest($test); $subject = new TestSuiteIterator($testSuite); $subject->next(); @@ -99,7 +100,7 @@ public function testValidAfterLastElementReturnsFalse(): void public function testGetChildrenForEmptyTestSuiteThrowsException(): void { - $subject = new TestSuiteIterator(new TestSuite); + $subject = new TestSuiteIterator(TestSuite::empty('test suite name')); $this->expectException(NoChildTestSuiteException::class); @@ -117,11 +118,11 @@ public function testGetChildrenForCurrentTestThrowsException(): void public function testGetChildrenReturnsNewInstanceWithCurrentTestSuite(): void { - $childSuite = new TestSuite; - $test = new EmptyTestCaseTest; + $childSuite = TestSuite::empty('test suite name'); + $test = new Success('testOne'); $childSuite->addTest($test); - $testSuite = new TestSuite; + $testSuite = TestSuite::empty('test suite name'); $testSuite->addTest($childSuite); $subject = new TestSuiteIterator($testSuite); @@ -134,8 +135,8 @@ public function testGetChildrenReturnsNewInstanceWithCurrentTestSuite(): void public function testHasChildrenForCurrentTestSuiteReturnsTrue(): void { - $testSuite = new TestSuite; - $childSuite = new TestSuite; + $testSuite = TestSuite::empty('test suite name'); + $childSuite = TestSuite::empty('test suite name'); $testSuite->addTest($childSuite); $subject = new TestSuiteIterator($testSuite); @@ -151,16 +152,16 @@ public function testHasChildrenForCurrentTestReturnsFalse(): void public function testHasChildrenForNoTestsReturnsFalse(): void { - $subject = new TestSuiteIterator(new TestSuite); + $subject = new TestSuiteIterator(TestSuite::empty('test suite name')); $this->assertFalse($subject->hasChildren()); } private function suiteWithEmptyTestCase(): TestSuite { - $suite = new TestSuite; + $suite = TestSuite::empty('test suite name'); - $suite->addTest(new EmptyTestCaseTest); + $suite->addTest(new Success('testOne')); return $suite; } diff --git a/tests/unit/Framework/TestSuiteTest.php b/tests/unit/Framework/TestSuiteTest.php index 77d6d7ae882..bb90995cf40 100644 --- a/tests/unit/Framework/TestSuiteTest.php +++ b/tests/unit/Framework/TestSuiteTest.php @@ -9,278 +9,31 @@ */ namespace PHPUnit\Framework; -use const DIRECTORY_SEPARATOR; -use const PHP_EOL; -use function array_pop; -use DataProviderDependencyTest; -use DependencyFailureTest; -use DependencyOnClassTest; -use DependencySuccessTest; -use MultiDependencyTest; -use PHPUnit\TestFixture\BeforeAndAfterTest; -use PHPUnit\TestFixture\BeforeClassAndAfterClassTest; -use PHPUnit\TestFixture\BeforeClassWithOnlyDataProviderTest; -use PHPUnit\TestFixture\DataProviderIncompleteTest; -use PHPUnit\TestFixture\DataProviderSkippedTest; -use PHPUnit\TestFixture\DoubleTestCase; -use PHPUnit\TestFixture\ExceptionInTearDownAfterClassTest; -use PHPUnit\TestFixture\InheritedTestCase; -use PHPUnit\TestFixture\NoTestCases; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\TestFixture\AbstractTestCase; +use PHPUnit\TestFixture\DependencyFailureTest; +use PHPUnit\TestFixture\DependencyOnClassTest; +use PHPUnit\TestFixture\DependencySuccessTest; +use PHPUnit\TestFixture\MultiDependencyTest; +use PHPUnit\TestFixture\NoTestCase; use PHPUnit\TestFixture\NotPublicTestCase; -use PHPUnit\TestFixture\NotVoidTestCase; -use PHPUnit\TestFixture\OneTestCase; -use PHPUnit\TestFixture\OverrideTestCase; -use PHPUnit\TestFixture\PreConditionAndPostConditionTest; -use PHPUnit\TestFixture\RequirementsClassBeforeClassHookTest; -use PHPUnit\TestFixture\Success; -use PHPUnit\TestFixture\TestCaseWithExceptionInHook; -use PHPUnit\TestFixture\TestWithTest; +use ReflectionClass; -/** - * @small - */ +#[CoversClass(TestSuite::class)] +#[Small] final class TestSuiteTest extends TestCase { - /** - * @var TestResult - */ - private $result; - - protected function setUp(): void - { - $this->result = new TestResult; - } - - protected function tearDown(): void - { - $this->result = null; - } - - /** - * @testdox TestSuite can be created with name of existing non-TestCase class - */ - public function testSuiteNameCanBeSameAsExistingNonTestClassName(): void - { - $suite = new TestSuite('stdClass'); - $suite->addTestSuite(OneTestCase::class); - $suite->run($this->result); - - $this->assertCount(1, $this->result); - } - - public function testAddTestSuite(): void - { - $suite = new TestSuite(OneTestCase::class); - - $suite->run($this->result); - - $this->assertCount(1, $this->result); - } - - public function testInheritedTests(): void - { - $suite = new TestSuite(InheritedTestCase::class); - - $suite->run($this->result); - - $this->assertTrue($this->result->wasSuccessful()); - $this->assertCount(2, $this->result); - } - - public function testNoTestCases(): void - { - $suite = new TestSuite(NoTestCases::class); - - $suite->run($this->result); - - $this->assertNotTrue($this->result->wasSuccessful()); - $this->assertEquals(0, $this->result->failureCount()); - $this->assertEquals(1, $this->result->warningCount()); - $this->assertCount(1, $this->result); - } - public function testNotPublicTestCase(): void { - $suite = new TestSuite(NotPublicTestCase::class); - - $this->assertCount(2, $suite); - } - - public function testNotVoidTestCase(): void - { - $suite = new TestSuite(NotVoidTestCase::class); + $suite = TestSuite::fromClassReflector(new ReflectionClass(NotPublicTestCase::class)); $this->assertCount(1, $suite); } - public function testOneTestCase(): void - { - $suite = new TestSuite(OneTestCase::class); - - $suite->run($this->result); - - $this->assertEquals(0, $this->result->errorCount()); - $this->assertEquals(0, $this->result->failureCount()); - $this->assertCount(1, $this->result); - $this->assertTrue($this->result->wasSuccessful()); - } - - public function testShadowedTests(): void - { - $suite = new TestSuite(OverrideTestCase::class); - - $suite->run($this->result); - - $this->assertCount(1, $this->result); - } - - public function testBeforeClassAndAfterClassAnnotations(): void - { - $suite = new TestSuite(BeforeClassAndAfterClassTest::class); - - BeforeClassAndAfterClassTest::resetProperties(); - $suite->run($this->result); - - $this->assertEquals(1, BeforeClassAndAfterClassTest::$beforeClassWasRun, '@beforeClass method was not run once for the whole suite.'); - $this->assertEquals(1, BeforeClassAndAfterClassTest::$afterClassWasRun, '@afterClass method was not run once for the whole suite.'); - } - - public function testBeforeClassWithDataProviders(): void - { - $suite = new TestSuite(BeforeClassWithOnlyDataProviderTest::class); - - BeforeClassWithOnlyDataProviderTest::resetProperties(); - $suite->run($this->result); - - $this->assertTrue(BeforeClassWithOnlyDataProviderTest::$setUpBeforeClassWasCalled, 'setUpBeforeClass method was not run.'); - $this->assertTrue(BeforeClassWithOnlyDataProviderTest::$beforeClassWasCalled, '@beforeClass method was not run.'); - } - - public function testBeforeAndAfterAnnotations(): void - { - $test = new TestSuite(BeforeAndAfterTest::class); - - BeforeAndAfterTest::resetProperties(); - $test->run(); - - $this->assertEquals(2, BeforeAndAfterTest::$beforeWasRun); - $this->assertEquals(2, BeforeAndAfterTest::$afterWasRun); - } - - public function testPreConditionAndPostConditionAnnotations(): void - { - $test = new TestSuite(PreConditionAndPostConditionTest::class); - - PreConditionAndPostConditionTest::resetProperties(); - $test->run(); - - $this->assertSame(1, PreConditionAndPostConditionTest::$preConditionWasVerified); - $this->assertSame(1, PreConditionAndPostConditionTest::$postConditionWasVerified); - } - - public function testTestWithAnnotation(): void - { - $test = new TestSuite(TestWithTest::class); - - BeforeAndAfterTest::resetProperties(); - $result = $test->run(); - - $this->assertCount(4, $result->passed()); - } - - public function testSkippedTestDataProvider(): void - { - $suite = new TestSuite(DataProviderSkippedTest::class); - - $suite->run($this->result); - - $this->assertEquals(3, $this->result->count()); - $this->assertEquals(1, $this->result->skippedCount()); - } - - public function testItErrorsOnlyOnceOnHookException(): void - { - $suite = new TestSuite(TestCaseWithExceptionInHook::class); - - $suite->run($this->result); - - $this->assertEquals(2, $this->result->count()); - $this->assertEquals(1, $this->result->errorCount()); - $this->assertEquals(1, $this->result->skippedCount()); - } - - public function testTestDataProviderDependency(): void - { - $suite = new TestSuite(DataProviderDependencyTest::class); - - $suite->run($this->result); - - $skipped = $this->result->skipped(); - $lastSkippedResult = array_pop($skipped); - $message = $lastSkippedResult->thrownException()->getMessage(); - - $this->assertStringContainsString('Test for DataProviderDependencyTest::testDependency skipped by data provider', $message); - } - - public function testIncompleteTestDataProvider(): void - { - $suite = new TestSuite(DataProviderIncompleteTest::class); - - $suite->run($this->result); - - $this->assertEquals(3, $this->result->count()); - $this->assertEquals(1, $this->result->notImplementedCount()); - } - - public function testRequirementsBeforeClassHook(): void - { - $suite = new TestSuite(RequirementsClassBeforeClassHookTest::class); - - $suite->run($this->result); - - $this->assertEquals(0, $this->result->errorCount()); - $this->assertEquals(1, $this->result->skippedCount()); - } - - public function testDoNotSkipInheritedClass(): void - { - $suite = new TestSuite( - 'DontSkipInheritedClass' - ); - - $dir = TEST_FILES_PATH . DIRECTORY_SEPARATOR . 'Inheritance' . DIRECTORY_SEPARATOR; - - $suite->addTestFile($dir . 'InheritanceA.php'); - $suite->addTestFile($dir . 'InheritanceB.php'); - - $result = $suite->run(); - - $this->assertCount(2, $result); - } - - /** - * @testdox Handles exceptions in tearDownAfterClass() - */ - public function testTearDownAfterClassInTestSuite(): void - { - $suite = new TestSuite(ExceptionInTearDownAfterClassTest::class); - $suite->run($this->result); - - $this->assertSame(3, $this->result->count()); - $this->assertCount(1, $this->result->failures()); - - $failure = $this->result->failures()[0]; - - $this->assertSame( - 'Exception in PHPUnit\TestFixture\ExceptionInTearDownAfterClassTest::tearDownAfterClass' . PHP_EOL . - 'throw Exception in tearDownAfterClass()', - $failure->thrownException()->getMessage() - ); - } - public function testNormalizeProvidedDependencies(): void { - $suite = new TestSuite(MultiDependencyTest::class); + $suite = TestSuite::fromClassReflector(new ReflectionClass(MultiDependencyTest::class)); $this->assertEquals([ MultiDependencyTest::class . '::class', @@ -294,14 +47,16 @@ public function testNormalizeProvidedDependencies(): void public function testNormalizeRequiredDependencies(): void { - $suite = new TestSuite(MultiDependencyTest::class); + $suite = TestSuite::fromClassReflector(new ReflectionClass(MultiDependencyTest::class)); $this->assertSame([], $suite->requires()); } public function testDetectMissingDependenciesBetweenTestSuites(): void { - $suite = new TestSuite(DependencyOnClassTest::class); + $suite = TestSuite::fromClassReflector( + new ReflectionClass(DependencyOnClassTest::class), + ); $this->assertEquals([ DependencyOnClassTest::class . '::class', @@ -317,9 +72,9 @@ public function testDetectMissingDependenciesBetweenTestSuites(): void public function testResolveDependenciesBetweenTestSuites(): void { - $suite = new TestSuite(DependencyOnClassTest::class); - $suite->addTestSuite(DependencyFailureTest::class); - $suite->addTestSuite(DependencySuccessTest::class); + $suite = TestSuite::fromClassReflector(new ReflectionClass(DependencyOnClassTest::class)); + $suite->addTestSuite(new ReflectionClass(DependencyFailureTest::class)); + $suite->addTestSuite(new ReflectionClass(DependencySuccessTest::class)); $this->assertEquals([ DependencyOnClassTest::class . '::class', @@ -330,8 +85,8 @@ public function testResolveDependenciesBetweenTestSuites(): void DependencyFailureTest::class . '::testTwo', DependencyFailureTest::class . '::testThree', DependencyFailureTest::class . '::testFour', - DependencyFailureTest::class . '::testHandlesDependsAnnotationForNonexistentTests', - DependencyFailureTest::class . '::testHandlesDependsAnnotationWithNoMethodSpecified', + DependencyFailureTest::class . '::testHandlesDependencyOnTestMethodThatDoesNotExist', + DependencyFailureTest::class . '::testHandlesDependencyOnTestMethodWithEmptyName', DependencySuccessTest::class . '::class', DependencySuccessTest::class . '::testOne', DependencySuccessTest::class . '::testTwo', @@ -345,9 +100,8 @@ public function testResolveDependenciesBetweenTestSuites(): void public function testResolverOnlyUsesSuitesAndCases(): void { - $suite = new TestSuite('SomeName'); - $suite->addTest(new DoubleTestCase(new Success)); - $suite->addTestSuite(new TestSuite(DependencyOnClassTest::class)); + $suite = TestSuite::empty('SomeName'); + $suite->addTestSuite(new ReflectionClass(DependencyOnClassTest::class)); $this->assertEquals([ 'SomeName::class', @@ -361,4 +115,22 @@ public function testResolverOnlyUsesSuitesAndCases(): void DependencyFailureTest::class . '::class', ], $suite->requires(), 'Required test names incorrect'); } + + public function testRejectsAbstractTestClass(): void + { + $suite = TestSuite::empty('the-test-suite'); + + $this->expectException(Exception::class); + + $suite->addTestSuite(new ReflectionClass(AbstractTestCase::class)); + } + + public function testRejectsClassThatDoesNotExtendTestClass(): void + { + $suite = TestSuite::empty('the-test-suite'); + + $this->expectException(Exception::class); + + $suite->addTestSuite(new ReflectionClass(NoTestCase::class)); + } } diff --git a/tests/unit/Logging/TestDox/NamePrettifierTest.php b/tests/unit/Logging/TestDox/NamePrettifierTest.php new file mode 100644 index 00000000000..fdf763cb31f --- /dev/null +++ b/tests/unit/Logging/TestDox/NamePrettifierTest.php @@ -0,0 +1,224 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use DateTimeImmutable; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\BackedEnumeration; +use PHPUnit\TestFixture\Enumeration; +use PHPUnit\TestFixture\TestDox\TestDoxAttributeOnTestClassTest; +use PHPUnit\TestFixture\TestDoxTest; +use stdClass; + +#[CoversClass(NamePrettifier::class)] +#[Group('testdox')] +#[Small] +final class NamePrettifierTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function classNameProvider(): array + { + return [ + [ + 'Foo', + 'FooTest', + ], + [ + 'Foo', + 'TestFoo', + ], + [ + 'Foo', + 'TestsFoo', + ], + [ + 'Foo', + 'TestFooTest', + ], + [ + 'Foo (Test\Foo)', + 'Test\FooTest', + ], + [ + 'Foo (Tests\Foo)', + 'Tests\FooTest', + ], + [ + 'Unnamed Tests', + 'TestTest', + ], + [ + 'Système Testé', + 'SystèmeTestéTest', + ], + [ + 'Expression Évaluée', + 'ExpressionÉvaluéeTest', + ], + [ + 'Custom Title', + TestDoxAttributeOnTestClassTest::class, + ], + ]; + } + + /** + * @return non-empty-list + */ + public static function methodNameProvider(): array + { + return [ + [ + '', + '', + ], + [ + '', + 'test', + ], + [ + 'This is a test', + 'this_is_a_test', + ], + [ + 'This is a test', + 'test_this_is_a_test', + ], + [ + 'Foo for bar is 0', + 'testFooForBarIs0', + ], + [ + 'Foo for baz is 1', + 'testFooForBazIs1', + ], + [ + 'This has a 123 in its name', + 'testThisHasA123InItsName', + ], + [ + 'Sets redirect header on 301', + 'testSetsRedirectHeaderOn301', + ], + [ + 'Sets redirect header on 302', + 'testSetsRedirectHeaderOn302', + ], + [ + '100 users', + 'test100Users', + ], + ]; + } + + /** + * @return non-empty-list + */ + public static function objectProvider(): array + { + $object = new class + { + public function __toString(): string + { + return 'object as string'; + } + }; + + $data = [['string'], true, 0.0, 1, 'string', $object, new stdClass, Enumeration::Test, BackedEnumeration::Test, null, '']; + + $testWithDataWithIntegerKey = new TestDoxTest('testTwo'); + $testWithDataWithIntegerKey->setData(0, $data); + + $testWithDataWithStringKey = new TestDoxTest('testTwo'); + $testWithDataWithStringKey->setData('a', $data); + + $testWithDataAndTestDoxPlaceholders = new TestDoxTest('testFour'); + $testWithDataAndTestDoxPlaceholders->setData('a', $data); + + $testWithTestDoxFormatter = new TestDoxTest('testFive'); + $testWithTestDoxFormatter->setData(0, [new DateTimeImmutable('2025-07-09')]); + + return [ + [ + 'One', + new TestDoxTest('testOne'), + false, + ], + [ + 'Two with data set #0', + $testWithDataWithIntegerKey, + false, + ], + [ + 'Two with data set "a"', + $testWithDataWithStringKey, + false, + ], + [ + 'This is a custom test description', + new TestDoxTest('testThree'), + false, + ], + [ + 'This is a custom test description with placeholders array true 0.0 1 string object as string stdClass Test test null \'\' default', + $testWithDataAndTestDoxPlaceholders, + false, + ], + [ + 'This is a custom description: 2025-07-09', + $testWithTestDoxFormatter, + false, + ], + ]; + } + + /** + * @param non-empty-string $expected + * @param non-empty-string $className + */ + #[DataProvider('classNameProvider')] + public function testNameOfTestClassCanBePrettified(string $expected, string $className): void + { + $this->assertSame($expected, (new NamePrettifier)->prettifyTestClassName($className)); + } + + /** + * @param non-empty-string $expected + * @param non-empty-string $methodName + */ + #[DataProvider('methodNameProvider')] + public function testNameOfTestMethodCanBePrettified(string $expected, string $methodName): void + { + $this->assertSame($expected, (new NamePrettifier)->prettifyTestMethodName($methodName)); + } + + /** + * @param non-empty-string $expected + */ + #[DataProvider('objectProvider')] + public function test_TestCase_can_be_prettified(string $expected, TestCase $testCase, bool $colorize): void + { + $this->assertSame($expected, (new NamePrettifier)->prettifyTestCase($testCase, $colorize)); + } + + public function testStripsNumericSuffixFromTestMethodNameWhenTestMethodNameWithoutThatSuffixWasPreviouslyProcessed(): void + { + $namePrettifier = new NamePrettifier; + + $this->assertSame('This is a test', $namePrettifier->prettifyTestMethodName('testThisIsATest')); + $this->assertSame('This is a test', $namePrettifier->prettifyTestMethodName('testThisIsATest2')); + } +} diff --git a/tests/unit/Metadata/Api/CodeCoverageTest.php b/tests/unit/Metadata/Api/CodeCoverageTest.php new file mode 100644 index 00000000000..b8c8125985c --- /dev/null +++ b/tests/unit/Metadata/Api/CodeCoverageTest.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Api; + +use function array_shift; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\CoversClassOnClassTest; +use PHPUnit\TestFixture\CoversNothingOnClassTest; +use PHPUnit\TestFixture\Metadata\Attribute\CoversTest; +use PHPUnit\TestFixture\Metadata\Attribute\UsesTest; +use PHPUnit\TestFixture\NoCoverageAttributesTest; +use SebastianBergmann\CodeCoverage\Test\Target\Class_; +use SebastianBergmann\CodeCoverage\Test\Target\ClassesThatExtendClass; +use SebastianBergmann\CodeCoverage\Test\Target\ClassesThatImplementInterface; +use SebastianBergmann\CodeCoverage\Test\Target\Function_; +use SebastianBergmann\CodeCoverage\Test\Target\Method; +use SebastianBergmann\CodeCoverage\Test\Target\Namespace_; +use SebastianBergmann\CodeCoverage\Test\Target\Trait_; + +#[CoversClass(CodeCoverage::class)] +#[Small] +#[Group('metadata')] +final class CodeCoverageTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function canSkipCoverageProvider(): array + { + return [ + [false, NoCoverageAttributesTest::class], + [false, CoversClassOnClassTest::class], + [true, CoversNothingOnClassTest::class], + ]; + } + + #[TestDox('Maps #[Covers*()] metadata to phpunit/php-code-coverage TargetCollection')] + public function testMapsCoversMetadataToCodeCoverageTargetCollection(): void + { + $targets = (new CodeCoverage)->coversTargets(CoversTest::class, 'testOne'); + + $this->assertNotFalse($targets); + $this->assertCount(7, $targets); + + $targets = $targets->asArray(); + + $target = array_shift($targets); + $this->assertInstanceOf(Namespace_::class, $target); + $this->assertSame('PHPUnit\TestFixture\Metadata\Attribute', $target->namespace()); + + $target = array_shift($targets); + $this->assertInstanceOf(Class_::class, $target); + $this->assertSame('PHPUnit\TestFixture\Metadata\Attribute\Example', $target->className()); + + $target = array_shift($targets); + $this->assertInstanceOf(ClassesThatExtendClass::class, $target); + $this->assertSame('PHPUnit\TestFixture\Metadata\Attribute\Example', $target->className()); + + $target = array_shift($targets); + $this->assertInstanceOf(ClassesThatImplementInterface::class, $target); + $this->assertSame('PHPUnit\TestFixture\Metadata\Attribute\Example', $target->interfaceName()); + + $target = array_shift($targets); + $this->assertInstanceOf(Trait_::class, $target); + $this->assertSame('PHPUnit\TestFixture\Metadata\Attribute\ExampleTrait', $target->traitName()); + + $target = array_shift($targets); + $this->assertInstanceOf(Method::class, $target); + $this->assertSame('PHPUnit\TestFixture\Metadata\Attribute\Example', $target->className()); + $this->assertSame('method', $target->methodName()); + + $target = array_shift($targets); + $this->assertInstanceOf(Function_::class, $target); + $this->assertSame('f', $target->functionName()); + } + + #[TestDox('Maps #[Uses*()] metadata to phpunit/php-code-coverage TargetCollection')] + public function testMapsUsesMetadataToCodeCoverageTargetCollection(): void + { + $targets = (new CodeCoverage)->usesTargets(UsesTest::class, 'testOne'); + + $this->assertNotFalse($targets); + $this->assertCount(7, $targets); + + $targets = $targets->asArray(); + + $target = array_shift($targets); + $this->assertInstanceOf(Namespace_::class, $target); + $this->assertSame('PHPUnit\TestFixture\Metadata\Attribute', $target->namespace()); + + $target = array_shift($targets); + $this->assertInstanceOf(Class_::class, $target); + $this->assertSame('PHPUnit\TestFixture\Metadata\Attribute\Example', $target->className()); + + $target = array_shift($targets); + $this->assertInstanceOf(ClassesThatExtendClass::class, $target); + $this->assertSame('PHPUnit\TestFixture\Metadata\Attribute\Example', $target->className()); + + $target = array_shift($targets); + $this->assertInstanceOf(ClassesThatImplementInterface::class, $target); + $this->assertSame('PHPUnit\TestFixture\Metadata\Attribute\Example', $target->interfaceName()); + + $target = array_shift($targets); + $this->assertInstanceOf(Trait_::class, $target); + $this->assertSame('PHPUnit\TestFixture\Metadata\Attribute\ExampleTrait', $target->traitName()); + + $target = array_shift($targets); + $this->assertInstanceOf(Method::class, $target); + $this->assertSame('PHPUnit\TestFixture\Metadata\Attribute\Example', $target->className()); + $this->assertSame('method', $target->methodName()); + + $target = array_shift($targets); + $this->assertInstanceOf(Function_::class, $target); + $this->assertSame('f', $target->functionName()); + } + + /** + * @param class-string $testCase + */ + #[DataProvider('canSkipCoverageProvider')] + public function testWhetherCollectionOfCodeCoverageDataCanBeSkippedCanBeDetermined(bool $expected, string $testCase): void + { + $test = new $testCase('testSomething'); + $coverageRequired = (new CodeCoverage)->shouldCodeCoverageBeCollectedFor($test); + $canSkipCoverage = !$coverageRequired; + + $this->assertSame($expected, $canSkipCoverage); + } +} diff --git a/tests/unit/Metadata/Api/DataProviderTest.php b/tests/unit/Metadata/Api/DataProviderTest.php new file mode 100644 index 00000000000..28e5f1bd79b --- /dev/null +++ b/tests/unit/Metadata/Api/DataProviderTest.php @@ -0,0 +1,228 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Api; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\InvalidDataProviderException; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\DuplicateKeyDataProvidersTest; +use PHPUnit\TestFixture\DuplicateKeyDataProviderTest; +use PHPUnit\TestFixture\MultipleDataProviderTest; +use PHPUnit\TestFixture\TestWithAttributeDataProviderTest; +use PHPUnit\TestFixture\VariousIterableDataProviderTest; + +#[CoversClass(DataProvider::class)] +#[Small] +#[Group('metadata')] +final class DataProviderTest extends TestCase +{ + /** + * Check if all data providers are being merged. + */ + public function testMultipleDataProviders(): void + { + $dataSets = $this->getRawDataFromProvidedData( + (new DataProvider)->providedData(MultipleDataProviderTest::class, 'testOne'), + ); + + $this->assertCount(9, $dataSets); + + $aCount = 0; + $bCount = 0; + $cCount = 0; + + for ($i = 0; $i < 9; $i++) { + $aCount += $dataSets[$i][0] != null ? 1 : 0; + $bCount += $dataSets[$i][1] != null ? 1 : 0; + $cCount += $dataSets[$i][2] != null ? 1 : 0; + } + + $this->assertEquals(3, $aCount); + $this->assertEquals(3, $bCount); + $this->assertEquals(3, $cCount); + } + + public function testMultipleYieldIteratorDataProviders(): void + { + $dataSets = $this->getRawDataFromProvidedData( + (new DataProvider)->providedData(MultipleDataProviderTest::class, 'testTwo'), + ); + + $this->assertCount(9, $dataSets); + + $aCount = 0; + $bCount = 0; + $cCount = 0; + + for ($i = 0; $i < 9; $i++) { + $aCount += $dataSets[$i][0] != null ? 1 : 0; + $bCount += $dataSets[$i][1] != null ? 1 : 0; + $cCount += $dataSets[$i][2] != null ? 1 : 0; + } + + $this->assertEquals(3, $aCount); + $this->assertEquals(3, $bCount); + $this->assertEquals(3, $cCount); + } + + public function testWithVariousIterableDataProvidersFromParent(): void + { + $dataSets = $this->getRawDataFromProvidedData( + (new DataProvider)->providedData(VariousIterableDataProviderTest::class, 'testFromParent'), + ); + + $this->assertEquals([ + ['J'], + ['K'], + ['L'], + ['M'], + ['N'], + ['O'], + ['P'], + ['Q'], + ['R'], + ], $dataSets); + } + + public function testWithVariousIterableDataProvidersInParent(): void + { + $dataSets = $this->getRawDataFromProvidedData( + (new DataProvider)->providedData(VariousIterableDataProviderTest::class, 'testInParent'), + ); + + $this->assertEquals([ + ['J'], + ['K'], + ['L'], + ['M'], + ['N'], + ['O'], + ['P'], + ['Q'], + ['R'], + ], $dataSets); + } + + public function testWithVariousIterableAbstractDataProviders(): void + { + $dataSets = $this->getRawDataFromProvidedData( + (new DataProvider)->providedData(VariousIterableDataProviderTest::class, 'testAbstract'), + ); + + $this->assertEquals([ + ['S'], + ['T'], + ['U'], + ['V'], + ['W'], + ['X'], + ['Y'], + ['Z'], + ['P'], + ], $dataSets); + } + + public function testWithVariousIterableStaticDataProviders(): void + { + $dataSets = $this->getRawDataFromProvidedData( + (new DataProvider)->providedData(VariousIterableDataProviderTest::class, 'testStatic'), + ); + + $this->assertEquals([ + ['A'], + ['B'], + ['C'], + ['D'], + ['E'], + ['F'], + ['G'], + ['H'], + ['I'], + ], $dataSets); + } + + public function testWithVariousIterableNonStaticDataProviders(): void + { + $dataSets = $this->getRawDataFromProvidedData( + (new DataProvider)->providedData(VariousIterableDataProviderTest::class, 'testNonStatic'), + ); + + $this->assertEquals([ + ['S'], + ['T'], + ['U'], + ['V'], + ['W'], + ['X'], + ['Y'], + ['Z'], + ['P'], + ], $dataSets); + } + + public function testWithDuplicateKeyDataProvider(): void + { + $this->expectException(InvalidDataProviderException::class); + $this->expectExceptionMessage('The key "foo" has already been defined by provider PHPUnit\TestFixture\DuplicateKeyDataProviderTest::dataProvider'); + + /* @noinspection UnusedFunctionResultInspection */ + (new DataProvider)->providedData(DuplicateKeyDataProviderTest::class, 'test'); + } + + public function testTestWithAttribute(): void + { + $dataSets = $this->getRawDataFromProvidedData( + (new DataProvider)->providedData(TestWithAttributeDataProviderTest::class, 'testWithAttribute'), + ); + + $this->assertSame([ + 'foo' => ['a', 'b'], + 'bar' => ['c', 'd'], + 0 => ['e', 'f'], + 1 => ['g', 'h'], + ], $dataSets); + } + + public function testTestWithAttributeWithDuplicateKey(): void + { + $this->expectException(InvalidDataProviderException::class); + $this->expectExceptionMessage('The key "foo" has already been defined by TestWith#0 attribute'); + + /* @noinspection UnusedFunctionResultInspection */ + (new DataProvider)->providedData(TestWithAttributeDataProviderTest::class, 'testWithDuplicateName'); + } + + public function testWithDuplicateKeyDataProviders(): void + { + $this->expectException(InvalidDataProviderException::class); + $this->expectExceptionMessage('The key "bar" has already been defined by provider PHPUnit\TestFixture\DuplicateKeyDataProvidersTest::dataProvider1'); + + /* @noinspection UnusedFunctionResultInspection */ + (new DataProvider)->providedData(DuplicateKeyDataProvidersTest::class, 'test'); + } + + /** + * @param array $providedData + * + * @return array + */ + private function getRawDataFromProvidedData(array $providedData): array + { + $raw = []; + + foreach ($providedData as $key => $data) { + $raw[$key] = $data->value(); + } + + return $raw; + } +} diff --git a/tests/unit/Metadata/Api/GroupsTest.php b/tests/unit/Metadata/Api/GroupsTest.php new file mode 100644 index 00000000000..08ff574e84c --- /dev/null +++ b/tests/unit/Metadata/Api/GroupsTest.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Api; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\LargeGroupAttributesTest; +use PHPUnit\TestFixture\MediumGroupAttributesTest; +use PHPUnit\TestFixture\NoGroupsMetadataTest; +use PHPUnit\TestFixture\SmallGroupAnnotationsTest; +use PHPUnit\TestFixture\SmallGroupAttributesTest; + +#[CoversClass(Groups::class)] +#[Small] +#[Group('metadata')] +final class GroupsTest extends TestCase +{ + public static function provider(): array + { + return [ + [ + [ + ], + NoGroupsMetadataTest::class, + 'testOne', + false, + ], + + [ + [ + 'the-group', + 'the-ticket', + 'small', + 'another-group', + 'another-ticket', + ], + SmallGroupAttributesTest::class, + 'testOne', + false, + ], + + [ + [ + 'the-group', + 'the-ticket', + 'small', + 'another-group', + 'another-ticket', + ], + SmallGroupAnnotationsTest::class, + 'testOne', + false, + ], + + [ + [ + 'the-group', + 'the-ticket', + 'small', + 'another-group', + 'another-ticket', + '__phpunit_covers_phpunit\testfixture\coveredclass', + '__phpunit_uses_phpunit\testfixture\coveredclass', + ], + SmallGroupAttributesTest::class, + 'testOne', + true, + ], + + [ + [ + 'the-group', + 'the-ticket', + 'small', + 'another-group', + 'another-ticket', + '__phpunit_covers_phpunit\testfixture\coveredclass', + '__phpunit_uses_phpunit\testfixture\coveredclass', + ], + SmallGroupAnnotationsTest::class, + 'testOne', + true, + ], + + [ + [ + 'medium', + ], + MediumGroupAttributesTest::class, + 'testOne', + false, + ], + + [ + [ + 'large', + ], + LargeGroupAttributesTest::class, + 'testOne', + false, + ], + ]; + } + + #[DataProvider('provider')] + public function testAssignsGroups(array $expected, string $className, string $methodName, bool $includeVirtual): void + { + $this->assertSame( + $expected, + (new Groups)->groups($className, $methodName, $includeVirtual), + ); + } + + public function testAssignsSize(): void + { + $this->assertTrue((new Groups)->size(SmallGroupAttributesTest::class, 'testOne')->isSmall()); + $this->assertTrue((new Groups)->size(MediumGroupAttributesTest::class, 'testOne')->isMedium()); + $this->assertTrue((new Groups)->size(LargeGroupAttributesTest::class, 'testOne')->isLarge()); + $this->assertTrue((new Groups)->size(NoGroupsMetadataTest::class, 'testOne')->isUnknown()); + } +} diff --git a/tests/unit/Metadata/Api/HookMethodsTest.php b/tests/unit/Metadata/Api/HookMethodsTest.php new file mode 100644 index 00000000000..f912b50205a --- /dev/null +++ b/tests/unit/Metadata/Api/HookMethodsTest.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Api; + +use function array_keys; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use PHPUnit\Runner\HookMethod; +use PHPUnit\Runner\HookMethodCollection; +use PHPUnit\TestFixture\TestWithHookMethodsPrioritizedTest; +use PHPUnit\TestFixture\TestWithHookMethodsTest; +use PHPUnit\TestFixture\TestWithoutHookMethodsTest; + +#[CoversClass(HookMethods::class)] +#[Small] +#[Group('metadata')] +final class HookMethodsTest extends TestCase +{ + public function testReturnsDefaultHookMethodsForClassThatDoesNotExist(): void + { + $this->assertEquals( + [ + 'beforeClass' => HookMethodCollection::defaultBeforeClass(), + 'before' => HookMethodCollection::defaultBefore(), + 'preCondition' => HookMethodCollection::defaultPreCondition(), + 'postCondition' => HookMethodCollection::defaultPostCondition(), + 'after' => HookMethodCollection::defaultAfter(), + 'afterClass' => HookMethodCollection::defaultAfterClass(), + ], + (new HookMethods)->hookMethods('does not exist'), + ); + } + + public function testReturnsDefaultHookMethodsInTestClassWithoutHookMethods(): void + { + $this->assertEquals( + [ + 'beforeClass' => HookMethodCollection::defaultBeforeClass(), + 'before' => HookMethodCollection::defaultBefore(), + 'preCondition' => HookMethodCollection::defaultPreCondition(), + 'postCondition' => HookMethodCollection::defaultPostCondition(), + 'after' => HookMethodCollection::defaultAfter(), + 'afterClass' => HookMethodCollection::defaultAfterClass(), + ], + (new HookMethods)->hookMethods(TestWithoutHookMethodsTest::class), + ); + } + + public function testFindsHookMethodsInTestClassWithHookMethods(): void + { + $hookMethods = (new HookMethods)->hookMethods(TestWithHookMethodsTest::class); + $this->assertSame(['beforeClass', 'before', 'preCondition', 'postCondition', 'after', 'afterClass'], array_keys($hookMethods)); + + $beforeClassHooks = HookMethodCollection::defaultBeforeClass(); + $beforeClassHooks->add(new HookMethod('beforeFirstTestWithAttribute', 0)); + $this->assertEquals($beforeClassHooks, $hookMethods['beforeClass']); + + $beforeHooks = HookMethodCollection::defaultBefore(); + $beforeHooks->add(new HookMethod('beforeEachTestWithAttribute', 0)); + $this->assertEquals($beforeHooks, $hookMethods['before']); + + $preConditionHooks = HookMethodCollection::defaultPreCondition(); + $preConditionHooks->add(new HookMethod('preConditionsWithAttribute', 0)); + $this->assertEquals($preConditionHooks, $hookMethods['preCondition']); + + $postConditionHooks = HookMethodCollection::defaultPostCondition(); + $postConditionHooks->add(new HookMethod('postConditionsWithAttribute', 0)); + $this->assertEquals($postConditionHooks, $hookMethods['postCondition']); + + $afterHooks = HookMethodCollection::defaultAfter(); + $afterHooks->add(new HookMethod('afterEachTestWithAttribute', 0)); + $this->assertEquals($afterHooks, $hookMethods['after']); + + $afterClassHooks = HookMethodCollection::defaultAfterClass(); + $afterClassHooks->add(new HookMethod('afterLastTestWithAttribute', 0)); + $this->assertEquals($afterClassHooks, $hookMethods['afterClass']); + } + + public function testFindsHookMethodsInTestClassWithHookMethodsPrioritized(): void + { + $hookMethods = (new HookMethods)->hookMethods(TestWithHookMethodsPrioritizedTest::class); + $this->assertSame(['beforeClass', 'before', 'preCondition', 'postCondition', 'after', 'afterClass'], array_keys($hookMethods)); + + $beforeClassHooks = HookMethodCollection::defaultBeforeClass(); + $beforeClassHooks->add(new HookMethod('beforeFirstTest', 1)); + $this->assertEquals($beforeClassHooks, $hookMethods['beforeClass']); + + $beforeHooks = HookMethodCollection::defaultBefore(); + $beforeHooks->add(new HookMethod('beforeEachTest', 2)); + $this->assertEquals($beforeHooks, $hookMethods['before']); + + $preConditionHooks = HookMethodCollection::defaultPreCondition(); + $preConditionHooks->add(new HookMethod('preConditions', 3)); + $this->assertEquals($preConditionHooks, $hookMethods['preCondition']); + + $postConditionHooks = HookMethodCollection::defaultPostCondition(); + $postConditionHooks->add(new HookMethod('postConditions', 4)); + $this->assertEquals($postConditionHooks, $hookMethods['postCondition']); + + $afterHooks = HookMethodCollection::defaultAfter(); + $afterHooks->add(new HookMethod('afterEachTest', 5)); + $this->assertEquals($afterHooks, $hookMethods['after']); + + $afterClassHooks = HookMethodCollection::defaultAfterClass(); + $afterClassHooks->add(new HookMethod('afterLastTest', 6)); + $this->assertEquals($afterClassHooks, $hookMethods['afterClass']); + } +} diff --git a/tests/unit/Metadata/Api/RequirementsTest.php b/tests/unit/Metadata/Api/RequirementsTest.php new file mode 100644 index 00000000000..0bd0be255bc --- /dev/null +++ b/tests/unit/Metadata/Api/RequirementsTest.php @@ -0,0 +1,162 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Api; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\RequirementsEnvironmentVariableTest; + +#[CoversClass(Requirements::class)] +#[Small] +#[Group('metadata')] +final class RequirementsTest extends TestCase +{ + public static function missingRequirementsProvider(): array + { + return [ + ['testOne', []], + ['testNine', [ + 'Function testFunc() is required.', + ]], + ['testTen', [ + 'PHP extension testExt is required.', + ]], + ['testAlwaysSkip', [ + 'PHPUnit >= 1111111 is required.', + ]], + ['testAlwaysSkip2', [ + 'PHP >= 9999999 is required.', + ]], + ['testAlwaysSkip3', [ + 'Operating system DOESNOTEXIST is required.', + ]], + ['testAllPossibleRequirements', [ + 'PHP 99-dev is required.', + 'PHPUnit 99-dev is required.', + 'Operating system DOESNOTEXIST is required.', + 'Operating system DOESNOTEXIST is required.', + 'Function testFuncOne() is required.', + 'Function testFunc2() is required.', + 'Method DoesNotExist::doesNotExist() is required.', + 'PHP extension testExtOne is required.', + 'PHP extension testExt2 is required.', + 'PHP extension testExtThree >= 2.0 is required.', + 'Setting "not_a_setting" is required to be "Off".', + ]], + ['testPHPVersionOperatorLessThan', [ + 'PHP < 5.4 is required.', + ]], + ['testPHPVersionOperatorLessThanEquals', [ + 'PHP <= 5.4 is required.', + ]], + ['testPHPVersionOperatorGreaterThan', [ + 'PHP > 99 is required.', + ]], + ['testPHPVersionOperatorGreaterThanEquals', [ + 'PHP >= 99 is required.', + ]], + ['testPHPVersionOperatorNoSpace', [ + 'PHP >= 99 is required.', + ]], + ['testPHPVersionOperatorEquals', [ + 'PHP = 5.4 is required.', + ]], + ['testPHPVersionOperatorDoubleEquals', [ + 'PHP == 5.4 is required.', + ]], + ['testPHPUnitVersionOperatorLessThan', [ + 'PHPUnit < 1.0 is required.', + ]], + ['testPHPUnitVersionOperatorLessThanEquals', [ + 'PHPUnit <= 1.0 is required.', + ]], + ['testPHPUnitVersionOperatorGreaterThan', [ + 'PHPUnit > 99 is required.', + ]], + ['testPHPUnitVersionOperatorGreaterThanEquals', [ + 'PHPUnit >= 99 is required.', + ]], + ['testPHPUnitVersionOperatorEquals', [ + 'PHPUnit = 1.0 is required.', + ]], + ['testPHPUnitVersionOperatorDoubleEquals', [ + 'PHPUnit == 1.0 is required.', + ]], + ['testPHPUnitVersionOperatorNoSpace', [ + 'PHPUnit >= 99 is required.', + ]], + ['testExtensionVersionOperatorLessThan', [ + 'PHP extension testExtOne < 1.0 is required.', + ]], + ['testExtensionVersionOperatorLessThanEquals', [ + 'PHP extension testExtOne <= 1.0 is required.', + ]], + ['testExtensionVersionOperatorGreaterThan', [ + 'PHP extension testExtOne > 99 is required.', + ]], + ['testExtensionVersionOperatorGreaterThanEquals', [ + 'PHP extension testExtOne >= 99 is required.', + ]], + ['testExtensionVersionOperatorEquals', [ + 'PHP extension testExtOne = 1.0 is required.', + ]], + ['testExtensionVersionOperatorDoubleEquals', [ + 'PHP extension testExtOne == 1.0 is required.', + ]], + ['testExtensionVersionOperatorNoSpace', [ + 'PHP extension testExtOne >= 99 is required.', + ]], + ['testVersionConstraintTildeMajor', [ + 'PHP ~1.0 is required.', + 'PHPUnit ~2.0 is required.', + ]], + ['testVersionConstraintCaretMajor', [ + 'PHP ^1.0 is required.', + 'PHPUnit ^2.0 is required.', + ]], + ['testPHPUnitExtensionRequired', [ + 'PHPUnit extension "PHPUnit\TestFixture\SomeExtension" is required.', + 'PHPUnit extension "PHPUnit\TestFixture\SomeOtherExtension" is required.', + ]], + ]; + } + + protected function tearDown(): void + { + unset($_ENV['FOO'], $_ENV['BAR']); + } + + #[DataProvider('missingRequirementsProvider')] + public function testGetMissingRequirements(string $test, array $result): void + { + $this->assertEquals( + $result, + (new Requirements)->requirementsNotSatisfiedFor(\PHPUnit\TestFixture\RequirementsTest::class, $test), + ); + } + + public function testGetMissingEnvironmentVariableRequirements(): void + { + $_ENV['FOO'] = 'foo'; + $_ENV['BAR'] = ''; + + $this->assertEquals( + [ + 'Environment variable "FOO" is required to be "bar".', + 'Environment variable "BAR" is required.', + 'Environment variable "BAZ" is required.', + ], + (new Requirements)->requirementsNotSatisfiedFor(RequirementsEnvironmentVariableTest::class, 'testRequiresEnvironmentVariable'), + ); + } +} diff --git a/tests/unit/Metadata/MetadataCollectionTest.php b/tests/unit/Metadata/MetadataCollectionTest.php new file mode 100644 index 00000000000..89aa5eb9df0 --- /dev/null +++ b/tests/unit/Metadata/MetadataCollectionTest.php @@ -0,0 +1,635 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; +use PHPUnit\Metadata\Version\ComparisonRequirement; +use PHPUnit\Util\VersionComparisonOperator; +use stdClass; + +#[CoversClass(MetadataCollection::class)] +#[CoversClass(MetadataCollectionIterator::class)] +#[UsesClass(After::class)] +#[UsesClass(AfterClass::class)] +#[UsesClass(BackupGlobals::class)] +#[UsesClass(BackupStaticProperties::class)] +#[UsesClass(Before::class)] +#[UsesClass(BeforeClass::class)] +#[UsesClass(CoversClass::class)] +#[UsesClass(CoversFunction::class)] +#[UsesClass(CoversMethod::class)] +#[UsesClass(CoversNothing::class)] +#[UsesClass(DataProvider::class)] +#[UsesClass(DependsOnClass::class)] +#[UsesClass(DependsOnMethod::class)] +#[UsesClass(DisableReturnValueGenerationForTestDoubles::class)] +#[UsesClass(DoesNotPerformAssertions::class)] +#[UsesClass(Group::class)] +#[UsesClass(Metadata::class)] +#[UsesClass(PostCondition::class)] +#[UsesClass(PreCondition::class)] +#[UsesClass(PreserveGlobalState::class)] +#[UsesClass(RequiresFunction::class)] +#[UsesClass(RequiresMethod::class)] +#[UsesClass(RequiresOperatingSystem::class)] +#[UsesClass(RequiresOperatingSystemFamily::class)] +#[UsesClass(RequiresPhp::class)] +#[UsesClass(RequiresPhpExtension::class)] +#[UsesClass(RequiresPhpunit::class)] +#[UsesClass(RequiresPhpunitExtension::class)] +#[UsesClass(RequiresEnvironmentVariable::class)] +#[UsesClass(RequiresSetting::class)] +#[UsesClass(RunInSeparateProcess::class)] +#[UsesClass(RunTestsInSeparateProcesses::class)] +#[UsesClass(Test::class)] +#[UsesClass(TestDox::class)] +#[UsesClass(TestWith::class)] +#[UsesClass(UsesClass::class)] +#[UsesClass(UsesFunction::class)] +#[Small] +#[Group('metadata')] +final class MetadataCollectionTest extends TestCase +{ + public function testCanBeEmpty(): void + { + $collection = MetadataCollection::fromArray([]); + + $this->assertCount(0, $collection); + $this->assertTrue($collection->isEmpty()); + $this->assertFalse($collection->isNotEmpty()); + } + + public function testCanBeCreatedFromArray(): void + { + $metadata = Metadata::test(); + + $collection = MetadataCollection::fromArray([$metadata]); + + $this->assertContains($metadata, $collection); + } + + public function testIsCountable(): void + { + $metadata = Metadata::test(); + + $collection = MetadataCollection::fromArray([$metadata]); + + $this->assertCount(1, $collection); + $this->assertFalse($collection->isEmpty()); + $this->assertTrue($collection->isNotEmpty()); + } + + public function testIsIterable(): void + { + $metadata = Metadata::test(); + + foreach (MetadataCollection::fromArray([$metadata]) as $key => $value) { + $this->assertSame(0, $key); + $this->assertSame($metadata, $value); + } + } + + public function testCanBeMerged(): void + { + $a = MetadataCollection::fromArray([Metadata::before(0)]); + $b = MetadataCollection::fromArray([Metadata::after(0)]); + $c = $a->mergeWith($b); + + $this->assertCount(2, $c); + $this->assertTrue($c->asArray()[0]->isBefore()); + $this->assertTrue($c->asArray()[1]->isAfter()); + } + + public function test_Can_be_filtered_for_class_level_metadata(): void + { + $collection = MetadataCollection::fromArray( + [ + Metadata::coversClass(''), + Metadata::ignoreDeprecationsOnMethod(), + ], + ); + + $this->assertCount(2, $collection); + + $this->assertCount(1, $collection->isClassLevel()); + $this->assertTrue($collection->isClassLevel()->asArray()[0]->isClassLevel()); + + $this->assertCount(1, $collection->isMethodLevel()); + $this->assertTrue($collection->isMethodLevel()->asArray()[0]->isMethodLevel()); + } + + public function test_Can_be_filtered_for_AfterClass(): void + { + $collection = $this->collectionWithOneOfEach()->isAfterClass(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isAfterClass()); + } + + public function test_Can_be_filtered_for_After(): void + { + $collection = $this->collectionWithOneOfEach()->isAfter(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isAfter()); + } + + public function test_Can_be_filtered_for_BackupGlobals(): void + { + $collection = $this->collectionWithOneOfEach()->isBackupGlobals(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isBackupGlobals()); + } + + public function test_Can_be_filtered_for_BackupStaticProperties(): void + { + $collection = $this->collectionWithOneOfEach()->isBackupStaticProperties(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isBackupStaticProperties()); + } + + public function test_Can_be_filtered_for_BeforeClass(): void + { + $collection = $this->collectionWithOneOfEach()->isBeforeClass(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isBeforeClass()); + } + + public function test_Can_be_filtered_for_Before(): void + { + $collection = $this->collectionWithOneOfEach()->isBefore(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isBefore()); + } + + public function test_Can_be_filtered_for_CoversNamespace(): void + { + $collection = $this->collectionWithOneOfEach()->isCoversNamespace(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isCoversNamespace()); + } + + public function test_Can_be_filtered_for_CoversClass(): void + { + $collection = $this->collectionWithOneOfEach()->isCoversClass(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isCoversClass()); + } + + public function test_Can_be_filtered_for_CoversClassesThatExtendClass(): void + { + $collection = $this->collectionWithOneOfEach()->isCoversClassesThatExtendClass(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isCoversClassesThatExtendClass()); + } + + public function test_Can_be_filtered_for_CoversClassesThatImplementInterface(): void + { + $collection = $this->collectionWithOneOfEach()->isCoversClassesThatImplementInterface(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isCoversClassesThatImplementInterface()); + } + + public function test_Can_be_filtered_for_CoversTrait(): void + { + $collection = $this->collectionWithOneOfEach()->isCoversTrait(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isCoversTrait()); + } + + public function test_Can_be_filtered_for_CoversFunction(): void + { + $collection = $this->collectionWithOneOfEach()->isCoversFunction(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isCoversFunction()); + } + + public function test_Can_be_filtered_for_CoversMethod(): void + { + $collection = $this->collectionWithOneOfEach()->isCoversMethod(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isCoversMethod()); + } + + public function test_Can_be_filtered_for_CoversNothing(): void + { + $collection = $this->collectionWithOneOfEach()->isCoversNothing(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isCoversNothing()); + } + + public function test_Can_be_filtered_for_DataProvider(): void + { + $collection = $this->collectionWithOneOfEach()->isDataProvider(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isDataProvider()); + } + + public function test_Can_be_filtered_for_Depends(): void + { + $collection = $this->collectionWithOneOfEach()->isDepends(); + + $this->assertCount(2, $collection); + $this->assertTrue($collection->asArray()[0]->isDependsOnClass()); + $this->assertTrue($collection->asArray()[1]->isDependsOnMethod()); + } + + public function test_Can_be_filtered_for_DependsOnClass(): void + { + $collection = $this->collectionWithOneOfEach()->isDependsOnClass(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isDependsOnClass()); + } + + public function test_Can_be_filtered_for_DependsOnMethod(): void + { + $collection = $this->collectionWithOneOfEach()->isDependsOnMethod(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isDependsOnMethod()); + } + + public function test_Can_be_filtered_for_DisableReturnValueGenerationForTestDoubles(): void + { + $collection = $this->collectionWithOneOfEach()->isDisableReturnValueGenerationForTestDoubles(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isDisableReturnValueGenerationForTestDoubles()); + } + + public function test_Can_be_filtered_for_DoesNotPerformAssertions(): void + { + $collection = $this->collectionWithOneOfEach()->isDoesNotPerformAssertions(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isDoesNotPerformAssertions()); + } + + public function test_Can_be_filtered_for_ExcludeGlobalVariableFromBackup(): void + { + $collection = $this->collectionWithOneOfEach()->isExcludeGlobalVariableFromBackup(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isExcludeGlobalVariableFromBackup()); + } + + public function test_Can_be_filtered_for_ExcludeStaticPropertyFromBackup(): void + { + $collection = $this->collectionWithOneOfEach()->isExcludeStaticPropertyFromBackup(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isExcludeStaticPropertyFromBackup()); + } + + public function test_Can_be_filtered_for_Group(): void + { + $collection = $this->collectionWithOneOfEach()->isGroup(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isGroup()); + } + + public function test_Can_be_filtered_for_IgnoreDeprecations(): void + { + $collection = $this->collectionWithOneOfEach()->isIgnoreDeprecations(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isIgnoreDeprecations()); + } + + public function test_Can_be_filtered_for_IgnorePhpunitDeprecations(): void + { + $collection = $this->collectionWithOneOfEach()->isIgnorePhpunitDeprecations(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isIgnorePhpunitDeprecations()); + } + + public function test_Can_be_filtered_for_IgnorePhpunitWarnings(): void + { + $collection = $this->collectionWithOneOfEach()->isIgnorePhpunitWarnings(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isIgnorePhpunitWarnings()); + } + + public function test_Can_be_filtered_for_PostCondition(): void + { + $collection = $this->collectionWithOneOfEach()->isPostCondition(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isPostCondition()); + } + + public function test_Can_be_filtered_for_PreCondition(): void + { + $collection = $this->collectionWithOneOfEach()->isPreCondition(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isPreCondition()); + } + + public function test_Can_be_filtered_for_PreserveGlobalState(): void + { + $collection = $this->collectionWithOneOfEach()->isPreserveGlobalState(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isPreserveGlobalState()); + } + + public function test_Can_be_filtered_for_RequiresMethod(): void + { + $collection = $this->collectionWithOneOfEach()->isRequiresMethod(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isRequiresMethod()); + } + + public function test_Can_be_filtered_for_RequiresFunction(): void + { + $collection = $this->collectionWithOneOfEach()->isRequiresFunction(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isRequiresFunction()); + } + + public function test_Can_be_filtered_for_RequiresOperatingSystemFamily(): void + { + $collection = $this->collectionWithOneOfEach()->isRequiresOperatingSystemFamily(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isRequiresOperatingSystemFamily()); + } + + public function test_Can_be_filtered_for_RequiresOperatingSystem(): void + { + $collection = $this->collectionWithOneOfEach()->isRequiresOperatingSystem(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isRequiresOperatingSystem()); + } + + public function test_Can_be_filtered_for_RequiresPhpExtension(): void + { + $collection = $this->collectionWithOneOfEach()->isRequiresPhpExtension(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isRequiresPhpExtension()); + } + + public function test_Can_be_filtered_for_RequiresPhp(): void + { + $collection = $this->collectionWithOneOfEach()->isRequiresPhp(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isRequiresPhp()); + } + + public function test_Can_be_filtered_for_RequiresPhpunit(): void + { + $collection = $this->collectionWithOneOfEach()->isRequiresPhpunit(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isRequiresPhpunit()); + } + + public function test_Can_be_filtered_for_RequiresPhpunitExtension(): void + { + $collection = $this->collectionWithOneOfEach()->isRequiresPhpunitExtension(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isRequiresPhpunitExtension()); + } + + public function test_Can_be_filtered_for_RequiresEnvironmentVariable(): void + { + $collection = $this->collectionWithOneOfEach()->isRequiresEnvironmentVariable(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isRequiresEnvironmentVariable()); + } + + public function test_Can_be_filtered_for_WithEnvironmentVariable(): void + { + $collection = $this->collectionWithOneOfEach()->isWithEnvironmentVariable(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isWithEnvironmentVariable()); + } + + public function test_Can_be_filtered_for_RequiresSetting(): void + { + $collection = $this->collectionWithOneOfEach()->isRequiresSetting(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isRequiresSetting()); + } + + public function test_Can_be_filtered_for_RunInSeparateProcess(): void + { + $collection = $this->collectionWithOneOfEach()->isRunInSeparateProcess(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isRunInSeparateProcess()); + } + + public function test_Can_be_filtered_for_RunTestsInSeparateProcesses(): void + { + $collection = $this->collectionWithOneOfEach()->isRunTestsInSeparateProcesses(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isRunTestsInSeparateProcesses()); + } + + public function test_Can_be_filtered_for_TestDox(): void + { + $collection = $this->collectionWithOneOfEach()->isTestDox(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isTestDox()); + } + + public function test_Can_be_filtered_for_TestDoxFormatter(): void + { + $collection = $this->collectionWithOneOfEach()->isTestDoxFormatter(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isTestDoxFormatter()); + } + + public function test_Can_be_filtered_for_Test(): void + { + $collection = $this->collectionWithOneOfEach()->isTest(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isTest()); + } + + public function test_Can_be_filtered_for_TestWith(): void + { + $collection = $this->collectionWithOneOfEach()->isTestWith(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isTestWith()); + } + + public function test_Can_be_filtered_for_UsesNamespace(): void + { + $collection = $this->collectionWithOneOfEach()->isUsesNamespace(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isUsesNamespace()); + } + + public function test_Can_be_filtered_for_UsesClass(): void + { + $collection = $this->collectionWithOneOfEach()->isUsesClass(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isUsesClass()); + } + + public function test_Can_be_filtered_for_UsesClassesThatExtendClass(): void + { + $collection = $this->collectionWithOneOfEach()->isUsesClassesThatExtendClass(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isUsesClassesThatExtendClass()); + } + + public function test_Can_be_filtered_for_UsesClassesThatImplementInterface(): void + { + $collection = $this->collectionWithOneOfEach()->isUsesClassesThatImplementInterface(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isUsesClassesThatImplementInterface()); + } + + public function test_Can_be_filtered_for_UsesTrait(): void + { + $collection = $this->collectionWithOneOfEach()->isUsesTrait(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isUsesTrait()); + } + + public function test_Can_be_filtered_for_UsesFunction(): void + { + $collection = $this->collectionWithOneOfEach()->isUsesFunction(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isUsesFunction()); + } + + public function test_Can_be_filtered_for_UsesMethod(): void + { + $collection = $this->collectionWithOneOfEach()->isUsesMethod(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isUsesMethod()); + } + + public function test_Can_be_filtered_for_WithoutErrorHandler(): void + { + $collection = $this->collectionWithOneOfEach()->isWithoutErrorHandler(); + + $this->assertCount(1, $collection); + $this->assertTrue($collection->asArray()[0]->isWithoutErrorHandler()); + } + + private function collectionWithOneOfEach(): MetadataCollection + { + return MetadataCollection::fromArray( + [ + Metadata::afterClass(0), + Metadata::after(0), + Metadata::backupGlobalsOnClass(true), + Metadata::backupStaticPropertiesOnClass(true), + Metadata::beforeClass(0), + Metadata::before(0), + Metadata::coversNamespace(''), + Metadata::coversClass(''), + Metadata::coversClassesThatExtendClass(''), + Metadata::coversClassesThatImplementInterface(''), + Metadata::coversTrait(''), + Metadata::coversFunction(''), + Metadata::coversMethod('', ''), + Metadata::coversNothingOnClass(), + Metadata::dataProvider('', '', true), + Metadata::dependsOnClass('', false, false), + Metadata::dependsOnMethod('', '', false, false), + Metadata::disableReturnValueGenerationForTestDoubles(), + Metadata::doesNotPerformAssertionsOnClass(), + Metadata::excludeGlobalVariableFromBackupOnClass(''), + Metadata::excludeStaticPropertyFromBackupOnClass('', ''), + Metadata::groupOnClass(''), + Metadata::ignoreDeprecationsOnClass(), + Metadata::ignorePhpunitDeprecationsOnClass(), + Metadata::ignorePhpunitWarnings(null), + Metadata::postCondition(0), + Metadata::preCondition(0), + Metadata::preserveGlobalStateOnClass(true), + Metadata::requiresMethodOnClass('', ''), + Metadata::requiresFunctionOnClass(''), + Metadata::requiresOperatingSystemFamilyOnClass(''), + Metadata::requiresOperatingSystemOnClass(''), + Metadata::requiresPhpExtensionOnClass('', null), + Metadata::requiresPhpOnClass( + new ComparisonRequirement( + '8.0.0', + new VersionComparisonOperator('>='), + ), + ), + Metadata::requiresPhpunitOnClass( + new ComparisonRequirement( + '10.0.0', + new VersionComparisonOperator('>='), + ), + ), + Metadata::requiresPhpunitExtensionOnClass(stdClass::class), + Metadata::requiresEnvironmentVariableOnClass('foo', 'bar'), + Metadata::requiresSettingOnClass('foo', 'bar'), + Metadata::runInSeparateProcess(), + Metadata::runTestsInSeparateProcesses(), + Metadata::testDoxOnClass(''), + Metadata::testDoxFormatter('', ''), + Metadata::test(), + Metadata::testWith([]), + Metadata::usesNamespace(''), + Metadata::usesClass(''), + Metadata::usesClassesThatExtendClass(''), + Metadata::usesClassesThatImplementInterface(''), + Metadata::usesTrait(''), + Metadata::usesFunction(''), + Metadata::usesMethod('', ''), + Metadata::withEnvironmentVariableOnClass('foo', 'bar'), + Metadata::withoutErrorHandler(), + ], + ); + } +} diff --git a/tests/unit/Metadata/MetadataTest.php b/tests/unit/Metadata/MetadataTest.php new file mode 100644 index 00000000000..c95cc4ce14c --- /dev/null +++ b/tests/unit/Metadata/MetadataTest.php @@ -0,0 +1,4985 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversClassesThatExtendClass; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use PHPUnit\Metadata\Version\ComparisonRequirement; +use PHPUnit\TestFixture\Metadata\Attribute\ExampleTrait; +use PHPUnit\Util\VersionComparisonOperator; + +#[CoversClass(Metadata::class)] +#[CoversClassesThatExtendClass(Metadata::class)] +#[Small] +#[Group('metadata')] +final class MetadataTest extends TestCase +{ + public function testCanBeAfter(): void + { + $priority = 0; + + $metadata = Metadata::after($priority); + + $this->assertTrue($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + $this->assertSame($priority, $metadata->priority()); + } + + public function testCanBeAfterClass(): void + { + $priority = 0; + + $metadata = Metadata::afterClass($priority); + + $this->assertFalse($metadata->isAfter()); + $this->assertTrue($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + $this->assertSame($priority, $metadata->priority()); + } + + public function testCanBeBackupGlobalsOnClass(): void + { + $metadata = Metadata::backupGlobalsOnClass(false); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertTrue($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertFalse($metadata->enabled()); + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeBackupGlobalsOnMethod(): void + { + $metadata = Metadata::backupGlobalsOnMethod(false); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertTrue($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertFalse($metadata->enabled()); + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeBackupStaticPropertiesOnClass(): void + { + $metadata = Metadata::backupStaticPropertiesOnClass(false); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertTrue($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertFalse($metadata->enabled()); + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeBackupStaticPropertiesOnMethod(): void + { + $metadata = Metadata::backupStaticPropertiesOnMethod(false); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertTrue($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertFalse($metadata->enabled()); + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeBeforeClass(): void + { + $priority = 0; + + $metadata = Metadata::beforeClass($priority); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertTrue($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + $this->assertSame($priority, $metadata->priority()); + } + + public function testCanBeBefore(): void + { + $priority = 0; + + $metadata = Metadata::before($priority); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertTrue($metadata->isBefore()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + $this->assertSame($priority, $metadata->priority()); + } + + public function testCanBeCoversClass(): void + { + $metadata = Metadata::coversClass(self::class); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertTrue($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame(self::class, $metadata->className()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeCoversNamespace(): void + { + $namespace = 'namespace'; + + $metadata = Metadata::coversNamespace($namespace); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertTrue($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame($namespace, $metadata->namespace()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeCoversClassesThatExtendClass(): void + { + $metadata = Metadata::coversClassesThatExtendClass(self::class); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertTrue($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame(self::class, $metadata->className()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeCoversClassesThatImplementInterface(): void + { + $metadata = Metadata::coversClassesThatImplementInterface(self::class); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertTrue($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame(self::class, $metadata->interfaceName()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeCoversFunction(): void + { + $metadata = Metadata::coversFunction('f'); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertTrue($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('f', $metadata->functionName()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeCoversMethod(): void + { + $metadata = Metadata::coversMethod(self::class, 'testCanBeCoversMethod'); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertTrue($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame(self::class, $metadata->className()); + $this->assertSame('testCanBeCoversMethod', $metadata->methodName()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeCoversNothingOnMethod(): void + { + $metadata = Metadata::coversNothingOnMethod(); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertTrue($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeCoversNothingOnClass(): void + { + $metadata = Metadata::coversNothingOnClass(); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertTrue($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeCoversTrait(): void + { + $metadata = Metadata::coversTrait(ExampleTrait::class); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertTrue($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame(ExampleTrait::class, $metadata->traitName()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeDataProvider(): void + { + $metadata = Metadata::dataProvider(self::class, 'method', true); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertTrue($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame(self::class, $metadata->className()); + $this->assertSame('method', $metadata->methodName()); + $this->assertTrue($metadata->validateArgumentCount()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeDependsOnClass(): void + { + $metadata = Metadata::dependsOnClass(self::class, false, false); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertTrue($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame(self::class, $metadata->className()); + $this->assertFalse($metadata->deepClone()); + $this->assertFalse($metadata->shallowClone()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeDependsOnMethod(): void + { + $metadata = Metadata::dependsOnMethod(self::class, 'method', false, false); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertTrue($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame(self::class, $metadata->className()); + $this->assertSame('method', $metadata->methodName()); + $this->assertFalse($metadata->deepClone()); + $this->assertFalse($metadata->shallowClone()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeDisableReturnValueGenerationForTestDoubles(): void + { + $metadata = Metadata::disableReturnValueGenerationForTestDoubles(); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertTrue($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeDoesNotPerformAssertionsOnClass(): void + { + $metadata = Metadata::doesNotPerformAssertionsOnClass(); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertTrue($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeDoesNotPerformAssertionsOnMethod(): void + { + $metadata = Metadata::doesNotPerformAssertionsOnMethod(); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertTrue($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeExcludeGlobalVariableFromBackupOnClass(): void + { + $metadata = Metadata::excludeGlobalVariableFromBackupOnClass('variable'); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertTrue($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('variable', $metadata->globalVariableName()); + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeExcludeGlobalVariableFromBackupOnMethod(): void + { + $metadata = Metadata::excludeGlobalVariableFromBackupOnMethod('variable'); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertTrue($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('variable', $metadata->globalVariableName()); + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeExcludeStaticPropertyFromBackupOnClass(): void + { + $metadata = Metadata::excludeStaticPropertyFromBackupOnClass('class', 'property'); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertTrue($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('class', $metadata->className()); + $this->assertSame('property', $metadata->propertyName()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeExcludeStaticPropertyFromBackupOnMethod(): void + { + $metadata = Metadata::excludeStaticPropertyFromBackupOnMethod('class', 'property'); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertTrue($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('class', $metadata->className()); + $this->assertSame('property', $metadata->propertyName()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeGroupOnClass(): void + { + $metadata = Metadata::groupOnClass('name'); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertTrue($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('name', $metadata->groupName()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeIgnoreDeprecationsOnClass(): void + { + $metadata = Metadata::ignoreDeprecationsOnClass(); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertTrue($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeIgnoreDeprecationsOnMethod(): void + { + $metadata = Metadata::ignoreDeprecationsOnMethod(); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertTrue($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeIgnorePhpunitDeprecationsOnClass(): void + { + $metadata = Metadata::ignorePhpunitDeprecationsOnClass(); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertTrue($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeIgnorePhpunitDeprecationsOnMethod(): void + { + $metadata = Metadata::ignorePhpunitDeprecationsOnMethod(); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertTrue($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeIgnorePHPUnitWarnings(): void + { + $metadata = Metadata::ignorePHPUnitWarnings(null); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertTrue($metadata->isIgnorePhpunitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeGroupOnMethod(): void + { + $metadata = Metadata::groupOnMethod('name'); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertTrue($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('name', $metadata->groupName()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeRunTestsInSeparateProcesses(): void + { + $metadata = Metadata::runTestsInSeparateProcesses(); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertTrue($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeRunInSeparateProcess(): void + { + $metadata = Metadata::runInSeparateProcess(); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertTrue($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeTest(): void + { + $metadata = Metadata::test(); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertTrue($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBePreCondition(): void + { + $priority = 0; + + $metadata = Metadata::preCondition($priority); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertTrue($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + $this->assertSame($priority, $metadata->priority()); + } + + public function testCanBePostCondition(): void + { + $priority = 0; + + $metadata = Metadata::postCondition($priority); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertTrue($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + $this->assertSame($priority, $metadata->priority()); + } + + public function testCanBePreserveGlobalStateOnClass(): void + { + $metadata = Metadata::preserveGlobalStateOnClass(false); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertTrue($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertFalse($metadata->enabled()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBePreserveGlobalStateOnMethod(): void + { + $metadata = Metadata::preserveGlobalStateOnMethod(false); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertTrue($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertFalse($metadata->enabled()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeRequiresMethodOnClass(): void + { + $metadata = Metadata::requiresMethodOnClass(self::class, __METHOD__); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertTrue($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame(self::class, $metadata->className()); + $this->assertSame(__METHOD__, $metadata->methodName()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeRequiresMethodOnMethod(): void + { + $metadata = Metadata::requiresMethodOnMethod(self::class, __METHOD__); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertTrue($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame(self::class, $metadata->className()); + $this->assertSame(__METHOD__, $metadata->methodName()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeRequiresFunctionOnClass(): void + { + $metadata = Metadata::requiresFunctionOnClass('f'); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertTrue($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('f', $metadata->functionName()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeRequiresFunctionOnMethod(): void + { + $metadata = Metadata::requiresFunctionOnMethod('f'); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertTrue($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('f', $metadata->functionName()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeRequiresOperatingSystemOnClass(): void + { + $metadata = Metadata::requiresOperatingSystemOnClass('Linux'); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertTrue($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('Linux', $metadata->operatingSystem()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeRequiresOperatingSystemOnMethod(): void + { + $metadata = Metadata::requiresOperatingSystemOnMethod('Linux'); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertTrue($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('Linux', $metadata->operatingSystem()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeRequiresOperatingSystemFamilyOnClass(): void + { + $metadata = Metadata::requiresOperatingSystemFamilyOnClass('Linux'); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertTrue($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('Linux', $metadata->operatingSystemFamily()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeRequiresOperatingSystemFamilyOnMethod(): void + { + $metadata = Metadata::requiresOperatingSystemFamilyOnMethod('Linux'); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertTrue($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('Linux', $metadata->operatingSystemFamily()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeRequiresPhpOnClass(): void + { + $metadata = Metadata::requiresPhpOnClass( + new ComparisonRequirement( + '8.0.0', + new VersionComparisonOperator('>='), + ), + ); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertTrue($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('>= 8.0.0', $metadata->versionRequirement()->asString()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeRequiresPhpOnMethod(): void + { + $metadata = Metadata::requiresPhpOnMethod( + new ComparisonRequirement( + '8.0.0', + new VersionComparisonOperator('>='), + ), + ); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertTrue($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('>= 8.0.0', $metadata->versionRequirement()->asString()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeRequiresPhpExtensionOnClass(): void + { + $metadata = Metadata::requiresPhpExtensionOnClass('test', null); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertTrue($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('test', $metadata->extension()); + $this->assertFalse($metadata->hasVersionRequirement()); + + $this->expectException(NoVersionRequirementException::class); + $metadata->versionRequirement(); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeRequiresPhpExtensionWithVersionOnClass(): void + { + $metadata = Metadata::requiresPhpExtensionOnClass( + 'test', + new ComparisonRequirement( + '1.0.0', + new VersionComparisonOperator('>='), + ), + ); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertTrue($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('test', $metadata->extension()); + $this->assertTrue($metadata->hasVersionRequirement()); + $this->assertSame('>= 1.0.0', $metadata->versionRequirement()->asString()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeRequiresPhpExtensionOnMethod(): void + { + $metadata = Metadata::requiresPhpExtensionOnMethod('test', null); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertTrue($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('test', $metadata->extension()); + $this->assertFalse($metadata->hasVersionRequirement()); + + $this->expectException(NoVersionRequirementException::class); + $metadata->versionRequirement(); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeRequiresPhpExtensionWithVersionOnMethod(): void + { + $metadata = Metadata::requiresPhpExtensionOnMethod( + 'test', + new ComparisonRequirement( + '1.0.0', + new VersionComparisonOperator('>='), + ), + ); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertTrue($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('test', $metadata->extension()); + $this->assertTrue($metadata->hasVersionRequirement()); + $this->assertSame('>= 1.0.0', $metadata->versionRequirement()->asString()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeRequiresPhpunitOnClass(): void + { + $metadata = Metadata::requiresPhpunitOnClass( + new ComparisonRequirement( + '10.0.0', + new VersionComparisonOperator('>='), + ), + ); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertTrue($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('>= 10.0.0', $metadata->versionRequirement()->asString()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeRequiresPhpunitExtensionOnClass(): void + { + $metadata = Metadata::requiresPhpunitExtensionOnClass(SomeExtension::class); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertTrue($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame(SomeExtension::class, $metadata->extensionClass()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeRequiresPhpunitOnMethod(): void + { + $metadata = Metadata::requiresPhpunitOnMethod( + new ComparisonRequirement( + '10.0.0', + new VersionComparisonOperator('>='), + ), + ); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertTrue($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('>= 10.0.0', $metadata->versionRequirement()->asString()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeRequiresPhpunitExtensionOnMethod(): void + { + $metadata = Metadata::requiresPhpunitExtensionOnMethod(SomeExtension::class); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertTrue($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame(SomeExtension::class, $metadata->extensionClass()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeRequiresEnvironmentVariableOnMethod(): void + { + $metadata = Metadata::requiresEnvironmentVariableOnMethod('foo', 'bar'); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertTrue($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('foo', $metadata->environmentVariableName()); + $this->assertSame('bar', $metadata->value()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeRequiresEnvironmentVariableOnClass(): void + { + $metadata = Metadata::requiresEnvironmentVariableOnClass('foo', 'bar'); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertTrue($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('foo', $metadata->environmentVariableName()); + $this->assertSame('bar', $metadata->value()); + + $this->assertFalse($metadata->isMethodLevel()); + $this->assertTrue($metadata->isClassLevel()); + } + + public function testCanBeWithEnvironmentVariableOnMethod(): void + { + $metadata = Metadata::withEnvironmentVariableOnMethod('foo', 'bar'); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertTrue($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('foo', $metadata->environmentVariableName()); + $this->assertSame('bar', $metadata->value()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeWithEnvironmentVariableOnClass(): void + { + $metadata = Metadata::withEnvironmentVariableOnClass('foo', 'bar'); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertTrue($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('foo', $metadata->environmentVariableName()); + $this->assertSame('bar', $metadata->value()); + + $this->assertFalse($metadata->isMethodLevel()); + $this->assertTrue($metadata->isClassLevel()); + } + + public function testCanBeRequiresSettingOnClass(): void + { + $metadata = Metadata::requiresSettingOnClass('foo', 'bar'); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertTrue($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('foo', $metadata->setting()); + $this->assertSame('bar', $metadata->value()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeRequiresSettingOnMethod(): void + { + $metadata = Metadata::requiresSettingOnMethod('foo', 'bar'); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertTrue($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('foo', $metadata->setting()); + $this->assertSame('bar', $metadata->value()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeTestDoxOnClass(): void + { + $metadata = Metadata::testDoxOnClass('text'); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertTrue($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('text', $metadata->text()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeTestDoxOnMethod(): void + { + $metadata = Metadata::testDoxOnMethod('text'); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertTrue($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('text', $metadata->text()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeTestDoxFormatterOnMethod(): void + { + $metadata = Metadata::testDoxFormatter(self::class, 'method'); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertTrue($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame(self::class, $metadata->className()); + $this->assertSame('method', $metadata->methodName()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeTestWith(): void + { + $metadata = Metadata::testWith(['a', 'b']); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertTrue($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame(['a', 'b'], $metadata->data()); + $this->assertFalse($metadata->hasName()); + $this->assertNull($metadata->name()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } + + public function testCanBeUsesNamespace(): void + { + $namespace = 'namespace'; + + $metadata = Metadata::usesNamespace($namespace); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertTrue($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame($namespace, $metadata->namespace()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeUsesClass(): void + { + $metadata = Metadata::usesClass(self::class); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertTrue($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame(self::class, $metadata->className()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeUsesClassesThatExtendClass(): void + { + $metadata = Metadata::usesClassesThatExtendClass(self::class); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertTrue($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame(self::class, $metadata->className()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeUsesClassesThatImplementInterface(): void + { + $metadata = Metadata::usesClassesThatImplementInterface(self::class); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertTrue($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame(self::class, $metadata->interfaceName()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeUsesFunction(): void + { + $metadata = Metadata::usesFunction('f'); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertTrue($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame('f', $metadata->functionName()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeUsesMethod(): void + { + $metadata = Metadata::usesMethod(self::class, 'testCanBeUsesMethod'); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertTrue($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame(self::class, $metadata->className()); + $this->assertSame('testCanBeUsesMethod', $metadata->methodName()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeUsesTrait(): void + { + $metadata = Metadata::usesTrait(ExampleTrait::class); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertTrue($metadata->isUsesTrait()); + $this->assertFalse($metadata->isWithoutErrorHandler()); + + $this->assertSame(ExampleTrait::class, $metadata->traitName()); + + $this->assertTrue($metadata->isClassLevel()); + $this->assertFalse($metadata->isMethodLevel()); + } + + public function testCanBeWithoutErrorHandler(): void + { + $metadata = Metadata::withoutErrorHandler(); + + $this->assertFalse($metadata->isAfter()); + $this->assertFalse($metadata->isAfterClass()); + $this->assertFalse($metadata->isBackupGlobals()); + $this->assertFalse($metadata->isBackupStaticProperties()); + $this->assertFalse($metadata->isBeforeClass()); + $this->assertFalse($metadata->isBefore()); + $this->assertFalse($metadata->isCoversNamespace()); + $this->assertFalse($metadata->isCoversClass()); + $this->assertFalse($metadata->isCoversClassesThatExtendClass()); + $this->assertFalse($metadata->isCoversClassesThatImplementInterface()); + $this->assertFalse($metadata->isCoversFunction()); + $this->assertFalse($metadata->isCoversMethod()); + $this->assertFalse($metadata->isCoversNothing()); + $this->assertFalse($metadata->isCoversTrait()); + $this->assertFalse($metadata->isDataProvider()); + $this->assertFalse($metadata->isDependsOnClass()); + $this->assertFalse($metadata->isDependsOnMethod()); + $this->assertFalse($metadata->isDisableReturnValueGenerationForTestDoubles()); + $this->assertFalse($metadata->isDoesNotPerformAssertions()); + $this->assertFalse($metadata->isExcludeGlobalVariableFromBackup()); + $this->assertFalse($metadata->isExcludeStaticPropertyFromBackup()); + $this->assertFalse($metadata->isGroup()); + $this->assertFalse($metadata->isIgnoreDeprecations()); + $this->assertFalse($metadata->isIgnorePhpunitDeprecations()); + $this->assertFalse($metadata->isIgnorePHPUnitWarnings()); + $this->assertFalse($metadata->isRunInSeparateProcess()); + $this->assertFalse($metadata->isRunTestsInSeparateProcesses()); + $this->assertFalse($metadata->isTest()); + $this->assertFalse($metadata->isPreCondition()); + $this->assertFalse($metadata->isPostCondition()); + $this->assertFalse($metadata->isPreserveGlobalState()); + $this->assertFalse($metadata->isRequiresMethod()); + $this->assertFalse($metadata->isRequiresFunction()); + $this->assertFalse($metadata->isRequiresOperatingSystem()); + $this->assertFalse($metadata->isRequiresOperatingSystemFamily()); + $this->assertFalse($metadata->isRequiresPhp()); + $this->assertFalse($metadata->isRequiresPhpExtension()); + $this->assertFalse($metadata->isRequiresPhpunit()); + $this->assertFalse($metadata->isRequiresPhpunitExtension()); + $this->assertFalse($metadata->isRequiresEnvironmentVariable()); + $this->assertFalse($metadata->isWithEnvironmentVariable()); + $this->assertFalse($metadata->isRequiresSetting()); + $this->assertFalse($metadata->isTestDox()); + $this->assertFalse($metadata->isTestDoxFormatter()); + $this->assertFalse($metadata->isTestWith()); + $this->assertFalse($metadata->isUsesNamespace()); + $this->assertFalse($metadata->isUsesClass()); + $this->assertFalse($metadata->isUsesClassesThatExtendClass()); + $this->assertFalse($metadata->isUsesClassesThatImplementInterface()); + $this->assertFalse($metadata->isUsesFunction()); + $this->assertFalse($metadata->isUsesMethod()); + $this->assertFalse($metadata->isUsesTrait()); + $this->assertTrue($metadata->isWithoutErrorHandler()); + + $this->assertTrue($metadata->isMethodLevel()); + $this->assertFalse($metadata->isClassLevel()); + } +} diff --git a/tests/unit/Metadata/Parser/AttributeParserTest.php b/tests/unit/Metadata/Parser/AttributeParserTest.php new file mode 100644 index 00000000000..1f69fba5e60 --- /dev/null +++ b/tests/unit/Metadata/Parser/AttributeParserTest.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Parser; + +use PHPUnit\Framework\Attributes\After; +use PHPUnit\Framework\Attributes\AfterClass; +use PHPUnit\Framework\Attributes\BackupGlobals; +use PHPUnit\Framework\Attributes\BackupStaticProperties; +use PHPUnit\Framework\Attributes\Before; +use PHPUnit\Framework\Attributes\BeforeClass; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversClassesThatExtendClass; +use PHPUnit\Framework\Attributes\CoversClassesThatImplementInterface; +use PHPUnit\Framework\Attributes\CoversFunction; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\CoversNamespace; +use PHPUnit\Framework\Attributes\CoversNothing; +use PHPUnit\Framework\Attributes\CoversTrait; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Depends; +use PHPUnit\Framework\Attributes\DependsExternal; +use PHPUnit\Framework\Attributes\DependsExternalUsingDeepClone; +use PHPUnit\Framework\Attributes\DependsExternalUsingShallowClone; +use PHPUnit\Framework\Attributes\DependsOnClass; +use PHPUnit\Framework\Attributes\DependsOnClassUsingDeepClone; +use PHPUnit\Framework\Attributes\DependsOnClassUsingShallowClone; +use PHPUnit\Framework\Attributes\DependsUsingDeepClone; +use PHPUnit\Framework\Attributes\DependsUsingShallowClone; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; +use PHPUnit\Framework\Attributes\ExcludeGlobalVariableFromBackup; +use PHPUnit\Framework\Attributes\ExcludeStaticPropertyFromBackup; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\IgnorePhpunitWarnings; +use PHPUnit\Framework\Attributes\Large; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\Attributes\PostCondition; +use PHPUnit\Framework\Attributes\PreCondition; +use PHPUnit\Framework\Attributes\PreserveGlobalState; +use PHPUnit\Framework\Attributes\RequiresEnvironmentVariable; +use PHPUnit\Framework\Attributes\RequiresFunction; +use PHPUnit\Framework\Attributes\RequiresMethod; +use PHPUnit\Framework\Attributes\RequiresOperatingSystem; +use PHPUnit\Framework\Attributes\RequiresOperatingSystemFamily; +use PHPUnit\Framework\Attributes\RequiresPhp; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; +use PHPUnit\Framework\Attributes\RequiresPhpunit; +use PHPUnit\Framework\Attributes\RequiresPhpunitExtension; +use PHPUnit\Framework\Attributes\RequiresSetting; +use PHPUnit\Framework\Attributes\RunInSeparateProcess; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\Attributes\TestDoxFormatter; +use PHPUnit\Framework\Attributes\TestDoxFormatterExternal; +use PHPUnit\Framework\Attributes\TestWith; +use PHPUnit\Framework\Attributes\TestWithJson; +use PHPUnit\Framework\Attributes\Ticket; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\Attributes\UsesClassesThatExtendClass; +use PHPUnit\Framework\Attributes\UsesClassesThatImplementInterface; +use PHPUnit\Framework\Attributes\UsesFunction; +use PHPUnit\Framework\Attributes\UsesMethod; +use PHPUnit\Framework\Attributes\UsesNamespace; +use PHPUnit\Framework\Attributes\UsesTrait; +use PHPUnit\Framework\Attributes\WithEnvironmentVariable; +use PHPUnit\Framework\Attributes\WithoutErrorHandler; +use PHPUnit\Metadata\DisableReturnValueGenerationForTestDoubles; +use PHPUnit\Metadata\InvalidAttributeException; + +#[CoversClass(AttributeParser::class)] +#[CoversClass(AfterClass::class)] +#[CoversClass(After::class)] +#[CoversClass(BackupGlobals::class)] +#[CoversClass(BackupStaticProperties::class)] +#[CoversClass(BeforeClass::class)] +#[CoversClass(Before::class)] +#[CoversClass(CoversClass::class)] +#[CoversClass(CoversClassesThatExtendClass::class)] +#[CoversClass(CoversClassesThatImplementInterface::class)] +#[CoversClass(CoversFunction::class)] +#[CoversClass(CoversMethod::class)] +#[CoversClass(CoversNamespace::class)] +#[CoversClass(CoversNothing::class)] +#[CoversClass(CoversTrait::class)] +#[CoversClass(DataProviderExternal::class)] +#[CoversClass(DataProvider::class)] +#[CoversClass(DependsExternal::class)] +#[CoversClass(DependsExternalUsingDeepClone::class)] +#[CoversClass(DependsExternalUsingShallowClone::class)] +#[CoversClass(DependsOnClass::class)] +#[CoversClass(DependsOnClassUsingDeepClone::class)] +#[CoversClass(DependsOnClassUsingShallowClone::class)] +#[CoversClass(Depends::class)] +#[CoversClass(DependsUsingDeepClone::class)] +#[CoversClass(DependsUsingShallowClone::class)] +#[CoversClass(DisableReturnValueGenerationForTestDoubles::class)] +#[CoversClass(DoesNotPerformAssertions::class)] +#[CoversClass(ExcludeGlobalVariableFromBackup::class)] +#[CoversClass(ExcludeStaticPropertyFromBackup::class)] +#[CoversClass(Group::class)] +#[CoversClass(InvalidAttributeException::class)] +#[CoversClass(Large::class)] +#[CoversClass(Medium::class)] +#[CoversClass(PostCondition::class)] +#[CoversClass(PreCondition::class)] +#[CoversClass(PreserveGlobalState::class)] +#[CoversClass(RequiresFunction::class)] +#[CoversClass(RequiresMethod::class)] +#[CoversClass(RequiresOperatingSystemFamily::class)] +#[CoversClass(RequiresOperatingSystem::class)] +#[CoversClass(RequiresPhpExtension::class)] +#[CoversClass(RequiresPhp::class)] +#[CoversClass(RequiresPhpunit::class)] +#[CoversClass(RequiresPhpunitExtension::class)] +#[CoversClass(RequiresEnvironmentVariable::class)] +#[CoversClass(RequiresSetting::class)] +#[CoversClass(RunInSeparateProcess::class)] +#[CoversClass(RunTestsInSeparateProcesses::class)] +#[CoversClass(Small::class)] +#[CoversClass(TestDox::class)] +#[CoversClass(TestDoxFormatter::class)] +#[CoversClass(TestDoxFormatterExternal::class)] +#[CoversClass(Test::class)] +#[CoversClass(TestWithJson::class)] +#[CoversClass(TestWith::class)] +#[CoversClass(Ticket::class)] +#[CoversClass(UsesClass::class)] +#[CoversClass(UsesClassesThatExtendClass::class)] +#[CoversClass(UsesClassesThatImplementInterface::class)] +#[CoversClass(UsesFunction::class)] +#[CoversClass(UsesMethod::class)] +#[CoversClass(UsesNamespace::class)] +#[CoversClass(UsesTrait::class)] +#[CoversClass(WithEnvironmentVariable::class)] +#[CoversClass(WithoutErrorHandler::class)] +#[CoversClass(IgnorePhpunitWarnings::class)] +#[Small] +#[Group('metadata')] +#[Group('metadata/attributes')] +final class AttributeParserTest extends AttributeParserTestCase +{ + protected function parser(): Parser + { + return new AttributeParser; + } +} diff --git a/tests/unit/Metadata/Parser/AttributeParserTestCase.php b/tests/unit/Metadata/Parser/AttributeParserTestCase.php new file mode 100644 index 00000000000..8a042f12209 --- /dev/null +++ b/tests/unit/Metadata/Parser/AttributeParserTestCase.php @@ -0,0 +1,1217 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Parser; + +use function assert; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; +use PHPUnit\Metadata\DependsOnClass; +use PHPUnit\Metadata\DependsOnMethod; +use PHPUnit\Metadata\InvalidAttributeException; +use PHPUnit\Metadata\RequiresEnvironmentVariable; +use PHPUnit\Metadata\RequiresPhp; +use PHPUnit\Metadata\RequiresPhpExtension; +use PHPUnit\Metadata\RequiresPhpunit; +use PHPUnit\Metadata\RequiresPhpunitExtension; +use PHPUnit\Metadata\RequiresSetting; +use PHPUnit\Metadata\Version\ComparisonRequirement; +use PHPUnit\Metadata\Version\ConstraintRequirement; +use PHPUnit\Metadata\WithEnvironmentVariable; +use PHPUnit\TestFixture\Metadata\Attribute\AnotherTest; +use PHPUnit\TestFixture\Metadata\Attribute\BackupGlobalsTest; +use PHPUnit\TestFixture\Metadata\Attribute\BackupStaticPropertiesTest; +use PHPUnit\TestFixture\Metadata\Attribute\CoversNothingTest; +use PHPUnit\TestFixture\Metadata\Attribute\CoversTest; +use PHPUnit\TestFixture\Metadata\Attribute\DependencyTest; +use PHPUnit\TestFixture\Metadata\Attribute\DisableReturnValueGenerationForTestDoublesTest; +use PHPUnit\TestFixture\Metadata\Attribute\DoesNotPerformAssertionsTest; +use PHPUnit\TestFixture\Metadata\Attribute\DuplicateSmallAttributeTest; +use PHPUnit\TestFixture\Metadata\Attribute\DuplicateTestAttributeTest; +use PHPUnit\TestFixture\Metadata\Attribute\Example; +use PHPUnit\TestFixture\Metadata\Attribute\ExampleTrait; +use PHPUnit\TestFixture\Metadata\Attribute\GroupTest; +use PHPUnit\TestFixture\Metadata\Attribute\IgnoreDeprecationsClassTest; +use PHPUnit\TestFixture\Metadata\Attribute\IgnoreDeprecationsMethodTest; +use PHPUnit\TestFixture\Metadata\Attribute\IgnorePhpunitDeprecationsClassTest; +use PHPUnit\TestFixture\Metadata\Attribute\IgnorePhpunitDeprecationsMethodTest; +use PHPUnit\TestFixture\Metadata\Attribute\IgnorePhpunitWarningsTest; +use PHPUnit\TestFixture\Metadata\Attribute\LargeTest; +use PHPUnit\TestFixture\Metadata\Attribute\MediumTest; +use PHPUnit\TestFixture\Metadata\Attribute\NonPhpunitAttributeTest; +use PHPUnit\TestFixture\Metadata\Attribute\PhpunitAttributeThatDoesNotExistTest; +use PHPUnit\TestFixture\Metadata\Attribute\PreserveGlobalStateTest; +use PHPUnit\TestFixture\Metadata\Attribute\ProcessIsolationTest; +use PHPUnit\TestFixture\Metadata\Attribute\RequiresEnvironmentVariableTest; +use PHPUnit\TestFixture\Metadata\Attribute\RequiresFunctionTest; +use PHPUnit\TestFixture\Metadata\Attribute\RequiresMethodTest; +use PHPUnit\TestFixture\Metadata\Attribute\RequiresOperatingSystemFamilyTest; +use PHPUnit\TestFixture\Metadata\Attribute\RequiresOperatingSystemTest; +use PHPUnit\TestFixture\Metadata\Attribute\RequiresPhpExtensionTest; +use PHPUnit\TestFixture\Metadata\Attribute\RequiresPhpTest; +use PHPUnit\TestFixture\Metadata\Attribute\RequiresPhpunitExtensionTest; +use PHPUnit\TestFixture\Metadata\Attribute\RequiresPhpunitTest; +use PHPUnit\TestFixture\Metadata\Attribute\RequiresSettingTest; +use PHPUnit\TestFixture\Metadata\Attribute\SmallTest; +use PHPUnit\TestFixture\Metadata\Attribute\TestDoxTest; +use PHPUnit\TestFixture\Metadata\Attribute\TestWithTest; +use PHPUnit\TestFixture\Metadata\Attribute\UsesTest; +use PHPUnit\TestFixture\Metadata\Attribute\WithEnvironmentVariableTest; +use PHPUnit\TestFixture\Metadata\Attribute\WithoutErrorHandlerTest; + +abstract class AttributeParserTestCase extends TestCase +{ + #[TestDox('Parses #[BackupGlobals] attribute on class')] + public function test_parses_BackupGlobals_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(BackupGlobalsTest::class)->isBackupGlobals(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isBackupGlobals()); + $this->assertTrue($metadata->asArray()[0]->enabled()); + } + + #[TestDox('Parses #[BackupStaticProperties] attribute on class')] + public function test_parses_BackupStaticProperties_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(BackupStaticPropertiesTest::class)->isBackupStaticProperties(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isBackupStaticProperties()); + $this->assertTrue($metadata->asArray()[0]->enabled()); + } + + #[TestDox('Parses #[CoversNamespace] attribute on class')] + public function test_parses_CoversNamespace_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(CoversTest::class)->isCoversNamespace(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isCoversNamespace()); + $this->assertSame('PHPUnit\TestFixture\Metadata\Attribute', $metadata->asArray()[0]->namespace()); + } + + #[TestDox('Parses #[CoversClass] attribute on class')] + public function test_parses_CoversClass_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(CoversTest::class)->isCoversClass(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isCoversClass()); + $this->assertSame(Example::class, $metadata->asArray()[0]->className()); + } + + #[TestDox('Parses #[CoversClassesThatExtendClass] attribute on class')] + public function test_parses_CoversClassesThatExtendClass_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(CoversTest::class)->isCoversClassesThatExtendClass(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isCoversClassesThatExtendClass()); + $this->assertSame(Example::class, $metadata->asArray()[0]->className()); + } + + #[TestDox('Parses #[CoversClassesThatImplementInterface] attribute on class')] + public function test_parses_CoversClassesThatImplementInterface_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(CoversTest::class)->isCoversClassesThatImplementInterface(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isCoversClassesThatImplementInterface()); + $this->assertSame(Example::class, $metadata->asArray()[0]->interfaceName()); + } + + #[TestDox('Parses #[CoversTrait] attribute on class')] + public function test_parses_CoversTrait_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(CoversTest::class)->isCoversTrait(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isCoversTrait()); + $this->assertSame(ExampleTrait::class, $metadata->asArray()[0]->traitName()); + } + + #[TestDox('Parses #[CoversFunction] attribute on class')] + public function test_parses_CoversFunction_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(CoversTest::class)->isCoversFunction(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isCoversFunction()); + $this->assertSame('f', $metadata->asArray()[0]->functionName()); + } + + #[TestDox('Parses #[CoversMethod] attribute on class')] + public function test_parses_CoversMethod_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(CoversTest::class)->isCoversMethod(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isCoversMethod()); + $this->assertSame(Example::class, $metadata->asArray()[0]->className()); + $this->assertSame('method', $metadata->asArray()[0]->methodName()); + } + + #[TestDox('Parses #[CoversNothing] attribute on class')] + public function test_parses_CoversNothing_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(CoversNothingTest::class)->isCoversNothing(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isCoversNothing()); + } + + #[TestDox('Parses #[DisableReturnValueGenerationForTestDoubles] attribute on class')] + public function test_parses_DisableReturnValueGenerationForTestDoubles_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(DisableReturnValueGenerationForTestDoublesTest::class)->isDisableReturnValueGenerationForTestDoubles(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isDisableReturnValueGenerationForTestDoubles()); + } + + #[TestDox('Parses #[DoesNotPerformAssertions] attribute on class')] + public function test_parses_DoesNotPerformAssertions_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(DoesNotPerformAssertionsTest::class)->isDoesNotPerformAssertions(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isDoesNotPerformAssertions()); + } + + #[TestDox('Parses #[ExcludeGlobalVariableFromBackup] attribute on class')] + public function test_parses_ExcludeGlobalVariableFromBackup_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(BackupGlobalsTest::class)->isExcludeGlobalVariableFromBackup(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isExcludeGlobalVariableFromBackup()); + $this->assertSame('foo', $metadata->asArray()[0]->globalVariableName()); + } + + #[TestDox('Parses #[ExcludeStaticPropertyFromBackup] attribute on class')] + public function test_parses_ExcludeStaticPropertyFromBackup_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(BackupStaticPropertiesTest::class)->isExcludeStaticPropertyFromBackup(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isExcludeStaticPropertyFromBackup()); + $this->assertSame('className', $metadata->asArray()[0]->className()); + $this->assertSame('propertyName', $metadata->asArray()[0]->propertyName()); + } + + #[TestDox('Parses #[Group] attribute on class')] + public function test_parses_Group_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(GroupTest::class)->isGroup(); + + $this->assertCount(2, $metadata); + $this->assertTrue($metadata->asArray()[0]->isGroup()); + $this->assertSame('group', $metadata->asArray()[0]->groupName()); + } + + #[TestDox('Parses #[Large] attribute on class')] + public function test_parses_Large_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(LargeTest::class)->isGroup(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isGroup()); + $this->assertSame('large', $metadata->asArray()[0]->groupName()); + } + + #[TestDox('Parses #[Medium] attribute on class')] + public function test_parses_Medium_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(MediumTest::class)->isGroup(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isGroup()); + $this->assertSame('medium', $metadata->asArray()[0]->groupName()); + } + + #[TestDox('Parses #[IgnoreDeprecations] attribute on class')] + public function test_parses_IgnoreDeprecations_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(IgnoreDeprecationsClassTest::class)->isIgnoreDeprecations(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isIgnoreDeprecations()); + } + + #[TestDox('Parses #[IgnorePhpunitDeprecations] attribute on class')] + public function test_parses_IgnorePhpunitDeprecations_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(IgnorePhpunitDeprecationsClassTest::class)->isIgnorePhpunitDeprecations(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isIgnorePhpunitDeprecations()); + } + + #[TestDox('Parses #[PreserveGlobalState] attribute on class')] + public function test_parses_PreserveGlobalState_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(PreserveGlobalStateTest::class)->isPreserveGlobalState(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isPreserveGlobalState()); + $this->assertTrue($metadata->asArray()[0]->enabled()); + } + + #[TestDox('Parses #[RequiresMethod] attribute on class')] + public function test_parses_RequiresMethod_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(RequiresMethodTest::class)->isRequiresMethod(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isRequiresMethod()); + $this->assertSame('ClassName', $metadata->asArray()[0]->className()); + $this->assertSame('methodName', $metadata->asArray()[0]->methodName()); + } + + #[TestDox('Parses #[RequiresFunction] attribute on class')] + public function test_parses_RequiresFunction_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(RequiresFunctionTest::class)->isRequiresFunction(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isRequiresFunction()); + $this->assertSame('f', $metadata->asArray()[0]->functionName()); + } + + #[TestDox('Parses #[RequiresOperatingSystem] attribute on class')] + public function test_parses_RequiresOperatingSystem_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(RequiresOperatingSystemTest::class)->isRequiresOperatingSystem(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isRequiresOperatingSystem()); + $this->assertSame('Linux', $metadata->asArray()[0]->operatingSystem()); + } + + #[TestDox('Parses #[RequiresOperatingSystemFamily] attribute on class')] + public function test_parses_RequiresOperatingSystemFamily_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(RequiresOperatingSystemFamilyTest::class)->isRequiresOperatingSystemFamily(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isRequiresOperatingSystemFamily()); + $this->assertSame('Linux', $metadata->asArray()[0]->operatingSystemFamily()); + } + + #[TestDox('Parses #[RequiresPhp] attribute on class')] + public function test_parses_RequiresPhp_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(RequiresPhpTest::class)->isRequiresPhp(); + + $this->assertCount(1, $metadata); + + $requirement = $metadata->asArray()[0]; + + $this->assertTrue($requirement->isRequiresPhp()); + + assert($requirement instanceof RequiresPhp); + + $versionRequirement = $requirement->versionRequirement(); + + assert($versionRequirement instanceof ConstraintRequirement); + + $this->assertSame('8.0.0', $versionRequirement->asString()); + } + + #[TestDox('Parses #[RequiresPhpExtension] attribute on class')] + public function test_parses_RequiresPhpExtension_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(RequiresPhpExtensionTest::class)->isRequiresPhpExtension(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isRequiresPhpExtension()); + $this->assertSame('foo', $metadata->asArray()[0]->extension()); + $this->assertTrue($metadata->asArray()[0]->hasVersionRequirement()); + $this->assertSame('>= 1.0', $metadata->asArray()[0]->versionRequirement()->asString()); + } + + #[TestDox('Parses #[RequiresPhpunit] attribute on class')] + public function test_parses_RequiresPhpunit_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(RequiresPhpunitTest::class)->isRequiresPhpunit(); + + $this->assertCount(1, $metadata); + + $requirement = $metadata->asArray()[0]; + + $this->assertTrue($requirement->isRequiresPhpunit()); + + assert($requirement instanceof RequiresPhpunit); + + $versionRequirement = $requirement->versionRequirement(); + + assert($versionRequirement instanceof ComparisonRequirement); + + $this->assertSame('>= 10.0.0', $versionRequirement->asString()); + } + + #[TestDox('Parses #[RequiresPhpunitExtension] attribute on class')] + public function test_parses_RequiresPhpunitExtension_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(RequiresPhpunitExtensionTest::class)->isRequiresPhpunitExtension(); + + $this->assertCount(1, $metadata); + + $requirement = $metadata->asArray()[0]; + + $this->assertTrue($requirement->isRequiresPhpunitExtension()); + + assert($requirement instanceof RequiresPhpunitExtension); + + $this->assertSame('PHPUnit\TestFixture\Metadata\Attribute\SomeExtension', $requirement->extensionClass()); + } + + #[TestDox('Parses #[RequiresEnvironmentVariable] attribute on class')] + public function test_parses_RequiresEnvironmentVariable_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(RequiresEnvironmentVariableTest::class)->isRequiresEnvironmentVariable(); + + $this->assertCount(1, $metadata); + + $requirement = $metadata->asArray()[0]; + + $this->assertTrue($requirement->isRequiresEnvironmentVariable()); + + assert($requirement instanceof RequiresEnvironmentVariable); + + $this->assertSame('foo', $requirement->environmentVariableName()); + $this->assertSame('bar', $requirement->value()); + } + + #[TestDox('Parses #[WithEnvironmentVariable] attribute on class')] + public function test_parses_WithEnvironmentVariable_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(WithEnvironmentVariableTest::class)->isWithEnvironmentVariable(); + + $this->assertCount(1, $metadata); + + $withEnvironmentVariable = $metadata->asArray()[0]; + $this->assertTrue($withEnvironmentVariable->isWithEnvironmentVariable()); + assert($withEnvironmentVariable instanceof WithEnvironmentVariable); + $this->assertSame('foo', $withEnvironmentVariable->environmentVariableName()); + $this->assertSame('bar', $withEnvironmentVariable->value()); + } + + #[TestDox('Parses #[RequiresSetting] attribute on class')] + public function test_parses_RequiresSetting_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(RequiresSettingTest::class)->isRequiresSetting(); + + $this->assertCount(1, $metadata); + + $requirement = $metadata->asArray()[0]; + + $this->assertTrue($requirement->isRequiresSetting()); + + assert($requirement instanceof RequiresSetting); + + $this->assertSame('setting', $requirement->setting()); + $this->assertSame('value', $requirement->value()); + } + + #[TestDox('Parses #[RunTestsInSeparateProcesses] attribute on class')] + public function test_parses_RunTestsInSeparateProcesses_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(ProcessIsolationTest::class)->isRunTestsInSeparateProcesses(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isRunTestsInSeparateProcesses()); + } + + #[TestDox('Parses #[Small] attribute on class')] + public function test_parses_Small_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(SmallTest::class)->isGroup(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isGroup()); + $this->assertSame('small', $metadata->asArray()[0]->groupName()); + } + + #[TestDox('Parses #[TestDox] attribute on class')] + public function test_parses_TestDox_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(TestDoxTest::class)->isTestDox(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isTestDox()); + $this->assertSame('text', $metadata->asArray()[0]->text()); + } + + #[TestDox('Parses #[Ticket] attribute on class')] + public function test_parses_Ticket_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(GroupTest::class)->isGroup(); + + $this->assertCount(2, $metadata); + $this->assertTrue($metadata->asArray()[1]->isGroup()); + $this->assertSame('ticket', $metadata->asArray()[1]->groupName()); + } + + #[TestDox('Parses #[UsesNamespace] attribute on class')] + public function test_parses_UsesNamespace_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(UsesTest::class)->isUsesNamespace(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isUsesNamespace()); + $this->assertSame('PHPUnit\TestFixture\Metadata\Attribute', $metadata->asArray()[0]->namespace()); + } + + #[TestDox('Parses #[UsesClass] attribute on class')] + public function test_parses_UsesClass_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(UsesTest::class)->isUsesClass(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isUsesClass()); + $this->assertSame(Example::class, $metadata->asArray()[0]->className()); + } + + #[TestDox('Parses #[UsesClassesThatExtendClass] attribute on class')] + public function test_parses_UsesClassesThatExtendClass_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(UsesTest::class)->isUsesClassesThatExtendClass(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isUsesClassesThatExtendClass()); + $this->assertSame(Example::class, $metadata->asArray()[0]->className()); + } + + #[TestDox('Parses #[UsesClassesThatImplementInterface] attribute on class')] + public function test_parses_UsesClassesThatImplementInterface_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(UsesTest::class)->isUsesClassesThatImplementInterface(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isUsesClassesThatImplementInterface()); + $this->assertSame(Example::class, $metadata->asArray()[0]->interfaceName()); + } + + #[TestDox('Parses #[UsesTrait] attribute on class')] + public function test_parses_UsesTrait_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(UsesTest::class)->isUsesTrait(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isUsesTrait()); + $this->assertSame(ExampleTrait::class, $metadata->asArray()[0]->traitName()); + } + + #[TestDox('Parses #[UsesFunction] attribute on class')] + public function test_parses_UsesFunction_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(UsesTest::class)->isUsesFunction(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isUsesFunction()); + $this->assertSame('f', $metadata->asArray()[0]->functionName()); + } + + #[TestDox('Parses #[UsesMethod] attribute on class')] + public function test_parses_UsesMethod_attribute_on_class(): void + { + $metadata = $this->parser()->forClass(UsesTest::class)->isUsesMethod(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isUsesMethod()); + $this->assertSame(Example::class, $metadata->asArray()[0]->className()); + $this->assertSame('method', $metadata->asArray()[0]->methodName()); + } + + #[TestDox('Parses #[After] attribute on class')] + public function test_parses_After_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(SmallTest::class, 'afterTest')->isAfter(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isAfter()); + } + + #[TestDox('Parses #[AfterClass] attribute on class')] + public function test_parses_AfterClass_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(SmallTest::class, 'afterTests')->isAfterClass(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isAfterClass()); + } + + #[TestDox('Parses #[BackupGlobals] attribute on method')] + public function test_parses_BackupGlobals_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(BackupGlobalsTest::class, 'testOne')->isBackupGlobals(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isBackupGlobals()); + $this->assertFalse($metadata->asArray()[0]->enabled()); + } + + #[TestDox('Parses #[BackupStaticProperties] attribute on method')] + public function test_parses_BackupStaticProperties_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(BackupStaticPropertiesTest::class, 'testOne')->isBackupStaticProperties(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isBackupStaticProperties()); + $this->assertFalse($metadata->asArray()[0]->enabled()); + } + + #[TestDox('Parses #[Before] attribute on method')] + public function test_parses_Before_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(SmallTest::class, 'beforeTest')->isBefore(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isBefore()); + } + + #[TestDox('Parses #[BeforeClass] attribute on method')] + public function test_parses_BeforeClass_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(SmallTest::class, 'beforeTests')->isBeforeClass(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isBeforeClass()); + } + + #[TestDox('Parses #[CoversNothing] attribute on method')] + public function test_parses_CoversNothing_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(CoversNothingTest::class, 'testOne')->isCoversNothing(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isCoversNothing()); + } + + #[TestDox('Parses #[DataProvider] attribute on method')] + public function test_parses_DataProvider_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(SmallTest::class, 'testWithDataProvider')->isDataProvider(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isDataProvider()); + $this->assertSame(SmallTest::class, $metadata->asArray()[0]->className()); + $this->assertSame('provider', $metadata->asArray()[0]->methodName()); + } + + #[TestDox('Parses #[DataProviderExternal] attribute on method')] + public function test_parses_DataProviderExternal_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(SmallTest::class, 'testWithDataProviderExternal')->isDataProvider(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isDataProvider()); + $this->assertSame(SmallTest::class, $metadata->asArray()[0]->className()); + $this->assertSame('provider', $metadata->asArray()[0]->methodName()); + } + + #[TestDox('Parses #[Depends] attribute on method')] + public function test_parses_Depends_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(DependencyTest::class, 'testOne')->isDependsOnMethod(); + + $this->assertCount(1, $metadata); + + $depends = $metadata->asArray()[0]; + + assert($depends instanceof DependsOnMethod); + + $this->assertSame(DependencyTest::class, $depends->className()); + $this->assertSame('testOne', $depends->methodName()); + $this->assertFalse($depends->deepClone()); + $this->assertFalse($depends->shallowClone()); + } + + #[TestDox('Parses #[DependsUsingDeepClone] attribute on method')] + public function test_parses_DependsUsingDeepClone_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(DependencyTest::class, 'testTwo')->isDependsOnMethod(); + + $this->assertCount(1, $metadata); + + $depends = $metadata->asArray()[0]; + + assert($depends instanceof DependsOnMethod); + + $this->assertSame(DependencyTest::class, $depends->className()); + $this->assertSame('testOne', $depends->methodName()); + $this->assertTrue($depends->deepClone()); + $this->assertFalse($depends->shallowClone()); + } + + #[TestDox('Parses #[DependsUsingShallowClone] attribute on method')] + public function test_parses_DependsUsingShallowClone_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(DependencyTest::class, 'testThree')->isDependsOnMethod(); + + $this->assertCount(1, $metadata); + + $depends = $metadata->asArray()[0]; + + assert($depends instanceof DependsOnMethod); + + $this->assertSame(DependencyTest::class, $depends->className()); + $this->assertSame('testOne', $depends->methodName()); + $this->assertFalse($depends->deepClone()); + $this->assertTrue($depends->shallowClone()); + } + + #[TestDox('Parses #[DependsExternal] attribute on method')] + public function test_parses_DependsExternal_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(DependencyTest::class, 'testFour')->isDependsOnMethod(); + + $this->assertCount(1, $metadata); + + $depends = $metadata->asArray()[0]; + + assert($depends instanceof DependsOnMethod); + + $this->assertSame(AnotherTest::class, $depends->className()); + $this->assertSame('testOne', $depends->methodName()); + $this->assertFalse($depends->deepClone()); + $this->assertFalse($depends->shallowClone()); + } + + #[TestDox('Parses #[DependsExternalUsingDeepClone] attribute on method')] + public function test_parses_DependsExternalUsingDeepClone_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(DependencyTest::class, 'testFive')->isDependsOnMethod(); + + $this->assertCount(1, $metadata); + + $depends = $metadata->asArray()[0]; + + assert($depends instanceof DependsOnMethod); + + $this->assertSame(AnotherTest::class, $depends->className()); + $this->assertSame('testOne', $depends->methodName()); + $this->assertTrue($depends->deepClone()); + $this->assertFalse($depends->shallowClone()); + } + + #[TestDox('Parses #[DependsExternalUsingShallowClone] attribute on method')] + public function test_parses_DependsExternalUsingShallowClone_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(DependencyTest::class, 'testSix')->isDependsOnMethod(); + + $this->assertCount(1, $metadata); + + $depends = $metadata->asArray()[0]; + + assert($depends instanceof DependsOnMethod); + + $this->assertSame(AnotherTest::class, $depends->className()); + $this->assertSame('testOne', $depends->methodName()); + $this->assertFalse($depends->deepClone()); + $this->assertTrue($depends->shallowClone()); + } + + #[TestDox('Parses #[DependsOnClass] attribute on method')] + public function test_parses_DependsOnClass_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(DependencyTest::class, 'testSeven')->isDependsOnClass(); + + $this->assertCount(1, $metadata); + + $depends = $metadata->asArray()[0]; + + assert($depends instanceof DependsOnClass); + + $this->assertSame(AnotherTest::class, $depends->className()); + $this->assertFalse($depends->deepClone()); + $this->assertFalse($depends->shallowClone()); + } + + #[TestDox('Parses #[DependsOnClassUsingDeepClone] attribute on method')] + public function test_parses_DependsOnClassUsingDeepClone_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(DependencyTest::class, 'testEight')->isDependsOnClass(); + + $this->assertCount(1, $metadata); + + $depends = $metadata->asArray()[0]; + + assert($depends instanceof DependsOnClass); + + $this->assertSame(AnotherTest::class, $depends->className()); + $this->assertTrue($depends->deepClone()); + $this->assertFalse($depends->shallowClone()); + } + + #[TestDox('Parses #[DependsOnClassUsingShallowClone] attribute on method')] + public function test_parses_DependsOnClassUsingShallowClone_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(DependencyTest::class, 'testNine')->isDependsOnClass(); + + $this->assertCount(1, $metadata); + + $depends = $metadata->asArray()[0]; + + assert($depends instanceof DependsOnClass); + + $this->assertSame(AnotherTest::class, $depends->className()); + $this->assertFalse($depends->deepClone()); + $this->assertTrue($depends->shallowClone()); + } + + #[TestDox('Parses #[DoesNotPerformAssertions] attribute on method')] + public function test_parses_DoesNotPerformAssertions_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(DoesNotPerformAssertionsTest::class, 'testOne')->isDoesNotPerformAssertions(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isDoesNotPerformAssertions()); + } + + #[TestDox('Parses #[ExcludeGlobalVariableFromBackup] attribute on method')] + public function test_parses_ExcludeGlobalVariableFromBackup_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(BackupGlobalsTest::class, 'testOne')->isExcludeGlobalVariableFromBackup(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isExcludeGlobalVariableFromBackup()); + $this->assertSame('bar', $metadata->asArray()[0]->globalVariableName()); + } + + #[TestDox('Parses #[ExcludeStaticPropertyFromBackup] attribute on method')] + public function test_parses_ExcludeStaticPropertyFromBackup_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(BackupStaticPropertiesTest::class, 'testOne')->isExcludeStaticPropertyFromBackup(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isExcludeStaticPropertyFromBackup()); + $this->assertSame('anotherClassName', $metadata->asArray()[0]->className()); + $this->assertSame('propertyName', $metadata->asArray()[0]->propertyName()); + } + + #[TestDox('Parses #[Group] attribute on method')] + public function test_parses_Group_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(GroupTest::class, 'testOne')->isGroup(); + + $this->assertCount(2, $metadata); + $this->assertTrue($metadata->asArray()[0]->isGroup()); + $this->assertSame('another-group', $metadata->asArray()[0]->groupName()); + } + + #[TestDox('Parses #[IgnoreDeprecations] attribute on method')] + public function test_parses_IgnoreDeprecations_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(IgnoreDeprecationsMethodTest::class, 'testOne')->isIgnoreDeprecations(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isIgnoreDeprecations()); + } + + #[TestDox('Parses #[IgnorePhpunitDeprecations] attribute on method')] + public function test_parses_IgnorePhpunitDeprecations_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(IgnorePhpunitDeprecationsMethodTest::class, 'testOne')->isIgnorePhpunitDeprecations(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isIgnorePhpunitDeprecations()); + } + + #[TestDox('Parses #[IgnorePhpunitWarnings] attribute on method')] + public function test_parses_IgnorePhpunitWarnings_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(IgnorePhpunitWarningsTest::class, 'testOne')->isIgnorePhpunitWarnings(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isIgnorePhpunitWarnings()); + } + + #[TestDox('Parses #[PostCondition] attribute on method')] + public function test_parses_PostCondition_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(SmallTest::class, 'postCondition')->isPostCondition(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isPostCondition()); + } + + #[TestDox('Parses #[PreCondition] attribute on method')] + public function test_parses_PreCondition_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(SmallTest::class, 'preCondition')->isPreCondition(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isPreCondition()); + } + + #[TestDox('Parses #[PreserveGlobalState] attribute on method')] + public function test_parses_PreserveGlobalState_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(PreserveGlobalStateTest::class, 'testOne')->isPreserveGlobalState(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isPreserveGlobalState()); + $this->assertFalse($metadata->asArray()[0]->enabled()); + } + + #[TestDox('Parses #[RequiresMethod] attribute on method')] + public function test_parses_RequiresMethod_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(RequiresMethodTest::class, 'testOne')->isRequiresMethod(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isRequiresMethod()); + $this->assertSame('AnotherClassName', $metadata->asArray()[0]->className()); + $this->assertSame('anotherMethodName', $metadata->asArray()[0]->methodName()); + } + + #[TestDox('Parses #[RequiresFunction] attribute on method')] + public function test_parses_RequiresFunction_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(RequiresFunctionTest::class, 'testOne')->isRequiresFunction(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isRequiresFunction()); + $this->assertSame('g', $metadata->asArray()[0]->functionName()); + } + + #[TestDox('Parses #[RequiresOperatingSystem] attribute on method')] + public function test_parses_RequiresOperatingSystem_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(RequiresOperatingSystemTest::class, 'testOne')->isRequiresOperatingSystem(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isRequiresOperatingSystem()); + $this->assertSame('Linux', $metadata->asArray()[0]->operatingSystem()); + } + + #[TestDox('Parses #[RequiresOperatingSystemFamily] attribute on method')] + public function test_parses_RequiresOperatingSystemFamily_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(RequiresOperatingSystemFamilyTest::class, 'testOne')->isRequiresOperatingSystemFamily(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isRequiresOperatingSystemFamily()); + $this->assertSame('Linux', $metadata->asArray()[0]->operatingSystemFamily()); + } + + #[TestDox('Parses #[RequiresPhp] attribute on method')] + public function test_parses_RequiresPhp_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(RequiresPhpTest::class, 'testOne')->isRequiresPhp(); + + $this->assertCount(1, $metadata); + + $requirement = $metadata->asArray()[0]; + + $this->assertTrue($requirement->isRequiresPhp()); + + assert($requirement instanceof RequiresPhp); + + $versionRequirement = $requirement->versionRequirement(); + + assert($versionRequirement instanceof ConstraintRequirement); + + $this->assertSame('^8.0', $versionRequirement->asString()); + } + + #[TestDox('Parses #[RequiresPhpExtension] attribute on method')] + public function test_parses_RequiresPhpExtension_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(RequiresPhpExtensionTest::class, 'testOne')->isRequiresPhpExtension(); + + $this->assertCount(1, $metadata); + + $requirement = $metadata->asArray()[0]; + + $this->assertTrue($requirement->isRequiresPhpExtension()); + + assert($requirement instanceof RequiresPhpExtension); + + $this->assertSame('bar', $requirement->extension()); + $this->assertTrue($requirement->hasVersionRequirement()); + + $versionRequirement = $requirement->versionRequirement(); + + assert($versionRequirement instanceof ComparisonRequirement); + + $this->assertSame('>= 2.0', $versionRequirement->asString()); + + $metadata = $this->parser()->forMethod(RequiresPhpExtensionTest::class, 'testTwo'); + + $this->assertCount(1, $metadata); + + $requirement = $metadata->asArray()[0]; + + $this->assertTrue($requirement->isRequiresPhpExtension()); + + assert($requirement instanceof RequiresPhpExtension); + + $this->assertSame('baz', $requirement->extension()); + $this->assertTrue($requirement->hasVersionRequirement()); + + $versionRequirement = $requirement->versionRequirement(); + + assert($versionRequirement instanceof ConstraintRequirement); + + $this->assertSame('^1.0', $versionRequirement->asString()); + } + + #[TestDox('Parses #[RequiresPhpunit] attribute on method')] + public function test_parses_RequiresPhpunit_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(RequiresPhpunitTest::class, 'testOne')->isRequiresPhpunit(); + + $this->assertCount(1, $metadata); + + $requirement = $metadata->asArray()[0]; + + $this->assertTrue($requirement->isRequiresPhpunit()); + + assert($requirement instanceof RequiresPhpunit); + + $versionRequirement = $requirement->versionRequirement(); + + assert($versionRequirement instanceof ConstraintRequirement); + + $this->assertSame('^10.0', $versionRequirement->asString()); + } + + #[TestDox('Parses #[RequiresPhpunitExtension] attribute on method')] + public function test_parses_RequiresPhpunitExtension_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(RequiresPhpunitExtensionTest::class, 'testOne')->isRequiresPhpunitExtension(); + + $this->assertCount(2, $metadata); + + $requirement = $metadata->asArray()[0]; + $this->assertTrue($requirement->isRequiresPhpunitExtension()); + assert($requirement instanceof RequiresPhpunitExtension); + $this->assertSame('PHPUnit\TestFixture\Metadata\Attribute\SomeExtension', $requirement->extensionClass()); + + $requirement = $metadata->asArray()[1]; + $this->assertTrue($requirement->isRequiresPhpunitExtension()); + assert($requirement instanceof RequiresPhpunitExtension); + $this->assertSame('PHPUnit\TestFixture\Metadata\Attribute\SomeOtherExtension', $requirement->extensionClass()); + } + + #[TestDox('Parses #[RequiresEnvironmentVariable] attribute on method')] + public function test_parses_RequiresEnvironmentVariable_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(RequiresEnvironmentVariableTest::class, 'testOne')->isRequiresEnvironmentVariable(); + + $this->assertCount(2, $metadata); + + $requirement = $metadata->asArray()[0]; + $this->assertTrue($requirement->isRequiresEnvironmentVariable()); + assert($requirement instanceof RequiresEnvironmentVariable); + $this->assertSame('foo', $requirement->environmentVariableName()); + $this->assertNull($requirement->value()); + + $requirement = $metadata->asArray()[1]; + $this->assertTrue($requirement->isRequiresEnvironmentVariable()); + assert($requirement instanceof RequiresEnvironmentVariable); + $this->assertSame('bar', $requirement->environmentVariableName()); + $this->assertSame('baz', $requirement->value()); + } + + #[TestDox('Parses #[WithEnvironmentVariable] attribute on method')] + public function test_parses_WithEnvironmentVariable_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(WithEnvironmentVariableTest::class, 'testOne')->isWithEnvironmentVariable(); + + $this->assertCount(2, $metadata); + + $withEnvironmentVariable = $metadata->asArray()[0]; + $this->assertTrue($withEnvironmentVariable->isWithEnvironmentVariable()); + assert($withEnvironmentVariable instanceof WithEnvironmentVariable); + $this->assertSame('foo', $withEnvironmentVariable->environmentVariableName()); + $this->assertNull($withEnvironmentVariable->value()); + + $withEnvironmentVariable = $metadata->asArray()[1]; + $this->assertTrue($withEnvironmentVariable->isWithEnvironmentVariable()); + assert($withEnvironmentVariable instanceof WithEnvironmentVariable); + $this->assertSame('bar', $withEnvironmentVariable->environmentVariableName()); + $this->assertSame('baz', $withEnvironmentVariable->value()); + } + + #[TestDox('Parses #[RequiresSetting] attribute on method')] + public function test_parses_RequiresSetting_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(RequiresSettingTest::class, 'testOne')->isRequiresSetting(); + + $this->assertCount(1, $metadata); + + $requirement = $metadata->asArray()[0]; + + $this->assertTrue($requirement->isRequiresSetting()); + + assert($requirement instanceof RequiresSetting); + + $this->assertSame('another-setting', $requirement->setting()); + $this->assertSame('another-value', $requirement->value()); + } + + #[TestDox('Parses #[RunInSeparateProcess] attribute on method')] + public function test_parses_RunInSeparateProcess_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(ProcessIsolationTest::class, 'testOne')->isRunInSeparateProcess(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isRunInSeparateProcess()); + } + + #[TestDox('Parses #[Test] attribute on method')] + public function test_parses_Test_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(SmallTest::class, 'one')->isTest(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isTest()); + } + + #[TestDox('Parses #[TestDox] attribute on method')] + public function test_parses_TestDox_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(TestDoxTest::class, 'testOne')->isTestDox(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isTestDox()); + $this->assertSame('text', $metadata->asArray()[0]->text()); + } + + #[TestDox('Parses #[TestDoxFormatter] attribute on method')] + public function test_parses_TestDoxFormatter_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(TestDoxTest::class, 'testTwo')->isTestDoxFormatter(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isTestDoxFormatter()); + $this->assertSame('methodName', $metadata->asArray()[0]->methodName()); + } + + #[TestDox('Parses #[TestDoxFormatterExternal] attribute on method')] + public function test_parses_TestDoxFormatterExternal_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(TestDoxTest::class, 'testThree')->isTestDoxFormatter(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isTestDoxFormatter()); + $this->assertSame('ClassName', $metadata->asArray()[0]->className()); + $this->assertSame('methodName', $metadata->asArray()[0]->methodName()); + } + + #[TestDox('Parses #[TestWith] attribute on method')] + public function test_parses_TestWith_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(TestWithTest::class, 'testOne')->isTestWith(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isTestWith()); + $this->assertSame([1, 2, 3], $metadata->asArray()[0]->data()); + $this->assertFalse($metadata->asArray()[0]->hasName()); + $this->assertNull($metadata->asArray()[0]->name()); + } + + #[TestDox('Parses #[TestWith] attribute with name on method')] + public function test_parses_TestWith_attribute_with_name_on_method(): void + { + $metadata = $this->parser()->forMethod(TestWithTest::class, 'testOneWithName')->isTestWith(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isTestWith()); + $this->assertSame([1, 2, 3], $metadata->asArray()[0]->data()); + $this->assertTrue($metadata->asArray()[0]->hasName()); + $this->assertSame('Name1', $metadata->asArray()[0]->name()); + } + + #[TestDox('Parses #[TestWithJson] attribute on method')] + public function test_parses_TestWithJson_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(TestWithTest::class, 'testTwo')->isTestWith(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isTestWith()); + $this->assertSame([1, 2, 3], $metadata->asArray()[0]->data()); + $this->assertFalse($metadata->asArray()[0]->hasName()); + $this->assertNull($metadata->asArray()[0]->name()); + } + + #[TestDox('Parses #[TestWithJson] attribute with name on method')] + public function test_parses_TestWithJson_attribute_with_name_on_method(): void + { + $metadata = $this->parser()->forMethod(TestWithTest::class, 'testTwoWithName')->isTestWith(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isTestWith()); + $this->assertSame([1, 2, 3], $metadata->asArray()[0]->data()); + $this->assertTrue($metadata->asArray()[0]->hasName()); + $this->assertSame('Name2', $metadata->asArray()[0]->name()); + } + + #[TestDox('Parses #[Ticket] attribute on method')] + public function test_parses_Ticket_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(GroupTest::class, 'testOne')->isGroup(); + + $this->assertCount(2, $metadata); + $this->assertTrue($metadata->asArray()[1]->isGroup()); + $this->assertSame('another-ticket', $metadata->asArray()[1]->groupName()); + } + + #[TestDox('Parses #[WithoutErrorHandler] attribute on method')] + public function test_parses_WithoutErrorHandler_attribute_on_method(): void + { + $metadata = $this->parser()->forMethod(WithoutErrorHandlerTest::class, 'testOne')->isWithoutErrorHandler(); + + $this->assertCount(1, $metadata); + $this->assertTrue($metadata->asArray()[0]->isWithoutErrorHandler()); + } + + public function test_parses_attributes_for_class_and_method(): void + { + $metadata = $this->parser()->forClassAndMethod(CoversTest::class, 'testOne'); + + $this->assertCount(1, $metadata->isCoversClass()); + $this->assertCount(1, $metadata->isCoversFunction()); + } + + public function test_ignores_attributes_not_owned_by_PHPUnit(): void + { + $metadata = $this->parser()->forClassAndMethod(NonPhpunitAttributeTest::class, 'testOne'); + + $this->assertTrue($metadata->isEmpty()); + } + + public function test_ignores_attributes_in_PHPUnit_namespace_that_do_not_exist(): void + { + $metadata = $this->parser()->forClassAndMethod(PhpunitAttributeThatDoesNotExistTest::class, 'testOne'); + + $this->assertTrue($metadata->isEmpty()); + } + + public function test_handles_ReflectionException_raised_when_instantiating_attribute_on_class(): void + { + $this->expectException(InvalidAttributeException::class); + + $this->parser()->forClass(DuplicateSmallAttributeTest::class); + } + + public function test_handles_ReflectionException_raised_when_instantiating_attribute_on_method(): void + { + $this->expectException(InvalidAttributeException::class); + + $this->parser()->forMethod(DuplicateTestAttributeTest::class, 'testOne'); + } + + abstract protected function parser(): Parser; +} diff --git a/tests/unit/Metadata/Parser/CachedAttributeParserTest.php b/tests/unit/Metadata/Parser/CachedAttributeParserTest.php new file mode 100644 index 00000000000..ccfd66d5e66 --- /dev/null +++ b/tests/unit/Metadata/Parser/CachedAttributeParserTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Parser; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(CachingParser::class)] +#[Small] +#[Group('metadata')] +#[Group('metadata/attributes')] +final class CachedAttributeParserTest extends AttributeParserTestCase +{ + protected function parser(): Parser + { + return new CachingParser(new AttributeParser); + } +} diff --git a/tests/unit/Metadata/Version/RequirementTest.php b/tests/unit/Metadata/Version/RequirementTest.php new file mode 100644 index 00000000000..a1b0a8f02a5 --- /dev/null +++ b/tests/unit/Metadata/Version/RequirementTest.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +use PharIo\Version\VersionConstraintParser; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; +use PHPUnit\Metadata\Version\ComparisonRequirement; +use PHPUnit\Metadata\Version\ConstraintRequirement; +use PHPUnit\Metadata\Version\Requirement; +use PHPUnit\Util\VersionComparisonOperator; + +#[CoversClass(ComparisonRequirement::class)] +#[CoversClass(ConstraintRequirement::class)] +#[CoversClass(Requirement::class)] +#[UsesClass(VersionComparisonOperator::class)] +#[Small] +#[Group('metadata')] +final class RequirementTest extends TestCase +{ + public static function constraintProvider(): array + { + return [ + [ + true, + '1.0.0', + new ConstraintRequirement( + (new VersionConstraintParser)->parse('1.0.0'), + ), + ], + ]; + } + + public static function comparisonProvider(): array + { + return [ + [true, '1.0.0', new ComparisonRequirement('1.0.0', new VersionComparisonOperator('='))], + ]; + } + + public function testCanBeCreatedFromStringWithVersionConstraint(): void + { + $requirement = Requirement::from('^1.0'); + + $this->assertInstanceOf(ConstraintRequirement::class, $requirement); + $this->assertSame('^1.0', $requirement->asString()); + } + + #[DataProvider('constraintProvider')] + public function testVersionRequirementCanBeCheckedUsingVersionConstraint(bool $expected, string $version, ConstraintRequirement $requirement): void + { + $this->assertSame($expected, $requirement->isSatisfiedBy($version)); + } + + public function testCanBeCreatedFromStringWithSimpleComparison(): void + { + $requirement = Requirement::from('>= 1.0'); + + $this->assertInstanceOf(ComparisonRequirement::class, $requirement); + $this->assertSame('>= 1.0', $requirement->asString()); + } + + #[DataProvider('comparisonProvider')] + public function testVersionRequirementCanBeCheckedUsingSimpleComparison(bool $expected, string $version, ComparisonRequirement $requirement): void + { + $this->assertSame($expected, $requirement->isSatisfiedBy($version)); + } + + public function testCannotBeCreatedFromInvalidString(): void + { + $this->expectException(InvalidVersionRequirementException::class); + + Requirement::from('invalid'); + } +} diff --git a/tests/unit/Runner/Baseline/BaselineTest.php b/tests/unit/Runner/Baseline/BaselineTest.php new file mode 100644 index 00000000000..a93cf49c883 --- /dev/null +++ b/tests/unit/Runner/Baseline/BaselineTest.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use function realpath; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Baseline::class)] +#[Small] +final class BaselineTest extends TestCase +{ + public function testGroupsIssuesByFileAndLine(): void + { + $baseline = new Baseline; + + $baseline->add($this->issue()); + + $issues = $baseline->groupedByFileAndLine(); + + $this->assertCount(1, $issues); + $this->assertArrayHasKey($this->issue()->file(), $issues); + + $lines = $issues[$this->issue()->file()]; + + $this->assertCount(1, $lines); + $this->assertArrayHasKey(10, $lines); + + $line = $lines[10]; + + $this->assertCount(1, $line); + $this->assertArrayHasKey(0, $line); + + $this->assertTrue($this->issue()->equals($line[0])); + } + + public function testCanBeQueried(): void + { + $baseline = new Baseline; + + $baseline->add($this->issue()); + + $this->assertTrue($baseline->has($this->issue())); + $this->assertFalse($baseline->has($this->anotherIssue())); + $this->assertFalse($baseline->has($this->yetAnotherIssue())); + } + + private function issue(): Issue + { + return Issue::from( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + 10, + null, + 'Undefined variable $b', + ); + } + + private function anotherIssue(): Issue + { + return Issue::from( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + 11, + null, + 'Undefined variable $c', + ); + } + + private function yetAnotherIssue(): Issue + { + return Issue::from( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + 10, + null, + 'yet another issue', + ); + } +} diff --git a/tests/unit/Runner/Baseline/IssueTest.php b/tests/unit/Runner/Baseline/IssueTest.php new file mode 100644 index 00000000000..b484f3c93f4 --- /dev/null +++ b/tests/unit/Runner/Baseline/IssueTest.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use function realpath; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use PHPUnit\Runner\FileDoesNotExistException; + +#[CoversClass(Issue::class)] +#[CoversClass(FileDoesNotExistException::class)] +#[CoversClass(FileDoesNotHaveLineException::class)] +#[Small] +final class IssueTest extends TestCase +{ + public function testHasFile(): void + { + $this->assertSame( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + $this->issue()->file(), + ); + } + + public function testHasLine(): void + { + $this->assertSame(10, $this->issue()->line()); + } + + public function testHasHash(): void + { + $this->assertSame('6bf70f0b8c461415955e2d3a97cfbe664f5b957b', $this->issue()->hash()); + } + + public function testHasDescription(): void + { + $this->assertSame('Undefined variable $b', $this->issue()->description()); + } + + public function testIsComparable(): void + { + $this->assertTrue($this->issue()->equals($this->issue())); + $this->assertFalse($this->issue()->equals($this->anotherIssue())); + } + + public function testCannotBeCreatedForFileThatDoesNotExist(): void + { + $this->expectException(FileDoesNotExistException::class); + + Issue::from( + 'does-not-exist.php', + 1, + null, + 'description', + ); + } + + public function testCannotBeCreatedForLineThatDoesNotExist(): void + { + $this->expectException(FileDoesNotHaveLineException::class); + + Issue::from( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + 1234, + null, + 'description', + ); + } + + private function issue(): Issue + { + return Issue::from( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + 10, + null, + 'Undefined variable $b', + ); + } + + private function anotherIssue(): Issue + { + return Issue::from( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + 11, + null, + 'Undefined variable $c', + ); + } +} diff --git a/tests/unit/Runner/Baseline/ReaderTest.php b/tests/unit/Runner/Baseline/ReaderTest.php new file mode 100644 index 00000000000..0f95fedda48 --- /dev/null +++ b/tests/unit/Runner/Baseline/ReaderTest.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use function realpath; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Reader::class)] +#[Small] +final class ReaderTest extends TestCase +{ + public function testReadsBaselineFromFileWithValidXml(): void + { + $baseline = (new Reader)->read(__DIR__ . '/../../../_files/baseline/expected.xml'); + + $this->assertTrue($baseline->has($this->issue())); + $this->assertTrue($baseline->has($this->anotherIssue())); + $this->assertTrue($baseline->has($this->yetAnotherIssue())); + } + + public function testCannotReadBaselineFromFileThatDoesNotExist(): void + { + $this->expectException(CannotLoadBaselineException::class); + + (new Reader)->read('does-not-exist.xml'); + } + + public function testCannotReadBaselineFromFileWithInvalidXml(): void + { + $this->expectException(CannotLoadBaselineException::class); + + (new Reader)->read(realpath(__DIR__ . '/../../../end-to-end/_files/baseline/invalid-baseline/baseline.xml')); + } + + public function testCannotReadBaselineFromFileWithIncompatibleXml(): void + { + $this->expectException(CannotLoadBaselineException::class); + + (new Reader)->read(realpath(__DIR__ . '/../../../end-to-end/_files/baseline/unsupported-baseline/baseline.xml')); + } + + private function issue(): Issue + { + return Issue::from( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + 10, + null, + 'Undefined variable $b', + ); + } + + private function anotherIssue(): Issue + { + return Issue::from( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + 11, + null, + 'Undefined variable $c', + ); + } + + private function yetAnotherIssue(): Issue + { + return Issue::from( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + 10, + null, + 'yet another issue', + ); + } +} diff --git a/tests/unit/Runner/Baseline/RelativePathCalculatorTest.php b/tests/unit/Runner/Baseline/RelativePathCalculatorTest.php new file mode 100644 index 00000000000..7ca8ac54dc1 --- /dev/null +++ b/tests/unit/Runner/Baseline/RelativePathCalculatorTest.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(RelativePathCalculator::class)] +#[Small] +/** + * @see Copied from https://github.com/phpstan/phpstan-src/blob/1.10.33/src/File/ParentDirectoryRelativePathHelper.php + */ +final class RelativePathCalculatorTest extends TestCase +{ + public static function dataGetRelativePath(): array + { + return [ + [ + '/usr/var/www', + '/usr/var/www/test.php', + 'test.php', + ], + [ + '/usr/var/www/foo/bar/baz', + '/usr/var/www/test.php', + '../../../test.php', + ], + [ + '/', + '/usr/var/www/test.php', + '/usr/var/www/test.php', + ], + [ + '/usr/var/www', + '/usr/var/www/src/test.php', + 'src/test.php', + ], + [ + '/usr/var/www/', + '/usr/var/www/src/test.php', + 'src/test.php', + ], + [ + '/usr/var/www', + '/usr/var/test.php', + '../test.php', + ], + [ + '/usr/var/www/', + '/usr/var/test.php', + '../test.php', + ], + [ + '/usr/var/www/', + '/usr/var/web/test.php', + '../web/test.php', + ], + [ + '/usr/var/www/', + '/usr/var/web/foo/test.php', + '../web/foo/test.php', + ], + [ + '/', + '/test.php', + '/test.php', + ], + [ + '/var/www', + '/usr/test.php', + '/usr/test.php', + ], + [ + 'C:\\var', + 'C:\\var\\test.php', + 'test.php', + ], + [ + 'C:\\var', + 'C:\\var\\src\\test.php', + 'src/test.php', + ], + [ + 'C:\\var', + 'C:\\test.php', + '../test.php', + ], + [ + 'C:\\var\\', + 'C:\\usr\\test.php', + '../usr/test.php', + ], + [ + 'C:\\', + 'C:\\test.php', + 'test.php', + ], + [ + 'C:\\', + 'C:\\src\\test.php', + 'src/test.php', + ], + [ + 'C:\\var', + 'D:\\var\\src\\test.php', + 'D:\\var\\src\\test.php', + ], + [ + '/usr/var/www', + 'file:///usr/var/www/test.php', + 'test.php', + ], + ]; + } + + #[DataProvider('dataGetRelativePath')] + public function testGetRelativePath(string $baselineDirectory, string $filename, string $expectedRelativePath): void + { + $calculator = new RelativePathCalculator($baselineDirectory); + + $this->assertSame( + $expectedRelativePath, + $calculator->calculate($filename), + ); + } +} diff --git a/tests/unit/Runner/Baseline/WriterTest.php b/tests/unit/Runner/Baseline/WriterTest.php new file mode 100644 index 00000000000..29bed42389a --- /dev/null +++ b/tests/unit/Runner/Baseline/WriterTest.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use const DIRECTORY_SEPARATOR; +use function getcwd; +use function ltrim; +use function realpath; +use function str_replace; +use function unlink; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Writer::class)] +#[Small] +final class WriterTest extends TestCase +{ + private string $target; + + public static function baselinePathProvider(): iterable + { + $absoluteBaselinePath = __DIR__ . '/../../../_files/baseline/expected.xml'; + + yield [$absoluteBaselinePath]; + + yield [ltrim(str_replace(getcwd(), '', $absoluteBaselinePath), DIRECTORY_SEPARATOR)]; + } + + protected function setUp(): void + { + $this->target = realpath(__DIR__ . '/../../../_files/baseline') . DIRECTORY_SEPARATOR . 'actual.xml'; + } + + protected function tearDown(): void + { + @unlink($this->target); + } + + #[DataProvider('baselinePathProvider')] + public function testWritesBaselineToFileInXmlFormat(string $baselinePath): void + { + (new Writer)->write($this->target, $this->baseline()); + + $this->assertXmlFileEqualsXmlFile($baselinePath, $this->target); + } + + public function testItThrowsExceptionIfBaseLinePathDoesNotExists(): void + { + $this->expectException(CannotWriteBaselineException::class); + + (new Writer)->write('/path/to/invalid', $this->baseline()); + } + + private function baseline(): Baseline + { + $baseline = new Baseline; + + $baseline->add($this->issue()); + $baseline->add($this->anotherIssue()); + $baseline->add($this->yetAnotherIssue()); + + return $baseline; + } + + private function issue(): Issue + { + return Issue::from( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + 10, + null, + 'Undefined variable $b', + ); + } + + private function anotherIssue(): Issue + { + return Issue::from( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + 11, + null, + 'Undefined variable $c', + ); + } + + private function yetAnotherIssue(): Issue + { + return Issue::from( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + 10, + null, + 'yet another issue', + ); + } +} diff --git a/tests/unit/Runner/DefaultTestResultCacheTest.php b/tests/unit/Runner/DefaultTestResultCacheTest.php deleted file mode 100644 index f925ce6fbd1..00000000000 --- a/tests/unit/Runner/DefaultTestResultCacheTest.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -use PHPUnit\Framework\TestCase; - -/** - * @covers \PHPUnit\Runner\DefaultTestResultCache - * @small - */ -final class DefaultTestResultCacheTest extends TestCase -{ - /** - * @var DefaultTestResultCache - */ - private $subject; - - protected function setUp(): void - { - $this->subject = new DefaultTestResultCache(); - } - - public function testGetTimeForNonExistentTestNameReturnsFloatZero(): void - { - $this->assertSame(0.0, $this->subject->getTime('doesNotExist')); - } -} diff --git a/tests/unit/Runner/ErrorHandlerTest.php b/tests/unit/Runner/ErrorHandlerTest.php new file mode 100644 index 00000000000..5dd9c63137a --- /dev/null +++ b/tests/unit/Runner/ErrorHandlerTest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use const E_USER_DEPRECATED; +use function trigger_error; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use ReflectionClass; + +#[CoversClass(ErrorHandler::class)] +#[Small] +final class ErrorHandlerTest extends TestCase +{ + public function testThrowsExceptionWhenUsingInvalidOrderOption(): void + { + $errorHandler = ErrorHandler::instance(); + $errorHandler->registerDeprecationHandler(); + trigger_error('deprecation', E_USER_DEPRECATED); + $errorHandler->restoreDeprecationHandler(); + $refl = new ReflectionClass($errorHandler); + $globalDeprecations = $refl->getProperty('globalDeprecations'); + $registeredDeprecations = $globalDeprecations->getValue($errorHandler); + $this->assertCount(1, $registeredDeprecations); + $this->assertSame('deprecation', $registeredDeprecations[0][1]); + $globalDeprecations->setValue($errorHandler, []); + } +} diff --git a/tests/unit/Runner/Filter/ExcludeNameFilterIteratorTest.php b/tests/unit/Runner/Filter/ExcludeNameFilterIteratorTest.php new file mode 100644 index 00000000000..83a77d6c392 --- /dev/null +++ b/tests/unit/Runner/Filter/ExcludeNameFilterIteratorTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Filter; + +use Exception; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestSuite; +use PHPUnit\TestFixture\BankAccountTest; + +#[CoversClass(ExcludeNameFilterIterator::class)] +#[Small] +class ExcludeNameFilterIteratorTest extends TestCase +{ + /** + * @throws Exception + */ + public function testCaseSensitiveMatch(): void + { + $this->assertTrue($this->createFilter('NotMatchingPattern')->accept()); + } + + /** + * @throws Exception + */ + public function testCaseInsensitiveMatch(): void + { + $this->assertTrue($this->createFilter('notmatchingbankaccount')->accept()); + } + + /** + * @throws Exception + */ + private function createFilter(string $filter): ExcludeNameFilterIterator + { + $suite = TestSuite::empty('test suite name'); + $suite->addTest(new BankAccountTest('testBalanceIsInitiallyZero')); + + $iterator = new ExcludeNameFilterIterator($suite->getIterator(), $filter); + + $iterator->rewind(); + + return $iterator; + } +} diff --git a/tests/unit/Runner/Filter/IncludeNameFilterIteratorTest.php b/tests/unit/Runner/Filter/IncludeNameFilterIteratorTest.php new file mode 100644 index 00000000000..7ce439efd06 --- /dev/null +++ b/tests/unit/Runner/Filter/IncludeNameFilterIteratorTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Filter; + +use Exception; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestSuite; +use PHPUnit\TestFixture\BankAccountTest; + +#[CoversClass(IncludeNameFilterIterator::class)] +#[Small] +final class IncludeNameFilterIteratorTest extends TestCase +{ + /** + * @throws Exception + */ + public function testCaseSensitiveMatch(): void + { + $this->assertTrue($this->createFilter('BankAccountTest')->accept()); + } + + /** + * @throws Exception + */ + public function testCaseInsensitiveMatch(): void + { + $this->assertTrue($this->createFilter('bankaccounttest')->accept()); + } + + /** + * @throws Exception + */ + private function createFilter(string $filter): IncludeNameFilterIterator + { + $suite = TestSuite::empty('test suite name'); + $suite->addTest(new BankAccountTest('testBalanceIsInitiallyZero')); + + $iterator = new IncludeNameFilterIterator($suite->getIterator(), $filter); + + $iterator->rewind(); + + return $iterator; + } +} diff --git a/tests/unit/Runner/Filter/NameFilterIteratorTest.php b/tests/unit/Runner/Filter/NameFilterIteratorTest.php deleted file mode 100644 index e7f7c88c991..00000000000 --- a/tests/unit/Runner/Filter/NameFilterIteratorTest.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner\Filter; - -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestSuite; -use PHPUnit\TestFixture\BankAccountTest; - -/** - * @small - */ -final class NameFilterIteratorTest extends TestCase -{ - public function testCaseSensitiveMatch(): void - { - $this->assertTrue($this->createFilter('BankAccountTest')->accept()); - } - - public function testCaseInsensitiveMatch(): void - { - $this->assertTrue($this->createFilter('bankaccounttest')->accept()); - } - - private function createFilter(string $filter): NameFilterIterator - { - $suite = new TestSuite; - $suite->addTest(new BankAccountTest('testBalanceIsInitiallyZero')); - - $iterator = new NameFilterIterator($suite->getIterator(), $filter); - - $iterator->rewind(); - - return $iterator; - } -} diff --git a/tests/unit/Runner/Filter/TestIdFilterIteratorTest.php b/tests/unit/Runner/Filter/TestIdFilterIteratorTest.php new file mode 100644 index 00000000000..dc448e16b56 --- /dev/null +++ b/tests/unit/Runner/Filter/TestIdFilterIteratorTest.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Filter; + +use function assert; +use Iterator; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Framework\TestSuiteIterator; +use PHPUnit\TestFixture\BankAccountTest; + +#[CoversClass(TestIdFilterIterator::class)] +#[CoversClass(TestSuiteIterator::class)] +#[Small] +final class TestIdFilterIteratorTest extends TestCase +{ + public function testAcceptsTestsBasedOnTheirId(): void + { + $id = 'PHPUnit\TestFixture\BankAccountTest::testBalanceCannotBecomeNegative'; + + foreach ($this->testSuiteIterator([$id]) as $test) { + assert($test instanceof TestCase); + + $this->assertSame($id, $test->valueObjectForEvents()->id()); + } + } + + /** + * @param list $testIds + */ + private function testSuiteIterator(array $testIds): Iterator + { + $factory = new Factory; + + $factory->addTestIdFilter($testIds); + + $testSuite = $this->testSuite(); + + $testSuite->injectFilter($factory); + + return $testSuite->getIterator(); + } + + private function testSuite(): TestSuite + { + $suite = TestSuite::empty('test suite name'); + + $suite->addTest(new BankAccountTest('testBalanceIsInitiallyZero')); + $suite->addTest(new BankAccountTest('testBalanceCannotBecomeNegative')); + $suite->addTest(new BankAccountTest('testBalanceCannotBecomeNegative2')); + + return $suite; + } +} diff --git a/tests/unit/Runner/HookMethod/HookMethodCollectionTest.php b/tests/unit/Runner/HookMethod/HookMethodCollectionTest.php new file mode 100644 index 00000000000..600bd95c4c7 --- /dev/null +++ b/tests/unit/Runner/HookMethod/HookMethodCollectionTest.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(HookMethodCollection::class)] +#[Small] +final class HookMethodCollectionTest extends TestCase +{ + public static function provider(): iterable + { + return [ + [ + HookMethodCollection::defaultBeforeClass()->add(new HookMethod('someMethod', 0)), + ['someMethod', 'setUpBeforeClass'], + ], + [ + HookMethodCollection::defaultBefore()->add(new HookMethod('someMethod', 0)), + ['someMethod', 'setUp'], + ], + [ + HookMethodCollection::defaultPreCondition()->add(new HookMethod('someMethod', 0)), + ['someMethod', 'assertPreConditions'], + ], + [ + HookMethodCollection::defaultPostCondition()->add(new HookMethod('someMethod', 0)), + ['assertPostConditions', 'someMethod'], + ], + [ + HookMethodCollection::defaultAfter()->add(new HookMethod('someMethod', 0)), + ['tearDown', 'someMethod'], + ], + [ + HookMethodCollection::defaultAfterClass()->add(new HookMethod('someMethod', 0)), + ['tearDownAfterClass', 'someMethod'], + ], + [ + HookMethodCollection::defaultBeforeClass() + ->add(new HookMethod('methodWithHighPriority', priority: 1)) + ->add(new HookMethod('methodWithVeryLowPriority', priority: -10)) + ->add(new HookMethod('methodWithLowPriority', priority: -1)) + ->add(new HookMethod('methodWithVeryHighPriority', priority: 10)) + ->add(new HookMethod('methodWithoutPriority', 0)), + [ + 'methodWithVeryHighPriority', + 'methodWithHighPriority', + 'methodWithoutPriority', + 'setUpBeforeClass', + 'methodWithLowPriority', + 'methodWithVeryLowPriority', + ], + ], + ]; + } + + #[DataProvider('provider')] + public function testIterator(HookMethodCollection $hookMethodsCollection, array $expected): void + { + $this->assertSame($expected, $hookMethodsCollection->methodNamesSortedByPriority()); + } +} diff --git a/tests/unit/Runner/NullTestResultCacheTest.php b/tests/unit/Runner/NullTestResultCacheTest.php deleted file mode 100644 index c1ee12584ab..00000000000 --- a/tests/unit/Runner/NullTestResultCacheTest.php +++ /dev/null @@ -1,29 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; -use PHPUnit\Runner\BaseTestRunner; -use PHPUnit\Runner\NullTestResultCache; - -/** - * @group test-reorder - * @small - */ -final class NullTestResultCacheTest extends TestCase -{ - public function testHasWorkingStubs(): void - { - $cache = new NullTestResultCache; - $cache->load(); - $cache->persist(); - - $this->assertSame(BaseTestRunner::STATUS_UNKNOWN, $cache->getState('testName')); - $this->assertSame(0.0, $cache->getTime('testName')); - } -} diff --git a/tests/unit/Runner/Phpt/ParserTest.php b/tests/unit/Runner/Phpt/ParserTest.php new file mode 100644 index 00000000000..8abec6ddf28 --- /dev/null +++ b/tests/unit/Runner/Phpt/ParserTest.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Phpt; + +use function glob; +use function str_replace; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Parser::class)] +#[Small] +final class ParserTest extends TestCase +{ + /** + * @return non-empty-array + */ + public static function unsupportedSections(): array + { + $data = []; + + foreach (glob(__DIR__ . '/../../../_files/phpt/unsupported/*.phpt') as $file) { + $data[str_replace([__DIR__ . '/../../../_files/phpt/unsupported/', '.phpt'], '', $file)] = [$file]; + } + + return $data; + } + + /** + * @return non-empty-list + */ + public static function invalidFiles(): array + { + $data = []; + + foreach (glob(__DIR__ . '/../../../_files/phpt/invalid/*.phpt') as $file) { + $data[] = [$file]; + } + + return $data; + } + + /** + * @param non-empty-string $file + */ + #[DataProvider('unsupportedSections')] + #[TestDox('PHPT section --$_dataName-- is not supported')] + public function testRejectsUnsupportedSections(string $file): void + { + $parser = new Parser; + + $this->expectException(UnsupportedPhptSectionException::class); + + $parser->parse($file); + } + + /** + * @param non-empty-string $file + */ + #[DataProvider('invalidFiles')] + public function testRejectsInvalidPhptFile(string $file): void + { + $parser = new Parser; + + $this->expectException(InvalidPhptFileException::class); + + $parser->parse($file); + } +} diff --git a/tests/unit/Runner/PhptTestCaseTest.php b/tests/unit/Runner/PhptTestCaseTest.php deleted file mode 100644 index 42eab2b404b..00000000000 --- a/tests/unit/Runner/PhptTestCaseTest.php +++ /dev/null @@ -1,402 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -use const PHP_EOL; -use function file_put_contents; -use function strtr; -use function sys_get_temp_dir; -use function touch; -use function unlink; -use PHPUnit\Framework\TestCase; -use PHPUnit\Util\PHP\AbstractPhpProcess; - -/** - * @medium - */ -final class PhptTestCaseTest extends TestCase -{ - private const EXPECT_CONTENT = <<<'EOF' ---TEST-- -EXPECT test ---FILE-- - ---EXPECT-- -Hello PHPUnit! -EOF; - - private const EXPECTF_CONTENT = <<<'EOF' ---TEST-- -EXPECTF test ---FILE-- - ---EXPECTF-- -Hello %s! -EOF; - - private const EXPECTREGEX_CONTENT = <<<'EOF' ---TEST-- -EXPECTREGEX test ---FILE-- - ---EXPECTREGEX-- -Hello [HPU]{4}[nit]{3}! -EOF; - - private const EXPECT_MISSING_ASSERTION_CONTENT = <<<'EOF' ---TEST-- -Missing EXPECTF value test ---EXPECTF-- ---FILE-- - -EOF; - - private const FILE_SECTION = <<<'EOF' - - -EOF; - - /** - * @var string - */ - private $dirname; - - /** - * @var string - */ - private $filename; - - /** - * @var PhptTestCase - */ - private $testCase; - - /** - * @var AbstractPhpProcess|\PHPUnit\Framework\MockObject\MockObject - */ - private $phpProcess; - - protected function setUp(): void - { - $this->dirname = sys_get_temp_dir(); - $this->filename = $this->dirname . '/phpunit.phpt'; - touch($this->filename); - - $this->phpProcess = $this->getMockForAbstractClass(AbstractPhpProcess::class, [], '', false); - $this->testCase = new PhptTestCase($this->filename, $this->phpProcess); - } - - protected function tearDown(): void - { - @unlink($this->filename); - - $this->phpProcess = null; - $this->testCase = null; - } - - public function testAlwaysReportsNumberOfAssertionsIsOne(): void - { - $this->assertSame(1, $this->testCase->getNumAssertions()); - } - - public function testAlwaysReportsItDoesNotUseADataprovider(): void - { - $this->assertSame(false, $this->testCase->usesDataProvider()); - } - - public function testShouldRunFileSectionAsTest(): void - { - $this->setPhpContent($this->ensureCorrectEndOfLine(self::EXPECT_CONTENT)); - - $fileSection = '' . PHP_EOL; - - $this->phpProcess - ->expects($this->once()) - ->method('runJob') - ->with($fileSection) - ->willReturn(['stdout' => '', 'stderr' => '']); - - $this->testCase->run(); - } - - public function testRenderFileSection(): void - { - $this->setPhpContent($this->ensureCorrectEndOfLine( - <<<'EOF' ---TEST-- -Something to decribe it ---FILE-- - ---EXPECT-- -Something -EOF - )); - - $renderedCode = "dirname . "' . '" . $this->filename . "'; ?>" . PHP_EOL; - - $this->phpProcess - ->expects($this->once()) - ->method('runJob') - ->with($renderedCode) - ->willReturn(['stdout' => '', 'stderr' => '']); - - $this->testCase->run(); - } - - public function testRenderSkipifSection(): void - { - $phptContent = self::EXPECT_CONTENT . PHP_EOL; - $phptContent .= '--SKIPIF--' . PHP_EOL; - $phptContent .= "" . PHP_EOL; - - $this->setPhpContent($phptContent); - - $renderedCode = "filename . "'; ?>" . PHP_EOL; - - $this->phpProcess - ->expects($this->at(0)) - ->method('runJob') - ->with($renderedCode) - ->willReturn(['stdout' => '', 'stderr' => '']); - - $this->testCase->run(); - } - - public function testShouldRunSkipifSectionWhenExists(): void - { - $skipifSection = '' . PHP_EOL; - - $phptContent = self::EXPECT_CONTENT . PHP_EOL; - $phptContent .= '--SKIPIF--' . PHP_EOL; - $phptContent .= $skipifSection; - - $this->setPhpContent($phptContent); - - $this->phpProcess - ->expects($this->at(0)) - ->method('runJob') - ->with($skipifSection) - ->willReturn(['stdout' => '', 'stderr' => '']); - - $this->testCase->run(); - } - - public function testShouldNotRunTestSectionIfSkipifSectionReturnsOutputWithSkipWord(): void - { - $skipifSection = '' . PHP_EOL; - - $phptContent = self::EXPECT_CONTENT . PHP_EOL; - $phptContent .= '--SKIPIF--' . PHP_EOL; - $phptContent .= $skipifSection; - - $this->setPhpContent($phptContent); - - $this->phpProcess - ->expects($this->once()) - ->method('runJob') - ->with($skipifSection) - ->willReturn(['stdout' => 'skip: Reason', 'stderr' => '']); - - $this->testCase->run(); - } - - public function testShouldRunCleanSectionWhenDefined(): void - { - $cleanSection = '' . PHP_EOL; - - $phptContent = self::EXPECT_CONTENT . PHP_EOL; - $phptContent .= '--CLEAN--' . PHP_EOL; - $phptContent .= $cleanSection; - - $this->setPhpContent($phptContent); - - $this->phpProcess - ->expects($this->at(1)) - ->method('runJob') - ->with($cleanSection); - - $this->testCase->run(); - } - - public function testShouldSkipTestWhenPhptFileIsEmpty(): void - { - $this->setPhpContent(''); - - $result = $this->testCase->run(); - $this->assertCount(1, $result->skipped()); - $this->assertSame('Invalid PHPT file', $result->skipped()[0]->thrownException()->getMessage()); - } - - public function testShouldSkipTestWhenFileSectionIsMissing(): void - { - $this->setPhpContent( - <<<'EOF' ---TEST-- -Something to describe it ---EXPECT-- -Something -EOF - ); - - $result = $this->testCase->run(); - - $this->assertCount(1, $result->skipped()); - $this->assertSame('Invalid PHPT file', $result->skipped()[0]->thrownException()->getMessage()); - } - - public function testShouldSkipTestWhenThereIsNoExpecOrExpectifOrExpecregexSectionInPhptFile(): void - { - $this->setPhpContent( - << -EOF - ); - - $result = $this->testCase->run(); - - $this->assertCount(1, $result->skipped()); - $skipMessage = $result->skipped()[0]->thrownException()->getMessage(); - $this->assertSame('Invalid PHPT file', $skipMessage); - } - - public function testShouldSkipTestWhenSectionHeaderIsMalformed(): void - { - $this->setPhpContent( - <<<'EOF' ----- ---TEST-- -This is not going to work out ---EXPECT-- -Tears and misery -EOF - ); - - $result = $this->testCase->run(); - - $this->assertCount(1, $result->skipped()); - $skipMessage = $result->skipped()[0]->thrownException()->getMessage(); - $this->assertSame('Invalid PHPT file: empty section header', $skipMessage); - } - - public function testShouldValidateExpectSession(): void - { - $this->setPhpContent(self::EXPECT_CONTENT); - - $this->phpProcess - ->expects($this->once()) - ->method('runJob') - ->with(self::FILE_SECTION) - ->willReturn(['stdout' => 'Hello PHPUnit!', 'stderr' => '']); - - $result = $this->testCase->run(); - - $this->assertTrue($result->wasSuccessful()); - } - - public function testShouldValidateExpectfSession(): void - { - $this->setPhpContent(self::EXPECTF_CONTENT); - - $this->phpProcess - ->expects($this->once()) - ->method('runJob') - ->with(self::FILE_SECTION) - ->willReturn(['stdout' => 'Hello PHPUnit!', 'stderr' => '']); - - $result = $this->testCase->run(); - - $this->assertTrue($result->wasSuccessful()); - } - - public function testShouldValidateExpectregexSession(): void - { - $this->setPhpContent(self::EXPECTREGEX_CONTENT); - - $this->phpProcess - ->expects($this->once()) - ->method('runJob') - ->with(self::FILE_SECTION) - ->willReturn(['stdout' => 'Hello PHPUnit!', 'stderr' => '']); - - $result = $this->testCase->run(); - - $this->assertTrue($result->wasSuccessful()); - } - - public function testShouldSkipTestWhenExpectHasNoValue(): void - { - $this->setPhpContent(self::EXPECT_MISSING_ASSERTION_CONTENT); - - $result = $this->testCase->run(); - - $this->assertCount(1, $result->errors()); - $skipMessage = $result->errors()[0]->thrownException()->getMessage(); - $this->assertSame('No PHPT expectation found', $skipMessage); - } - - /** - * @testdox PHPT tests return their filename as test name - */ - public function testPHPTReturnsFilenameAsTestName(): void - { - $this->assertSame($this->filename, $this->testCase->getName()); - } - - /** - * @testdox PHPT tests return their filename as unique sortId - */ - public function testPHPTReturnsFilenameAsSortId(): void - { - $this->assertSame($this->filename, $this->testCase->sortId()); - } - - /** - * @testdox PHPT tests do not affect dependency resolution - */ - public function testPHPTDoesNotAffectDependencyResolution(): void - { - $this->assertSame([], $this->testCase->provides()); - $this->assertSame([], $this->testCase->requires()); - } - - /** - * Defines the content of the current PHPT test. - * - * @param string $content - */ - private function setPhpContent($content): void - { - file_put_contents($this->filename, $content); - } - - /** - * Ensures the correct line ending is used for comparison. - * - * @param string $content - * - * @return string - */ - private function ensureCorrectEndOfLine($content) - { - return strtr( - $content, - [ - "\r\n" => PHP_EOL, - "\r" => PHP_EOL, - "\n" => PHP_EOL, - ] - ); - } -} diff --git a/tests/unit/Runner/ResultCache/DefaultResultCacheTest.php b/tests/unit/Runner/ResultCache/DefaultResultCacheTest.php new file mode 100644 index 00000000000..7a0e7da82b9 --- /dev/null +++ b/tests/unit/Runner/ResultCache/DefaultResultCacheTest.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use function sys_get_temp_dir; +use function tempnam; +use function uniqid; +use function unlink; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestStatus\TestStatus; +use PHPUnit\TestFixture\MultiDependencyTest; + +#[CoversClass(DefaultResultCache::class)] +#[Small] +final class DefaultResultCacheTest extends TestCase +{ + public function testGetTimeForNonExistentTestNameReturnsFloatZero(): void + { + $this->assertSame(0.0, (new DefaultResultCache)->time(ResultCacheId::fromTestClassAndMethodName(self::class, 'doesNotExist'))); + } + + public function testReadsCacheFromProvidedFilename(): void + { + $cacheFile = TEST_FILES_PATH . '../end-to-end/execution-order/_files/MultiDependencyTest_result_cache.txt'; + $cache = new DefaultResultCache($cacheFile); + $cache->load(); + + $this->assertTrue($cache->status(ResultCacheId::fromTestClassAndMethodName(MultiDependencyTest::class, 'testOne'))->isUnknown()); + $this->assertTrue($cache->status(ResultCacheId::fromTestClassAndMethodName(MultiDependencyTest::class, 'testFive'))->isSkipped()); + } + + public function testDoesClearCacheBeforeLoad(): void + { + $cacheFile = TEST_FILES_PATH . '../end-to-end/execution-order/_files/MultiDependencyTest_result_cache.txt'; + $cache = new DefaultResultCache($cacheFile); + $resultCacheId = ResultCacheId::fromTestClassAndMethodName(self::class, 'someTest'); + $cache->setStatus($resultCacheId, TestStatus::failure()); + + $this->assertTrue($cache->status(ResultCacheId::fromTestClassAndMethodName(MultiDependencyTest::class, 'testFive'))->isUnknown()); + + $cache->load(); + + $this->assertTrue($cache->status(ResultCacheId::fromTestClassAndMethodName(MultiDependencyTest::class, 'someTest'))->isUnknown()); + $this->assertTrue($cache->status(ResultCacheId::fromTestClassAndMethodName(MultiDependencyTest::class, 'testFive'))->isSkipped()); + } + + public function testCanPersistCacheToFile(): void + { + $cacheFile = tempnam(sys_get_temp_dir(), 'phpunit_'); + $cache = new DefaultResultCache($cacheFile); + $resultCacheId = ResultCacheId::fromTestClassAndMethodName(self::class, 'test' . uniqid('', true)); + + $cache->setStatus($resultCacheId, TestStatus::skipped()); + $cache->persist(); + + $cache = new DefaultResultCache($cacheFile); + $cache->load(); + + $this->assertTrue($cache->status($resultCacheId)->isSkipped()); + + unlink($cacheFile); + } + + public function testCanBeMerged(): void + { + $cacheSourceOne = new DefaultResultCache; + $cacheSourceOne->setStatus(ResultCacheId::fromTestClassAndMethodName(self::class, 'status.a'), TestStatus::skipped()); + $cacheSourceOne->setStatus(ResultCacheId::fromTestClassAndMethodName(self::class, 'status.b'), TestStatus::incomplete()); + $cacheSourceOne->setTime(ResultCacheId::fromTestClassAndMethodName(self::class, 'time.a'), 1); + $cacheSourceOne->setTime(ResultCacheId::fromTestClassAndMethodName(self::class, 'time.b'), 2); + $cacheSourceTwo = new DefaultResultCache; + $cacheSourceTwo->setStatus(ResultCacheId::fromTestClassAndMethodName(self::class, 'status.c'), TestStatus::failure()); + $cacheSourceTwo->setTime(ResultCacheId::fromTestClassAndMethodName(self::class, 'time.c'), 4); + + $sum = new DefaultResultCache; + $sum->mergeWith($cacheSourceOne); + + $this->assertSame(TestStatus::skipped()->asString(), $sum->status(ResultCacheId::fromTestClassAndMethodName(self::class, 'status.a'))->asString()); + $this->assertSame(TestStatus::incomplete()->asString(), $sum->status(ResultCacheId::fromTestClassAndMethodName(self::class, 'status.b'))->asString()); + $this->assertNotSame(TestStatus::failure()->asString(), $sum->status(ResultCacheId::fromTestClassAndMethodName(self::class, 'status.c'))->asString()); + + $this->assertSame(1.0, $sum->time(ResultCacheId::fromTestClassAndMethodName(self::class, 'time.a'))); + $this->assertSame(2.0, $sum->time(ResultCacheId::fromTestClassAndMethodName(self::class, 'time.b'))); + $this->assertNotSame(4.0, $sum->time(ResultCacheId::fromTestClassAndMethodName(self::class, 'time.c'))); + + $sum->mergeWith($cacheSourceTwo); + + $this->assertSame(TestStatus::failure()->asString(), $sum->status(ResultCacheId::fromTestClassAndMethodName(self::class, 'status.c'))->asString()); + $this->assertSame(4.0, $sum->time(ResultCacheId::fromTestClassAndMethodName(self::class, 'time.c'))); + } +} diff --git a/tests/unit/Runner/ResultCache/NullTestResultCacheTest.php b/tests/unit/Runner/ResultCache/NullTestResultCacheTest.php new file mode 100644 index 00000000000..571bc4a1116 --- /dev/null +++ b/tests/unit/Runner/ResultCache/NullTestResultCacheTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(NullResultCache::class)] +#[Small] +final class NullTestResultCacheTest extends TestCase +{ + public function testHasWorkingStubs(): void + { + $cache = new NullResultCache; + $cache->load(); + $cache->persist(); + + $this->assertTrue($cache->status(ResultCacheId::fromTestClassAndMethodName(self::class, 'testName'))->isUnknown()); + $this->assertSame(0.0, $cache->time(ResultCacheId::fromTestClassAndMethodName(self::class, 'testName'))); + } +} diff --git a/tests/unit/Runner/ResultCache/ResultCacheIdTest.php b/tests/unit/Runner/ResultCache/ResultCacheIdTest.php new file mode 100644 index 00000000000..4840973e2ab --- /dev/null +++ b/tests/unit/Runner/ResultCache/ResultCacheIdTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use PHPUnit\Event\Code\Phpt; +use PHPUnit\Event\Code\TestDox; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\TestData\TestDataCollection; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Reorderable; +use PHPUnit\Framework\TestCase; +use PHPUnit\Metadata\MetadataCollection; + +#[CoversClass(ResultCacheId::class)] +#[Small] +final class ResultCacheIdTest extends TestCase +{ + public static function provideResultCacheIds(): iterable + { + yield ['PHPUnit\Runner\ResultCache\ResultCacheIdTest::a method', ResultCacheId::fromTestClassAndMethodName(self::class, 'a method')]; + + yield ['PHPUnit\Runner\ResultCache\ResultCacheIdTest::testMethod', ResultCacheId::fromTest(self::testMethod())]; + } + + #[DataProvider('provideResultCacheIds')] + public function testResultCacheId(string $expectedString, ResultCacheId $cacheId): void + { + $this->assertSame($expectedString, $cacheId->asString()); + } + + public function testReorderableResultCacheId(): void + { + $reorderable = $this; + $this->assertInstanceOf(Reorderable::class, $reorderable); + + $this->assertSame('PHPUnit\Runner\ResultCache\ResultCacheIdTest::testReorderableResultCacheId', ResultCacheId::fromReorderable($reorderable)->asString()); + } + + public function testPhptResultCacheId(): void + { + $file = 'test.phpt'; + $phptTest = new Phpt($file); + + $this->assertSame('test.phpt', ResultCacheId::fromTest($phptTest)->asString()); + } + + private static function testMethod(): TestMethod + { + return new TestMethod( + self::class, + 'testMethod', + 'TestClass.php', + 1, + new TestDox('', '', ''), + MetadataCollection::fromArray([]), + TestDataCollection::fromArray([]), + ); + } +} diff --git a/tests/unit/Runner/ResultCacheExtensionTest.php b/tests/unit/Runner/ResultCacheExtensionTest.php deleted file mode 100644 index 16a6cde9f32..00000000000 --- a/tests/unit/Runner/ResultCacheExtensionTest.php +++ /dev/null @@ -1,149 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestCaseTest; -use PHPUnit\Framework\TestResult; -use PHPUnit\Framework\TestSuite; -use PHPUnit\TestFixture\EmptyTestCaseTest; -use PHPUnit\TestFixture\Failure; -use PHPUnit\TestFixture\Success; -use PHPUnit\TestFixture\TestError; -use PHPUnit\TestFixture\TestIncomplete; -use PHPUnit\TestFixture\TestRisky; -use PHPUnit\TestFixture\TestSkipped; -use PHPUnit\TestFixture\TestWarning; - -/** - * @group test-reorder - * @small - */ -final class ResultCacheExtensionTest extends TestCase -{ - /** - * @var DefaultTestResultCache - */ - protected $cache; - - /** - * @var ResultCacheExtension - */ - protected $extension; - - /** - * @var TestResult - */ - protected $result; - - protected function setUp(): void - { - $this->cache = new DefaultTestResultCache; - $this->extension = new ResultCacheExtension($this->cache); - - $listener = new TestListenerAdapter; - $listener->add($this->extension); - - $this->result = new TestResult; - $this->result->addListener($listener); - } - - /** - * @testdox Clean up test name $_dataName - * @dataProvider longTestNamesDataprovider - */ - public function testStripsDataproviderParametersFromTestName(string $testName, string $expectedTestName): void - { - $test = new TestCaseTest($testName); - $test->run($this->result); - - $this->assertSame(BaseTestRunner::STATUS_ERROR, $this->cache->getState($expectedTestName)); - } - - public function longTestNamesDataprovider(): array - { - return [ - 'ClassName::testMethod' => [ - 'testSomething', - TestCaseTest::class . '::testSomething', ], - 'ClassName::testMethod and data set number and vardump' => [ - 'testMethod with data set #123 (\'a\', "A", 0, false)', - TestCaseTest::class . '::testMethod with data set #123', ], - 'ClassName::testMethod and data set name and vardump' => [ - 'testMethod with data set "data name" (\'a\', "A\", 0, false)', - TestCaseTest::class . '::testMethod with data set "data name"', ], - ]; - } - - public function testError(): void - { - $test = new TestError('test_name'); - $test->run($this->result); - - $this->assertSame(BaseTestRunner::STATUS_ERROR, $this->cache->getState(TestError::class . '::test_name')); - } - - public function testFailure(): void - { - $test = new Failure('test_name'); - $test->run($this->result); - - $this->assertSame(BaseTestRunner::STATUS_FAILURE, $this->cache->getState(Failure::class . '::test_name')); - } - - public function testSkipped(): void - { - $test = new TestSkipped('test_name'); - $test->run($this->result); - - $this->assertSame(BaseTestRunner::STATUS_SKIPPED, $this->cache->getState(TestSkipped::class . '::test_name')); - } - - public function testIncomplete(): void - { - $test = new TestIncomplete('test_name'); - $test->run($this->result); - - $this->assertSame(BaseTestRunner::STATUS_INCOMPLETE, $this->cache->getState(TestIncomplete::class . '::test_name')); - } - - public function testPassedTestsOnlyCacheTime(): void - { - $test = new Success('test_name'); - $test->run($this->result); - - $this->assertSame(BaseTestRunner::STATUS_UNKNOWN, $this->cache->getState(Success::class . '::test_name')); - } - - public function testWarning(): void - { - $test = new TestWarning('test_name'); - $test->run($this->result); - - $this->assertSame(BaseTestRunner::STATUS_WARNING, $this->cache->getState(TestWarning::class . '::test_name')); - } - - public function testRisky(): void - { - $test = new TestRisky('test_name'); - $test->run($this->result); - - $this->assertSame(BaseTestRunner::STATUS_RISKY, $this->cache->getState(TestRisky::class . '::test_name')); - } - - public function testEmptySuite(): void - { - $suite = new TestSuite; - $suite->addTestSuite(EmptyTestCaseTest::class); - $suite->run($this->result); - - $this->assertSame(BaseTestRunner::STATUS_WARNING, $this->cache->getState('Warning')); - } -} diff --git a/tests/unit/Runner/TestResultCacheTest.php b/tests/unit/Runner/TestResultCacheTest.php deleted file mode 100644 index 2c4cf3d4082..00000000000 --- a/tests/unit/Runner/TestResultCacheTest.php +++ /dev/null @@ -1,96 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -use PHPUnit\Framework\TestCase; -use PHPUnit\Runner\BaseTestRunner; -use PHPUnit\Runner\DefaultTestResultCache; - -/** - * @group test-reorder - * @small - */ -final class TestResultCacheTest extends TestCase -{ - public function testReadsCacheFromProvidedFilename(): void - { - $cacheFile = TEST_FILES_PATH . '../end-to-end/execution-order/_files/MultiDependencyTest_result_cache.txt'; - $cache = new DefaultTestResultCache($cacheFile); - $cache->load(); - - $this->assertSame(BaseTestRunner::STATUS_UNKNOWN, $cache->getState(\MultiDependencyTest::class . '::testOne')); - $this->assertSame(BaseTestRunner::STATUS_SKIPPED, $cache->getState(\MultiDependencyTest::class . '::testFive')); - } - - public function testDoesClearCacheBeforeLoad(): void - { - $cacheFile = TEST_FILES_PATH . '../end-to-end/execution-order/_files/MultiDependencyTest_result_cache.txt'; - $cache = new DefaultTestResultCache($cacheFile); - $cache->setState('someTest', BaseTestRunner::STATUS_FAILURE); - - $this->assertSame(BaseTestRunner::STATUS_UNKNOWN, $cache->getState(\MultiDependencyTest::class . '::testFive')); - - $cache->load(); - - $this->assertSame(BaseTestRunner::STATUS_UNKNOWN, $cache->getState(\MultiDependencyTest::class . '::someTest')); - $this->assertSame(BaseTestRunner::STATUS_SKIPPED, $cache->getState(\MultiDependencyTest::class . '::testFive')); - } - - public function testShouldNotSerializePassedTestsAsDefectButTimeIsStored(): void - { - $cache = new DefaultTestResultCache; - $cache->setState('testOne', BaseTestRunner::STATUS_PASSED); - $cache->setTime('testOne', 123); - - $data = \serialize($cache); - $this->assertSame('C:37:"PHPUnit\Runner\DefaultTestResultCache":64:{a:2:{s:7:"defects";a:0:{}s:5:"times";a:1:{s:7:"testOne";d:123;}}}', $data); - } - - public function testCanPersistCacheToFile(): void - { - // Create a cache with one result and store it - $cacheFile = \tempnam(\sys_get_temp_dir(), 'phpunit_'); - $cache = new DefaultTestResultCache($cacheFile); - $testName = 'test' . \uniqid(); - $cache->setState($testName, BaseTestRunner::STATUS_SKIPPED); - $cache->persist(); - unset($cache); - - // Load the cache we just created - $loadedCache = new DefaultTestResultCache($cacheFile); - $loadedCache->load(); - $this->assertSame(BaseTestRunner::STATUS_SKIPPED, $loadedCache->getState($testName)); - - // Clean up - \unlink($cacheFile); - } - - public function testShouldReturnEmptyCacheWhenFileDoesNotExist(): void - { - $cache = new DefaultTestResultCache('/a/wrong/path/file'); - $cache->load(); - - $this->assertTrue($this->isSerializedEmptyCache(\serialize($cache))); - } - - public function testShouldReturnEmptyCacheFromInvalidFile(): void - { - $cacheFile = \tempnam(\sys_get_temp_dir(), 'phpunit_'); - \file_put_contents($cacheFile, ''); - - $cache = new DefaultTestResultCache($cacheFile); - $cache->load(); - - $this->assertTrue($this->isSerializedEmptyCache(\serialize($cache))); - } - - public function isSerializedEmptyCache(string $data): bool - { - return $data === 'C:37:"PHPUnit\Runner\DefaultTestResultCache":44:{a:2:{s:7:"defects";a:0:{}s:5:"times";a:0:{}}}'; - } -} diff --git a/tests/unit/Runner/TestSuiteSorterTest.php b/tests/unit/Runner/TestSuiteSorterTest.php index ea3d3253d5d..55c7f77a903 100644 --- a/tests/unit/Runner/TestSuiteSorterTest.php +++ b/tests/unit/Runner/TestSuiteSorterTest.php @@ -10,30 +10,25 @@ namespace PHPUnit\Runner; use function mt_srand; -use MultiDependencyTest; -use NotReorderableTest; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestStatus\TestStatus; use PHPUnit\Framework\TestSuite; -use PHPUnit\TestFixture\EmptyTestCaseTest; -use PHPUnit\TestFixture\FailureTest; -use PHPUnit\TestFixture\Success; -use PHPUnit\TestFixture\TestWithDifferentSizes; - -/** - * @testdox Reordering test execution - * @group test-reorder - * @small - */ +use PHPUnit\Runner\ResultCache\DefaultResultCache; +use PHPUnit\Runner\ResultCache\ResultCacheId; +use PHPUnit\TestFixture\FaillingDataProviderTest; +use PHPUnit\TestFixture\MultiDependencyTest; +use ReflectionClass; + +#[CoversClass(TestSuiteSorter::class)] +#[Small] final class TestSuiteSorterTest extends TestCase { - /** - * Constants to improve clarity of @dataprovider. - */ - private const IGNORE_DEPENDENCIES = false; - - private const RESOLVE_DEPENDENCIES = true; - - private const MULTIDEPENDENCYTEST_EXECUTION_ORDER = [ + private const bool IGNORE_DEPENDENCIES = false; + private const bool RESOLVE_DEPENDENCIES = true; + private const array MULTI_DEPENDENCY_TEST_EXECUTION_ORDER = [ MultiDependencyTest::class . '::testOne', MultiDependencyTest::class . '::testTwo', MultiDependencyTest::class . '::testThree', @@ -41,121 +36,7 @@ final class TestSuiteSorterTest extends TestCase MultiDependencyTest::class . '::testFive', ]; - public function testThrowsExceptionWhenUsingInvalidOrderOption(): void - { - $suite = new TestSuite; - $suite->addTestSuite(MultiDependencyTest::class); - $sorter = new TestSuiteSorter; - - $this->expectException(Exception::class); - $this->expectExceptionMessage('$order must be one of TestSuiteSorter::ORDER_[DEFAULT|REVERSED|RANDOMIZED|DURATION|SIZE]'); - $sorter->reorderTestsInSuite($suite, -1, false, TestSuiteSorter::ORDER_DEFAULT); - } - - public function testThrowsExceptionWhenUsingInvalidOrderDefectsOption(): void - { - $suite = new TestSuite; - $suite->addTestSuite(MultiDependencyTest::class); - $sorter = new TestSuiteSorter; - - $this->expectException(Exception::class); - $this->expectExceptionMessage('$orderDefects must be one of TestSuiteSorter::ORDER_DEFAULT, TestSuiteSorter::ORDER_DEFECTS_FIRST'); - $sorter->reorderTestsInSuite($suite, TestSuiteSorter::ORDER_DEFAULT, false, -1); - } - - /** - * @testdox Empty TestSuite not affected (order=$order, resolve=$resolveDependencies, defects=$orderDefects) - * @dataProvider suiteSorterOptionPermutationsProvider - */ - public function testShouldNotAffectEmptyTestSuite(int $order, bool $resolveDependencies, int $orderDefects): void - { - $sorter = new TestSuiteSorter; - $suite = new TestSuite; - - $sorter->reorderTestsInSuite($suite, $order, $resolveDependencies, $orderDefects); - - $this->assertEmpty($suite->tests()); - $this->assertEmpty($sorter->getOriginalExecutionOrder()); - $this->assertEmpty($sorter->getExecutionOrder()); - } - - /** - * @dataProvider commonSorterOptionsProvider - */ - public function testBasicExecutionOrderOptions(int $order, bool $resolveDependencies, array $expectedOrder): void - { - $suite = new TestSuite; - $suite->addTestSuite(MultiDependencyTest::class); - $sorter = new TestSuiteSorter; - - $sorter->reorderTestsInSuite($suite, $order, $resolveDependencies, TestSuiteSorter::ORDER_DEFAULT); - - $this->assertSame(self::MULTIDEPENDENCYTEST_EXECUTION_ORDER, $sorter->getOriginalExecutionOrder()); - $this->assertSame($expectedOrder, $sorter->getExecutionOrder()); - } - - public function testCanSetRandomizationWithASeed(): void - { - $suite = new TestSuite; - $suite->addTestSuite(MultiDependencyTest::class); - $sorter = new TestSuiteSorter; - - mt_srand(54321); - $sorter->reorderTestsInSuite($suite, TestSuiteSorter::ORDER_RANDOMIZED, false, TestSuiteSorter::ORDER_DEFAULT); - - $expectedOrder = [ - MultiDependencyTest::class . '::testTwo', - MultiDependencyTest::class . '::testFour', - MultiDependencyTest::class . '::testFive', - MultiDependencyTest::class . '::testThree', - MultiDependencyTest::class . '::testOne', - ]; - - $this->assertSame($expectedOrder, $sorter->getExecutionOrder()); - } - - public function testCanSetRandomizationWithASeedAndResolveDependencies(): void - { - $suite = new TestSuite; - $suite->addTestSuite(MultiDependencyTest::class); - $sorter = new TestSuiteSorter; - - mt_srand(54321); - $sorter->reorderTestsInSuite($suite, TestSuiteSorter::ORDER_RANDOMIZED, true, TestSuiteSorter::ORDER_DEFAULT); - - $expectedOrder = [ - MultiDependencyTest::class . '::testTwo', - MultiDependencyTest::class . '::testFive', - MultiDependencyTest::class . '::testOne', - MultiDependencyTest::class . '::testThree', - MultiDependencyTest::class . '::testFour', - ]; - - $this->assertSame($expectedOrder, $sorter->getExecutionOrder()); - } - - /** - * @dataProvider orderDurationWithoutCacheProvider - */ - public function testOrderDurationWithoutCache(bool $resolveDependencies, array $expected): void - { - $suite = new TestSuite; - - $suite->addTestSuite(MultiDependencyTest::class); - - $sorter = new TestSuiteSorter; - - $sorter->reorderTestsInSuite( - $suite, - TestSuiteSorter::ORDER_DURATION, - $resolveDependencies, - TestSuiteSorter::ORDER_DEFAULT - ); - - $this->assertSame($expected, $sorter->getExecutionOrder()); - } - - public function orderDurationWithoutCacheProvider(): array + public static function orderDurationWithoutCacheProvider(): array { return [ 'dependency-ignore' => [ @@ -181,34 +62,7 @@ public function orderDurationWithoutCacheProvider(): array ]; } - /** - * @dataProvider orderDurationWithCacheProvider - */ - public function testOrderDurationWithCache(bool $resolveDependencies, array $testTimes, array $expected): void - { - $suite = new TestSuite; - - $suite->addTestSuite(MultiDependencyTest::class); - - $cache = new DefaultTestResultCache; - - foreach ($testTimes as $testName => $time) { - $cache->setTime(MultiDependencyTest::class . '::' . $testName, $time); - } - - $sorter = new TestSuiteSorter($cache); - - $sorter->reorderTestsInSuite( - $suite, - TestSuiteSorter::ORDER_DURATION, - $resolveDependencies, - TestSuiteSorter::ORDER_DEFAULT - ); - - $this->assertSame($expected, $sorter->getExecutionOrder()); - } - - public function orderDurationWithCacheProvider(): array + public static function orderDurationWithCacheProvider(): array { return [ 'duration-same-dependency-ignore' => [ @@ -283,37 +137,16 @@ public function orderDurationWithCacheProvider(): array } /** - * @dataProvider defectsSorterOptionsProvider - */ - public function testSuiteSorterDefectsOptions(int $order, bool $resolveDependencies, array $runState, array $expected): void - { - $suite = new TestSuite; - $suite->addTestSuite(MultiDependencyTest::class); - - $cache = new DefaultTestResultCache; - - foreach ($runState as $testName => $data) { - $cache->setState(MultiDependencyTest::class . '::' . $testName, $data['state']); - $cache->setTime(MultiDependencyTest::class . '::' . $testName, $data['time']); - } - - $sorter = new TestSuiteSorter($cache); - $sorter->reorderTestsInSuite($suite, $order, $resolveDependencies, TestSuiteSorter::ORDER_DEFECTS_FIRST); - - $this->assertSame($expected, $sorter->getExecutionOrder()); - } - - /** - * A @dataprovider for basic execution reordering options based on MultiDependencyTest. + * A data provider for basic execution reordering options based on MultiDependencyTest. * * This class has the following relevant properties: * - * - it has five tests 'testOne' ... 'testFive' - * - 'testThree' @depends on both 'testOne' and 'testTwo' - * - 'testFour' @depends on 'MultiDependencyTest::testThree' to test FQN @depends - * - 'testFive' has no dependencies + * - it has five tests testOne, testTwo, testThree, testFour, testFive + * - testThree depends on testOne and testTwo + * - testFour depends on MultiDependencyTest::testThree + * - testFive has no dependencies */ - public function commonSorterOptionsProvider(): array + public static function commonSorterOptionsProvider(): array { return [ 'default' => [ @@ -370,16 +203,16 @@ public function commonSorterOptionsProvider(): array } /** - * A @dataprovider for testing defects execution reordering options based on MultiDependencyTest. + * A data provider for testing defects execution reordering options based on MultiDependencyTest. * * This class has the following relevant properties: * - * - it has five tests 'testOne' ... 'testFive' - * - 'testThree' @depends on both 'testOne' and 'testTwo' - * - 'testFour' @depends on 'MultiDependencyTest::testThree' to test FQN @depends - * - 'testFive' has no dependencies + * - it has five tests testOne, testTwo, testThree, testFour, testFive + * - testThree depends on testOne and testTwo + * - testFour depends on MultiDependencyTest::testThree + * - testFive has no dependencies */ - public function defectsSorterOptionsProvider(): array + public static function defectsSorterOptionsProvider(): array { return [ // The most simple situation should work as normal @@ -387,11 +220,11 @@ public function defectsSorterOptionsProvider(): array TestSuiteSorter::ORDER_DEFAULT, self::IGNORE_DEPENDENCIES, [ - 'testOne' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testTwo' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testThree' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testFour' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testFive' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], + 'testOne' => ['state' => TestStatus::success(), 'time' => 1], + 'testTwo' => ['state' => TestStatus::success(), 'time' => 1], + 'testThree' => ['state' => TestStatus::success(), 'time' => 1], + 'testFour' => ['state' => TestStatus::success(), 'time' => 1], + 'testFive' => ['state' => TestStatus::success(), 'time' => 1], ], [ MultiDependencyTest::class . '::testOne', @@ -423,11 +256,11 @@ public function defectsSorterOptionsProvider(): array TestSuiteSorter::ORDER_DEFAULT, self::IGNORE_DEPENDENCIES, [ - 'testOne' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testTwo' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testThree' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testFour' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testFive' => ['state' => BaseTestRunner::STATUS_SKIPPED, 'time' => 1], + 'testOne' => ['state' => TestStatus::success(), 'time' => 1], + 'testTwo' => ['state' => TestStatus::success(), 'time' => 1], + 'testThree' => ['state' => TestStatus::success(), 'time' => 1], + 'testFour' => ['state' => TestStatus::success(), 'time' => 1], + 'testFive' => ['state' => TestStatus::skipped(), 'time' => 1], ], [ MultiDependencyTest::class . '::testFive', @@ -443,11 +276,11 @@ public function defectsSorterOptionsProvider(): array TestSuiteSorter::ORDER_DEFAULT, self::IGNORE_DEPENDENCIES, [ - 'testOne' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testTwo' => ['state' => BaseTestRunner::STATUS_SKIPPED, 'time' => 1], - 'testThree' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testFour' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testFive' => ['state' => BaseTestRunner::STATUS_SKIPPED, 'time' => 0], + 'testOne' => ['state' => TestStatus::success(), 'time' => 1], + 'testTwo' => ['state' => TestStatus::skipped(), 'time' => 1], + 'testThree' => ['state' => TestStatus::success(), 'time' => 1], + 'testFour' => ['state' => TestStatus::success(), 'time' => 1], + 'testFive' => ['state' => TestStatus::skipped(), 'time' => 0], ], [ MultiDependencyTest::class . '::testFive', @@ -463,11 +296,11 @@ public function defectsSorterOptionsProvider(): array TestSuiteSorter::ORDER_DEFAULT, self::IGNORE_DEPENDENCIES, [ - 'testOne' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testTwo' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testThree' => ['state' => BaseTestRunner::STATUS_SKIPPED, 'time' => 1], - 'testFour' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testFive' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], + 'testOne' => ['state' => TestStatus::success(), 'time' => 1], + 'testTwo' => ['state' => TestStatus::success(), 'time' => 1], + 'testThree' => ['state' => TestStatus::skipped(), 'time' => 1], + 'testFour' => ['state' => TestStatus::success(), 'time' => 1], + 'testFive' => ['state' => TestStatus::success(), 'time' => 1], ], [ MultiDependencyTest::class . '::testThree', @@ -483,11 +316,11 @@ public function defectsSorterOptionsProvider(): array TestSuiteSorter::ORDER_DEFAULT, self::RESOLVE_DEPENDENCIES, [ - 'testOne' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testTwo' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testThree' => ['state' => BaseTestRunner::STATUS_SKIPPED, 'time' => 1], - 'testFour' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testFive' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], + 'testOne' => ['state' => TestStatus::success(), 'time' => 1], + 'testTwo' => ['state' => TestStatus::success(), 'time' => 1], + 'testThree' => ['state' => TestStatus::skipped(), 'time' => 1], + 'testFour' => ['state' => TestStatus::success(), 'time' => 1], + 'testFive' => ['state' => TestStatus::success(), 'time' => 1], ], [ MultiDependencyTest::class . '::testOne', @@ -503,11 +336,11 @@ public function defectsSorterOptionsProvider(): array TestSuiteSorter::ORDER_REVERSED, self::IGNORE_DEPENDENCIES, [ - 'testOne' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testTwo' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testThree' => ['state' => BaseTestRunner::STATUS_SKIPPED, 'time' => 1], - 'testFour' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testFive' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], + 'testOne' => ['state' => TestStatus::success(), 'time' => 1], + 'testTwo' => ['state' => TestStatus::success(), 'time' => 1], + 'testThree' => ['state' => TestStatus::skipped(), 'time' => 1], + 'testFour' => ['state' => TestStatus::success(), 'time' => 1], + 'testFive' => ['state' => TestStatus::success(), 'time' => 1], ], [ MultiDependencyTest::class . '::testThree', @@ -524,11 +357,11 @@ public function defectsSorterOptionsProvider(): array TestSuiteSorter::ORDER_DEFAULT, self::RESOLVE_DEPENDENCIES, [ - 'testOne' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testTwo' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testThree' => ['state' => BaseTestRunner::STATUS_SKIPPED, 'time' => 0], - 'testFour' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testFive' => ['state' => BaseTestRunner::STATUS_SKIPPED, 'time' => 1], + 'testOne' => ['state' => TestStatus::success(), 'time' => 1], + 'testTwo' => ['state' => TestStatus::success(), 'time' => 1], + 'testThree' => ['state' => TestStatus::skipped(), 'time' => 0], + 'testFour' => ['state' => TestStatus::success(), 'time' => 1], + 'testFive' => ['state' => TestStatus::skipped(), 'time' => 1], ], [ MultiDependencyTest::class . '::testFive', @@ -548,9 +381,9 @@ public function defectsSorterOptionsProvider(): array TestSuiteSorter::ORDER_REVERSED, self::RESOLVE_DEPENDENCIES, [ - 'testOne' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testTwo' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testThree' => ['state' => BaseTestRunner::STATUS_SKIPPED, 'time' => 1], + 'testOne' => ['state' => TestStatus::success(), 'time' => 1], + 'testTwo' => ['state' => TestStatus::success(), 'time' => 1], + 'testThree' => ['state' => TestStatus::skipped(), 'time' => 1], ], [ MultiDependencyTest::class . '::testFive', @@ -568,11 +401,11 @@ public function defectsSorterOptionsProvider(): array TestSuiteSorter::ORDER_DEFAULT, self::RESOLVE_DEPENDENCIES, [ - 'testOne' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testTwo' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testThree' => ['state' => BaseTestRunner::STATUS_PASSED, 'time' => 1], - 'testFour' => ['state' => BaseTestRunner::STATUS_FAILURE, 'time' => 1], - 'testFive' => ['state' => BaseTestRunner::STATUS_FAILURE, 'time' => 1], + 'testOne' => ['state' => TestStatus::success(), 'time' => 1], + 'testTwo' => ['state' => TestStatus::success(), 'time' => 1], + 'testThree' => ['state' => TestStatus::success(), 'time' => 1], + 'testFour' => ['state' => TestStatus::failure(), 'time' => 1], + 'testFive' => ['state' => TestStatus::failure(), 'time' => 1], ], [ MultiDependencyTest::class . '::testFive', @@ -585,23 +418,7 @@ public function defectsSorterOptionsProvider(): array ]; } - /** - * @see https://github.com/lstrojny/phpunit-clever-and-smart/issues/38 - */ - public function testCanHandleSuiteWithEmptyTestCase(): void - { - $suite = new TestSuite; - $suite->addTestSuite(EmptyTestCaseTest::class); - - $sorter = new TestSuiteSorter; - - $sorter->reorderTestsInSuite($suite, TestSuiteSorter::ORDER_DEFAULT, false, TestSuiteSorter::ORDER_DEFAULT); - - $this->assertSame(EmptyTestCaseTest::class, $suite->tests()[0]->getName()); - $this->assertSame('No tests found in class "PHPUnit\TestFixture\EmptyTestCaseTest".', $suite->tests()[0]->tests()[0]->getMessage()); - } - - public function suiteSorterOptionPermutationsProvider(): array + public static function suiteSorterOptionPermutationsProvider(): array { $orderValues = [TestSuiteSorter::ORDER_DEFAULT, TestSuiteSorter::ORDER_REVERSED, TestSuiteSorter::ORDER_RANDOMIZED]; $resolveValues = [false, true]; @@ -620,37 +437,277 @@ public function suiteSorterOptionPermutationsProvider(): array return $data; } - public function testOrderBySize(): void + /** + * A data provider for testing defects execution reordering options based on FaillingDataProviderTest. + */ + public static function defectsSorterWithDataProviderProvider(): array { - $suite = new TestSuite; - $suite->addTestSuite(TestWithDifferentSizes::class); + return [ + // The most simple situation should work as normal + 'default, no defects' => [ + TestSuiteSorter::ORDER_DEFAULT, + self::IGNORE_DEPENDENCIES, + [ + 'testOne' => ['state' => TestStatus::success(), 'time' => 1], + 'testWithProvider with data set "@good1"' => ['state' => TestStatus::success(), 'time' => 1], + 'testWithProvider with data set "@good2"' => ['state' => TestStatus::success(), 'time' => 1], + 'testWithProvider with data set "@good3"' => ['state' => TestStatus::success(), 'time' => 1], + 'testWithProvider with data set "@fail1"' => ['state' => TestStatus::success(), 'time' => 1], + 'testWithProvider with data set "@fail2"' => ['state' => TestStatus::success(), 'time' => 1], + ], + [ + FaillingDataProviderTest::class . '::testWithProvider with data set "good1"', + FaillingDataProviderTest::class . '::testWithProvider with data set "good2"', + FaillingDataProviderTest::class . '::testWithProvider with data set "fail1"', + FaillingDataProviderTest::class . '::testWithProvider with data set "good3"', + FaillingDataProviderTest::class . '::testWithProvider with data set "fail2"', + FaillingDataProviderTest::class . '::testOne', + ], + ], + + // Running with an empty cache should not spook the TestSuiteSorter + 'default, empty result cache' => [ + TestSuiteSorter::ORDER_DEFAULT, + self::IGNORE_DEPENDENCIES, + [ + // empty result cache + ], + [ + FaillingDataProviderTest::class . '::testWithProvider with data set "good1"', + FaillingDataProviderTest::class . '::testWithProvider with data set "good2"', + FaillingDataProviderTest::class . '::testWithProvider with data set "fail1"', + FaillingDataProviderTest::class . '::testWithProvider with data set "good3"', + FaillingDataProviderTest::class . '::testWithProvider with data set "fail2"', + FaillingDataProviderTest::class . '::testOne', + ], + ], + + 'default, defects' => [ + TestSuiteSorter::ORDER_DEFAULT, + self::IGNORE_DEPENDENCIES, + [ + 'testOne' => ['state' => TestStatus::success(), 'time' => 1], + 'testWithProvider with data set "good1"' => ['state' => TestStatus::success(), 'time' => 1], + 'testWithProvider with data set "good2"' => ['state' => TestStatus::success(), 'time' => 1], + 'testWithProvider with data set "good3"' => ['state' => TestStatus::success(), 'time' => 1], + 'testWithProvider with data set "fail1"' => ['state' => TestStatus::error(), 'time' => 1], + 'testWithProvider with data set "fail2"' => ['state' => TestStatus::error(), 'time' => 1], + ], + [ + FaillingDataProviderTest::class . '::testWithProvider with data set "fail1"', + FaillingDataProviderTest::class . '::testWithProvider with data set "fail2"', + FaillingDataProviderTest::class . '::testWithProvider with data set "good1"', + FaillingDataProviderTest::class . '::testWithProvider with data set "good2"', + FaillingDataProviderTest::class . '::testWithProvider with data set "good3"', + FaillingDataProviderTest::class . '::testOne', + ], + ], + ]; + } + + public function testThrowsExceptionWhenUsingInvalidOrderOption(): void + { + $suite = TestSuite::empty('test suite name'); + $suite->addTestSuite(new ReflectionClass(MultiDependencyTest::class)); + $sorter = new TestSuiteSorter; + + $this->expectException(InvalidOrderException::class); + + $sorter->reorderTestsInSuite($suite, -1, false, TestSuiteSorter::ORDER_DEFAULT); + } + + public function testThrowsExceptionWhenUsingInvalidOrderDefectsOption(): void + { + $suite = TestSuite::empty('test suite name'); + $suite->addTestSuite(new ReflectionClass(MultiDependencyTest::class)); + $sorter = new TestSuiteSorter; + + $this->expectException(InvalidOrderException::class); + + $sorter->reorderTestsInSuite($suite, TestSuiteSorter::ORDER_DEFAULT, false, -1); + } + + #[DataProvider('suiteSorterOptionPermutationsProvider')] + public function testShouldNotAffectEmptyTestSuite(int $order, bool $resolveDependencies, int $orderDefects): void + { + $sorter = new TestSuiteSorter; + $suite = TestSuite::empty('test suite name'); + + $sorter->reorderTestsInSuite($suite, $order, $resolveDependencies, $orderDefects); + + $this->assertEmpty($suite->tests()); + $this->assertEmpty($sorter->getOriginalExecutionOrder()); + $this->assertEmpty($sorter->getExecutionOrder()); + } + + #[DataProvider('commonSorterOptionsProvider')] + public function testBasicExecutionOrderOptions(int $order, bool $resolveDependencies, array $expectedOrder): void + { + $suite = TestSuite::empty('test suite name'); + $suite->addTestSuite(new ReflectionClass(MultiDependencyTest::class)); + $sorter = new TestSuiteSorter; + + $sorter->reorderTestsInSuite($suite, $order, $resolveDependencies, TestSuiteSorter::ORDER_DEFAULT); + + $this->assertSame(self::MULTI_DEPENDENCY_TEST_EXECUTION_ORDER, $sorter->getOriginalExecutionOrder()); + $this->assertSame($expectedOrder, $sorter->getExecutionOrder()); + } + + public function testCanSetRandomizationWithASeed(): void + { + $suite = TestSuite::empty('test suite name'); + $suite->addTestSuite(new ReflectionClass(MultiDependencyTest::class)); + $sorter = new TestSuiteSorter; + + mt_srand(54321); + $sorter->reorderTestsInSuite($suite, TestSuiteSorter::ORDER_RANDOMIZED, false, TestSuiteSorter::ORDER_DEFAULT); + + $expectedOrder = [ + MultiDependencyTest::class . '::testTwo', + MultiDependencyTest::class . '::testFour', + MultiDependencyTest::class . '::testFive', + MultiDependencyTest::class . '::testThree', + MultiDependencyTest::class . '::testOne', + ]; + + $this->assertSame($expectedOrder, $sorter->getExecutionOrder()); + } + + public function testCanSetRandomizationWithDefectsFirst(): void + { + $cache = new DefaultResultCache; + + $runState = [ + 'testOne' => ['state' => TestStatus::success(), 'time' => 1], + 'testTwo' => ['state' => TestStatus::success(), 'time' => 1], + 'testThree' => ['state' => TestStatus::error(), 'time' => 1], + 'testFour' => ['state' => TestStatus::success(), 'time' => 1], + 'testFive' => ['state' => TestStatus::error(), 'time' => 1], + ]; + + foreach ($runState as $testName => $data) { + $cache->setStatus(ResultCacheId::fromTestClassAndMethodName(MultiDependencyTest::class, $testName), $data['state']); + $cache->setTime(ResultCacheId::fromTestClassAndMethodName(MultiDependencyTest::class, $testName), $data['time']); + } + + $sorter = new TestSuiteSorter($cache); + + $suite = TestSuite::empty('test suite name'); + $suite->addTestSuite(new ReflectionClass(MultiDependencyTest::class)); + + mt_srand(54321); + $sorter->reorderTestsInSuite($suite, TestSuiteSorter::ORDER_RANDOMIZED, false, TestSuiteSorter::ORDER_DEFECTS_FIRST); + + $expectedOrder = [ + MultiDependencyTest::class . '::testFive', + MultiDependencyTest::class . '::testThree', + MultiDependencyTest::class . '::testTwo', + MultiDependencyTest::class . '::testFour', + MultiDependencyTest::class . '::testOne', + ]; + + $this->assertSame($expectedOrder, $sorter->getExecutionOrder()); + } + + public function testCanSetRandomizationWithASeedAndResolveDependencies(): void + { + $suite = TestSuite::empty('test suite name'); + $suite->addTestSuite(new ReflectionClass(MultiDependencyTest::class)); $sorter = new TestSuiteSorter; - $sorter->reorderTestsInSuite($suite, TestSuiteSorter::ORDER_SIZE, true, TestSuiteSorter::ORDER_DEFAULT); + mt_srand(54321); + $sorter->reorderTestsInSuite($suite, TestSuiteSorter::ORDER_RANDOMIZED, true, TestSuiteSorter::ORDER_DEFAULT); $expectedOrder = [ - TestWithDifferentSizes::class . '::testDataProviderWithSizeSmall with data set #0', - TestWithDifferentSizes::class . '::testDataProviderWithSizeSmall with data set #1', - TestWithDifferentSizes::class . '::testDataProviderWithSizeMedium with data set #0', - TestWithDifferentSizes::class . '::testDataProviderWithSizeMedium with data set #1', - TestWithDifferentSizes::class . '::testWithSizeMedium', - TestWithDifferentSizes::class . '::testWithSizeLarge', - TestWithDifferentSizes::class . '::testWithSizeSmall', - TestWithDifferentSizes::class . '::testWithSizeUnknown', + MultiDependencyTest::class . '::testTwo', + MultiDependencyTest::class . '::testFive', + MultiDependencyTest::class . '::testOne', + MultiDependencyTest::class . '::testThree', + MultiDependencyTest::class . '::testFour', ]; $this->assertSame($expectedOrder, $sorter->getExecutionOrder()); } - public function testSorterQuietlyIgnoresNonReorderable(): void + #[DataProvider('orderDurationWithoutCacheProvider')] + public function testOrderDurationWithoutCache(bool $resolveDependencies, array $expected): void { - $suite = new TestSuite; - $testCollection = [new Success, new NotReorderableTest, new FailureTest]; - $suite->setTests($testCollection); + $suite = TestSuite::empty('test suite name'); + + $suite->addTestSuite(new ReflectionClass(MultiDependencyTest::class)); $sorter = new TestSuiteSorter; - $sorter->reorderTestsInSuite($suite, TestSuiteSorter::ORDER_DURATION, true, TestSuiteSorter::ORDER_DEFECTS_FIRST); - $this->assertEquals($testCollection, $suite->tests()); + $sorter->reorderTestsInSuite( + $suite, + TestSuiteSorter::ORDER_DURATION, + $resolveDependencies, + TestSuiteSorter::ORDER_DEFAULT, + ); + + $this->assertSame($expected, $sorter->getExecutionOrder()); + } + + #[DataProvider('orderDurationWithCacheProvider')] + public function testOrderDurationWithCache(bool $resolveDependencies, array $testTimes, array $expected): void + { + $suite = TestSuite::empty('test suite name'); + + $suite->addTestSuite(new ReflectionClass(MultiDependencyTest::class)); + + $cache = new DefaultResultCache; + + foreach ($testTimes as $testName => $time) { + $cache->setTime(ResultCacheId::fromTestClassAndMethodName(MultiDependencyTest::class, $testName), $time); + } + + $sorter = new TestSuiteSorter($cache); + + $sorter->reorderTestsInSuite( + $suite, + TestSuiteSorter::ORDER_DURATION, + $resolveDependencies, + TestSuiteSorter::ORDER_DEFAULT, + ); + + $this->assertSame($expected, $sorter->getExecutionOrder()); + } + + #[DataProvider('defectsSorterOptionsProvider')] + public function testSuiteSorterDefectsOptions(int $order, bool $resolveDependencies, array $runState, array $expected): void + { + $suite = TestSuite::empty('test suite name'); + $suite->addTestSuite(new ReflectionClass(MultiDependencyTest::class)); + + $cache = new DefaultResultCache; + + foreach ($runState as $testName => $data) { + $cache->setStatus(ResultCacheId::fromTestClassAndMethodName(MultiDependencyTest::class, $testName), $data['state']); + $cache->setTime(ResultCacheId::fromTestClassAndMethodName(MultiDependencyTest::class, $testName), $data['time']); + } + + $sorter = new TestSuiteSorter($cache); + $sorter->reorderTestsInSuite($suite, $order, $resolveDependencies, TestSuiteSorter::ORDER_DEFECTS_FIRST); + + $this->assertSame($expected, $sorter->getExecutionOrder()); + } + + #[DataProvider('defectsSorterWithDataProviderProvider')] + public function testSuiteSorterDefectsWithDataProviderTest(int $order, bool $resolveDependencies, array $runState, array $expected): void + { + $suite = TestSuite::empty('test suite name'); + $suite->addTestSuite(new ReflectionClass(FaillingDataProviderTest::class)); + + $cache = new DefaultResultCache; + + foreach ($runState as $testName => $data) { + $cache->setStatus(ResultCacheId::fromTestClassAndMethodName(FaillingDataProviderTest::class, $testName), $data['state']); + $cache->setTime(ResultCacheId::fromTestClassAndMethodName(FaillingDataProviderTest::class, $testName), $data['time']); + } + + $sorter = new TestSuiteSorter($cache); + $sorter->reorderTestsInSuite($suite, $order, $resolveDependencies, TestSuiteSorter::ORDER_DEFECTS_FIRST); + + $this->assertSame($expected, $sorter->getExecutionOrder()); } } diff --git a/tests/unit/TextUI/AbstractSouceFilterTestCase.php b/tests/unit/TextUI/AbstractSouceFilterTestCase.php new file mode 100644 index 00000000000..accc20b1a64 --- /dev/null +++ b/tests/unit/TextUI/AbstractSouceFilterTestCase.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use const DIRECTORY_SEPARATOR; +use function ltrim; +use function realpath; +use function str_replace; +use PHPUnit\Framework\TestCase; + +abstract class AbstractSouceFilterTestCase extends TestCase +{ + protected static function createSource( + ?FilterDirectoryCollection $includeDirectories = null, + ?FilterDirectoryCollection $excludeDirectories = null, + ?FileCollection $includeFiles = null, + ?FileCollection $excludeFiles = null, + ): Source { + return new Source( + null, + false, + $includeDirectories ?? FilterDirectoryCollection::fromArray([]), + $includeFiles ?? FileCollection::fromArray([]), + $excludeDirectories ?? FilterDirectoryCollection::fromArray([]), + $excludeFiles ?? FileCollection::fromArray([]), + false, + false, + false, + false, + false, + false, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + } + + protected static function fixturePath(?string $subPath = null): string + { + $path = realpath(__DIR__ . '/../..') . '/_files/source-filter'; + + if ($subPath !== null) { + $path = $path . '/' . ltrim($subPath, '/'); + } + + return str_replace('/', DIRECTORY_SEPARATOR, $path); + } +} diff --git a/tests/unit/TextUI/Command/Commands/AtLeastVersionCommandTest.php b/tests/unit/TextUI/Command/Commands/AtLeastVersionCommandTest.php new file mode 100644 index 00000000000..0d93287c93f --- /dev/null +++ b/tests/unit/TextUI/Command/Commands/AtLeastVersionCommandTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Command; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(AtLeastVersionCommand::class)] +#[Small] +final class AtLeastVersionCommandTest extends TestCase +{ + public function testSucceedsWhenRequirementIsMet(): void + { + $command = new AtLeastVersionCommand('10'); + + $result = $command->execute(); + + $this->assertSame('', $result->output()); + $this->assertSame(0, $result->shellExitCode()); + } + + public function testFailsWhenRequirementIsNotMet(): void + { + $command = new AtLeastVersionCommand('100'); + + $result = $command->execute(); + + $this->assertSame('', $result->output()); + $this->assertSame(1, $result->shellExitCode()); + } +} diff --git a/tests/unit/TextUI/Command/Commands/VersionCheckCommandTest.php b/tests/unit/TextUI/Command/Commands/VersionCheckCommandTest.php new file mode 100644 index 00000000000..7b3936a7b62 --- /dev/null +++ b/tests/unit/TextUI/Command/Commands/VersionCheckCommandTest.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Command; + +use const PHP_EOL; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\MockObject\Stub; +use PHPUnit\Framework\TestCase; +use PHPUnit\Util\Http\Downloader; + +#[CoversClass(VersionCheckCommand::class)] +#[Small] +final class VersionCheckCommandTest extends TestCase +{ + /** + * @return non-empty-list + */ + public static function provider(): array + { + return [ + [ + 'You are using the latest version of PHPUnit.' . PHP_EOL, + Result::SUCCESS, + 10, + '10.5.0', + '10.5.0', + '10.5.0', + ], + [ + 'You are not using the latest version of PHPUnit.' . PHP_EOL . + 'The latest version compatible with PHPUnit 10.5.0 is PHPUnit 10.5.1.' . PHP_EOL . + 'The latest version is PHPUnit 10.5.1.' . PHP_EOL, + Result::FAILURE, + 10, + '10.5.0', + '10.5.1', + '10.5.1', + ], + [ + 'You are not using the latest version of PHPUnit.' . PHP_EOL . + 'The latest version compatible with PHPUnit 10.5.0 is PHPUnit 10.5.1.' . PHP_EOL . + 'The latest version is PHPUnit 11.0.0.' . PHP_EOL, + Result::FAILURE, + 10, + '10.5.0', + '11.0.0', + '10.5.1', + ], + ]; + } + + /** + * @param non-empty-string $expectedMessage + * @param non-negative-int $expectedShellExitCode + * @param positive-int $majorVersionNumber + * @param non-empty-string $versionId + * @param non-empty-string $latestVersion + * @param non-empty-string $latestCompatibleVersion + */ + #[DataProvider('provider')] + public function testChecksVersion(string $expectedMessage, int $expectedShellExitCode, int $majorVersionNumber, string $versionId, string $latestVersion, string $latestCompatibleVersion): void + { + $command = new VersionCheckCommand( + $this->downloader($latestVersion, $latestCompatibleVersion), + $majorVersionNumber, + $versionId, + ); + + $result = $command->execute(); + + $this->assertSame($expectedMessage, $result->output()); + $this->assertSame($expectedShellExitCode, $result->shellExitCode()); + } + + private function downloader(string $latestVersion, string $latestCompatibleVersion): Downloader&Stub + { + $downloader = $this->createStub(Downloader::class); + + $downloader + ->method('download') + ->willReturn($latestVersion, $latestCompatibleVersion); + + return $downloader; + } +} diff --git a/tests/unit/TextUI/Configuration/Cli/BuilderTest.php b/tests/unit/TextUI/Configuration/Cli/BuilderTest.php new file mode 100644 index 00000000000..303ebe8e40b --- /dev/null +++ b/tests/unit/TextUI/Configuration/Cli/BuilderTest.php @@ -0,0 +1,2693 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\CliArguments; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; +use PHPUnit\Runner\TestSuiteSorter; + +#[CoversClass(Builder::class)] +#[CoversClass(Configuration::class)] +#[Small] +#[TestDox('CLI Options Parser')] +final class BuilderTest extends TestCase +{ + #[TestDox('argument')] + public function testArguments(): void + { + $configuration = (new Builder)->fromParameters(['command', 'argument']); + + $this->assertSame(['argument'], $configuration->arguments()); + } + + #[TestDox('--all')] + public function testAll(): void + { + $configuration = (new Builder)->fromParameters(['--all']); + + $this->assertTrue($configuration->hasAll()); + $this->assertTrue($configuration->all()); + } + + public function testAllMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasAll()); + + $this->expectException(Exception::class); + + $configuration->all(); + } + + #[TestDox('--colors')] + public function testColorsImplicitAuto(): void + { + $configuration = (new Builder)->fromParameters(['--colors']); + + $this->assertTrue($configuration->hasColors()); + $this->assertSame('auto', $configuration->colors()); + } + + #[TestDox('--colors=auto')] + public function testColorsExplicitAuto(): void + { + $configuration = (new Builder)->fromParameters(['--colors=auto']); + + $this->assertTrue($configuration->hasColors()); + $this->assertSame('auto', $configuration->colors()); + } + + #[TestDox('--colors=always')] + public function testColorsAlways(): void + { + $configuration = (new Builder)->fromParameters(['--colors=always']); + + $this->assertTrue($configuration->hasColors()); + $this->assertSame('always', $configuration->colors()); + } + + #[TestDox('--colors=never')] + public function testColorsNever(): void + { + $configuration = (new Builder)->fromParameters(['--colors=never']); + + $this->assertTrue($configuration->hasColors()); + $this->assertSame('never', $configuration->colors()); + } + + public function testColorsMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasColors()); + + $this->expectException(Exception::class); + + $configuration->colors(); + } + + #[TestDox('--bootstrap script.php')] + public function testBootstrap(): void + { + $configuration = (new Builder)->fromParameters(['--bootstrap', 'script.php']); + + $this->assertTrue($configuration->hasBootstrap()); + $this->assertSame('script.php', $configuration->bootstrap()); + } + + public function testBootstrapMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasBootstrap()); + + $this->expectException(Exception::class); + + $configuration->bootstrap(); + } + + #[TestDox('--cache-directory directory')] + public function testCacheDirectory(): void + { + $configuration = (new Builder)->fromParameters(['--cache-directory', 'directory']); + + $this->assertTrue($configuration->hasCacheDirectory()); + $this->assertSame('directory', $configuration->cacheDirectory()); + } + + public function testCacheDirectoryMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasCacheDirectory()); + + $this->expectException(Exception::class); + + $configuration->cacheDirectory(); + } + + #[TestDox('--cache-result')] + public function testCacheResult(): void + { + $configuration = (new Builder)->fromParameters(['--cache-result']); + + $this->assertTrue($configuration->hasCacheResult()); + $this->assertTrue($configuration->cacheResult()); + } + + #[TestDox('--do-not-cache-result')] + public function testDoNotCacheResult(): void + { + $configuration = (new Builder)->fromParameters(['--do-not-cache-result']); + + $this->assertTrue($configuration->hasCacheResult()); + $this->assertFalse($configuration->cacheResult()); + } + + public function testCacheResultMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasCacheResult()); + + $this->expectException(Exception::class); + + $configuration->cacheResult(); + } + + #[TestDox('--columns ')] + public function testColumnsNumber(): void + { + $configuration = (new Builder)->fromParameters(['--columns', '100']); + + $this->assertTrue($configuration->hasColumns()); + $this->assertSame(100, $configuration->columns()); + } + + #[TestDox('--columns max')] + public function testColumnsMax(): void + { + $configuration = (new Builder)->fromParameters(['--columns', 'max']); + + $this->assertTrue($configuration->hasColumns()); + $this->assertSame('max', $configuration->columns()); + } + + public function testColumnsMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasColumns()); + + $this->expectException(Exception::class); + + $configuration->columns(); + } + + #[TestDox('-c file')] + public function testConfigurationShort(): void + { + $configuration = (new Builder)->fromParameters(['-c', 'file']); + + $this->assertTrue($configuration->hasConfigurationFile()); + $this->assertSame('file', $configuration->configurationFile()); + } + + #[TestDox('--configuration file')] + public function testConfiguration(): void + { + $configuration = (new Builder)->fromParameters(['--configuration', 'file']); + + $this->assertTrue($configuration->hasConfigurationFile()); + $this->assertSame('file', $configuration->configurationFile()); + } + + public function testConfigurationMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasConfigurationFile()); + + $this->expectException(Exception::class); + + $configuration->configurationFile(); + } + + #[TestDox('--warm-coverage-cache')] + public function testWarmCoverageCache(): void + { + $configuration = (new Builder)->fromParameters(['--warm-coverage-cache']); + + $this->assertTrue($configuration->warmCoverageCache()); + } + + public function testWarmCoverageCacheMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->warmCoverageCache()); + } + + #[TestDox('--coverage-clover file')] + public function testCoverageClover(): void + { + $configuration = (new Builder)->fromParameters(['--coverage-clover', 'file']); + + $this->assertTrue($configuration->hasCoverageClover()); + $this->assertSame('file', $configuration->coverageClover()); + } + + public function testCoverageCloverMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasCoverageClover()); + + $this->expectException(Exception::class); + + $configuration->coverageClover(); + } + + #[TestDox('--coverage-cobertura file')] + public function testCoverageCobertura(): void + { + $configuration = (new Builder)->fromParameters(['--coverage-cobertura', 'file']); + + $this->assertTrue($configuration->hasCoverageCobertura()); + $this->assertSame('file', $configuration->coverageCobertura()); + } + + public function testCoverageCoberturaMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasCoverageCobertura()); + + $this->expectException(Exception::class); + + $configuration->coverageCobertura(); + } + + #[TestDox('--coverage-crap4j file')] + public function testCoverageCrap4j(): void + { + $configuration = (new Builder)->fromParameters(['--coverage-crap4j', 'file']); + + $this->assertTrue($configuration->hasCoverageCrap4J()); + $this->assertSame('file', $configuration->coverageCrap4J()); + } + + public function testCoverageCrap4jMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasCoverageCrap4J()); + + $this->expectException(Exception::class); + + $configuration->coverageCrap4J(); + } + + #[TestDox('--coverage-html directory')] + public function testCoverageHtml(): void + { + $configuration = (new Builder)->fromParameters(['--coverage-html', 'directory']); + + $this->assertTrue($configuration->hasCoverageHtml()); + $this->assertSame('directory', $configuration->coverageHtml()); + } + + public function testCoverageHtmlMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasCoverageHtml()); + + $this->expectException(Exception::class); + + $configuration->coverageHtml(); + } + + #[TestDox('--coverage-openclover file')] + public function testCoverageOpenClover(): void + { + $configuration = (new Builder)->fromParameters(['--coverage-openclover', 'file']); + + $this->assertTrue($configuration->hasCoverageOpenClover()); + $this->assertSame('file', $configuration->coverageOpenClover()); + } + + public function testCoverageOpenCloverMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasCoverageOpenClover()); + + $this->expectException(Exception::class); + + $configuration->coverageOpenClover(); + } + + #[TestDox('--coverage-php file')] + public function testCoveragePhp(): void + { + $configuration = (new Builder)->fromParameters(['--coverage-php', 'file']); + + $this->assertTrue($configuration->hasCoveragePhp()); + $this->assertSame('file', $configuration->coveragePhp()); + } + + public function testCoveragePhpMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasCoveragePhp()); + + $this->expectException(Exception::class); + + $configuration->coveragePhp(); + } + + #[TestDox('--coverage-text')] + public function testCoverageText(): void + { + $configuration = (new Builder)->fromParameters(['--coverage-text']); + + $this->assertTrue($configuration->hasCoverageText()); + $this->assertSame('php://stdout', $configuration->coverageText()); + } + + #[TestDox('--coverage-text=file')] + public function testCoverageTextFile(): void + { + $configuration = (new Builder)->fromParameters(['--coverage-text=file']); + + $this->assertTrue($configuration->hasCoverageText()); + $this->assertSame('file', $configuration->coverageText()); + } + + public function testCoverageTextMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasCoverageText()); + + $this->expectException(Exception::class); + + $configuration->coverageText(); + } + + #[TestDox('--only-summary-for-coverage-text')] + public function testOnlySummaryForCoverageText(): void + { + $configuration = (new Builder)->fromParameters(['--only-summary-for-coverage-text']); + + $this->assertTrue($configuration->hasCoverageTextShowOnlySummary()); + $this->assertTrue($configuration->coverageTextShowOnlySummary()); + } + + public function testOnlySummaryForCoverageTextMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasCoverageTextShowOnlySummary()); + + $this->expectException(Exception::class); + + $configuration->coverageTextShowOnlySummary(); + } + + #[TestDox('--show-uncovered-for-coverage-text')] + public function testShowUncoveredForCoverageText(): void + { + $configuration = (new Builder)->fromParameters(['--show-uncovered-for-coverage-text']); + + $this->assertTrue($configuration->hasCoverageTextShowUncoveredFiles()); + $this->assertTrue($configuration->coverageTextShowUncoveredFiles()); + } + + public function testShowUncoveredForCoverageTextMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasCoverageTextShowUncoveredFiles()); + + $this->expectException(Exception::class); + + $configuration->coverageTextShowUncoveredFiles(); + } + + #[TestDox('--coverage-xml directory')] + public function testCoverageXml(): void + { + $configuration = (new Builder)->fromParameters(['--coverage-xml', 'directory']); + + $this->assertTrue($configuration->hasCoverageXml()); + $this->assertSame('directory', $configuration->coverageXml()); + } + + public function testCoverageXmlMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasCoverageXml()); + + $this->expectException(Exception::class); + + $configuration->coverageXml(); + } + + #[TestDox('--path-coverage')] + public function testPathCoverage(): void + { + $configuration = (new Builder)->fromParameters(['--path-coverage']); + + $this->assertTrue($configuration->hasPathCoverage()); + $this->assertTrue($configuration->pathCoverage()); + } + + public function testPathCoverageMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasPathCoverage()); + + $this->expectException(Exception::class); + + $configuration->pathCoverage(); + } + + #[TestDox('-d foo=bar')] + public function testIniSetting(): void + { + $configuration = (new Builder)->fromParameters(['-d', 'foo=bar']); + + $this->assertTrue($configuration->hasIniSettings()); + $this->assertSame(['foo' => 'bar'], $configuration->iniSettings()); + } + + #[TestDox('-d foo')] + public function testIniSetting2(): void + { + $configuration = (new Builder)->fromParameters(['-d', 'foo']); + + $this->assertTrue($configuration->hasIniSettings()); + $this->assertSame(['foo' => '1'], $configuration->iniSettings()); + } + + public function testIniSettingMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasIniSettings()); + + $this->expectException(Exception::class); + + $configuration->iniSettings(); + } + + #[TestDox('-h')] + public function testHelpShort(): void + { + $configuration = (new Builder)->fromParameters(['-h']); + + $this->assertTrue($configuration->help()); + } + + #[TestDox('--help')] + public function testHelp(): void + { + $configuration = (new Builder)->fromParameters(['--help']); + + $this->assertTrue($configuration->help()); + } + + public function testHelpMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->help()); + } + + #[TestDox('--filter string')] + public function testFilter(): void + { + $configuration = (new Builder)->fromParameters(['--filter', 'string']); + + $this->assertTrue($configuration->hasFilter()); + $this->assertSame('string', $configuration->filter()); + } + + public function testFilterMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasFilter()); + + $this->expectException(Exception::class); + + $configuration->filter(); + } + + #[TestDox('--exclude-filter string')] + public function testExcludeFilter(): void + { + $configuration = (new Builder)->fromParameters(['--exclude-filter', 'string']); + + $this->assertTrue($configuration->hasExcludeFilter()); + $this->assertSame('string', $configuration->excludeFilter()); + } + + public function testExcludeFilterMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasExcludeFilter()); + + $this->expectException(Exception::class); + + $configuration->excludeFilter(); + } + + #[TestDox('--testsuite string')] + public function testTestSuite(): void + { + $configuration = (new Builder)->fromParameters(['--testsuite', 'string']); + + $this->assertTrue($configuration->hasTestSuite()); + $this->assertSame('string', $configuration->testSuite()); + } + + public function testTestSuiteMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasTestSuite()); + + $this->expectException(Exception::class); + + $configuration->testSuite(); + } + + #[TestDox('--exclude-testsuite string')] + public function testExcludeTestSuite(): void + { + $configuration = (new Builder)->fromParameters(['--exclude-testsuite', 'string']); + + $this->assertTrue($configuration->hasExcludedTestSuite()); + $this->assertSame('string', $configuration->excludedTestSuite()); + } + + public function testExcludeTestSuiteMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasExcludedTestSuite()); + + $this->expectException(Exception::class); + + $configuration->excludedTestSuite(); + } + + #[TestDox('--generate-baseline file')] + public function testGenerateBaseline(): void + { + $configuration = (new Builder)->fromParameters(['--generate-baseline', 'file']); + + $this->assertTrue($configuration->hasGenerateBaseline()); + $this->assertStringEndsWith('file', $configuration->generateBaseline()); + } + + #[TestDox('--generate-baseline /path/to/file')] + public function testGenerateBaselineWithPathToFile(): void + { + $configuration = (new Builder)->fromParameters(['--generate-baseline', '/path/to/file']); + + $this->assertTrue($configuration->hasGenerateBaseline()); + $this->assertSame('/path/to/file', $configuration->generateBaseline()); + } + + public function testGenerateBaselineMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasGenerateBaseline()); + + $this->expectException(Exception::class); + + $configuration->generateBaseline(); + } + + #[TestDox('--use-baseline file')] + public function testUseBaseline(): void + { + $configuration = (new Builder)->fromParameters(['--use-baseline', 'file']); + + $this->assertTrue($configuration->hasUseBaseline()); + $this->assertStringEndsWith('file', $configuration->useBaseline()); + } + + #[TestDox('--use-baseline /path/to/file')] + public function testUseBaselineWithPathToFile(): void + { + $configuration = (new Builder)->fromParameters(['--use-baseline', '/path/to/file']); + + $this->assertTrue($configuration->hasUseBaseline()); + $this->assertSame('/path/to/file', $configuration->useBaseline()); + } + + public function testUseBaselineMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasUseBaseline()); + + $this->expectException(Exception::class); + + $configuration->useBaseline(); + } + + #[TestDox('--ignore-baseline')] + public function testIgnoreBaseline(): void + { + $configuration = (new Builder)->fromParameters(['--ignore-baseline']); + + $this->assertTrue($configuration->ignoreBaseline()); + } + + #[TestDox('--generate-configuration')] + public function testGenerateConfiguration(): void + { + $configuration = (new Builder)->fromParameters(['--generate-configuration']); + + $this->assertTrue($configuration->generateConfiguration()); + } + + #[TestDox('--migrate-configuration')] + public function testMigrateConfiguration(): void + { + $configuration = (new Builder)->fromParameters(['--migrate-configuration']); + + $this->assertTrue($configuration->migrateConfiguration()); + } + + #[TestDox('--group string')] + public function testGroup(): void + { + $configuration = (new Builder)->fromParameters(['--group', 'string']); + + $this->assertTrue($configuration->hasGroups()); + $this->assertSame(['string'], $configuration->groups()); + } + + #[TestDox('--group string --group another-string')] + public function testGroups(): void + { + $configuration = (new Builder)->fromParameters(['--group', 'string', '--group', 'another-string']); + + $this->assertTrue($configuration->hasGroups()); + $this->assertSame(['string', 'another-string'], $configuration->groups()); + } + + public function testGroupMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasGroups()); + + $this->expectException(Exception::class); + + $configuration->groups(); + } + + #[TestDox('--exclude-group string')] + public function testExcludeGroup(): void + { + $configuration = (new Builder)->fromParameters(['--exclude-group', 'string']); + + $this->assertTrue($configuration->hasExcludeGroups()); + $this->assertSame(['string'], $configuration->excludeGroups()); + } + + #[TestDox('--exclude-group string --exclude-group another-string')] + public function testExcludeGroups(): void + { + $configuration = (new Builder)->fromParameters(['--exclude-group', 'string', '--exclude-group', 'another-string']); + + $this->assertTrue($configuration->hasExcludeGroups()); + $this->assertSame(['string', 'another-string'], $configuration->excludeGroups()); + } + + public function testExcludeGroupMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasGroups()); + + $this->expectException(Exception::class); + + $configuration->excludeGroups(); + } + + #[TestDox('--covers Foo\\Bar\\Baz')] + public function testCovers(): void + { + $configuration = (new Builder)->fromParameters(['--covers', 'Foo\\Bar\\Baz']); + + $this->assertTrue($configuration->hasTestsCovering()); + $this->assertSame(['foo\\bar\\baz'], $configuration->testsCovering()); + } + + public function testCoversMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasTestsCovering()); + + $this->expectException(Exception::class); + + $configuration->testsCovering(); + } + + #[TestDox('--uses Foo\\Bar\\Baz')] + public function testUses(): void + { + $configuration = (new Builder)->fromParameters(['--uses', 'Foo\\Bar\\Baz']); + + $this->assertTrue($configuration->hasTestsUsing()); + $this->assertSame(['foo\\bar\\baz'], $configuration->testsUsing()); + } + + public function testUsesMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasTestsUsing()); + + $this->expectException(Exception::class); + + $configuration->testsUsing(); + } + + #[TestDox('--requires-php-extension extension')] + public function testRequiresPhpExtension(): void + { + $configuration = (new Builder)->fromParameters(['--requires-php-extension', 'extension']); + + $this->assertTrue($configuration->hasTestsRequiringPhpExtension()); + $this->assertSame(['extension'], $configuration->testsRequiringPhpExtension()); + } + + public function testRequiresPhpExtensionMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasTestsRequiringPhpExtension()); + + $this->expectException(Exception::class); + + $configuration->testsRequiringPhpExtension(); + } + + #[TestDox('--test-suffix string')] + public function testTestSuffix(): void + { + $configuration = (new Builder)->fromParameters(['--test-suffix', 'string']); + + $this->assertTrue($configuration->hasTestSuffixes()); + $this->assertSame(['string'], $configuration->testSuffixes()); + } + + #[TestDox('--test-suffix string --test-suffix another-string')] + public function testTestSuffixes(): void + { + $configuration = (new Builder)->fromParameters(['--test-suffix', 'string', '--test-suffix', 'another-string']); + + $this->assertTrue($configuration->hasTestSuffixes()); + $this->assertSame(['string', 'another-string'], $configuration->testSuffixes()); + } + + public function testTestSuffixMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasTestSuffixes()); + + $this->expectException(Exception::class); + + $configuration->testSuffixes(); + } + + #[TestDox('--include-path string')] + public function testIncludePath(): void + { + $configuration = (new Builder)->fromParameters(['--include-path', 'string']); + + $this->assertTrue($configuration->hasIncludePath()); + $this->assertSame('string', $configuration->includePath()); + } + + public function testIncludePathMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasIncludePath()); + + $this->expectException(Exception::class); + + $configuration->includePath(); + } + + #[TestDox('--list-groups')] + public function testListGroups(): void + { + $configuration = (new Builder)->fromParameters(['--list-groups']); + + $this->assertTrue($configuration->listGroups()); + } + + #[TestDox('--list-suites')] + public function testListSuites(): void + { + $configuration = (new Builder)->fromParameters(['--list-suites']); + + $this->assertTrue($configuration->listSuites()); + } + + #[TestDox('--list-test-files')] + public function testListTestFiles(): void + { + $configuration = (new Builder)->fromParameters(['--list-test-files']); + + $this->assertTrue($configuration->listTestFiles()); + } + + #[TestDox('--list-tests')] + public function testListTests(): void + { + $configuration = (new Builder)->fromParameters(['--list-tests']); + + $this->assertTrue($configuration->listTests()); + } + + #[TestDox('--list-tests-xml file')] + public function testListTestsXml(): void + { + $configuration = (new Builder)->fromParameters(['--list-tests-xml', 'file']); + + $this->assertTrue($configuration->hasListTestsXml()); + $this->assertSame('file', $configuration->listTestsXml()); + } + + public function testListTestsXmlMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasListTestsXml()); + + $this->expectException(Exception::class); + + $configuration->listTestsXml(); + } + + #[TestDox('--log-events-text file')] + public function testEventsText(): void + { + $configuration = (new Builder)->fromParameters(['--log-events-text', 'file']); + + $this->assertTrue($configuration->hasLogEventsText()); + $this->assertStringEndsWith('file', $configuration->logEventsText()); + } + + #[TestDox('--log-events-text /invalid/path')] + public function testEventsTextInvalidPath(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('The path "/invalid/path" specified for the --log-events-text option could not be resolved'); + + (new Builder)->fromParameters(['--log-events-text', '/invalid/path']); + } + + public function testEventsTextMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasLogEventsText()); + + $this->expectException(Exception::class); + + $configuration->logEventsText(); + } + + #[TestDox('--log-events-verbose-text file')] + public function testEventsVerboseText(): void + { + $configuration = (new Builder)->fromParameters(['--log-events-verbose-text', 'file']); + + $this->assertTrue($configuration->hasLogEventsVerboseText()); + $this->assertStringEndsWith('file', $configuration->logEventsVerboseText()); + } + + #[TestDox('--log-events-verbose-text /invalid/path')] + public function testEventsVerboseTextInvalidPath(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('The path "/invalid/path" specified for the --log-events-verbose-text option could not be resolved'); + + (new Builder)->fromParameters(['--log-events-verbose-text', '/invalid/path']); + } + + public function testEventsVerboseTextMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasLogEventsVerboseText()); + + $this->expectException(Exception::class); + + $configuration->logEventsVerboseText(); + } + + #[TestDox('--log-junit file')] + public function testLogJunit(): void + { + $configuration = (new Builder)->fromParameters(['--log-junit', 'file']); + + $this->assertTrue($configuration->hasJunitLogfile()); + $this->assertSame('file', $configuration->junitLogfile()); + } + + public function testLogJunitMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasJunitLogfile()); + + $this->expectException(Exception::class); + + $configuration->junitLogfile(); + } + + #[TestDox('--log-otr file')] + public function testLogOtr(): void + { + $configuration = (new Builder)->fromParameters(['--log-otr', 'file']); + + $this->assertTrue($configuration->hasOtrLogfile()); + $this->assertSame('file', $configuration->otrLogfile()); + } + + public function testLogOtrMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasOtrLogfile()); + + $this->expectException(Exception::class); + + $configuration->otrLogfile(); + } + + #[TestDox('--log-teamcity file')] + public function testLogTeamcity(): void + { + $configuration = (new Builder)->fromParameters(['--log-teamcity', 'file']); + + $this->assertTrue($configuration->hasTeamcityLogfile()); + $this->assertSame('file', $configuration->teamcityLogfile()); + } + + public function testLogTeamcityMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasTeamcityLogfile()); + + $this->expectException(Exception::class); + + $configuration->teamcityLogfile(); + } + + #[TestDox('--order-by default')] + public function testOrderByDefault(): void + { + $configuration = (new Builder)->fromParameters(['--order-by', 'default']); + + $this->assertTrue($configuration->hasExecutionOrder()); + $this->assertSame(TestSuiteSorter::ORDER_DEFAULT, $configuration->executionOrder()); + $this->assertTrue($configuration->hasExecutionOrderDefects()); + $this->assertSame(TestSuiteSorter::ORDER_DEFAULT, $configuration->executionOrderDefects()); + $this->assertTrue($configuration->resolveDependencies()); + } + + #[TestDox('--order-by defects')] + public function testOrderByDefects(): void + { + $configuration = (new Builder)->fromParameters(['--order-by', 'defects']); + + $this->assertFalse($configuration->hasExecutionOrder()); + $this->assertTrue($configuration->hasExecutionOrderDefects()); + $this->assertSame(TestSuiteSorter::ORDER_DEFECTS_FIRST, $configuration->executionOrderDefects()); + $this->assertFalse($configuration->hasResolveDependencies()); + } + + #[TestDox('--order-by depends')] + public function testOrderByDepends(): void + { + $configuration = (new Builder)->fromParameters(['--order-by', 'depends']); + + $this->assertFalse($configuration->hasExecutionOrder()); + $this->assertFalse($configuration->hasExecutionOrderDefects()); + $this->assertTrue($configuration->hasResolveDependencies()); + } + + #[TestDox('--order-by duration')] + public function testOrderByDuration(): void + { + $configuration = (new Builder)->fromParameters(['--order-by', 'duration']); + + $this->assertTrue($configuration->hasExecutionOrder()); + $this->assertSame(TestSuiteSorter::ORDER_DURATION, $configuration->executionOrder()); + $this->assertFalse($configuration->hasExecutionOrderDefects()); + $this->assertFalse($configuration->hasResolveDependencies()); + } + + #[TestDox('--order-by random')] + public function testOrderByRandom(): void + { + $configuration = (new Builder)->fromParameters(['--order-by', 'random']); + + $this->assertTrue($configuration->hasExecutionOrder()); + $this->assertSame(TestSuiteSorter::ORDER_RANDOMIZED, $configuration->executionOrder()); + $this->assertFalse($configuration->hasExecutionOrderDefects()); + $this->assertFalse($configuration->hasResolveDependencies()); + } + + #[TestDox('--order-by reverse')] + public function testOrderByReverse(): void + { + $configuration = (new Builder)->fromParameters(['--order-by', 'reverse']); + + $this->assertTrue($configuration->hasExecutionOrder()); + $this->assertSame(TestSuiteSorter::ORDER_REVERSED, $configuration->executionOrder()); + $this->assertFalse($configuration->hasExecutionOrderDefects()); + $this->assertFalse($configuration->hasResolveDependencies()); + } + + #[TestDox('--order-by size')] + public function testOrderBySize(): void + { + $configuration = (new Builder)->fromParameters(['--order-by', 'size']); + + $this->assertTrue($configuration->hasExecutionOrder()); + $this->assertSame(TestSuiteSorter::ORDER_SIZE, $configuration->executionOrder()); + $this->assertFalse($configuration->hasExecutionOrderDefects()); + $this->assertFalse($configuration->hasResolveDependencies()); + } + + #[TestDox('--order-by depends,defects')] + public function testOrderByDependsDefects(): void + { + $configuration = (new Builder)->fromParameters(['--order-by', 'depends,defects']); + + $this->assertFalse($configuration->hasExecutionOrder()); + $this->assertTrue($configuration->hasExecutionOrderDefects()); + $this->assertSame(TestSuiteSorter::ORDER_DEFECTS_FIRST, $configuration->executionOrderDefects()); + $this->assertTrue($configuration->hasResolveDependencies()); + } + + #[TestDox('--order-by depends,duration')] + public function testOrderByDependsDuration(): void + { + $configuration = (new Builder)->fromParameters(['--order-by', 'depends,duration']); + + $this->assertTrue($configuration->hasExecutionOrder()); + $this->assertSame(TestSuiteSorter::ORDER_DURATION, $configuration->executionOrder()); + $this->assertFalse($configuration->hasExecutionOrderDefects()); + $this->assertTrue($configuration->hasResolveDependencies()); + } + + #[TestDox('--order-by depends,random')] + public function testOrderByDependsRandom(): void + { + $configuration = (new Builder)->fromParameters(['--order-by', 'depends,random']); + + $this->assertTrue($configuration->hasExecutionOrder()); + $this->assertSame(TestSuiteSorter::ORDER_RANDOMIZED, $configuration->executionOrder()); + $this->assertFalse($configuration->hasExecutionOrderDefects()); + $this->assertTrue($configuration->hasResolveDependencies()); + } + + #[TestDox('--order-by depends,reverse')] + public function testOrderByDependsReverse(): void + { + $configuration = (new Builder)->fromParameters(['--order-by', 'depends,reverse']); + + $this->assertTrue($configuration->hasExecutionOrder()); + $this->assertSame(TestSuiteSorter::ORDER_REVERSED, $configuration->executionOrder()); + $this->assertFalse($configuration->hasExecutionOrderDefects()); + $this->assertTrue($configuration->hasResolveDependencies()); + } + + #[TestDox('--order-by depends,size')] + public function testOrderByDependsSize(): void + { + $configuration = (new Builder)->fromParameters(['--order-by', 'depends,size']); + + $this->assertTrue($configuration->hasExecutionOrder()); + $this->assertSame(TestSuiteSorter::ORDER_SIZE, $configuration->executionOrder()); + $this->assertFalse($configuration->hasExecutionOrderDefects()); + $this->assertTrue($configuration->hasResolveDependencies()); + } + + #[TestDox('--order-by no-depends')] + public function testOrderByNoDepends(): void + { + $configuration = (new Builder)->fromParameters(['--order-by', 'no-depends']); + + $this->assertFalse($configuration->hasExecutionOrder()); + $this->assertFalse($configuration->hasExecutionOrderDefects()); + $this->assertTrue($configuration->hasResolveDependencies()); + $this->assertFalse($configuration->resolveDependencies()); + } + + #[TestDox('--order-by no-depends,defects')] + public function testOrderByNoDependsDefects(): void + { + $configuration = (new Builder)->fromParameters(['--order-by', 'no-depends,defects']); + + $this->assertFalse($configuration->hasExecutionOrder()); + $this->assertTrue($configuration->hasExecutionOrderDefects()); + $this->assertSame(TestSuiteSorter::ORDER_DEFECTS_FIRST, $configuration->executionOrderDefects()); + $this->assertTrue($configuration->hasResolveDependencies()); + $this->assertFalse($configuration->resolveDependencies()); + } + + #[TestDox('--order-by no-depends,duration')] + public function testOrderByNoDependsDuration(): void + { + $configuration = (new Builder)->fromParameters(['--order-by', 'no-depends,duration']); + + $this->assertTrue($configuration->hasExecutionOrder()); + $this->assertSame(TestSuiteSorter::ORDER_DURATION, $configuration->executionOrder()); + $this->assertFalse($configuration->hasExecutionOrderDefects()); + $this->assertTrue($configuration->hasResolveDependencies()); + $this->assertFalse($configuration->resolveDependencies()); + } + + #[TestDox('--order-by no-depends,random')] + public function testOrderByNoDependsRandom(): void + { + $configuration = (new Builder)->fromParameters(['--order-by', 'no-depends,random']); + + $this->assertTrue($configuration->hasExecutionOrder()); + $this->assertSame(TestSuiteSorter::ORDER_RANDOMIZED, $configuration->executionOrder()); + $this->assertFalse($configuration->hasExecutionOrderDefects()); + $this->assertTrue($configuration->hasResolveDependencies()); + $this->assertFalse($configuration->resolveDependencies()); + } + + #[TestDox('--order-by no-depends,reverse')] + public function testOrderByNoDependsReverse(): void + { + $configuration = (new Builder)->fromParameters(['--order-by', 'no-depends,reverse']); + + $this->assertTrue($configuration->hasExecutionOrder()); + $this->assertSame(TestSuiteSorter::ORDER_REVERSED, $configuration->executionOrder()); + $this->assertFalse($configuration->hasExecutionOrderDefects()); + $this->assertTrue($configuration->hasResolveDependencies()); + $this->assertFalse($configuration->resolveDependencies()); + } + + #[TestDox('--order-by no-depends,size')] + public function testOrderByNoDependsSize(): void + { + $configuration = (new Builder)->fromParameters(['--order-by', 'no-depends,size']); + + $this->assertTrue($configuration->hasExecutionOrder()); + $this->assertSame(TestSuiteSorter::ORDER_SIZE, $configuration->executionOrder()); + $this->assertFalse($configuration->hasExecutionOrderDefects()); + $this->assertTrue($configuration->hasResolveDependencies()); + $this->assertFalse($configuration->resolveDependencies()); + } + + #[TestDox('--order-by invalid')] + public function testOrderByInvalid(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('unrecognized --order-by option: invalid'); + + (new Builder)->fromParameters(['--order-by', 'invalid']); + } + + public function testExecutionOrderMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasExecutionOrder()); + + $this->expectException(Exception::class); + + $configuration->executionOrder(); + } + + public function testExecutionOrderDefectsMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasExecutionOrderDefects()); + + $this->expectException(Exception::class); + + $configuration->executionOrderDefects(); + } + + public function testResolveDependenciesMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasResolveDependencies()); + + $this->expectException(Exception::class); + + $configuration->resolveDependencies(); + } + + #[TestDox('--process-isolation')] + public function testProcessIsolation(): void + { + $configuration = (new Builder)->fromParameters(['--process-isolation']); + + $this->assertTrue($configuration->hasProcessIsolation()); + $this->assertTrue($configuration->processIsolation()); + } + + public function testProcessIsolationMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasProcessIsolation()); + + $this->expectException(Exception::class); + + $configuration->processIsolation(); + } + + #[TestDox('--stderr')] + public function testStderr(): void + { + $configuration = (new Builder)->fromParameters(['--stderr']); + + $this->assertTrue($configuration->hasStderr()); + $this->assertTrue($configuration->stderr()); + } + + public function testStderrMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasStderr()); + + $this->expectException(Exception::class); + + $configuration->stderr(); + } + + #[TestDox('--fail-on-all-issues')] + public function testFailOnAllIssues(): void + { + $configuration = (new Builder)->fromParameters(['--fail-on-all-issues']); + + $this->assertTrue($configuration->hasFailOnAllIssues()); + $this->assertTrue($configuration->failOnAllIssues()); + } + + public function testFailOnAllIssuesMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasFailOnAllIssues()); + + $this->expectException(Exception::class); + + $configuration->failOnAllIssues(); + } + + #[TestDox('--fail-on-deprecation')] + public function testFailOnDeprecation(): void + { + $configuration = (new Builder)->fromParameters(['--fail-on-deprecation']); + + $this->assertTrue($configuration->hasFailOnDeprecation()); + $this->assertTrue($configuration->failOnDeprecation()); + } + + public function testFailOnDeprecationMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasFailOnDeprecation()); + + $this->expectException(Exception::class); + + $configuration->failOnDeprecation(); + } + + #[TestDox('--fail-on-phpunit-deprecation')] + public function testFailOnPhpunitDeprecation(): void + { + $configuration = (new Builder)->fromParameters(['--fail-on-phpunit-deprecation']); + + $this->assertTrue($configuration->hasFailOnPhpunitDeprecation()); + $this->assertTrue($configuration->failOnPhpunitDeprecation()); + } + + public function testFailOnPhpunitDeprecationMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasFailOnPhpunitDeprecation()); + + $this->expectException(Exception::class); + + $configuration->failOnPhpunitDeprecation(); + } + + #[TestDox('--fail-on-phpunit-notice')] + public function testFailOnPhpunitNotice(): void + { + $configuration = (new Builder)->fromParameters(['--fail-on-phpunit-notice']); + + $this->assertTrue($configuration->hasFailOnPhpunitNotice()); + $this->assertTrue($configuration->failOnPhpunitNotice()); + } + + public function testFailOnPhpunitNoticeMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasFailOnPhpunitNotice()); + + $this->expectException(Exception::class); + + $configuration->failOnPhpunitNotice(); + } + + #[TestDox('--fail-on-phpunit-warning')] + public function testFailOnPhpunitWarning(): void + { + $configuration = (new Builder)->fromParameters(['--fail-on-phpunit-warning']); + + $this->assertTrue($configuration->hasFailOnPhpunitWarning()); + $this->assertTrue($configuration->failOnPhpunitWarning()); + } + + public function testFailOnPhpunitWarningMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasFailOnPhpunitWarning()); + + $this->expectException(Exception::class); + + $configuration->failOnPhpunitWarning(); + } + + #[TestDox('--fail-on-empty-test-suite')] + public function testFailOnEmptyTestSuite(): void + { + $configuration = (new Builder)->fromParameters(['--fail-on-empty-test-suite']); + + $this->assertTrue($configuration->hasFailOnEmptyTestSuite()); + $this->assertTrue($configuration->failOnEmptyTestSuite()); + } + + public function testFailOnEmptyTestSuiteMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasFailOnEmptyTestSuite()); + + $this->expectException(Exception::class); + + $configuration->failOnEmptyTestSuite(); + } + + #[TestDox('--fail-on-incomplete')] + public function testFailOnIncomplete(): void + { + $configuration = (new Builder)->fromParameters(['--fail-on-incomplete']); + + $this->assertTrue($configuration->hasFailOnIncomplete()); + $this->assertTrue($configuration->failOnIncomplete()); + } + + public function testFailOnIncompleteMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasFailOnIncomplete()); + + $this->expectException(Exception::class); + + $configuration->failOnIncomplete(); + } + + #[TestDox('--fail-on-notice')] + public function testFailOnNotice(): void + { + $configuration = (new Builder)->fromParameters(['--fail-on-notice']); + + $this->assertTrue($configuration->hasFailOnNotice()); + $this->assertTrue($configuration->failOnNotice()); + } + + public function testFailOnNoticeMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasFailOnNotice()); + + $this->expectException(Exception::class); + + $configuration->failOnNotice(); + } + + #[TestDox('--fail-on-risky')] + public function testFailOnRisky(): void + { + $configuration = (new Builder)->fromParameters(['--fail-on-risky']); + + $this->assertTrue($configuration->hasFailOnRisky()); + $this->assertTrue($configuration->failOnRisky()); + } + + public function testFailOnRiskyMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasFailOnRisky()); + + $this->expectException(Exception::class); + + $configuration->failOnRisky(); + } + + #[TestDox('--fail-on-skipped')] + public function testFailOnSkipped(): void + { + $configuration = (new Builder)->fromParameters(['--fail-on-skipped']); + + $this->assertTrue($configuration->hasFailOnSkipped()); + $this->assertTrue($configuration->failOnSkipped()); + } + + public function testFailOnSkippedMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasFailOnSkipped()); + + $this->expectException(Exception::class); + + $configuration->failOnSkipped(); + } + + #[TestDox('--fail-on-warning')] + public function testFailOnWarning(): void + { + $configuration = (new Builder)->fromParameters(['--fail-on-warning']); + + $this->assertTrue($configuration->hasFailOnWarning()); + $this->assertTrue($configuration->failOnWarning()); + } + + public function testFailOnWarningMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasFailOnWarning()); + + $this->expectException(Exception::class); + + $configuration->failOnWarning(); + } + + #[TestDox('--do-not-fail-on-deprecation')] + public function testDoNotFailOnDeprecation(): void + { + $configuration = (new Builder)->fromParameters(['--do-not-fail-on-deprecation']); + + $this->assertTrue($configuration->hasDoNotFailOnDeprecation()); + $this->assertTrue($configuration->doNotFailOnDeprecation()); + } + + public function testDoNotFailOnDeprecationMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasDoNotFailOnDeprecation()); + + $this->expectException(Exception::class); + + $configuration->doNotFailOnDeprecation(); + } + + #[TestDox('--do-not-fail-on-phpunit-deprecation')] + public function testDoNotFailOnPhpunitDeprecation(): void + { + $configuration = (new Builder)->fromParameters(['--do-not-fail-on-phpunit-deprecation']); + + $this->assertTrue($configuration->hasDoNotFailOnPhpunitDeprecation()); + $this->assertTrue($configuration->doNotFailOnPhpunitDeprecation()); + } + + public function testDoNotFailOnPhpunitDeprecationMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasDoNotFailOnPhpunitDeprecation()); + + $this->expectException(Exception::class); + + $configuration->doNotFailOnPhpunitDeprecation(); + } + + #[TestDox('--do-not-fail-on-phpunit-notice')] + public function testDoNotFailOnPhpunitNotice(): void + { + $configuration = (new Builder)->fromParameters(['--do-not-fail-on-phpunit-notice']); + + $this->assertTrue($configuration->hasDoNotFailOnPhpunitNotice()); + $this->assertTrue($configuration->doNotFailOnPhpunitNotice()); + } + + public function testDoNotFailOnPhpunitNoticeMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasDoNotFailOnPhpunitNotice()); + + $this->expectException(Exception::class); + + $configuration->doNotFailOnPhpunitNotice(); + } + + #[TestDox('--do-not-fail-on-phpunit-warning')] + public function testDoNotFailOnPhpunitWarning(): void + { + $configuration = (new Builder)->fromParameters(['--do-not-fail-on-phpunit-warning']); + + $this->assertTrue($configuration->hasDoNotFailOnPhpunitWarning()); + $this->assertTrue($configuration->doNotFailOnPhpunitWarning()); + } + + public function testDoNotFailOnPhpunitWarningMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasDoNotFailOnPhpunitWarning()); + + $this->expectException(Exception::class); + + $configuration->doNotFailOnPhpunitWarning(); + } + + #[TestDox('--do-not-fail-on-empty-test-suite')] + public function testDoNotFailOnEmptyTestSuite(): void + { + $configuration = (new Builder)->fromParameters(['--do-not-fail-on-empty-test-suite']); + + $this->assertTrue($configuration->hasDoNotFailOnEmptyTestSuite()); + $this->assertTrue($configuration->doNotFailOnEmptyTestSuite()); + } + + public function testDoNotFailOnEmptyTestSuiteMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasDoNotFailOnEmptyTestSuite()); + + $this->expectException(Exception::class); + + $configuration->doNotFailOnEmptyTestSuite(); + } + + #[TestDox('--do-not-fail-on-incomplete')] + public function testDoNotFailOnIncomplete(): void + { + $configuration = (new Builder)->fromParameters(['--do-not-fail-on-incomplete']); + + $this->assertTrue($configuration->hasDoNotFailOnIncomplete()); + $this->assertTrue($configuration->doNotFailOnIncomplete()); + } + + public function testDoNotFailOnIncompleteMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasDoNotFailOnIncomplete()); + + $this->expectException(Exception::class); + + $configuration->doNotFailOnIncomplete(); + } + + #[TestDox('--do-not-fail-on-notice')] + public function testDoNotFailOnNotice(): void + { + $configuration = (new Builder)->fromParameters(['--do-not-fail-on-notice']); + + $this->assertTrue($configuration->hasDoNotFailOnNotice()); + $this->assertTrue($configuration->doNotFailOnNotice()); + } + + public function testDoNotFailOnNoticeMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasDoNotFailOnNotice()); + + $this->expectException(Exception::class); + + $configuration->doNotFailOnNotice(); + } + + #[TestDox('--do-not-fail-on-risky')] + public function testDoNotFailOnRisky(): void + { + $configuration = (new Builder)->fromParameters(['--do-not-fail-on-risky']); + + $this->assertTrue($configuration->hasDoNotFailOnRisky()); + $this->assertTrue($configuration->doNotFailOnRisky()); + } + + public function testDoNotFailOnRiskyMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasDoNotFailOnRisky()); + + $this->expectException(Exception::class); + + $configuration->doNotFailOnRisky(); + } + + #[TestDox('--do-not-fail-on-skipped')] + public function testDoNotFailOnSkipped(): void + { + $configuration = (new Builder)->fromParameters(['--do-not-fail-on-skipped']); + + $this->assertTrue($configuration->hasDoNotFailOnSkipped()); + $this->assertTrue($configuration->doNotFailOnSkipped()); + } + + public function testDoNotFailOnSkippedMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasDoNotFailOnSkipped()); + + $this->expectException(Exception::class); + + $configuration->doNotFailOnSkipped(); + } + + #[TestDox('--do-not-fail-on-warning')] + public function testDoNotFailOnWarning(): void + { + $configuration = (new Builder)->fromParameters(['--do-not-fail-on-warning']); + + $this->assertTrue($configuration->hasDoNotFailOnWarning()); + $this->assertTrue($configuration->doNotFailOnWarning()); + } + + public function testDoNotFailOnWarningMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasDoNotFailOnWarning()); + + $this->expectException(Exception::class); + + $configuration->doNotFailOnWarning(); + } + + #[TestDox('--stop-on-defect')] + public function testStopOnDefect(): void + { + $configuration = (new Builder)->fromParameters(['--stop-on-defect']); + + $this->assertTrue($configuration->hasStopOnDefect()); + $this->assertTrue($configuration->stopOnDefect()); + } + + public function testStopOnDefectMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasStopOnDefect()); + + $this->expectException(Exception::class); + + $configuration->stopOnDefect(); + } + + #[TestDox('--stop-on-deprecation')] + public function testStopOnDeprecation(): void + { + $configuration = (new Builder)->fromParameters(['--stop-on-deprecation']); + + $this->assertTrue($configuration->hasStopOnDeprecation()); + $this->assertTrue($configuration->stopOnDeprecation()); + + $this->expectException(Exception::class); + + $configuration->specificDeprecationToStopOn(); + } + + #[TestDox('--stop-on-deprecation=message')] + public function testStopOnDeprecationMessage(): void + { + $configuration = (new Builder)->fromParameters(['--stop-on-deprecation=message']); + + $this->assertTrue($configuration->hasStopOnDeprecation()); + $this->assertTrue($configuration->stopOnDeprecation()); + $this->assertTrue($configuration->hasSpecificDeprecationToStopOn()); + $this->assertSame('message', $configuration->specificDeprecationToStopOn()); + } + + public function testStopOnDeprecationMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasStopOnDeprecation()); + + $this->expectException(Exception::class); + + $configuration->stopOnDeprecation(); + } + + #[TestDox('--stop-on-error')] + public function testStopOnError(): void + { + $configuration = (new Builder)->fromParameters(['--stop-on-error']); + + $this->assertTrue($configuration->hasStopOnError()); + $this->assertTrue($configuration->stopOnError()); + } + + public function testStopOnErrorMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasStopOnError()); + + $this->expectException(Exception::class); + + $configuration->stopOnError(); + } + + #[TestDox('--stop-on-failure')] + public function testStopOnFailure(): void + { + $configuration = (new Builder)->fromParameters(['--stop-on-failure']); + + $this->assertTrue($configuration->hasStopOnFailure()); + $this->assertTrue($configuration->stopOnFailure()); + } + + public function testStopOnFailureMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasStopOnFailure()); + + $this->expectException(Exception::class); + + $configuration->stopOnFailure(); + } + + #[TestDox('--stop-on-incomplete')] + public function testStopOnIncomplete(): void + { + $configuration = (new Builder)->fromParameters(['--stop-on-incomplete']); + + $this->assertTrue($configuration->hasStopOnIncomplete()); + $this->assertTrue($configuration->stopOnIncomplete()); + } + + public function testStopOnIncompleteMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasStopOnIncomplete()); + + $this->expectException(Exception::class); + + $configuration->stopOnIncomplete(); + } + + #[TestDox('--stop-on-notice')] + public function testStopOnNotice(): void + { + $configuration = (new Builder)->fromParameters(['--stop-on-notice']); + + $this->assertTrue($configuration->hasStopOnNotice()); + $this->assertTrue($configuration->stopOnNotice()); + } + + public function testStopOnNoticeMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasStopOnNotice()); + + $this->expectException(Exception::class); + + $configuration->stopOnNotice(); + } + + #[TestDox('--stop-on-risky')] + public function testStopOnRisky(): void + { + $configuration = (new Builder)->fromParameters(['--stop-on-risky']); + + $this->assertTrue($configuration->hasStopOnRisky()); + $this->assertTrue($configuration->stopOnRisky()); + } + + public function testStopOnRiskyMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasStopOnRisky()); + + $this->expectException(Exception::class); + + $configuration->stopOnRisky(); + } + + #[TestDox('--stop-on-skipped')] + public function testStopOnSkipped(): void + { + $configuration = (new Builder)->fromParameters(['--stop-on-skipped']); + + $this->assertTrue($configuration->hasStopOnSkipped()); + $this->assertTrue($configuration->stopOnSkipped()); + } + + public function testStopOnSkippedMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasStopOnSkipped()); + + $this->expectException(Exception::class); + + $configuration->stopOnSkipped(); + } + + #[TestDox('--stop-on-warning')] + public function testStopOnWarning(): void + { + $configuration = (new Builder)->fromParameters(['--stop-on-warning']); + + $this->assertTrue($configuration->hasStopOnWarning()); + $this->assertTrue($configuration->stopOnWarning()); + } + + public function testStopOnWarningMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasStopOnWarning()); + + $this->expectException(Exception::class); + + $configuration->stopOnWarning(); + } + + #[TestDox('--teamcity')] + public function testTeamcity(): void + { + $configuration = (new Builder)->fromParameters(['--teamcity']); + + $this->assertTrue($configuration->hasTeamCityPrinter()); + $this->assertTrue($configuration->teamCityPrinter()); + } + + public function testTeamcityMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasTeamCityPrinter()); + + $this->expectException(Exception::class); + + $configuration->teamCityPrinter(); + } + + #[TestDox('--testdox')] + public function testTestDox(): void + { + $configuration = (new Builder)->fromParameters(['--testdox']); + + $this->assertTrue($configuration->hasTestDoxPrinter()); + $this->assertTrue($configuration->testdoxPrinter()); + } + + public function testTestDoxMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasTestDoxPrinter()); + + $this->expectException(Exception::class); + + $configuration->testdoxPrinter(); + } + + #[TestDox('--testdox-summary')] + public function testTestDoxPrinterSummary(): void + { + $configuration = (new Builder)->fromParameters(['--testdox-summary']); + + $this->assertTrue($configuration->hasTestDoxPrinterSummary()); + $this->assertTrue($configuration->testdoxPrinterSummary()); + } + + public function testTestDoxPrinterSummaryMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasTestDoxPrinterSummary()); + + $this->expectException(Exception::class); + + $configuration->testdoxPrinterSummary(); + } + + #[TestDox('--testdox-html file')] + public function testTestDoxHtml(): void + { + $configuration = (new Builder)->fromParameters(['--testdox-html', 'file']); + + $this->assertTrue($configuration->hasTestdoxHtmlFile()); + $this->assertSame('file', $configuration->testdoxHtmlFile()); + } + + public function testTestDoxHtmlMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasTestdoxHtmlFile()); + + $this->expectException(Exception::class); + + $configuration->testdoxHtmlFile(); + } + + #[TestDox('--testdox-text file')] + public function testTestDoxText(): void + { + $configuration = (new Builder)->fromParameters(['--testdox-text', 'file']); + + $this->assertTrue($configuration->hasTestdoxTextFile()); + $this->assertSame('file', $configuration->testdoxTextFile()); + } + + public function testTestDoxTextMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasTestdoxTextFile()); + + $this->expectException(Exception::class); + + $configuration->testdoxTextFile(); + } + + #[TestDox('--no-configuration')] + public function testNoConfiguration(): void + { + $configuration = (new Builder)->fromParameters(['--no-configuration']); + + $this->assertFalse($configuration->useDefaultConfiguration()); + } + + #[TestDox('--no-extensions')] + public function testNoExtensions(): void + { + $configuration = (new Builder)->fromParameters(['--no-extensions']); + + $this->assertTrue($configuration->hasNoExtensions()); + $this->assertTrue($configuration->noExtensions()); + } + + public function testNoExtensionsMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasNoExtensions()); + + $this->expectException(Exception::class); + + $configuration->noExtensions(); + } + + #[TestDox('--no-coverage')] + public function testNoCoverage(): void + { + $configuration = (new Builder)->fromParameters(['--no-coverage']); + + $this->assertTrue($configuration->hasNoCoverage()); + $this->assertTrue($configuration->noCoverage()); + } + + public function testNoCoverageMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasNoCoverage()); + + $this->expectException(Exception::class); + + $configuration->noCoverage(); + } + + #[TestDox('--no-logging')] + public function testNoLogging(): void + { + $configuration = (new Builder)->fromParameters(['--no-logging']); + + $this->assertTrue($configuration->hasNoLogging()); + $this->assertTrue($configuration->noLogging()); + } + + public function testNoLoggingMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasNoLogging()); + + $this->expectException(Exception::class); + + $configuration->noLogging(); + } + + #[TestDox('--no-output')] + public function testNoOutput(): void + { + $configuration = (new Builder)->fromParameters(['--no-output']); + + $this->assertTrue($configuration->hasNoOutput()); + $this->assertTrue($configuration->noOutput()); + } + + public function testNoOutputMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasNoOutput()); + + $this->expectException(Exception::class); + + $configuration->noOutput(); + } + + #[TestDox('--no-progress')] + public function testNoProgress(): void + { + $configuration = (new Builder)->fromParameters(['--no-progress']); + + $this->assertTrue($configuration->hasNoProgress()); + $this->assertTrue($configuration->noProgress()); + } + + public function testNoProgressMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasNoProgress()); + + $this->expectException(Exception::class); + + $configuration->noProgress(); + } + + #[TestDox('--no-results')] + public function testNoResults(): void + { + $configuration = (new Builder)->fromParameters(['--no-results']); + + $this->assertTrue($configuration->hasNoResults()); + $this->assertTrue($configuration->noResults()); + } + + public function testNoResultsMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasNoResults()); + + $this->expectException(Exception::class); + + $configuration->noResults(); + } + + #[TestDox('--globals-backup')] + public function testGlobalsBackup(): void + { + $configuration = (new Builder)->fromParameters(['--globals-backup']); + + $this->assertTrue($configuration->hasBackupGlobals()); + $this->assertTrue($configuration->backupGlobals()); + } + + public function testGlobalsBackupMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasBackupGlobals()); + + $this->expectException(Exception::class); + + $configuration->backupGlobals(); + } + + #[TestDox('--static-backup')] + public function testStaticBackup(): void + { + $configuration = (new Builder)->fromParameters(['--static-backup']); + + $this->assertTrue($configuration->hasBackupStaticProperties()); + $this->assertTrue($configuration->backupStaticProperties()); + } + + public function testStaticBackupMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasBackupStaticProperties()); + + $this->expectException(Exception::class); + + $configuration->backupStaticProperties(); + } + + #[TestDox('--atleast-version string')] + public function testAtLeastVersion(): void + { + $configuration = (new Builder)->fromParameters(['--atleast-version', 'string']); + + $this->assertTrue($configuration->hasAtLeastVersion()); + $this->assertSame('string', $configuration->atLeastVersion()); + } + + public function testAtLeastVersionMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasAtLeastVersion()); + + $this->expectException(Exception::class); + + $configuration->atLeastVersion(); + } + + #[TestDox('--version')] + public function testVersion(): void + { + $configuration = (new Builder)->fromParameters(['--version']); + + $this->assertTrue($configuration->version()); + } + + #[TestDox('--do-not-report-useless-tests')] + public function testDoNotReportUselessTests(): void + { + $configuration = (new Builder)->fromParameters(['--do-not-report-useless-tests']); + + $this->assertTrue($configuration->hasReportUselessTests()); + $this->assertFalse($configuration->reportUselessTests()); + } + + public function testDoNotReportUselessTestsMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasReportUselessTests()); + + $this->expectException(Exception::class); + + $configuration->reportUselessTests(); + } + + #[TestDox('--strict-coverage')] + public function testStrictCoverage(): void + { + $configuration = (new Builder)->fromParameters(['--strict-coverage']); + + $this->assertTrue($configuration->hasStrictCoverage()); + $this->assertTrue($configuration->strictCoverage()); + } + + public function testStrictCoverageMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasStrictCoverage()); + + $this->expectException(Exception::class); + + $configuration->strictCoverage(); + } + + #[TestDox('--disable-coverage-ignore')] + public function testDisableCoverageIgnore(): void + { + $configuration = (new Builder)->fromParameters(['--disable-coverage-ignore']); + + $this->assertTrue($configuration->hasDisableCodeCoverageIgnore()); + $this->assertTrue($configuration->disableCodeCoverageIgnore()); + } + + public function testDisableCoverageIgnoreMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasDisableCodeCoverageIgnore()); + + $this->expectException(Exception::class); + + $configuration->disableCodeCoverageIgnore(); + } + + #[TestDox('--strict-global-state')] + public function testStrictGlobalState(): void + { + $configuration = (new Builder)->fromParameters(['--strict-global-state']); + + $this->assertTrue($configuration->hasBeStrictAboutChangesToGlobalState()); + $this->assertTrue($configuration->beStrictAboutChangesToGlobalState()); + } + + public function testStrictGlobalStateMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasBeStrictAboutChangesToGlobalState()); + + $this->expectException(Exception::class); + + $configuration->beStrictAboutChangesToGlobalState(); + } + + #[TestDox('--disallow-test-output')] + public function testDisallowTestOutput(): void + { + $configuration = (new Builder)->fromParameters(['--disallow-test-output']); + + $this->assertTrue($configuration->hasDisallowTestOutput()); + $this->assertTrue($configuration->disallowTestOutput()); + } + + public function testDisallowTestOutputMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasDisallowTestOutput()); + + $this->expectException(Exception::class); + + $configuration->disallowTestOutput(); + } + + #[TestDox('--display-all-issues')] + public function testDisplayAllIssues(): void + { + $configuration = (new Builder)->fromParameters(['--display-all-issues']); + + $this->assertTrue($configuration->hasDisplayDetailsOnAllIssues()); + $this->assertTrue($configuration->displayDetailsOnAllIssues()); + } + + public function testDisplayAllIssuesMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasDisplayDetailsOnAllIssues()); + + $this->expectException(Exception::class); + + $configuration->displayDetailsOnAllIssues(); + } + + #[TestDox('--display-incomplete')] + public function testDisplayIncomplete(): void + { + $configuration = (new Builder)->fromParameters(['--display-incomplete']); + + $this->assertTrue($configuration->hasDisplayDetailsOnIncompleteTests()); + $this->assertTrue($configuration->displayDetailsOnIncompleteTests()); + } + + public function testDisplayIncompleteMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasDisplayDetailsOnIncompleteTests()); + + $this->expectException(Exception::class); + + $configuration->displayDetailsOnIncompleteTests(); + } + + #[TestDox('--display-skipped')] + public function testDisplaySkipped(): void + { + $configuration = (new Builder)->fromParameters(['--display-skipped']); + + $this->assertTrue($configuration->hasDisplayDetailsOnSkippedTests()); + $this->assertTrue($configuration->displayDetailsOnSkippedTests()); + } + + public function testDisplaySkippedMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasDisplayDetailsOnSkippedTests()); + + $this->expectException(Exception::class); + + $configuration->displayDetailsOnSkippedTests(); + } + + #[TestDox('--display-deprecations')] + public function testDisplayDeprecations(): void + { + $configuration = (new Builder)->fromParameters(['--display-deprecations']); + + $this->assertTrue($configuration->hasDisplayDetailsOnTestsThatTriggerDeprecations()); + $this->assertTrue($configuration->displayDetailsOnTestsThatTriggerDeprecations()); + } + + public function testDisplayDeprecationsMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasDisplayDetailsOnTestsThatTriggerDeprecations()); + + $this->expectException(Exception::class); + + $configuration->displayDetailsOnTestsThatTriggerDeprecations(); + } + + #[TestDox('--display-phpunit-deprecations')] + public function testDisplayPhpunitDeprecations(): void + { + $configuration = (new Builder)->fromParameters(['--display-phpunit-deprecations']); + + $this->assertTrue($configuration->hasDisplayDetailsOnPhpunitDeprecations()); + $this->assertTrue($configuration->displayDetailsOnPhpunitDeprecations()); + } + + public function testDisplayPhpunitDeprecationsMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasDisplayDetailsOnPhpunitDeprecations()); + + $this->expectException(Exception::class); + + $configuration->displayDetailsOnPhpunitDeprecations(); + } + + #[TestDox('--display-phpunit-notices')] + public function testDisplayPhpunitNotices(): void + { + $configuration = (new Builder)->fromParameters(['--display-phpunit-notices']); + + $this->assertTrue($configuration->hasDisplayDetailsOnPhpunitNotices()); + $this->assertTrue($configuration->displayDetailsOnPhpunitNotices()); + } + + public function testDisplayPhpunitNoticesMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasDisplayDetailsOnPhpunitNotices()); + + $this->expectException(Exception::class); + + $configuration->displayDetailsOnPhpunitNotices(); + } + + #[TestDox('--display-errors')] + public function testDisplayErrors(): void + { + $configuration = (new Builder)->fromParameters(['--display-errors']); + + $this->assertTrue($configuration->hasDisplayDetailsOnTestsThatTriggerErrors()); + $this->assertTrue($configuration->displayDetailsOnTestsThatTriggerErrors()); + } + + public function testDisplayErrorsMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasDisplayDetailsOnTestsThatTriggerErrors()); + + $this->expectException(Exception::class); + + $configuration->displayDetailsOnTestsThatTriggerErrors(); + } + + #[TestDox('--display-notices')] + public function testDisplayNotices(): void + { + $configuration = (new Builder)->fromParameters(['--display-notices']); + + $this->assertTrue($configuration->hasDisplayDetailsOnTestsThatTriggerNotices()); + $this->assertTrue($configuration->displayDetailsOnTestsThatTriggerNotices()); + } + + public function testDisplayNoticesMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasDisplayDetailsOnTestsThatTriggerNotices()); + + $this->expectException(Exception::class); + + $configuration->displayDetailsOnTestsThatTriggerNotices(); + } + + #[TestDox('--display-warnings')] + public function testDisplayWarnings(): void + { + $configuration = (new Builder)->fromParameters(['--display-warnings']); + + $this->assertTrue($configuration->hasDisplayDetailsOnTestsThatTriggerWarnings()); + $this->assertTrue($configuration->displayDetailsOnTestsThatTriggerWarnings()); + } + + public function testDisplayWarningsMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasDisplayDetailsOnTestsThatTriggerWarnings()); + + $this->expectException(Exception::class); + + $configuration->displayDetailsOnTestsThatTriggerWarnings(); + } + + #[TestDox('--default-time-limit ')] + public function testDefaultTimeLimit(): void + { + $configuration = (new Builder)->fromParameters(['--default-time-limit', '10']); + + $this->assertTrue($configuration->hasDefaultTimeLimit()); + $this->assertSame(10, $configuration->defaultTimeLimit()); + } + + public function testDefaultTimeLimitMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasDefaultTimeLimit()); + + $this->expectException(Exception::class); + + $configuration->defaultTimeLimit(); + } + + #[TestDox('--enforce-time-limit')] + public function testEnforceTimeLimit(): void + { + $configuration = (new Builder)->fromParameters(['--enforce-time-limit']); + + $this->assertTrue($configuration->hasEnforceTimeLimit()); + $this->assertTrue($configuration->enforceTimeLimit()); + } + + public function testEnforceTimeLimitMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasEnforceTimeLimit()); + + $this->expectException(Exception::class); + + $configuration->enforceTimeLimit(); + } + + #[TestDox('--reverse-list')] + public function testReverseList(): void + { + $configuration = (new Builder)->fromParameters(['--reverse-list']); + + $this->assertTrue($configuration->hasReverseList()); + $this->assertTrue($configuration->reverseList()); + } + + public function testReverseListMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasReverseList()); + + $this->expectException(Exception::class); + + $configuration->reverseList(); + } + + #[TestDox('--check-version')] + public function testCheckVersion(): void + { + $configuration = (new Builder)->fromParameters(['--check-version']); + + $this->assertTrue($configuration->checkVersion()); + } + + #[TestDox('--coverage-filter directory')] + public function testCoverageFilterDirectory(): void + { + $configuration = (new Builder)->fromParameters(['--coverage-filter', 'directory']); + + $this->assertTrue($configuration->hasCoverageFilter()); + $this->assertSame(['directory'], $configuration->coverageFilter()); + } + + #[TestDox('--coverage-filter directory --coverage-filter another-directory')] + public function testCoverageFilterDirectories(): void + { + $configuration = (new Builder)->fromParameters(['--coverage-filter', 'directory', '--coverage-filter', 'another-directory']); + + $this->assertTrue($configuration->hasCoverageFilter()); + $this->assertSame(['directory', 'another-directory'], $configuration->coverageFilter()); + } + + public function testCoverageFilterMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasCoverageFilter()); + + $this->expectException(Exception::class); + + $configuration->coverageFilter(); + } + + #[TestDox('--random-order')] + public function testRandomOrder(): void + { + $configuration = (new Builder)->fromParameters(['--random-order']); + + $this->assertTrue($configuration->hasExecutionOrder()); + $this->assertSame(TestSuiteSorter::ORDER_RANDOMIZED, $configuration->executionOrder()); + } + + #[TestDox('--resolve-dependencies')] + public function testResolveDependencies(): void + { + $configuration = (new Builder)->fromParameters(['--resolve-dependencies']); + + $this->assertTrue($configuration->hasResolveDependencies()); + $this->assertTrue($configuration->resolveDependencies()); + } + + #[TestDox('--ignore-dependencies')] + public function testIgnoreDependencies(): void + { + $configuration = (new Builder)->fromParameters(['--ignore-dependencies']); + + $this->assertTrue($configuration->hasResolveDependencies()); + $this->assertFalse($configuration->resolveDependencies()); + } + + #[TestDox('--reverse-order')] + public function testReverseOrder(): void + { + $configuration = (new Builder)->fromParameters(['--reverse-order']); + + $this->assertTrue($configuration->hasExecutionOrder()); + $this->assertSame(TestSuiteSorter::ORDER_REVERSED, $configuration->executionOrder()); + } + + #[TestDox('--random-order-seed')] + public function testRandomOrderSeed(): void + { + $configuration = (new Builder)->fromParameters(['--random-order-seed', '1234']); + + $this->assertTrue($configuration->hasRandomOrderSeed()); + $this->assertSame(1234, $configuration->randomOrderSeed()); + } + + public function testRandomOrderSeedMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasRandomOrderSeed()); + + $this->expectException(Exception::class); + + $configuration->randomOrderSeed(); + } + + #[TestDox('--debug')] + public function testDebug(): void + { + $configuration = (new Builder)->fromParameters(['--debug']); + + $this->assertTrue($configuration->debug()); + } + + #[TestDox('--with-telemetry')] + public function testWithTelemetry(): void + { + $configuration = (new Builder)->fromParameters(['--with-telemetry']); + + $this->assertTrue($configuration->withTelemetry()); + } + + #[TestDox('--extension')] + public function testExtension(): void + { + $configuration = (new Builder)->fromParameters(['--extension', 'ExtensionClass']); + + $this->assertTrue($configuration->hasExtensions()); + $this->assertSame(['ExtensionClass'], $configuration->extensions()); + } + + public function testExtensionMayNotBeConfigured(): void + { + $configuration = (new Builder)->fromParameters([]); + + $this->assertFalse($configuration->hasExtensions()); + + $this->expectException(Exception::class); + + $configuration->extensions(); + } + + public function testInvalidOption(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('Unknown option "--invalid-option"'); + + (new Builder)->fromParameters(['--invalid-option']); + } +} diff --git a/tests/unit/TextUI/Configuration/MergerTest.php b/tests/unit/TextUI/Configuration/MergerTest.php new file mode 100644 index 00000000000..5b7edd9eba2 --- /dev/null +++ b/tests/unit/TextUI/Configuration/MergerTest.php @@ -0,0 +1,137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function uniqid; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\Attributes\Ticket; +use PHPUnit\Framework\TestCase; +use PHPUnit\TextUI\CliArguments\Builder; +use PHPUnit\TextUI\Configuration\Merger; + +#[CoversClass(Merger::class)] +#[Medium] +final class MergerTest extends TestCase +{ + public function testNoLoggingShouldOnlyAffectXmlConfiguration(): void + { + $junitLog = uniqid('junit_log_'); + $fromFile = (new Loader)->load(TEST_FILES_PATH . 'configuration_logging.xml'); + + $this->assertTrue($fromFile->logging()->hasTeamCity()); + $this->assertTrue($fromFile->logging()->hasTestDoxHtml()); + $this->assertTrue($fromFile->logging()->hasTestDoxText()); + + $this->assertTrue($fromFile->logging()->hasJunit()); + $this->assertNotSame($junitLog, $fromFile->logging()->junit()->target()->path()); + + $fromCli = (new Builder)->fromParameters([ + '--no-logging', + '--log-junit', + $junitLog, + ]); + + $mergedConfig = (new Merger)->merge($fromCli, $fromFile); + + $this->assertFalse($mergedConfig->hasLogfileTeamcity()); + $this->assertFalse($mergedConfig->hasLogfileTestdoxHtml()); + $this->assertFalse($mergedConfig->hasLogfileTestdoxText()); + + $this->assertTrue($mergedConfig->hasLogfileJunit()); + $this->assertSame($junitLog, $mergedConfig->logfileJunit()); + } + + public function testNoCoverageShouldOnlyAffectXmlConfiguration(): void + { + $phpCoverage = uniqid('php_coverage_'); + $fromFile = (new Loader)->load(TEST_FILES_PATH . 'configuration_codecoverage.xml'); + + $this->assertTrue($fromFile->codeCoverage()->hasClover()); + $this->assertTrue($fromFile->codeCoverage()->hasCobertura()); + $this->assertTrue($fromFile->codeCoverage()->hasCrap4j()); + $this->assertTrue($fromFile->codeCoverage()->hasHtml()); + $this->assertTrue($fromFile->codeCoverage()->hasOpenClover()); + $this->assertTrue($fromFile->codeCoverage()->hasText()); + $this->assertTrue($fromFile->codeCoverage()->hasXml()); + + $this->assertTrue($fromFile->codeCoverage()->hasPhp()); + $this->assertNotSame($phpCoverage, $fromFile->codeCoverage()->php()->target()->path()); + + $fromCli = (new Builder)->fromParameters([ + '--no-coverage', + '--coverage-php', + $phpCoverage, + ]); + + $mergedConfig = (new Merger)->merge($fromCli, $fromFile); + + $this->assertFalse($mergedConfig->hasCoverageClover()); + $this->assertFalse($mergedConfig->hasCoverageCobertura()); + $this->assertFalse($mergedConfig->hasCoverageCrap4j()); + $this->assertFalse($mergedConfig->hasCoverageHtml()); + $this->assertFalse($mergedConfig->hasCoverageOpenClover()); + $this->assertFalse($mergedConfig->hasCoverageText()); + $this->assertFalse($mergedConfig->hasCoverageXml()); + + $this->assertTrue($mergedConfig->hasCoveragePhp()); + $this->assertSame($phpCoverage, $mergedConfig->coveragePhp()); + } + + #[Ticket('https://github.com/sebastianbergmann/phpunit/issues/6340')] + public function testIssue6340(): void + { + $fromFile = (new Loader)->load(TEST_FILES_PATH . 'configuration-issue-6340.xml'); + + $this->assertTrue($fromFile->phpunit()->failOnPhpunitDeprecation()); + $this->assertTrue($fromFile->phpunit()->failOnPhpunitNotice()); + $this->assertTrue($fromFile->phpunit()->failOnDeprecation()); + $this->assertTrue($fromFile->phpunit()->failOnNotice()); + $this->assertTrue($fromFile->phpunit()->failOnWarning()); + $this->assertTrue($fromFile->phpunit()->failOnIncomplete()); + $this->assertTrue($fromFile->phpunit()->failOnSkipped()); + + $fromCli = (new Builder)->fromParameters([ + '--do-not-fail-on-phpunit-deprecation', + '--do-not-fail-on-phpunit-notice', + '--do-not-fail-on-deprecation', + '--do-not-fail-on-notice', + '--do-not-fail-on-warning', + '--do-not-fail-on-incomplete', + '--do-not-fail-on-skipped', + ]); + + $this->assertTrue($fromCli->doNotFailOnPhpunitDeprecation()); + $this->assertTrue($fromCli->doNotFailOnPhpunitNotice()); + $this->assertTrue($fromCli->doNotFailOnDeprecation()); + $this->assertTrue($fromCli->doNotFailOnNotice()); + $this->assertTrue($fromCli->doNotFailOnWarning()); + $this->assertTrue($fromCli->doNotFailOnIncomplete()); + $this->assertTrue($fromCli->doNotFailOnSkipped()); + + $mergedConfig = (new Merger)->merge($fromCli, $fromFile); + + $this->assertTrue($mergedConfig->doNotFailOnPhpunitDeprecation()); + $this->assertTrue($mergedConfig->doNotFailOnPhpunitNotice()); + $this->assertTrue($mergedConfig->doNotFailOnDeprecation()); + $this->assertTrue($mergedConfig->doNotFailOnNotice()); + $this->assertTrue($mergedConfig->doNotFailOnWarning()); + $this->assertTrue($mergedConfig->doNotFailOnIncomplete()); + $this->assertTrue($mergedConfig->doNotFailOnSkipped()); + + $this->assertFalse($mergedConfig->displayDetailsOnPhpunitDeprecations()); + $this->assertFalse($mergedConfig->displayDetailsOnPhpunitNotices()); + $this->assertFalse($mergedConfig->displayDetailsOnTestsThatTriggerDeprecations()); + $this->assertFalse($mergedConfig->displayDetailsOnTestsThatTriggerNotices()); + $this->assertFalse($mergedConfig->displayDetailsOnTestsThatTriggerWarnings()); + $this->assertFalse($mergedConfig->displayDetailsOnIncompleteTests()); + $this->assertFalse($mergedConfig->displayDetailsOnSkippedTests()); + } +} diff --git a/tests/unit/TextUI/Configuration/Value/ConstantCollectionTest.php b/tests/unit/TextUI/Configuration/Value/ConstantCollectionTest.php new file mode 100644 index 00000000000..d01426fcf8e --- /dev/null +++ b/tests/unit/TextUI/Configuration/Value/ConstantCollectionTest.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(ConstantCollection::class)] +#[CoversClass(ConstantCollectionIterator::class)] +#[UsesClass(Constant::class)] +#[Small] +final class ConstantCollectionTest extends TestCase +{ + public function testIsCreatedFromArray(): void + { + $element = $this->element(); + $elements = ConstantCollection::fromArray([$element]); + + $this->assertSame([$element], $elements->asArray()); + } + + public function testIsCountable(): void + { + $element = $this->element(); + $elements = ConstantCollection::fromArray([$element]); + + $this->assertCount(1, $elements); + } + + public function testIsIterable(): void + { + $element = $this->element(); + $elements = ConstantCollection::fromArray([$element]); + + foreach ($elements as $index => $_constant) { + $this->assertSame(0, $index); + $this->assertSame($element, $_constant); + } + } + + private function element(): Constant + { + return new Constant('name', 'value'); + } +} diff --git a/tests/unit/TextUI/Configuration/Value/ConstantTest.php b/tests/unit/TextUI/Configuration/Value/ConstantTest.php new file mode 100644 index 00000000000..96ec65acc76 --- /dev/null +++ b/tests/unit/TextUI/Configuration/Value/ConstantTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Constant::class)] +#[Small] +final class ConstantTest extends TestCase +{ + public function testHasName(): void + { + $this->assertSame('name', new Constant('name', 'value')->name()); + } + + public function testHasValue(): void + { + $this->assertSame('value', new Constant('name', 'value')->value()); + $this->assertSame(true, new Constant('name', true)->value()); + } +} diff --git a/tests/unit/TextUI/Configuration/Value/DirectoryCollectionTest.php b/tests/unit/TextUI/Configuration/Value/DirectoryCollectionTest.php new file mode 100644 index 00000000000..6031909affd --- /dev/null +++ b/tests/unit/TextUI/Configuration/Value/DirectoryCollectionTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(DirectoryCollection::class)] +#[CoversClass(DirectoryCollectionIterator::class)] +#[UsesClass(Directory::class)] +#[Small] +final class DirectoryCollectionTest extends TestCase +{ + public function testIsCreatedFromArray(): void + { + $element = $this->element(); + $elements = DirectoryCollection::fromArray([$element]); + + $this->assertSame([$element], $elements->asArray()); + } + + public function testIsCountable(): void + { + $element = $this->element(); + $elements = DirectoryCollection::fromArray([$element]); + + $this->assertCount(1, $elements); + $this->assertFalse($elements->isEmpty()); + } + + public function testIsIterable(): void + { + $element = $this->element(); + $elements = DirectoryCollection::fromArray([$element]); + + foreach ($elements as $index => $_constant) { + $this->assertSame(0, $index); + $this->assertSame($element, $_constant); + } + } + + private function element(): Directory + { + return new Directory('path'); + } +} diff --git a/tests/unit/TextUI/Configuration/Value/DirectoryTest.php b/tests/unit/TextUI/Configuration/Value/DirectoryTest.php new file mode 100644 index 00000000000..ad66f2040b6 --- /dev/null +++ b/tests/unit/TextUI/Configuration/Value/DirectoryTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Directory::class)] +#[Small] +final class DirectoryTest extends TestCase +{ + public function testHasPath(): void + { + $path = 'path'; + + $this->assertSame($path, new Directory($path)->path()); + } +} diff --git a/tests/unit/TextUI/Configuration/Value/ExtensionBootstrapCollectionTest.php b/tests/unit/TextUI/Configuration/Value/ExtensionBootstrapCollectionTest.php new file mode 100644 index 00000000000..c5aec047506 --- /dev/null +++ b/tests/unit/TextUI/Configuration/Value/ExtensionBootstrapCollectionTest.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(ExtensionBootstrapCollection::class)] +#[CoversClass(ExtensionBootstrapCollectionIterator::class)] +#[UsesClass(ExtensionBootstrap::class)] +#[Small] +final class ExtensionBootstrapCollectionTest extends TestCase +{ + public function testIsCreatedFromArray(): void + { + $element = $this->element(); + $elements = ExtensionBootstrapCollection::fromArray([$element]); + + $this->assertSame([$element], $elements->asArray()); + } + + public function testIsIterable(): void + { + $element = $this->element(); + $elements = ExtensionBootstrapCollection::fromArray([$element]); + + foreach ($elements as $index => $_constant) { + $this->assertSame(0, $index); + $this->assertSame($element, $_constant); + } + } + + private function element(): ExtensionBootstrap + { + return new ExtensionBootstrap('ClassName', []); + } +} diff --git a/tests/unit/TextUI/Configuration/Value/ExtensionBootstrapTest.php b/tests/unit/TextUI/Configuration/Value/ExtensionBootstrapTest.php new file mode 100644 index 00000000000..81296710a86 --- /dev/null +++ b/tests/unit/TextUI/Configuration/Value/ExtensionBootstrapTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(ExtensionBootstrap::class)] +#[Small] +final class ExtensionBootstrapTest extends TestCase +{ + public function testHasClassName(): void + { + $className = 'ClassName'; + + $this->assertSame($className, new ExtensionBootstrap($className, [])->className()); + } + + public function testHasParameters(): void + { + $parameters = ['foo' => 'bar']; + + $this->assertSame($parameters, new ExtensionBootstrap('ClassName', $parameters)->parameters()); + } +} diff --git a/tests/unit/TextUI/Configuration/Value/FileCollectionTest.php b/tests/unit/TextUI/Configuration/Value/FileCollectionTest.php new file mode 100644 index 00000000000..e79ef89df39 --- /dev/null +++ b/tests/unit/TextUI/Configuration/Value/FileCollectionTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(FileCollection::class)] +#[CoversClass(FileCollectionIterator::class)] +#[UsesClass(File::class)] +#[Small] +final class FileCollectionTest extends TestCase +{ + public function testIsCreatedFromArray(): void + { + $element = $this->element(); + $elements = FileCollection::fromArray([$element]); + + $this->assertSame([$element], $elements->asArray()); + } + + public function testIsCountable(): void + { + $element = $this->element(); + $elements = FileCollection::fromArray([$element]); + + $this->assertCount(1, $elements); + $this->assertTrue($elements->notEmpty()); + } + + public function testIsIterable(): void + { + $element = $this->element(); + $elements = FileCollection::fromArray([$element]); + + foreach ($elements as $index => $_constant) { + $this->assertSame(0, $index); + $this->assertSame($element, $_constant); + } + } + + private function element(): File + { + return new File('path'); + } +} diff --git a/tests/unit/TextUI/Configuration/Value/FileTest.php b/tests/unit/TextUI/Configuration/Value/FileTest.php new file mode 100644 index 00000000000..1defb5d8cb1 --- /dev/null +++ b/tests/unit/TextUI/Configuration/Value/FileTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(File::class)] +#[Small] +final class FileTest extends TestCase +{ + public function testHasPath(): void + { + $path = 'path'; + + $this->assertSame($path, new File($path)->path()); + } +} diff --git a/tests/unit/TextUI/Configuration/Value/FilterDirectoryCollectionTest.php b/tests/unit/TextUI/Configuration/Value/FilterDirectoryCollectionTest.php new file mode 100644 index 00000000000..5f96b7958fb --- /dev/null +++ b/tests/unit/TextUI/Configuration/Value/FilterDirectoryCollectionTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(FilterDirectoryCollection::class)] +#[CoversClass(FilterDirectoryCollectionIterator::class)] +#[UsesClass(FilterDirectory::class)] +#[Small] +final class FilterDirectoryCollectionTest extends TestCase +{ + public function testIsCreatedFromArray(): void + { + $element = $this->element(); + $elements = FilterDirectoryCollection::fromArray([$element]); + + $this->assertSame([$element], $elements->asArray()); + } + + public function testIsCountable(): void + { + $element = $this->element(); + $elements = FilterDirectoryCollection::fromArray([$element]); + + $this->assertCount(1, $elements); + $this->assertTrue($elements->notEmpty()); + } + + public function testIsIterable(): void + { + $element = $this->element(); + $elements = FilterDirectoryCollection::fromArray([$element]); + + foreach ($elements as $index => $_constant) { + $this->assertSame(0, $index); + $this->assertSame($element, $_constant); + } + } + + private function element(): FilterDirectory + { + return new FilterDirectory('path', 'prefix', 'suffix'); + } +} diff --git a/tests/unit/TextUI/Configuration/Value/FilterDirectoryTest.php b/tests/unit/TextUI/Configuration/Value/FilterDirectoryTest.php new file mode 100644 index 00000000000..8611a5b0b31 --- /dev/null +++ b/tests/unit/TextUI/Configuration/Value/FilterDirectoryTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(FilterDirectory::class)] +#[Small] +final class FilterDirectoryTest extends TestCase +{ + public function testHasPath(): void + { + $path = 'path'; + + $this->assertSame($path, new FilterDirectory($path, 'prefix', 'suffix')->path()); + } + + public function testHasPrefix(): void + { + $prefix = 'prefix'; + + $this->assertSame($prefix, new FilterDirectory('path', $prefix, 'suffix')->prefix()); + } + + public function testHasSuffix(): void + { + $suffix = 'suffix'; + + $this->assertSame($suffix, new FilterDirectory('path', 'prefix', $suffix)->suffix()); + } +} diff --git a/tests/unit/TextUI/Configuration/Value/GroupCollectionTest.php b/tests/unit/TextUI/Configuration/Value/GroupCollectionTest.php new file mode 100644 index 00000000000..32a229526ef --- /dev/null +++ b/tests/unit/TextUI/Configuration/Value/GroupCollectionTest.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(GroupCollection::class)] +#[CoversClass(GroupCollectionIterator::class)] +#[UsesClass(Group::class)] +#[Small] +final class GroupCollectionTest extends TestCase +{ + public function testIsCreatedFromArray(): void + { + $element = $this->element(); + $elements = GroupCollection::fromArray([$element]); + + $this->assertSame([$element], $elements->asArray()); + $this->assertFalse($elements->isEmpty()); + } + + public function testIsIterable(): void + { + $element = $this->element(); + $elements = GroupCollection::fromArray([$element]); + + foreach ($elements as $index => $_constant) { + $this->assertSame(0, $index); + $this->assertSame($element, $_constant); + } + } + + public function testCanBeRepresentedAsArrayOfStrings(): void + { + $elements = GroupCollection::fromArray( + [ + new Group('foo'), + new Group('bar'), + ], + ); + + $this->assertSame(['foo', 'bar'], $elements->asArrayOfStrings()); + } + + private function element(): Group + { + return new Group('name'); + } +} diff --git a/tests/unit/TextUI/Configuration/Value/GroupTest.php b/tests/unit/TextUI/Configuration/Value/GroupTest.php new file mode 100644 index 00000000000..5dc506eaac0 --- /dev/null +++ b/tests/unit/TextUI/Configuration/Value/GroupTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Group::class)] +#[Small] +final class GroupTest extends TestCase +{ + public function testHasPath(): void + { + $name = 'name'; + + $this->assertSame($name, new Group($name)->name()); + } +} diff --git a/tests/unit/TextUI/Configuration/Value/IniSettingCollectionTest.php b/tests/unit/TextUI/Configuration/Value/IniSettingCollectionTest.php new file mode 100644 index 00000000000..11c5f9a6fe8 --- /dev/null +++ b/tests/unit/TextUI/Configuration/Value/IniSettingCollectionTest.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(IniSettingCollection::class)] +#[CoversClass(IniSettingCollectionIterator::class)] +#[UsesClass(IniSetting::class)] +#[Small] +final class IniSettingCollectionTest extends TestCase +{ + public function testIsCreatedFromArray(): void + { + $element = $this->element(); + $elements = IniSettingCollection::fromArray([$element]); + + $this->assertSame([$element], $elements->asArray()); + } + + public function testIsCountable(): void + { + $element = $this->element(); + $elements = IniSettingCollection::fromArray([$element]); + + $this->assertCount(1, $elements); + } + + public function testIsIterable(): void + { + $element = $this->element(); + $elements = IniSettingCollection::fromArray([$element]); + + foreach ($elements as $index => $_IniSetting) { + $this->assertSame(0, $index); + $this->assertSame($element, $_IniSetting); + } + } + + private function element(): IniSetting + { + return new IniSetting('name', 'value'); + } +} diff --git a/tests/unit/TextUI/Configuration/Value/IniSettingTest.php b/tests/unit/TextUI/Configuration/Value/IniSettingTest.php new file mode 100644 index 00000000000..a4d93f259de --- /dev/null +++ b/tests/unit/TextUI/Configuration/Value/IniSettingTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(IniSetting::class)] +#[Small] +final class IniSettingTest extends TestCase +{ + public function testHasName(): void + { + $this->assertSame('name', new IniSetting('name', 'value')->name()); + } + + public function testHasValue(): void + { + $this->assertSame('value', new IniSetting('name', 'value')->value()); + } +} diff --git a/tests/unit/TextUI/Configuration/Value/PhpTest.php b/tests/unit/TextUI/Configuration/Value/PhpTest.php new file mode 100644 index 00000000000..db139ffedfd --- /dev/null +++ b/tests/unit/TextUI/Configuration/Value/PhpTest.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Php::class)] +#[Small] +final class PhpTest extends TestCase +{ + private readonly DirectoryCollection $includePaths; + private readonly IniSettingCollection $iniSettings; + private readonly ConstantCollection $constants; + private readonly VariableCollection $globalVariables; + private readonly VariableCollection $envVariables; + private readonly VariableCollection $postVariables; + private readonly VariableCollection $getVariables; + private readonly VariableCollection $cookieVariables; + private readonly VariableCollection $serverVariables; + private readonly VariableCollection $filesVariables; + private readonly VariableCollection $requestVariables; + private readonly Php $fixture; + + protected function setUp(): void + { + $this->includePaths = DirectoryCollection::fromArray([]); + $this->iniSettings = IniSettingCollection::fromArray([]); + $this->constants = ConstantCollection::fromArray([]); + $this->globalVariables = VariableCollection::fromArray([]); + $this->envVariables = VariableCollection::fromArray([]); + $this->postVariables = VariableCollection::fromArray([]); + $this->getVariables = VariableCollection::fromArray([]); + $this->cookieVariables = VariableCollection::fromArray([]); + $this->serverVariables = VariableCollection::fromArray([]); + $this->filesVariables = VariableCollection::fromArray([]); + $this->requestVariables = VariableCollection::fromArray([]); + + $this->fixture = new Php( + $this->includePaths, + $this->iniSettings, + $this->constants, + $this->globalVariables, + $this->envVariables, + $this->postVariables, + $this->getVariables, + $this->cookieVariables, + $this->serverVariables, + $this->filesVariables, + $this->requestVariables, + ); + } + + public function testHasIncludePaths(): void + { + $this->assertSame($this->includePaths, $this->fixture->includePaths()); + } + + public function testHasIniSettings(): void + { + $this->assertSame($this->iniSettings, $this->fixture->iniSettings()); + } + + public function testHasConstants(): void + { + $this->assertSame($this->constants, $this->fixture->constants()); + } + + public function testHasGlobalVariables(): void + { + $this->assertSame($this->globalVariables, $this->fixture->globalVariables()); + } + + public function testHasEnvVariables(): void + { + $this->assertSame($this->envVariables, $this->fixture->envVariables()); + } + + public function testHasPostVariables(): void + { + $this->assertSame($this->postVariables, $this->fixture->postVariables()); + } + + public function testHasGetVariables(): void + { + $this->assertSame($this->getVariables, $this->fixture->getVariables()); + } + + public function testHasCookieVariables(): void + { + $this->assertSame($this->cookieVariables, $this->fixture->cookieVariables()); + } + + public function testHasServerVariables(): void + { + $this->assertSame($this->serverVariables, $this->fixture->serverVariables()); + } + + public function testHasFilesVariables(): void + { + $this->assertSame($this->filesVariables, $this->fixture->filesVariables()); + } + + public function testHasRequestVariables(): void + { + $this->assertSame($this->requestVariables, $this->fixture->requestVariables()); + } +} diff --git a/tests/unit/TextUI/Configuration/Value/SourceTest.php b/tests/unit/TextUI/Configuration/Value/SourceTest.php new file mode 100644 index 00000000000..cda89543505 --- /dev/null +++ b/tests/unit/TextUI/Configuration/Value/SourceTest.php @@ -0,0 +1,831 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Source::class)] +#[UsesClass(FilterDirectory::class)] +#[UsesClass(FilterDirectoryCollection::class)] +#[UsesClass(FilterDirectoryCollectionIterator::class)] +#[UsesClass(File::class)] +#[UsesClass(FileCollection::class)] +#[UsesClass(FileCollectionIterator::class)] +#[Small] +final class SourceTest extends TestCase +{ + public function testHasIncludeDirectories(): void + { + $includeDirectories = FilterDirectoryCollection::fromArray([]); + + $source = new Source( + null, + false, + $includeDirectories, + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + false, + false, + false, + false, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertSame($includeDirectories, $source->includeDirectories()); + } + + public function testHasIncludeFiles(): void + { + $includeFiles = FileCollection::fromArray([]); + + $source = new Source( + null, + false, + FilterDirectoryCollection::fromArray([]), + $includeFiles, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + false, + false, + false, + false, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertSame($includeFiles, $source->includeFiles()); + } + + public function testHasExcludeDirectories(): void + { + $excludeDirectories = FilterDirectoryCollection::fromArray([]); + + $source = new Source( + null, + false, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + $excludeDirectories, + FileCollection::fromArray([]), + false, + false, + false, + false, + false, + false, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertSame($excludeDirectories, $source->excludeDirectories()); + } + + public function testHasExcludeFiles(): void + { + $excludeFiles = FileCollection::fromArray([]); + + $source = new Source( + null, + false, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + $excludeFiles, + false, + false, + false, + false, + false, + false, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertSame($excludeFiles, $source->excludeFiles()); + } + + public function testMayHaveBaseline(): void + { + $baseline = 'baseline.xml'; + + $source = new Source( + $baseline, + false, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + false, + false, + false, + false, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertSame($baseline, $source->baseline()); + $this->assertTrue($source->hasBaseline()); + $this->assertTrue($source->useBaseline()); + } + + public function testMayNotHaveBaseline(): void + { + $source = new Source( + null, + false, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + false, + false, + false, + false, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertFalse($source->hasBaseline()); + $this->assertFalse($source->useBaseline()); + + $this->expectException(NoBaselineException::class); + + $source->baseline(); + } + + public function testRestrictionOfNoticesMayBeDisabled(): void + { + $source = new Source( + null, + false, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + false, + false, + false, + false, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertFalse($source->restrictNotices()); + } + + public function testRestrictionOfNoticesMayBeEnabled(): void + { + $source = new Source( + null, + false, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + true, + false, + false, + false, + false, + false, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertTrue($source->restrictNotices()); + } + + public function testRestrictionOfWarningsMayBeDisabled(): void + { + $source = new Source( + null, + false, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + false, + false, + false, + false, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertFalse($source->restrictWarnings()); + } + + public function testRestrictionOfWarningsMayBeEnabled(): void + { + $source = new Source( + null, + false, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + true, + false, + false, + false, + false, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertTrue($source->restrictWarnings()); + } + + public function testIgnoringOfSuppressedDeprecationsMayBeDisabled(): void + { + $source = new Source( + null, + false, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + false, + false, + false, + false, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertFalse($source->ignoreSuppressionOfDeprecations()); + } + + public function testIgnoringOfSuppressedDeprecationsMayBeEnabled(): void + { + $source = new Source( + null, + false, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + true, + false, + false, + false, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertTrue($source->ignoreSuppressionOfDeprecations()); + } + + public function testIgnoringOfSuppressedPhpDeprecationsMayBeDisabled(): void + { + $source = new Source( + null, + false, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + false, + false, + false, + false, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertFalse($source->ignoreSuppressionOfPhpDeprecations()); + } + + public function testIgnoringOfSuppressedPhpDeprecationsMayBeEnabled(): void + { + $source = new Source( + null, + false, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + false, + true, + false, + false, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertTrue($source->ignoreSuppressionOfPhpDeprecations()); + } + + public function testIgnoringOfSuppressedErrorsMayBeDisabled(): void + { + $source = new Source( + null, + false, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + false, + false, + false, + false, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertFalse($source->ignoreSuppressionOfErrors()); + } + + public function testIgnoringOfSuppressedErrorsMayBeEnabled(): void + { + $source = new Source( + null, + false, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + false, + false, + true, + false, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertTrue($source->ignoreSuppressionOfErrors()); + } + + public function testIgnoringOfSuppressedNoticesMayBeDisabled(): void + { + $source = new Source( + null, + false, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + false, + false, + false, + false, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertFalse($source->ignoreSuppressionOfNotices()); + } + + public function testIgnoringOfSuppressedNoticesMayBeEnabled(): void + { + $source = new Source( + null, + false, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + false, + false, + false, + true, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertTrue($source->ignoreSuppressionOfNotices()); + } + + public function testIgnoringOfSuppressedPhpNoticesMayBeDisabled(): void + { + $source = new Source( + null, + false, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + false, + false, + false, + false, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertFalse($source->ignoreSuppressionOfPhpNotices()); + } + + public function testIgnoringOfSuppressedPhpNoticesMayBeEnabled(): void + { + $source = new Source( + null, + false, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + false, + false, + false, + false, + true, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertTrue($source->ignoreSuppressionOfPhpNotices()); + } + + public function testIgnoringOfSuppressedWarningsMayBeDisabled(): void + { + $source = new Source( + null, + false, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + false, + false, + false, + false, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertFalse($source->ignoreSuppressionOfWarnings()); + } + + public function testIgnoringOfSuppressedWarningsMayBeEnabled(): void + { + $source = new Source( + null, + false, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + false, + false, + false, + false, + false, + true, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertTrue($source->ignoreSuppressionOfWarnings()); + } + + public function testIgnoringOfSuppressedPhpWarningsMayBeDisabled(): void + { + $source = new Source( + null, + false, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + false, + false, + false, + false, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertFalse($source->ignoreSuppressionOfPhpWarnings()); + } + + public function testIgnoringOfSuppressedPhpWarningsMayBeEnabled(): void + { + $source = new Source( + null, + false, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + false, + false, + false, + false, + false, + false, + true, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertTrue($source->ignoreSuppressionOfPhpWarnings()); + } + + public function testMayBeEmpty(): void + { + $source = new Source( + null, + false, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + false, + false, + false, + false, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertFalse($source->notEmpty()); + } + + public function testMayNotBeEmpty(): void + { + $source = new Source( + null, + false, + FilterDirectoryCollection::fromArray( + [ + new FilterDirectory( + 'path', + 'prefix', + 'suffix', + ), + ], + ), + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + false, + false, + false, + false, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ); + + $this->assertTrue($source->notEmpty()); + } +} diff --git a/tests/unit/TextUI/Configuration/Value/TestDirectoryCollectionTest.php b/tests/unit/TextUI/Configuration/Value/TestDirectoryCollectionTest.php new file mode 100644 index 00000000000..ea1423c572b --- /dev/null +++ b/tests/unit/TextUI/Configuration/Value/TestDirectoryCollectionTest.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; +use PHPUnit\Util\VersionComparisonOperator; + +#[CoversClass(TestDirectoryCollection::class)] +#[CoversClass(TestDirectoryCollectionIterator::class)] +#[UsesClass(TestDirectory::class)] +#[Small] +final class TestDirectoryCollectionTest extends TestCase +{ + public function testIsCreatedFromArray(): void + { + $element = $this->element(); + $elements = TestDirectoryCollection::fromArray([$element]); + + $this->assertSame([$element], $elements->asArray()); + } + + public function testIsCountable(): void + { + $element = $this->element(); + $elements = TestDirectoryCollection::fromArray([$element]); + + $this->assertCount(1, $elements); + $this->assertFalse($elements->isEmpty()); + } + + public function testIsIterable(): void + { + $element = $this->element(); + $elements = TestDirectoryCollection::fromArray([$element]); + + foreach ($elements as $index => $_constant) { + $this->assertSame(0, $index); + $this->assertSame($element, $_constant); + } + } + + private function element(): TestDirectory + { + return new TestDirectory( + 'path', + 'prefix', + 'suffix', + '8.2.0', + new VersionComparisonOperator('>='), + [], + ); + } +} diff --git a/tests/unit/TextUI/Configuration/Value/TestDirectoryTest.php b/tests/unit/TextUI/Configuration/Value/TestDirectoryTest.php new file mode 100644 index 00000000000..040c5e316fd --- /dev/null +++ b/tests/unit/TextUI/Configuration/Value/TestDirectoryTest.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use PHPUnit\Util\VersionComparisonOperator; + +#[CoversClass(TestDirectory::class)] +#[Small] +final class TestDirectoryTest extends TestCase +{ + public function testHasPath(): void + { + $this->assertSame('path', $this->fixture()->path()); + } + + public function testHasPrefix(): void + { + $this->assertSame('prefix', $this->fixture()->prefix()); + } + + public function testHasSuffix(): void + { + $this->assertSame('suffix', $this->fixture()->suffix()); + } + + public function testHasPhpVersion(): void + { + $this->assertSame('8.2.0', $this->fixture()->phpVersion()); + } + + public function testHasPhpVersionOperator(): void + { + $this->assertSame('>=', $this->fixture()->phpVersionOperator()->asString()); + } + + public function testHasGroups(): void + { + $this->assertSame(['group'], $this->fixture()->groups()); + } + + private function fixture(): TestDirectory + { + return new TestDirectory( + 'path', + 'prefix', + 'suffix', + '8.2.0', + new VersionComparisonOperator('>='), + ['group'], + ); + } +} diff --git a/tests/unit/TextUI/Configuration/Value/TestFileCollectionTest.php b/tests/unit/TextUI/Configuration/Value/TestFileCollectionTest.php new file mode 100644 index 00000000000..7691b4cb2d2 --- /dev/null +++ b/tests/unit/TextUI/Configuration/Value/TestFileCollectionTest.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; +use PHPUnit\Util\VersionComparisonOperator; + +#[CoversClass(TestFileCollection::class)] +#[CoversClass(TestFileCollectionIterator::class)] +#[UsesClass(TestFile::class)] +#[Small] +final class TestFileCollectionTest extends TestCase +{ + public function testIsCreatedFromArray(): void + { + $element = $this->element(); + $elements = TestFileCollection::fromArray([$element]); + + $this->assertSame([$element], $elements->asArray()); + } + + public function testIsCountable(): void + { + $element = $this->element(); + $elements = TestFileCollection::fromArray([$element]); + + $this->assertCount(1, $elements); + $this->assertFalse($elements->isEmpty()); + } + + public function testIsIterable(): void + { + $element = $this->element(); + $elements = TestFileCollection::fromArray([$element]); + + foreach ($elements as $index => $_constant) { + $this->assertSame(0, $index); + $this->assertSame($element, $_constant); + } + } + + private function element(): TestFile + { + return new TestFile( + 'path', + '8.2.0', + new VersionComparisonOperator('>='), + [], + ); + } +} diff --git a/tests/unit/TextUI/Configuration/Value/TestFileTest.php b/tests/unit/TextUI/Configuration/Value/TestFileTest.php new file mode 100644 index 00000000000..63b2914f005 --- /dev/null +++ b/tests/unit/TextUI/Configuration/Value/TestFileTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use PHPUnit\Util\VersionComparisonOperator; + +#[CoversClass(TestFile::class)] +#[Small] +final class TestFileTest extends TestCase +{ + public function testHasPath(): void + { + $this->assertSame('path', $this->fixture()->path()); + } + + public function testHasPhpVersion(): void + { + $this->assertSame('8.2.0', $this->fixture()->phpVersion()); + } + + public function testHasPhpVersionOperator(): void + { + $this->assertSame('>=', $this->fixture()->phpVersionOperator()->asString()); + } + + public function testHasGroups(): void + { + $this->assertSame(['group'], $this->fixture()->groups()); + } + + private function fixture(): TestFile + { + return new TestFile( + 'path', + '8.2.0', + new VersionComparisonOperator('>='), + ['group'], + ); + } +} diff --git a/tests/unit/TextUI/Configuration/Value/TestSuiteCollectionTest.php b/tests/unit/TextUI/Configuration/Value/TestSuiteCollectionTest.php new file mode 100644 index 00000000000..e3dd3644c3c --- /dev/null +++ b/tests/unit/TextUI/Configuration/Value/TestSuiteCollectionTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(TestSuiteCollection::class)] +#[CoversClass(TestSuiteCollectionIterator::class)] +#[UsesClass(TestSuite::class)] +#[Small] +final class TestSuiteCollectionTest extends TestCase +{ + public function testIsCreatedFromArray(): void + { + $element = $this->element(); + $elements = TestSuiteCollection::fromArray([$element]); + + $this->assertSame([$element], $elements->asArray()); + } + + public function testIsCountable(): void + { + $element = $this->element(); + $elements = TestSuiteCollection::fromArray([$element]); + + $this->assertCount(1, $elements); + $this->assertFalse($elements->isEmpty()); + } + + public function testIsIterable(): void + { + $element = $this->element(); + $elements = TestSuiteCollection::fromArray([$element]); + + foreach ($elements as $index => $_constant) { + $this->assertSame(0, $index); + $this->assertSame($element, $_constant); + } + } + + private function element(): TestSuite + { + return new TestSuite( + 'name', + TestDirectoryCollection::fromArray([]), + TestFileCollection::fromArray([]), + FileCollection::fromArray([]), + ); + } +} diff --git a/tests/unit/TextUI/Configuration/Value/TestSuiteTest.php b/tests/unit/TextUI/Configuration/Value/TestSuiteTest.php new file mode 100644 index 00000000000..6c417fa8297 --- /dev/null +++ b/tests/unit/TextUI/Configuration/Value/TestSuiteTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(TestSuite::class)] +#[Small] +final class TestSuiteTest extends TestCase +{ + private readonly string $name; + private readonly TestDirectoryCollection $directories; + private readonly TestFileCollection $files; + private readonly FileCollection $excludedFiles; + private readonly TestSuite $fixture; + + protected function setUp(): void + { + $this->name = 'name'; + $this->directories = TestDirectoryCollection::fromArray([]); + $this->files = TestFileCollection::fromArray([]); + $this->excludedFiles = FileCollection::fromArray([]); + + $this->fixture = new TestSuite( + $this->name, + $this->directories, + $this->files, + $this->excludedFiles, + ); + } + + public function testHasName(): void + { + $this->assertSame($this->name, $this->fixture->name()); + } + + public function testDirectories(): void + { + $this->assertSame($this->directories, $this->fixture->directories()); + } + + public function testHasFiles(): void + { + $this->assertSame($this->files, $this->fixture->files()); + } + + public function testHasExcludedFiles(): void + { + $this->assertSame($this->excludedFiles, $this->fixture->exclude()); + } +} diff --git a/tests/unit/TextUI/Configuration/Value/VariableCollectionTest.php b/tests/unit/TextUI/Configuration/Value/VariableCollectionTest.php new file mode 100644 index 00000000000..ad2f686e173 --- /dev/null +++ b/tests/unit/TextUI/Configuration/Value/VariableCollectionTest.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(VariableCollection::class)] +#[CoversClass(VariableCollectionIterator::class)] +#[UsesClass(Variable::class)] +#[Small] +final class VariableCollectionTest extends TestCase +{ + public function testIsCreatedFromArray(): void + { + $element = $this->element(); + $elements = VariableCollection::fromArray([$element]); + + $this->assertSame([$element], $elements->asArray()); + } + + public function testIsCountable(): void + { + $element = $this->element(); + $elements = VariableCollection::fromArray([$element]); + + $this->assertCount(1, $elements); + } + + public function testIsIterable(): void + { + $element = $this->element(); + $elements = VariableCollection::fromArray([$element]); + + foreach ($elements as $index => $_Variable) { + $this->assertSame(0, $index); + $this->assertSame($element, $_Variable); + } + } + + private function element(): Variable + { + return new Variable('name', 'value', false); + } +} diff --git a/tests/unit/TextUI/Configuration/Value/VariableTest.php b/tests/unit/TextUI/Configuration/Value/VariableTest.php new file mode 100644 index 00000000000..6981aa28c61 --- /dev/null +++ b/tests/unit/TextUI/Configuration/Value/VariableTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Variable::class)] +#[Small] +final class VariableTest extends TestCase +{ + public function testHasName(): void + { + $this->assertSame('name', new Variable('name', 'value', false)->name()); + } + + public function testHasValue(): void + { + $this->assertSame('value', new Variable('name', 'value', false)->value()); + } + + public function testValueCanBeForced(): void + { + $this->assertFalse(new Variable('name', 'value', false)->force()); + $this->assertTrue(new Variable('name', 'value', true)->force()); + } +} diff --git a/tests/unit/TextUI/Configuration/Xml/GeneratorTest.php b/tests/unit/TextUI/Configuration/Xml/GeneratorTest.php new file mode 100644 index 00000000000..375596e5797 --- /dev/null +++ b/tests/unit/TextUI/Configuration/Xml/GeneratorTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Generator::class)] +#[Small] +final class GeneratorTest extends TestCase +{ + public function testGeneratesConfigurationCorrectly(): void + { + $generator = new Generator; + + $this->assertEquals( + ' + + + + tests + + + + + + src + + + +', + $generator->generateDefaultConfiguration( + 'https://schema.phpunit.de/X.Y/phpunit.xsd', + 'vendor/autoload.php', + 'tests', + 'src', + '.phpunit.cache', + ), + ); + } +} diff --git a/tests/unit/TextUI/Configuration/Xml/LoaderTest.php b/tests/unit/TextUI/Configuration/Xml/LoaderTest.php new file mode 100644 index 00000000000..cecb5a77e95 --- /dev/null +++ b/tests/unit/TextUI/Configuration/Xml/LoaderTest.php @@ -0,0 +1,435 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use const DIRECTORY_SEPARATOR; +use const PHP_EOL; +use const PHP_VERSION; +use function file_put_contents; +use function iterator_to_array; +use function realpath; +use function sys_get_temp_dir; +use function uniqid; +use function unlink; +use PHPUnit\Framework\Attributes\CoversNamespace; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\TestCase; +use PHPUnit\Runner\TestSuiteSorter; +use PHPUnit\TextUI\Configuration\Configuration; +use SebastianBergmann\CodeCoverage\Report\Html\Colors; +use SebastianBergmann\CodeCoverage\Report\Thresholds; + +#[CoversNamespace('PHPUnit\TextUI\XmlConfiguration')] +#[Medium] +final class LoaderTest extends TestCase +{ + public static function configurationRootOptionsProvider(): array + { + return [ + 'executionOrder default' => ['executionOrder', 'default', TestSuiteSorter::ORDER_DEFAULT], + 'executionOrder random' => ['executionOrder', 'random', TestSuiteSorter::ORDER_RANDOMIZED], + 'executionOrder reverse' => ['executionOrder', 'reverse', TestSuiteSorter::ORDER_REVERSED], + 'executionOrder size' => ['executionOrder', 'size', TestSuiteSorter::ORDER_SIZE], + 'cacheDirectory absolute path' => ['cacheDirectory', '/path/to/cache', '/path/to/cache'], + 'cacheResult=false' => ['cacheResult', 'false', false], + 'cacheResult=true' => ['cacheResult', 'true', true], + 'columns' => ['columns', 'max', 'max'], + 'stopOnFailure' => ['stopOnFailure', 'true', true], + 'stopOnWarning' => ['stopOnWarning', 'true', true], + 'stopOnIncomplete' => ['stopOnIncomplete', 'true', true], + 'stopOnRisky' => ['stopOnRisky', 'true', true], + 'stopOnSkipped' => ['stopOnSkipped', 'true', true], + 'failOnEmptyTestSuite' => ['failOnEmptyTestSuite', 'true', true], + 'failOnWarning' => ['failOnWarning', 'true', true], + 'failOnRisky' => ['failOnRisky', 'true', true], + 'processIsolation' => ['processIsolation', 'true', true], + 'reverseDefectList' => ['reverseDefectList', 'true', true], + ]; + } + + public function testExceptionIsThrownForNotExistingConfigurationFile(): void + { + $this->expectException(Exception::class); + + /* @noinspection UnusedFunctionResultInspection */ + $this->configuration('not_existing_file.xml'); + } + + public function testShouldReadColorsWhenTrueInConfigurationFile(): void + { + $phpunit = $this->configuration('configuration.colors.true.xml')->phpunit(); + + $this->assertEquals(Configuration::COLOR_AUTO, $phpunit->colors()); + } + + public function testShouldReadColorsWhenFalseInConfigurationFile(): void + { + $phpunit = $this->configuration('configuration.colors.false.xml')->phpunit(); + + $this->assertEquals(Configuration::COLOR_NEVER, $phpunit->colors()); + } + + public function testShouldReadColorsWhenEmptyInConfigurationFile(): void + { + $phpunit = $this->configuration('configuration.colors.empty.xml')->phpunit(); + + $this->assertEquals(Configuration::COLOR_NEVER, $phpunit->colors()); + } + + public function testShouldReadColorsWhenInvalidInConfigurationFile(): void + { + $phpunit = $this->configuration('configuration.colors.invalid.xml')->phpunit(); + + $this->assertEquals(Configuration::COLOR_NEVER, $phpunit->colors()); + } + + public function testInvalidConfigurationGeneratesValidationErrors(): void + { + $configuration = $this->configuration('configuration.colors.invalid.xml'); + + $this->assertTrue($configuration->hasValidationErrors()); + } + + public function testShouldUseDefaultValuesForInvalidIntegers(): void + { + $phpunit = $this->configuration('configuration.columns.default.xml')->phpunit(); + + $this->assertEquals(80, $phpunit->columns()); + } + + #[DataProvider('configurationRootOptionsProvider')] + public function testShouldParseXmlConfigurationRootAttributes(string $optionName, string $optionValue, bool|int|string $expected): void + { + $tmpFilename = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'phpunit.' . $optionName . uniqid('', true) . '.xml'; + $xml = "" . PHP_EOL; + file_put_contents($tmpFilename, $xml); + + $configuration = (new Loader)->load($tmpFilename); + + $this->assertFalse($configuration->hasValidationErrors()); + + $this->assertEquals($expected, $configuration->phpunit()->{$optionName}()); + + @unlink($tmpFilename); + } + + public function testShouldParseXmlConfigurationExecutionOrderCombined(): void + { + $tmpFilename = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'phpunit.' . uniqid('', true) . '.xml'; + $xml = "" . PHP_EOL; + file_put_contents($tmpFilename, $xml); + + $configuration = (new Loader)->load($tmpFilename); + + $this->assertFalse($configuration->hasValidationErrors()); + + $this->assertTrue($configuration->phpunit()->defectsFirst()); + $this->assertTrue($configuration->phpunit()->resolveDependencies()); + + @unlink($tmpFilename); + } + + public function testSourceConfigurationIsReadCorrectly(): void + { + $source = $this->configuration('configuration_codecoverage.xml')->source(); + + $this->assertTrue($source->hasBaseline()); + $this->assertSame(realpath(__DIR__ . '/../../../../_files') . DIRECTORY_SEPARATOR . '.phpunit/baseline.xml', $source->baseline()); + + $directory = iterator_to_array($source->includeDirectories(), false)[0]; + + $this->assertSame('/path/to/files', $directory->path()); + $this->assertSame('', $directory->prefix()); + $this->assertSame('.php', $directory->suffix()); + + $file = iterator_to_array($source->includeFiles(), false)[0]; + $this->assertSame('/path/to/file', $file->path()); + + $file = iterator_to_array($source->includeFiles(), false)[1]; + $this->assertSame('/path/to/file', $file->path()); + + $directory = iterator_to_array($source->excludeDirectories(), false)[0]; + $this->assertSame('/path/to/files', $directory->path()); + $this->assertSame('', $directory->prefix()); + $this->assertSame('.php', $directory->suffix()); + + $file = iterator_to_array($source->excludeFiles(), false)[0]; + $this->assertSame('/path/to/file', $file->path()); + + $this->assertSame( + [ + 'functions' => [ + 'PHPUnit\TestFixture\DeprecationTrigger\trigger_deprecation', + ], + 'methods' => [ + 'PHPUnit\TestFixture\DeprecationTrigger\DeprecationTrigger::triggerDeprecation', + ], + ], + $source->deprecationTriggers(), + ); + + $this->assertTrue($source->ignoreSelfDeprecations()); + $this->assertTrue($source->ignoreDirectDeprecations()); + $this->assertTrue($source->ignoreIndirectDeprecations()); + } + + public function testCodeCoverageConfigurationIsReadCorrectly(): void + { + $codeCoverage = $this->configuration('configuration_codecoverage.xml')->codeCoverage(); + + $this->assertTrue($codeCoverage->pathCoverage()); + $this->assertTrue($codeCoverage->includeUncoveredFiles()); + $this->assertTrue($codeCoverage->ignoreDeprecatedCodeUnits()); + $this->assertTrue($codeCoverage->disableCodeCoverageIgnore()); + + $this->assertTrue($codeCoverage->hasClover()); + $this->assertSame(TEST_FILES_PATH . 'clover.xml', $codeCoverage->clover()->target()->path()); + + $this->assertTrue($codeCoverage->hasOpenClover()); + $this->assertSame(TEST_FILES_PATH . 'openclover.xml', $codeCoverage->openClover()->target()->path()); + + $this->assertTrue($codeCoverage->hasCobertura()); + $this->assertSame(TEST_FILES_PATH . 'cobertura.xml', $codeCoverage->cobertura()->target()->path()); + + $this->assertTrue($codeCoverage->hasCrap4j()); + $this->assertSame(TEST_FILES_PATH . 'crap4j.xml', $codeCoverage->crap4j()->target()->path()); + + $defaultColors = Colors::default(); + $defaultThresholds = Thresholds::default(); + + $this->assertTrue($codeCoverage->hasHtml()); + $this->assertSame(TEST_FILES_PATH . 'coverage', $codeCoverage->html()->target()->path()); + $this->assertSame($defaultThresholds->lowUpperBound(), $codeCoverage->html()->lowUpperBound()); + $this->assertSame($defaultThresholds->highLowerBound(), $codeCoverage->html()->highLowerBound()); + $this->assertSame($defaultColors->successLow(), $codeCoverage->html()->colorSuccessLow()); + $this->assertSame($defaultColors->successMedium(), $codeCoverage->html()->colorSuccessMedium()); + $this->assertSame($defaultColors->successHigh(), $codeCoverage->html()->colorSuccessHigh()); + $this->assertSame($defaultColors->warning(), $codeCoverage->html()->colorWarning()); + $this->assertSame($defaultColors->danger(), $codeCoverage->html()->colorDanger()); + $this->assertFalse($codeCoverage->html()->hasCustomCssFile()); + + $this->assertTrue($codeCoverage->hasPhp()); + $this->assertSame(TEST_FILES_PATH . 'coverage.php', $codeCoverage->php()->target()->path()); + + $this->assertTrue($codeCoverage->hasText()); + $this->assertSame(TEST_FILES_PATH . 'coverage.txt', $codeCoverage->text()->target()->path()); + $this->assertFalse($codeCoverage->text()->showUncoveredFiles()); + $this->assertTrue($codeCoverage->text()->showOnlySummary()); + + $this->assertTrue($codeCoverage->hasXml()); + $this->assertSame(TEST_FILES_PATH . 'coverage', $codeCoverage->xml()->target()->path()); + } + + public function testGroupConfigurationIsReadCorrectly(): void + { + $groups = $this->configuration('configuration.xml')->groups(); + + $this->assertTrue($groups->hasInclude()); + $this->assertSame(['name'], $groups->include()->asArrayOfStrings()); + + $this->assertTrue($groups->hasExclude()); + $this->assertSame(['name'], $groups->exclude()->asArrayOfStrings()); + } + + public function testExtensionConfigurationIsReadCorrectly(): void + { + $extensions = $this->configuration('configuration.xml')->extensions(); + + $this->assertCount(1, $extensions->asArray()); + + $extension = $extensions->asArray()[0]; + + $this->assertSame('MyExtension', $extension->className()); + + $this->assertSame( + [ + 'foo' => 'bar', + 'bar' => 'foo', + ], + $extension->parameters(), + ); + } + + public function testLoggingConfigurationIsReadCorrectly(): void + { + $logging = $this->configuration('configuration_logging.xml')->logging(); + + $this->assertTrue($logging->hasJunit()); + $this->assertSame(TEST_FILES_PATH . 'junit.xml', $logging->junit()->target()->path()); + + $this->assertTrue($logging->hasOtr()); + $this->assertSame(TEST_FILES_PATH . 'otr.xml', $logging->otr()->target()->path()); + $this->assertTrue($logging->otr()->includeGitInformation()); + + $this->assertTrue($logging->hasTeamCity()); + $this->assertSame(TEST_FILES_PATH . 'teamcity.txt', $logging->teamCity()->target()->path()); + + $this->assertTrue($logging->hasTestDoxHtml()); + $this->assertSame(TEST_FILES_PATH . 'testdox.html', $logging->testDoxHtml()->target()->path()); + + $this->assertTrue($logging->hasTestDoxText()); + $this->assertSame(TEST_FILES_PATH . 'testdox.txt', $logging->testDoxText()->target()->path()); + } + + public function testPHPConfigurationIsReadCorrectly(): void + { + $php = $this->configuration('configuration.xml')->php(); + + $this->assertSame(TEST_FILES_PATH . '.', $php->includePaths()->asArray()[0]->path()); + $this->assertSame('/path/to/lib', $php->includePaths()->asArray()[1]->path()); + + $this->assertSame('foo', $php->iniSettings()->asArray()[0]->name()); + $this->assertSame('bar', $php->iniSettings()->asArray()[0]->value()); + $this->assertSame('highlight.keyword', $php->iniSettings()->asArray()[1]->name()); + $this->assertSame('#123456', $php->iniSettings()->asArray()[1]->value()); + + $this->assertSame('FOO', $php->constants()->asArray()[0]->name()); + $this->assertFalse($php->constants()->asArray()[0]->value()); + $this->assertSame('BAR', $php->constants()->asArray()[1]->name()); + $this->assertTrue($php->constants()->asArray()[1]->value()); + + $this->assertSame('foo', $php->globalVariables()->asArray()[0]->name()); + $this->assertFalse($php->globalVariables()->asArray()[0]->value()); + + $this->assertSame('foo', $php->postVariables()->asArray()[0]->name()); + $this->assertSame('bar', $php->postVariables()->asArray()[0]->value()); + + $this->assertSame('foo', $php->getVariables()->asArray()[0]->name()); + $this->assertSame('bar', $php->getVariables()->asArray()[0]->value()); + + $this->assertSame('foo', $php->cookieVariables()->asArray()[0]->name()); + $this->assertSame('bar', $php->cookieVariables()->asArray()[0]->value()); + + $this->assertSame('foo', $php->serverVariables()->asArray()[0]->name()); + $this->assertSame('bar', $php->serverVariables()->asArray()[0]->value()); + + $this->assertSame('foo', $php->filesVariables()->asArray()[0]->name()); + $this->assertSame('bar', $php->filesVariables()->asArray()[0]->value()); + + $this->assertSame('foo', $php->requestVariables()->asArray()[0]->name()); + $this->assertSame('bar', $php->requestVariables()->asArray()[0]->value()); + + $this->assertSame('foo', $php->envVariables()->asArray()[0]->name()); + $this->assertTrue($php->envVariables()->asArray()[0]->value()); + $this->assertFalse($php->envVariables()->asArray()[0]->force()); + + $this->assertSame('foo_force', $php->envVariables()->asArray()[1]->name()); + $this->assertSame('forced', $php->envVariables()->asArray()[1]->value()); + $this->assertTrue($php->envVariables()->asArray()[1]->force()); + + $this->assertSame('bar', $php->envVariables()->asArray()[2]->name()); + $this->assertSame('true', $php->envVariables()->asArray()[2]->value()); + $this->assertFalse($php->envVariables()->asArray()[2]->force()); + } + + public function testPHPUnitConfigurationIsReadCorrectly(): void + { + $phpunit = $this->configuration('configuration.xml')->phpunit(); + + $this->assertTrue($phpunit->backupGlobals()); + $this->assertFalse($phpunit->backupStaticProperties()); + $this->assertFalse($phpunit->beStrictAboutChangesToGlobalState()); + $this->assertSame('/path/to/bootstrap.php', $phpunit->bootstrap()); + $this->assertSame(80, $phpunit->columns()); + $this->assertSame('never', $phpunit->colors()); + $this->assertFalse($phpunit->stderr()); + $this->assertFalse($phpunit->requireCoverageMetadata()); + $this->assertFalse($phpunit->stopOnFailure()); + $this->assertFalse($phpunit->stopOnWarning()); + $this->assertFalse($phpunit->beStrictAboutTestsThatDoNotTestAnything()); + $this->assertFalse($phpunit->beStrictAboutCoverageMetadata()); + $this->assertFalse($phpunit->beStrictAboutOutputDuringTests()); + $this->assertSame(123, $phpunit->defaultTimeLimit()); + $this->assertFalse($phpunit->enforceTimeLimit()); + $this->assertSame('/tmp', $phpunit->extensionsDirectory()); + $this->assertSame('My Test Suite', $phpunit->defaultTestSuite()); + $this->assertSame(1, $phpunit->timeoutForSmallTests()); + $this->assertSame(10, $phpunit->timeoutForMediumTests()); + $this->assertSame(60, $phpunit->timeoutForLargeTests()); + $this->assertFalse($phpunit->failOnEmptyTestSuite()); + $this->assertFalse($phpunit->failOnIncomplete()); + $this->assertFalse($phpunit->failOnRisky()); + $this->assertFalse($phpunit->failOnSkipped()); + $this->assertFalse($phpunit->failOnWarning()); + $this->assertSame(TestSuiteSorter::ORDER_DEFAULT, $phpunit->executionOrder()); + $this->assertFalse($phpunit->defectsFirst()); + $this->assertTrue($phpunit->resolveDependencies()); + $this->assertTrue($phpunit->controlGarbageCollector()); + $this->assertSame(1000, $phpunit->numberOfTestsBeforeGarbageCollection()); + $this->assertSame(10, $phpunit->shortenArraysForExportThreshold()); + } + + public function test_TestDox_configuration_is_parsed_correctly(): void + { + $configuration = $this->configuration('configuration_testdox.xml')->phpunit(); + + $this->assertTrue($configuration->testdoxPrinter()); + $this->assertTrue($configuration->testdoxPrinterSummary()); + } + + public function testConfigurationForSingleTestSuiteCanBeLoaded(): void + { + $testSuites = $this->configuration('configuration_testsuite.xml')->testSuite(); + + $this->assertCount(1, $testSuites); + + $first = $testSuites->asArray()[0]; + $this->assertSame('first', $first->name()); + $this->assertCount(1, $first->directories()); + $this->assertSame(TEST_FILES_PATH . 'tests/first', $first->directories()->asArray()[0]->path()); + $this->assertSame('', $first->directories()->asArray()[0]->prefix()); + $this->assertSame('Test.php', $first->directories()->asArray()[0]->suffix()); + $this->assertSame(PHP_VERSION, $first->directories()->asArray()[0]->phpVersion()); + $this->assertSame('>=', $first->directories()->asArray()[0]->phpVersionOperator()->asString()); + $this->assertCount(0, $first->files()); + $this->assertCount(0, $first->exclude()); + } + + public function testConfigurationForMultipleTestSuitesCanBeLoaded(): void + { + $testSuites = $this->configuration('configuration_testsuites.xml')->testSuite(); + + $this->assertCount(2, $testSuites); + + $first = $testSuites->asArray()[0]; + $this->assertSame('first', $first->name()); + $this->assertCount(1, $first->directories()); + $this->assertSame(TEST_FILES_PATH . 'tests/first', $first->directories()->asArray()[0]->path()); + $this->assertSame('', $first->directories()->asArray()[0]->prefix()); + $this->assertSame('Test.php', $first->directories()->asArray()[0]->suffix()); + $this->assertSame(PHP_VERSION, $first->directories()->asArray()[0]->phpVersion()); + $this->assertSame('>=', $first->directories()->asArray()[0]->phpVersionOperator()->asString()); + $this->assertCount(0, $first->files()); + $this->assertCount(0, $first->exclude()); + $this->assertSame(['foo'], $first->directories()->asArray()[0]->groups()); + + $second = $testSuites->asArray()[1]; + $this->assertSame('second', $second->name()); + $this->assertSame(TEST_FILES_PATH . 'tests/second', $second->directories()->asArray()[0]->path()); + $this->assertSame('test', $second->directories()->asArray()[0]->prefix()); + $this->assertSame('.phpt', $second->directories()->asArray()[0]->suffix()); + $this->assertSame('1.2.3', $second->directories()->asArray()[0]->phpVersion()); + $this->assertSame('==', $second->directories()->asArray()[0]->phpVersionOperator()->asString()); + $this->assertCount(1, $second->files()); + $this->assertSame(TEST_FILES_PATH . 'tests/file.php', $second->files()->asArray()[0]->path()); + $this->assertSame('4.5.6', $second->files()->asArray()[0]->phpVersion()); + $this->assertSame('!=', $second->files()->asArray()[0]->phpVersionOperator()->asString()); + $this->assertCount(1, $second->exclude()); + $this->assertSame(TEST_FILES_PATH . 'tests/second/_files', $second->exclude()->asArray()[0]->path()); + $this->assertSame(['bar'], $second->directories()->asArray()[0]->groups()); + $this->assertSame(['baz'], $second->files()->asArray()[0]->groups()); + } + + private function configuration(string $filename): LoadedFromFileConfiguration + { + return (new Loader)->load(TEST_FILES_PATH . $filename); + } +} diff --git a/tests/unit/TextUI/Configuration/Xml/MigratorTest.php b/tests/unit/TextUI/Configuration/Xml/MigratorTest.php new file mode 100644 index 00000000000..221d2978a13 --- /dev/null +++ b/tests/unit/TextUI/Configuration/Xml/MigratorTest.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; +use PHPUnit\Util\Xml\Loader as XmlLoader; + +final class MigratorTest extends TestCase +{ + public static function provider(): array + { + return [ + 'PHPUnit 9.2' => [ + __DIR__ . '/../../../../_files/XmlConfigurationMigration/output-9.2.xml', + __DIR__ . '/../../../../_files/XmlConfigurationMigration/input-9.2.xml', + ], + 'PHPUnit 9.5' => [ + __DIR__ . '/../../../../_files/XmlConfigurationMigration/output-9.5.xml', + __DIR__ . '/../../../../_files/XmlConfigurationMigration/input-9.5.xml', + ], + 'Relative Path' => [ + __DIR__ . '/../../../../_files/XmlConfigurationMigration/output-relative-schema-path.xml', + __DIR__ . '/../../../../_files/XmlConfigurationMigration/input-relative-schema-path.xml', + ], + 'Issue 5859' => [ + __DIR__ . '/../../../../_files/XmlConfigurationMigration/output-issue-5859.xml', + __DIR__ . '/../../../../_files/XmlConfigurationMigration/input-issue-5859.xml', + ], + 'Issue 6087' => [ + __DIR__ . '/../../../../_files/XmlConfigurationMigration/output-issue-6087.xml', + __DIR__ . '/../../../../_files/XmlConfigurationMigration/input-issue-6087.xml', + ], + ]; + } + + #[DataProvider('provider')] + public function testCanMigrateConfigurationFileThatValidatesAgainstPreviousSchema(string $output, string $input): void + { + $this->assertEquals( + (new XmlLoader)->loadFile($output), + (new XmlLoader)->load((new Migrator)->migrate($input)), + ); + } +} diff --git a/tests/unit/TextUI/Configuration/Xml/SchemaFinderTest.php b/tests/unit/TextUI/Configuration/Xml/SchemaFinderTest.php new file mode 100644 index 00000000000..6b4f703a19f --- /dev/null +++ b/tests/unit/TextUI/Configuration/Xml/SchemaFinderTest.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function count; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use PHPUnit\Runner\Version; + +#[CoversClass(SchemaFinder::class)] +#[Small] +final class SchemaFinderTest extends TestCase +{ + public function testListsAvailableSchemas(): void + { + $schemas = (new SchemaFinder)->available(); + + $this->assertSame((new Version)->series(), $schemas[0]); + $this->assertSame('8.5', $schemas[count($schemas) - 1]); + } + + public function testFindsExistingSchema(): void + { + $this->assertFileExists((new SchemaFinder)->find((new Version)->series())); + } + + public function testDoesNotFindNonExistentSchema(): void + { + $this->expectException(CannotFindSchemaException::class); + + (new SchemaFinder)->find('0.0'); + } +} diff --git a/tests/unit/TextUI/Configuration/Xml/ValidatorTest.php b/tests/unit/TextUI/Configuration/Xml/ValidatorTest.php new file mode 100644 index 00000000000..1d58e7b7ee2 --- /dev/null +++ b/tests/unit/TextUI/Configuration/Xml/ValidatorTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function file_get_contents; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use PHPUnit\Runner\Version; +use PHPUnit\Util\Xml\Loader; + +#[CoversClass(Validator::class)] +#[CoversClass(ValidationResult::class)] +#[Small] +final class ValidatorTest extends TestCase +{ + public function testValidatesValidXmlFile(): void + { + $result = (new Validator)->validate( + (new Loader)->loadFile(__DIR__ . '/../../../../../phpunit.xml'), + (new SchemaFinder)->find(Version::series()), + ); + + $this->assertFalse($result->hasValidationErrors()); + $this->assertSame('', $result->asString()); + } + + public function testDoesNotValidateInvalidXmlFile(): void + { + $result = (new Validator)->validate( + (new Loader)->loadFile( + __DIR__ . '/../../../../end-to-end/migration/_files/possibility-to-migrate-from-92-is-detected/phpunit.xml', + ), + (new SchemaFinder)->find(Version::series()), + ); + + $this->assertTrue($result->hasValidationErrors()); + $this->assertStringEqualsStringIgnoringLineEndings( + file_get_contents(__DIR__ . '/../../../../_files/invalid-configuration.txt'), + $result->asString(), + ); + } +} diff --git a/tests/unit/TextUI/Output/Default/DefaultPrinterTest.php b/tests/unit/TextUI/Output/Default/DefaultPrinterTest.php new file mode 100644 index 00000000000..d843dc379c9 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/DefaultPrinterTest.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\TestCase; +use PHPUnit\TextUI\CannotOpenSocketException; +use PHPUnit\TextUI\InvalidSocketException; +use PHPUnit\TextUI\Output\DefaultPrinter; + +#[CoversClass(DefaultPrinter::class)] +#[Medium] +final class DefaultPrinterTest extends TestCase +{ + public static function providePrinter(): array + { + $data = [ + 'standard output' => [DefaultPrinter::standardOutput()], + 'standard error' => [DefaultPrinter::standardError()], + ]; + + try { + $data['socket'] = [DefaultPrinter::from('socket://www.example.com:80')]; + } catch (CannotOpenSocketException $e) { + } + + return $data; + } + + #[DataProvider('providePrinter')] + public function testFlush(DefaultPrinter $printer): void + { + $printer->flush(); + $this->expectOutputString(''); + } + + public function testInvalidSocket(): void + { + $this->expectException(InvalidSocketException::class); + DefaultPrinter::from('socket://hostname:port:wrong'); + } +} diff --git a/tests/unit/TextUI/Output/Default/ResultPrinterTest.php b/tests/unit/TextUI/Output/Default/ResultPrinterTest.php new file mode 100644 index 00000000000..ac8465043c1 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/ResultPrinterTest.php @@ -0,0 +1,587 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default; + +use const PHP_OS; +use function hrtime; +use function stripos; +use Exception; +use PHPUnit\Event\Code\TestDoxBuilder; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\Code\ThrowableBuilder; +use PHPUnit\Event\Telemetry\Duration; +use PHPUnit\Event\Telemetry\GarbageCollectorStatus; +use PHPUnit\Event\Telemetry\HRTime; +use PHPUnit\Event\Telemetry\Info; +use PHPUnit\Event\Telemetry\MemoryUsage; +use PHPUnit\Event\Telemetry\Snapshot; +use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; +use PHPUnit\Event\Test\ConsideredRisky; +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\PhpunitDeprecationTriggered; +use PHPUnit\Event\Test\PhpunitErrorTriggered; +use PHPUnit\Event\Test\PhpunitNoticeTriggered; +use PHPUnit\Event\Test\PhpunitWarningTriggered; +use PHPUnit\Event\Test\Skipped as TestSkipped; +use PHPUnit\Event\TestData\TestDataCollection; +use PHPUnit\Event\TestRunner\DeprecationTriggered as TestRunnerDeprecationTriggered; +use PHPUnit\Event\TestRunner\NoticeTriggered as TestRunnerNoticeTriggered; +use PHPUnit\Event\TestRunner\WarningTriggered as TestRunnerWarningTriggered; +use PHPUnit\Event\TestSuite\Skipped as TestSuiteSkipped; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\IncompleteTestError; +use PHPUnit\Framework\TestCase; +use PHPUnit\Metadata\MetadataCollection; +use PHPUnit\TestRunner\TestResult\Issues\Issue; +use PHPUnit\TestRunner\TestResult\TestResult; +use PHPUnit\TextUI\Output\Printer; + +#[CoversClass(ResultPrinter::class)] +#[Medium] +final class ResultPrinterTest extends TestCase +{ + /** + * @return array + */ + public static function provider(): array + { + return [ + 'no tests' => [ + 'no_tests.txt', + self::createTestResult( + numberOfTestsRun: 0, + ), + ], + + 'successful test without issues' => [ + 'successful_test_without_issues.txt', + self::createTestResult(), + ], + + 'errored test' => [ + 'errored_test.txt', + self::createTestResult( + testErroredEvents: [ + self::erroredTest(), + ], + ), + ], + + 'failed test' => [ + 'failed_test.txt', + self::createTestResult( + testFailedEvents: [ + self::failedTest(), + ], + ), + ], + + 'incomplete test' => [ + 'incomplete_test.txt', + self::createTestResult( + testMarkedIncompleteEvents: [ + new MarkedIncomplete( + self::telemetryInfo(), + self::testMethod(), + ThrowableBuilder::from(new IncompleteTestError('message')), + ), + ], + ), + ], + + 'skipped test' => [ + 'skipped_test.txt', + self::createTestResult( + testSkippedEvents: [ + new TestSkipped( + self::telemetryInfo(), + self::testMethod(), + 'message', + ), + ], + ), + ], + + 'risky test with single-line message' => [ + 'risky_test_single_line_message.txt', + self::createTestResult( + testConsideredRiskyEvents: [ + 'Foo::testBar' => [ + self::riskyTest('message'), + ], + ], + ), + ], + + 'risky test with multiple reasons with single-line messages' => [ + 'risky_test_with_multiple_reasons_with_single_line_messages.txt', + self::createTestResult( + testConsideredRiskyEvents: [ + 'Foo::testBar' => [ + self::riskyTest('message'), + self::riskyTest('message'), + ], + ], + ), + ], + + 'risky test with multiple reasons with multi-line messages' => [ + (stripos(PHP_OS, 'WIN') === 0) ? + 'risky_test_with_multiple_reasons_with_multi_line_messages_windows.txt' : + 'risky_test_with_multiple_reasons_with_multi_line_messages.txt', + self::createTestResult( + testConsideredRiskyEvents: [ + 'Foo::testBar' => [ + self::riskyTest("message\nmessage\nmessage"), + self::riskyTest("message\nmessage\nmessage"), + ], + ], + ), + ], + + 'errored test that is risky' => [ + 'errored_test_that_is_risky.txt', + self::createTestResult( + testErroredEvents: [ + self::erroredTest(), + ], + testConsideredRiskyEvents: [ + 'Foo::testBar' => [ + self::riskyTest('message'), + ], + ], + ), + ], + + 'failed test that is risky' => [ + 'failed_test_that_is_risky.txt', + self::createTestResult( + testFailedEvents: [ + self::failedTest(), + ], + testConsideredRiskyEvents: [ + 'Foo::testBar' => [ + self::riskyTest('message'), + ], + ], + ), + ], + + 'successful test that triggers deprecation (do not display stack trace)' => [ + 'successful_test_with_deprecation.txt', + self::createTestResult( + deprecations: [ + Issue::from( + 'Foo.php', + 1, + 'message', + self::testMethod(), + ), + ], + ), + ], + + 'successful test that triggers deprecation (display stack trace)' => [ + 'successful_test_with_deprecation_with_stack_trace.txt', + self::createTestResult( + deprecations: [ + Issue::from( + 'Foo.php', + 1, + 'message', + self::testMethod(), + '/path/to/file.php:1234', + ), + ], + ), + true, + ], + + 'successful test that triggers PHP deprecation' => [ + 'successful_test_with_php_deprecation.txt', + self::createTestResult( + phpDeprecations: [ + Issue::from( + 'Foo.php', + 1, + 'message', + self::testMethod(), + ), + Issue::from( + 'Foo.php', + 2, + 'another message', + self::testMethod(), + ), + ], + ), + ], + + 'successful test that triggers the same PHP deprecation multiple times' => [ + 'successful_test_with_php_deprecation_multiple.txt', + self::createTestResult( + phpDeprecations: self::samePhpDeprecationsTriggeredTwice(), + ), + ], + + 'successful test that triggers PHPUnit deprecation' => [ + 'successful_test_with_phpunit_deprecation.txt', + self::createTestResult( + testTriggeredPhpunitDeprecationEvents: [ + 'Foo::testBar' => [ + new PhpunitDeprecationTriggered( + self::telemetryInfo(), + self::testMethod(), + 'message', + ), + ], + ], + testRunnerTriggeredDeprecationEvents: [ + new TestRunnerDeprecationTriggered( + self::telemetryInfo(), + 'message', + ), + ], + ), + ], + + 'successful test that triggers error' => [ + 'successful_test_with_error.txt', + self::createTestResult( + errors: [ + Issue::from( + 'Foo.php', + 1, + 'message', + self::testMethod(), + ), + ], + ), + ], + + 'successful test that triggers notice' => [ + 'successful_test_with_notice.txt', + self::createTestResult( + notices: [ + Issue::from( + 'Foo.php', + 1, + 'message', + self::testMethod(), + ), + ], + ), + ], + + 'successful test that triggers PHP notice' => [ + 'successful_test_with_php_notice.txt', + self::createTestResult( + phpNotices: [ + Issue::from( + 'Foo.php', + 1, + 'message', + self::testMethod(), + ), + ], + ), + ], + + 'successful test that triggers warning' => [ + 'successful_test_with_warning.txt', + self::createTestResult( + warnings: [ + Issue::from( + 'Foo.php', + 1, + 'message', + self::testMethod(), + ), + ], + ), + ], + + 'successful test that triggers PHP warning' => [ + 'successful_test_with_php_warning.txt', + self::createTestResult( + phpWarnings: [ + Issue::from( + 'Foo.php', + 1, + 'message', + self::testMethod(), + ), + ], + ), + ], + + 'test that triggers PHPUnit error' => [ + 'test_with_phpunit_error.txt', + self::createTestResult( + numberOfTests: 0, + numberOfTestsRun: 0, + numberOfAssertions: 0, + testTriggeredPhpunitErrorEvents: [ + 'Foo::testBar' => [ + new PhpunitErrorTriggered( + self::telemetryInfo(), + self::testMethod(), + 'message', + ), + ], + ], + ), + ], + + 'successful test and PHPUnit test runner notice' => [ + 'successful_test_and_phpunit_test_runner_notice.txt', + self::createTestResult( + testRunnerTriggeredNoticeEvents: [ + new TestRunnerNoticeTriggered( + self::telemetryInfo(), + 'message', + ), + ], + ), + ], + + 'successful test that triggers PHPUnit notice' => [ + 'successful_test_with_phpunit_notice.txt', + self::createTestResult( + testTriggeredPhpunitNoticeEvents: [ + 'Foo::testBar' => [ + new PhpunitNoticeTriggered( + self::telemetryInfo(), + self::testMethod(), + 'message', + ), + ], + ], + ), + ], + + 'successful test that triggers PHPUnit warning' => [ + 'successful_test_with_phpunit_warning.txt', + self::createTestResult( + testTriggeredPhpunitWarningEvents: [ + 'Foo::testBar' => [ + new PhpunitWarningTriggered( + self::telemetryInfo(), + self::testMethod(), + 'message', + false, + ), + ], + ], + ), + ], + + 'successful test that triggers baseline-ignored issue' => [ + 'successful_test_with_baseline_ignored_issue.txt', + self::createTestResult( + numberOfIssuesIgnoredByBaseline: 1, + ), + ], + + 'successful test that triggers baseline-ignored issues' => [ + 'successful_test_with_baseline_ignored_issues.txt', + self::createTestResult( + numberOfIssuesIgnoredByBaseline: 2, + ), + ], + ]; + } + + #[DataProvider('provider')] + public function testPrintsExpectedOutputForTestResultObject(string $expectationFile, TestResult $result, bool $stackTraceForDeprecations = false): void + { + $printer = $this->printer(); + + $resultPrinter = new ResultPrinter( + $printer, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false, + ); + + $resultPrinter->print($result, $stackTraceForDeprecations); + + /* @noinspection PhpPossiblePolymorphicInvocationInspection */ + $this->assertStringMatchesFormatFile( + __DIR__ . '/expectations/result/' . $expectationFile, + $printer->buffer(), + ); + } + + private function printer(): Printer + { + return new class implements Printer + { + private string $buffer = ''; + + public function print(string $buffer): void + { + $this->buffer .= $buffer; + } + + public function flush(): void + { + } + + public function buffer(): string + { + return $this->buffer; + } + }; + } + + /** + * @param list $testErroredEvents + * @param list $testFailedEvents + * @param array> $testConsideredRiskyEvents + * @param list $testSuiteSkippedEvents + * @param list $testSkippedEvents + * @param list $testMarkedIncompleteEvents + * @param list $deprecations + * @param list $phpDeprecations + * @param array> $testTriggeredPhpunitDeprecationEvents + * @param list $errors + * @param list $notices + * @param list $phpNotices + * @param list $warnings + * @param list $phpWarnings + * @param array> $testTriggeredPhpunitErrorEvents + * @param array> $testTriggeredPhpunitNoticeEvents + * @param array> $testTriggeredPhpunitWarningEvents + * @param list $testRunnerTriggeredDeprecationEvents + * @param list $testRunnerTriggeredNoticeEvents + * @param list $testRunnerTriggeredWarningEvents + */ + private static function createTestResult(int $numberOfTests = 1, int $numberOfTestsRun = 1, int $numberOfAssertions = 1, array $testErroredEvents = [], array $testFailedEvents = [], array $testConsideredRiskyEvents = [], array $testSuiteSkippedEvents = [], array $testSkippedEvents = [], array $testMarkedIncompleteEvents = [], array $deprecations = [], array $phpDeprecations = [], array $testTriggeredPhpunitDeprecationEvents = [], array $errors = [], array $notices = [], array $phpNotices = [], array $warnings = [], array $phpWarnings = [], array $testTriggeredPhpunitErrorEvents = [], array $testTriggeredPhpunitNoticeEvents = [], array $testTriggeredPhpunitWarningEvents = [], array $testRunnerTriggeredDeprecationEvents = [], array $testRunnerTriggeredNoticeEvents = [], array $testRunnerTriggeredWarningEvents = [], int $numberOfIssuesIgnoredByBaseline = 0): TestResult + { + return new TestResult( + $numberOfTests, + $numberOfTestsRun, + $numberOfAssertions, + $testErroredEvents, + $testFailedEvents, + $testConsideredRiskyEvents, + $testSuiteSkippedEvents, + $testSkippedEvents, + $testMarkedIncompleteEvents, + $testTriggeredPhpunitDeprecationEvents, + $testTriggeredPhpunitErrorEvents, + $testTriggeredPhpunitNoticeEvents, + $testTriggeredPhpunitWarningEvents, + $testRunnerTriggeredDeprecationEvents, + $testRunnerTriggeredNoticeEvents, + $testRunnerTriggeredWarningEvents, + $errors, + $deprecations, + $notices, + $warnings, + $phpDeprecations, + $phpNotices, + $phpWarnings, + $numberOfIssuesIgnoredByBaseline, + ); + } + + private static function erroredTest(): Errored + { + return new Errored( + self::telemetryInfo(), + self::testMethod(), + ThrowableBuilder::from(new Exception('message')), + ); + } + + private static function failedTest(): Failed + { + return new Failed( + self::telemetryInfo(), + self::testMethod(), + ThrowableBuilder::from( + new ExpectationFailedException( + 'Failed asserting that false is true.', + ), + ), + null, + ); + } + + private static function riskyTest(string $message): ConsideredRisky + { + return new ConsideredRisky( + self::telemetryInfo(), + self::testMethod(), + $message, + ); + } + + private static function testMethod(): TestMethod + { + return new TestMethod( + 'FooTest', + 'testBar', + 'FooTest.php', + 1, + TestDoxBuilder::fromClassNameAndMethodName('Foo', 'bar'), + MetadataCollection::fromArray([]), + TestDataCollection::fromArray([]), + ); + } + + private static function telemetryInfo(): Info + { + return new Info( + new Snapshot( + HRTime::fromSecondsAndNanoseconds(...hrtime(false)), + MemoryUsage::fromBytes(1000), + MemoryUsage::fromBytes(2000), + new GarbageCollectorStatus(0, 0, 0, 0, 0.0, 0.0, 0.0, 0.0, false, false, false, 0), + ), + Duration::fromSecondsAndNanoseconds(123, 456), + MemoryUsage::fromBytes(2000), + Duration::fromSecondsAndNanoseconds(234, 567), + MemoryUsage::fromBytes(3000), + ); + } + + private static function samePhpDeprecationsTriggeredTwice(): array + { + $issue = Issue::from( + 'Foo.php', + 1, + 'message', + self::testMethod(), + ); + + $issue->triggeredBy(self::testMethod()); + + return [$issue]; + } +} diff --git a/tests/unit/TextUI/Output/Default/SummaryPrinterTest.php b/tests/unit/TextUI/Output/Default/SummaryPrinterTest.php new file mode 100644 index 00000000000..2cb087238df --- /dev/null +++ b/tests/unit/TextUI/Output/Default/SummaryPrinterTest.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default; + +use const PHP_OS_FAMILY; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestRunner\TestResult\TestResult; +use PHPUnit\TextUI\Output\Printer; +use PHPUnit\TextUI\Output\SummaryPrinter; + +#[CoversClass(SummaryPrinter::class)] +#[Medium] +final class SummaryPrinterTest extends TestCase +{ + #[DataProviderExternal(ResultPrinterTest::class, 'provider', false)] + public function testPrintsExpectedOutputForTestResultObject(string $expectationFile, TestResult $result): void + { + $printer = $this->printer(); + + $summaryPrinter = new SummaryPrinter($printer, false); + + $summaryPrinter->print($result); + + /* @noinspection PhpPossiblePolymorphicInvocationInspection */ + $this->assertStringMatchesFormatFile( + __DIR__ . '/expectations/summary/' . $expectationFile, + $printer->buffer(), + ); + } + + #[DataProviderExternal(ResultPrinterTest::class, 'provider', false)] + public function testPrintsExpectedColouredOutputForTestResultObject(string $expectationFile, TestResult $result): void + { + if (PHP_OS_FAMILY === 'Windows') { + $this->markTestSkipped('Cannot test this behaviour on Windows'); + } + + $printer = $this->printer(); + + $summaryPrinter = new SummaryPrinter($printer, true); + + $summaryPrinter->print($result); + + /* @noinspection PhpPossiblePolymorphicInvocationInspection */ + $this->assertStringMatchesFormatFile( + __DIR__ . '/expectations/summary-coloured/' . $expectationFile, + $printer->buffer(), + ); + } + + private function printer(): Printer + { + return new class implements Printer + { + private string $buffer = ''; + + public function print(string $buffer): void + { + $this->buffer .= $buffer; + } + + public function flush(): void + { + } + + public function buffer(): string + { + return $this->buffer; + } + }; + } +} diff --git a/tests/unit/TextUI/Output/Default/expectations/result/errored_test.txt b/tests/unit/TextUI/Output/Default/expectations/result/errored_test.txt new file mode 100644 index 00000000000..adab017ac7c --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/result/errored_test.txt @@ -0,0 +1,6 @@ +There was 1 error: + +1) FooTest::testBar +Exception: message + +%A diff --git a/tests/unit/TextUI/Output/Default/expectations/result/errored_test_that_is_risky.txt b/tests/unit/TextUI/Output/Default/expectations/result/errored_test_that_is_risky.txt new file mode 100644 index 00000000000..00e82fd2d09 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/result/errored_test_that_is_risky.txt @@ -0,0 +1,15 @@ +There was 1 error: + +1) FooTest::testBar +Exception: message + +%A + +-- + +There was 1 risky test: + +1) FooTest::testBar +message + +%s:%i diff --git a/tests/unit/TextUI/Output/Default/expectations/result/failed_test.txt b/tests/unit/TextUI/Output/Default/expectations/result/failed_test.txt new file mode 100644 index 00000000000..4c2af2d945b --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/result/failed_test.txt @@ -0,0 +1,6 @@ +There was 1 failure: + +1) FooTest::testBar +Failed asserting that false is true. + +%A diff --git a/tests/unit/TextUI/Output/Default/expectations/result/failed_test_that_is_risky.txt b/tests/unit/TextUI/Output/Default/expectations/result/failed_test_that_is_risky.txt new file mode 100644 index 00000000000..54a8f601353 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/result/failed_test_that_is_risky.txt @@ -0,0 +1,15 @@ +There was 1 failure: + +1) FooTest::testBar +Failed asserting that false is true. + +%A + +-- + +There was 1 risky test: + +1) FooTest::testBar +message + +%s:%i diff --git a/tests/unit/TextUI/Output/Default/expectations/result/incomplete_test.txt b/tests/unit/TextUI/Output/Default/expectations/result/incomplete_test.txt new file mode 100644 index 00000000000..80cf7d89810 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/result/incomplete_test.txt @@ -0,0 +1,6 @@ +There was 1 incomplete test: + +1) FooTest::testBar +message + +%A diff --git a/tests/unit/TextUI/Output/Default/expectations/result/no_tests.txt b/tests/unit/TextUI/Output/Default/expectations/result/no_tests.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/unit/TextUI/Output/Default/expectations/result/risky_test_single_line_message.txt b/tests/unit/TextUI/Output/Default/expectations/result/risky_test_single_line_message.txt new file mode 100644 index 00000000000..0e520846c1a --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/result/risky_test_single_line_message.txt @@ -0,0 +1,6 @@ +There was 1 risky test: + +1) FooTest::testBar +message + +FooTest.php:1 diff --git a/tests/unit/TextUI/Output/Default/expectations/result/risky_test_with_multiple_reasons_with_multi_line_messages.txt b/tests/unit/TextUI/Output/Default/expectations/result/risky_test_with_multiple_reasons_with_multi_line_messages.txt new file mode 100644 index 00000000000..3fd85f81546 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/result/risky_test_with_multiple_reasons_with_multi_line_messages.txt @@ -0,0 +1,12 @@ +There was 1 risky test: + +1) FooTest::testBar +* message + message + message + +* message + message + message + +%s:%i diff --git a/tests/unit/TextUI/Output/Default/expectations/result/risky_test_with_multiple_reasons_with_multi_line_messages_windows.txt b/tests/unit/TextUI/Output/Default/expectations/result/risky_test_with_multiple_reasons_with_multi_line_messages_windows.txt new file mode 100644 index 00000000000..d4e06f9ad29 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/result/risky_test_with_multiple_reasons_with_multi_line_messages_windows.txt @@ -0,0 +1,12 @@ +There was 1 risky test: + +1) FooTest::testBar +* message +message +message + +* message +message +message + +%s:%i diff --git a/tests/unit/TextUI/Output/Default/expectations/result/risky_test_with_multiple_reasons_with_single_line_messages.txt b/tests/unit/TextUI/Output/Default/expectations/result/risky_test_with_multiple_reasons_with_single_line_messages.txt new file mode 100644 index 00000000000..f9cfca51031 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/result/risky_test_with_multiple_reasons_with_single_line_messages.txt @@ -0,0 +1,8 @@ +There was 1 risky test: + +1) FooTest::testBar +* message + +* message + +%s:%i diff --git a/tests/unit/TextUI/Output/Default/expectations/result/skipped_test.txt b/tests/unit/TextUI/Output/Default/expectations/result/skipped_test.txt new file mode 100644 index 00000000000..5dc6b2186fb --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/result/skipped_test.txt @@ -0,0 +1,4 @@ +There was 1 skipped test: + +1) FooTest::testBar +message diff --git a/tests/unit/TextUI/Output/Default/expectations/result/successful_test_and_phpunit_test_runner_notice.txt b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_and_phpunit_test_runner_notice.txt new file mode 100644 index 00000000000..5d62e6f6a10 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_and_phpunit_test_runner_notice.txt @@ -0,0 +1,3 @@ +There was 1 PHPUnit test runner notice: + +1) message diff --git a/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_baseline_ignored_issue.txt b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_baseline_ignored_issue.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_baseline_ignored_issues.txt b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_baseline_ignored_issues.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_deprecation.txt b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_deprecation.txt new file mode 100644 index 00000000000..57d0c61a164 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_deprecation.txt @@ -0,0 +1,9 @@ +1 test triggered 1 deprecation: + +1) Foo.php:1 +message + +Triggered by: + +* FooTest::testBar + FooTest.php:1 diff --git a/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_deprecation_with_stack_trace.txt b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_deprecation_with_stack_trace.txt new file mode 100644 index 00000000000..346a84ea8e2 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_deprecation_with_stack_trace.txt @@ -0,0 +1,11 @@ +1 test triggered 1 deprecation: + +1) Foo.php:1 +message + +/path/to/file.php:1234 + +Triggered by: + +* FooTest::testBar + FooTest.php:1 diff --git a/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_error.txt b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_error.txt new file mode 100644 index 00000000000..590e2f2a37f --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_error.txt @@ -0,0 +1,9 @@ +1 test triggered 1 error: + +1) Foo.php:1 +message + +Triggered by: + +* FooTest::testBar + FooTest.php:1 diff --git a/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_notice.txt b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_notice.txt new file mode 100644 index 00000000000..ea9652becfc --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_notice.txt @@ -0,0 +1,9 @@ +1 test triggered 1 notice: + +1) Foo.php:1 +message + +Triggered by: + +* FooTest::testBar + FooTest.php:1 diff --git a/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_php_deprecation.txt b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_php_deprecation.txt new file mode 100644 index 00000000000..812178e3681 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_php_deprecation.txt @@ -0,0 +1,17 @@ +1 test triggered 2 PHP deprecations: + +1) Foo.php:1 +message + +Triggered by: + +* FooTest::testBar + FooTest.php:1 + +2) Foo.php:2 +another message + +Triggered by: + +* FooTest::testBar + FooTest.php:1 diff --git a/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_php_deprecation_multiple.txt b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_php_deprecation_multiple.txt new file mode 100644 index 00000000000..60ba73e2e7b --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_php_deprecation_multiple.txt @@ -0,0 +1,9 @@ +1 test triggered 1 PHP deprecation: + +1) Foo.php:1 +message + +Triggered by: + +* FooTest::testBar (2 times) + FooTest.php:1 diff --git a/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_php_notice.txt b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_php_notice.txt new file mode 100644 index 00000000000..218f5a62cb1 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_php_notice.txt @@ -0,0 +1,9 @@ +1 test triggered 1 PHP notice: + +1) Foo.php:1 +message + +Triggered by: + +* FooTest::testBar + FooTest.php:1 diff --git a/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_php_warning.txt b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_php_warning.txt new file mode 100644 index 00000000000..2cc3c2d58ac --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_php_warning.txt @@ -0,0 +1,9 @@ +1 test triggered 1 PHP warning: + +1) Foo.php:1 +message + +Triggered by: + +* FooTest::testBar + FooTest.php:1 diff --git a/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_phpunit_deprecation.txt b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_phpunit_deprecation.txt new file mode 100644 index 00000000000..39ca95251a0 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_phpunit_deprecation.txt @@ -0,0 +1,12 @@ +There was 1 PHPUnit test runner deprecation: + +1) message + +-- + +1 test triggered 1 PHPUnit deprecation: + +1) FooTest::testBar +message + +%s:%i diff --git a/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_phpunit_notice.txt b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_phpunit_notice.txt new file mode 100644 index 00000000000..9a555cb975f --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_phpunit_notice.txt @@ -0,0 +1,6 @@ +1 test triggered 1 PHPUnit notice: + +1) FooTest::testBar +message + +FooTest.php:1 diff --git a/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_phpunit_warning.txt b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_phpunit_warning.txt new file mode 100644 index 00000000000..ae7719307bc --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_phpunit_warning.txt @@ -0,0 +1,6 @@ +1 test triggered 1 PHPUnit warning: + +1) FooTest::testBar +message + +%s:%i diff --git a/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_warning.txt b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_warning.txt new file mode 100644 index 00000000000..406d3cc0792 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_with_warning.txt @@ -0,0 +1,9 @@ +1 test triggered 1 warning: + +1) Foo.php:1 +message + +Triggered by: + +* FooTest::testBar + FooTest.php:1 diff --git a/tests/unit/TextUI/Output/Default/expectations/result/successful_test_without_issues.txt b/tests/unit/TextUI/Output/Default/expectations/result/successful_test_without_issues.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/unit/TextUI/Output/Default/expectations/result/test_with_phpunit_error.txt b/tests/unit/TextUI/Output/Default/expectations/result/test_with_phpunit_error.txt new file mode 100644 index 00000000000..c3bec03d8f3 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/result/test_with_phpunit_error.txt @@ -0,0 +1,6 @@ +There was 1 PHPUnit error: + +1) FooTest::testBar +message + +FooTest.php:1 diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/errored_test.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/errored_test.txt new file mode 100644 index 00000000000..72823db76a4 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/errored_test.txt @@ -0,0 +1,2 @@ +ERRORS! +Tests: 1, Assertions: 1, Errors: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/errored_test_that_is_risky.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/errored_test_that_is_risky.txt new file mode 100644 index 00000000000..9b6e37cdfd3 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/errored_test_that_is_risky.txt @@ -0,0 +1,2 @@ +ERRORS! +Tests: 1, Assertions: 1, Errors: 1, Risky: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/failed_test.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/failed_test.txt new file mode 100644 index 00000000000..181e8af35c2 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/failed_test.txt @@ -0,0 +1,2 @@ +FAILURES! +Tests: 1, Assertions: 1, Failures: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/failed_test_that_is_risky.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/failed_test_that_is_risky.txt new file mode 100644 index 00000000000..a7dbe5e08d2 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/failed_test_that_is_risky.txt @@ -0,0 +1,2 @@ +FAILURES! +Tests: 1, Assertions: 1, Failures: 1, Risky: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/incomplete_test.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/incomplete_test.txt new file mode 100644 index 00000000000..b1f01090b65 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/incomplete_test.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Incomplete: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/no_tests.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/no_tests.txt new file mode 100644 index 00000000000..d95baa6038f --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/no_tests.txt @@ -0,0 +1 @@ +No tests executed! diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/risky_test_single_line_message.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/risky_test_single_line_message.txt new file mode 100644 index 00000000000..143fe74bb69 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/risky_test_single_line_message.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Risky: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/risky_test_with_multiple_reasons_with_multi_line_messages.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/risky_test_with_multiple_reasons_with_multi_line_messages.txt new file mode 100644 index 00000000000..143fe74bb69 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/risky_test_with_multiple_reasons_with_multi_line_messages.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Risky: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/risky_test_with_multiple_reasons_with_multi_line_messages_windows.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/risky_test_with_multiple_reasons_with_multi_line_messages_windows.txt new file mode 100644 index 00000000000..2ae6f23df31 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/risky_test_with_multiple_reasons_with_multi_line_messages_windows.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Risky: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/risky_test_with_multiple_reasons_with_single_line_messages.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/risky_test_with_multiple_reasons_with_single_line_messages.txt new file mode 100644 index 00000000000..143fe74bb69 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/risky_test_with_multiple_reasons_with_single_line_messages.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Risky: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/skipped_test.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/skipped_test.txt new file mode 100644 index 00000000000..d1db9ee88f9 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/skipped_test.txt @@ -0,0 +1,2 @@ +OK, but some tests were skipped! +Tests: 1, Assertions: 1, Skipped: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_and_phpunit_test_runner_notice.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_and_phpunit_test_runner_notice.txt new file mode 100644 index 00000000000..e462ec384a9 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_and_phpunit_test_runner_notice.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Notices: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_baseline_ignored_issue.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_baseline_ignored_issue.txt new file mode 100644 index 00000000000..7a38d34254c --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_baseline_ignored_issue.txt @@ -0,0 +1,3 @@ +OK (1 test, 1 assertion) + +1 issue was ignored by baseline. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_baseline_ignored_issues.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_baseline_ignored_issues.txt new file mode 100644 index 00000000000..5d79463b8a8 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_baseline_ignored_issues.txt @@ -0,0 +1,3 @@ +OK (1 test, 1 assertion) + +2 issues were ignored by baseline. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_deprecation.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_deprecation.txt new file mode 100644 index 00000000000..16cfe86c139 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_deprecation.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Deprecations: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_deprecation_with_stack_trace.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_deprecation_with_stack_trace.txt new file mode 100644 index 00000000000..16cfe86c139 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_deprecation_with_stack_trace.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Deprecations: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_error.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_error.txt new file mode 100644 index 00000000000..97e139df0b2 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_error.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Errors: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_notice.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_notice.txt new file mode 100644 index 00000000000..48231abcb42 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_notice.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Notices: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_php_deprecation.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_php_deprecation.txt new file mode 100644 index 00000000000..4749d0a5411 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_php_deprecation.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Deprecations: 2. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_php_deprecation_multiple.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_php_deprecation_multiple.txt new file mode 100644 index 00000000000..16cfe86c139 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_php_deprecation_multiple.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Deprecations: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_php_notice.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_php_notice.txt new file mode 100644 index 00000000000..48231abcb42 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_php_notice.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Notices: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_php_warning.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_php_warning.txt new file mode 100644 index 00000000000..de9601e6f77 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_php_warning.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Warnings: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_phpunit_deprecation.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_phpunit_deprecation.txt new file mode 100644 index 00000000000..6ef7ae1a4f7 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_phpunit_deprecation.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Deprecations: 2. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_phpunit_notice.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_phpunit_notice.txt new file mode 100644 index 00000000000..e462ec384a9 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_phpunit_notice.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Notices: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_phpunit_warning.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_phpunit_warning.txt new file mode 100644 index 00000000000..3438c3cc1e6 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_phpunit_warning.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_warning.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_warning.txt new file mode 100644 index 00000000000..de9601e6f77 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_with_warning.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Warnings: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_without_issues.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_without_issues.txt new file mode 100644 index 00000000000..da9758dd4dc --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/successful_test_without_issues.txt @@ -0,0 +1 @@ +OK (1 test, 1 assertion) diff --git a/tests/unit/TextUI/Output/Default/expectations/summary-coloured/test_with_phpunit_error.txt b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/test_with_phpunit_error.txt new file mode 100644 index 00000000000..d95baa6038f --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary-coloured/test_with_phpunit_error.txt @@ -0,0 +1 @@ +No tests executed! diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/errored_test.txt b/tests/unit/TextUI/Output/Default/expectations/summary/errored_test.txt new file mode 100644 index 00000000000..49b585ccfca --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/errored_test.txt @@ -0,0 +1,2 @@ +ERRORS! +Tests: 1, Assertions: 1, Errors: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/errored_test_that_is_risky.txt b/tests/unit/TextUI/Output/Default/expectations/summary/errored_test_that_is_risky.txt new file mode 100644 index 00000000000..9c5e0b930b0 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/errored_test_that_is_risky.txt @@ -0,0 +1,2 @@ +ERRORS! +Tests: 1, Assertions: 1, Errors: 1, Risky: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/failed_test.txt b/tests/unit/TextUI/Output/Default/expectations/summary/failed_test.txt new file mode 100644 index 00000000000..ce476b0f456 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/failed_test.txt @@ -0,0 +1,2 @@ +FAILURES! +Tests: 1, Assertions: 1, Failures: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/failed_test_that_is_risky.txt b/tests/unit/TextUI/Output/Default/expectations/summary/failed_test_that_is_risky.txt new file mode 100644 index 00000000000..476745183e5 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/failed_test_that_is_risky.txt @@ -0,0 +1,2 @@ +FAILURES! +Tests: 1, Assertions: 1, Failures: 1, Risky: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/incomplete_test.txt b/tests/unit/TextUI/Output/Default/expectations/summary/incomplete_test.txt new file mode 100644 index 00000000000..529dd1c69f2 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/incomplete_test.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Incomplete: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/no_tests.txt b/tests/unit/TextUI/Output/Default/expectations/summary/no_tests.txt new file mode 100644 index 00000000000..6cf1a983e2b --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/no_tests.txt @@ -0,0 +1 @@ +No tests executed! diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/risky_test_single_line_message.txt b/tests/unit/TextUI/Output/Default/expectations/summary/risky_test_single_line_message.txt new file mode 100644 index 00000000000..2ae6f23df31 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/risky_test_single_line_message.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Risky: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/risky_test_with_multiple_reasons_with_multi_line_messages.txt b/tests/unit/TextUI/Output/Default/expectations/summary/risky_test_with_multiple_reasons_with_multi_line_messages.txt new file mode 100644 index 00000000000..2ae6f23df31 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/risky_test_with_multiple_reasons_with_multi_line_messages.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Risky: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/risky_test_with_multiple_reasons_with_multi_line_messages_windows.txt b/tests/unit/TextUI/Output/Default/expectations/summary/risky_test_with_multiple_reasons_with_multi_line_messages_windows.txt new file mode 100644 index 00000000000..2ae6f23df31 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/risky_test_with_multiple_reasons_with_multi_line_messages_windows.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Risky: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/risky_test_with_multiple_reasons_with_single_line_messages.txt b/tests/unit/TextUI/Output/Default/expectations/summary/risky_test_with_multiple_reasons_with_single_line_messages.txt new file mode 100644 index 00000000000..2ae6f23df31 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/risky_test_with_multiple_reasons_with_single_line_messages.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Risky: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/skipped_test.txt b/tests/unit/TextUI/Output/Default/expectations/summary/skipped_test.txt new file mode 100644 index 00000000000..378257774c5 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/skipped_test.txt @@ -0,0 +1,2 @@ +OK, but some tests were skipped! +Tests: 1, Assertions: 1, Skipped: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_and_phpunit_test_runner_notice.txt b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_and_phpunit_test_runner_notice.txt new file mode 100644 index 00000000000..5d8a4fb209f --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_and_phpunit_test_runner_notice.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Notices: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_baseline_ignored_issue.txt b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_baseline_ignored_issue.txt new file mode 100644 index 00000000000..1ba4579e02d --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_baseline_ignored_issue.txt @@ -0,0 +1,3 @@ +OK (1 test, 1 assertion) + +1 issue was ignored by baseline. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_baseline_ignored_issues.txt b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_baseline_ignored_issues.txt new file mode 100644 index 00000000000..cb11847ada7 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_baseline_ignored_issues.txt @@ -0,0 +1,3 @@ +OK (1 test, 1 assertion) + +2 issues were ignored by baseline. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_deprecation.txt b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_deprecation.txt new file mode 100644 index 00000000000..2501835aa6f --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_deprecation.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Deprecations: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_deprecation_with_stack_trace.txt b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_deprecation_with_stack_trace.txt new file mode 100644 index 00000000000..2501835aa6f --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_deprecation_with_stack_trace.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Deprecations: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_error.txt b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_error.txt new file mode 100644 index 00000000000..7485151bd93 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_error.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Errors: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_notice.txt b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_notice.txt new file mode 100644 index 00000000000..e5e0a403e12 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_notice.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Notices: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_php_deprecation.txt b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_php_deprecation.txt new file mode 100644 index 00000000000..952e1f6a996 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_php_deprecation.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Deprecations: 2. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_php_deprecation_multiple.txt b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_php_deprecation_multiple.txt new file mode 100644 index 00000000000..2501835aa6f --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_php_deprecation_multiple.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Deprecations: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_php_notice.txt b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_php_notice.txt new file mode 100644 index 00000000000..e5e0a403e12 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_php_notice.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Notices: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_php_warning.txt b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_php_warning.txt new file mode 100644 index 00000000000..253995cfad4 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_php_warning.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Warnings: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_phpunit_deprecation.txt b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_phpunit_deprecation.txt new file mode 100644 index 00000000000..b30542b38c1 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_phpunit_deprecation.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Deprecations: 2. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_phpunit_notice.txt b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_phpunit_notice.txt new file mode 100644 index 00000000000..5d8a4fb209f --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_phpunit_notice.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Notices: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_phpunit_warning.txt b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_phpunit_warning.txt new file mode 100644 index 00000000000..0edddfe9583 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_phpunit_warning.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, PHPUnit Warnings: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_warning.txt b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_warning.txt new file mode 100644 index 00000000000..253995cfad4 --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_with_warning.txt @@ -0,0 +1,2 @@ +OK, but there were issues! +Tests: 1, Assertions: 1, Warnings: 1. diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_without_issues.txt b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_without_issues.txt new file mode 100644 index 00000000000..06c5d69bc2b --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/successful_test_without_issues.txt @@ -0,0 +1 @@ +OK (1 test, 1 assertion) diff --git a/tests/unit/TextUI/Output/Default/expectations/summary/test_with_phpunit_error.txt b/tests/unit/TextUI/Output/Default/expectations/summary/test_with_phpunit_error.txt new file mode 100644 index 00000000000..6cf1a983e2b --- /dev/null +++ b/tests/unit/TextUI/Output/Default/expectations/summary/test_with_phpunit_error.txt @@ -0,0 +1 @@ +No tests executed! diff --git a/tests/unit/TextUI/PhpHandlerTest.php b/tests/unit/TextUI/PhpHandlerTest.php new file mode 100644 index 00000000000..020d80113cd --- /dev/null +++ b/tests/unit/TextUI/PhpHandlerTest.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use const BAR; +use const FOO; +use const PATH_SEPARATOR; +use function getenv; +use function ini_get; +use function ini_set; +use function putenv; +use PHPUnit\Framework\Attributes\BackupGlobals; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\Attributes\Ticket; +use PHPUnit\Framework\TestCase; +use PHPUnit\TextUI\XmlConfiguration\Loader; + +#[CoversClass(PhpHandler::class)] +#[Medium] +final class PhpHandlerTest extends TestCase +{ + #[BackupGlobals(true)] + public function testPHPConfigurationIsHandledCorrectly(): void + { + $savedIniHighlightKeyword = ini_get('highlight.keyword'); + + $this->handle(); + + $path = TEST_FILES_PATH . '.' . PATH_SEPARATOR . '/path/to/lib'; + $this->assertStringStartsWith($path, ini_get('include_path')); + $this->assertEquals('#123456', ini_get('highlight.keyword')); + $this->assertFalse(FOO); + $this->assertTrue(BAR); + $this->assertFalse($GLOBALS['foo']); + $this->assertTrue((bool) $_ENV['foo']); + $this->assertEquals(1, getenv('foo')); + $this->assertEquals('bar', $_POST['foo']); + $this->assertEquals('bar', $_GET['foo']); + $this->assertEquals('bar', $_COOKIE['foo']); + $this->assertEquals('bar', $_SERVER['foo']); + $this->assertEquals('bar', $_FILES['foo']); + $this->assertEquals('bar', $_REQUEST['foo']); + + ini_set('highlight.keyword', $savedIniHighlightKeyword); + } + + #[BackupGlobals(true)] + #[Ticket('https://github.com/sebastianbergmann/phpunit/issues/1181')] + public function testHandlePHPConfigurationDoesNotOverwriteExistingEnvArrayVariables(): void + { + $_ENV['foo'] = false; + + $this->handle(); + + $this->assertFalse($_ENV['foo']); + $this->assertEquals('forced', getenv('foo_force')); + } + + #[BackupGlobals(true)] + #[Ticket('https://github.com/sebastianbergmann/phpunit/issues/1181')] + public function testHandlePHPConfigurationDoesNotOverwriteVariablesFromPutEnv(): void + { + $backupFoo = getenv('foo'); + + putenv('foo=putenv'); + + $this->handle(); + + $this->assertEquals('putenv', $_ENV['foo']); + $this->assertEquals('putenv', getenv('foo')); + + if ($backupFoo === false) { + putenv('foo'); // delete variable from environment + } else { + putenv("foo={$backupFoo}"); + } + } + + #[BackupGlobals(true)] + #[Ticket('https://github.com/sebastianbergmann/phpunit/issues/1181')] + public function testHandlePHPConfigurationDoesOverwriteVariablesFromPutEnvWhenForced(): void + { + putenv('foo_force=putenv'); + + $this->handle(); + + $this->assertEquals('forced', $_ENV['foo_force']); + $this->assertEquals('forced', getenv('foo_force')); + } + + #[BackupGlobals(true)] + #[Ticket('https://github.com/sebastianbergmann/phpunit/issues/2353')] + public function testHandlePHPConfigurationDoesForceOverwrittenExistingEnvArrayVariables(): void + { + $_ENV['foo_force'] = false; + + $this->handle(); + + $this->assertEquals('forced', $_ENV['foo_force']); + $this->assertEquals('forced', getenv('foo_force')); + } + + private function handle(): void + { + $configuration = (new Loader)->load(TEST_FILES_PATH . 'configuration.xml')->php(); + + (new PhpHandler)->handle($configuration); + } +} diff --git a/tests/unit/TextUI/SourceFilterTest.php b/tests/unit/TextUI/SourceFilterTest.php new file mode 100644 index 00000000000..4b2d4fb9bce --- /dev/null +++ b/tests/unit/TextUI/SourceFilterTest.php @@ -0,0 +1,430 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function json_encode; +use function sprintf; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(SourceFilter::class)] +#[Small] +final class SourceFilterTest extends AbstractSouceFilterTestCase +{ + public static function provider(): array + { + return [ + 'file included using file' => [ + [ + self::fixturePath('a/PrefixSuffix.php') => true, + ], + self::createSource(includeFiles: FileCollection::fromArray( + [ + new File(self::fixturePath('/a/PrefixSuffix.php')), + ], + )), + ], + 'file included using file, but excluded using directory' => [ + [ + self::fixturePath('a/PrefixSuffix.php') => false, + ], + self::createSource( + includeFiles: FileCollection::fromArray([ + new File(self::fixturePath('/a/PrefixSuffix.php')), + ]), + excludeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory( + self::fixturePath('/a'), + '', + '.php', + ), + ], + ), + ), + ], + 'file included using file, but excluded using file' => [ + [ + self::fixturePath('a/PrefixSuffix.php') => false, + ], + self::createSource( + includeFiles: FileCollection::fromArray( + [ + new File(self::fixturePath('/a/PrefixSuffix.php')), + ], + ), + excludeFiles: FileCollection::fromArray( + [ + new File(self::fixturePath('/a/PrefixSuffix.php')), + ], + ), + ), + ], + 'file included using directory' => [ + [ + self::fixturePath('a/PrefixSuffix.php') => true, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory(self::fixturePath(), '', '.php'), + ], + ), + ), + ], + 'file included using directory, but excluded using file' => [ + [ + self::fixturePath('a/PrefixSuffix.php') => false, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory(self::fixturePath(), '', '.php'), + ], + ), + excludeFiles: FileCollection::fromArray( + [ + new File(self::fixturePath('/a/PrefixSuffix.php')), + ], + ), + ), + ], + 'file included using directory, but excluded using directory' => [ + [ + self::fixturePath('a/PrefixSuffix.php') => false, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory(self::fixturePath(), '', '.php'), + ], + ), + excludeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory(self::fixturePath('/a'), '', '.php'), + ], + ), + ), + ], + 'file included using directory, but wrong suffix' => [ + [ + self::fixturePath('a/PrefixSuffix.php') => false, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory(self::fixturePath(), '', 'Foobar.php'), + ], + ), + ), + ], + 'file included using directory, but wrong prefix' => [ + [ + self::fixturePath('a/PrefixSuffix.php') => false, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory(self::fixturePath(), 'WrongPrefix', '.php'), + ], + ), + ), + ], + 'file included using directory, but not excluded by suffix' => [ + [ + self::fixturePath('a/PrefixSuffix.php') => true, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory(self::fixturePath(), '', '.php'), + ], + ), + excludeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory(self::fixturePath(), '', 'WrongSuffix.php'), + ], + ), + ), + ], + 'file included using directory, but not excluded by prefix' => [ + [ + self::fixturePath('a/PrefixSuffix.php') => false, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory(self::fixturePath(), 'BadPrefix', '.php'), + ], + ), + ), + ], + 'directory wildcard does not include files at same level' => [ + [ + self::fixturePath('a/PrefixSuffix.php') => false, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory(self::fixturePath(), 'a/*', '.php'), + ], + ), + ), + ], + 'directory wildcard with suffix does not match files' => [ + [ + self::fixturePath('a/PrefixSuffix.php') => false, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory(self::fixturePath('a/Pre*'), '', '.php'), + ], + ), + ), + ], + 'directory wildcard with suffix matches directories' => [ + [ + self::fixturePath('a/PrefixSuffix.php') => true, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory(self::fixturePath('a*'), '', '.php'), + ], + ), + ), + ], + 'directory wildcard with prefix matches directories' => [ + [ + self::fixturePath('a/PrefixSuffix.php') => true, + self::fixturePath('a/c/PrefixSuffix.php') => true, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory(self::fixturePath('*a'), '', '.php'), + ], + ), + ), + ], + 'directory wildcards with prefix and suffix matches directories' => [ + [ + self::fixturePath('a/PrefixSuffix.php') => false, + self::fixturePath('a/c/PrefixSuffix.php') => true, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory(self::fixturePath('*a/c*'), '', '.php'), + ], + ), + ), + ], + 'directory wildcard includes files' => [ + [ + self::fixturePath('a/c/PrefixSuffix.php') => true, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory(self::fixturePath('a/*'), '', '.php'), + ], + ), + ), + ], + 'directory wildcard includes files in sub-directories' => [ + [ + self::fixturePath('a/c/d/PrefixSuffix.php') => true, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory(self::fixturePath('a/*'), '', '.php'), + ], + ), + ), + ], + 'wildcard includes all files' => [ + [ + self::fixturePath('a/c/d/PrefixSuffix.php') => true, + self::fixturePath('a/c/PrefixSuffix.php') => true, + self::fixturePath('a/PrefixSuffix.php') => true, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory(self::fixturePath('*'), '', '.php'), + ], + ), + ), + ], + 'globstar includes files at globstar\'s level' => [ + [ + self::fixturePath('a/c/PrefixSuffix.php') => true, + self::fixturePath('a/PrefixSuffix.php') => true, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory(self::fixturePath('a/**'), '', '.php'), + ], + ), + ), + ], + 'globstar includes files at globstar\'s level (2)' => [ + [ + self::fixturePath('a/c/PrefixSuffix.php') => true, + self::fixturePath('a/PrefixSuffix.php') => false, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory(self::fixturePath('a/c/**'), '', '.php'), + ], + ), + ), + ], + 'globstar includes all files' => [ + [ + self::fixturePath('a/c/d/PrefixSuffix.php') => true, + self::fixturePath('a/c/PrefixSuffix.php') => true, + self::fixturePath('a/PrefixSuffix.php') => true, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory( + self::fixturePath('**'), + '', + '.php', + ), + ], + ), + ), + ], + 'globstar with any single char prefix includes sibling files' => [ + [ + self::fixturePath('a/PrefixSuffix.php') => false, + self::fixturePath('a/c/PrefixSuffix.php') => true, + self::fixturePath('a/c/d/PrefixSuffix.php') => true, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory( + self::fixturePath('a/c/Z**'), + '', + '.php', + ), + ], + ), + ), + ], + 'globstar with any more than a single char prefix does not include sibling files' => [ + [ + self::fixturePath('a/PrefixSuffix.php') => false, + self::fixturePath('a/c/PrefixSuffix.php') => false, + self::fixturePath('a/c/d/PrefixSuffix.php') => false, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory( + self::fixturePath('a/c/ZZ**'), + '', + '.php', + ), + ], + ), + ), + ], + 'globstar includes zero directories' => [ + [ + self::fixturePath('a/PrefixSuffix.php') => true, + self::fixturePath('a/c/PrefixSuffix.php') => true, + self::fixturePath('a/c/d/PrefixSuffix.php') => true, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory( + self::fixturePath('**/a'), + '', + '.php', + ), + ], + ), + ), + ], + 'globstar with trailing directory' => [ + [ + self::fixturePath('a/PrefixSuffix.php') => false, + self::fixturePath('a/c/PrefixSuffix.php') => false, + self::fixturePath('a/c/d/PrefixSuffix.php') => true, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory( + self::fixturePath('/a/**/d'), + '', + '.php', + ), + ], + ), + ), + ], + 'question mark' => [ + [ + self::fixturePath('a/c/d/PrefixSuffix.php') => true, + self::fixturePath('a/c/PrefixSuffix.php') => false, + self::fixturePath('a/PrefixSuffix.php') => false, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory(self::fixturePath('a/?/d'), '', '.php'), + ], + ), + ), + ], + 'multiple question marks' => [ + [ + self::fixturePath('a/c/d/PrefixSuffix.php') => true, + self::fixturePath('b/e/PrefixSuffix.php') => false, + self::fixturePath('a/c/PrefixSuffix.php') => false, + self::fixturePath('a/PrefixSuffix.php') => false, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory(self::fixturePath('?/?/d'), '', '.php'), + ], + ), + ), + ], + ]; + } + + #[DataProvider('provider')] + public function testDeterminesWhetherFileIsIncluded(array $expectations, Source $source): void + { + foreach ($expectations as $file => $shouldInclude) { + $this->assertFileExists($file); + $this->assertSame( + $shouldInclude, + new SourceFilter((new SourceMapper)->map($source))->includes($file), + sprintf('expected match to return %s for: %s', json_encode($shouldInclude), $file), + ); + } + } +} diff --git a/tests/unit/TextUI/SourceMapperTest.php b/tests/unit/TextUI/SourceMapperTest.php new file mode 100644 index 00000000000..c4cb7387f92 --- /dev/null +++ b/tests/unit/TextUI/SourceMapperTest.php @@ -0,0 +1,352 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use const PHP_OS_FAMILY; +use Generator; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; + +#[CoversClass(SourceMapper::class)] +#[Small] +final class SourceMapperTest extends AbstractSouceFilterTestCase +{ + public static function provider(): Generator + { + yield 'file included using file' => [ + [ + self::fixturePath('a/PrefixSuffix.php') => true, + ], + self::createSource( + includeFiles: FileCollection::fromArray([ + new File(self::fixturePath('a/PrefixSuffix.php')), + ]), + ), + ]; + + yield 'file included using file, but excluded using directory' => [ + [ + ], + self::createSource( + includeFiles: FileCollection::fromArray( + [ + new File(self::fixturePath('/a/PrefixSuffix.php')), + ], + ), + excludeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory( + self::fixturePath('/a'), + '', + '.php', + ), + ], + ), + ), + ]; + + yield 'file included using file, but excluded using file' => [ + [ + ], + self::createSource( + includeFiles: FileCollection::fromArray( + [ + new File(self::fixturePath('/a/PrefixSuffix.php')), + ], + ), + excludeFiles: FileCollection::fromArray( + [ + new File(self::fixturePath('/a/PrefixSuffix.php')), + ], + ), + ), + ]; + + $fileHiddenOnUnix = self::fixturePath('a/c/.hidden/PrefixSuffix.php'); + + $expectedFiles = [ + $fileHiddenOnUnix => true, + self::fixturePath('a/PrefixSuffix.php') => true, + self::fixturePath('a/c/Prefix.php') => true, + self::fixturePath('a/c/PrefixSuffix.php') => true, + self::fixturePath('a/c/Suffix.php') => true, + self::fixturePath('a/c/d/Prefix.php') => true, + self::fixturePath('a/c/d/PrefixSuffix.php') => true, + self::fixturePath('a/c/d/Suffix.php') => true, + self::fixturePath('b/PrefixSuffix.php') => true, + self::fixturePath('b/e/PrefixSuffix.php') => true, + self::fixturePath('b/e/PrefixExampleSuffix.php') => true, + self::fixturePath('b/e/g/PrefixSuffix.php') => true, + self::fixturePath('b/f/PrefixSuffix.php') => true, + self::fixturePath('b/f/h/PrefixSuffix.php') => true, + ]; + + if (PHP_OS_FAMILY !== 'Windows') { + unset($expectedFiles[$fileHiddenOnUnix]); + } + + yield 'file included using directory' => [ + $expectedFiles, + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory( + self::fixturePath(), + '', + '.php', + ), + ], + ), + ), + ]; + + $expectedFiles = [ + $fileHiddenOnUnix => true, + self::fixturePath('a/c/Prefix.php') => true, + self::fixturePath('a/c/PrefixSuffix.php') => true, + self::fixturePath('a/c/Suffix.php') => true, + self::fixturePath('a/c/d/Prefix.php') => true, + self::fixturePath('a/c/d/PrefixSuffix.php') => true, + self::fixturePath('a/c/d/Suffix.php') => true, + self::fixturePath('b/PrefixSuffix.php') => true, + self::fixturePath('b/e/PrefixSuffix.php') => true, + self::fixturePath('b/e/PrefixExampleSuffix.php') => true, + self::fixturePath('b/e/g/PrefixSuffix.php') => true, + self::fixturePath('b/f/PrefixSuffix.php') => true, + self::fixturePath('b/f/h/PrefixSuffix.php') => true, + ]; + + if (PHP_OS_FAMILY !== 'Windows') { + unset($expectedFiles[$fileHiddenOnUnix]); + } + + yield 'file included using directory, but excluded using file' => [ + $expectedFiles, + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory( + self::fixturePath(), + '', + '.php', + ), + ], + ), + excludeFiles: FileCollection::fromArray( + [ + new File(self::fixturePath('/a/PrefixSuffix.php')), + ], + ), + ), + ]; + + yield 'file included using directory, but excluded using directory' => [ + [ + self::fixturePath('b/PrefixSuffix.php') => true, + self::fixturePath('b/e/PrefixSuffix.php') => true, + self::fixturePath('b/e/PrefixExampleSuffix.php') => true, + self::fixturePath('b/e/g/PrefixSuffix.php') => true, + self::fixturePath('b/f/PrefixSuffix.php') => true, + self::fixturePath('b/f/h/PrefixSuffix.php') => true, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory( + self::fixturePath(), + '', + '.php', + ), + ], + ), + excludeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory( + self::fixturePath('/a'), + '', + '.php', + ), + ], + ), + ), + ]; + + yield 'files included using directory and prefix' => [ + [ + self::fixturePath('b/e/PrefixExampleSuffix.php') => true, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory( + path: self::fixturePath(), + prefix: 'PrefixExample', + suffix: '.php', + ), + ], + ), + ), + ]; + + yield 'files included using directory and suffix' => [ + [ + self::fixturePath('b/e/PrefixExampleSuffix.php') => true, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory( + path: self::fixturePath(), + prefix: '', + suffix: 'ExampleSuffix.php', + ), + ], + ), + ), + ]; + + yield 'files excluded using directory and prefix' => [ + [ + self::fixturePath('a/c/Suffix.php') => true, + self::fixturePath('a/c/d/Suffix.php') => true, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory( + self::fixturePath(), + '', + '.php', + ), + ], + ), + excludeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory( + path: self::fixturePath(), + prefix: 'Prefix', + suffix: '.php', + ), + ], + ), + ), + ]; + + yield 'files excluded using directory and suffix' => [ + [ + self::fixturePath('a/c/Prefix.php') => true, + self::fixturePath('a/c/d/Prefix.php') => true, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory( + self::fixturePath(), + '', + '.php', + ), + ], + ), + excludeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory( + path: self::fixturePath(), + prefix: '', + suffix: 'Suffix.php', + ), + ], + ), + ), + ]; + + yield 'files included using same directory and different suffixes' => [ + [ + self::fixturePath('a/c/Prefix.php') => true, + self::fixturePath('a/c/d/Prefix.php') => true, + self::fixturePath('b/e/PrefixExampleSuffix.php') => true, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory( + self::fixturePath(), + '', + 'ExampleSuffix.php', + ), + new FilterDirectory( + self::fixturePath(), + '', + 'Prefix.php', + ), + ], + ), + ), + ]; + + yield 'files included using same directory and different prefixes' => [ + [ + self::fixturePath('a/c/Suffix.php') => true, + self::fixturePath('a/c/d/Suffix.php') => true, + self::fixturePath('b/e/PrefixExampleSuffix.php') => true, + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory( + self::fixturePath(), + 'Suffix', + '.php', + ), + new FilterDirectory( + self::fixturePath(), + 'PrefixExample', + '.php', + ), + ], + ), + ), + ]; + + yield 'files excluded using same directory and different prefixes' => [ + [ + ], + self::createSource( + includeDirectories: FilterDirectoryCollection::fromArray([ + new FilterDirectory( + self::fixturePath(), + '', + '.php', + ), + ]), + excludeDirectories: FilterDirectoryCollection::fromArray( + [ + new FilterDirectory( + self::fixturePath(), + 'Prefix', + '.php', + ), + new FilterDirectory( + self::fixturePath(), + 'Suffix', + '.php', + ), + ], + ), + ), + ]; + } + + #[DataProvider('provider')] + public function testDeterminesWhetherFileIsIncluded(array $expected, Source $source): void + { + $this->assertEquals($expected, (new SourceMapper)->map($source)); + } +} diff --git a/tests/unit/TextUI/XmlConfigurationTest.php b/tests/unit/TextUI/XmlConfigurationTest.php deleted file mode 100644 index e508c1d7d38..00000000000 --- a/tests/unit/TextUI/XmlConfigurationTest.php +++ /dev/null @@ -1,733 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use const BAR; -use const DIRECTORY_SEPARATOR; -use const FOO; -use const PATH_SEPARATOR; -use const PHP_EOL; -use const PHP_VERSION; -use function file_put_contents; -use function getenv; -use function ini_get; -use function ini_set; -use function iterator_to_array; -use function putenv; -use function sys_get_temp_dir; -use function uniqid; -use function unlink; -use PHPUnit\Framework\Exception; -use PHPUnit\Framework\TestCase; -use PHPUnit\Runner\StandardTestSuiteLoader; -use PHPUnit\Runner\TestSuiteSorter; -use PHPUnit\TextUI\DefaultResultPrinter; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\Directory; -use PHPUnit\Util\TestDox\CliTestDoxPrinter; -use stdClass; - -/** - * @medium - */ -final class XmlConfigurationTest extends TestCase -{ - public function testExceptionIsThrownForNotExistingConfigurationFile(): void - { - $this->expectException(Exception::class); - - /* @noinspection UnusedFunctionResultInspection */ - $this->configuration('not_existing_file.xml'); - } - - public function testShouldReadColorsWhenTrueInConfigurationFile(): void - { - $phpunit = $this->configuration('configuration.colors.true.xml')->phpunit(); - - $this->assertEquals(DefaultResultPrinter::COLOR_AUTO, $phpunit->colors()); - } - - public function testShouldReadColorsWhenFalseInConfigurationFile(): void - { - $phpunit = $this->configuration('configuration.colors.false.xml')->phpunit(); - - $this->assertEquals(DefaultResultPrinter::COLOR_NEVER, $phpunit->colors()); - } - - public function testShouldReadColorsWhenEmptyInConfigurationFile(): void - { - $phpunit = $this->configuration('configuration.colors.empty.xml')->phpunit(); - - $this->assertEquals(DefaultResultPrinter::COLOR_NEVER, $phpunit->colors()); - } - - public function testShouldReadColorsWhenInvalidInConfigurationFile(): void - { - $phpunit = $this->configuration('configuration.colors.invalid.xml')->phpunit(); - - $this->assertEquals(DefaultResultPrinter::COLOR_NEVER, $phpunit->colors()); - } - - public function testInvalidConfigurationGeneratesValidationErrors(): void - { - $configuration = $this->configuration('configuration.colors.invalid.xml'); - - $this->assertTrue($configuration->hasValidationErrors()); - } - - public function testShouldUseDefaultValuesForInvalidIntegers(): void - { - $phpunit = $this->configuration('configuration.columns.default.xml')->phpunit(); - - $this->assertEquals(80, $phpunit->columns()); - } - - /** - * @testdox Parse XML configuration root attribute $optionName = $optionValue - * @dataProvider configurationRootOptionsProvider - * - * @group test-reorder - * - * @param bool|int|string $expected - */ - public function testShouldParseXmlConfigurationRootAttributes(string $optionName, string $optionValue, $expected): void - { - $tmpFilename = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'phpunit.' . $optionName . uniqid('', true) . '.xml'; - $xml = "" . PHP_EOL; - file_put_contents($tmpFilename, $xml); - - $configuration = (new Loader)->load($tmpFilename); - - $this->assertFalse($configuration->hasValidationErrors()); - - $this->assertEquals($expected, $configuration->phpunit()->{$optionName}()); - - @unlink($tmpFilename); - } - - public function configurationRootOptionsProvider(): array - { - return [ - 'executionOrder default' => ['executionOrder', 'default', TestSuiteSorter::ORDER_DEFAULT], - 'executionOrder random' => ['executionOrder', 'random', TestSuiteSorter::ORDER_RANDOMIZED], - 'executionOrder reverse' => ['executionOrder', 'reverse', TestSuiteSorter::ORDER_REVERSED], - 'executionOrder size' => ['executionOrder', 'size', TestSuiteSorter::ORDER_SIZE], - 'cacheResult=false' => ['cacheResult', 'false', false], - 'cacheResult=true' => ['cacheResult', 'true', true], - 'cacheResultFile absolute path' => ['cacheResultFile', '/path/to/result/cache', '/path/to/result/cache'], - 'columns' => ['columns', 'max', 'max'], - 'stopOnFailure' => ['stopOnFailure', 'true', true], - 'stopOnWarning' => ['stopOnWarning', 'true', true], - 'stopOnIncomplete' => ['stopOnIncomplete', 'true', true], - 'stopOnRisky' => ['stopOnRisky', 'true', true], - 'stopOnSkipped' => ['stopOnSkipped', 'true', true], - 'failOnEmptyTestSuite' => ['failOnEmptyTestSuite', 'true', true], - 'failOnWarning' => ['failOnWarning', 'true', true], - 'failOnRisky' => ['failOnRisky', 'true', true], - 'processIsolation' => ['processIsolation', 'true', true], - 'testSuiteLoaderFile absolute path' => ['testSuiteLoaderFile', '/path/to/file', '/path/to/file'], - 'reverseDefectList' => ['reverseDefectList', 'true', true], - 'registerMockObjectsFromTestArgumentsRecursively' => ['registerMockObjectsFromTestArgumentsRecursively', 'true', true], - ]; - } - - public function testShouldParseXmlConfigurationExecutionOrderCombined(): void - { - $tmpFilename = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'phpunit.' . uniqid('', true) . '.xml'; - $xml = "" . PHP_EOL; - file_put_contents($tmpFilename, $xml); - - $configuration = (new Loader)->load($tmpFilename); - - $this->assertFalse($configuration->hasValidationErrors()); - - $this->assertTrue($configuration->phpunit()->defectsFirst()); - $this->assertTrue($configuration->phpunit()->resolveDependencies()); - - @unlink($tmpFilename); - } - - public function testCodeCoverageConfigurationIsReadCorrectly(): void - { - $codeCoverage = $this->configuration('configuration_codecoverage.xml')->codeCoverage(); - - $this->assertTrue($codeCoverage->pathCoverage()); - $this->assertTrue($codeCoverage->includeUncoveredFiles()); - $this->assertTrue($codeCoverage->processUncoveredFiles()); - $this->assertTrue($codeCoverage->ignoreDeprecatedCodeUnits()); - $this->assertTrue($codeCoverage->disableCodeCoverageIgnore()); - $this->assertTrue($codeCoverage->cacheTokens()); - - /** @var Directory $directory */ - $directory = iterator_to_array($codeCoverage->directories(), false)[0]; - $this->assertSame('/path/to/files', $directory->path()); - $this->assertSame('', $directory->prefix()); - $this->assertSame('.php', $directory->suffix()); - $this->assertSame('DEFAULT', $directory->group()); - - /** @var File $file */ - $file = iterator_to_array($codeCoverage->files(), false)[0]; - $this->assertSame('/path/to/file', $file->path()); - - /** @var File $file */ - $file = iterator_to_array($codeCoverage->files(), false)[1]; - $this->assertSame('/path/to/file', $file->path()); - - /** @var Directory $directory */ - $directory = iterator_to_array($codeCoverage->excludeDirectories(), false)[0]; - $this->assertSame('/path/to/files', $directory->path()); - $this->assertSame('', $directory->prefix()); - $this->assertSame('.php', $directory->suffix()); - $this->assertSame('DEFAULT', $directory->group()); - - /** @var File $file */ - $file = iterator_to_array($codeCoverage->excludeFiles(), false)[0]; - $this->assertSame('/path/to/file', $file->path()); - - $this->assertTrue($codeCoverage->hasClover()); - $this->assertSame(TEST_FILES_PATH . 'clover.xml', $codeCoverage->clover()->target()->path()); - - $this->assertTrue($codeCoverage->hasCrap4j()); - $this->assertSame(TEST_FILES_PATH . 'crap4j.xml', $codeCoverage->crap4j()->target()->path()); - - $this->assertTrue($codeCoverage->hasHtml()); - $this->assertSame(TEST_FILES_PATH . 'coverage', $codeCoverage->html()->target()->path()); - $this->assertSame(50, $codeCoverage->html()->lowUpperBound()); - $this->assertSame(90, $codeCoverage->html()->highLowerBound()); - - $this->assertTrue($codeCoverage->hasPhp()); - $this->assertSame(TEST_FILES_PATH . 'coverage.php', $codeCoverage->php()->target()->path()); - - $this->assertTrue($codeCoverage->hasText()); - $this->assertSame(TEST_FILES_PATH . 'coverage.txt', $codeCoverage->text()->target()->path()); - $this->assertFalse($codeCoverage->text()->showUncoveredFiles()); - $this->assertTrue($codeCoverage->text()->showOnlySummary()); - - $this->assertTrue($codeCoverage->hasXml()); - $this->assertSame(TEST_FILES_PATH . 'coverage', $codeCoverage->xml()->target()->path()); - } - - public function testLegacyCodeCoverageConfigurationIsReadCorrectly(): void - { - $codeCoverage = $this->configuration('configuration_legacy_codecoverage.xml')->codeCoverage(); - - $this->assertTrue($codeCoverage->includeUncoveredFiles()); - $this->assertTrue($codeCoverage->processUncoveredFiles()); - $this->assertTrue($codeCoverage->ignoreDeprecatedCodeUnits()); - $this->assertTrue($codeCoverage->disableCodeCoverageIgnore()); - $this->assertTrue($codeCoverage->cacheTokens()); - - /** @var Directory $directory */ - $directory = iterator_to_array($codeCoverage->directories(), false)[0]; - $this->assertSame('/path/to/files', $directory->path()); - $this->assertSame('', $directory->prefix()); - $this->assertSame('.php', $directory->suffix()); - $this->assertSame('DEFAULT', $directory->group()); - - /** @var File $file */ - $file = iterator_to_array($codeCoverage->files(), false)[0]; - $this->assertSame('/path/to/file', $file->path()); - - /** @var File $file */ - $file = iterator_to_array($codeCoverage->files(), false)[1]; - $this->assertSame('/path/to/file', $file->path()); - - /** @var Directory $directory */ - $directory = iterator_to_array($codeCoverage->excludeDirectories(), false)[0]; - $this->assertSame('/path/to/files', $directory->path()); - $this->assertSame('', $directory->prefix()); - $this->assertSame('.php', $directory->suffix()); - $this->assertSame('DEFAULT', $directory->group()); - - /** @var File $file */ - $file = iterator_to_array($codeCoverage->excludeFiles(), false)[0]; - $this->assertSame('/path/to/file', $file->path()); - - $this->assertTrue($codeCoverage->hasClover()); - $this->assertSame(TEST_FILES_PATH . 'clover.xml', $codeCoverage->clover()->target()->path()); - - $this->assertTrue($codeCoverage->hasCrap4j()); - $this->assertSame(TEST_FILES_PATH . 'crap4j.xml', $codeCoverage->crap4j()->target()->path()); - - $this->assertTrue($codeCoverage->hasHtml()); - $this->assertSame(TEST_FILES_PATH . 'coverage', $codeCoverage->html()->target()->path()); - $this->assertSame(50, $codeCoverage->html()->lowUpperBound()); - $this->assertSame(90, $codeCoverage->html()->highLowerBound()); - - $this->assertTrue($codeCoverage->hasPhp()); - $this->assertSame(TEST_FILES_PATH . 'coverage.php', $codeCoverage->php()->target()->path()); - - $this->assertTrue($codeCoverage->hasText()); - $this->assertSame(TEST_FILES_PATH . 'coverage.txt', $codeCoverage->text()->target()->path()); - $this->assertFalse($codeCoverage->text()->showUncoveredFiles()); - $this->assertTrue($codeCoverage->text()->showOnlySummary()); - - $this->assertTrue($codeCoverage->hasXml()); - $this->assertSame(TEST_FILES_PATH . 'coverage', $codeCoverage->xml()->target()->path()); - } - - public function testGroupConfigurationIsReadCorrectly(): void - { - $groups = $this->configuration('configuration.xml')->groups(); - - $this->assertTrue($groups->hasInclude()); - $this->assertSame(['name'], $groups->include()->asArrayOfStrings()); - - $this->assertTrue($groups->hasExclude()); - $this->assertSame(['name'], $groups->exclude()->asArrayOfStrings()); - } - - public function testTestdoxGroupConfigurationIsReadCorrectly(): void - { - $testdox = $this->configuration('configuration.xml')->testdoxGroups(); - - $this->assertTrue($testdox->hasInclude()); - $this->assertSame(['name'], $testdox->include()->asArrayOfStrings()); - - $this->assertTrue($testdox->hasExclude()); - $this->assertSame(['name'], $testdox->exclude()->asArrayOfStrings()); - } - - public function testListenerConfigurationIsReadCorrectly(): void - { - $dir = __DIR__; - $includePath = ini_get('include_path'); - - ini_set('include_path', $dir . PATH_SEPARATOR . $includePath); - - $i = 1; - - foreach ($this->configuration('configuration.xml')->listeners() as $listener) { - switch ($i) { - case 1: - $this->assertSame('MyListener', $listener->className()); - $this->assertTrue($listener->hasSourceFile()); - $this->assertSame('/optional/path/to/MyListener.php', $listener->sourceFile()); - $this->assertTrue($listener->hasArguments()); - $this->assertEquals( - [ - 0 => [ - 0 => 'Sebastian', - ], - 1 => 22, - 2 => 'April', - 3 => 19.78, - 4 => null, - 5 => new stdClass, - 6 => TEST_FILES_PATH . 'MyTestFile.php', - 7 => TEST_FILES_PATH . 'MyRelativePath', - 8 => true, - ], - $listener->arguments() - ); - - break; - - case 2: - $this->assertSame('IncludePathListener', $listener->className()); - $this->assertTrue($listener->hasSourceFile()); - $this->assertSame(TEST_FILES_PATH . 'ConfigurationTest.php', $listener->sourceFile()); - $this->assertFalse($listener->hasArguments()); - $this->assertSame([], $listener->arguments()); - - break; - - case 3: - $this->assertSame('CompactArgumentsListener', $listener->className()); - $this->assertTrue($listener->hasSourceFile()); - $this->assertSame('/CompactArgumentsListener.php', $listener->sourceFile()); - $this->assertTrue($listener->hasArguments()); - $this->assertSame([0 => 42, 1 => false], $listener->arguments()); - - break; - } - - $i++; - } - - ini_set('include_path', $includePath); - } - - public function testExtensionConfigurationIsReadCorrectly(): void - { - $dir = __DIR__; - $includePath = ini_get('include_path'); - - ini_set('include_path', $dir . PATH_SEPARATOR . $includePath); - - $i = 1; - - foreach ($this->configuration('configuration.xml')->extensions() as $extension) { - switch ($i) { - case 1: - $this->assertSame('MyExtension', $extension->className()); - $this->assertTrue($extension->hasSourceFile()); - $this->assertSame('/optional/path/to/MyExtension.php', $extension->sourceFile()); - $this->assertTrue($extension->hasArguments()); - $this->assertEquals( - [ - 0 => [ - 0 => 'Sebastian', - ], - 1 => 22, - 2 => 'April', - 3 => 19.78, - 4 => null, - 5 => new stdClass, - 6 => TEST_FILES_PATH . 'MyTestFile.php', - 7 => TEST_FILES_PATH . 'MyRelativePath', - ], - $extension->arguments() - ); - - break; - - case 2: - $this->assertSame('IncludePathExtension', $extension->className()); - $this->assertTrue($extension->hasSourceFile()); - $this->assertSame(TEST_FILES_PATH . 'ConfigurationTest.php', $extension->sourceFile()); - $this->assertFalse($extension->hasArguments()); - $this->assertSame([], $extension->arguments()); - - break; - - case 3: - $this->assertSame('CompactArgumentsExtension', $extension->className()); - $this->assertTrue($extension->hasSourceFile()); - $this->assertSame('/CompactArgumentsExtension.php', $extension->sourceFile()); - $this->assertTrue($extension->hasArguments()); - $this->assertSame([0 => 42], $extension->arguments()); - - break; - } - - $i++; - } - - ini_set('include_path', $includePath); - } - - public function testLoggingConfigurationIsReadCorrectly(): void - { - $logging = $this->configuration('configuration_logging.xml')->logging(); - - $this->assertTrue($logging->hasJunit()); - $this->assertSame(TEST_FILES_PATH . 'junit.xml', $logging->junit()->target()->path()); - - $this->assertTrue($logging->hasTeamCity()); - $this->assertSame(TEST_FILES_PATH . 'teamcity.txt', $logging->teamCity()->target()->path()); - - $this->assertTrue($logging->hasTestDoxHtml()); - $this->assertSame(TEST_FILES_PATH . 'testdox.html', $logging->testDoxHtml()->target()->path()); - - $this->assertTrue($logging->hasTestDoxText()); - $this->assertSame(TEST_FILES_PATH . 'testdox.txt', $logging->testDoxText()->target()->path()); - - $this->assertTrue($logging->hasTestDoxXml()); - $this->assertSame(TEST_FILES_PATH . 'testdox.xml', $logging->testDoxXml()->target()->path()); - - $this->assertTrue($logging->hasText()); - $this->assertSame(TEST_FILES_PATH . 'logfile.txt', $logging->text()->target()->path()); - } - - public function testLegacyLoggingConfigurationIsReadCorrectly(): void - { - $logging = $this->configuration('configuration_legacy_logging.xml')->logging(); - - $this->assertTrue($logging->hasJunit()); - $this->assertSame(TEST_FILES_PATH . 'junit.xml', $logging->junit()->target()->path()); - - $this->assertTrue($logging->hasTeamCity()); - $this->assertSame(TEST_FILES_PATH . 'teamcity.txt', $logging->teamCity()->target()->path()); - - $this->assertTrue($logging->hasTestDoxHtml()); - $this->assertSame(TEST_FILES_PATH . 'testdox.html', $logging->testDoxHtml()->target()->path()); - - $this->assertTrue($logging->hasTestDoxText()); - $this->assertSame(TEST_FILES_PATH . 'testdox.txt', $logging->testDoxText()->target()->path()); - - $this->assertTrue($logging->hasTestDoxXml()); - $this->assertSame(TEST_FILES_PATH . 'testdox.xml', $logging->testDoxXml()->target()->path()); - - $this->assertTrue($logging->hasText()); - $this->assertSame(TEST_FILES_PATH . 'logfile.txt', $logging->text()->target()->path()); - } - - /** - * @testdox PHP configuration is read correctly - */ - public function testPHPConfigurationIsReadCorrectly(): void - { - $php = $this->configuration('configuration.xml')->php(); - - $this->assertSame(TEST_FILES_PATH . '.', $php->includePaths()->asArray()[0]->path()); - $this->assertSame('/path/to/lib', $php->includePaths()->asArray()[1]->path()); - - $this->assertSame('foo', $php->iniSettings()->asArray()[0]->name()); - $this->assertSame('bar', $php->iniSettings()->asArray()[0]->value()); - $this->assertSame('highlight.keyword', $php->iniSettings()->asArray()[1]->name()); - $this->assertSame('#123456', $php->iniSettings()->asArray()[1]->value()); - - $this->assertSame('FOO', $php->constants()->asArray()[0]->name()); - $this->assertFalse($php->constants()->asArray()[0]->value()); - $this->assertSame('BAR', $php->constants()->asArray()[1]->name()); - $this->assertTrue($php->constants()->asArray()[1]->value()); - - $this->assertSame('foo', $php->globalVariables()->asArray()[0]->name()); - $this->assertFalse($php->globalVariables()->asArray()[0]->value()); - - $this->assertSame('foo', $php->postVariables()->asArray()[0]->name()); - $this->assertSame('bar', $php->postVariables()->asArray()[0]->value()); - - $this->assertSame('foo', $php->getVariables()->asArray()[0]->name()); - $this->assertSame('bar', $php->getVariables()->asArray()[0]->value()); - - $this->assertSame('foo', $php->cookieVariables()->asArray()[0]->name()); - $this->assertSame('bar', $php->cookieVariables()->asArray()[0]->value()); - - $this->assertSame('foo', $php->serverVariables()->asArray()[0]->name()); - $this->assertSame('bar', $php->serverVariables()->asArray()[0]->value()); - - $this->assertSame('foo', $php->filesVariables()->asArray()[0]->name()); - $this->assertSame('bar', $php->filesVariables()->asArray()[0]->value()); - - $this->assertSame('foo', $php->requestVariables()->asArray()[0]->name()); - $this->assertSame('bar', $php->requestVariables()->asArray()[0]->value()); - - $this->assertSame('foo', $php->envVariables()->asArray()[0]->name()); - $this->assertTrue($php->envVariables()->asArray()[0]->value()); - $this->assertFalse($php->envVariables()->asArray()[0]->force()); - - $this->assertSame('foo_force', $php->envVariables()->asArray()[1]->name()); - $this->assertSame('forced', $php->envVariables()->asArray()[1]->value()); - $this->assertTrue($php->envVariables()->asArray()[1]->force()); - - $this->assertSame('bar', $php->envVariables()->asArray()[2]->name()); - $this->assertSame('true', $php->envVariables()->asArray()[2]->value()); - $this->assertFalse($php->envVariables()->asArray()[2]->force()); - } - - /** - * @testdox PHP configuration is handled correctly - * @backupGlobals enabled - */ - public function testPHPConfigurationIsHandledCorrectly(): void - { - $savedIniHighlightKeyword = ini_get('highlight.keyword'); - - (new PhpHandler)->handle($this->configuration('configuration.xml')->php()); - - $path = TEST_FILES_PATH . '.' . PATH_SEPARATOR . '/path/to/lib'; - $this->assertStringStartsWith($path, ini_get('include_path')); - $this->assertEquals('#123456', ini_get('highlight.keyword')); - $this->assertFalse(FOO); - $this->assertTrue(BAR); - $this->assertFalse($GLOBALS['foo']); - $this->assertTrue((bool) $_ENV['foo']); - $this->assertEquals(1, getenv('foo')); - $this->assertEquals('bar', $_POST['foo']); - $this->assertEquals('bar', $_GET['foo']); - $this->assertEquals('bar', $_COOKIE['foo']); - $this->assertEquals('bar', $_SERVER['foo']); - $this->assertEquals('bar', $_FILES['foo']); - $this->assertEquals('bar', $_REQUEST['foo']); - - ini_set('highlight.keyword', $savedIniHighlightKeyword); - } - - /** - * @testdox handlePHPConfiguration() does not overwrite existing $ENV[] variables - * @backupGlobals enabled - * - * @see https://github.com/sebastianbergmann/phpunit/issues/1181 - */ - public function testHandlePHPConfigurationDoesNotOverwriteExistingEnvArrayVariables(): void - { - $_ENV['foo'] = false; - - (new PhpHandler)->handle($this->configuration('configuration.xml')->php()); - - $this->assertFalse($_ENV['foo']); - $this->assertEquals('forced', getenv('foo_force')); - } - - /** - * @testdox handlePHPConfiguration() does force overwritten existing $ENV[] variables - * @backupGlobals enabled - * - * @see https://github.com/sebastianbergmann/phpunit/issues/2353 - */ - public function testHandlePHPConfigurationDoesForceOverwrittenExistingEnvArrayVariables(): void - { - $_ENV['foo_force'] = false; - - (new PhpHandler)->handle($this->configuration('configuration.xml')->php()); - - $this->assertEquals('forced', $_ENV['foo_force']); - $this->assertEquals('forced', getenv('foo_force')); - } - - /** - * @testdox handlePHPConfiguration() does not overwrite variables from putenv() - * @backupGlobals enabled - * - * @see https://github.com/sebastianbergmann/phpunit/issues/1181 - */ - public function testHandlePHPConfigurationDoesNotOverwriteVariablesFromPutEnv(): void - { - $backupFoo = getenv('foo'); - - putenv('foo=putenv'); - - (new PhpHandler)->handle($this->configuration('configuration.xml')->php()); - - $this->assertEquals('putenv', $_ENV['foo']); - $this->assertEquals('putenv', getenv('foo')); - - if ($backupFoo === false) { - putenv('foo'); // delete variable from environment - } else { - putenv("foo={$backupFoo}"); - } - } - - /** - * @testdox handlePHPConfiguration() does overwrite variables from putenv() when forced - * @backupGlobals enabled - * - * @see https://github.com/sebastianbergmann/phpunit/issues/1181 - */ - public function testHandlePHPConfigurationDoesOverwriteVariablesFromPutEnvWhenForced(): void - { - putenv('foo_force=putenv'); - - (new PhpHandler)->handle($this->configuration('configuration.xml')->php()); - - $this->assertEquals('forced', $_ENV['foo_force']); - $this->assertEquals('forced', getenv('foo_force')); - } - - /** - * @testdox PHPUnit configuration is read correctly - */ - public function testPHPUnitConfigurationIsReadCorrectly(): void - { - $phpunit = $this->configuration('configuration.xml')->phpunit(); - - $this->assertTrue($phpunit->backupGlobals()); - $this->assertFalse($phpunit->backupStaticAttributes()); - $this->assertFalse($phpunit->beStrictAboutChangesToGlobalState()); - $this->assertSame('/path/to/bootstrap.php', $phpunit->bootstrap()); - $this->assertSame(80, $phpunit->columns()); - $this->assertSame('never', $phpunit->colors()); - $this->assertFalse($phpunit->stderr()); - $this->assertTrue($phpunit->convertDeprecationsToExceptions()); - $this->assertTrue($phpunit->convertErrorsToExceptions()); - $this->assertTrue($phpunit->convertNoticesToExceptions()); - $this->assertTrue($phpunit->convertWarningsToExceptions()); - $this->assertFalse($phpunit->forceCoversAnnotation()); - $this->assertFalse($phpunit->stopOnFailure()); - $this->assertFalse($phpunit->stopOnWarning()); - $this->assertFalse($phpunit->beStrictAboutTestsThatDoNotTestAnything()); - $this->assertFalse($phpunit->beStrictAboutCoversAnnotation()); - $this->assertFalse($phpunit->beStrictAboutOutputDuringTests()); - $this->assertSame(123, $phpunit->defaultTimeLimit()); - $this->assertFalse($phpunit->enforceTimeLimit()); - $this->assertSame('/tmp', $phpunit->extensionsDirectory()); - $this->assertSame(DefaultResultPrinter::class, $phpunit->printerClass()); - $this->assertSame(StandardTestSuiteLoader::class, $phpunit->testSuiteLoaderClass()); - $this->assertSame('My Test Suite', $phpunit->defaultTestSuite()); - $this->assertFalse($phpunit->verbose()); - $this->assertSame(1, $phpunit->timeoutForSmallTests()); - $this->assertSame(10, $phpunit->timeoutForMediumTests()); - $this->assertSame(60, $phpunit->timeoutForLargeTests()); - $this->assertFalse($phpunit->beStrictAboutResourceUsageDuringSmallTests()); - $this->assertFalse($phpunit->beStrictAboutTodoAnnotatedTests()); - $this->assertFalse($phpunit->failOnEmptyTestSuite()); - $this->assertFalse($phpunit->failOnIncomplete()); - $this->assertFalse($phpunit->failOnRisky()); - $this->assertFalse($phpunit->failOnSkipped()); - $this->assertFalse($phpunit->failOnWarning()); - $this->assertSame(TestSuiteSorter::ORDER_DEFAULT, $phpunit->executionOrder()); - $this->assertFalse($phpunit->defectsFirst()); - $this->assertTrue($phpunit->resolveDependencies()); - $this->assertTrue($phpunit->noInteraction()); - } - - public function test_TestDox_configuration_is_parsed_correctly(): void - { - $this->assertSame( - CliTestDoxPrinter::class, - $this->configuration('configuration_testdox.xml')->phpunit()->printerClass() - ); - } - - public function test_Conflict_between_testdox_and_printerClass_is_detected(): void - { - $phpunit = $this->configuration('configuration_testdox_printerClass.xml')->phpunit(); - - $this->assertSame(CliTestDoxPrinter::class, $phpunit->printerClass()); - $this->assertTrue($phpunit->conflictBetweenPrinterClassAndTestdox()); - } - - public function testConfigurationForSingleTestSuiteCanBeLoaded(): void - { - $testsuites = $this->configuration('configuration_testsuite.xml')->testSuite(); - - $this->assertCount(1, $testsuites); - - $first = $testsuites->asArray()[0]; - $this->assertSame('first', $first->name()); - $this->assertCount(1, $first->directories()); - $this->assertSame(TEST_FILES_PATH . 'tests/first', $first->directories()->asArray()[0]->path()); - $this->assertSame('', $first->directories()->asArray()[0]->prefix()); - $this->assertSame('Test.php', $first->directories()->asArray()[0]->suffix()); - $this->assertSame(PHP_VERSION, $first->directories()->asArray()[0]->phpVersion()); - $this->assertSame('>=', $first->directories()->asArray()[0]->phpVersionOperator()->asString()); - $this->assertCount(0, $first->files()); - $this->assertCount(0, $first->exclude()); - } - - public function testConfigurationForMultipleTestSuitesCanBeLoaded(): void - { - $testsuites = $this->configuration('configuration_testsuites.xml')->testSuite(); - - $this->assertCount(2, $testsuites); - - $first = $testsuites->asArray()[0]; - $this->assertSame('first', $first->name()); - $this->assertCount(1, $first->directories()); - $this->assertSame(TEST_FILES_PATH . 'tests/first', $first->directories()->asArray()[0]->path()); - $this->assertSame('', $first->directories()->asArray()[0]->prefix()); - $this->assertSame('Test.php', $first->directories()->asArray()[0]->suffix()); - $this->assertSame(PHP_VERSION, $first->directories()->asArray()[0]->phpVersion()); - $this->assertSame('>=', $first->directories()->asArray()[0]->phpVersionOperator()->asString()); - $this->assertCount(0, $first->files()); - $this->assertCount(0, $first->exclude()); - - $second = $testsuites->asArray()[1]; - $this->assertSame('second', $second->name()); - $this->assertSame(TEST_FILES_PATH . 'tests/second', $second->directories()->asArray()[0]->path()); - $this->assertSame('test', $second->directories()->asArray()[0]->prefix()); - $this->assertSame('.phpt', $second->directories()->asArray()[0]->suffix()); - $this->assertSame('1.2.3', $second->directories()->asArray()[0]->phpVersion()); - $this->assertSame('==', $second->directories()->asArray()[0]->phpVersionOperator()->asString()); - $this->assertCount(1, $second->files()); - $this->assertSame(TEST_FILES_PATH . 'tests/file.php', $second->files()->asArray()[0]->path()); - $this->assertSame('4.5.6', $second->files()->asArray()[0]->phpVersion()); - $this->assertSame('!=', $second->files()->asArray()[0]->phpVersionOperator()->asString()); - $this->assertCount(1, $second->exclude()); - $this->assertSame(TEST_FILES_PATH . 'tests/second/_files', $second->exclude()->asArray()[0]->path()); - } - - private function configuration(string $filename): Configuration - { - return (new Loader)->load(TEST_FILES_PATH . $filename); - } -} diff --git a/tests/unit/Util/Annotation/RegistryTest.php b/tests/unit/Util/Annotation/RegistryTest.php deleted file mode 100644 index f56e7eded0f..00000000000 --- a/tests/unit/Util/Annotation/RegistryTest.php +++ /dev/null @@ -1,90 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\Annotation; - -use PHPUnit\Framework\TestCase; -use PHPUnit\TestFixture\NumericGroupAnnotationTest; -use PHPUnit\Util\Exception; -use ThisClassDoesNotExist; - -/** - * @small - * - * @covers \PHPUnit\Util\Annotation\Registry - * - * @uses \PHPUnit\Util\Annotation\DocBlock - */ -final class RegistryTest extends TestCase -{ - public function testRegistryLookupWithExistingClassAnnotation(): void - { - $annotation = Registry::getInstance()->forClassName(self::class); - - self::assertSame( - [ - 'small' => [''], - 'covers' => ['\PHPUnit\Util\Annotation\Registry'], - 'uses' => ['\PHPUnit\Util\Annotation\DocBlock'], - ], - $annotation->symbolAnnotations() - ); - - self::assertSame( - $annotation, - Registry::getInstance()->forClassName(self::class), - 'Registry memoizes retrieved DocBlock instances' - ); - } - - public function testRegistryLookupWithExistingMethodAnnotation(): void - { - $annotation = Registry::getInstance()->forMethod( - NumericGroupAnnotationTest::class, - 'testTicketAnnotationSupportsNumericValue' - ); - - self::assertSame( - [ - 'testdox' => ['Empty test for @ticket numeric annotation values'], - 'ticket' => ['3502'], - 'author' => ['C. Lippy'], - 'see' => ['https://github.com/sebastianbergmann/phpunit/issues/3502'], - ], - $annotation->symbolAnnotations() - ); - - self::assertSame( - $annotation, - Registry::getInstance()->forMethod( - NumericGroupAnnotationTest::class, - 'testTicketAnnotationSupportsNumericValue' - ), - 'Registry memoizes retrieved DocBlock instances' - ); - } - - public function testClassLookupForAClassThatDoesNotExistFails(): void - { - $registry = Registry::getInstance(); - - $this->expectException(Exception::class); - - $registry->forClassName(ThisClassDoesNotExist::class); - } - - public function testMethodLookupForAMethodThatDoesNotExistFails(): void - { - $registry = Registry::getInstance(); - - $this->expectException(Exception::class); - - $registry->forMethod(self::class, 'thisMethodDoesNotExist'); - } -} diff --git a/tests/unit/Util/ColorTest.php b/tests/unit/Util/ColorTest.php index 0fe30549ae1..57700918e9d 100644 --- a/tests/unit/Util/ColorTest.php +++ b/tests/unit/Util/ColorTest.php @@ -10,86 +10,20 @@ namespace PHPUnit\Util; use const DIRECTORY_SEPARATOR; +use const PHP_EOL; +use function str_repeat; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; -/** - * @covers \PHPUnit\Util\Color - * @testdox Basic ANSI color highlighting support - * @small - */ +#[CoversClass(Color::class)] +#[Small] +#[TestDox('Basic ANSI color highlighting support')] final class ColorTest extends TestCase { - /** - * @testdox Colorize with $_dataName - * @dataProvider colorizeProvider - */ - public function testColorize(string $color, string $buffer, string $expected): void - { - $this->assertSame($expected, Color::colorize($color, $buffer)); - } - - /** - * @testdox Colorize path $path after $prevPath - * @dataProvider colorizePathProvider - */ - public function testColorizePath(?string $prevPath, string $path, bool $colorizeFilename, string $expected): void - { - $this->assertSame($expected, Color::colorizePath($path, $prevPath, $colorizeFilename)); - } - - /** - * @testdox dim($m) and colorize('dim',$m) return different ANSI codes - */ - public function testDimAndColorizeDimAreDifferent(): void - { - $buffer = 'some string'; - $this->assertNotSame(Color::dim($buffer), Color::colorize('dim', $buffer)); - } - - /** - * @testdox Visualize all whitespace characters in $actual - * @dataProvider whitespacedStringProvider - */ - public function testVisibleWhitespace(string $actual, string $expected): void - { - $this->assertSame($expected, Color::visualizeWhitespace($actual, true)); - } - - /** - * @testdox Visualize whitespace but ignore EOL - */ - public function testVisibleWhitespaceWithoutEOL(): void - { - $string = "line1\nline2\n"; - $this->assertSame($string, Color::visualizeWhitespace($string, false)); - } - - /** - * @dataProvider unnamedDataSetProvider - */ - public function testPrettifyUnnamedDataprovider(int $value): void - { - $this->assertSame($value, $value); - } - - /** - * @dataProvider namedDataSetProvider - */ - public function testPrettifyNamedDataprovider(int $value): void - { - $this->assertSame($value, $value); - } - - /** - * @testdox TestDox shows name of data set $_dataName with value $value - * @dataProvider namedDataSetProvider - */ - public function testTestdoxDatanameAsParameter(int $value): void - { - $this->assertSame($value, $value); - } - - public function colorizeProvider(): array + public static function colorizeProvider(): array { return [ 'no color' => ['', 'string', 'string'], @@ -97,10 +31,11 @@ public function colorizeProvider(): array 'multiple colors' => ['bold,dim,fg-blue,bg-yellow', 'string', "\x1b[1;2;34;43mstring\x1b[0m"], 'invalid color' => ['fg-invalid', 'some text', 'some text'], 'valid and invalid colors' => ['fg-invalid,bg-blue', 'some text', "\e[44msome text\e[0m"], + 'empty string' => ['fg-blue', '', ''], ]; } - public function colorizePathProvider(): array + public static function colorizePathProvider(): array { $sep = DIRECTORY_SEPARATOR; $sepDim = Color::dim($sep); @@ -139,7 +74,42 @@ public function colorizePathProvider(): array ]; } - public function whitespacedStringProvider(): array + public static function colorizeTextBoxProvider(): array + { + return [ + 'fitting text' => [ + 40, // simulate 40 char wide terminal + 'this is fine' . PHP_EOL . + PHP_EOL . + 'all lines fit nicely' . PHP_EOL . + 'bottom text', + Color::colorize('red', 'this is fine ') . PHP_EOL . + Color::colorize('red', ' ') . PHP_EOL . + Color::colorize('red', 'all lines fit nicely') . PHP_EOL . + Color::colorize('red', 'bottom text '), + ], + 'oversize text' => [ + 20, // simulate 20 char wide terminal + 'this is also fine' . PHP_EOL . + PHP_EOL . + 'the very long lines do not stretch the whole textbox' . PHP_EOL . + 'anymore', + Color::colorize('red', 'this is also fine ') . PHP_EOL . + Color::colorize('red', ' ') . PHP_EOL . + Color::colorize('red', 'the very long lines do not stretch the whole textbox') . PHP_EOL . + Color::colorize('red', 'anymore '), + ], + 'default terminal width cap' => [ + 80, // simulate (default) 80 char wide terminal + str_repeat('.123456789', 8) . PHP_EOL . + 'this is a shorter line', + Color::colorize('red', str_repeat('.123456789', 8)) . PHP_EOL . + Color::colorize('red', 'this is a shorter line '), + ], + ]; + } + + public static function whitespacedStringProvider(): array { return [ ['no-spaces', @@ -156,7 +126,7 @@ public function whitespacedStringProvider(): array ]; } - public function unnamedDataSetProvider(): array + public static function unnamedDataSetProvider(): array { return [ [1], @@ -164,11 +134,72 @@ public function unnamedDataSetProvider(): array ]; } - public function namedDataSetProvider(): array + public static function namedDataSetProvider(): array { return [ 'one' => [1], 'two' => [2], ]; } + + #[TestDox('Colorize with $_dataName')] + #[DataProvider('colorizeProvider')] + public function testColorize(string $color, string $buffer, string $expected): void + { + $this->assertSame($expected, Color::colorize($color, $buffer)); + } + + #[TestDox('Colorize path $path after $prevPath')] + #[DataProvider('colorizePathProvider')] + public function testColorizePath(?string $prevPath, string $path, bool $colorizeFilename, string $expected): void + { + $this->assertSame($expected, Color::colorizePath($path, $prevPath, $colorizeFilename)); + } + + #[TestDox('Colorize an autosizing text box')] + #[DataProvider('colorizeTextBoxProvider')] + public function testColorizeTextBox(int $columns, string $buffer, string $expected): void + { + $this->assertSame($expected, Color::colorizeTextBox('red', $buffer, $columns)); + } + + #[TestDox('dim($m) and colorize(\'dim\',$m) return different ANSI codes')] + public function testDimAndColorizeDimAreDifferent(): void + { + $buffer = 'some string'; + $this->assertNotSame(Color::dim($buffer), Color::colorize('dim', $buffer)); + } + + #[DataProvider('whitespacedStringProvider')] + #[TestDox('Visualize all whitespace characters in $actual')] + public function testVisibleWhitespace(string $actual, string $expected): void + { + $this->assertSame($expected, Color::visualizeWhitespace($actual, true)); + } + + #[TestDox('Visualize whitespace but ignore EOL')] + public function testVisualizeWhitespaceButIgnoreEol(): void + { + $string = "line1\nline2\n"; + $this->assertSame($string, Color::visualizeWhitespace($string, false)); + } + + #[DataProvider('unnamedDataSetProvider')] + public function testPrettifyUnnamedDataprovider(int $value): void + { + $this->assertSame($value, $value); + } + + #[DataProvider('namedDataSetProvider')] + public function testPrettifyNamedDataprovider(int $value): void + { + $this->assertSame($value, $value); + } + + #[DataProvider('namedDataSetProvider')] + #[TestDox('TestDox shows name of data set $_dataName with value $value')] + public function testTestdoxDatanameAsParameter(int $value): void + { + $this->assertSame($value, $value); + } } diff --git a/tests/unit/Util/ConfigurationGeneratorTest.php b/tests/unit/Util/ConfigurationGeneratorTest.php deleted file mode 100644 index 8c636e5f370..00000000000 --- a/tests/unit/Util/ConfigurationGeneratorTest.php +++ /dev/null @@ -1,59 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use PHPUnit\Framework\TestCase; -use PHPUnit\TextUI\XmlConfiguration\Generator; - -/** - * @small - * @covers \PHPUnit\TextUI\XmlConfiguration\Generator - */ -final class ConfigurationGeneratorTest extends TestCase -{ - public function testGeneratesConfigurationCorrectly(): void - { - $generator = new Generator; - - $this->assertEquals( - ' - - - - tests - - - - - - src - - - -', - $generator->generateDefaultConfiguration( - 'X.Y.Z', - 'vendor/autoload.php', - 'tests', - 'src' - ) - ); - } -} diff --git a/tests/unit/Util/ExcludeListTest.php b/tests/unit/Util/ExcludeListTest.php new file mode 100644 index 00000000000..31eff0d0d75 --- /dev/null +++ b/tests/unit/Util/ExcludeListTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use function realpath; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(ExcludeList::class)] +#[CoversClass(InvalidDirectoryException::class)] +#[Small] +#[RunTestsInSeparateProcesses] +final class ExcludeListTest extends TestCase +{ + public function testIsInitialized(): void + { + $excludeList = new ExcludeList(true); + + $this->assertContains( + realpath(__DIR__ . '/../../../src'), + $excludeList->getExcludedDirectories(), + ); + } + + public function testExclusionOfFileCanBeQueried(): void + { + $excludeList = new ExcludeList(true); + + $this->assertTrue($excludeList->isExcluded(realpath(__DIR__ . '/../../../src/Framework/TestCase.php'))); + $this->assertFalse($excludeList->isExcluded(__FILE__)); + } + + public function testCanBeDisabled(): void + { + $excludeList = new ExcludeList(false); + + $this->assertFalse($excludeList->isExcluded(realpath(__DIR__ . '/../../../src/Framework/TestCase.php'))); + } + + public function testAdditionalDirectoryCanBeExcluded(): void + { + $directory = realpath(__DIR__); + + ExcludeList::addDirectory($directory); + + $excludeList = new ExcludeList(true); + + $this->assertContains($directory, $excludeList->getExcludedDirectories()); + } + + public function testAdditionalDirectoryThatDoesNotExistCannotBeExcluded(): void + { + $this->expectException(InvalidDirectoryException::class); + + ExcludeList::addDirectory('/does/not/exist'); + } +} diff --git a/tests/unit/Util/FilesystemTest.php b/tests/unit/Util/FilesystemTest.php new file mode 100644 index 00000000000..2543bc693c0 --- /dev/null +++ b/tests/unit/Util/FilesystemTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use const DIRECTORY_SEPARATOR; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Filesystem::class)] +#[Small] +final class FilesystemTest extends TestCase +{ + public function testCanResolveStreamOrFile(): void + { + $this->assertSame('php://stdout', Filesystem::resolveStreamOrFile('php://stdout')); + $this->assertSame('socket://hostname:port', Filesystem::resolveStreamOrFile('socket://hostname:port')); + $this->assertSame(__FILE__, Filesystem::resolveStreamOrFile(__FILE__)); + $this->assertSame(__DIR__ . DIRECTORY_SEPARATOR . 'does-not-exist', Filesystem::resolveStreamOrFile(__DIR__ . '/does-not-exist')); + $this->assertFalse(Filesystem::resolveStreamOrFile(__DIR__ . '/does-not-exist/does-not-exist')); + } +} diff --git a/tests/unit/Util/FilterTest.php b/tests/unit/Util/FilterTest.php new file mode 100644 index 00000000000..01056ba7623 --- /dev/null +++ b/tests/unit/Util/FilterTest.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use Exception; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Test::class)] +#[Small] +final class FilterTest extends TestCase +{ + public function testUnwrapThrowableUsesPreviousValues(): void + { + $first = new Exception('first', 123, null); + $second = new Exception('second', 345, $first); + + $this->assertSame(Filter::stackTraceFromThrowableAsString($second), Filter::stackTraceFromThrowableAsString($first)); + } +} diff --git a/tests/unit/Util/GetoptTest.php b/tests/unit/Util/GetoptTest.php deleted file mode 100644 index 30e1adc06d6..00000000000 --- a/tests/unit/Util/GetoptTest.php +++ /dev/null @@ -1,261 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use function explode; -use PHPUnit\Framework\TestCase; - -/** - * @small - * @covers \PHPUnit\Util\Getopt - */ -final class GetoptTest extends TestCase -{ - /** - * @covers \PHPUnit\Util\Getopt::parse - * @covers \PHPUnit\Util\Getopt::parseLongOption - */ - public function testItIncludeTheLongOptionsAfterTheArgument(): void - { - $args = [ - 'command', - 'myArgument', - '--colors', - ]; - $actual = Getopt::parse($args, '', ['colors==']); - - $expected = [ - [ - [ - '--colors', - null, - ], - ], - [ - 'myArgument', - ], - ]; - - $this->assertEquals($expected, $actual); - } - - /** - * @covers \PHPUnit\Util\Getopt::parse - * @covers \PHPUnit\Util\Getopt::parseShortOption - */ - public function testItIncludeTheShortOptionsAfterTheArgument(): void - { - $args = [ - 'command', - 'myArgument', - '-v', - ]; - $actual = Getopt::parse($args, 'v'); - - $expected = [ - [ - [ - 'v', - null, - ], - ], - [ - 'myArgument', - ], - ]; - - $this->assertEquals($expected, $actual); - } - - /** - * @covers \PHPUnit\Util\Getopt::parse - */ - public function testShortOptionUnrecognizedException(): void - { - $args = [ - 'command', - 'myArgument', - '-v', - ]; - - $this->expectException(Exception::class); - $this->expectExceptionMessage('unrecognized option -- v'); - - Getopt::parse($args, ''); - } - - /** - * @covers \PHPUnit\Util\Getopt::parse - * @covers \PHPUnit\Util\Getopt::parseShortOption - */ - public function testShortOptionRequiresAnArgumentException(): void - { - $args = [ - 'command', - 'myArgument', - '-f', - ]; - - $this->expectException(Exception::class); - $this->expectExceptionMessage('option requires an argument -- f'); - - Getopt::parse($args, 'f:'); - } - - /** - * @covers \PHPUnit\Util\Getopt::parse - * @covers \PHPUnit\Util\Getopt::parseShortOption - */ - public function testShortOptionHandleAnOptionalValue(): void - { - $args = [ - 'command', - 'myArgument', - '-f', - ]; - $actual = Getopt::parse($args, 'f::'); - $expected = [ - [ - [ - 'f', - null, - ], - ], - [ - 'myArgument', - ], - ]; - $this->assertEquals($expected, $actual); - } - - /** - * @covers \PHPUnit\Util\Getopt::parse - * @covers \PHPUnit\Util\Getopt::parseLongOption - */ - public function testLongOptionIsAmbiguousException(): void - { - $args = [ - 'command', - '--col', - ]; - - $this->expectException(Exception::class); - $this->expectExceptionMessage('option --col is ambiguous'); - - Getopt::parse($args, '', ['columns', 'colors']); - } - - /** - * @covers \PHPUnit\Util\Getopt::parse - * @covers \PHPUnit\Util\Getopt::parseLongOption - */ - public function testLongOptionUnrecognizedException(): void - { - // the exception 'unrecognized option --option' is not thrown - // if the there are not defined extended options - $args = [ - 'command', - '--foo', - ]; - - $this->expectException(Exception::class); - $this->expectExceptionMessage('unrecognized option --foo'); - - Getopt::parse($args, '', ['colors']); - } - - /** - * @covers \PHPUnit\Util\Getopt::parse - * @covers \PHPUnit\Util\Getopt::parseLongOption - */ - public function testLongOptionRequiresAnArgumentException(): void - { - $args = [ - 'command', - '--foo', - ]; - - $this->expectException(Exception::class); - $this->expectExceptionMessage('option --foo requires an argument'); - - Getopt::parse($args, '', ['foo=']); - } - - /** - * @covers \PHPUnit\Util\Getopt::parse - * @covers \PHPUnit\Util\Getopt::parseLongOption - */ - public function testLongOptionDoesNotAllowAnArgumentException(): void - { - $args = [ - 'command', - '--foo=bar', - ]; - - $this->expectException(Exception::class); - $this->expectExceptionMessage("option --foo doesn't allow an argument"); - - Getopt::parse($args, '', ['foo']); - } - - /** - * @covers \PHPUnit\Util\Getopt::parse - * @covers \PHPUnit\Util\Getopt::parseLongOption - */ - public function testItHandlesLongParametesWithValues(): void - { - $command = 'command parameter-0 --exec parameter-1 --conf config.xml --optn parameter-2 --optn=content-of-o parameter-n'; - $args = explode(' ', $command); - unset($args[0]); - $expected = [ - [ - ['--exec', null], - ['--conf', 'config.xml'], - ['--optn', null], - ['--optn', 'content-of-o'], - ], - [ - 'parameter-0', - 'parameter-1', - 'parameter-2', - 'parameter-n', - ], - ]; - $actual = Getopt::parse($args, '', ['exec', 'conf=', 'optn==']); - $this->assertEquals($expected, $actual); - } - - /** - * @covers \PHPUnit\Util\Getopt::parse - * @covers \PHPUnit\Util\Getopt::parseShortOption - */ - public function testItHandlesShortParametesWithValues(): void - { - $command = 'command parameter-0 -x parameter-1 -c config.xml -o parameter-2 -ocontent-of-o parameter-n'; - $args = explode(' ', $command); - unset($args[0]); - $expected = [ - [ - ['x', null], - ['c', 'config.xml'], - ['o', null], - ['o', 'content-of-o'], - ], - [ - 'parameter-0', - 'parameter-1', - 'parameter-2', - 'parameter-n', - ], - ]; - $actual = Getopt::parse($args, 'xc:o::'); - $this->assertEquals($expected, $actual); - } -} diff --git a/tests/unit/Util/GlobalStateTest.php b/tests/unit/Util/GlobalStateTest.php index 59c3dc03872..a34151bb593 100644 --- a/tests/unit/Util/GlobalStateTest.php +++ b/tests/unit/Util/GlobalStateTest.php @@ -9,11 +9,12 @@ */ namespace PHPUnit\Util; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; use PHPUnit\Framework\TestCase; -/** - * @small - */ +#[CoversClass(GlobalState::class)] +#[Small] final class GlobalStateTest extends TestCase { public function testIncludedFilesAsStringSkipsVfsProtocols(): void @@ -30,7 +31,7 @@ public function testIncludedFilesAsStringSkipsVfsProtocols(): void $this->assertEquals( "require_once '" . $dir . "/GlobalStateTest.php';\n" . "require_once 'file://" . $dir . "/XmlTest.php';\n", - GlobalState::processIncludedFilesAsString($files) + GlobalState::processIncludedFilesAsString($files), ); } } diff --git a/tests/unit/Util/JsonTest.php b/tests/unit/Util/JsonTest.php index cfe86c5a74a..ce7fdd687fe 100644 --- a/tests/unit/Util/JsonTest.php +++ b/tests/unit/Util/JsonTest.php @@ -9,32 +9,17 @@ */ namespace PHPUnit\Util; -use PHPUnit\Framework\Exception; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; -/** - * @small - */ +#[CoversClass(Json::class)] +#[Small] final class JsonTest extends TestCase { - /** - * @testdox Canonicalize $actual - * @dataProvider canonicalizeProvider - * - * @throws \PHPUnit\Framework\ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function testCanonicalize($actual, $expected, $expectError): void - { - [$error, $canonicalized] = Json::canonicalize($actual); - $this->assertEquals($expectError, $error); - - if (!$expectError) { - $this->assertEquals($expected, $canonicalized); - } - } - - public function canonicalizeProvider(): array + public static function canonicalizeProvider(): array { return [ ['{"name":"John","age":"35"}', '{"age":"35","name":"John"}', false], @@ -43,21 +28,7 @@ public function canonicalizeProvider(): array ]; } - /** - * @covers \PHPUnit\Util\Json::prettify - * @testdox Prettify $actual to $expected - * @dataProvider prettifyProvider - * - * @throws \PHPUnit\Framework\Exception - * @throws \PHPUnit\Framework\ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function testPrettify($actual, $expected): void - { - $this->assertEquals($expected, Json::prettify($actual)); - } - - public function prettifyProvider(): array + public static function prettifyProvider(): array { return [ ['{"name":"John","age": "5"}', "{\n \"name\": \"John\",\n \"age\": \"5\"\n}"], @@ -66,23 +37,39 @@ public function prettifyProvider(): array ]; } - /** - * @covers \PHPUnit\Util\Json::prettify - * @dataProvider prettifyExceptionProvider - */ - public function testPrettifyException($json): void - { - $this->expectException(Exception::class); - $this->expectExceptionMessage('Cannot prettify invalid json'); - - Json::prettify($json); - } - - public function prettifyExceptionProvider(): array + public static function prettifyExceptionProvider(): array { return [ ['"name":"John","age": "5"}'], [''], ]; } + + #[DataProvider('canonicalizeProvider')] + #[TestDox('Canonicalize $actual')] + public function testCanonicalize(string $actual, string $expected, bool $expectError): void + { + [$error, $canonicalized] = Json::canonicalize($actual); + + $this->assertEquals($expectError, $error); + + if (!$expectError) { + $this->assertEquals($expected, $canonicalized); + } + } + + #[DataProvider('prettifyProvider')] + #[TestDox('Prettify $actual to $expected')] + public function testPrettify(string $actual, string $expected): void + { + $this->assertEquals($expected, Json::prettify($actual)); + } + + #[DataProvider('prettifyExceptionProvider')] + public function testPrettifyException(string $json): void + { + $this->expectException(InvalidJsonException::class); + + Json::prettify($json); + } } diff --git a/tests/unit/Util/PHP/AbstractPhpProcessTest.php b/tests/unit/Util/PHP/AbstractPhpProcessTest.php deleted file mode 100644 index c8ee4b97fa7..00000000000 --- a/tests/unit/Util/PHP/AbstractPhpProcessTest.php +++ /dev/null @@ -1,122 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\PHP; - -use PHPUnit\Framework\TestCase; - -/** - * @small - */ -final class AbstractPhpProcessTest extends TestCase -{ - /** - * @var AbstractPhpProcess|\PHPUnit\Framework\MockObject\MockObject - */ - private $phpProcess; - - protected function setUp(): void - { - $this->phpProcess = $this->getMockForAbstractClass(AbstractPhpProcess::class); - } - - protected function tearDown(): void - { - $this->phpProcess = null; - } - - public function testShouldNotUseStderrRedirectionByDefault(): void - { - $this->assertFalse($this->phpProcess->useStderrRedirection()); - } - - public function testShouldDefinedIfUseStderrRedirection(): void - { - $this->phpProcess->setUseStderrRedirection(true); - - $this->assertTrue($this->phpProcess->useStderrRedirection()); - } - - public function testShouldDefinedIfDoNotUseStderrRedirection(): void - { - $this->phpProcess->setUseStderrRedirection(false); - - $this->assertFalse($this->phpProcess->useStderrRedirection()); - } - - public function testShouldUseGivenSettingsToCreateCommand(): void - { - $settings = [ - 'allow_url_fopen=1', - 'auto_append_file=', - 'display_errors=1', - ]; - - $expectedCommandFormat = '%s -d %callow_url_fopen=1%c -d %cauto_append_file=%c -d %cdisplay_errors=1%c%S'; - $actualCommand = $this->phpProcess->getCommand($settings); - - $this->assertStringMatchesFormat($expectedCommandFormat, $actualCommand); - } - - public function testShouldRedirectStderrToStdoutWhenDefined(): void - { - $this->phpProcess->setUseStderrRedirection(true); - - $expectedCommandFormat = '%s 2>&1'; - $actualCommand = $this->phpProcess->getCommand([]); - - $this->assertStringMatchesFormat($expectedCommandFormat, $actualCommand); - } - - public function testShouldUseArgsToCreateCommand(): void - { - $this->phpProcess->setArgs('foo=bar'); - - $expectedCommandFormat = '%s foo=bar'; - $actualCommand = $this->phpProcess->getCommand([]); - - $this->assertStringMatchesFormat($expectedCommandFormat, $actualCommand); - } - - public function testShouldHaveFileToCreateCommand(): void - { - $expectedCommandFormat = '%s %cfile.php%c'; - $actualCommand = $this->phpProcess->getCommand([], 'file.php'); - - $this->assertStringMatchesFormat($expectedCommandFormat, $actualCommand); - } - - public function testStdinGetterAndSetter(): void - { - $this->phpProcess->setStdin('foo'); - - $this->assertEquals('foo', $this->phpProcess->getStdin()); - } - - public function testArgsGetterAndSetter(): void - { - $this->phpProcess->setArgs('foo=bar'); - - $this->assertEquals('foo=bar', $this->phpProcess->getArgs()); - } - - public function testEnvGetterAndSetter(): void - { - $this->phpProcess->setEnv(['foo' => 'bar']); - - $this->assertEquals(['foo' => 'bar'], $this->phpProcess->getEnv()); - } - - public function testTimeoutGetterAndSetter(): void - { - $this->phpProcess->setTimeout(30); - - $this->assertEquals(30, $this->phpProcess->getTimeout()); - } -} diff --git a/tests/unit/Util/PHP/DefaultJobRunnerTest.php b/tests/unit/Util/PHP/DefaultJobRunnerTest.php new file mode 100644 index 00000000000..6c3bdf48254 --- /dev/null +++ b/tests/unit/Util/PHP/DefaultJobRunnerTest.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\PHP; + +use Generator; +use PHPUnit\Event\Emitter; +use PHPUnit\Event\Facade; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\ChildProcessResultProcessor; +use PHPUnit\Framework\TestCase; +use PHPUnit\Runner\CodeCoverage; +use PHPUnit\TestRunner\TestResult\PassedTests; + +#[CoversClass(DefaultJobRunner::class)] +#[UsesClass(Job::class)] +#[UsesClass(Result::class)] +#[Small] +final class DefaultJobRunnerTest extends TestCase +{ + public static function provider(): Generator + { + yield 'output to stdout' => [ + new Result('test', ''), + new Job( + <<<'EOT' + [ + new Result('', 'test'), + new Job( + <<<'EOT' + [ + new Result('test-stdout', 'test-stderr'), + new Job( + <<<'EOT' + [ + new Result('test', ''), + new Job( + <<<'EOT' + [ + new Result('test', ''), + new Job( + <<<'EOT' + 'test'], + ), + ]; + + yield 'arguments' => [ + new Result('test', ''), + new Job( + <<<'EOT' + [ + new Result('test', ''), + new Job( + <<<'EOT' +createStub(Emitter::class), + new PassedTests, + new CodeCoverage, + ), + ); + + $result = $jobRunner->run($job); + + $this->assertSame($expected->stdout(), $result->stdout()); + $this->assertSame($expected->stderr(), $result->stderr()); + } +} diff --git a/tests/unit/Util/PHP/JobTest.php b/tests/unit/Util/PHP/JobTest.php new file mode 100644 index 00000000000..e2b9a6fd1a4 --- /dev/null +++ b/tests/unit/Util/PHP/JobTest.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\PHP; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Job::class)] +#[Small] +final class JobTest extends TestCase +{ + public function testHasCode(): void + { + $code = 'the-code'; + + $job = new Job( + $code, + [], + [], + [], + null, + false, + ); + + $this->assertSame($code, $job->code()); + + $this->assertFalse($job->hasEnvironmentVariables()); + $this->assertFalse($job->hasInput()); + $this->assertFalse($job->redirectErrors()); + } + + public function testMayHavePhpSettings(): void + { + $phpSettings = ['foo' => 'bar']; + + $job = new Job( + 'the-code', + $phpSettings, + [], + [], + null, + false, + ); + + $this->assertSame($phpSettings, $job->phpSettings()); + + $this->assertFalse($job->hasEnvironmentVariables()); + $this->assertFalse($job->hasInput()); + $this->assertFalse($job->redirectErrors()); + } + + public function testMayHaveEnvironmentVariables(): void + { + $environmentVariables = ['foo' => 'bar']; + + $job = new Job( + 'the-code', + [], + $environmentVariables, + [], + null, + false, + ); + + $this->assertTrue($job->hasEnvironmentVariables()); + $this->assertSame($environmentVariables, $job->environmentVariables()); + + $this->assertFalse($job->hasInput()); + $this->assertFalse($job->redirectErrors()); + } + + public function testMayHaveArguments(): void + { + $arguments = ['foo', 'bar']; + + $job = new Job( + 'the-code', + [], + [], + $arguments, + null, + false, + ); + + $this->assertTrue($job->hasArguments()); + $this->assertSame($arguments, $job->arguments()); + + $this->assertFalse($job->hasEnvironmentVariables()); + $this->assertFalse($job->hasInput()); + $this->assertFalse($job->redirectErrors()); + } + + public function testMayHaveInput(): void + { + $input = 'the-input'; + + $job = new Job( + 'the-code', + [], + [], + [], + $input, + false, + ); + + $this->assertTrue($job->hasInput()); + $this->assertSame($input, $job->input()); + + $this->assertFalse($job->hasEnvironmentVariables()); + $this->assertFalse($job->redirectErrors()); + } + + public function testMayNotHaveInput(): void + { + $job = new Job( + 'the-code', + [], + [], + [], + null, + false, + ); + + $this->assertFalse($job->hasInput()); + + $this->expectException(PhpProcessException::class); + + $job->input(); + } + + public function testMayRedirectErrors(): void + { + $job = new Job( + 'the-code', + [], + [], + [], + null, + true, + ); + + $this->assertTrue($job->redirectErrors()); + } +} diff --git a/tests/unit/Util/PHP/ResultTest.php b/tests/unit/Util/PHP/ResultTest.php new file mode 100644 index 00000000000..c8597f2fbd7 --- /dev/null +++ b/tests/unit/Util/PHP/ResultTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\PHP; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Result::class)] +#[Small] +final class ResultTest extends TestCase +{ + public function testHasOutputFromStdout(): void + { + $this->assertSame('stdout', $this->fixture()->stdout()); + } + + public function testHasOutputFromStderr(): void + { + $this->assertSame('stderr', $this->fixture()->stderr()); + } + + private function fixture(): Result + { + return new Result('stdout', 'stderr'); + } +} diff --git a/tests/unit/Util/ReflectionTest.php b/tests/unit/Util/ReflectionTest.php new file mode 100644 index 00000000000..30cb057c009 --- /dev/null +++ b/tests/unit/Util/ReflectionTest.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use function realpath; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\BankAccountTest; +use PHPUnit\TestFixture\ConcreteTestClassExtendingAbstractTestClassWithoutTestSuffixTest; +use ReflectionClass; + +#[CoversClass(Reflection::class)] +#[Small] +final class ReflectionTest extends TestCase +{ + public function testFindsSourceLocationForMethod(): void + { + $this->assertSame( + [ + 'file' => realpath(__DIR__ . '/../../_files/BankAccountTest.php'), + 'line' => 22, + ], + Reflection::sourceLocationFor(BankAccountTest::class, 'testBalanceIsInitiallyZero'), + ); + } + + public function testFindsSourceLocationForMethodInAbstractTestCase(): void + { + $this->assertSame( + [ + 'file' => realpath(__DIR__ . '/../../_files/abstract/without-test-suffix/AbstractTestCase.php'), + 'line' => 16, + ], + Reflection::sourceLocationFor(ConcreteTestClassExtendingAbstractTestClassWithoutTestSuffixTest::class, 'testOne'), + ); + } + + public function testReturnsUnknownSourceLocationForMethodThatDoesNotExist(): void + { + $this->assertSame( + [ + 'file' => 'unknown', + 'line' => 0, + ], + Reflection::sourceLocationFor('DoesNotExist', 'doesNotExist'), + ); + } + + public function testFindsPublicMethodsInTestClass(): void + { + $methods = Reflection::publicMethodsDeclaredDirectlyInTestClass(new ReflectionClass(BankAccountTest::class)); + + $this->assertCount(3, $methods); + $this->assertSame('testBalanceIsInitiallyZero', $methods[0]->getName()); + $this->assertSame('testBalanceCannotBecomeNegative', $methods[1]->getName()); + $this->assertSame('testBalanceCannotBecomeNegative2', $methods[2]->getName()); + } + + public function testFindsMethodsInTestClass(): void + { + $methods = Reflection::methodsDeclaredDirectlyInTestClass(new ReflectionClass(BankAccountTest::class)); + + $this->assertCount(3, $methods); + $this->assertSame('testBalanceIsInitiallyZero', $methods[0]->getName()); + $this->assertSame('testBalanceCannotBecomeNegative', $methods[1]->getName()); + $this->assertSame('testBalanceCannotBecomeNegative2', $methods[2]->getName()); + } +} diff --git a/tests/unit/Util/RegularExpressionTest.php b/tests/unit/Util/RegularExpressionTest.php deleted file mode 100644 index 8e41f33d261..00000000000 --- a/tests/unit/Util/RegularExpressionTest.php +++ /dev/null @@ -1,62 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use Exception; -use PHPUnit\Framework\TestCase; - -/** - * @small - */ -final class RegularExpressionTest extends TestCase -{ - public function validRegexpProvider(): array - { - return [ - ['#valid regexp#', 'valid regexp', 1], - [';val.*xp;', 'valid regexp', 1], - ['/val.*xp/i', 'VALID REGEXP', 1], - ['/a val.*p/', 'valid regexp', 0], - ]; - } - - public function invalidRegexpProvider(): array - { - return [ - ['valid regexp', 'valid regexp'], - [';val.*xp', 'valid regexp'], - ['val.*xp/i', 'VALID REGEXP'], - ]; - } - - /** - * @testdox Valid regex $pattern on $subject returns $return - * @dataProvider validRegexpProvider - * - * @throws Exception - * @throws \PHPUnit\Framework\ExpectationFailedException - */ - public function testValidRegex($pattern, $subject, $return): void - { - $this->assertEquals($return, RegularExpression::safeMatch($pattern, $subject)); - } - - /** - * @testdox Invalid regex $pattern on $subject - * @dataProvider invalidRegexpProvider - * - * @throws Exception - * @throws \PHPUnit\Framework\ExpectationFailedException - */ - public function testInvalidRegex($pattern, $subject): void - { - $this->assertFalse(RegularExpression::safeMatch($pattern, $subject)); - } -} diff --git a/tests/unit/Util/TestClassTest.php b/tests/unit/Util/TestClassTest.php deleted file mode 100644 index 7e37b91e74f..00000000000 --- a/tests/unit/Util/TestClassTest.php +++ /dev/null @@ -1,1595 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use function array_merge; -use function get_class; -use function preg_match; -use function range; -use function realpath; -use PharIo\Version\VersionConstraint; -use PHPUnit\Framework\CodeCoverageException; -use PHPUnit\Framework\ExecutionOrderDependency; -use PHPUnit\Framework\InvalidDataProviderException; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\Warning; -use PHPUnit\TestFixture\CoverageClassExtendedTest; -use PHPUnit\TestFixture\CoverageClassNothingTest; -use PHPUnit\TestFixture\CoverageClassTest; -use PHPUnit\TestFixture\CoverageClassWithoutAnnotationsTest; -use PHPUnit\TestFixture\CoverageCoversOverridesCoversNothingTest; -use PHPUnit\TestFixture\CoverageFunctionParenthesesTest; -use PHPUnit\TestFixture\CoverageFunctionParenthesesWhitespaceTest; -use PHPUnit\TestFixture\CoverageFunctionTest; -use PHPUnit\TestFixture\CoverageMethodNothingCoversMethod; -use PHPUnit\TestFixture\CoverageMethodNothingTest; -use PHPUnit\TestFixture\CoverageMethodOneLineAnnotationTest; -use PHPUnit\TestFixture\CoverageMethodParenthesesTest; -use PHPUnit\TestFixture\CoverageMethodParenthesesWhitespaceTest; -use PHPUnit\TestFixture\CoverageMethodTest; -use PHPUnit\TestFixture\CoverageNamespacedFunctionTest; -use PHPUnit\TestFixture\CoverageNoneTest; -use PHPUnit\TestFixture\CoverageNotPrivateTest; -use PHPUnit\TestFixture\CoverageNotProtectedTest; -use PHPUnit\TestFixture\CoverageNotPublicTest; -use PHPUnit\TestFixture\CoveragePrivateTest; -use PHPUnit\TestFixture\CoverageProtectedTest; -use PHPUnit\TestFixture\CoveragePublicTest; -use PHPUnit\TestFixture\CoverageTwoDefaultClassAnnotations; -use PHPUnit\TestFixture\DuplicateKeyDataProviderTest; -use PHPUnit\TestFixture\MultipleDataProviderTest; -use PHPUnit\TestFixture\NamespaceCoverageClassExtendedTest; -use PHPUnit\TestFixture\NamespaceCoverageClassTest; -use PHPUnit\TestFixture\NamespaceCoverageCoversClassPublicTest; -use PHPUnit\TestFixture\NamespaceCoverageCoversClassTest; -use PHPUnit\TestFixture\NamespaceCoverageMethodTest; -use PHPUnit\TestFixture\NamespaceCoverageNotPrivateTest; -use PHPUnit\TestFixture\NamespaceCoverageNotProtectedTest; -use PHPUnit\TestFixture\NamespaceCoverageNotPublicTest; -use PHPUnit\TestFixture\NamespaceCoveragePrivateTest; -use PHPUnit\TestFixture\NamespaceCoverageProtectedTest; -use PHPUnit\TestFixture\NamespaceCoveragePublicTest; -use PHPUnit\TestFixture\NotExistingCoveredElementTest; -use PHPUnit\TestFixture\NumericGroupAnnotationTest; -use PHPUnit\TestFixture\ParseTestMethodAnnotationsMock; -use PHPUnit\TestFixture\RequirementsClassDocBlockTest; -use PHPUnit\TestFixture\RequirementsTest; -use PHPUnit\TestFixture\Test3194; -use PHPUnit\TestFixture\VariousDocblockDefinedDataProvider; -use PHPUnit\TestFixture\VariousIterableDataProviderTest; -use PHPUnit\Util\Annotation\DocBlock; -use ReflectionClass; -use ReflectionMethod; - -/** - * @small - */ -final class TestClassTest extends TestCase -{ - /** - * @var string - */ - private $fileRequirementsTest; - - /** - * @testdox Test::getRequirements() for $test - * @dataProvider requirementsProvider - * - * @throws Warning - * @throws \PHPUnit\Framework\ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function testGetRequirements($test, $result): void - { - $this->assertEquals( - $result, - Test::getRequirements(RequirementsTest::class, $test) - ); - } - - public function requirementsProvider(): array - { - return [ - [ - 'testOne', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - ], - ], - ], - - [ - 'testTwo', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHPUnit' => 21, - ], - 'PHPUnit' => ['version' => '1.0', 'operator' => ''], - ], - ], - - [ - 'testThree', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHP' => 28, - ], - 'PHP' => ['version' => '2.0', 'operator' => ''], - ], - ], - - [ - 'testFour', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHPUnit' => 35, - 'PHP' => 36, - ], - 'PHPUnit' => ['version' => '2.0', 'operator' => ''], - 'PHP' => ['version' => '1.0', 'operator' => ''], - ], - ], - - [ - 'testFive', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHP' => 43, - ], - 'PHP' => ['version' => '5.4.0RC6', 'operator' => ''], - ], - ], - - [ - 'testSix', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHP' => 50, - ], - 'PHP' => ['version' => '5.4.0-alpha1', 'operator' => ''], - ], - ], - - [ - 'testSeven', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHP' => 57, - ], - 'PHP' => ['version' => '5.4.0beta2', 'operator' => ''], - ], - ], - - [ - 'testEight', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHP' => 64, - ], - 'PHP' => ['version' => '5.4-dev', 'operator' => ''], - ], - ], - - [ - 'testNine', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'function_testFunc' => 71, - ], - 'functions' => ['testFunc'], - ], - ], - - [ - 'testTen', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'extension_testExt' => 87, - ], - 'extensions' => ['testExt'], - ], - ], - - [ - 'testEleven', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'OS' => 94, - 'OSFAMILY' => 95, - ], - 'OS' => 'SunOS', - 'OSFAMILY' => 'Solaris', - ], - ], - - [ - 'testSpace', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'extension_spl' => 173, - 'OS' => 174, - ], - 'extensions' => ['spl'], - 'OS' => '.*', - ], - ], - - [ - 'testAllPossibleRequirements', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHP' => 102, - 'PHPUnit' => 103, - 'OS' => 104, - 'function_testFuncOne' => 105, - 'function_testFunc2' => 106, - 'extension_testExtOne' => 107, - 'extension_testExt2' => 108, - 'extension_testExtThree' => 109, - '__SETTING_not_a_setting' => 110, - ], - 'PHP' => ['version' => '99-dev', 'operator' => ''], - 'PHPUnit' => ['version' => '99-dev', 'operator' => ''], - 'OS' => 'DOESNOTEXIST', - 'functions' => [ - 'testFuncOne', - 'testFunc2', - ], - 'setting' => [ - 'not_a_setting' => 'Off', - ], - 'extensions' => [ - 'testExtOne', - 'testExt2', - 'testExtThree', - ], - 'extension_versions' => [ - 'testExtThree' => ['version' => '2.0', 'operator' => ''], - ], - ], - ], - - ['testSpecificExtensionVersion', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'extension_testExt' => 181, - ], - 'extension_versions' => ['testExt' => ['version' => '1.8.0', 'operator' => '']], - 'extensions' => ['testExt'], - ], - ], - ['testPHPVersionOperatorLessThan', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHP' => 189, - ], - 'PHP' => ['version' => '5.4', 'operator' => '<'], - ], - ], - ['testPHPVersionOperatorLessThanEquals', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHP' => 197, - ], - 'PHP' => ['version' => '5.4', 'operator' => '<='], - ], - ], - ['testPHPVersionOperatorGreaterThan', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHP' => 205, - ], - 'PHP' => ['version' => '99', 'operator' => '>'], - ], - ], - ['testPHPVersionOperatorGreaterThanEquals', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHP' => 213, - ], - 'PHP' => ['version' => '99', 'operator' => '>='], - ], - ], - ['testPHPVersionOperatorEquals', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHP' => 221, - ], - 'PHP' => ['version' => '5.4', 'operator' => '='], - ], - ], - ['testPHPVersionOperatorDoubleEquals', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHP' => 229, - ], - 'PHP' => ['version' => '5.4', 'operator' => '=='], - ], - ], - ['testPHPVersionOperatorBangEquals', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHP' => 237, - ], - 'PHP' => ['version' => '99', 'operator' => '!='], - ], - ], - ['testPHPVersionOperatorNotEquals', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHP' => 245, - ], - 'PHP' => ['version' => '99', 'operator' => '<>'], - ], - ], - ['testPHPVersionOperatorNoSpace', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHP' => 253, - ], - 'PHP' => ['version' => '99', 'operator' => '>='], - ], - ], - ['testPHPUnitVersionOperatorLessThan', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHPUnit' => 261, - ], - 'PHPUnit' => ['version' => '1.0', 'operator' => '<'], - ], - ], - ['testPHPUnitVersionOperatorLessThanEquals', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHPUnit' => 269, - ], - 'PHPUnit' => ['version' => '1.0', 'operator' => '<='], - ], - ], - ['testPHPUnitVersionOperatorGreaterThan', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHPUnit' => 277, - ], - 'PHPUnit' => ['version' => '99', 'operator' => '>'], - ], - ], - ['testPHPUnitVersionOperatorGreaterThanEquals', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHPUnit' => 285, - ], - 'PHPUnit' => ['version' => '99', 'operator' => '>='], - ], - ], - ['testPHPUnitVersionOperatorEquals', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHPUnit' => 293, - ], - 'PHPUnit' => ['version' => '1.0', 'operator' => '='], - ], - ], - ['testPHPUnitVersionOperatorDoubleEquals', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHPUnit' => 301, - ], - 'PHPUnit' => ['version' => '1.0', 'operator' => '=='], - ], - ], - ['testPHPUnitVersionOperatorBangEquals', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHPUnit' => 309, - ], - 'PHPUnit' => ['version' => '99', 'operator' => '!='], - ], - ], - ['testPHPUnitVersionOperatorNotEquals', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHPUnit' => 317, - ], - 'PHPUnit' => ['version' => '99', 'operator' => '<>'], - ], - ], - ['testPHPUnitVersionOperatorNoSpace', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'PHPUnit' => 325, - ], - 'PHPUnit' => ['version' => '99', 'operator' => '>='], - ], - ], - ['testExtensionVersionOperatorLessThanEquals', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'extension_testExtOne' => 339, - ], - 'extensions' => ['testExtOne'], - 'extension_versions' => ['testExtOne' => ['version' => '1.0', 'operator' => '<=']], - ], - ], - ['testExtensionVersionOperatorGreaterThan', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'extension_testExtOne' => 346, - ], - 'extensions' => ['testExtOne'], - 'extension_versions' => ['testExtOne' => ['version' => '99', 'operator' => '>']], - ], - ], - ['testExtensionVersionOperatorGreaterThanEquals', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'extension_testExtOne' => 353, - ], - 'extensions' => ['testExtOne'], - 'extension_versions' => ['testExtOne' => ['version' => '99', 'operator' => '>=']], - ], - ], - ['testExtensionVersionOperatorEquals', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'extension_testExtOne' => 360, - ], - 'extensions' => ['testExtOne'], - 'extension_versions' => ['testExtOne' => ['version' => '1.0', 'operator' => '=']], - ], - ], - ['testExtensionVersionOperatorDoubleEquals', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'extension_testExtOne' => 367, - ], - 'extensions' => ['testExtOne'], - 'extension_versions' => ['testExtOne' => ['version' => '1.0', 'operator' => '==']], - ], - ], - ['testExtensionVersionOperatorBangEquals', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'extension_testExtOne' => 374, - ], - 'extensions' => ['testExtOne'], - 'extension_versions' => ['testExtOne' => ['version' => '99', 'operator' => '!=']], - ], - ], - ['testExtensionVersionOperatorNotEquals', - [ - '__OFFSET' => [ - '__FILE' => $this->getRequirementsTestClassFile(), - 'extension_testExtOne' => 381, - ], - 'extensions' => ['testExtOne'], - 'extension_versions' => ['testExtOne' => ['version' => '99', 'operator' => '<>']], - ], - ], - ['testExtensionVersionOperatorNoSpace', - [ - '__OFFSET' => [ - '__FILE' => $this->fileRequirementsTest, - 'extension_testExtOne' => 388, - ], - 'extensions' => ['testExtOne'], - 'extension_versions' => ['testExtOne' => ['version' => '99', 'operator' => '>=']], - ], - ], - ]; - } - - /** - * @testdox Test::getRequirements() with constraints for $test - * @dataProvider requirementsWithVersionConstraintsProvider - * - * @throws Exception - * @throws Warning - * @throws \PHPUnit\Framework\ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function testGetRequirementsWithVersionConstraints($test, array $result): void - { - $requirements = Test::getRequirements(RequirementsTest::class, $test); - - foreach ($result as $type => $expected_requirement) { - $this->assertArrayHasKey( - "{$type}_constraint", - $requirements - ); - $this->assertArrayHasKey( - 'constraint', - $requirements["{$type}_constraint"] - ); - $this->assertInstanceOf( - VersionConstraint::class, - $requirements["{$type}_constraint"]['constraint'] - ); - $this->assertSame( - $expected_requirement['constraint'], - $requirements["{$type}_constraint"]['constraint']->asString() - ); - } - } - - public function requirementsWithVersionConstraintsProvider(): array - { - return [ - [ - 'testVersionConstraintTildeMajor', - [ - 'PHP' => [ - 'constraint' => '~1.0', - ], - 'PHPUnit' => [ - 'constraint' => '~2.0', - ], - ], - ], - [ - 'testVersionConstraintCaretMajor', - [ - 'PHP' => [ - 'constraint' => '^1.0', - ], - 'PHPUnit' => [ - 'constraint' => '^2.0', - ], - ], - ], - [ - 'testVersionConstraintTildeMinor', - [ - 'PHP' => [ - 'constraint' => '~3.4.7', - ], - 'PHPUnit' => [ - 'constraint' => '~4.7.1', - ], - ], - ], - [ - 'testVersionConstraintCaretMinor', - [ - 'PHP' => [ - 'constraint' => '^7.0.17', - ], - 'PHPUnit' => [ - 'constraint' => '^4.7.1', - ], - ], - ], - [ - 'testVersionConstraintCaretOr', - [ - 'PHP' => [ - 'constraint' => '^5.6 || ^7.0', - ], - 'PHPUnit' => [ - 'constraint' => '^5.0 || ^6.0', - ], - ], - ], - [ - 'testVersionConstraintTildeOr', - [ - 'PHP' => [ - 'constraint' => '~5.6.22 || ~7.0.17', - ], - 'PHPUnit' => [ - 'constraint' => '^5.0.5 || ^6.0.6', - ], - ], - ], - [ - 'testVersionConstraintTildeOrCaret', - [ - 'PHP' => [ - 'constraint' => '~5.6.22 || ^7.0', - ], - 'PHPUnit' => [ - 'constraint' => '~5.6.22 || ^7.0', - ], - ], - ], - [ - 'testVersionConstraintCaretOrTilde', - [ - 'PHP' => [ - 'constraint' => '^5.6 || ~7.0.17', - ], - 'PHPUnit' => [ - 'constraint' => '^5.6 || ~7.0.17', - ], - ], - ], - [ - 'testVersionConstraintRegexpIgnoresWhitespace', - [ - 'PHP' => [ - 'constraint' => '~5.6.22 || ~7.0.17', - ], - 'PHPUnit' => [ - 'constraint' => '~5.6.22 || ~7.0.17', - ], - ], - ], - ]; - } - - /** - * @dataProvider requirementsWithInvalidVersionConstraintsThrowsExceptionProvider - * - * @throws Warning - */ - public function testGetRequirementsWithInvalidVersionConstraintsThrowsException($test): void - { - $this->expectException(Warning::class); - - Test::getRequirements(RequirementsTest::class, $test); - } - - public function requirementsWithInvalidVersionConstraintsThrowsExceptionProvider(): array - { - return [ - ['testVersionConstraintInvalidPhpConstraint'], - ['testVersionConstraintInvalidPhpUnitConstraint'], - ]; - } - - public function testGetRequirementsMergesClassAndMethodDocBlocks(): void - { - $file = (new ReflectionClass(RequirementsClassDocBlockTest::class))->getFileName(); - - $expectedAnnotations = [ - '__OFFSET' => [ - '__FILE' => $file, - 'PHP' => 22, - 'PHPUnit' => 23, - 'OS' => 24, - 'function_testFuncClass' => 16, - 'extension_testExtClass' => 17, - 'function_testFuncMethod' => 25, - 'extension_testExtMethod' => 26, - ], - 'PHP' => ['version' => '5.4', 'operator' => ''], - 'PHPUnit' => ['version' => '3.7', 'operator' => ''], - 'OS' => 'WINNT', - 'functions' => [ - 'testFuncClass', - 'testFuncMethod', - ], - 'extensions' => [ - 'testExtClass', - 'testExtMethod', - ], - ]; - - $this->assertEquals( - $expectedAnnotations, - Test::getRequirements(RequirementsClassDocBlockTest::class, 'testMethod') - ); - } - - /** - * @testdox Test::getMissingRequirements() for $test - * @dataProvider missingRequirementsProvider - * - * @throws Warning - * @throws \PHPUnit\Framework\ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function testGetMissingRequirements($test, $result): void - { - $this->assertEquals( - $result, - Test::getMissingRequirements(RequirementsTest::class, $test) - ); - } - - public function missingRequirementsProvider(): array - { - return [ - ['testOne', []], - ['testNine', [ - '__OFFSET_LINE=71', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'Function testFunc is required.', - ]], - ['testTen', [ - '__OFFSET_LINE=87', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'Extension testExt is required.', - ]], - ['testAlwaysSkip', [ - '__OFFSET_LINE=145', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'PHPUnit >= 1111111 is required.', - ]], - ['testAlwaysSkip2', [ - '__OFFSET_LINE=152', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'PHP >= 9999999 is required.', - ]], - ['testAlwaysSkip3', [ - '__OFFSET_LINE=159', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'Operating system matching /DOESNOTEXIST/i is required.', - ]], - ['testAllPossibleRequirements', [ - '__OFFSET_LINE=102', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'PHP >= 99-dev is required.', - 'PHPUnit >= 99-dev is required.', - 'Operating system matching /DOESNOTEXIST/i is required.', - 'Function testFuncOne is required.', - 'Function testFunc2 is required.', - 'Setting "not_a_setting" must be "Off".', - 'Extension testExtOne is required.', - 'Extension testExt2 is required.', - 'Extension testExtThree >= 2.0 is required.', - ]], - ['testPHPVersionOperatorLessThan', [ - '__OFFSET_LINE=189', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'PHP < 5.4 is required.', - ]], - ['testPHPVersionOperatorLessThanEquals', [ - '__OFFSET_LINE=197', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'PHP <= 5.4 is required.', - ]], - ['testPHPVersionOperatorGreaterThan', [ - '__OFFSET_LINE=205', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'PHP > 99 is required.', - ]], - ['testPHPVersionOperatorGreaterThanEquals', [ - '__OFFSET_LINE=213', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'PHP >= 99 is required.', - ]], - ['testPHPVersionOperatorNoSpace', [ - '__OFFSET_LINE=253', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'PHP >= 99 is required.', - ]], - ['testPHPVersionOperatorEquals', [ - '__OFFSET_LINE=221', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'PHP = 5.4 is required.', - ]], - ['testPHPVersionOperatorDoubleEquals', [ - '__OFFSET_LINE=229', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'PHP == 5.4 is required.', - ]], - ['testPHPUnitVersionOperatorLessThan', [ - '__OFFSET_LINE=261', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'PHPUnit < 1.0 is required.', - ]], - ['testPHPUnitVersionOperatorLessThanEquals', [ - '__OFFSET_LINE=269', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'PHPUnit <= 1.0 is required.', - ]], - ['testPHPUnitVersionOperatorGreaterThan', [ - '__OFFSET_LINE=277', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'PHPUnit > 99 is required.', - ]], - ['testPHPUnitVersionOperatorGreaterThanEquals', [ - '__OFFSET_LINE=285', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'PHPUnit >= 99 is required.', - ]], - ['testPHPUnitVersionOperatorEquals', [ - '__OFFSET_LINE=293', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'PHPUnit = 1.0 is required.', - ]], - ['testPHPUnitVersionOperatorDoubleEquals', [ - '__OFFSET_LINE=301', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'PHPUnit == 1.0 is required.', - ]], - ['testPHPUnitVersionOperatorNoSpace', [ - '__OFFSET_LINE=325', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'PHPUnit >= 99 is required.', - ]], - ['testExtensionVersionOperatorLessThan', [ - '__OFFSET_LINE=332', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'Extension testExtOne < 1.0 is required.', - ]], - ['testExtensionVersionOperatorLessThanEquals', [ - '__OFFSET_LINE=339', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'Extension testExtOne <= 1.0 is required.', - ]], - ['testExtensionVersionOperatorGreaterThan', [ - '__OFFSET_LINE=346', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'Extension testExtOne > 99 is required.', - ]], - ['testExtensionVersionOperatorGreaterThanEquals', [ - '__OFFSET_LINE=353', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'Extension testExtOne >= 99 is required.', - ]], - ['testExtensionVersionOperatorEquals', [ - '__OFFSET_LINE=360', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'Extension testExtOne = 1.0 is required.', - ]], - ['testExtensionVersionOperatorDoubleEquals', [ - '__OFFSET_LINE=367', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'Extension testExtOne == 1.0 is required.', - ]], - ['testExtensionVersionOperatorNoSpace', [ - '__OFFSET_LINE=388', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'Extension testExtOne >= 99 is required.', - ]], - ['testVersionConstraintTildeMajor', [ - '__OFFSET_LINE=395', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'PHP version does not match the required constraint ~1.0.', - 'PHPUnit version does not match the required constraint ~2.0.', - ]], - ['testVersionConstraintCaretMajor', [ - '__OFFSET_LINE=403', - '__OFFSET_FILE=' . $this->getRequirementsTestClassFile(), - 'PHP version does not match the required constraint ^1.0.', - 'PHPUnit version does not match the required constraint ^2.0.', - ]], - ]; - } - - /** - * @todo This test does not really test functionality of \PHPUnit\Util\Test - */ - public function testGetProvidedDataRegEx(): void - { - $result = preg_match(DocBlock::REGEX_DATA_PROVIDER, '@dataProvider method', $matches); - $this->assertEquals(1, $result); - $this->assertEquals('method', $matches[1]); - - $result = preg_match(DocBlock::REGEX_DATA_PROVIDER, '@dataProvider class::method', $matches); - $this->assertEquals(1, $result); - $this->assertEquals('class::method', $matches[1]); - - $result = preg_match(DocBlock::REGEX_DATA_PROVIDER, '@dataProvider namespace\class::method', $matches); - $this->assertEquals(1, $result); - $this->assertEquals('namespace\class::method', $matches[1]); - - $result = preg_match(DocBlock::REGEX_DATA_PROVIDER, '@dataProvider namespace\namespace\class::method', $matches); - $this->assertEquals(1, $result); - $this->assertEquals('namespace\namespace\class::method', $matches[1]); - - $result = preg_match(DocBlock::REGEX_DATA_PROVIDER, '@dataProvider メソッド', $matches); - $this->assertEquals(1, $result); - $this->assertEquals('メソッド', $matches[1]); - } - - /** - * Check if all data providers are being merged. - */ - public function testMultipleDataProviders(): void - { - $dataSets = Test::getProvidedData(MultipleDataProviderTest::class, 'testOne'); - - $this->assertCount(9, $dataSets); - - $aCount = 0; - $bCount = 0; - $cCount = 0; - - for ($i = 0; $i < 9; $i++) { - $aCount += $dataSets[$i][0] != null ? 1 : 0; - $bCount += $dataSets[$i][1] != null ? 1 : 0; - $cCount += $dataSets[$i][2] != null ? 1 : 0; - } - - $this->assertEquals(3, $aCount); - $this->assertEquals(3, $bCount); - $this->assertEquals(3, $cCount); - } - - public function testMultipleYieldIteratorDataProviders(): void - { - $dataSets = Test::getProvidedData(MultipleDataProviderTest::class, 'testTwo'); - - $this->assertCount(9, $dataSets); - - $aCount = 0; - $bCount = 0; - $cCount = 0; - - for ($i = 0; $i < 9; $i++) { - $aCount += $dataSets[$i][0] != null ? 1 : 0; - $bCount += $dataSets[$i][1] != null ? 1 : 0; - $cCount += $dataSets[$i][2] != null ? 1 : 0; - } - - $this->assertEquals(3, $aCount); - $this->assertEquals(3, $bCount); - $this->assertEquals(3, $cCount); - } - - public function testWithVariousIterableDataProvidersFromParent(): void - { - $dataSets = Test::getProvidedData(VariousIterableDataProviderTest::class, 'testFromParent'); - - $this->assertEquals([ - ['J'], - ['K'], - ['L'], - ['M'], - ['N'], - ['O'], - ['P'], - ['Q'], - ['R'], - - ], $dataSets); - } - - public function testWithVariousIterableDataProvidersInParent(): void - { - $dataSets = Test::getProvidedData(VariousIterableDataProviderTest::class, 'testInParent'); - - $this->assertEquals([ - ['J'], - ['K'], - ['L'], - ['M'], - ['N'], - ['O'], - ['P'], - ['Q'], - ['R'], - - ], $dataSets); - } - - public function testWithVariousIterableAbstractDataProviders(): void - { - $dataSets = Test::getProvidedData(VariousIterableDataProviderTest::class, 'testAbstract'); - - $this->assertEquals([ - ['S'], - ['T'], - ['U'], - ['V'], - ['W'], - ['X'], - ['Y'], - ['Z'], - ['P'], - - ], $dataSets); - } - - public function testWithVariousIterableStaticDataProviders(): void - { - $dataSets = Test::getProvidedData(VariousIterableDataProviderTest::class, 'testStatic'); - - $this->assertEquals([ - ['A'], - ['B'], - ['C'], - ['D'], - ['E'], - ['F'], - ['G'], - ['H'], - ['I'], - ], $dataSets); - } - - public function testWithVariousIterableNonStaticDataProviders(): void - { - $dataSets = Test::getProvidedData(VariousIterableDataProviderTest::class, 'testNonStatic'); - - $this->assertEquals([ - ['S'], - ['T'], - ['U'], - ['V'], - ['W'], - ['X'], - ['Y'], - ['Z'], - ['P'], - ], $dataSets); - } - - public function testWithDuplicateKeyDataProviders(): void - { - $this->expectException(InvalidDataProviderException::class); - $this->expectExceptionMessage('The key "foo" has already been defined in the data provider "dataProvider".'); - - Test::getProvidedData(DuplicateKeyDataProviderTest::class, 'test'); - } - - public function testTestWithEmptyAnnotation(): void - { - $result = DocBlock::ofMethod( - new ReflectionMethod( - VariousDocblockDefinedDataProvider::class, - 'anotherAnnotation' - ), - VariousDocblockDefinedDataProvider::class - )->getProvidedData(); - - $this->assertNull($result); - } - - public function testTestWithSimpleCase(): void - { - $result = DocBlock::ofMethod( - new ReflectionMethod( - VariousDocblockDefinedDataProvider::class, - 'testWith1' - ), - VariousDocblockDefinedDataProvider::class - )->getProvidedData(); - - $this->assertEquals([[1]], $result); - } - - public function testTestWithMultiLineMultiParameterCase(): void - { - $result = DocBlock::ofMethod( - new ReflectionMethod( - VariousDocblockDefinedDataProvider::class, - 'testWith1234' - ), - VariousDocblockDefinedDataProvider::class - )->getProvidedData(); - - $this->assertEquals([[1, 2], [3, 4]], $result); - } - - public function testTestWithVariousTypes(): void - { - $result = DocBlock::ofMethod( - new ReflectionMethod( - VariousDocblockDefinedDataProvider::class, - 'testWithABTrueNull' - ), - VariousDocblockDefinedDataProvider::class - )->getProvidedData(); - - $this->assertEquals([['ab'], [true], [null]], $result); - } - - public function testTestWithAnnotationAfter(): void - { - $result = DocBlock::ofMethod( - new ReflectionMethod( - VariousDocblockDefinedDataProvider::class, - 'testWith12AndAnotherAnnotation' - ), - VariousDocblockDefinedDataProvider::class - )->getProvidedData(); - - $this->assertEquals([[1], [2]], $result); - } - - public function testTestWithSimpleTextAfter(): void - { - $result = DocBlock::ofMethod( - new ReflectionMethod( - VariousDocblockDefinedDataProvider::class, - 'testWith12AndBlahBlah' - ), - VariousDocblockDefinedDataProvider::class - )->getProvidedData(); - - $this->assertEquals([[1], [2]], $result); - } - - public function testTestWithCharacterEscape(): void - { - $result = DocBlock::ofMethod( - new ReflectionMethod( - VariousDocblockDefinedDataProvider::class, - 'testWithEscapedString' - ), - VariousDocblockDefinedDataProvider::class - )->getProvidedData(); - - $this->assertEquals([['"', '"']], $result); - } - - public function testTestWithThrowsProperExceptionIfDatasetCannotBeParsed(): void - { - $docBlock = DocBlock::ofMethod( - new ReflectionMethod( - VariousDocblockDefinedDataProvider::class, - 'testWithMalformedValue' - ), - VariousDocblockDefinedDataProvider::class - ); - - $this->expectException(Exception::class); - $this->expectExceptionMessageMatches('/^The data set for the @testWith annotation cannot be parsed:/'); - - $docBlock->getProvidedData(); - } - - public function testTestWithThrowsProperExceptionIfMultiLineDatasetCannotBeParsed(): void - { - $docBlock = DocBlock::ofMethod( - new ReflectionMethod( - VariousDocblockDefinedDataProvider::class, - 'testWithWellFormedAndMalformedValue' - ), - VariousDocblockDefinedDataProvider::class - ); - - $this->expectException(Exception::class); - $this->expectExceptionMessageMatches('/^The data set for the @testWith annotation cannot be parsed:/'); - - $docBlock->getProvidedData(); - } - - public function testParseDependsAnnotation(): void - { - $this->assertEquals( - [ - new ExecutionOrderDependency(get_class($this), 'Foo'), - new ExecutionOrderDependency(get_class($this), 'ほげ'), - new ExecutionOrderDependency('AnotherClass::Foo'), - ], - Test::getDependencies(get_class($this), 'methodForTestParseAnnotation') - ); - } - - /** - * @depends Foo - * @depends ほげ - * @depends AnotherClass::Foo - * - * @todo Remove fixture from test class - */ - public function methodForTestParseAnnotation(): void - { - } - - public function testParseAnnotationThatIsOnlyOneLine(): void - { - $this->assertEquals( - [new ExecutionOrderDependency(get_class($this), 'Bar')], - Test::getDependencies(get_class($this), 'methodForTestParseAnnotationThatIsOnlyOneLine') - ); - } - - /** @depends Bar */ - public function methodForTestParseAnnotationThatIsOnlyOneLine(): void - { - // TODO Remove fixture from test class - } - - /** - * @dataProvider getLinesToBeCoveredProvider - * - * @throws CodeCoverageException - * @throws \PHPUnit\Framework\ExpectationFailedException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public function testGetLinesToBeCovered($test, $lines): void - { - switch ($test) { - case CoverageMethodNothingCoversMethod::class: - case CoverageClassNothingTest::class: - case CoverageMethodNothingTest::class: - $expected = false; - - break; - - case CoverageCoversOverridesCoversNothingTest::class: - $expected = [TEST_FILES_PATH . 'CoveredClass.php' => $lines]; - - break; - - case CoverageNoneTest::class: - $expected = []; - - break; - - case CoverageFunctionTest::class: - $expected = [ - TEST_FILES_PATH . 'CoveredFunction.php' => $lines, - ]; - - break; - - case NamespaceCoverageClassExtendedTest::class: - case NamespaceCoverageClassTest::class: - case NamespaceCoverageMethodTest::class: - case NamespaceCoverageNotPrivateTest::class: - case NamespaceCoverageNotProtectedTest::class: - case NamespaceCoverageNotPublicTest::class: - case NamespaceCoveragePrivateTest::class: - case NamespaceCoverageProtectedTest::class: - case NamespaceCoveragePublicTest::class: - case NamespaceCoverageCoversClassTest::class: - case NamespaceCoverageCoversClassPublicTest::class: - $expected = [ - TEST_FILES_PATH . 'NamespaceCoveredClass.php' => $lines, - ]; - - break; - - default: - $expected = [TEST_FILES_PATH . 'CoveredClass.php' => $lines]; - } - - $this->assertEqualsCanonicalizing( - $expected, - Test::getLinesToBeCovered( - $test, - 'testSomething' - ) - ); - } - - public function testGetLinesToBeCovered2(): void - { - $this->expectException(CodeCoverageException::class); - - Test::getLinesToBeCovered( - NotExistingCoveredElementTest::class, - 'testOne' - ); - } - - public function testGetLinesToBeCovered3(): void - { - $this->expectException(CodeCoverageException::class); - - Test::getLinesToBeCovered( - NotExistingCoveredElementTest::class, - 'testTwo' - ); - } - - public function testGetLinesToBeCovered4(): void - { - $this->expectException(CodeCoverageException::class); - - Test::getLinesToBeCovered( - NotExistingCoveredElementTest::class, - 'testThree' - ); - } - - public function testGetLinesToBeCoveredSkipsNonExistentMethods(): void - { - $this->assertSame( - [], - Test::getLinesToBeCovered( - NotExistingCoveredElementTest::class, - 'methodDoesNotExist' - ) - ); - } - - public function testTwoCoversDefaultClassAnnotationsAreNotAllowed(): void - { - $this->expectException(CodeCoverageException::class); - - Test::getLinesToBeCovered( - CoverageTwoDefaultClassAnnotations::class, - 'testSomething' - ); - } - - public function testFunctionParenthesesAreAllowed(): void - { - $this->assertSame( - [TEST_FILES_PATH . 'CoveredFunction.php' => range(10, 12)], - Test::getLinesToBeCovered( - CoverageFunctionParenthesesTest::class, - 'testSomething' - ) - ); - } - - public function testFunctionParenthesesAreAllowedWithWhitespace(): void - { - $this->assertSame( - [TEST_FILES_PATH . 'CoveredFunction.php' => range(10, 12)], - Test::getLinesToBeCovered( - CoverageFunctionParenthesesWhitespaceTest::class, - 'testSomething' - ) - ); - } - - public function testMethodParenthesesAreAllowed(): void - { - $this->assertSame( - [TEST_FILES_PATH . 'CoveredClass.php' => range(31, 35)], - Test::getLinesToBeCovered( - CoverageMethodParenthesesTest::class, - 'testSomething' - ) - ); - } - - public function testMethodParenthesesAreAllowedWithWhitespace(): void - { - $this->assertSame( - [TEST_FILES_PATH . 'CoveredClass.php' => range(31, 35)], - Test::getLinesToBeCovered( - CoverageMethodParenthesesWhitespaceTest::class, - 'testSomething' - ) - ); - } - - public function testNamespacedFunctionCanBeCoveredOrUsed(): void - { - $this->assertEquals( - [ - TEST_FILES_PATH . 'NamespaceCoveredFunction.php' => range(12, 15), - ], - Test::getLinesToBeCovered( - CoverageNamespacedFunctionTest::class, - 'testFunc' - ) - ); - } - - public function getLinesToBeCoveredProvider(): array - { - return [ - [ - CoverageNoneTest::class, - [], - ], - [ - CoverageClassExtendedTest::class, - array_merge(range(29, 46), range(12, 27)), - ], - [ - CoverageClassTest::class, - range(29, 46), - ], - [ - CoverageMethodTest::class, - range(31, 35), - ], - [ - CoverageMethodOneLineAnnotationTest::class, - range(31, 35), - ], - [ - CoverageNotPrivateTest::class, - array_merge(range(31, 35), range(37, 41)), - ], - [ - CoverageNotProtectedTest::class, - array_merge(range(31, 35), range(43, 45)), - ], - [ - CoverageNotPublicTest::class, - array_merge(range(37, 41), range(43, 45)), - ], - [ - CoveragePrivateTest::class, - range(43, 45), - ], - [ - CoverageProtectedTest::class, - range(37, 41), - ], - [ - CoveragePublicTest::class, - range(31, 35), - ], - [ - CoverageFunctionTest::class, - range(10, 12), - ], - [ - NamespaceCoverageClassExtendedTest::class, - array_merge(range(29, 46), range(12, 27)), - ], - [ - NamespaceCoverageClassTest::class, - range(29, 46), - ], - [ - NamespaceCoverageMethodTest::class, - range(31, 35), - ], - [ - NamespaceCoverageNotPrivateTest::class, - array_merge(range(31, 35), range(37, 41)), - ], - [ - NamespaceCoverageNotProtectedTest::class, - array_merge(range(31, 35), range(43, 45)), - ], - [ - NamespaceCoverageNotPublicTest::class, - array_merge(range(37, 41), range(43, 45)), - ], - [ - NamespaceCoveragePrivateTest::class, - range(43, 45), - ], - [ - NamespaceCoverageProtectedTest::class, - range(37, 41), - ], - [ - NamespaceCoveragePublicTest::class, - range(31, 35), - ], - [ - NamespaceCoverageCoversClassTest::class, - array_merge(range(43, 45), range(37, 41), range(31, 35), range(24, 26), range(19, 22), range(14, 17)), - ], - [ - NamespaceCoverageCoversClassPublicTest::class, - range(31, 35), - ], - [ - CoverageClassNothingTest::class, - false, - ], - [ - CoverageMethodNothingTest::class, - false, - ], - [ - CoverageCoversOverridesCoversNothingTest::class, - range(31, 35), - ], - [ - CoverageMethodNothingCoversMethod::class, - false, - ], - ]; - } - - public function testParseTestMethodAnnotationsIncorporatesTraits(): void - { - $result = Test::parseTestMethodAnnotations(ParseTestMethodAnnotationsMock::class); - - $this->assertArrayHasKey('class', $result); - $this->assertArrayHasKey('method', $result); - $this->assertArrayHasKey('theClassAnnotation', $result['class']); - $this->assertArrayHasKey('theTraitAnnotation', $result['class']); - } - - public function testCoversAnnotationIncludesTraitsUsedByClass(): void - { - $this->assertSame( - [ - TEST_FILES_PATH . '3194.php' => array_merge(range(14, 20), range(22, 30)), - ], - Test::getLinesToBeCovered( - Test3194::class, - 'testOne' - ) - ); - } - - /** - * @dataProvider canSkipCoverageProvider - */ - public function testCanSkipCoverage($testCase, $expectedCanSkip): void - { - $test = new $testCase('testSomething'); - $coverageRequired = Test::requiresCodeCoverageDataCollection($test); - $canSkipCoverage = !$coverageRequired; - - $this->assertEquals($expectedCanSkip, $canSkipCoverage); - } - - public function canSkipCoverageProvider(): array - { - return [ - [CoverageClassTest::class, false], - [CoverageClassWithoutAnnotationsTest::class, false], - [CoverageCoversOverridesCoversNothingTest::class, false], - [CoverageClassNothingTest::class, true], - [CoverageMethodNothingTest::class, true], - ]; - } - - /** - * @testdox Parse @author/@ticket for $class::$method - * @dataProvider getGroupsProvider - */ - public function testGetGroupsFromAuthorAndTicketAnnotations(string $class, string $method, array $groups): void - { - self::assertSame($groups, Test::getGroups($class, $method)); - } - - public function getGroupsProvider(): array - { - return [ - [ - NumericGroupAnnotationTest::class, - '', - ['Companion Cube', 't123456'], - ], - [ - NumericGroupAnnotationTest::class, - 'testTicketAnnotationSupportsNumericValue', - ['C. Lippy', 't123456', '3502'], - ], - [ - NumericGroupAnnotationTest::class, - 'testGroupAnnotationSupportsNumericValue', - ['Companion Cube', '3502', 't123456'], - ], - ]; - } - - private function getRequirementsTestClassFile(): string - { - if (!$this->fileRequirementsTest) { - $reflector = new ReflectionClass(RequirementsTest::class); - $this->fileRequirementsTest = realpath($reflector->getFileName()); - } - - return $this->fileRequirementsTest; - } -} diff --git a/tests/unit/Util/TestDox/CliTestDoxPrinterColorTest.php b/tests/unit/Util/TestDox/CliTestDoxPrinterColorTest.php deleted file mode 100644 index 2e44d6ab137..00000000000 --- a/tests/unit/Util/TestDox/CliTestDoxPrinterColorTest.php +++ /dev/null @@ -1,55 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\TestDox; - -use const PHP_EOL; -use function implode; -use PHPUnit\Framework\AssertionFailedError; -use PHPUnit\Framework\TestCase; -use PHPUnit\TestFixture\TestableCliTestDoxPrinter; -use PHPUnit\TextUI\DefaultResultPrinter; -use PHPUnit\Util\Color; - -/** - * @group testdox - * @small - */ -final class CliTestDoxPrinterColorTest extends TestCase -{ - /** - * @var TestableCliTestDoxPrinter - */ - private $printer; - - protected function setUp(): void - { - $this->printer = new TestableCliTestDoxPrinter(null, true, DefaultResultPrinter::COLOR_ALWAYS); - } - - protected function tearDown(): void - { - $this->printer = null; - } - - public function testColorizesDiffInFailureMessage(): void - { - $raw = implode(PHP_EOL, ['some message', '--- Expected', '+++ Actual', '@@ @@']); - $failure = new AssertionFailedError($raw); - - $this->printer->startTest($this); - $this->printer->addFailure($this, $failure, 0); - $this->printer->endTest($this, 0.001); - - $this->assertStringContainsString(Color::colorize('bg-red,fg-white', 'some message'), $this->printer->getBuffer()); - $this->assertStringContainsString(Color::colorize('fg-red', '---' . Color::dim('·') . 'Expected'), $this->printer->getBuffer()); - $this->assertStringContainsString(Color::colorize('fg-green', '+++' . Color::dim('·') . 'Actual'), $this->printer->getBuffer()); - $this->assertStringContainsString(Color::colorize('fg-cyan', '@@ @@'), $this->printer->getBuffer()); - } -} diff --git a/tests/unit/Util/TestDox/CliTestDoxPrinterTest.php b/tests/unit/Util/TestDox/CliTestDoxPrinterTest.php deleted file mode 100644 index 86c5c64fd94..00000000000 --- a/tests/unit/Util/TestDox/CliTestDoxPrinterTest.php +++ /dev/null @@ -1,214 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\TestDox; - -use Exception; -use PHPUnit\Framework\AssertionFailedError; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\Warning; -use PHPUnit\TestFixture\TestableCliTestDoxPrinter; - -/** - * @group testdox - * @small - */ -final class CliTestDoxPrinterTest extends TestCase -{ - /** - * @var TestableCliTestDoxPrinter - */ - private $printer; - - /** - * @var TestableCliTestDoxPrinter - */ - private $verbosePrinter; - - protected function setUp(): void - { - $this->printer = new TestableCliTestDoxPrinter; - $this->verbosePrinter = new TestableCliTestDoxPrinter(null, true); - } - - protected function tearDown(): void - { - $this->printer = null; - $this->verbosePrinter = null; - } - - public function testPrintsTheClassNameOfTheTestClass(): void - { - $this->printer->startTest($this); - $this->printer->endTest($this, 0); - - $this->assertStringStartsWith('Cli Test Dox Printer (PHPUnit\Util\TestDox\CliTestDoxPrinter)', $this->printer->getBuffer()); - } - - public function testPrintsThePrettifiedMethodName(): void - { - $this->printer->startTest($this); - $this->printer->endTest($this, 0.001); - - $this->assertStringContainsString('Prints the prettified method name', $this->printer->getBuffer()); - } - - public function testPrintsCheckMarkForSuccessfulTest(): void - { - $this->printer->startTest($this); - $this->printer->endTest($this, 0.001); - - $this->assertStringContainsString('✔', $this->printer->getBuffer()); - } - - public function testDoesNotPrintAdditionalInformationForSuccessfulTest(): void - { - $this->printer->startTest($this); - $this->printer->endTest($this, 0.001); - - $this->assertStringNotContainsString('│', $this->printer->getBuffer()); - } - - public function testPrintsCrossForTestWithError(): void - { - $this->printer->startTest($this); - $this->printer->addError($this, new Exception, 0); - $this->printer->endTest($this, 0.001); - - $this->assertStringContainsString('✘', $this->printer->getBuffer()); - } - - public function testPrintsAdditionalInformationForTestWithError(): void - { - $this->printer->startTest($this); - $this->printer->addError($this, new Exception, 0); - $this->printer->endTest($this, 0.001); - - $this->assertStringContainsString('│', $this->printer->getBuffer()); - } - - public function testPrintsWarningTriangleForTestWithWarning(): void - { - $this->printer->startTest($this); - $this->printer->addWarning($this, new Warning, 0); - $this->printer->endTest($this, 0.001); - - $this->assertStringContainsString('⚠', $this->printer->getBuffer()); - } - - public function testPrintsAdditionalInformationForTestWithWarning(): void - { - $this->printer->startTest($this); - $this->printer->addWarning($this, new Warning, 0); - $this->printer->endTest($this, 0.001); - - $this->assertStringContainsString('│', $this->printer->getBuffer()); - } - - public function testPrintsCrossForTestWithFailure(): void - { - $this->printer->startTest($this); - $this->printer->addFailure($this, new AssertionFailedError, 0); - $this->printer->endTest($this, 0.001); - - $this->assertStringContainsString('✘', $this->printer->getBuffer()); - } - - public function testPrintsAdditionalInformationForTestWithFailure(): void - { - $this->printer->startTest($this); - $this->printer->addFailure($this, new AssertionFailedError, 0); - $this->printer->endTest($this, 0.001); - - $this->assertStringContainsString('│', $this->printer->getBuffer()); - } - - public function testPrintsEmptySetSymbolForTestWithFailure(): void - { - $this->printer->startTest($this); - $this->printer->addIncompleteTest($this, new Exception, 0); - $this->printer->endTest($this, 0.001); - - $this->assertStringContainsString('∅', $this->printer->getBuffer()); - } - - public function testDoesNotPrintAdditionalInformationForIncompleteTestByDefault(): void - { - $this->printer->startTest($this); - $this->printer->addIncompleteTest($this, new Exception, 0); - $this->printer->endTest($this, 0.001); - - $this->assertStringNotContainsString('│', $this->printer->getBuffer()); - } - - public function testPrintsAdditionalInformationForIncompleteTestInVerboseMode(): void - { - $this->verbosePrinter->startTest($this); - $this->verbosePrinter->addIncompleteTest($this, new Exception('E_X_C_E_P_T_I_O_N'), 0); - $this->verbosePrinter->endTest($this, 0.001); - - $this->assertStringContainsString('│', $this->verbosePrinter->getBuffer()); - $this->assertStringContainsString('E_X_C_E_P_T_I_O_N', $this->verbosePrinter->getBuffer()); - } - - public function testPrintsRadioactiveSymbolForRiskyTest(): void - { - $this->printer->startTest($this); - $this->printer->addRiskyTest($this, new Exception, 0); - $this->printer->endTest($this, 0.001); - - $this->assertStringContainsString('☢', $this->printer->getBuffer()); - } - - public function testDoesNotPrintAdditionalInformationForRiskyTestByDefault(): void - { - $this->printer->startTest($this); - $this->printer->addRiskyTest($this, new Exception, 0); - $this->printer->endTest($this, 0.001); - - $this->assertStringNotContainsString('│', $this->printer->getBuffer()); - } - - public function testPrintsAdditionalInformationForRiskyTestInVerboseMode(): void - { - $this->verbosePrinter->startTest($this); - $this->verbosePrinter->addRiskyTest($this, new Exception, 0); - $this->verbosePrinter->endTest($this, 0.001); - - $this->assertStringContainsString('│', $this->verbosePrinter->getBuffer()); - } - - public function testPrintsArrowForSkippedTest(): void - { - $this->printer->startTest($this); - $this->printer->addSkippedTest($this, new Exception, 0); - $this->printer->endTest($this, 0.001); - - $this->assertStringContainsString('↩', $this->printer->getBuffer()); - } - - public function testDoesNotPrintAdditionalInformationForSkippedTestByDefault(): void - { - $this->printer->startTest($this); - $this->printer->addSkippedTest($this, new Exception, 0); - $this->printer->endTest($this, 0.001); - - $this->assertStringNotContainsString('│', $this->printer->getBuffer()); - } - - public function testPrintsAdditionalInformationForSkippedTestInVerboseMode(): void - { - $this->verbosePrinter->startTest($this); - $this->verbosePrinter->addSkippedTest($this, new Exception('S_K_I_P_P_E_D'), 0); - $this->verbosePrinter->endTest($this, 0.001); - - $this->assertStringContainsString('│', $this->verbosePrinter->getBuffer()); - $this->assertStringContainsString('S_K_I_P_P_E_D', $this->verbosePrinter->getBuffer()); - } -} diff --git a/tests/unit/Util/TestDox/NamePrettifierTest.php b/tests/unit/Util/TestDox/NamePrettifierTest.php deleted file mode 100644 index 9f9ee1fbed3..00000000000 --- a/tests/unit/Util/TestDox/NamePrettifierTest.php +++ /dev/null @@ -1,177 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\TestDox; - -use PHPUnit\Framework\TestCase; - -/** - * @group testdox - * @small - */ -final class NamePrettifierTest extends TestCase -{ - /** - * @var NamePrettifier - */ - private $namePrettifier; - - protected function setUp(): void - { - $this->namePrettifier = new NamePrettifier; - } - - protected function tearDown(): void - { - $this->namePrettifier = null; - } - - public function testTitleHasSensibleDefaults(): void - { - $this->assertEquals('Foo', $this->namePrettifier->prettifyTestClass('FooTest')); - $this->assertEquals('Foo', $this->namePrettifier->prettifyTestClass('TestFoo')); - $this->assertEquals('Foo', $this->namePrettifier->prettifyTestClass('TestFooTest')); - $this->assertEquals('Foo (Test\Foo)', $this->namePrettifier->prettifyTestClass('Test\FooTest')); - $this->assertEquals('Foo (Tests\Foo)', $this->namePrettifier->prettifyTestClass('Tests\FooTest')); - $this->assertEquals('Unnamed Tests', $this->namePrettifier->prettifyTestClass('TestTest')); - } - - public function testTestNameIsConvertedToASentence(): void - { - $this->assertEquals('This is a test', $this->namePrettifier->prettifyTestMethod('testThisIsATest')); - $this->assertEquals('This is a test', $this->namePrettifier->prettifyTestMethod('testThisIsATest2')); - $this->assertEquals('This is a test', $this->namePrettifier->prettifyTestMethod('this_is_a_test')); - $this->assertEquals('This is a test', $this->namePrettifier->prettifyTestMethod('test_this_is_a_test')); - $this->assertEquals('Foo for bar is 0', $this->namePrettifier->prettifyTestMethod('testFooForBarIs0')); - $this->assertEquals('Foo for baz is 1', $this->namePrettifier->prettifyTestMethod('testFooForBazIs1')); - $this->assertEquals('This has a 123 in its name', $this->namePrettifier->prettifyTestMethod('testThisHasA123InItsName')); - $this->assertEquals('', $this->namePrettifier->prettifyTestMethod('test')); - } - - /** - * @ticket 224 - */ - public function testTestNameIsNotGroupedWhenNotInSequence(): void - { - $this->assertEquals('Sets redirect header on 301', $this->namePrettifier->prettifyTestMethod('testSetsRedirectHeaderOn301')); - $this->assertEquals('Sets redirect header on 302', $this->namePrettifier->prettifyTestMethod('testSetsRedirectHeaderOn302')); - } - - public function testPhpDoxIgnoreDataKeys(): void - { - $test = new class extends TestCase { - public function __construct() - { - parent::__construct('testAddition', [ - 'augend' => 1, - 'addend' => 2, - 'result' => 3, - ]); - } - - public function testAddition(int $augend, int $addend, int $result): void - { - } - - public function getAnnotations(): array - { - return [ - 'method' => [ - 'testdox' => ['$augend + $addend = $result'], - ], - ]; - } - }; - - $this->assertEquals('1 + 2 = 3', $this->namePrettifier->prettifyTestCase($test)); - } - - public function testPhpDoxUsesDefaultValue(): void - { - $test = new class extends TestCase { - public function __construct() - { - parent::__construct('testAddition', []); - } - - public function testAddition(int $augend = 1, int $addend = 2, int $result = 3): void - { - } - - public function getAnnotations(): array - { - return [ - 'method' => [ - 'testdox' => ['$augend + $addend = $result'], - ], - ]; - } - }; - - $this->assertEquals('1 + 2 = 3', $this->namePrettifier->prettifyTestCase($test)); - } - - public function testPhpDoxArgumentExporting(): void - { - $test = new class extends TestCase { - public function __construct() - { - parent::__construct('testExport', [ - 'int' => 1234, - 'strInt' => '1234', - 'float' => 2.123, - 'strFloat' => '2.123', - 'string' => 'foo', - 'bool' => true, - 'null' => null, - ]); - } - - public function testExport($int, $strInt, $float, $strFloat, $string, $bool, $null): void - { - } - - public function getAnnotations(): array - { - return [ - 'method' => [ - 'testdox' => ['$int, $strInt, $float, $strFloat, $string, $bool, $null'], - ], - ]; - } - }; - - $this->assertEquals('1234, 1234, 2.123, 2.123, foo, true, NULL', $this->namePrettifier->prettifyTestCase($test)); - } - - public function testPhpDoxReplacesLongerVariablesFirst(): void - { - $test = new class extends TestCase { - public function __construct() - { - parent::__construct('testFoo', []); - } - - public function testFoo(int $a = 1, int $ab = 2, int $abc = 3): void - { - } - - public function getAnnotations(): array - { - return [ - 'method' => [ - 'testdox' => ['$a, "$a", $a$ab, $abc, $abcd, $ab'], - ], - ]; - } - }; - - $this->assertEquals('1, "1", 12, 3, $abcd, 2', $this->namePrettifier->prettifyTestCase($test)); - } -} diff --git a/tests/unit/Util/TestTest.php b/tests/unit/Util/TestTest.php new file mode 100644 index 00000000000..ad78c9f238e --- /dev/null +++ b/tests/unit/Util/TestTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use PHPUnit\TestFixture\TestCaseTest; +use ReflectionMethod; + +#[CoversClass(Test::class)] +#[Small] +final class TestTest extends TestCase +{ + public static function provider(): array + { + return [ + [true, new ReflectionMethod(TestCaseTest::class, 'testOne')], + [true, new ReflectionMethod(TestCaseTest::class, 'two')], + [false, new ReflectionMethod(TestCaseTest::class, 'three')], + [false, new ReflectionMethod(TestCaseTest::class, 'four')], + ]; + } + + #[DataProvider('provider')] + public function testDetectsTestMethods(bool $result, ReflectionMethod $method): void + { + $this->assertSame($result, Test::isTestMethod($method)); + } +} diff --git a/tests/unit/Util/VersionComparisonOperatorTest.php b/tests/unit/Util/VersionComparisonOperatorTest.php new file mode 100644 index 00000000000..39745275a9d --- /dev/null +++ b/tests/unit/Util/VersionComparisonOperatorTest.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(VersionComparisonOperator::class)] +#[CoversClass(InvalidVersionOperatorException::class)] +#[Small] +final class VersionComparisonOperatorTest extends TestCase +{ + /** + * @return non-empty-list> + */ + public static function validValues(): array + { + return [ + ['<'], + ['lt'], + ['<='], + ['le'], + ['>'], + ['gt'], + ['>='], + ['ge'], + ['=='], + ['='], + ['eq'], + ['!='], + ['<>'], + ['ne'], + ]; + } + + #[DataProvider('validValues')] + #[TestDox('Can be created from "$string"')] + public function testCanBeCreatedFromValidString(string $string): void + { + $this->assertSame($string, new VersionComparisonOperator($string)->asString()); + } + + public function testCannotBeCreatedFromInvalidString(): void + { + $this->expectException(InvalidVersionOperatorException::class); + + new VersionComparisonOperator(''); + } +} diff --git a/tests/unit/Util/XDebugFilterScriptGeneratorTest.php b/tests/unit/Util/XDebugFilterScriptGeneratorTest.php deleted file mode 100644 index 0209f142e5d..00000000000 --- a/tests/unit/Util/XDebugFilterScriptGeneratorTest.php +++ /dev/null @@ -1,112 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use const DIRECTORY_SEPARATOR; -use function addslashes; -use function basename; -use function dirname; -use function sprintf; -use PHPUnit\Framework\TestCase; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\CodeCoverage; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\Directory; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\DirectoryCollection; -use PHPUnit\TextUI\XmlConfiguration\File; -use PHPUnit\TextUI\XmlConfiguration\FileCollection; - -/** - * @small - * @covers \PHPUnit\Util\XdebugFilterScriptGenerator - */ -final class XDebugFilterScriptGeneratorTest extends TestCase -{ - public function testReturnsExpectedScript(): void - { - $expectedDirectory = sprintf(addslashes('%s' . DIRECTORY_SEPARATOR), __DIR__); - $expected = <<assertDirectoryDoesNotExist($directoryPathThatDoesNotExist); - - $filterConfiguration = new CodeCoverage( - DirectoryCollection::fromArray( - [ - new Directory( - __DIR__, - '', - '.php', - 'DEFAULT' - ), - new Directory( - sprintf('%s/', __DIR__), - '', - '.php', - 'DEFAULT' - ), - new Directory( - sprintf('%s/./%s', dirname(__DIR__), basename(__DIR__)), - '', - '.php', - 'DEFAULT' - ), - new Directory( - $directoryPathThatDoesNotExist, - '', - '.php', - 'DEFAULT' - ), - ] - ), - FileCollection::fromArray( - [ - new File('src/foo.php'), - new File('src/bar.php'), - ] - ), - DirectoryCollection::fromArray([]), - FileCollection::fromArray([]), - false, - true, - true, - false, - false, - false, - null, - null, - null, - null, - null, - null - ); - - $writer = new XdebugFilterScriptGenerator; - $actual = $writer->generate($filterConfiguration); - - $this->assertSame($expected, $actual); - } -} diff --git a/tests/unit/Util/Xml/LoaderTest.php b/tests/unit/Util/Xml/LoaderTest.php new file mode 100644 index 00000000000..44a31321299 --- /dev/null +++ b/tests/unit/Util/Xml/LoaderTest.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Xml; + +use DOMDocument; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Loader::class)] +#[Small] +final class LoaderTest extends TestCase +{ + public function testCanParseFileWithValidXml(): void + { + $document = (new Loader)->loadFile(__DIR__ . '/../../../_files/configuration.xml'); + + $this->assertInstanceOf(DOMDocument::class, $document); + } + + public function testCannotParseFileThatDoesNotExist(): void + { + $this->expectException(XmlException::class); + $this->expectExceptionMessage('Could not read XML from file "/does/not/exist.xml"'); + + (new Loader)->loadFile('/does/not/exist.xml'); + } + + public function testCannotParseEmptyFile(): void + { + $this->expectException(XmlException::class); + + (new Loader)->loadFile(__DIR__ . '/../../../_files/empty.xml'); + } + + public function testCannotParseFileWithInvalidXml(): void + { + $this->expectException(XmlException::class); + $this->expectExceptionMessageMatches("#Premature end of data in tag test line 1|EndTag: 'loadFile(__DIR__ . '/../../../_files/invalid.xml'); + } + + public function testCanParseStringWithValidXml(): void + { + $document = (new Loader)->load(''); + + $this->assertInstanceOf(DOMDocument::class, $document); + } + + public function testCannotParseEmptyString(): void + { + $this->expectException(XmlException::class); + $this->expectExceptionMessage('Could not parse XML from empty string'); + + (new Loader)->load(''); + } + + public function testCannotParseStringWithInvalidXml(): void + { + $this->expectException(XmlException::class); + $this->expectExceptionMessageMatches("#Premature end of data in tag test line 1|EndTag: 'load(''); + } +} diff --git a/tests/unit/Util/XmlTest.php b/tests/unit/Util/XmlTest.php index 3f30f91ffb8..120183ed864 100644 --- a/tests/unit/Util/XmlTest.php +++ b/tests/unit/Util/XmlTest.php @@ -13,17 +13,29 @@ use function ord; use function sprintf; use DOMDocument; -use PHPUnit\Framework\Exception; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; use PHPUnit\Framework\TestCase; +use PHPUnit\TextUI\XmlConfiguration\ValidationResult; -/** - * @small - */ +#[CoversClass(Xml::class)] +#[CoversClass(ValidationResult::class)] +#[Small] final class XmlTest extends TestCase { - /** - * @dataProvider charProvider - */ + public static function charProvider(): array + { + $data = []; + + for ($i = 0; $i < 256; $i++) { + $data[] = [chr($i)]; + } + + return $data; + } + + #[DataProvider('charProvider')] public function testPrepareString(string $char): void { $e = null; @@ -40,96 +52,11 @@ public function testPrepareString(string $char): void $this->assertNull( $e, sprintf( - '\PHPUnit\Util\Xml::prepareString("\x%02x") should not crash DomDocument', - ord($char) - ) + '%s::prepareString("\x%02x") should not crash %s', + Xml::class, + ord($char), + DOMDocument::class, + ), ); } - - public function charProvider(): array - { - $data = []; - - for ($i = 0; $i < 256; $i++) { - $data[] = [chr($i)]; - } - - return $data; - } - - public function testLoadEmptyString(): void - { - $this->expectException(Exception::class); - $this->expectExceptionMessage('Could not load XML from empty string'); - - Xml::load(''); - } - - public function testLoadArray(): void - { - $this->expectException(Exception::class); - $this->expectExceptionMessage('Could not load XML from array'); - - Xml::load([1, 2, 3]); - } - - public function testLoadBoolean(): void - { - $this->expectException(Exception::class); - $this->expectExceptionMessage('Could not load XML from boolean'); - - Xml::load(false); - } - - /** - * @testdox Nested xmlToVariable() - */ - public function testNestedXmlToVariable(): void - { - $xml = 'foobar'; - $dom = new DOMDocument; - $dom->loadXML($xml); - - $expected = [ - 'a' => [ - 'b' => 'foo', - ], - 'c' => 'bar', - ]; - - $actual = Xml::xmlToVariable($dom->documentElement); - - $this->assertSame($expected, $actual); - } - - /** - * @testdox xmlToVariable() can handle multiple of the same argument type - */ - public function testXmlToVariableCanHandleMultipleOfTheSameArgumentType(): void - { - $xml = 'abc'; - $dom = new DOMDocument; - $dom->loadXML($xml); - - $expected = ['a' => 'a', 'b' => 'b', 'c' => 'c']; - - $actual = Xml::xmlToVariable($dom->documentElement); - - $this->assertSame($expected, (array) $actual); - } - - /** - * @testdox xmlToVariable() can construct objects with constructor arguments recursively - */ - public function testXmlToVariableCanConstructObjectsWithConstructorArgumentsRecursively(): void - { - $xml = 'one0two'; - $dom = new DOMDocument; - $dom->loadXML($xml); - - $actual = Xml::xmlToVariable($dom->documentElement); - - $this->assertEquals('one', $actual->getMessage()); - $this->assertEquals('two', $actual->getPrevious()->getMessage()); - } } diff --git a/tools/.phpstan/composer.json b/tools/.phpstan/composer.json new file mode 100644 index 00000000000..9851b7c082c --- /dev/null +++ b/tools/.phpstan/composer.json @@ -0,0 +1,14 @@ +{ + "require-dev": { + "phpstan/phpstan": "^2.1.32", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan-strict-rules": "^2.0.7", + "tomasvotruba/type-coverage": "^2.0.2", + "ergebnis/phpstan-rules": "^2.12.0" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true + } + } +} diff --git a/tools/.phpstan/composer.lock b/tools/.phpstan/composer.lock new file mode 100644 index 00000000000..e823a15ade8 --- /dev/null +++ b/tools/.phpstan/composer.lock @@ -0,0 +1,387 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "2c8ea294a7a4e09e4793951d07859bf8", + "packages": [], + "packages-dev": [ + { + "name": "ergebnis/phpstan-rules", + "version": "2.12.0", + "source": { + "type": "git", + "url": "https://github.com/ergebnis/phpstan-rules.git", + "reference": "c4e0121a937b3b551f800a86e7d78794da2783ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ergebnis/phpstan-rules/zipball/c4e0121a937b3b551f800a86e7d78794da2783ea", + "reference": "c4e0121a937b3b551f800a86e7d78794da2783ea", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "phpstan/phpstan": "^2.1.8" + }, + "require-dev": { + "codeception/codeception": "^4.0.0 || ^5.0.0", + "doctrine/orm": "^2.20.0 || ^3.3.0", + "ergebnis/composer-normalize": "^2.47.0", + "ergebnis/license": "^2.6.0", + "ergebnis/php-cs-fixer-config": "^6.54.0", + "ergebnis/phpunit-slow-test-detector": "^2.20.0", + "fakerphp/faker": "^1.24.1", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpstan/phpstan-phpunit": "^2.0.7", + "phpstan/phpstan-strict-rules": "^2.0.6", + "phpunit/phpunit": "^9.6.21", + "psr/container": "^2.0.2", + "symfony/finder": "^5.4.45", + "symfony/process": "^5.4.47" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Ergebnis\\PHPStan\\Rules\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com/" + } + ], + "description": "Provides rules for phpstan/phpstan.", + "homepage": "https://github.com/ergebnis/phpstan-rules", + "keywords": [ + "PHPStan", + "phpstan-rules" + ], + "support": { + "issues": "https://github.com/ergebnis/phpstan-rules/issues", + "security": "https://github.com/ergebnis/phpstan-rules/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/phpstan-rules" + }, + "time": "2025-09-07T13:31:33+00:00" + }, + { + "name": "nette/utils", + "version": "v4.0.8", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/c930ca4e3cf4f17dcfb03037703679d2396d2ede", + "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede", + "shasum": "" + }, + "require": { + "php": "8.0 - 8.5" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "^1.2", + "nette/tester": "^2.5", + "phpstan/phpstan-nette": "^2.0@stable", + "tracy/tracy": "^2.9" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com/" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org/", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.0.8" + }, + "time": "2025-08-06T21:43:34+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/85e90b3942d06b2326fba0403ec24fe912372936", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.9.0 || ^2.0" + }, + "require-dev": { + "composer/composer": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.4.3" + }, + "time": "2024-09-04T20:21:43+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "2.1.32", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e126cad1e30a99b137b8ed75a85a676450ebb227", + "reference": "e126cad1e30a99b137b8ed75a85a676450ebb227", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2025-11-11T15:18:17+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "2.0.7", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "d6211c46213d4181054b3d77b10a5c5cb0d59538" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/d6211c46213d4181054b3d77b10a5c5cb0d59538", + "reference": "d6211c46213d4181054b3d77b10a5c5cb0d59538", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.1.29" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.7" + }, + "time": "2025-09-26T11:19:08+00:00" + }, + { + "name": "tomasvotruba/type-coverage", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/TomasVotruba/type-coverage.git", + "reference": "d033429580f2c18bda538fa44f2939236a990e0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/TomasVotruba/type-coverage/zipball/d033429580f2c18bda538fa44f2939236a990e0c", + "reference": "d033429580f2c18bda538fa44f2939236a990e0c", + "shasum": "" + }, + "require": { + "nette/utils": "^3.2 || ^4.0", + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "config/extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TomasVotruba\\TypeCoverage\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Measure type coverage of your project", + "keywords": [ + "phpstan-extension", + "static analysis" + ], + "support": { + "issues": "https://github.com/TomasVotruba/type-coverage/issues", + "source": "https://github.com/TomasVotruba/type-coverage/tree/2.0.2" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2025-01-07T00:10:26+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/tools/.phpstan/vendor/autoload.php b/tools/.phpstan/vendor/autoload.php new file mode 100644 index 00000000000..94a663ef036 --- /dev/null +++ b/tools/.phpstan/vendor/autoload.php @@ -0,0 +1,22 @@ +handle = fopen($realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpstan/phpstan/phpstan'); + } +} + +return include __DIR__ . '/..'.'/phpstan/phpstan/phpstan'; diff --git a/tools/.phpstan/vendor/bin/phpstan.phar b/tools/.phpstan/vendor/bin/phpstan.phar new file mode 100755 index 00000000000..bcb169015c0 --- /dev/null +++ b/tools/.phpstan/vendor/bin/phpstan.phar @@ -0,0 +1,118 @@ +#!/usr/bin/env php +handle = fopen($realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpstan/phpstan/phpstan.phar'); + } +} + +return include __DIR__ . '/..'.'/phpstan/phpstan/phpstan.phar'; diff --git a/tools/.phpstan/vendor/composer/ClassLoader.php b/tools/.phpstan/vendor/composer/ClassLoader.php new file mode 100644 index 00000000000..7824d8f7eaf --- /dev/null +++ b/tools/.phpstan/vendor/composer/ClassLoader.php @@ -0,0 +1,579 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ + private $vendorDir; + + // PSR-4 + /** + * @var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var list + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array>> + */ + private $prefixesPsr0 = array(); + /** + * @var list + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var array + */ + private $missingClasses = array(); + + /** @var string|null */ + private $apcuPrefix; + + /** + * @var array + */ + private static $registeredLoaders = array(); + + /** + * @param string|null $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return array> + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return list + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return list + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return array Array of classname => path + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders keyed by their corresponding vendor directories. + * + * @return array + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/tools/.phpstan/vendor/composer/InstalledVersions.php b/tools/.phpstan/vendor/composer/InstalledVersions.php new file mode 100644 index 00000000000..2052022fd8e --- /dev/null +++ b/tools/.phpstan/vendor/composer/InstalledVersions.php @@ -0,0 +1,396 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to + * @internal + */ + private static $selfDir = null; + + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool + */ + private static $installedIsLocalDir; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + + // when using reload, we disable the duplicate protection to ensure that self::$installed data is + // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not, + // so we have to assume it does not, and that may result in duplicate data being returned when listing + // all installed packages for example + self::$installedIsLocalDir = false; + } + + /** + * @return string + */ + private static function getSelfDir() + { + if (self::$selfDir === null) { + self::$selfDir = strtr(__DIR__, '\\', '/'); + } + + return self::$selfDir; + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + $copiedLocalDir = false; + + if (self::$canGetVendors) { + $selfDir = self::getSelfDir(); + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + $vendorDir = strtr($vendorDir, '\\', '/'); + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + self::$installedByVendor[$vendorDir] = $required; + $installed[] = $required; + if (self::$installed === null && $vendorDir.'/composer' === $selfDir) { + self::$installed = $required; + self::$installedIsLocalDir = true; + } + } + if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) { + $copiedLocalDir = true; + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array() && !$copiedLocalDir) { + $installed[] = self::$installed; + } + + return $installed; + } +} diff --git a/tools/.phpstan/vendor/composer/LICENSE b/tools/.phpstan/vendor/composer/LICENSE new file mode 100644 index 00000000000..f27399a042d --- /dev/null +++ b/tools/.phpstan/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +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. + diff --git a/tools/.phpstan/vendor/composer/autoload_classmap.php b/tools/.phpstan/vendor/composer/autoload_classmap.php new file mode 100644 index 00000000000..c66a950246a --- /dev/null +++ b/tools/.phpstan/vendor/composer/autoload_classmap.php @@ -0,0 +1,60 @@ + $vendorDir . '/composer/InstalledVersions.php', + 'Nette\\ArgumentOutOfRangeException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\DeprecatedException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\DirectoryNotFoundException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\FileNotFoundException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\HtmlStringable' => $vendorDir . '/nette/utils/src/HtmlStringable.php', + 'Nette\\IOException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\InvalidArgumentException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\InvalidStateException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\Iterators\\CachingIterator' => $vendorDir . '/nette/utils/src/Iterators/CachingIterator.php', + 'Nette\\Iterators\\Mapper' => $vendorDir . '/nette/utils/src/Iterators/Mapper.php', + 'Nette\\Localization\\ITranslator' => $vendorDir . '/nette/utils/src/compatibility.php', + 'Nette\\Localization\\Translator' => $vendorDir . '/nette/utils/src/Translator.php', + 'Nette\\MemberAccessException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\NotImplementedException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\NotSupportedException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\OutOfRangeException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\ShouldNotHappenException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\SmartObject' => $vendorDir . '/nette/utils/src/SmartObject.php', + 'Nette\\StaticClass' => $vendorDir . '/nette/utils/src/StaticClass.php', + 'Nette\\UnexpectedValueException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\Utils\\ArrayHash' => $vendorDir . '/nette/utils/src/Utils/ArrayHash.php', + 'Nette\\Utils\\ArrayList' => $vendorDir . '/nette/utils/src/Utils/ArrayList.php', + 'Nette\\Utils\\Arrays' => $vendorDir . '/nette/utils/src/Utils/Arrays.php', + 'Nette\\Utils\\AssertionException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Callback' => $vendorDir . '/nette/utils/src/Utils/Callback.php', + 'Nette\\Utils\\DateTime' => $vendorDir . '/nette/utils/src/Utils/DateTime.php', + 'Nette\\Utils\\FileInfo' => $vendorDir . '/nette/utils/src/Utils/FileInfo.php', + 'Nette\\Utils\\FileSystem' => $vendorDir . '/nette/utils/src/Utils/FileSystem.php', + 'Nette\\Utils\\Finder' => $vendorDir . '/nette/utils/src/Utils/Finder.php', + 'Nette\\Utils\\Floats' => $vendorDir . '/nette/utils/src/Utils/Floats.php', + 'Nette\\Utils\\Helpers' => $vendorDir . '/nette/utils/src/Utils/Helpers.php', + 'Nette\\Utils\\Html' => $vendorDir . '/nette/utils/src/Utils/Html.php', + 'Nette\\Utils\\IHtmlString' => $vendorDir . '/nette/utils/src/compatibility.php', + 'Nette\\Utils\\Image' => $vendorDir . '/nette/utils/src/Utils/Image.php', + 'Nette\\Utils\\ImageColor' => $vendorDir . '/nette/utils/src/Utils/ImageColor.php', + 'Nette\\Utils\\ImageException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\ImageType' => $vendorDir . '/nette/utils/src/Utils/ImageType.php', + 'Nette\\Utils\\Iterables' => $vendorDir . '/nette/utils/src/Utils/Iterables.php', + 'Nette\\Utils\\Json' => $vendorDir . '/nette/utils/src/Utils/Json.php', + 'Nette\\Utils\\JsonException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\ObjectHelpers' => $vendorDir . '/nette/utils/src/Utils/ObjectHelpers.php', + 'Nette\\Utils\\Paginator' => $vendorDir . '/nette/utils/src/Utils/Paginator.php', + 'Nette\\Utils\\Random' => $vendorDir . '/nette/utils/src/Utils/Random.php', + 'Nette\\Utils\\Reflection' => $vendorDir . '/nette/utils/src/Utils/Reflection.php', + 'Nette\\Utils\\ReflectionMethod' => $vendorDir . '/nette/utils/src/Utils/ReflectionMethod.php', + 'Nette\\Utils\\RegexpException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Strings' => $vendorDir . '/nette/utils/src/Utils/Strings.php', + 'Nette\\Utils\\Type' => $vendorDir . '/nette/utils/src/Utils/Type.php', + 'Nette\\Utils\\UnknownImageFileException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Validators' => $vendorDir . '/nette/utils/src/Utils/Validators.php', +); diff --git a/tools/.phpstan/vendor/composer/autoload_files.php b/tools/.phpstan/vendor/composer/autoload_files.php new file mode 100644 index 00000000000..b62e293ba24 --- /dev/null +++ b/tools/.phpstan/vendor/composer/autoload_files.php @@ -0,0 +1,10 @@ + $vendorDir . '/phpstan/phpstan/bootstrap.php', +); diff --git a/tools/.phpstan/vendor/composer/autoload_namespaces.php b/tools/.phpstan/vendor/composer/autoload_namespaces.php new file mode 100644 index 00000000000..15a2ff3ad6d --- /dev/null +++ b/tools/.phpstan/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/tomasvotruba/type-coverage/src'), + 'PHPStan\\ExtensionInstaller\\' => array($vendorDir . '/phpstan/extension-installer/src'), + 'PHPStan\\' => array($vendorDir . '/phpstan/phpstan-strict-rules/src'), + 'Nette\\' => array($vendorDir . '/nette/utils/src'), + 'Ergebnis\\PHPStan\\Rules\\' => array($vendorDir . '/ergebnis/phpstan-rules/src'), +); diff --git a/tools/.phpstan/vendor/composer/autoload_real.php b/tools/.phpstan/vendor/composer/autoload_real.php new file mode 100644 index 00000000000..c91f94e3d39 --- /dev/null +++ b/tools/.phpstan/vendor/composer/autoload_real.php @@ -0,0 +1,48 @@ +register(true); + + $filesToLoad = \Composer\Autoload\ComposerStaticInitf9e7218f71d5874b5632927df4f72bd7::$files; + $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; + } + }, null, null); + foreach ($filesToLoad as $fileIdentifier => $file) { + $requireFile($fileIdentifier, $file); + } + + return $loader; + } +} diff --git a/tools/.phpstan/vendor/composer/autoload_static.php b/tools/.phpstan/vendor/composer/autoload_static.php new file mode 100644 index 00000000000..b66b4992eae --- /dev/null +++ b/tools/.phpstan/vendor/composer/autoload_static.php @@ -0,0 +1,119 @@ + __DIR__ . '/..' . '/phpstan/phpstan/bootstrap.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'T' => + array ( + 'TomasVotruba\\TypeCoverage\\' => 26, + ), + 'P' => + array ( + 'PHPStan\\ExtensionInstaller\\' => 27, + 'PHPStan\\' => 8, + ), + 'N' => + array ( + 'Nette\\' => 6, + ), + 'E' => + array ( + 'Ergebnis\\PHPStan\\Rules\\' => 23, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'TomasVotruba\\TypeCoverage\\' => + array ( + 0 => __DIR__ . '/..' . '/tomasvotruba/type-coverage/src', + ), + 'PHPStan\\ExtensionInstaller\\' => + array ( + 0 => __DIR__ . '/..' . '/phpstan/extension-installer/src', + ), + 'PHPStan\\' => + array ( + 0 => __DIR__ . '/..' . '/phpstan/phpstan-strict-rules/src', + ), + 'Nette\\' => + array ( + 0 => __DIR__ . '/..' . '/nette/utils/src', + ), + 'Ergebnis\\PHPStan\\Rules\\' => + array ( + 0 => __DIR__ . '/..' . '/ergebnis/phpstan-rules/src', + ), + ); + + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'Nette\\ArgumentOutOfRangeException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\DeprecatedException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\DirectoryNotFoundException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\FileNotFoundException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\HtmlStringable' => __DIR__ . '/..' . '/nette/utils/src/HtmlStringable.php', + 'Nette\\IOException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\InvalidArgumentException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\InvalidStateException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\Iterators\\CachingIterator' => __DIR__ . '/..' . '/nette/utils/src/Iterators/CachingIterator.php', + 'Nette\\Iterators\\Mapper' => __DIR__ . '/..' . '/nette/utils/src/Iterators/Mapper.php', + 'Nette\\Localization\\ITranslator' => __DIR__ . '/..' . '/nette/utils/src/compatibility.php', + 'Nette\\Localization\\Translator' => __DIR__ . '/..' . '/nette/utils/src/Translator.php', + 'Nette\\MemberAccessException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\NotImplementedException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\NotSupportedException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\OutOfRangeException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\ShouldNotHappenException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\SmartObject' => __DIR__ . '/..' . '/nette/utils/src/SmartObject.php', + 'Nette\\StaticClass' => __DIR__ . '/..' . '/nette/utils/src/StaticClass.php', + 'Nette\\UnexpectedValueException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\Utils\\ArrayHash' => __DIR__ . '/..' . '/nette/utils/src/Utils/ArrayHash.php', + 'Nette\\Utils\\ArrayList' => __DIR__ . '/..' . '/nette/utils/src/Utils/ArrayList.php', + 'Nette\\Utils\\Arrays' => __DIR__ . '/..' . '/nette/utils/src/Utils/Arrays.php', + 'Nette\\Utils\\AssertionException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Callback' => __DIR__ . '/..' . '/nette/utils/src/Utils/Callback.php', + 'Nette\\Utils\\DateTime' => __DIR__ . '/..' . '/nette/utils/src/Utils/DateTime.php', + 'Nette\\Utils\\FileInfo' => __DIR__ . '/..' . '/nette/utils/src/Utils/FileInfo.php', + 'Nette\\Utils\\FileSystem' => __DIR__ . '/..' . '/nette/utils/src/Utils/FileSystem.php', + 'Nette\\Utils\\Finder' => __DIR__ . '/..' . '/nette/utils/src/Utils/Finder.php', + 'Nette\\Utils\\Floats' => __DIR__ . '/..' . '/nette/utils/src/Utils/Floats.php', + 'Nette\\Utils\\Helpers' => __DIR__ . '/..' . '/nette/utils/src/Utils/Helpers.php', + 'Nette\\Utils\\Html' => __DIR__ . '/..' . '/nette/utils/src/Utils/Html.php', + 'Nette\\Utils\\IHtmlString' => __DIR__ . '/..' . '/nette/utils/src/compatibility.php', + 'Nette\\Utils\\Image' => __DIR__ . '/..' . '/nette/utils/src/Utils/Image.php', + 'Nette\\Utils\\ImageColor' => __DIR__ . '/..' . '/nette/utils/src/Utils/ImageColor.php', + 'Nette\\Utils\\ImageException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\ImageType' => __DIR__ . '/..' . '/nette/utils/src/Utils/ImageType.php', + 'Nette\\Utils\\Iterables' => __DIR__ . '/..' . '/nette/utils/src/Utils/Iterables.php', + 'Nette\\Utils\\Json' => __DIR__ . '/..' . '/nette/utils/src/Utils/Json.php', + 'Nette\\Utils\\JsonException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\ObjectHelpers' => __DIR__ . '/..' . '/nette/utils/src/Utils/ObjectHelpers.php', + 'Nette\\Utils\\Paginator' => __DIR__ . '/..' . '/nette/utils/src/Utils/Paginator.php', + 'Nette\\Utils\\Random' => __DIR__ . '/..' . '/nette/utils/src/Utils/Random.php', + 'Nette\\Utils\\Reflection' => __DIR__ . '/..' . '/nette/utils/src/Utils/Reflection.php', + 'Nette\\Utils\\ReflectionMethod' => __DIR__ . '/..' . '/nette/utils/src/Utils/ReflectionMethod.php', + 'Nette\\Utils\\RegexpException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Strings' => __DIR__ . '/..' . '/nette/utils/src/Utils/Strings.php', + 'Nette\\Utils\\Type' => __DIR__ . '/..' . '/nette/utils/src/Utils/Type.php', + 'Nette\\Utils\\UnknownImageFileException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Validators' => __DIR__ . '/..' . '/nette/utils/src/Utils/Validators.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInitf9e7218f71d5874b5632927df4f72bd7::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInitf9e7218f71d5874b5632927df4f72bd7::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInitf9e7218f71d5874b5632927df4f72bd7::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/tools/.phpstan/vendor/composer/installed.json b/tools/.phpstan/vendor/composer/installed.json new file mode 100644 index 00000000000..76385ec26b9 --- /dev/null +++ b/tools/.phpstan/vendor/composer/installed.json @@ -0,0 +1,399 @@ +{ + "packages": [ + { + "name": "ergebnis/phpstan-rules", + "version": "2.12.0", + "version_normalized": "2.12.0.0", + "source": { + "type": "git", + "url": "https://github.com/ergebnis/phpstan-rules.git", + "reference": "c4e0121a937b3b551f800a86e7d78794da2783ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ergebnis/phpstan-rules/zipball/c4e0121a937b3b551f800a86e7d78794da2783ea", + "reference": "c4e0121a937b3b551f800a86e7d78794da2783ea", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "phpstan/phpstan": "^2.1.8" + }, + "require-dev": { + "codeception/codeception": "^4.0.0 || ^5.0.0", + "doctrine/orm": "^2.20.0 || ^3.3.0", + "ergebnis/composer-normalize": "^2.47.0", + "ergebnis/license": "^2.6.0", + "ergebnis/php-cs-fixer-config": "^6.54.0", + "ergebnis/phpunit-slow-test-detector": "^2.20.0", + "fakerphp/faker": "^1.24.1", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpstan/phpstan-phpunit": "^2.0.7", + "phpstan/phpstan-strict-rules": "^2.0.6", + "phpunit/phpunit": "^9.6.21", + "psr/container": "^2.0.2", + "symfony/finder": "^5.4.45", + "symfony/process": "^5.4.47" + }, + "time": "2025-09-07T13:31:33+00:00", + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Ergebnis\\PHPStan\\Rules\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com/" + } + ], + "description": "Provides rules for phpstan/phpstan.", + "homepage": "https://github.com/ergebnis/phpstan-rules", + "keywords": [ + "PHPStan", + "phpstan-rules" + ], + "support": { + "issues": "https://github.com/ergebnis/phpstan-rules/issues", + "security": "https://github.com/ergebnis/phpstan-rules/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/phpstan-rules" + }, + "install-path": "../ergebnis/phpstan-rules" + }, + { + "name": "nette/utils", + "version": "v4.0.8", + "version_normalized": "4.0.8.0", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/c930ca4e3cf4f17dcfb03037703679d2396d2ede", + "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede", + "shasum": "" + }, + "require": { + "php": "8.0 - 8.5" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "^1.2", + "nette/tester": "^2.5", + "phpstan/phpstan-nette": "^2.0@stable", + "tracy/tracy": "^2.9" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" + }, + "time": "2025-08-06T21:43:34+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com/" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org/", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.0.8" + }, + "install-path": "../nette/utils" + }, + { + "name": "phpstan/extension-installer", + "version": "1.4.3", + "version_normalized": "1.4.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/85e90b3942d06b2326fba0403ec24fe912372936", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.9.0 || ^2.0" + }, + "require-dev": { + "composer/composer": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0" + }, + "time": "2024-09-04T20:21:43+00:00", + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.4.3" + }, + "install-path": "../phpstan/extension-installer" + }, + { + "name": "phpstan/phpstan", + "version": "2.1.32", + "version_normalized": "2.1.32.0", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e126cad1e30a99b137b8ed75a85a676450ebb227", + "reference": "e126cad1e30a99b137b8ed75a85a676450ebb227", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "time": "2025-11-11T15:18:17+00:00", + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "install-path": "../phpstan/phpstan" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "2.0.7", + "version_normalized": "2.0.7.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "d6211c46213d4181054b3d77b10a5c5cb0d59538" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/d6211c46213d4181054b3d77b10a5c5cb0d59538", + "reference": "d6211c46213d4181054b3d77b10a5c5cb0d59538", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.1.29" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6" + }, + "time": "2025-09-26T11:19:08+00:00", + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.7" + }, + "install-path": "../phpstan/phpstan-strict-rules" + }, + { + "name": "tomasvotruba/type-coverage", + "version": "2.0.2", + "version_normalized": "2.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/TomasVotruba/type-coverage.git", + "reference": "d033429580f2c18bda538fa44f2939236a990e0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/TomasVotruba/type-coverage/zipball/d033429580f2c18bda538fa44f2939236a990e0c", + "reference": "d033429580f2c18bda538fa44f2939236a990e0c", + "shasum": "" + }, + "require": { + "nette/utils": "^3.2 || ^4.0", + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0" + }, + "time": "2025-01-07T00:10:26+00:00", + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "config/extension.neon" + ] + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "TomasVotruba\\TypeCoverage\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Measure type coverage of your project", + "keywords": [ + "phpstan-extension", + "static analysis" + ], + "support": { + "issues": "https://github.com/TomasVotruba/type-coverage/issues", + "source": "https://github.com/TomasVotruba/type-coverage/tree/2.0.2" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "install-path": "../tomasvotruba/type-coverage" + } + ], + "dev": true, + "dev-package-names": [ + "ergebnis/phpstan-rules", + "nette/utils", + "phpstan/extension-installer", + "phpstan/phpstan", + "phpstan/phpstan-strict-rules", + "tomasvotruba/type-coverage" + ] +} diff --git a/tools/.phpstan/vendor/composer/installed.php b/tools/.phpstan/vendor/composer/installed.php new file mode 100644 index 00000000000..501f917aa43 --- /dev/null +++ b/tools/.phpstan/vendor/composer/installed.php @@ -0,0 +1,77 @@ + array( + 'name' => '__root__', + 'pretty_version' => '12.5.x-dev', + 'version' => '12.5.9999999.9999999-dev', + 'reference' => '48e4bfd6c671ec0410cf92447e73bde6598539ba', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => true, + ), + 'versions' => array( + '__root__' => array( + 'pretty_version' => '12.5.x-dev', + 'version' => '12.5.9999999.9999999-dev', + 'reference' => '48e4bfd6c671ec0410cf92447e73bde6598539ba', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'ergebnis/phpstan-rules' => array( + 'pretty_version' => '2.12.0', + 'version' => '2.12.0.0', + 'reference' => 'c4e0121a937b3b551f800a86e7d78794da2783ea', + 'type' => 'phpstan-extension', + 'install_path' => __DIR__ . '/../ergebnis/phpstan-rules', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'nette/utils' => array( + 'pretty_version' => 'v4.0.8', + 'version' => '4.0.8.0', + 'reference' => 'c930ca4e3cf4f17dcfb03037703679d2396d2ede', + 'type' => 'library', + 'install_path' => __DIR__ . '/../nette/utils', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'phpstan/extension-installer' => array( + 'pretty_version' => '1.4.3', + 'version' => '1.4.3.0', + 'reference' => '85e90b3942d06b2326fba0403ec24fe912372936', + 'type' => 'composer-plugin', + 'install_path' => __DIR__ . '/../phpstan/extension-installer', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'phpstan/phpstan' => array( + 'pretty_version' => '2.1.32', + 'version' => '2.1.32.0', + 'reference' => 'e126cad1e30a99b137b8ed75a85a676450ebb227', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpstan/phpstan', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'phpstan/phpstan-strict-rules' => array( + 'pretty_version' => '2.0.7', + 'version' => '2.0.7.0', + 'reference' => 'd6211c46213d4181054b3d77b10a5c5cb0d59538', + 'type' => 'phpstan-extension', + 'install_path' => __DIR__ . '/../phpstan/phpstan-strict-rules', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'tomasvotruba/type-coverage' => array( + 'pretty_version' => '2.0.2', + 'version' => '2.0.2.0', + 'reference' => 'd033429580f2c18bda538fa44f2939236a990e0c', + 'type' => 'phpstan-extension', + 'install_path' => __DIR__ . '/../tomasvotruba/type-coverage', + 'aliases' => array(), + 'dev_requirement' => true, + ), + ), +); diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/CHANGELOG.md b/tools/.phpstan/vendor/ergebnis/phpstan-rules/CHANGELOG.md new file mode 100644 index 00000000000..7729147afef --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/CHANGELOG.md @@ -0,0 +1,748 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +For a full diff see [`2.12.0...main`][2.12.0...main]. + +## [`2.12.0`][2.12.0] + +For a full diff see [`2.11.0...2.12.0`][2.11.0...2.12.0]. + +### Added + +- Added support for PHP 8.5 ([#977]), by [@localheinz] + +## [`2.11.0`][2.11.0] + +For a full diff see [`2.10.5...2.11.0`][2.10.5...2.11.0]. + +### Changed + +- Allowed installation on PHP 8.5 ([#972]), by [@localheinz] + +## [`2.10.5`][2.10.5] + +For a full diff see [`2.10.4...2.10.5`][2.10.4...2.10.5]. + +### Fixed + +- Adjusted `Methods\NoNamedArgumentRule` to handle calls to constructors of variable class names ([#957]), by [@localheinz] +- Adjusted `Methods\NoNamedArgumentRule` to describe known calls only ([#958]), by [@localheinz] + +## [`2.10.4`][2.10.4] + +For a full diff see [`2.10.3...2.10.4`][2.10.3...2.10.4]. + +### Fixed + +- Adjusted `Methods\NoNamedArgumentRule` to handle static calls on variable expressions ([#947]), by [@localheinz] +- Adjusted `Methods\NoNamedArgumentRule` to handle calls on invokables ([#948]), by [@localheinz] +- Adjusted `Methods\NoNamedArgumentRule` to handle calls on callables assigned to properties ([#949]), by [@localheinz] +- Adjusted `Methods\NoNamedArgumentRule` to handle all other calls with generic error message ([#951]), by [@localheinz] + +## [`2.10.3`][2.10.3] + +For a full diff see [`2.10.2...2.10.3`][2.10.2...2.10.3]. + +### Fixed + +- Adjusted `Methods\InvokeParentHookMethodRule` to ignore comments ([#944]), by [@localheinz] + +## [`2.10.2`][2.10.2] + +For a full diff see [`2.10.1...2.10.2`][2.10.1...2.10.2]. + +### Fixed + +- Renamed error identifier for `Methods\InvokeParentHookMethodRule` ([#943]), by [@localheinz] + +## [`2.10.1`][2.10.1] + +For a full diff see [`2.10.0...2.10.1`][2.10.0...2.10.1]. + +### Fixed + +- Fixed schema for configuration of `Methods\InvokeParentHookMethodRule` ([#940]), by [@localheinz] + +## [`2.10.0`][2.10.0] + +For a full diff see [`2.9.0...2.10.0`][2.9.0...2.10.0]. + +### Added + +- Added `Methods\InvokeParentHookMethodRule`, which reports an error when a hook method that overrides a hook method in a parent class does not invoke the overridden hook method in the expected order ([#939]), by [@localheinz] + +## [`2.9.0`][2.9.0] + +For a full diff see [`2.8.0...2.9.0`][2.8.0...2.9.0]. + +### Added + +- Added `CallLikes\NoNamedArgumentRule`, which reports an error when an anonymous function, a function, or a method is invoked using a named argument ([#914]), by [@localheinz] + +### Changed + +- Required `phpstan/phpstan:^2.1.8` ([#938]), by [@localheinz] + +## [`2.8.0`][2.8.0] + +For a full diff see [`2.7.0...2.8.0`][2.7.0...2.8.0]. + +### Added + +- Added `allRules` parameter to allow disabling and enabling all rules ([#913]), by [@localheinz] +- Added `Expressions\NoAssignByReferenceRule`, which reports an error when a variable is assigned by reference ([#914]), by [@localheinz] + +## [`2.7.0`][2.7.0] + +For a full diff see [`2.6.1...2.7.0`][2.6.1...2.7.0]. + +### Added + +- Added `Closures\NoParameterPassedByReferenceRule`, `Functions\NoParameterPassedByReferenceRule`, `Methods\NoParameterPassedByReferenceRule`, which report an error when a closure, a function, or a method has a parameter that is passed by reference ([#911]), by [@localheinz] +- Added `Functions\NoReturnByReferenceRule` and `Methods\NoReturnByReferenceRule`, which report an error when a function or a method returns by reference ([#912]), by [@localheinz] + +## [`2.6.1`][2.6.1] + +For a full diff see [`2.6.0...2.6.1`][2.6.0...2.6.1]. + +### Fixed + +- Adjusted `Methods\NoParameterWithNullableTypeDeclarationRule` to use the appropriate error identifier ([#902]), by [@manuelkiessling] + +## [`2.6.0`][2.6.0] + +For a full diff see [`2.5.2...2.6.0`][2.5.2...2.6.0]. + +### Added + +- Added support for `phpstan/phpstan:^2.0.0` ([#873]), by [@localheinz] + +## [`2.5.2`][2.5.2] + +For a full diff see [`2.5.1...2.5.2`][2.5.1...2.5.2]. + +### Fixed + +- Adjusted `Closures\NoNullableReturnTypeDeclarationRule`, `Closures\NoParameterWithNullableTypeDeclarationRule`, `Functions\NoNullableReturnTypeDeclarationRule`, `Functions\NoParameterWithNullableTypeDeclarationRule`, `Methods\NoNullableReturnTypeDeclarationRule`, `Methods\NoParameterWithNullableTypeDeclarationRule` to detect cases where `null` is referenced with incorrect case or relative to the root namespace ([#897]), by [@localheinz] + +## [`2.5.1`][2.5.1] + +For a full diff see [`2.5.0...2.5.1`][2.5.0...2.5.1]. + +### Fixed + +- Returned rule with error identifier ([#882]), by [@localheinz] +- Adjusted `Methods\FinalInAbstractClassRule` to ignore Doctrine embeddables and entities ([#396]), by [@localheinz] +- Adjusted `Expressions\NoCompactRule` to detect usages of `compact()` with incorrect case ([#889]), by [@localheinz] +- Adjusted `Methods\PrivateInFinalClassRule` to use more appropriate message when detecting a `protected` method in an anonymous class ([#890]), by [@localheinz] +- Adjusted `Methods\PrivateInFinalClassRule` to ignore `protected` methods from traits ([#891]), by [@localheinz] +- Adjusted `Methods\PrivateInFinalClassRule` to ignore `protected` methods with `phpunit/phpunit` attributes requiring methods to be `protected` ([#863]), by [@cosmastech] +- Adjusted `Methods\PrivateInFinalClassRule` to ignore `protected` methods with `phpunit/phpunit` annotations requiring methods to be `protected` ([#895]), by [@cosmastech] + +## [`2.5.0`][2.5.0] + +For a full diff see [`2.4.0...2.5.0`][2.4.0...2.5.0]. + +### Added + +- Added rule error identifiers ([#875]), by [@localheinz] +- Added support for PHP 8.0 ([#877]), by [@localheinz] +- Added support for PHP 7.4 ([#880]), by [@localheinz] + +### Changed + +- Removed dependency on `nikic/php-parser` ([#878]), by [@localheinz] + +## [`2.4.0`][2.4.0] + +For a full diff see [`2.3.0...2.4.0`][2.3.0...2.4.0]. + +### Added + +- Added support for PHP 8.4 ([#872]), by [@localheinz] + +## [`2.3.0`][2.3.0] + +For a full diff see [`2.2.0...2.3.0`][2.2.0...2.3.0]. + +### Changed + +- Allowed installation on PHP 8.4 ([#862]), by [@localheinz] + +## [`2.2.0`][2.2.0] + +For a full diff see [`2.1.0...2.2.0`][2.1.0...2.2.0]. + +### Changed + +- Allowed installation of `nikic/php-parser:^5.0.0` ([#735]), by [@localheinz] + +## [`2.1.0`][2.1.0] + +For a full diff see [`2.0.0...2.1.0`][2.0.0...2.1.0]. + +### Changed + +- Dropped support for PHP 8.0 ([#567]), by [@localheinz] +- Added support for PHP 8.3 ([#604]), by [@nunomaduro] + +## [`2.0.0`][2.0.0] + +For a full diff see [`1.0.0...2.0.0`][1.0.0...2.0.0]. + +### Added + +- Added `methodsAllowedToUseContainerTypeDeclarations` parameter to allow configuring a list of method names that are allowed to have container parameter type declarations ([#541), by [@localheinz] +- Allowed disabling rules ([#542), by [@localheinz] +- Added support for nullable union types ([#543), by [@localheinz] + +### Changed + +- Dropped support for PHP 7.2 ([#496]), by [@localheinz] +- Dropped support for PHP 7.3 ([#498]), by [@localheinz] +- Dropped support for PHP 7.4 ([#499]), by [@localheinz] +- Added support for PHP 8.2 ([#540]), by [@localheinz] + +### Removed + +- Removed `Expressions\NoEmptyRule` ([#525]), by [@enumag] + +## [`1.0.0`][1.0.0] + +For a full diff see [`0.15.3...1.0.0`][0.15.3...1.0.0]. + +### Changed + +- Added support for `phpstan/phpstan:^1.0.0` and dropped support for non-stable versions of `phpstan/phpstan` ([#381]), by [@rpkamp] + +### Fixed + +- Adjusted `Classes\FinalRule` to not report an error when a non-final class has a `Doctrinbe\ORM\Mapping\Entity` attribute ([#395]), by [@localheinz] + +## [`0.15.3`][0.15.3] + +For a full diff see [`0.15.2...0.15.3`][0.15.2...0.15.3]. + +### Changed + +- Allow installation with PHP 8.0 ([#294]), by [@localheinz] + +## [`0.15.2`][0.15.2] + +For a full diff see [`0.15.1...0.15.2`][0.15.1...0.15.2]. + +### Changed + +- Dropped support for PHP 7.1 ([#259]), by [@localheinz] + +## [`0.15.1`][0.15.1] + +For a full diff see [`0.15.0...0.15.1`][0.15.0...0.15.1]. + +### Changed + +- Adjusted `Methods\FinalInAbstractClass` rule to allow non-`final` `public` constructors in abstract classes ([#248]), by [@Slamdunk] + +## [`0.15.0`][0.15.0] + +For a full diff see [`0.14.4...0.15.0`][0.14.4...0.15.0]. + +### Added + +- Added `Classes\PHPUnit\Framework\TestCaseWithSuffixRule`, which reports an error when a concrete class extending `PHPUnit\Framework\TestCase` does not have a `Test` suffix ([#225]), by [@localheinz] + +## [`0.14.4`][0.14.4] + +For a full diff see [`0.14.3...0.14.4`][0.14.3...0.14.4]. + +### Fixed + +- Ignored classes with `@ORM\Mapping\Entity` annotations in `FinalRule` ([#202]), by [@localheinz] + +## [`0.14.3`][0.14.3] + +For a full diff see [`0.14.2...0.14.3`][0.14.2...0.14.3]. + +### Fixed + +- Ignored first line in `DeclareStrictTypesRule` when it is a shebang ([#186]), by [@Great-Antique] + +## [`0.14.2`][0.14.2] + +For a full diff see [`0.14.1...0.14.2`][0.14.1...0.14.2]. + +### Fixed + +- Brought back support for PHP 7.1 ([#166]), by [@localheinz] + +## [`0.14.1`][0.14.1] + +For a full diff see [`0.14.0...0.14.1`][0.14.0...0.14.1]. + +### Fixed + +- Removed an inappropriate `replace` configuration from `composer.json` ([#161]), by [@localheinz] + +## [`0.14.0`][0.14.0] + +For a full diff see [`0.13.0...0.14.0`][0.13.0...0.14.0]. + +### Changed + +- Allowed installation of `phpstan/phpstan:~0.12.0` ([#147]), by [@localheinz] +- Renamed vendor namespace `Localheinz` to `Ergebnis` after move to [@ergebnis] ([#157]), by [@localheinz] + + Run + + ```sh + composer remove localheinz/phpstan-rules + ``` + + and + + ```sh + composer require ergebnis/phpstan-rules + ``` + + to update. + + Run + + ```sh + find . -type f -exec sed -i '.bak' 's/Localheinz\\PHPStan/Ergebnis\\PHPStan/g' {} \; + ``` + + to replace occurrences of `Localheinz\PHPStan` with `Ergebnis\PHPStan`. + + Run + + ```sh + find -type f -name '*.bak' -delete + ``` + + to delete backup files created in the previous step. + +- Moved parameters into `ergebnis` section to prevent conflicts with global parameters ([#158]), by [@localheinz] + + Where previously `phpstan.neon` looked like the following + + ```neon + parameters: + allowAbstractClasses: true + classesAllowedToBeExtended: [] + classesNotRequiredToBeAbstractOrFinal: [] + interfacesImplementedByContainers: + - Psr\Container\ContainerInterface + ``` + + these parameters now need to be moved into an `ergebnis` section: + + ```diff + parameters: + - allowAbstractClasses: true + - classesAllowedToBeExtended: [] + - classesNotRequiredToBeAbstractOrFinal: [] + - interfacesImplementedByContainers: + - - Psr\Container\ContainerInterface + + ergebnis: + + allowAbstractClasses: true + + classesAllowedToBeExtended: [] + + classesNotRequiredToBeAbstractOrFinal: [] + + interfacesImplementedByContainers: + + - Psr\Container\ContainerInterface + ``` + +### Fixed + +- Dropped support for PHP 7.1 ([#141]), by [@localheinz] + +## [`0.13.0`][0.13.0] + +For a full diff see [`0.12.2...0.13.0`][0.12.2...0.13.0]. + +### Added + +- Added `Methods\PrivateInFinalClassRule` which reports an error when a method in a `final` class is `protected` when it could be `private` ([#126]), by [@localheinz] + +## [`0.12.2`][0.12.2] + +For a full diff see [`0.12.1...0.12.2`][0.12.1...0.12.2]. + +### Fixed + +- Started ignoring interfaces from analysis by `Methods\FinalInAbstractClassRule` to avoid inappropriate errors ([#132]), by [@localheinz] + +## [`0.12.1`][0.12.1] + +For a full diff see [`0.12.0...0.12.1`][0.12.0...0.12.1]. + +### Fixed + +- Started resolving class name in type declaration before attempting to analyze it in the `Methods\NoParameterWithContainerTypeDeclarationRule` to avoid errors where class `self` is not found ([#128]), by [@localheinz] + +## [`0.12.0`][0.12.0] + +For a full diff see [`0.11.0...0.12.0`][0.11.0...0.12.0]. + +### Added + +- Added `Methods\NoParameterWithContainerTypeDeclarationRule`, which reports an error when a method has a type declaration that corresponds to a known dependency injection container or service locator ([#122]), by [@localheinz] +- Added `Methods\FinalInAbstractClassRule`, which reports an error when a concrete `public` or `protected` method in an `abstract` class is not `final` ([#123]), by [@localheinz] + +## [`0.11.0`][0.11.0] + +For a full diff see [`0.10.0...0.11.0`][0.10.0...0.11.0]. + +### Added + +- Added `Files\DeclareStrictTypesRule`, which reports an error when a PHP file does not have a `declare(strict_types=1)` declaration ([#79] +- Added `Expressions\NoEmptyRule`, which reports an error when the language construct `empty()` is used ([#110]), by [@localheinz] +- Added `Expressions\NoEvalRule`, which reports an error when the language construct `eval()` is used ([#112]), by [@localheinz] +- Added `Expressions\NoErrorSuppressionRule`, which reports an error when `@` is used to suppress errors ([#113]), by [@localheinz] +- Added `Expressions\NoCompactRule`, which reports an error when the function `compact()` is used ([#116]), by [@localheinz] +- Added `Statements\NoSwitchRule`, which reports an error when the statement `switch()` is used ([#117]), by [@localheinz] + +### Changed + +- Require at least `nikic/php-parser:^4.2.3` ([#102]), by [@localheinz] +- Require at least `phpstan/phpstan:~0.11.15` ([#103]), by [@localheinz] + +## [`0.10.0`][0.10.0] + +For a full diff see [`0.9.1...0.10.0`][0.9.1...0.10.0]. + +### Changed + +- Require at least `phpstan/phpstan:~0.11.7` ([#91]), by [@localheinz] + +### Fixed + +- Added missing `parametersSchema` configuration to `rules.neon`, which is required for use with `bleedingEdge.neon`, see [`phpstan/phpstan@54a125d`](https://github.com/phpstan/phpstan/commit/54a125df47fa097b792cb9a3e70c49f753f66b85) ([#93]), by [@localheinz] +* +## [`0.9.1`][0.9.1] + +For a full diff see [`0.9.0...0.9.1`][0.9.0...0.9.1]. + +### Changed + +- Allow usage with [`phpstan/extension-installer`](https://github.com/phpstan/extension-installer) ([#89]), by [@localheinz] + +## [`0.9.0`][0.9.0] + +For a full diff see [`0.8.1...0.9.0`][0.8.1...0.9.0]. + +### Changed + +- Adjusted `Classes\FinalRule` to ignore Doctrine entities when they are annotated ([#84]), by [@localheinz] + +## [`0.8.1`][0.8.1] + +For a full diff see [`0.8.0...0.8.1`][0.8.0...0.8.1]. + +### Fixed + +- Actually enable `Expressions\NoIssetRule` ([#83]), by [@localheinz] + +## [`0.8.0`][0.8.0] + +For a full diff see [`0.7.1...0.8.0`][0.7.1...0.8.0]. + +### Added + +- Added `Expressions\NoIssetRule`, which reports an error when the language construct `isset()` is used ([#81]), by [@localheinz] + +## [`0.7.1`][0.7.1] + +For a full diff see [`0.7.0...0.7.1`][0.7.0...0.7.1]. + +### Changed + +- Configured `Classes\NoExtendsRule` to allow a set of default classes to be extended ([#73]), by [@localheinz] + +## [`0.7.0`][0.7.0] + +For a full diff see [`0.6.0...0.7.0`][0.6.0...0.7.0]. + +### Added + +- Added `Classes\NoExtendsRule`, which reports an error when a class extends a class that is not allowed to be extended ([#68]), by [@localheinz] + +## [`0.6.0`][0.6.0] + +For a full diff see [`0.5.0...0.6.0`][0.5.0...0.6.0]. + +### Added + +- Allow installation with `phpstan/phpstan:~0.11.0` ([#65]), by [@localheinz] + +## [`0.5.0`][0.5.0] + +For a full diff see [`0.4.0...0.5.0`][0.4.0...0.5.0]. + +### Added + +- Added `Methods\NoConstructorParameterWithDefaultValueRule`, which reports an error when a constructor of an anonymous class or a class has a parameter with a default value ([#45]), by [@localheinz] +- Added parameters `$allowAbstractClasses` and `$classesNotRequiredToBeAbstractOrFinal` to allow configuration of `Classes`FinalRule` ([#51]), by [@localheinz] + +### Removed + +- Removed `Classes\AbstractOrFinalRule` after merging behaviour into `Classes\FinalRule` ([#51]), by [@localheinz] +- Removed default values from constructor of `Classes\FinalRule` ([#53]), by [@localheinz] + +## [`0.4.0`][0.4.0] + +For a full diff see [`0.3.0...0.4.0`][0.3.0...0.4.0] + +### Changed + +- Removed double-quotes from error messages to be more consistent with error messages generated by `phpstan/phstan` ([#39]), by [@localheinz] +- Modified wording and placement of method, function, and parameter names in error messages to be more consistent with error messages generated by `phpstan/phstan` ([#42]), by [@localheinz] + +## [`0.3.0`][0.3.0] + +For a full diff see [`0.2.0...0.3.0`][0.2.0...0.3.0] + +### Added + +- Added `Functions\NoNullableReturnTypeDeclarationRule`, which reports an error when a function has a nullable return type declaration, and `Methods\NoNullableReturnTypeDeclarationRule`, which reports an error when a method declared in an anonymous class, a class, or an interface has a nullable return type declaration ([#16]), by [@localheinz] +- Added `Closures\NoParameterWithNullDefaultValueRule`, which reports an error when a closure has a parameter with `null` as default value ([#26]), by [@localheinz] +- Added `Closures\NoNullableReturnTypeDeclarationRule`, which reports an error when a closure has a nullable return type declaration ([#29]), by [@localheinz] +- Added `Functions\NoParameterWithNullDefaultValueRule`, which reports an error when a function has a parameter with `null` as default value ([#31]), by [@localheinz] +- Added `Methods\NoParameterWithNullDefaultValueRule`, which reports an error when a method declared in an anonymous class, a class, or an interface has a parameter with `null` as default value ([#32]), by [@localheinz] +- Added `Closures\NoParameterWithNullableTypeDeclarationRule`, which reports an error when a closure has a parameter with a nullable type declaration ([#33]), by [@localheinz] +- Added `Functions\NoParameterWithNullableTypeDeclarationRule`, which reports an error when a function has a parameter with a nullable type declaration ([#34]), by [@localheinz] +- Added `Methods\NoParameterWithNullableTypeDeclarationRule`, which reports an error when a method declared in an anonymous class, a class, or an interface has a parameter with a nullable type declaration ([#35]), by [@localheinz] +- Extracted `rules.neon`, so you can easily enable all rules by including it in your `phpstan.neon` ([#37]), by [@localheinz] + +## [`0.2.0`][0.2.0] + +For a full diff see [`0.1.0...0.2.0`][0.1.0...0.2.0] + +### Added + +- Added `Classes\FinalRule`, which reports an error when a non-anonymous class is not `final`, ([#4]), by [@localheinz] + +### Changed + +- Added an `$excludeClassNames` argument to the constructors of `Classes\AbstractOrFinalRule` and `Classes\FinalRule` to allow whitelisting of classes, ([#11]), by [@localheinz] + +## [`0.1.0`][0.1.0] + +For a full diff see [`362c7ea...0.1.0`][362c7ea...0.1.0]. + +### Added + +- Added `Classes\AbstractOrFinalRule`, which reports an error when a non-anonymous class is neither `abstract` nor `final`, ([#1]), by [@localheinz] + +[0.1.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.1.0 +[0.2.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.2.0 +[0.3.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.3.0 +[0.4.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.4.0 +[0.5.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.5.0 +[0.6.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.6.0 +[0.7.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.7.0 +[0.7.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.7.1 +[0.8.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.8.0 +[0.8.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.8.1 +[0.9.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.9.0 +[0.9.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.9.1 +[0.10.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.10.0 +[0.11.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.11.0 +[0.12.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.12.0 +[0.12.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.12.1 +[0.12.2]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.12.2 +[0.13.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.13.0 +[0.14.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.14.0 +[0.14.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.14.1 +[0.14.2]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.14.2 +[0.14.3]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.14.3 +[0.14.4]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.14.4 +[0.15.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.15.0 +[0.15.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.15.1 +[0.15.2]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.15.2 +[0.15.3]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.15.3 +[1.0.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/1.0.0 +[2.0.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.0.0 +[2.1.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.1.0 +[2.2.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.2.0 +[2.3.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.3.0 +[2.4.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.4.0 +[2.5.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.5.0 +[2.5.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.5.1 +[2.5.2]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.5.2 +[2.6.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.6.0 +[2.6.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.6.1 +[2.7.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.7.0 +[2.8.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.8.0 +[2.9.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.9.0 +[2.10.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.10.0 +[2.10.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.10.1 +[2.10.2]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.10.2 +[2.10.3]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.10.3 +[2.10.4]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.10.4 +[2.10.5]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.10.5 +[2.11.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.11.0 +[2.12.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.12.0 + +[362c7ea...0.1.0]: https://github.com/ergebnis/phpstan-rules/compare/362c7ea...0.1.0 +[0.1.0...0.2.0]: https://github.com/ergebnis/phpstan-rules/compare/0.1.0...0.2.0 +[0.2.0...0.3.0]: https://github.com/ergebnis/phpstan-rules/compare/0.2.0...0.3.0 +[0.3.0...0.4.0]: https://github.com/ergebnis/phpstan-rules/compare/0.3.0...0.4.0 +[0.4.0...0.5.0]: https://github.com/ergebnis/phpstan-rules/compare/0.4.0...0.5.0 +[0.5.0...0.6.0]: https://github.com/ergebnis/phpstan-rules/compare/0.5.0...0.6.0 +[0.6.0...0.7.0]: https://github.com/ergebnis/phpstan-rules/compare/0.6.0...0.7.0 +[0.7.0...0.7.1]: https://github.com/ergebnis/phpstan-rules/compare/0.7.0...0.7.1 +[0.7.1...0.8.0]: https://github.com/ergebnis/phpstan-rules/compare/0.7.1...0.8.0 +[0.8.0...0.8.1]: https://github.com/ergebnis/phpstan-rules/compare/0.8.0...0.8.1 +[0.8.1...0.9.0]: https://github.com/ergebnis/phpstan-rules/compare/0.8.1...0.9.0 +[0.9.0...0.9.1]: https://github.com/ergebnis/phpstan-rules/compare/0.9.0...0.9.1 +[0.9.1...0.10.0]: https://github.com/ergebnis/phpstan-rules/compare/0.9.1...0.10.0 +[0.10.0...0.11.0]: https://github.com/ergebnis/phpstan-rules/compare/0.10.0...0.11.0 +[0.11.0...0.12.0]: https://github.com/ergebnis/phpstan-rules/compare/0.11.0...0.12.0 +[0.12.0...0.12.1]: https://github.com/ergebnis/phpstan-rules/compare/0.12.0...0.12.1 +[0.12.1...0.12.2]: https://github.com/ergebnis/phpstan-rules/compare/0.12.1...0.12.2 +[0.12.2...0.13.0]: https://github.com/ergebnis/phpstan-rules/compare/0.12.2...0.13.0 +[0.13.0...0.14.0]: https://github.com/ergebnis/phpstan-rules/compare/0.13.0...0.14.0 +[0.14.0...0.14.1]: https://github.com/ergebnis/phpstan-rules/compare/0.14.0...0.14.1 +[0.14.1...0.14.2]: https://github.com/ergebnis/phpstan-rules/compare/0.14.1...0.14.2 +[0.14.2...0.14.3]: https://github.com/ergebnis/phpstan-rules/compare/0.14.2...0.14.3 +[0.14.3...0.14.4]: https://github.com/ergebnis/phpstan-rules/compare/0.14.3...0.14.4 +[0.14.4...0.15.0]: https://github.com/ergebnis/phpstan-rules/compare/0.14.4...0.15.0 +[0.15.0...0.15.1]: https://github.com/ergebnis/phpstan-rules/compare/0.15.0...0.15.1 +[0.15.1...0.15.2]: https://github.com/ergebnis/phpstan-rules/compare/0.15.1...0.15.2 +[0.15.2...0.15.3]: https://github.com/ergebnis/phpstan-rules/compare/0.15.2...0.15.3 +[0.15.3...1.0.0]: https://github.com/ergebnis/phpstan-rules/compare/0.15.3...1.0.0 +[1.0.0...2.0.0]: https://github.com/ergebnis/phpstan-rules/compare/1.0.0...2.0.0 +[2.0.0...2.1.0]: https://github.com/ergebnis/phpstan-rules/compare/2.0.0...2.1.0 +[2.1.0...2.2.0]: https://github.com/ergebnis/phpstan-rules/compare/2.1.0...2.2.0 +[2.2.0...2.3.0]: https://github.com/ergebnis/phpstan-rules/compare/2.2.0...2.3.0 +[2.3.0...2.4.0]: https://github.com/ergebnis/phpstan-rules/compare/2.3.0...2.4.0 +[2.4.0...2.5.0]: https://github.com/ergebnis/phpstan-rules/compare/2.4.0...2.5.0 +[2.5.0...2.5.1]: https://github.com/ergebnis/phpstan-rules/compare/2.5.0...2.5.1 +[2.5.1...2.5.2]: https://github.com/ergebnis/phpstan-rules/compare/2.5.1...2.5.2 +[2.5.2...2.6.0]: https://github.com/ergebnis/phpstan-rules/compare/2.5.2...2.6.0 +[2.6.0...2.6.1]: https://github.com/ergebnis/phpstan-rules/compare/2.6.0...2.6.1 +[2.6.1...2.7.0]: https://github.com/ergebnis/phpstan-rules/compare/2.6.1...2.7.0 +[2.7.0...2.8.0]: https://github.com/ergebnis/phpstan-rules/compare/2.7.0...2.8.0 +[2.8.0...2.9.0]: https://github.com/ergebnis/phpstan-rules/compare/2.8.0...2.9.0 +[2.9.0...2.10.0]: https://github.com/ergebnis/phpstan-rules/compare/2.9.0...2.10.0 +[2.10.0...2.10.1]: https://github.com/ergebnis/phpstan-rules/compare/2.10.0...2.10.1 +[2.10.1...2.10.2]: https://github.com/ergebnis/phpstan-rules/compare/2.10.1...2.10.2 +[2.10.2...2.10.3]: https://github.com/ergebnis/phpstan-rules/compare/2.10.2...2.10.3 +[2.10.3...2.10.4]: https://github.com/ergebnis/phpstan-rules/compare/2.10.3...2.10.4 +[2.10.4...2.10.5]: https://github.com/ergebnis/phpstan-rules/compare/2.10.4...2.10.5 +[2.10.5...2.11.0]: https://github.com/ergebnis/phpstan-rules/compare/2.10.5...2.11.0 +[2.11.0...2.12.0]: https://github.com/ergebnis/phpstan-rules/compare/2.11.0...2.12.0 +[2.12.0...main]: https://github.com/ergebnis/phpstan-rules/compare/2.12.0...main + +[#1]: https://github.com/ergebnis/phpstan-rules/pull/1 +[#4]: https://github.com/ergebnis/phpstan-rules/pull/4 +[#11]: https://github.com/ergebnis/phpstan-rules/pull/11 +[#16]: https://github.com/ergebnis/phpstan-rules/pull/16 +[#26]: https://github.com/ergebnis/phpstan-rules/pull/26 +[#29]: https://github.com/ergebnis/phpstan-rules/pull/29 +[#31]: https://github.com/ergebnis/phpstan-rules/pull/31 +[#32]: https://github.com/ergebnis/phpstan-rules/pull/32 +[#33]: https://github.com/ergebnis/phpstan-rules/pull/33 +[#34]: https://github.com/ergebnis/phpstan-rules/pull/34 +[#35]: https://github.com/ergebnis/phpstan-rules/pull/35 +[#37]: https://github.com/ergebnis/phpstan-rules/pull/37 +[#39]: https://github.com/ergebnis/phpstan-rules/pull/39 +[#42]: https://github.com/ergebnis/phpstan-rules/pull/42 +[#45]: https://github.com/ergebnis/phpstan-rules/pull/45 +[#51]: https://github.com/ergebnis/phpstan-rules/pull/51 +[#53]: https://github.com/ergebnis/phpstan-rules/pull/53 +[#65]: https://github.com/ergebnis/phpstan-rules/pull/65 +[#68]: https://github.com/ergebnis/phpstan-rules/pull/68 +[#73]: https://github.com/ergebnis/phpstan-rules/pull/73 +[#79]: https://github.com/ergebnis/phpstan-rules/pull/79 +[#81]: https://github.com/ergebnis/phpstan-rules/pull/81 +[#83]: https://github.com/ergebnis/phpstan-rules/pull/83 +[#84]: https://github.com/ergebnis/phpstan-rules/pull/84 +[#89]: https://github.com/ergebnis/phpstan-rules/pull/89 +[#91]: https://github.com/ergebnis/phpstan-rules/pull/91 +[#93]: https://github.com/ergebnis/phpstan-rules/pull/93 +[#102]: https://github.com/ergebnis/phpstan-rules/pull/102 +[#103]: https://github.com/ergebnis/phpstan-rules/pull/103 +[#110]: https://github.com/ergebnis/phpstan-rules/pull/110 +[#112]: https://github.com/ergebnis/phpstan-rules/pull/112 +[#113]: https://github.com/ergebnis/phpstan-rules/pull/113 +[#116]: https://github.com/ergebnis/phpstan-rules/pull/116 +[#117]: https://github.com/ergebnis/phpstan-rules/pull/117 +[#122]: https://github.com/ergebnis/phpstan-rules/pull/122 +[#123]: https://github.com/ergebnis/phpstan-rules/pull/123 +[#126]: https://github.com/ergebnis/phpstan-rules/pull/126 +[#128]: https://github.com/ergebnis/phpstan-rules/pull/128 +[#132]: https://github.com/ergebnis/phpstan-rules/pull/132 +[#141]: https://github.com/ergebnis/phpstan-rules/pull/141 +[#147]: https://github.com/ergebnis/phpstan-rules/pull/147 +[#157]: https://github.com/ergebnis/phpstan-rules/pull/157 +[#158]: https://github.com/ergebnis/phpstan-rules/pull/158 +[#161]: https://github.com/ergebnis/phpstan-rules/pull/161 +[#166]: https://github.com/ergebnis/phpstan-rules/pull/166 +[#186]: https://github.com/ergebnis/phpstan-rules/pull/186 +[#202]: https://github.com/ergebnis/phpstan-rules/pull/202 +[#225]: https://github.com/ergebnis/phpstan-rules/pull/225 +[#248]: https://github.com/ergebnis/phpstan-rules/pull/248 +[#259]: https://github.com/ergebnis/phpstan-rules/pull/259 +[#294]: https://github.com/ergebnis/phpstan-rules/pull/294 +[#381]: https://github.com/ergebnis/phpstan-rules/pull/381 +[#395]: https://github.com/ergebnis/phpstan-rules/pull/395 +[#396]: https://github.com/ergebnis/phpstan-rules/pull/396 +[#496]: https://github.com/ergebnis/phpstan-rules/pull/496 +[#498]: https://github.com/ergebnis/phpstan-rules/pull/498 +[#499]: https://github.com/ergebnis/phpstan-rules/pull/498 +[#525]: https://github.com/ergebnis/phpstan-rules/pull/525 +[#540]: https://github.com/ergebnis/phpstan-rules/pull/540 +[#541]: https://github.com/ergebnis/phpstan-rules/pull/541 +[#542]: https://github.com/ergebnis/phpstan-rules/pull/542 +[#543]: https://github.com/ergebnis/phpstan-rules/pull/543 +[#567]: https://github.com/ergebnis/phpstan-rules/pull/567 +[#735]: https://github.com/ergebnis/phpstan-rules/pull/735 +[#862]: https://github.com/ergebnis/phpstan-rules/pull/862 +[#863]: https://github.com/ergebnis/phpstan-rules/pull/863 +[#872]: https://github.com/ergebnis/phpstan-rules/pull/872 +[#873]: https://github.com/ergebnis/phpstan-rules/pull/873 +[#875]: https://github.com/ergebnis/phpstan-rules/pull/875 +[#877]: https://github.com/ergebnis/phpstan-rules/pull/877 +[#878]: https://github.com/ergebnis/phpstan-rules/pull/878 +[#880]: https://github.com/ergebnis/phpstan-rules/pull/880 +[#882]: https://github.com/ergebnis/phpstan-rules/pull/882 +[#889]: https://github.com/ergebnis/phpstan-rules/pull/889 +[#890]: https://github.com/ergebnis/phpstan-rules/pull/890 +[#891]: https://github.com/ergebnis/phpstan-rules/pull/891 +[#895]: https://github.com/ergebnis/phpstan-rules/pull/895 +[#897]: https://github.com/ergebnis/phpstan-rules/pull/897 +[#902]: https://github.com/ergebnis/phpstan-rules/pull/902 +[#911]: https://github.com/ergebnis/phpstan-rules/pull/911 +[#912]: https://github.com/ergebnis/phpstan-rules/pull/912 +[#913]: https://github.com/ergebnis/phpstan-rules/pull/913 +[#914]: https://github.com/ergebnis/phpstan-rules/pull/914 +[#938]: https://github.com/ergebnis/phpstan-rules/pull/938 +[#939]: https://github.com/ergebnis/phpstan-rules/pull/939 +[#940]: https://github.com/ergebnis/phpstan-rules/pull/940 +[#943]: https://github.com/ergebnis/phpstan-rules/pull/943 +[#944]: https://github.com/ergebnis/phpstan-rules/pull/944 +[#947]: https://github.com/ergebnis/phpstan-rules/pull/947 +[#948]: https://github.com/ergebnis/phpstan-rules/pull/948 +[#949]: https://github.com/ergebnis/phpstan-rules/pull/949 +[#951]: https://github.com/ergebnis/phpstan-rules/pull/951 +[#957]: https://github.com/ergebnis/phpstan-rules/pull/957 +[#958]: https://github.com/ergebnis/phpstan-rules/pull/958 +[#972]: https://github.com/ergebnis/phpstan-rules/pull/972 +[#977]: https://github.com/ergebnis/phpstan-rules/pull/977 + +[@cosmastech]: https://github.com/cosmastech +[@enumag]: https://github.com/enumag +[@ergebnis]: https://github.com/ergebnis +[@Great-Antique]: https://github.com/Great-Antique +[@localheinz]: https://github.com/localheinz +[@manuelkiessling]: https://github.com/manuelkiessling +[@nunomaduro]: https://github.com/nunomaduro +[@rpkamp]: https://github.com/rpkamp +[@Slamdunk]: https://github.com/Slamdunk diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/LICENSE.md b/tools/.phpstan/vendor/ergebnis/phpstan-rules/LICENSE.md new file mode 100644 index 00000000000..130e719a290 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/LICENSE.md @@ -0,0 +1,16 @@ +# The MIT License (MIT) + +Copyright (c) 2018-2025 Andreas Möller + +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. diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/README.md b/tools/.phpstan/vendor/ergebnis/phpstan-rules/README.md new file mode 100644 index 00000000000..690d4ad0419 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/README.md @@ -0,0 +1,760 @@ +# phpstan-rules + +[![Integrate](https://github.com/ergebnis/phpstan-rules/workflows/Integrate/badge.svg)](https://github.com/ergebnis/phpstan-rules/actions) +[![Merge](https://github.com/ergebnis/phpstan-rules/workflows/Merge/badge.svg)](https://github.com/ergebnis/phpstan-rules/actions) +[![Release](https://github.com/ergebnis/phpstan-rules/workflows/Release/badge.svg)](https://github.com/ergebnis/phpstan-rules/actions) +[![Renew](https://github.com/ergebnis/phpstan-rules/workflows/Renew/badge.svg)](https://github.com/ergebnis/phpstan-rules/actions) + +[![Code Coverage](https://codecov.io/gh/ergebnis/phpstan-rules/branch/main/graph/badge.svg)](https://codecov.io/gh/ergebnis/phpstan-rules) + +[![Latest Stable Version](https://poser.pugx.org/ergebnis/phpstan-rules/v/stable)](https://packagist.org/packages/ergebnis/phpstan-rules) +[![Total Downloads](https://poser.pugx.org/ergebnis/phpstan-rules/downloads)](https://packagist.org/packages/ergebnis/phpstan-rules) +[![Monthly Downloads](http://poser.pugx.org/ergebnis/phpstan-rules/d/monthly)](https://packagist.org/packages/ergebnis/phpstan-rules) + +This project provides a [`composer`](https://getcomposer.org) package with rules for [`phpstan/phpstan`](https://github.com/phpstan/phpstan). + +## Installation + +Run + +```sh +composer require --dev ergebnis/phpstan-rules +``` + +## Usage + +All of the [rules](https://github.com/ergebnis/phpstan-rules#rules) provided (and used) by this library are included in [`rules.neon`](rules.neon). + +When you are using [`phpstan/extension-installer`](https://github.com/phpstan/extension-installer), `rules.neon` will be automatically included. + +Otherwise you need to include `rules.neon` in your `phpstan.neon`: + +```neon +includes: + - vendor/ergebnis/phpstan-rules/rules.neon +``` + +:bulb: You probably want to use these rules on top of the rules provided by: + +- [`phpstan/phpstan`](https://github.com/phpstan/phpstan) +- [`phpstan/phpstan-deprecation-rules`](https://github.com/phpstan/phpstan-deprecation-rules) +- [`phpstan/phpstan-strict-rules`](https://github.com/phpstan/phpstan-strict-rules) + +## Rules + +This package provides the following rules for use with [`phpstan/phpstan`](https://github.com/phpstan/phpstan): + +- [`Ergebnis\PHPStan\Rules\CallLikes\NoNamedArgumentRule`](https://github.com/ergebnis/phpstan-rules#calllikesnonamedargumentrule) +- [`Ergebnis\PHPStan\Rules\Classes\FinalRule`](https://github.com/ergebnis/phpstan-rules#classesfinalrule) +- [`Ergebnis\PHPStan\Rules\Classes\NoExtendsRule`](https://github.com/ergebnis/phpstan-rules#classesnoextendsrule) +- [`Ergebnis\PHPStan\Rules\Classes\PHPUnit\Framework\TestCaseWithSuffixRule`](https://github.com/ergebnis/phpstan-rules#classesphpunitframeworktestcasewithsuffixrule) +- [`Ergebnis\PHPStan\Rules\Closures\NoNullableReturnTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#closuresnonullablereturntypedeclarationrule) +- [`Ergebnis\PHPStan\Rules\Closures\NoParameterPassedByReferenceRule`](https://github.com/ergebnis/phpstan-rules#closuresnoparameterpassedbyreferencerule) +- [`Ergebnis\PHPStan\Rules\Closures\NoParameterWithNullableTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#closuresnoparameterwithnullabletypedeclarationrule) +- [`Ergebnis\PHPStan\Rules\Closures\NoParameterWithNullDefaultValueRule`](https://github.com/ergebnis/phpstan-rules#closuresnoparameterwithnulldefaultvaluerule) +- [`Ergebnis\PHPStan\Rules\Expressions\NoAssignByReferenceRule`](https://github.com/ergebnis/phpstan-rules#expressionsnoassignbyreferencerule) +- [`Ergebnis\PHPStan\Rules\Expressions\NoCompactRule`](https://github.com/ergebnis/phpstan-rules#expressionsnocompactrule) +- [`Ergebnis\PHPStan\Rules\Expressions\NoErrorSuppressionRule`](https://github.com/ergebnis/phpstan-rules#expressionsnoerrorsuppressionrule) +- [`Ergebnis\PHPStan\Rules\Expressions\NoEvalRule`](https://github.com/ergebnis/phpstan-rules#expressionsnoevalrule) +- [`Ergebnis\PHPStan\Rules\Expressions\NoIssetRule`](https://github.com/ergebnis/phpstan-rules#expressionsnoissetrule) +- [`Ergebnis\PHPStan\Rules\Files\DeclareStrictTypesRule`](https://github.com/ergebnis/phpstan-rules#filesdeclarestricttypesrule) +- [`Ergebnis\PHPStan\Rules\Functions\NoNullableReturnTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#functionsnonullablereturntypedeclarationrule) +- [`Ergebnis\PHPStan\Rules\Functions\NoParameterPassedByReferenceRule`](https://github.com/ergebnis/phpstan-rules#functionsnoparameterpassedbyreferencerule) +- [`Ergebnis\PHPStan\Rules\Functions\NoParameterWithNullableTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#functionsnoparameterwithnullabletypedeclarationrule) +- [`Ergebnis\PHPStan\Rules\Functions\NoParameterWithNullDefaultValueRule`](https://github.com/ergebnis/phpstan-rules#functionsnoparameterwithnulldefaultvaluerule) +- [`Ergebnis\PHPStan\Rules\Functions\NoReturnByReferenceRule`](https://github.com/ergebnis/phpstan-rules#functionsnoreturnbyreferencerule) +- [`Ergebnis\PHPStan\Rules\Methods\FinalInAbstractClassRule`](https://github.com/ergebnis/phpstan-rules#methodsfinalinabstractclassrule) +- [`Ergebnis\PHPStan\Rules\Methods\InvokeParentHookMethodRule`](https://github.com/ergebnis/phpstan-rules#methodsinvokeparenthookmethodrule) +- [`Ergebnis\PHPStan\Rules\Methods\NoConstructorParameterWithDefaultValueRule`](https://github.com/ergebnis/phpstan-rules#methodsnoconstructorparameterwithdefaultvaluerule) +- [`Ergebnis\PHPStan\Rules\Methods\NoNullableReturnTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#methodsnonullablereturntypedeclarationrule) +- [`Ergebnis\PHPStan\Rules\Methods\NoParameterPassedByReferenceRule`](https://github.com/ergebnis/phpstan-rules#methodsnoparameterpassedbyreferencerule) +- [`Ergebnis\PHPStan\Rules\Methods\NoParameterWithContainerTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#methodsnoparameterwithcontainertypedeclarationrule) +- [`Ergebnis\PHPStan\Rules\Methods\NoParameterWithNullableTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#methodsnoparameterwithnullabletypedeclarationrule) +- [`Ergebnis\PHPStan\Rules\Methods\NoParameterWithNullDefaultValueRule`](https://github.com/ergebnis/phpstan-rules#methodsnoparameterwithnulldefaultvaluerule) +- [`Ergebnis\PHPStan\Rules\Methods\NoReturnByReferenceRule`](https://github.com/ergebnis/phpstan-rules#methodsnoreturnbyreferencerule) +- [`Ergebnis\PHPStan\Rules\Methods\PrivateInFinalClassRule`](https://github.com/ergebnis/phpstan-rules#methodsprivateinfinalclassrule) +- [`Ergebnis\PHPStan\Rules\Statements\NoSwitchRule`](https://github.com/ergebnis/phpstan-rules#statementsnoswitchrule) + + +### CallLikes + +#### `CallLikes\NoNamedArgumentRule` + +This rule reports an error when an anonymous function, a function, or a method is invoked using a [named argument](https://www.php.net/manual/en/functions.arguments.php#functions.named-arguments). + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noNamedArgument: + enabled: false +``` + +### Classes + +#### `Classes\FinalRule` + +This rule reports an error when a non-anonymous class is not `final`. + +:bulb: This rule ignores classes that + +- use `@Entity`, `@ORM\Entity`, or `@ORM\Mapping\Entity` annotations +- use `Doctrine\ORM\Mapping\Entity` attributes + +on the class level. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + final: + enabled: false +``` + +##### Disallowing `abstract` classes + +By default, this rule allows to declare `abstract` classes. + +You can set the `allowAbstractClasses` parameter to `false` to disallow abstract classes. + +```neon +parameters: + ergebnis: + final: + allowAbstractClasses: false +``` + +##### Excluding classes from inspection + +You can set the `classesNotRequiredToBeAbstractOrFinal` parameter to a list of class names that you want to exclude from inspection. + +```neon +parameters: + ergebnis: + final: + classesNotRequiredToBeAbstractOrFinal: + - Foo\Bar\NeitherAbstractNorFinal + - Bar\Baz\NeitherAbstractNorFinal +``` + +#### `Classes\NoExtendsRule` + +This rule reports an error when a class extends another class. + +##### Defaults + +By default, this rule allows the following classes to be extended: + +- [`PHPUnit\Framework\TestCase`](https://github.com/sebastianbergmann/phpunit/blob/6.0.0/src/Framework/TestCase.php) + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noExtends: + enabled: false +``` + +##### Allowing classes to be extended + +You can set the `classesAllowedToBeExtended` parameter to a list of class names that you want to allow to be extended. + +```neon +parameters: + ergebnis: + noExtends: + classesAllowedToBeExtended: + - Ergebnis\PHPStan\Rules\Test\Integration\AbstractTestCase + - Ergebnis\PHPStan\Rules\Test\Integration\AbstractTestCase +``` + +#### `Classes\PHPUnit\Framework\TestCaseWithSuffixRule` + +This rule reports an error when a concrete class is a sub-class of `PHPUnit\Framework\TestCase` but does not have a `Test` suffix. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + testCaseWithSuffix: + enabled: false +``` + +### Closures + +#### `Closures\NoNullableReturnTypeDeclarationRule` + +This rule reports an error when a closure uses a nullable return type declaration. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noNullableReturnTypeDeclaration: + enabled: false +``` + +#### `Closures\NoParameterPassedByReferenceRule` + +This rule reports an error when a closure has a parameter that is [passed by reference](https://www.php.net/manual/en/language.references.pass.php). + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noParameterPassedByReference: + enabled: false +``` + +#### `Closures\NoParameterWithNullableTypeDeclarationRule` + +This rule reports an error when a closure has a parameter with a nullable type declaration. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noParameterWithNullableTypeDeclaration: + enabled: false +``` + +#### `Closures\NoParameterWithNullDefaultValueRule` + +This rule reports an error when a closure has a parameter with `null` as default value. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noParameterWithNullDefaultValue: + enabled: false +``` + +### Expressions + +#### `Expressions\NoAssignByReferenceRule` + +This rule reports an error when [a variable is assigned by reference](https://www.php.net/manual/en/language.references.whatdo.php#language.references.whatdo.assign). + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noAssignByReference: + enabled: false +``` + +#### `Expressions\NoCompactRule` + +This rule reports an error when the function [`compact()`](https://www.php.net/compact) is used. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noCompact: + enabled: false +``` + +#### `Expressions\NoErrorSuppressionRule` + +This rule reports an error when [`@`](https://www.php.net/manual/en/language.operators.errorcontrol.php) is used to suppress errors. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noErrorSuppression: + enabled: false +``` + +#### `Expressions\NoEvalRule` + +This rule reports an error when the language construct [`eval()`](https://www.php.net/eval) is used. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noEval: + enabled: false +``` + +#### `Expressions\NoIssetRule` + +This rule reports an error when the language construct [`isset()`](https://www.php.net/isset) is used. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noIsset: + enabled: false +``` + +### Files + +#### `Files\DeclareStrictTypesRule` + +This rule reports an error when a non-empty file does not contain a `declare(strict_types=1)` declaration. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + declareStrictTypes: + enabled: false +``` + +### Functions + +#### `Functions\NoNullableReturnTypeDeclarationRule` + +This rule reports an error when a function uses a nullable return type declaration. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noNullableReturnTypeDeclaration: + enabled: false +``` + +#### `Functions\NoParameterPassedByReferenceRule` + +This rule reports an error when a function has a parameter that is [passed by reference](https://www.php.net/manual/en/language.references.pass.php). + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noParameterPassedByReference: + enabled: false +``` + +#### `Functions\NoParameterWithNullableTypeDeclarationRule` + +This rule reports an error when a function has a parameter with a nullable type declaration. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noParameterWithNullableTypeDeclaration: + enabled: false +``` + +#### `Functions\NoParameterWithNullDefaultValueRule` + +This rule reports an error when a function has a parameter with `null` as default value. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noParameterWithNullDefaultValue: + enabled: false +``` + +#### `Functions\NoReturnByReferenceRule` + +This rule reports an error when a function [returns by reference](https://www.php.net/manual/en/language.references.return.php). + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noReturnByReference: + enabled: false +``` + +### Methods + +#### `Methods\FinalInAbstractClassRule` + +This rule reports an error when a concrete `public` or `protected` method in an `abstract` class is not `final`. + +:bulb: This rule ignores + +- Doctrine embeddables +- Doctrine entities + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + finalInAbstractClass: + enabled: false +``` + +#### `Methods\InvokeParentHookMethodRule` + +This rule reports an error when a hook method that overrides a hook method in a parent class does not invoke the overridden hook method in the expected order. + +##### Defaults + +By default, this rule requires the following hook methods to be invoked before doing something in the overriding method: + +- [`Codeception\PHPUnit\TestCase::_setUp()`](https://github.com/Codeception/phpunit-wrapper/blob/9.0.0/src/TestCase.php#L11-L13) +- [`Codeception\PHPUnit\TestCase::_setUpBeforeClass()`](https://github.com/Codeception/phpunit-wrapper/blob/9.0.0/src/TestCase.php#L25-L27) +- [`Codeception\Test\Unit::_before()`](https://github.com/Codeception/Codeception/blob/4.2.2/src/Codeception/Test/Unit.php#L63-L65) +- [`Codeception\Test\Unit::_setUp()`](https://github.com/Codeception/Codeception/blob/4.2.2/src/Codeception/Test/Unit.php#L34-L58) +- [`PHPUnit\Framework\TestCase::assertPreConditions()`](https://github.com/sebastianbergmann/phpunit/blob/6.0.0/src/Framework/TestCase.php#L2073-L2075) +- [`PHPUnit\Framework\TestCase::setUp()`](https://github.com/sebastianbergmann/phpunit/blob/6.0.0/src/Framework/TestCase.php#L2063-L2065) +- [`PHPUnit\Framework\TestCase::setUpBeforeClass()`](https://github.com/sebastianbergmann/phpunit/blob/6.0.0/src/Framework/TestCase.php#L2055-L2057) + +By default, this rule requires the following hook methods to be invoked after doing something in the overriding method: + +- [`Codeception\PHPUnit\TestCase::_tearDown()`](https://github.com/Codeception/phpunit-wrapper/blob/9.0.0/src/TestCase.php#L18-L20) +- [`Codeception\PHPUnit\TestCase::_tearDownAfterClass()`](https://github.com/Codeception/phpunit-wrapper/blob/9.0.0/src/TestCase.php#L32-L34) +- [`Codeception\Test\Unit::_after()`](https://github.com/Codeception/Codeception/blob/4.2.2/src/Codeception/Test/Unit.php#L75-L77) +- [`Codeception\Test\Unit::_tearDown()`](https://github.com/Codeception/Codeception/blob/4.2.2/src/Codeception/Test/Unit.php#L67-L70) +- [`PHPUnit\Framework\TestCase::assertPostConditions()`](https://github.com/sebastianbergmann/phpunit/blob/6.0.0/src/Framework/TestCase.php#L2083-L2085) +- [`PHPUnit\Framework\TestCase::tearDown()`](https://github.com/sebastianbergmann/phpunit/blob/6.0.0/src/Framework/TestCase.php#L2091-L2093) +- [`PHPUnit\Framework\TestCase::tearDownAfterClass()`](https://github.com/sebastianbergmann/phpunit/blob/6.0.0/src/Framework/TestCase.php#L2098-L2100) + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + invokeParentHookMethod: + enabled: false +``` + +##### Configuring methods to invoke the parent method in the right order: + +You can set the `hookMethods` parameter to a list of hook methods: + +```neon +parameters: + ergebnis: + invokeParentHookMethod: + hookMethods: + - className: "Example\Test\Functional\AbstractCest" + methodName: "_before" + hasContent: "yes" + invocation: "first" +``` + +- `className`: name of the class that declares the hook method +- `methodName`: name of the hook method +- `hasContent`: one of `"yes"`, `"no"`, `"maybe"` +- `invocation`: one of `"any"` (needs to be invoked), `"first"` (needs to be invoked before all other statements in the overriding hook method, `"last"` (needs to be invoked after all other statements in the overriding hook method) + +#### `Methods\NoConstructorParameterWithDefaultValueRule` + +This rule reports an error when a constructor declared in + +- an anonymous class +- a class + +has a default value. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noConstructorParameterWithDefaultValue: + enabled: false +``` + +#### `Methods\NoParameterPassedByReferenceRule` + +This rule reports an error when a method has a parameter that is [passed by reference](https://www.php.net/manual/en/language.references.pass.php). + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noParameterPassedByReference: + enabled: false +``` + +#### `Methods\NoNullableReturnTypeDeclarationRule` + +This rule reports an error when a method declared in + +- an anonymous class +- a class +- an interface + +uses a nullable return type declaration. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noNullableReturnTypeDeclaration: + enabled: false +``` + +#### `Methods\NoParameterWithContainerTypeDeclarationRule` + +This rule reports an error when a method has a type declaration for a known dependency injection container or service locator. + +##### Defaults + +By default, this rule disallows the use of type declarations indicating an implementation of + +- [`Psr\Container\ContainerInterface`](https://github.com/php-fig/container/blob/1.0.0/src/ContainerInterface.php) + +is expected to be injected into a method. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noParameterWithContainerTypeDeclaration: + enabled: false +``` + +##### Configuring container interfaces + +You can set the `interfacesImplementedByContainers` parameter to a list of interface names of additional containers and service locators. + +```neon +parameters: + ergebnis: + noParameterWithContainerTypeDeclaration: + interfacesImplementedByContainers: + - Fancy\DependencyInjection\ContainerInterface + - Other\ServiceLocatorInterface +``` + +##### Configuring methods allowed to use parameters with container type declarations + +You can set the `methodsAllowedToUseContainerTypeDeclarations` parameter to a list of method names that are allowed to use parameters with container type declarations. + +```neon +parameters: + ergebnis: + noParameterWithContainerTypeDeclaration: + methodsAllowedToUseContainerTypeDeclarations: + - loadExtension +``` + +#### `Methods\NoParameterWithNullableTypeDeclarationRule` + +This rule reports an error when a method declared in + +- an anonymous class +- a class +- an interface + +has a parameter with a nullable type declaration. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noParameterWithNullableTypeDeclaration: + enabled: false +``` + +#### `Methods\NoParameterWithNullDefaultValueRule` + +This rule reports an error when a method declared in + +- an anonymous class +- a class +- an interface + +has a parameter with `null` as default value. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noParameterWithNullDefaultValue: + enabled: false +``` + +#### `Functions\NoReturnByReferenceRule` + +This rule reports an error when a method [returns by reference](https://www.php.net/manual/en/language.references.return.php). + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noReturnByReference: + enabled: false +``` + +#### `Methods\PrivateInFinalClassRule` + +This rule reports an error when a method in a `final` class is `protected` but could be `private`. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + privateInFinalClass: + enabled: false +``` + +### Statements + +#### `Statements\NoSwitchRule` + +This rule reports an error when the statement [`switch()`](https://www.php.net/manual/control-structures.switch.php) is used. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noSwitch: + enabled: false +``` + +## Disabling all rules + +You can disable all rules using the `allRules` configuration parameter: + +```neon +parameters: + ergebnis: + allRules: false +``` + +## Enabling rules one-by-one + +If you have disabled all rules using the `allRules` configuration parameter, you can re-enable individual rules with their corresponding configuration parameters: + +```neon +parameters: + ergebnis: + allRules: false + privateInFinalClass: + enabled: true +``` + +## Changelog + +The maintainers of this project record notable changes to this project in a [changelog](CHANGELOG.md). + +## Contributing + +The maintainers of this project suggest following the [contribution guide](.github/CONTRIBUTING.md). + +## Code of Conduct + +The maintainers of this project ask contributors to follow the [code of conduct](https://github.com/ergebnis/.github/blob/main/CODE_OF_CONDUCT.md). + +## General Support Policy + +The maintainers of this project provide limited support. + +You can support the maintenance of this project by [sponsoring @ergebnis](https://github.com/sponsors/ergebnis). + +## PHP Version Support Policy + +This project supports PHP versions with [active and security support](https://www.php.net/supported-versions.php). + +The maintainers of this project add support for a PHP version following its initial release and drop support for a PHP version when it has reached the end of security support. + +## Security Policy + +This project has a [security policy](.github/SECURITY.md). + +## License + +This project uses the [MIT license](LICENSE.md). + +## Credits + +The method [`FinalRule::isWhitelistedClass()`](src/Classes/FinalRule.php) is inspired by the work on [`FinalClassFixer`](https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/2.15/src/Fixer/ClassNotation/FinalClassFixer.php) and [`FinalInternalClassFixer`](https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/2.15/src/Fixer/ClassNotation/FinalInternalClassFixer.php), contributed by [Dariusz Rumiński](https://github.com/keradus), [Filippo Tessarotto](https://github.com/Slamdunk), and [Spacepossum](https://github.com/SpacePossum) for [`friendsofphp/php-cs-fixer`](https://github.com/FriendsOfPHP/PHP-CS-Fixer) (originally licensed under MIT). + +## Social + +Follow [@localheinz](https://twitter.com/intent/follow?screen_name=localheinz) and [@ergebnis](https://twitter.com/intent/follow?screen_name=ergebnis) on Twitter. diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/composer.json b/tools/.phpstan/vendor/ergebnis/phpstan-rules/composer.json new file mode 100644 index 00000000000..5060dde2d30 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/composer.json @@ -0,0 +1,77 @@ +{ + "name": "ergebnis/phpstan-rules", + "description": "Provides rules for phpstan/phpstan.", + "license": "MIT", + "type": "phpstan-extension", + "keywords": [ + "phpstan", + "phpstan-rules" + ], + "authors": [ + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com/" + } + ], + "homepage": "https://github.com/ergebnis/phpstan-rules", + "support": { + "issues": "https://github.com/ergebnis/phpstan-rules/issues", + "source": "https://github.com/ergebnis/phpstan-rules", + "security": "https://github.com/ergebnis/phpstan-rules/blob/main/.github/SECURITY.md" + }, + "require": { + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "ext-mbstring": "*", + "phpstan/phpstan": "^2.1.8" + }, + "require-dev": { + "codeception/codeception": "^4.0.0 || ^5.0.0", + "doctrine/orm": "^2.20.0 || ^3.3.0", + "ergebnis/composer-normalize": "^2.47.0", + "ergebnis/license": "^2.6.0", + "ergebnis/php-cs-fixer-config": "^6.54.0", + "ergebnis/phpunit-slow-test-detector": "^2.20.0", + "fakerphp/faker": "^1.24.1", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpstan/phpstan-phpunit": "^2.0.7", + "phpstan/phpstan-strict-rules": "^2.0.6", + "phpunit/phpunit": "^9.6.21", + "psr/container": "^2.0.2", + "symfony/finder": "^5.4.45", + "symfony/process": "^5.4.47" + }, + "autoload": { + "psr-4": { + "Ergebnis\\PHPStan\\Rules\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Ergebnis\\PHPStan\\Rules\\Test\\": "test/" + } + }, + "config": { + "allow-plugins": { + "ergebnis/composer-normalize": true, + "infection/extension-installer": true, + "phpstan/extension-installer": true + }, + "audit": { + "abandoned": "report" + }, + "platform": { + "php": "7.4.33" + }, + "preferred-install": "dist", + "sort-packages": true + }, + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/rules.neon b/tools/.phpstan/vendor/ergebnis/phpstan-rules/rules.neon new file mode 100644 index 00000000000..0458e5a34f1 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/rules.neon @@ -0,0 +1,292 @@ +conditionalTags: + Ergebnis\PHPStan\Rules\CallLikes\NoNamedArgumentRule: + phpstan.rules.rule: %ergebnis.noNamedArgument.enabled% + Ergebnis\PHPStan\Rules\Classes\FinalRule: + phpstan.rules.rule: %ergebnis.final.enabled% + Ergebnis\PHPStan\Rules\Classes\NoExtendsRule: + phpstan.rules.rule: %ergebnis.noExtends.enabled% + Ergebnis\PHPStan\Rules\Classes\PHPUnit\Framework\TestCaseWithSuffixRule: + phpstan.rules.rule: %ergebnis.testCaseWithSuffix.enabled% + Ergebnis\PHPStan\Rules\Closures\NoNullableReturnTypeDeclarationRule: + phpstan.rules.rule: %ergebnis.noNullableReturnTypeDeclaration.enabled% + Ergebnis\PHPStan\Rules\Closures\NoParameterPassedByReferenceRule: + phpstan.rules.rule: %ergebnis.noParameterPassedByReference.enabled% + Ergebnis\PHPStan\Rules\Closures\NoParameterWithNullableTypeDeclarationRule: + phpstan.rules.rule: %ergebnis.noParameterWithNullableTypeDeclaration.enabled% + Ergebnis\PHPStan\Rules\Expressions\NoAssignByReferenceRule: + phpstan.rules.rule: %ergebnis.noAssignByReference.enabled% + Ergebnis\PHPStan\Rules\Expressions\NoCompactRule: + phpstan.rules.rule: %ergebnis.noCompact.enabled% + Ergebnis\PHPStan\Rules\Expressions\NoErrorSuppressionRule: + phpstan.rules.rule: %ergebnis.noErrorSuppression.enabled% + Ergebnis\PHPStan\Rules\Expressions\NoEvalRule: + phpstan.rules.rule: %ergebnis.noEval.enabled% + Ergebnis\PHPStan\Rules\Expressions\NoIssetRule: + phpstan.rules.rule: %ergebnis.noIsset.enabled% + Ergebnis\PHPStan\Rules\Files\DeclareStrictTypesRule: + phpstan.rules.rule: %ergebnis.declareStrictTypes.enabled% + Ergebnis\PHPStan\Rules\Functions\NoNullableReturnTypeDeclarationRule: + phpstan.rules.rule: %ergebnis.noNullableReturnTypeDeclaration.enabled% + Ergebnis\PHPStan\Rules\Functions\NoParameterPassedByReferenceRule: + phpstan.rules.rule: %ergebnis.noParameterPassedByReference.enabled% + Ergebnis\PHPStan\Rules\Functions\NoParameterWithNullableTypeDeclarationRule: + phpstan.rules.rule: %ergebnis.noParameterWithNullableTypeDeclaration.enabled% + Ergebnis\PHPStan\Rules\Functions\NoParameterWithNullDefaultValueRule: + phpstan.rules.rule: %ergebnis.noParameterWithNullDefaultValue.enabled% + Ergebnis\PHPStan\Rules\Functions\NoReturnByReferenceRule: + phpstan.rules.rule: %ergebnis.noReturnByReference.enabled% + Ergebnis\PHPStan\Rules\Methods\FinalInAbstractClassRule: + phpstan.rules.rule: %ergebnis.finalInAbstractClass.enabled% + Ergebnis\PHPStan\Rules\Methods\InvokeParentHookMethodRule: + phpstan.rules.rule: %ergebnis.invokeParentHookMethod.enabled% + Ergebnis\PHPStan\Rules\Methods\NoConstructorParameterWithDefaultValueRule: + phpstan.rules.rule: %ergebnis.noConstructorParameterWithDefaultValue.enabled% + Ergebnis\PHPStan\Rules\Methods\NoNullableReturnTypeDeclarationRule: + phpstan.rules.rule: %ergebnis.noNullableReturnTypeDeclaration.enabled% + Ergebnis\PHPStan\Rules\Methods\NoParameterPassedByReferenceRule: + phpstan.rules.rule: %ergebnis.noParameterPassedByReference.enabled% + Ergebnis\PHPStan\Rules\Methods\NoParameterWithContainerTypeDeclarationRule: + phpstan.rules.rule: %ergebnis.noParameterWithContainerTypeDeclaration.enabled% + Ergebnis\PHPStan\Rules\Methods\NoParameterWithNullableTypeDeclarationRule: + phpstan.rules.rule: %ergebnis.noParameterWithNullableTypeDeclaration.enabled% + Ergebnis\PHPStan\Rules\Methods\NoParameterWithNullDefaultValueRule: + phpstan.rules.rule: %ergebnis.noParameterWithNullDefaultValue.enabled% + Ergebnis\PHPStan\Rules\Methods\NoReturnByReferenceRule: + phpstan.rules.rule: %ergebnis.noReturnByReference.enabled% + Ergebnis\PHPStan\Rules\Methods\PrivateInFinalClassRule: + phpstan.rules.rule: %ergebnis.privateInFinalClass.enabled% + Ergebnis\PHPStan\Rules\Statements\NoSwitchRule: + phpstan.rules.rule: %ergebnis.noSwitch.enabled% + +parameters: + ergebnis: + allRules: true + declareStrictTypes: + enabled: %ergebnis.allRules% + final: + allowAbstractClasses: true + classesNotRequiredToBeAbstractOrFinal: [] + enabled: %ergebnis.allRules% + finalInAbstractClass: + enabled: %ergebnis.allRules% + invokeParentHookMethod: + enabled: %ergebnis.allRules% + hookMethods: [] + noAssignByReference: + enabled: %ergebnis.allRules% + noCompact: + enabled: %ergebnis.allRules% + noConstructorParameterWithDefaultValue: + enabled: %ergebnis.allRules% + noErrorSuppression: + enabled: %ergebnis.allRules% + noEval: + enabled: %ergebnis.allRules% + noExtends: + classesAllowedToBeExtended: [] + enabled: %ergebnis.allRules% + noIsset: + enabled: %ergebnis.allRules% + noNamedArgument: + enabled: %ergebnis.allRules% + noNullableReturnTypeDeclaration: + enabled: %ergebnis.allRules% + noParameterPassedByReference: + enabled: %ergebnis.allRules% + noParameterWithContainerTypeDeclaration: + enabled: %ergebnis.allRules% + interfacesImplementedByContainers: + - Psr\Container\ContainerInterface + methodsAllowedToUseContainerTypeDeclarations: [] + noParameterWithNullableTypeDeclaration: + enabled: %ergebnis.allRules% + noParameterWithNullDefaultValue: + enabled: %ergebnis.allRules% + noReturnByReference: + enabled: %ergebnis.allRules% + noSwitch: + enabled: %ergebnis.allRules% + privateInFinalClass: + enabled: %ergebnis.allRules% + testCaseWithSuffix: + enabled: %ergebnis.allRules% + +parametersSchema: + ergebnis: structure([ + allRules: bool() + declareStrictTypes: structure([ + enabled: bool(), + ]) + final: structure([ + allowAbstractClasses: bool() + classesNotRequiredToBeAbstractOrFinal: listOf(string()) + enabled: bool(), + ]) + finalInAbstractClass: structure([ + enabled: bool(), + ]) + invokeParentHookMethod: structure([ + enabled: bool(), + hookMethods: listOf(structure([ + className: string(), + hasContent: anyOf("no", "yes"), + invocation: anyOf("any", "first", "last"), + methodName: string(), + ])) + ]) + noAssignByReference: structure([ + enabled: bool(), + ]) + noCompact: structure([ + enabled: bool(), + ]) + noConstructorParameterWithDefaultValue: structure([ + enabled: bool(), + ]) + noErrorSuppression: structure([ + enabled: bool(), + ]) + noExtends: structure([ + classesAllowedToBeExtended: listOf(string()) + enabled: bool(), + ]) + noEval: structure([ + enabled: bool(), + ]) + noIsset: structure([ + enabled: bool(), + ]) + noNamedArgument: structure([ + enabled: bool(), + ]) + noNullableReturnTypeDeclaration: structure([ + enabled: bool(), + ]) + noParameterPassedByReference: structure([ + enabled: bool(), + ]) + noParameterWithContainerTypeDeclaration: structure([ + enabled: bool(), + interfacesImplementedByContainers: listOf(string()) + methodsAllowedToUseContainerTypeDeclarations: listOf(string()) + ]) + noParameterWithNullableTypeDeclaration: structure([ + enabled: bool(), + ]) + noParameterWithNullDefaultValue: structure([ + enabled: bool(), + ]) + noReturnByReference: structure([ + enabled: bool(), + ]) + noSwitch: structure([ + enabled: bool(), + ]) + privateInFinalClass: structure([ + enabled: bool(), + ]) + testCaseWithSuffix: structure([ + enabled: bool(), + ]) + ]) + +services: + - + class: Ergebnis\PHPStan\Rules\Analyzer + + - + class: Ergebnis\PHPStan\Rules\CallLikes\NoNamedArgumentRule + + - + class: Ergebnis\PHPStan\Rules\Classes\FinalRule + arguments: + allowAbstractClasses: %ergebnis.final.allowAbstractClasses% + classesNotRequiredToBeAbstractOrFinal: %ergebnis.final.classesNotRequiredToBeAbstractOrFinal% + + - + class: Ergebnis\PHPStan\Rules\Classes\NoExtendsRule + arguments: + classesAllowedToBeExtended: %ergebnis.noExtends.classesAllowedToBeExtended% + + - + class: Ergebnis\PHPStan\Rules\Classes\PHPUnit\Framework\TestCaseWithSuffixRule + + - + class: Ergebnis\PHPStan\Rules\Closures\NoNullableReturnTypeDeclarationRule + + - + class: Ergebnis\PHPStan\Rules\Closures\NoParameterPassedByReferenceRule + + - + class: Ergebnis\PHPStan\Rules\Closures\NoParameterWithNullableTypeDeclarationRule + + - + class: Ergebnis\PHPStan\Rules\Expressions\NoAssignByReferenceRule + + - + class: Ergebnis\PHPStan\Rules\Expressions\NoCompactRule + + - + class: Ergebnis\PHPStan\Rules\Expressions\NoErrorSuppressionRule + + - + class: Ergebnis\PHPStan\Rules\Expressions\NoEvalRule + + - + class: Ergebnis\PHPStan\Rules\Expressions\NoIssetRule + + - + class: Ergebnis\PHPStan\Rules\Files\DeclareStrictTypesRule + + - + class: Ergebnis\PHPStan\Rules\Functions\NoNullableReturnTypeDeclarationRule + + - + class: Ergebnis\PHPStan\Rules\Functions\NoParameterPassedByReferenceRule + + - + class: Ergebnis\PHPStan\Rules\Functions\NoParameterWithNullableTypeDeclarationRule + + - + class: Ergebnis\PHPStan\Rules\Functions\NoParameterWithNullDefaultValueRule + + - + class: Ergebnis\PHPStan\Rules\Functions\NoReturnByReferenceRule + + - + class: Ergebnis\PHPStan\Rules\Methods\FinalInAbstractClassRule + + - + class: Ergebnis\PHPStan\Rules\Methods\InvokeParentHookMethodRule + arguments: + hookMethods: %ergebnis.invokeParentHookMethod.hookMethods% + + - + class: Ergebnis\PHPStan\Rules\Methods\NoConstructorParameterWithDefaultValueRule + + - + class: Ergebnis\PHPStan\Rules\Methods\NoNullableReturnTypeDeclarationRule + + - + class: Ergebnis\PHPStan\Rules\Methods\NoParameterPassedByReferenceRule + + - + class: Ergebnis\PHPStan\Rules\Methods\NoParameterWithContainerTypeDeclarationRule + arguments: + interfacesImplementedByContainers: %ergebnis.noParameterWithContainerTypeDeclaration.interfacesImplementedByContainers% + methodsAllowedToUseContainerTypeDeclarations: %ergebnis.noParameterWithContainerTypeDeclaration.methodsAllowedToUseContainerTypeDeclarations% + + - + class: Ergebnis\PHPStan\Rules\Methods\NoParameterWithNullableTypeDeclarationRule + + - + class: Ergebnis\PHPStan\Rules\Methods\NoParameterWithNullDefaultValueRule + + - + class: Ergebnis\PHPStan\Rules\Methods\NoReturnByReferenceRule + + - + class: Ergebnis\PHPStan\Rules\Methods\PrivateInFinalClassRule + + - + class: Ergebnis\PHPStan\Rules\Statements\NoSwitchRule diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Analyzer.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Analyzer.php new file mode 100644 index 00000000000..938cab76203 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Analyzer.php @@ -0,0 +1,68 @@ +default instanceof Node\Expr\ConstFetch) { + return false; + } + + return 'null' === $parameter->default->name->toLowerString(); + } + + /** + * @param null|Node\ComplexType|Node\Identifier|Node\Name $typeDeclaration + */ + public function isNullableTypeDeclaration($typeDeclaration): bool + { + if ($typeDeclaration instanceof Node\NullableType) { + return true; + } + + if ($typeDeclaration instanceof Node\UnionType) { + foreach ($typeDeclaration->types as $type) { + if ( + $type instanceof Node\Identifier + && 'null' === $type->toLowerString() + ) { + return true; + } + + if ( + $type instanceof Node\Name\FullyQualified + && $type->hasAttribute('originalName') + ) { + $originalName = $type->getAttribute('originalName'); + + if ( + $originalName instanceof Node\Name + && 'null' === $originalName->toLowerString() + ) { + return true; + } + } + } + } + + return false; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/CallLikes/NoNamedArgumentRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/CallLikes/NoNamedArgumentRule.php new file mode 100644 index 00000000000..a87bbaffaeb --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/CallLikes/NoNamedArgumentRule.php @@ -0,0 +1,175 @@ + + */ +final class NoNamedArgumentRule implements Rules\Rule +{ + public function getNodeType(): string + { + return Node\Expr\CallLike::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (0 === \count($node->getArgs())) { + return []; + } + + /** @var list $namedArguments */ + $namedArguments = \array_values(\array_filter($node->getArgs(), static function (Node\Arg $argument): bool { + if (!$argument->name instanceof Node\Identifier) { + return false; + } + + return true; + })); + + if (0 === \count($namedArguments)) { + return []; + } + + $callLike = self::describeCallLike( + $node, + $scope, + ); + + return \array_map(static function (Node\Arg $namedArgument) use ($callLike): Rules\RuleError { + /** @var Node\Identifier $argumentName */ + $argumentName = $namedArgument->name; + + $message = \sprintf( + '%s is invoked with named argument for parameter $%s.', + $callLike, + $argumentName->toString(), + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noNamedArgument()->toString()) + ->build(); + }, $namedArguments); + } + + private static function describeCallLike( + Node\Expr\CallLike $node, + Analyser\Scope $scope + ): string { + if ($node instanceof Node\Expr\FuncCall) { + $functionName = $node->name; + + if ($functionName instanceof Node\Expr\PropertyFetch) { + return \sprintf( + 'Callable referenced by property $%s', + $functionName->name, + ); + } + + if ($functionName instanceof Node\Expr\Variable) { + return \sprintf( + 'Callable referenced by $%s', + $functionName->name, + ); + } + + if ($functionName instanceof Node\Name) { + return \sprintf( + 'Function %s()', + $functionName->name, + ); + } + } + + if ($node instanceof Node\Expr\MethodCall) { + $methodName = $node->name; + + if ($methodName instanceof Node\Identifier) { + $objectType = $scope->getType($node->var); + + $methodReflection = $scope->getMethodReflection( + $objectType, + $methodName->name, + ); + + if (null === $methodReflection) { + throw new ShouldNotHappenException(); + } + + $declaringClass = $methodReflection->getDeclaringClass(); + + if ($declaringClass->isAnonymous()) { + return \sprintf( + 'Method %s() of anonymous class', + $methodName->toString(), + ); + } + + return \sprintf( + 'Method %s::%s()', + $declaringClass->getName(), + $methodName->toString(), + ); + } + + return 'Method'; + } + + if ($node instanceof Node\Expr\StaticCall) { + $methodName = $node->name; + + if ($methodName instanceof Node\Identifier) { + $className = $node->class; + + if ($className instanceof Node\Name) { + return \sprintf( + 'Method %s::%s()', + $className->toString(), + $methodName->toString(), + ); + } + + return \sprintf( + 'Method %s()', + $methodName->toString(), + ); + } + + return 'Method'; + } + + if ($node instanceof Node\Expr\New_) { + $className = $node->class; + + if ($className instanceof Node\Name) { + return \sprintf( + 'Constructor of %s', + $className->toString(), + ); + } + + return 'Constructor'; + } + + return 'Callable'; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/ClassName.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/ClassName.php new file mode 100644 index 00000000000..e62b9083bf0 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/ClassName.php @@ -0,0 +1,51 @@ +value = $value; + } + + /** + * @param class-string $value + */ + public static function fromString(string $value): self + { + return new self($value); + } + + /** + * @return class-string + */ + public function toString(): string + { + return $this->value; + } + + public function equals(self $other): bool + { + return $this->value === $other->value; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/FinalRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/FinalRule.php new file mode 100644 index 00000000000..05a9f3ca6d3 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/FinalRule.php @@ -0,0 +1,162 @@ + + */ +final class FinalRule implements Rules\Rule +{ + /** + * @var list + */ + private static array $whitelistedAnnotations = [ + 'Entity', + 'ORM\Entity', + 'ORM\Mapping\Entity', + ]; + + /** + * @var list + */ + private static array $whitelistedAttributes = [ + ORM\Mapping\Entity::class, + ]; + private bool $allowAbstractClasses; + + /** + * @var list + */ + private array $classesNotRequiredToBeAbstractOrFinal; + private string $errorMessageTemplate = 'Class %s is not final.'; + + /** + * @param list $classesNotRequiredToBeAbstractOrFinal + */ + public function __construct( + bool $allowAbstractClasses, + array $classesNotRequiredToBeAbstractOrFinal + ) { + $this->allowAbstractClasses = $allowAbstractClasses; + $this->classesNotRequiredToBeAbstractOrFinal = \array_map(static function (string $classNotRequiredToBeAbstractOrFinal): string { + return $classNotRequiredToBeAbstractOrFinal; + }, $classesNotRequiredToBeAbstractOrFinal); + + if ($allowAbstractClasses) { + $this->errorMessageTemplate = 'Class %s is neither abstract nor final.'; + } + } + + public function getNodeType(): string + { + return Node\Stmt\Class_::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (!isset($node->namespacedName)) { + return []; + } + + if (\in_array($node->namespacedName->toString(), $this->classesNotRequiredToBeAbstractOrFinal, true)) { + return []; + } + + if ( + $this->allowAbstractClasses + && $node->isAbstract() + ) { + return []; + } + + if ($node->isFinal()) { + return []; + } + + if (self::hasWhitelistedAnnotation($node)) { + return []; + } + + if (self::hasWhitelistedAttribute($node)) { + return []; + } + + $message = \sprintf( + $this->errorMessageTemplate, + $node->namespacedName->toString(), + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::final()->toString()) + ->build(), + ]; + } + + /** + * This method is inspired by the work on PhpCsFixer\Fixer\ClassNotation\FinalClassFixer and + * PhpCsFixer\Fixer\ClassNotation\FinalInternalClassFixer contributed by Dariusz Rumiński, Filippo Tessarotto, and + * Spacepossum for friendsofphp/php-cs-fixer. + * + * @see https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/2.15/src/Fixer/ClassNotation/FinalClassFixer.php + * @see https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/2.15/src/Fixer/ClassNotation/FinalInternalClassFixer.php + * @see https://github.com/keradus + * @see https://github.com/SpacePossum + * @see https://github.com/Slamdunk + */ + private static function hasWhitelistedAnnotation(Node\Stmt\Class_ $node): bool + { + $docComment = $node->getDocComment(); + + if (!$docComment instanceof Comment\Doc) { + return false; + } + + $reformattedComment = $docComment->getReformattedText(); + + if (\is_int(\preg_match_all('/@(\S+)(?=\s|$)/', $reformattedComment, $matches))) { + foreach ($matches[1] as $annotation) { + foreach (self::$whitelistedAnnotations as $whitelistedAnnotation) { + if (0 === \mb_strpos($annotation, $whitelistedAnnotation)) { + return true; + } + } + } + } + + return false; + } + + private static function hasWhitelistedAttribute(Node\Stmt\Class_ $node): bool + { + foreach ($node->attrGroups as $attributeGroup) { + foreach ($attributeGroup->attrs as $attribute) { + if (\in_array($attribute->name->toString(), self::$whitelistedAttributes, true)) { + return true; + } + } + } + + return false; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/NoExtendsRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/NoExtendsRule.php new file mode 100644 index 00000000000..4d4864e92c3 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/NoExtendsRule.php @@ -0,0 +1,99 @@ + + */ +final class NoExtendsRule implements Rules\Rule +{ + /** + * @var list + */ + private static array $defaultClassesAllowedToBeExtended = [ + Framework\TestCase::class, + ]; + + /** + * @var list + */ + private array $classesAllowedToBeExtended; + + /** + * @param list $classesAllowedToBeExtended + */ + public function __construct(array $classesAllowedToBeExtended) + { + $this->classesAllowedToBeExtended = \array_values(\array_unique(\array_merge( + self::$defaultClassesAllowedToBeExtended, + \array_map(static function (string $classAllowedToBeExtended): string { + /** @var class-string $classAllowedToBeExtended */ + return $classAllowedToBeExtended; + }, $classesAllowedToBeExtended), + ))); + } + + public function getNodeType(): string + { + return Node\Stmt\Class_::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (!$node->extends instanceof Node\Name) { + return []; + } + + $extendedClassName = $node->extends->toString(); + + if (\in_array($extendedClassName, $this->classesAllowedToBeExtended, true)) { + return []; + } + + if (!isset($node->namespacedName)) { + $message = \sprintf( + 'Anonymous class is not allowed to extend "%s".', + $extendedClassName, + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noExtends()->toString()) + ->build(), + ]; + } + + $extendingClassName = $node->namespacedName->toString(); + + $message = \sprintf( + 'Class "%s" is not allowed to extend "%s".', + $extendingClassName, + $extendedClassName, + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noExtends()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/PHPUnit/Framework/TestCaseWithSuffixRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/PHPUnit/Framework/TestCaseWithSuffixRule.php new file mode 100644 index 00000000000..4b6d4792676 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/PHPUnit/Framework/TestCaseWithSuffixRule.php @@ -0,0 +1,95 @@ + + */ +final class TestCaseWithSuffixRule implements Rules\Rule +{ + /** + * @var list + */ + private static array $phpunitTestCaseClassNames = [ + 'PHPUnit\Framework\TestCase', + ]; + private Reflection\ReflectionProvider $reflectionProvider; + + public function __construct(Reflection\ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + + public function getNodeType(): string + { + return Node\Stmt\Class_::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if ($node->isAbstract()) { + return []; + } + + if (!$node->extends instanceof Node\Name) { + return []; + } + + if (!isset($node->namespacedName)) { + return []; + } + + $fullyQualifiedClassName = $node->namespacedName->toString(); + + $classReflection = $this->reflectionProvider->getClass($fullyQualifiedClassName); + + $extendedPhpunitTestCaseClassName = ''; + + foreach (self::$phpunitTestCaseClassNames as $phpunitTestCaseClassName) { + if ($classReflection->isSubclassOfClass($this->reflectionProvider->getClass($phpunitTestCaseClassName))) { + $extendedPhpunitTestCaseClassName = $phpunitTestCaseClassName; + + break; + } + } + + if ('' === $extendedPhpunitTestCaseClassName) { + return []; + } + + if (1 === \preg_match('/Test$/', $fullyQualifiedClassName)) { + return []; + } + + $message = \sprintf( + 'Class %s extends %s, is concrete, but does not have a Test suffix.', + $fullyQualifiedClassName, + $extendedPhpunitTestCaseClassName, + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::testCaseWithSuffix()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoNullableReturnTypeDeclarationRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoNullableReturnTypeDeclarationRule.php new file mode 100644 index 00000000000..64351579137 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoNullableReturnTypeDeclarationRule.php @@ -0,0 +1,53 @@ + + */ +final class NoNullableReturnTypeDeclarationRule implements Rules\Rule +{ + private Analyzer $analyzer; + + public function __construct(Analyzer $analyzer) + { + $this->analyzer = $analyzer; + } + + public function getNodeType(): string + { + return Node\Expr\Closure::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (!$this->analyzer->isNullableTypeDeclaration($node->getReturnType())) { + return []; + } + + return [ + Rules\RuleErrorBuilder::message('Closure has a nullable return type declaration.') + ->identifier(ErrorIdentifier::noNullableReturnTypeDeclaration()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterPassedByReferenceRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterPassedByReferenceRule.php new file mode 100644 index 00000000000..2c47cb742f8 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterPassedByReferenceRule.php @@ -0,0 +1,64 @@ + + */ +final class NoParameterPassedByReferenceRule implements Rules\Rule +{ + public function getNodeType(): string + { + return Node\Expr\Closure::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (0 === \count($node->params)) { + return []; + } + + $parametersPassedByReference = \array_values(\array_filter($node->params, static function (Node\Param $parameter): bool { + return $parameter->byRef; + })); + + if (0 === \count($parametersPassedByReference)) { + return []; + } + + return \array_map(static function (Node\Param $parameterPassedByReference): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterPassedByReference->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Closure has parameter $%s that is passed by reference.', + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterPassedByReference()->toString()) + ->build(); + }, $parametersPassedByReference); + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterWithNullDefaultValueRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterWithNullDefaultValueRule.php new file mode 100644 index 00000000000..94bd1463542 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterWithNullDefaultValueRule.php @@ -0,0 +1,72 @@ + + */ +final class NoParameterWithNullDefaultValueRule implements Rules\Rule +{ + private Analyzer $analyzer; + + public function __construct(Analyzer $analyzer) + { + $this->analyzer = $analyzer; + } + + public function getNodeType(): string + { + return Node\Expr\Closure::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (0 === \count($node->params)) { + return []; + } + + $parametersWithNullDefaultValue = \array_values(\array_filter($node->params, function (Node\Param $parameter): bool { + return $this->analyzer->hasNullDefaultValue($parameter); + })); + + if (0 === \count($parametersWithNullDefaultValue)) { + return []; + } + + return \array_map(static function (Node\Param $parameterWithNullDefaultValue): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterWithNullDefaultValue->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Closure has parameter $%s with null as default value.', + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterWithNullDefaultValue()->toString()) + ->build(); + }, $parametersWithNullDefaultValue); + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterWithNullableTypeDeclarationRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterWithNullableTypeDeclarationRule.php new file mode 100644 index 00000000000..3cff6ee6dc5 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterWithNullableTypeDeclarationRule.php @@ -0,0 +1,72 @@ + + */ +final class NoParameterWithNullableTypeDeclarationRule implements Rules\Rule +{ + private Analyzer $analyzer; + + public function __construct(Analyzer $analyzer) + { + $this->analyzer = $analyzer; + } + + public function getNodeType(): string + { + return Node\Expr\Closure::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (0 === \count($node->params)) { + return []; + } + + $parametersWithNullableTypeDeclaration = \array_values(\array_filter($node->params, function (Node\Param $parameter): bool { + return $this->analyzer->isNullableTypeDeclaration($parameter->type); + })); + + if (0 === \count($parametersWithNullableTypeDeclaration)) { + return []; + } + + return \array_map(static function (Node\Param $parameterWithNullableTypeDeclaration): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterWithNullableTypeDeclaration->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Closure has parameter $%s with a nullable type declaration.', + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterWithNullableTypeDeclaration()->toString()) + ->build(); + }, $parametersWithNullableTypeDeclaration); + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/ErrorIdentifier.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/ErrorIdentifier.php new file mode 100644 index 00000000000..3435c1998fa --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/ErrorIdentifier.php @@ -0,0 +1,140 @@ +value = $value; + } + + public static function declareStrictTypes(): self + { + return new self('declareStrictTypes'); + } + + public static function final(): self + { + return new self('final'); + } + + public static function finalInAbstractClass(): self + { + return new self('finalInAbstractClass'); + } + + public static function invokeParentHookMethod(): self + { + return new self('invokeParentHookMethod'); + } + + public static function noCompact(): self + { + return new self('noCompact'); + } + + public static function noConstructorParameterWithDefaultValue(): self + { + return new self('noConstructorParameterWithDefaultValue'); + } + + public static function noAssignByReference(): self + { + return new self('noAssignByReference'); + } + + public static function noErrorSuppression(): self + { + return new self('noErrorSuppression'); + } + + public static function noEval(): self + { + return new self('noEval'); + } + + public static function noExtends(): self + { + return new self('noExtends'); + } + + public static function noIsset(): self + { + return new self('noIsset'); + } + + public static function noNamedArgument(): self + { + return new self('noNamedArgument'); + } + + public static function noParameterPassedByReference(): self + { + return new self('noParameterPassedByReference'); + } + + public static function noParameterWithContainerTypeDeclaration(): self + { + return new self('noParameterWithContainerTypeDeclaration'); + } + + public static function noParameterWithNullDefaultValue(): self + { + return new self('noParameterWithNullDefaultValue'); + } + + public static function noParameterWithNullableTypeDeclaration(): self + { + return new self('noParameterWithNullableTypeDeclaration'); + } + + public static function noNullableReturnTypeDeclaration(): self + { + return new self('noNullableReturnTypeDeclaration'); + } + + public static function noReturnByReference(): self + { + return new self('noReturnByReference'); + } + + public static function noSwitch(): self + { + return new self('noSwitch'); + } + + public static function privateInFinalClass(): self + { + return new self('privateInFinalClass'); + } + + public static function testCaseWithSuffix(): self + { + return new self('testCaseWithSuffix'); + } + + public function toString(): string + { + return \sprintf( + 'ergebnis.%s', + $this->value, + ); + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoAssignByReferenceRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoAssignByReferenceRule.php new file mode 100644 index 00000000000..ad8cc1ef996 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoAssignByReferenceRule.php @@ -0,0 +1,41 @@ + + */ +final class NoAssignByReferenceRule implements Rules\Rule +{ + public function getNodeType(): string + { + return Node\Expr\AssignRef::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + return [ + Rules\RuleErrorBuilder::message('Assign by reference should not be used.') + ->identifier(ErrorIdentifier::noAssignByReference()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoCompactRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoCompactRule.php new file mode 100644 index 00000000000..cd12ffea348 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoCompactRule.php @@ -0,0 +1,49 @@ + + */ +final class NoCompactRule implements Rules\Rule +{ + public function getNodeType(): string + { + return Node\Expr\FuncCall::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (!$node->name instanceof Node\Name) { + return []; + } + + if ('compact' !== \mb_strtolower($scope->resolveName($node->name))) { + return []; + } + + return [ + Rules\RuleErrorBuilder::message('Function compact() should not be used.') + ->identifier(ErrorIdentifier::noCompact()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoErrorSuppressionRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoErrorSuppressionRule.php new file mode 100644 index 00000000000..49c0939c892 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoErrorSuppressionRule.php @@ -0,0 +1,41 @@ + + */ +final class NoErrorSuppressionRule implements Rules\Rule +{ + public function getNodeType(): string + { + return Node\Expr\ErrorSuppress::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + return [ + Rules\RuleErrorBuilder::message('Error suppression via "@" should not be used.') + ->identifier(ErrorIdentifier::noErrorSuppression()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoEvalRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoEvalRule.php new file mode 100644 index 00000000000..b54205787fc --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoEvalRule.php @@ -0,0 +1,41 @@ + + */ +final class NoEvalRule implements Rules\Rule +{ + public function getNodeType(): string + { + return Node\Expr\Eval_::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + return [ + Rules\RuleErrorBuilder::message('Language construct eval() should not be used.') + ->identifier(ErrorIdentifier::noEval()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoIssetRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoIssetRule.php new file mode 100644 index 00000000000..f1443d66742 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoIssetRule.php @@ -0,0 +1,41 @@ + + */ +final class NoIssetRule implements Rules\Rule +{ + public function getNodeType(): string + { + return Node\Expr\Isset_::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + return [ + Rules\RuleErrorBuilder::message('Language construct isset() should not be used.') + ->identifier(ErrorIdentifier::noIsset()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Files/DeclareStrictTypesRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Files/DeclareStrictTypesRule.php new file mode 100644 index 00000000000..5f9ec1153c9 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Files/DeclareStrictTypesRule.php @@ -0,0 +1,70 @@ + + */ +final class DeclareStrictTypesRule implements Rules\Rule +{ + public function getNodeType(): string + { + return FileNode::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + $nodes = $node->getNodes(); + + if (0 === \count($nodes)) { + return []; + } + + $firstNode = \array_shift($nodes); + + if ( + $firstNode instanceof Node\Stmt\InlineHTML + && 2 === $firstNode->getEndLine() + && 0 === \mb_strpos($firstNode->value, '#!') + ) { + $firstNode = \array_shift($nodes); + } + + if ($firstNode instanceof Node\Stmt\Declare_) { + foreach ($firstNode->declares as $declare) { + if ( + 'strict_types' === $declare->key->toLowerString() + && $declare->value instanceof Node\Scalar\LNumber + && 1 === $declare->value->value + ) { + return []; + } + } + } + + return [ + Rules\RuleErrorBuilder::message('File is missing a "declare(strict_types=1)" declaration.') + ->identifier(ErrorIdentifier::declareStrictTypes()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoNullableReturnTypeDeclarationRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoNullableReturnTypeDeclarationRule.php new file mode 100644 index 00000000000..06da4f724e8 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoNullableReturnTypeDeclarationRule.php @@ -0,0 +1,62 @@ + + */ +final class NoNullableReturnTypeDeclarationRule implements Rules\Rule +{ + private Analyzer $analyzer; + + public function __construct(Analyzer $analyzer) + { + $this->analyzer = $analyzer; + } + + public function getNodeType(): string + { + return Node\Stmt\Function_::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (!isset($node->namespacedName)) { + return []; + } + + if (!$this->analyzer->isNullableTypeDeclaration($node->getReturnType())) { + return []; + } + + $message = \sprintf( + 'Function %s() has a nullable return type declaration.', + $node->namespacedName->toString(), + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noNullableReturnTypeDeclaration()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterPassedByReferenceRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterPassedByReferenceRule.php new file mode 100644 index 00000000000..e461aa13900 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterPassedByReferenceRule.php @@ -0,0 +1,67 @@ + + */ +final class NoParameterPassedByReferenceRule implements Rules\Rule +{ + public function getNodeType(): string + { + return Node\Stmt\Function_::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (0 === \count($node->params)) { + return []; + } + + $parametersPassedByReference = \array_values(\array_filter($node->params, static function (Node\Param $parameter): bool { + return $parameter->byRef; + })); + + if (0 === \count($parametersPassedByReference)) { + return []; + } + + $functionName = $node->namespacedName; + + return \array_map(static function (Node\Param $parameterPassedByReference) use ($functionName): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterPassedByReference->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Function %s() has parameter $%s that is passed by reference.', + $functionName, + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterWithNullDefaultValue()->toString()) + ->build(); + }, $parametersPassedByReference); + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterWithNullDefaultValueRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterWithNullDefaultValueRule.php new file mode 100644 index 00000000000..fe82a4e3d33 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterWithNullDefaultValueRule.php @@ -0,0 +1,75 @@ + + */ +final class NoParameterWithNullDefaultValueRule implements Rules\Rule +{ + private Analyzer $analyzer; + + public function __construct(Analyzer $analyzer) + { + $this->analyzer = $analyzer; + } + + public function getNodeType(): string + { + return Node\Stmt\Function_::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (0 === \count($node->params)) { + return []; + } + + $parametersWithNullDefaultValue = \array_values(\array_filter($node->params, function (Node\Param $parameter): bool { + return $this->analyzer->hasNullDefaultValue($parameter); + })); + + if (0 === \count($parametersWithNullDefaultValue)) { + return []; + } + + $functionName = $node->namespacedName; + + return \array_map(static function (Node\Param $parameterWithNullDefaultValue) use ($functionName): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterWithNullDefaultValue->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Function %s() has parameter $%s with null as default value.', + $functionName, + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterWithNullDefaultValue()->toString()) + ->build(); + }, $parametersWithNullDefaultValue); + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterWithNullableTypeDeclarationRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterWithNullableTypeDeclarationRule.php new file mode 100644 index 00000000000..90275530b6f --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterWithNullableTypeDeclarationRule.php @@ -0,0 +1,75 @@ + + */ +final class NoParameterWithNullableTypeDeclarationRule implements Rules\Rule +{ + private Analyzer $analyzer; + + public function __construct(Analyzer $analyzer) + { + $this->analyzer = $analyzer; + } + + public function getNodeType(): string + { + return Node\Stmt\Function_::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (0 === \count($node->params)) { + return []; + } + + $parametersWithNullableTypeDeclaration = \array_values(\array_filter($node->params, function (Node\Param $parameter): bool { + return $this->analyzer->isNullableTypeDeclaration($parameter->type); + })); + + if (0 === \count($parametersWithNullableTypeDeclaration)) { + return []; + } + + $functionName = $node->namespacedName; + + return \array_map(static function (Node\Param $parameterWithNullableTypeDeclaration) use ($functionName): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterWithNullableTypeDeclaration->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Function %s() has parameter $%s with a nullable type declaration.', + $functionName, + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterWithNullableTypeDeclaration()->toString()) + ->build(); + }, $parametersWithNullableTypeDeclaration); + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoReturnByReferenceRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoReturnByReferenceRule.php new file mode 100644 index 00000000000..0efb7008b71 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoReturnByReferenceRule.php @@ -0,0 +1,50 @@ + + */ +final class NoReturnByReferenceRule implements Rules\Rule +{ + public function getNodeType(): string + { + return Node\Stmt\Function_::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (false === $node->byRef) { + return []; + } + + $message = \sprintf( + 'Function %s() returns by reference.', + $node->namespacedName, + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noReturnByReference()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/HasContent.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/HasContent.php new file mode 100644 index 00000000000..ced81778b9a --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/HasContent.php @@ -0,0 +1,74 @@ +value = $value; + } + + /** + * @throws \InvalidArgumentException + */ + public static function fromString(string $value): self + { + $values = [ + 'maybe', + 'no', + 'yes', + ]; + + if (!\in_array($value, $values, true)) { + throw new \InvalidArgumentException(\sprintf( + 'Value needs to be one of "%s", got "%s" instead.', + \implode('", "', $values), + $value, + )); + } + + return new self($value); + } + + public static function maybe(): self + { + return new self('maybe'); + } + + public static function no(): self + { + return new self('no'); + } + + public static function yes(): self + { + return new self('yes'); + } + + public function toString(): string + { + return $this->value; + } + + public function equals(self $other): bool + { + return $this->value === $other->value; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/HookMethod.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/HookMethod.php new file mode 100644 index 00000000000..2d14cad7b5d --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/HookMethod.php @@ -0,0 +1,71 @@ +className = $className; + $this->methodName = $methodName; + $this->invocation = $invocation; + $this->hasContent = $hasContent; + } + + public static function create( + ClassName $className, + MethodName $methodName, + Invocation $invocation, + HasContent $hasContent + ): self { + return new self( + $className, + $methodName, + $invocation, + $hasContent, + ); + } + + public function className(): ClassName + { + return $this->className; + } + + public function methodName(): MethodName + { + return $this->methodName; + } + + public function invocation(): Invocation + { + return $this->invocation; + } + + public function hasContent(): HasContent + { + return $this->hasContent; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Invocation.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Invocation.php new file mode 100644 index 00000000000..722ff5f1ac3 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Invocation.php @@ -0,0 +1,86 @@ +value = $value; + } + + /** + * @throws \InvalidArgumentException + */ + public static function fromString(string $value): self + { + $values = [ + 'any', + 'first', + 'last', + 'never', + ]; + + if (!\in_array($value, $values, true)) { + throw new \InvalidArgumentException(\sprintf( + 'Value needs to be one of "%s", got "%s" instead.', + \implode('", "', $values), + $value, + )); + } + + return new self($value); + } + + public static function any(): self + { + return new self('any'); + } + + public static function first(): self + { + return new self('first'); + } + + public static function last(): self + { + return new self('last'); + } + + public static function never(): self + { + return new self('never'); + } + + /** + * @return non-empty-string + */ + public function toString(): string + { + return $this->value; + } + + public function equals(self $other): bool + { + return $this->value === $other->value; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/MethodName.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/MethodName.php new file mode 100644 index 00000000000..47116cefb46 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/MethodName.php @@ -0,0 +1,51 @@ +value = $value; + } + + /** + * @param non-empty-string $value + */ + public static function fromString(string $value): self + { + return new self($value); + } + + /** + * @return non-empty-string + */ + public function toString(): string + { + return $this->value; + } + + public function equals(self $other): bool + { + return $this->value === $other->value; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/FinalInAbstractClassRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/FinalInAbstractClassRule.php new file mode 100644 index 00000000000..271870aee8e --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/FinalInAbstractClassRule.php @@ -0,0 +1,124 @@ + + */ +final class FinalInAbstractClassRule implements Rules\Rule +{ + private const DOCTRINE_ATTRIBUTE_NAMES = [ + ORM\Mapping\Embeddable::class, + ORM\Mapping\Entity::class, + ]; + private const DOCTRINE_ANNOTATION_NAMES = [ + '@ORM\\Mapping\\Embeddable', + '@ORM\\Embeddable', + '@Embeddable', + '@ORM\\Mapping\\Entity', + '@ORM\\Entity', + '@Entity', + ]; + + public function getNodeType(): string + { + return Node\Stmt\ClassMethod::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + /** @var Reflection\ClassReflection $containingClass */ + $containingClass = $scope->getClassReflection(); + + if (self::isDoctrineEntity($containingClass)) { + return []; + } + + if (!$containingClass->isAbstract()) { + return []; + } + + if ($containingClass->isInterface()) { + return []; + } + + if ($node->isAbstract()) { + return []; + } + + if ($node->isFinal()) { + return []; + } + + if ($node->isPrivate()) { + return []; + } + + if ('__construct' === $node->name->name) { + return []; + } + + $message = \sprintf( + 'Method %s::%s() is not final, but since the containing class is abstract, it should be.', + $containingClass->getName(), + $node->name->toString(), + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::finalInAbstractClass()->toString()) + ->build(), + ]; + } + + private static function isDoctrineEntity(Reflection\ClassReflection $containingClass): bool + { + $attributes = $containingClass->getNativeReflection()->getAttributes(); + + foreach ($attributes as $attribute) { + if (\in_array($attribute->getName(), self::DOCTRINE_ATTRIBUTE_NAMES, true)) { + return true; + } + } + + $resolvedPhpDocBlock = $containingClass->getResolvedPhpDoc(); + + if ($resolvedPhpDocBlock instanceof PhpDoc\ResolvedPhpDocBlock) { + foreach ($resolvedPhpDocBlock->getPhpDocNodes() as $phpDocNode) { + foreach ($phpDocNode->children as $child) { + if (!$child instanceof PhpDocParser\Ast\PhpDoc\PhpDocTagNode) { + continue; + } + + if (\in_array($child->name, self::DOCTRINE_ANNOTATION_NAMES, true)) { + return true; + } + } + } + } + + return false; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/InvokeParentHookMethodRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/InvokeParentHookMethodRule.php new file mode 100644 index 00000000000..888aa922e41 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/InvokeParentHookMethodRule.php @@ -0,0 +1,436 @@ + + */ +final class InvokeParentHookMethodRule implements Rules\Rule +{ + private Reflection\ReflectionProvider $reflectionProvider; + + /** + * @var list + */ + private array $hookMethods; + + /** + * @param array> $hookMethods + */ + public function __construct( + Reflection\ReflectionProvider $reflectionProvider, + array $hookMethods = [] + ) { + $this->reflectionProvider = $reflectionProvider; + $this->hookMethods = self::sort( + $reflectionProvider, + ...self::filter( + $reflectionProvider, + ...\array_merge( + self::defaultHookMethods(), + \array_map(static function (array $hookMethod): HookMethod { + return HookMethod::create( + ClassName::fromString($hookMethod['className']), + MethodName::fromString($hookMethod['methodName']), + Invocation::fromString($hookMethod['invocation']), + HasContent::fromString($hookMethod['hasContent']), + ); + }, $hookMethods), + ), + ), + ); + } + + public function getNodeType(): string + { + return Node\Stmt\ClassMethod::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + $classReflection = $scope->getClassReflection(); + + if (null === $classReflection) { + return []; + } + + $parentClassReflection = $classReflection->getParentClass(); + + if (null === $parentClassReflection) { + return []; + } + + $methodName = $node->name->toString(); + + $hookMethod = $this->findMatchingHookMethod( + $scope, + $parentClassReflection, + $classReflection, + $methodName, + ); + + if (!$hookMethod instanceof HookMethod) { + return []; + } + + $statements = $node->getStmts(); + + if (!\is_array($statements)) { + throw new ShouldNotHappenException(); + } + + $parentHookMethodInvocation = self::findParentHookMethodInvocation( + \array_values($statements), + $hookMethod, + ); + + if ($parentHookMethodInvocation->equals(Invocation::never())) { + if ($hookMethod->hasContent()->equals(HasContent::no())) { + return []; + } + + $message = \sprintf( + 'Method %s::%s() does not invoke parent::%s().', + $classReflection->getName(), + $methodName, + $hookMethod->methodName()->toString(), + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::invokeParentHookMethod()->toString()) + ->build(), + ]; + } + + if ($parentHookMethodInvocation->equals($hookMethod->invocation())) { + return []; + } + + if ($hookMethod->invocation()->equals(Invocation::first())) { + $message = \sprintf( + 'Method %s::%s() does not invoke parent::%s() before all other statements.', + $classReflection->getName(), + $methodName, + $hookMethod->methodName()->toString(), + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::invokeParentHookMethod()->toString()) + ->build(), + ]; + } + + if ($hookMethod->invocation()->equals(Invocation::last())) { + $message = \sprintf( + 'Method %s::%s() does not invoke parent::%s() after all other statements.', + $classReflection->getName(), + $methodName, + $hookMethod->methodName()->toString(), + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::invokeParentHookMethod()->toString()) + ->build(), + ]; + } + + throw new ShouldNotHappenException(); + } + + private function findMatchingHookMethod( + Analyser\Scope $scope, + Reflection\ClassReflection $parentClassReflection, + Reflection\ClassReflection $classReflection, + string $methodName + ): ?HookMethod { + foreach ($this->hookMethods as $hookMethod) { + if (!$classReflection->isSubclassOfClass($this->reflectionProvider->getClass($hookMethod->className()->toString()))) { + continue; + } + + if (\mb_strtolower($hookMethod->methodName()->toString()) !== \mb_strtolower($methodName)) { + continue; + } + + $parentMethodReflection = $parentClassReflection->getMethod( + $methodName, + $scope, + ); + + $declaringClassReflection = $parentMethodReflection->getDeclaringClass(); + + if (\mb_strtolower($hookMethod->className()->toString()) !== \mb_strtolower($declaringClassReflection->getName())) { + return HookMethod::create( + ClassName::fromString($declaringClassReflection->getName()), + MethodName::fromString($methodName), + $hookMethod->invocation(), + HasContent::maybe(), + ); + } + + return $hookMethod; + } + + return null; + } + + /** + * @param list $statements + */ + private static function findParentHookMethodInvocation( + array $statements, + HookMethod $hookMethod + ): Invocation { + $statementsWithOperations = \array_filter($statements, static function (Node $statement): bool { + if ($statement instanceof Node\Stmt\Nop) { + return false; + } + + return true; + }); + + $statementCount = \count($statementsWithOperations); + + foreach ($statementsWithOperations as $index => $statement) { + if (!$statement instanceof Node\Stmt\Expression) { + continue; + } + + if (!$statement->expr instanceof Node\Expr\StaticCall) { + continue; + } + + if (!$statement->expr->class instanceof Node\Name) { + continue; + } + + $className = (string) $statement->expr->class; + + if (\mb_strtolower($className) !== 'parent') { + continue; + } + + if (!$statement->expr->name instanceof Node\Identifier) { + continue; + } + + if (\mb_strtolower($statement->expr->name->toString()) === \mb_strtolower($hookMethod->methodName()->toString())) { + if (1 === $statementCount) { + return $hookMethod->invocation(); + } + + if (0 === $index) { + return Invocation::first(); + } + + if ($statementCount - 1 === $index) { + return Invocation::last(); + } + + return Invocation::any(); + } + } + + return Invocation::never(); + } + + /** + * @return list + */ + private static function filter( + Reflection\ReflectionProvider $reflectionProvider, + HookMethod ...$hookMethods + ): array { + return \array_values(\array_filter($hookMethods, static function (HookMethod $hookMethod) use ($reflectionProvider): bool { + return $reflectionProvider->hasClass($hookMethod->className()->toString()); + })); + } + + /** + * @return list + */ + private static function sort( + Reflection\ReflectionProvider $reflectionProvider, + HookMethod ...$hookMethods + ): array { + \usort($hookMethods, static function (HookMethod $a, HookMethod $b) use ($reflectionProvider): int { + if (\mb_strtolower($a->className()->toString()) === \mb_strtolower($b->className()->toString())) { + return 0; + } + + if ($reflectionProvider->getClass($a->className()->toString())->isSubclassOfClass($reflectionProvider->getClass($b->className()->toString()))) { + return -1; + } + + return 1; + }); + + return $hookMethods; + } + + /** + * @return list + */ + private static function defaultHookMethods(): array + { + return [ + /** + * @see https://github.com/sebastianbergmann/phpunit/blob/6.0.0/src/Framework/TestCase.php#L2083-L2085 + */ + HookMethod::create( + ClassName::fromString(Framework\TestCase::class), + MethodName::fromString('assertPostConditions'), + Invocation::last(), + HasContent::no(), + ), + /** + * @see https://github.com/sebastianbergmann/phpunit/blob/6.0.0/src/Framework/TestCase.php#L2073-L2075 + */ + HookMethod::create( + ClassName::fromString(Framework\TestCase::class), + MethodName::fromString('assertPreConditions'), + Invocation::first(), + HasContent::no(), + ), + /** + * @see https://github.com/sebastianbergmann/phpunit/blob/6.0.0/src/Framework/TestCase.php#L2063-L2065 + */ + HookMethod::create( + ClassName::fromString(Framework\TestCase::class), + MethodName::fromString('setUp'), + Invocation::first(), + HasContent::no(), + ), + /** + * @see https://github.com/sebastianbergmann/phpunit/blob/6.0.0/src/Framework/TestCase.php#L2055-L2057 + */ + HookMethod::create( + ClassName::fromString(Framework\TestCase::class), + MethodName::fromString('setUpBeforeClass'), + Invocation::first(), + HasContent::no(), + ), + /** + * @see https://github.com/sebastianbergmann/phpunit/blob/6.0.0/src/Framework/TestCase.php#L2091-L2093 + */ + HookMethod::create( + ClassName::fromString(Framework\TestCase::class), + MethodName::fromString('tearDown'), + Invocation::last(), + HasContent::no(), + ), + /** + * @see https://github.com/sebastianbergmann/phpunit/blob/6.0.0/src/Framework/TestCase.php#L2098-L2100 + */ + HookMethod::create( + ClassName::fromString(Framework\TestCase::class), + MethodName::fromString('tearDownAfterClass'), + Invocation::last(), + HasContent::no(), + ), + /** + * @see https://github.com/Codeception/phpunit-wrapper/blob/9.0.0/src/TestCase.php#L11-L13 + */ + HookMethod::create( + ClassName::fromString(PHPUnit\TestCase::class), + MethodName::fromString('_setUp'), + Invocation::first(), + HasContent::no(), + ), + /** + * @see https://github.com/Codeception/phpunit-wrapper/blob/9.0.0/src/TestCase.php#L25-L27 + */ + HookMethod::create( + ClassName::fromString(PHPUnit\TestCase::class), + MethodName::fromString('_setUpBeforeClass'), + Invocation::first(), + HasContent::no(), + ), + /** + * @see https://github.com/Codeception/phpunit-wrapper/blob/9.0.0/src/TestCase.php#L18-L20 + */ + HookMethod::create( + ClassName::fromString(PHPUnit\TestCase::class), + MethodName::fromString('_tearDown'), + Invocation::last(), + HasContent::no(), + ), + /** + * @see https://github.com/Codeception/phpunit-wrapper/blob/9.0.0/src/TestCase.php#L32-L34 + */ + HookMethod::create( + ClassName::fromString(PHPUnit\TestCase::class), + MethodName::fromString('_tearDownAfterClass'), + Invocation::last(), + HasContent::no(), + ), + /** + * @see https://github.com/Codeception/Codeception/blob/4.2.2/src/Codeception/Test/Unit.php#L75-L77 + */ + HookMethod::create( + ClassName::fromString(Test\Unit::class), + MethodName::fromString('_after'), + Invocation::last(), + HasContent::no(), + ), + /** + * @see https://github.com/Codeception/Codeception/blob/4.2.2/src/Codeception/Test/Unit.php#L63-L65 + */ + HookMethod::create( + ClassName::fromString(Test\Unit::class), + MethodName::fromString('_before'), + Invocation::first(), + HasContent::no(), + ), + /** + * @see https://github.com/Codeception/Codeception/blob/4.2.2/src/Codeception/Test/Unit.php#L34-L58 + */ + HookMethod::create( + ClassName::fromString(Test\Unit::class), + MethodName::fromString('_setUp'), + Invocation::first(), + HasContent::yes(), + ), + /** + * @see https://github.com/Codeception/Codeception/blob/4.2.2/src/Codeception/Test/Unit.php#L67-L70 + */ + HookMethod::create( + ClassName::fromString(Test\Unit::class), + MethodName::fromString('_tearDown'), + Invocation::last(), + HasContent::yes(), + ), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoConstructorParameterWithDefaultValueRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoConstructorParameterWithDefaultValueRule.php new file mode 100644 index 00000000000..e8b7e34a3f7 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoConstructorParameterWithDefaultValueRule.php @@ -0,0 +1,99 @@ + + */ +final class NoConstructorParameterWithDefaultValueRule implements Rules\Rule +{ + public function getNodeType(): string + { + return Node\Stmt\ClassMethod::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if ('__construct' !== $node->name->toLowerString()) { + return []; + } + + if (0 === \count($node->params)) { + return []; + } + + $parametersWithDefaultValue = \array_values(\array_filter($node->params, static function (Node\Param $parameter): bool { + return self::hasDefaultValue($parameter); + })); + + if (0 === \count($parametersWithDefaultValue)) { + return []; + } + + /** @var Reflection\ClassReflection $classReflection */ + $classReflection = $scope->getClassReflection(); + + if ($classReflection->isAnonymous()) { + return \array_map(static function (Node\Param $parameterWithDefaultValue): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterWithDefaultValue->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Constructor in anonymous class has parameter $%s with default value.', + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noConstructorParameterWithDefaultValue()->toString()) + ->build(); + }, $parametersWithDefaultValue); + } + + $className = $classReflection->getName(); + + return \array_map(static function (Node\Param $parameterWithDefaultValue) use ($className): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterWithDefaultValue->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Constructor in %s has parameter $%s with default value.', + $className, + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noConstructorParameterWithDefaultValue()->toString()) + ->build(); + }, $parametersWithDefaultValue); + } + + private static function hasDefaultValue(Node\Param $parameter): bool + { + return null !== $parameter->default; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoNullableReturnTypeDeclarationRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoNullableReturnTypeDeclarationRule.php new file mode 100644 index 00000000000..e0e84c94719 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoNullableReturnTypeDeclarationRule.php @@ -0,0 +1,76 @@ + + */ +final class NoNullableReturnTypeDeclarationRule implements Rules\Rule +{ + private Analyzer $analyzer; + + public function __construct(Analyzer $analyzer) + { + $this->analyzer = $analyzer; + } + + public function getNodeType(): string + { + return Node\Stmt\ClassMethod::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (!$this->analyzer->isNullableTypeDeclaration($node->getReturnType())) { + return []; + } + + /** @var Reflection\ClassReflection $classReflection */ + $classReflection = $scope->getClassReflection(); + + if ($classReflection->isAnonymous()) { + $message = \sprintf( + 'Method %s() in anonymous class has a nullable return type declaration.', + $node->name->name, + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noNullableReturnTypeDeclaration()->toString()) + ->build(), + ]; + } + + $message = \sprintf( + 'Method %s::%s() has a nullable return type declaration.', + $classReflection->getName(), + $node->name->name, + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noNullableReturnTypeDeclaration()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterPassedByReferenceRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterPassedByReferenceRule.php new file mode 100644 index 00000000000..4c90f696de0 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterPassedByReferenceRule.php @@ -0,0 +1,94 @@ + + */ +final class NoParameterPassedByReferenceRule implements Rules\Rule +{ + public function getNodeType(): string + { + return Node\Stmt\ClassMethod::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (0 === \count($node->params)) { + return []; + } + + $parametersExplicitlyPassedByReference = \array_values(\array_filter($node->params, static function (Node\Param $parameter): bool { + return $parameter->byRef; + })); + + if (0 === \count($parametersExplicitlyPassedByReference)) { + return []; + } + + $methodName = $node->name->toString(); + + /** @var Reflection\ClassReflection $classReflection */ + $classReflection = $scope->getClassReflection(); + + if ($classReflection->isAnonymous()) { + return \array_map(static function (Node\Param $parameterExplicitlyPassedByReference) use ($methodName): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterExplicitlyPassedByReference->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Method %s() in anonymous class has parameter $%s that is passed by reference.', + $methodName, + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterPassedByReference()->toString()) + ->build(); + }, $parametersExplicitlyPassedByReference); + } + + $className = $classReflection->getName(); + + return \array_map(static function (Node\Param $parameterExplicitlyPassedByReference) use ($className, $methodName): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterExplicitlyPassedByReference->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Method %s::%s() has parameter $%s that is passed by reference.', + $className, + $methodName, + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterPassedByReference()->toString()) + ->build(); + }, $parametersExplicitlyPassedByReference); + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithContainerTypeDeclarationRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithContainerTypeDeclarationRule.php new file mode 100644 index 00000000000..f64a8312c39 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithContainerTypeDeclarationRule.php @@ -0,0 +1,179 @@ + + */ +final class NoParameterWithContainerTypeDeclarationRule implements Rules\Rule +{ + private Reflection\ReflectionProvider $reflectionProvider; + + /** + * @var list + */ + private array $interfacesImplementedByContainers; + + /** + * @var list + */ + private array $methodsAllowedToUseContainerTypeDeclarations; + + /** + * @param list $interfacesImplementedByContainers + * @param list $methodsAllowedToUseContainerTypeDeclarations + */ + public function __construct( + Reflection\ReflectionProvider $reflectionProvider, + array $interfacesImplementedByContainers, + array $methodsAllowedToUseContainerTypeDeclarations + ) { + $this->reflectionProvider = $reflectionProvider; + $this->interfacesImplementedByContainers = \array_values(\array_filter( + \array_map(static function (string $interfaceImplementedByContainers): string { + return $interfaceImplementedByContainers; + }, $interfacesImplementedByContainers), + static function (string $interfaceImplementedByContainer): bool { + return \interface_exists($interfaceImplementedByContainer); + }, + )); + $this->methodsAllowedToUseContainerTypeDeclarations = $methodsAllowedToUseContainerTypeDeclarations; + } + + public function getNodeType(): string + { + return Node\Stmt\ClassMethod::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (0 === \count($this->interfacesImplementedByContainers)) { + return []; + } + + if (0 === \count($node->params)) { + return []; + } + + $methodName = $node->name->toString(); + + if (\in_array($methodName, $this->methodsAllowedToUseContainerTypeDeclarations, true)) { + return []; + } + + /** @var Reflection\ClassReflection $containingClass */ + $containingClass = $scope->getClassReflection(); + + return \array_values(\array_reduce( + $node->params, + function (array $errors, Node\Param $node) use ($scope, $containingClass, $methodName): array { + $type = $node->type; + + if (!$type instanceof Node\Name) { + return $errors; + } + + /** @var Node\Expr\Variable $variable */ + $variable = $node->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $classUsedInTypeDeclaration = $this->reflectionProvider->getClass($scope->resolveName($type)); + + if ($classUsedInTypeDeclaration->isInterface()) { + foreach ($this->interfacesImplementedByContainers as $interfaceImplementedByContainer) { + if ($classUsedInTypeDeclaration->getName() === $interfaceImplementedByContainer) { + $errors[] = self::createError( + $containingClass, + $methodName, + $parameterName, + $classUsedInTypeDeclaration, + ); + + return $errors; + } + + if ($classUsedInTypeDeclaration->getNativeReflection()->isSubclassOf($interfaceImplementedByContainer)) { + $errors[] = self::createError( + $containingClass, + $methodName, + $parameterName, + $classUsedInTypeDeclaration, + ); + + return $errors; + } + } + } + + foreach ($this->interfacesImplementedByContainers as $interfaceImplementedByContainer) { + if ($classUsedInTypeDeclaration->getNativeReflection()->implementsInterface($interfaceImplementedByContainer)) { + $errors[] = self::createError( + $containingClass, + $methodName, + $parameterName, + $classUsedInTypeDeclaration, + ); + + return $errors; + } + } + + return $errors; + }, + [], + )); + } + + private static function createError( + Reflection\ClassReflection $classReflection, + string $methodName, + string $parameterName, + Reflection\ClassReflection $classUsedInTypeDeclaration + ): Rules\RuleError { + if ($classReflection->isAnonymous()) { + $message = \sprintf( + 'Method %s() in anonymous class has a parameter $%s with a type declaration of %s, but containers should not be injected.', + $methodName, + $parameterName, + $classUsedInTypeDeclaration->getName(), + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterWithContainerTypeDeclaration()->toString()) + ->build(); + } + + $message = \sprintf( + 'Method %s::%s() has a parameter $%s with a type declaration of %s, but containers should not be injected.', + $classReflection->getName(), + $methodName, + $parameterName, + $classUsedInTypeDeclaration->getName(), + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterWithContainerTypeDeclaration()->toString()) + ->build(); + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithNullDefaultValueRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithNullDefaultValueRule.php new file mode 100644 index 00000000000..9888974c695 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithNullDefaultValueRule.php @@ -0,0 +1,102 @@ + + */ +final class NoParameterWithNullDefaultValueRule implements Rules\Rule +{ + private Analyzer $analyzer; + + public function __construct(Analyzer $analyzer) + { + $this->analyzer = $analyzer; + } + + public function getNodeType(): string + { + return Node\Stmt\ClassMethod::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (0 === \count($node->params)) { + return []; + } + + $parametersWithNullDefaultValue = \array_values(\array_filter($node->params, function (Node\Param $parameter): bool { + return $this->analyzer->hasNullDefaultValue($parameter); + })); + + if (0 === \count($parametersWithNullDefaultValue)) { + return []; + } + + $methodName = $node->name->toString(); + + /** @var Reflection\ClassReflection $classReflection */ + $classReflection = $scope->getClassReflection(); + + if ($classReflection->isAnonymous()) { + return \array_map(static function (Node\Param $parameterWithNullDefaultValue) use ($methodName): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterWithNullDefaultValue->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Method %s() in anonymous class has parameter $%s with null as default value.', + $methodName, + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterWithNullDefaultValue()->toString()) + ->build(); + }, $parametersWithNullDefaultValue); + } + + $className = $classReflection->getName(); + + return \array_map(static function (Node\Param $parameterWithNullDefaultValue) use ($className, $methodName): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterWithNullDefaultValue->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Method %s::%s() has parameter $%s with null as default value.', + $className, + $methodName, + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterWithNullDefaultValue()->toString()) + ->build(); + }, $parametersWithNullDefaultValue); + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithNullableTypeDeclarationRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithNullableTypeDeclarationRule.php new file mode 100644 index 00000000000..632cd7bea27 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithNullableTypeDeclarationRule.php @@ -0,0 +1,102 @@ + + */ +final class NoParameterWithNullableTypeDeclarationRule implements Rules\Rule +{ + private Analyzer $analyzer; + + public function __construct(Analyzer $analyzer) + { + $this->analyzer = $analyzer; + } + + public function getNodeType(): string + { + return Node\Stmt\ClassMethod::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (0 === \count($node->params)) { + return []; + } + + $parametersWithNullableTypeDeclaration = \array_values(\array_filter($node->params, function (Node\Param $parameter): bool { + return $this->analyzer->isNullableTypeDeclaration($parameter->type); + })); + + if (0 === \count($parametersWithNullableTypeDeclaration)) { + return []; + } + + $methodName = $node->name->toString(); + + /** @var Reflection\ClassReflection $classReflection */ + $classReflection = $scope->getClassReflection(); + + if ($classReflection->isAnonymous()) { + return \array_map(static function (Node\Param $parameterWithNullableTypeDeclaration) use ($methodName): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterWithNullableTypeDeclaration->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Method %s() in anonymous class has parameter $%s with a nullable type declaration.', + $methodName, + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterWithNullableTypeDeclaration()->toString()) + ->build(); + }, $parametersWithNullableTypeDeclaration); + } + + $className = $classReflection->getName(); + + return \array_map(static function (Node\Param $parameterWithNullableTypeDeclaration) use ($className, $methodName): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterWithNullableTypeDeclaration->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Method %s::%s() has parameter $%s with a nullable type declaration.', + $className, + $methodName, + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterWithNullableTypeDeclaration()->toString()) + ->build(); + }, $parametersWithNullableTypeDeclaration); + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoReturnByReferenceRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoReturnByReferenceRule.php new file mode 100644 index 00000000000..a1bf87ff604 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoReturnByReferenceRule.php @@ -0,0 +1,72 @@ + + */ +final class NoReturnByReferenceRule implements Rules\Rule +{ + public function getNodeType(): string + { + return Node\Stmt\ClassMethod::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (false === $node->byRef) { + return []; + } + + $methodName = $node->name->toString(); + + /** @var Reflection\ClassReflection $classReflection */ + $classReflection = $scope->getClassReflection(); + + if ($classReflection->isAnonymous()) { + $message = \sprintf( + 'Method %s() in anonymous class returns by reference.', + $methodName, + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noReturnByReference()->toString()) + ->build(), + ]; + } + + $className = $classReflection->getName(); + + $message = \sprintf( + 'Method %s::%s() returns by reference.', + $className, + $methodName, + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noReturnByReference()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/PrivateInFinalClassRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/PrivateInFinalClassRule.php new file mode 100644 index 00000000000..9aa2c3cc286 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/PrivateInFinalClassRule.php @@ -0,0 +1,198 @@ + + */ +final class PrivateInFinalClassRule implements Rules\Rule +{ + /** + * @var list + */ + private static array $whitelistedAnnotations = [ + '@after', + '@before', + '@postCondition', + '@preCondition', + ]; + + /** + * @var list + */ + private static array $whitelistedAttributes = [ + Framework\Attributes\After::class, + Framework\Attributes\Before::class, + Framework\Attributes\PostCondition::class, + Framework\Attributes\PreCondition::class, + ]; + private Type\FileTypeMapper $fileTypeMapper; + + public function __construct(Type\FileTypeMapper $fileTypeMapper) + { + $this->fileTypeMapper = $fileTypeMapper; + } + + public function getNodeType(): string + { + return Node\Stmt\ClassMethod::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + /** @var Reflection\ClassReflection $containingClass */ + $containingClass = $scope->getClassReflection(); + + if (!$containingClass->isFinal()) { + return []; + } + + if ($node->isPublic()) { + return []; + } + + if ($node->isPrivate()) { + return []; + } + + if ($this->hasWhitelistedAnnotation($node, $containingClass)) { + return []; + } + + if (self::hasWhitelistedAttribute($node)) { + return []; + } + + $methodName = $node->name->toString(); + + if (self::isDeclaredByParentClass($containingClass, $methodName)) { + return []; + } + + if (self::isDeclaredByTrait($containingClass, $methodName)) { + return []; + } + + /** @var Reflection\ClassReflection $classReflection */ + $classReflection = $scope->getClassReflection(); + + if ($classReflection->isAnonymous()) { + $message = \sprintf( + 'Method %s() in anonymous class is protected, but since the containing class is final, it can be private.', + $node->name->name, + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::privateInFinalClass()->toString()) + ->build(), + ]; + } + + $message = \sprintf( + 'Method %s::%s() is protected, but since the containing class is final, it can be private.', + $containingClass->getName(), + $methodName, + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::privateInFinalClass()->toString()) + ->build(), + ]; + } + + private function hasWhitelistedAnnotation( + Node\Stmt\ClassMethod $node, + Reflection\ClassReflection $containingClass + ): bool { + $docComment = $node->getDocComment(); + + if (!$docComment instanceof Comment\Doc) { + return false; + } + + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + null, + $containingClass->getName(), + null, + null, + $docComment->getText(), + ); + + foreach ($resolvedPhpDoc->getPhpDocNodes() as $phpDocNode) { + foreach ($phpDocNode->getTags() as $tag) { + if (\in_array($tag->name, self::$whitelistedAnnotations, true)) { + return true; + } + } + } + + return false; + } + + private static function hasWhitelistedAttribute(Node\Stmt\ClassMethod $node): bool + { + foreach ($node->attrGroups as $attributeGroup) { + foreach ($attributeGroup->attrs as $attribute) { + if (\in_array($attribute->name->toString(), self::$whitelistedAttributes, true)) { + return true; + } + } + } + + return false; + } + + private static function isDeclaredByParentClass( + Reflection\ClassReflection $containingClass, + string $methodName + ): bool { + $parentClass = $containingClass->getNativeReflection()->getParentClass(); + + if (!$parentClass instanceof \ReflectionClass) { + return false; + } + + if (!$parentClass->hasMethod($methodName)) { + return false; + } + + return true; + } + + private static function isDeclaredByTrait( + Reflection\ClassReflection $containingClass, + string $methodName + ): bool { + foreach ($containingClass->getTraits() as $trait) { + if ($trait->hasMethod($methodName)) { + return true; + } + } + + return false; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Statements/NoSwitchRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Statements/NoSwitchRule.php new file mode 100644 index 00000000000..127e5eed3e0 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Statements/NoSwitchRule.php @@ -0,0 +1,41 @@ + + */ +final class NoSwitchRule implements Rules\Rule +{ + public function getNodeType(): string + { + return Node\Stmt\Switch_::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + return [ + Rules\RuleErrorBuilder::message('Control structures using switch should not be used.') + ->identifier(ErrorIdentifier::noSwitch()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/nette/utils/.phpstorm.meta.php b/tools/.phpstan/vendor/nette/utils/.phpstorm.meta.php new file mode 100644 index 00000000000..25851af66c0 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/.phpstorm.meta.php @@ -0,0 +1,13 @@ + +✅ [Callback](https://doc.nette.org/utils/callback) - PHP callbacks
    +✅ [Filesystem](https://doc.nette.org/utils/filesystem) - copying, renaming, …
    +✅ [Finder](https://doc.nette.org/utils/finder) - finds files and directories
    +✅ [Floats](https://doc.nette.org/utils/floats) - floating point numbers
    +✅ [Helper Functions](https://doc.nette.org/utils/helpers)
    +✅ [HTML elements](https://doc.nette.org/utils/html-elements) - generate HTML
    +✅ [Images](https://doc.nette.org/utils/images) - crop, resize, rotate images
    +✅ [Iterables](https://doc.nette.org/utils/iterables)
    +✅ [JSON](https://doc.nette.org/utils/json) - encoding and decoding
    +✅ [Generating Random Strings](https://doc.nette.org/utils/random)
    +✅ [Paginator](https://doc.nette.org/utils/paginator) - pagination math
    +✅ [PHP Reflection](https://doc.nette.org/utils/reflection)
    +✅ [Strings](https://doc.nette.org/utils/strings) - useful text functions
    +✅ [SmartObject](https://doc.nette.org/utils/smartobject) - PHP object enhancements
    +✅ [Type](https://doc.nette.org/utils/type) - PHP data type
    +✅ [Validation](https://doc.nette.org/utils/validators) - validate inputs
    + +  + +Installation +------------ + +The recommended way to install is via Composer: + +``` +composer require nette/utils +``` + +Nette Utils 4.0 is compatible with PHP 8.0 to 8.5. + +  + +[Support Me](https://github.com/sponsors/dg) +-------------------------------------------- + +Do you like Nette Utils? Are you looking forward to the new features? + +[![Buy me a coffee](https://files.nette.org/icons/donation-3.svg)](https://github.com/sponsors/dg) + +Thank you! diff --git a/tools/.phpstan/vendor/nette/utils/src/HtmlStringable.php b/tools/.phpstan/vendor/nette/utils/src/HtmlStringable.php new file mode 100644 index 00000000000..d749d4ee8cd --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/HtmlStringable.php @@ -0,0 +1,22 @@ +counter === 1 || ($gridWidth && $this->counter !== 0 && (($this->counter - 1) % $gridWidth) === 0); + } + + + /** + * Is the current element the last one? + */ + public function isLast(?int $gridWidth = null): bool + { + return !$this->hasNext() || ($gridWidth && ($this->counter % $gridWidth) === 0); + } + + + /** + * Is the iterator empty? + */ + public function isEmpty(): bool + { + return $this->counter === 0; + } + + + /** + * Is the counter odd? + */ + public function isOdd(): bool + { + return $this->counter % 2 === 1; + } + + + /** + * Is the counter even? + */ + public function isEven(): bool + { + return $this->counter % 2 === 0; + } + + + /** + * Returns the counter. + */ + public function getCounter(): int + { + return $this->counter; + } + + + /** + * Returns the count of elements. + */ + public function count(): int + { + $inner = $this->getInnerIterator(); + if ($inner instanceof \Countable) { + return $inner->count(); + + } else { + throw new Nette\NotSupportedException('Iterator is not countable.'); + } + } + + + /** + * Forwards to the next element. + */ + public function next(): void + { + parent::next(); + if (parent::valid()) { + $this->counter++; + } + } + + + /** + * Rewinds the Iterator. + */ + public function rewind(): void + { + parent::rewind(); + $this->counter = parent::valid() ? 1 : 0; + } + + + /** + * Returns the next key. + */ + public function getNextKey(): mixed + { + return $this->getInnerIterator()->key(); + } + + + /** + * Returns the next element. + */ + public function getNextValue(): mixed + { + return $this->getInnerIterator()->current(); + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Iterators/Mapper.php b/tools/.phpstan/vendor/nette/utils/src/Iterators/Mapper.php new file mode 100644 index 00000000000..284da29da4c --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Iterators/Mapper.php @@ -0,0 +1,33 @@ +callback = $callback; + } + + + public function current(): mixed + { + return ($this->callback)(parent::current(), parent::key()); + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/SmartObject.php b/tools/.phpstan/vendor/nette/utils/src/SmartObject.php new file mode 100644 index 00000000000..3b2203f1f74 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/SmartObject.php @@ -0,0 +1,140 @@ +$name ?? null; + if (is_iterable($handlers)) { + foreach ($handlers as $handler) { + $handler(...$args); + } + } elseif ($handlers !== null) { + throw new UnexpectedValueException("Property $class::$$name must be iterable or null, " . get_debug_type($handlers) . ' given.'); + } + + return null; + } + + ObjectHelpers::strictCall($class, $name); + } + + + /** + * @throws MemberAccessException + */ + public static function __callStatic(string $name, array $args) + { + ObjectHelpers::strictStaticCall(static::class, $name); + } + + + /** + * @return mixed + * @throws MemberAccessException if the property is not defined. + */ + public function &__get(string $name) + { + $class = static::class; + + if ($prop = ObjectHelpers::getMagicProperties($class)[$name] ?? null) { // property getter + if (!($prop & 0b0001)) { + throw new MemberAccessException("Cannot read a write-only property $class::\$$name."); + } + + $m = ($prop & 0b0010 ? 'get' : 'is') . ucfirst($name); + if ($prop & 0b10000) { + $trace = debug_backtrace(0, 1)[0]; // suppose this method is called from __call() + $loc = isset($trace['file'], $trace['line']) + ? " in $trace[file] on line $trace[line]" + : ''; + trigger_error("Property $class::\$$name is deprecated, use $class::$m() method$loc.", E_USER_DEPRECATED); + } + + if ($prop & 0b0100) { // return by reference + return $this->$m(); + } else { + $val = $this->$m(); + return $val; + } + } else { + ObjectHelpers::strictGet($class, $name); + } + } + + + /** + * @throws MemberAccessException if the property is not defined or is read-only + */ + public function __set(string $name, mixed $value): void + { + $class = static::class; + + if (ObjectHelpers::hasProperty($class, $name)) { // unsetted property + $this->$name = $value; + + } elseif ($prop = ObjectHelpers::getMagicProperties($class)[$name] ?? null) { // property setter + if (!($prop & 0b1000)) { + throw new MemberAccessException("Cannot write to a read-only property $class::\$$name."); + } + + $m = 'set' . ucfirst($name); + if ($prop & 0b10000) { + $trace = debug_backtrace(0, 1)[0]; // suppose this method is called from __call() + $loc = isset($trace['file'], $trace['line']) + ? " in $trace[file] on line $trace[line]" + : ''; + trigger_error("Property $class::\$$name is deprecated, use $class::$m() method$loc.", E_USER_DEPRECATED); + } + + $this->$m($value); + + } else { + ObjectHelpers::strictSet($class, $name); + } + } + + + /** + * @throws MemberAccessException + */ + public function __unset(string $name): void + { + $class = static::class; + if (!ObjectHelpers::hasProperty($class, $name)) { + throw new MemberAccessException("Cannot unset the property $class::\$$name."); + } + } + + + public function __isset(string $name): bool + { + return isset(ObjectHelpers::getMagicProperties(static::class)[$name]); + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/StaticClass.php b/tools/.phpstan/vendor/nette/utils/src/StaticClass.php new file mode 100644 index 00000000000..b1d84862eed --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/StaticClass.php @@ -0,0 +1,34 @@ + + * @implements \ArrayAccess + */ +class ArrayHash extends \stdClass implements \ArrayAccess, \Countable, \IteratorAggregate +{ + /** + * Transforms array to ArrayHash. + * @param array $array + */ + public static function from(array $array, bool $recursive = true): static + { + $obj = new static; + foreach ($array as $key => $value) { + $obj->$key = $recursive && is_array($value) + ? static::from($value) + : $value; + } + + return $obj; + } + + + /** + * Returns an iterator over all items. + * @return \Iterator + */ + public function &getIterator(): \Iterator + { + foreach ((array) $this as $key => $foo) { + yield $key => $this->$key; + } + } + + + /** + * Returns items count. + */ + public function count(): int + { + return count((array) $this); + } + + + /** + * Replaces or appends an item. + * @param array-key $key + * @param T $value + */ + public function offsetSet($key, $value): void + { + if (!is_scalar($key)) { // prevents null + throw new Nette\InvalidArgumentException(sprintf('Key must be either a string or an integer, %s given.', get_debug_type($key))); + } + + $this->$key = $value; + } + + + /** + * Returns an item. + * @param array-key $key + * @return T + */ + #[\ReturnTypeWillChange] + public function offsetGet($key) + { + return $this->$key; + } + + + /** + * Determines whether an item exists. + * @param array-key $key + */ + public function offsetExists($key): bool + { + return isset($this->$key); + } + + + /** + * Removes the element from this list. + * @param array-key $key + */ + public function offsetUnset($key): void + { + unset($this->$key); + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/ArrayList.php b/tools/.phpstan/vendor/nette/utils/src/Utils/ArrayList.php new file mode 100644 index 00000000000..c9fe5386f85 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/ArrayList.php @@ -0,0 +1,137 @@ + + * @implements \ArrayAccess + */ +class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate +{ + use Nette\SmartObject; + + private array $list = []; + + + /** + * Transforms array to ArrayList. + * @param list $array + */ + public static function from(array $array): static + { + if (!Arrays::isList($array)) { + throw new Nette\InvalidArgumentException('Array is not valid list.'); + } + + $obj = new static; + $obj->list = $array; + return $obj; + } + + + /** + * Returns an iterator over all items. + * @return \Iterator + */ + public function &getIterator(): \Iterator + { + foreach ($this->list as &$item) { + yield $item; + } + } + + + /** + * Returns items count. + */ + public function count(): int + { + return count($this->list); + } + + + /** + * Replaces or appends an item. + * @param int|null $index + * @param T $value + * @throws Nette\OutOfRangeException + */ + public function offsetSet($index, $value): void + { + if ($index === null) { + $this->list[] = $value; + + } elseif (!is_int($index) || $index < 0 || $index >= count($this->list)) { + throw new Nette\OutOfRangeException('Offset invalid or out of range'); + + } else { + $this->list[$index] = $value; + } + } + + + /** + * Returns an item. + * @param int $index + * @return T + * @throws Nette\OutOfRangeException + */ + public function offsetGet($index): mixed + { + if (!is_int($index) || $index < 0 || $index >= count($this->list)) { + throw new Nette\OutOfRangeException('Offset invalid or out of range'); + } + + return $this->list[$index]; + } + + + /** + * Determines whether an item exists. + * @param int $index + */ + public function offsetExists($index): bool + { + return is_int($index) && $index >= 0 && $index < count($this->list); + } + + + /** + * Removes the element at the specified position in this list. + * @param int $index + * @throws Nette\OutOfRangeException + */ + public function offsetUnset($index): void + { + if (!is_int($index) || $index < 0 || $index >= count($this->list)) { + throw new Nette\OutOfRangeException('Offset invalid or out of range'); + } + + array_splice($this->list, $index, 1); + } + + + /** + * Prepends an item. + * @param T $value + */ + public function prepend(mixed $value): void + { + $first = array_slice($this->list, 0, 1); + $this->offsetSet(0, $value); + array_splice($this->list, 1, 0, $first); + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Arrays.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Arrays.php new file mode 100644 index 00000000000..8985a707812 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Arrays.php @@ -0,0 +1,555 @@ + $array + * @param array-key|array-key[] $key + * @param ?T $default + * @return ?T + * @throws Nette\InvalidArgumentException if item does not exist and default value is not provided + */ + public static function get(array $array, string|int|array $key, mixed $default = null): mixed + { + foreach (is_array($key) ? $key : [$key] as $k) { + if (is_array($array) && array_key_exists($k, $array)) { + $array = $array[$k]; + } else { + if (func_num_args() < 3) { + throw new Nette\InvalidArgumentException("Missing item '$k'."); + } + + return $default; + } + } + + return $array; + } + + + /** + * Returns reference to array item. If the index does not exist, new one is created with value null. + * @template T + * @param array $array + * @param array-key|array-key[] $key + * @return ?T + * @throws Nette\InvalidArgumentException if traversed item is not an array + */ + public static function &getRef(array &$array, string|int|array $key): mixed + { + foreach (is_array($key) ? $key : [$key] as $k) { + if (is_array($array) || $array === null) { + $array = &$array[$k]; + } else { + throw new Nette\InvalidArgumentException('Traversed item is not an array.'); + } + } + + return $array; + } + + + /** + * Recursively merges two fields. It is useful, for example, for merging tree structures. It behaves as + * the + operator for array, ie. it adds a key/value pair from the second array to the first one and retains + * the value from the first array in the case of a key collision. + * @template T1 + * @template T2 + * @param array $array1 + * @param array $array2 + * @return array + */ + public static function mergeTree(array $array1, array $array2): array + { + $res = $array1 + $array2; + foreach (array_intersect_key($array1, $array2) as $k => $v) { + if (is_array($v) && is_array($array2[$k])) { + $res[$k] = self::mergeTree($v, $array2[$k]); + } + } + + return $res; + } + + + /** + * Returns zero-indexed position of given array key. Returns null if key is not found. + */ + public static function getKeyOffset(array $array, string|int $key): ?int + { + return Helpers::falseToNull(array_search(self::toKey($key), array_keys($array), strict: true)); + } + + + /** + * @deprecated use getKeyOffset() + */ + public static function searchKey(array $array, $key): ?int + { + return self::getKeyOffset($array, $key); + } + + + /** + * Tests an array for the presence of value. + */ + public static function contains(array $array, mixed $value): bool + { + return in_array($value, $array, true); + } + + + /** + * Returns the first item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null. + * @template K of int|string + * @template V + * @param array $array + * @param ?callable(V, K, array): bool $predicate + * @return ?V + */ + public static function first(array $array, ?callable $predicate = null, ?callable $else = null): mixed + { + $key = self::firstKey($array, $predicate); + return $key === null + ? ($else ? $else() : null) + : $array[$key]; + } + + + /** + * Returns the last item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null. + * @template K of int|string + * @template V + * @param array $array + * @param ?callable(V, K, array): bool $predicate + * @return ?V + */ + public static function last(array $array, ?callable $predicate = null, ?callable $else = null): mixed + { + $key = self::lastKey($array, $predicate); + return $key === null + ? ($else ? $else() : null) + : $array[$key]; + } + + + /** + * Returns the key of first item (matching the specified predicate if given) or null if there is no such item. + * @template K of int|string + * @template V + * @param array $array + * @param ?callable(V, K, array): bool $predicate + * @return ?K + */ + public static function firstKey(array $array, ?callable $predicate = null): int|string|null + { + if (!$predicate) { + return array_key_first($array); + } + foreach ($array as $k => $v) { + if ($predicate($v, $k, $array)) { + return $k; + } + } + return null; + } + + + /** + * Returns the key of last item (matching the specified predicate if given) or null if there is no such item. + * @template K of int|string + * @template V + * @param array $array + * @param ?callable(V, K, array): bool $predicate + * @return ?K + */ + public static function lastKey(array $array, ?callable $predicate = null): int|string|null + { + return $predicate + ? self::firstKey(array_reverse($array, preserve_keys: true), $predicate) + : array_key_last($array); + } + + + /** + * Inserts the contents of the $inserted array into the $array immediately after the $key. + * If $key is null (or does not exist), it is inserted at the beginning. + */ + public static function insertBefore(array &$array, string|int|null $key, array $inserted): void + { + $offset = $key === null ? 0 : (int) self::getKeyOffset($array, $key); + $array = array_slice($array, 0, $offset, preserve_keys: true) + + $inserted + + array_slice($array, $offset, count($array), preserve_keys: true); + } + + + /** + * Inserts the contents of the $inserted array into the $array before the $key. + * If $key is null (or does not exist), it is inserted at the end. + */ + public static function insertAfter(array &$array, string|int|null $key, array $inserted): void + { + if ($key === null || ($offset = self::getKeyOffset($array, $key)) === null) { + $offset = count($array) - 1; + } + + $array = array_slice($array, 0, $offset + 1, preserve_keys: true) + + $inserted + + array_slice($array, $offset + 1, count($array), preserve_keys: true); + } + + + /** + * Renames key in array. + */ + public static function renameKey(array &$array, string|int $oldKey, string|int $newKey): bool + { + $offset = self::getKeyOffset($array, $oldKey); + if ($offset === null) { + return false; + } + + $val = &$array[$oldKey]; + $keys = array_keys($array); + $keys[$offset] = $newKey; + $array = array_combine($keys, $array); + $array[$newKey] = &$val; + return true; + } + + + /** + * Returns only those array items, which matches a regular expression $pattern. + * @param string[] $array + * @return string[] + */ + public static function grep( + array $array, + #[Language('RegExp')] + string $pattern, + bool|int $invert = false, + ): array + { + $flags = $invert ? PREG_GREP_INVERT : 0; + return Strings::pcre('preg_grep', [$pattern, $array, $flags]); + } + + + /** + * Transforms multidimensional array to flat array. + */ + public static function flatten(array $array, bool $preserveKeys = false): array + { + $res = []; + $cb = $preserveKeys + ? function ($v, $k) use (&$res): void { $res[$k] = $v; } + : function ($v) use (&$res): void { $res[] = $v; }; + array_walk_recursive($array, $cb); + return $res; + } + + + /** + * Checks if the array is indexed in ascending order of numeric keys from zero, a.k.a list. + * @return ($value is list ? true : false) + */ + public static function isList(mixed $value): bool + { + return is_array($value) && ( + PHP_VERSION_ID < 80100 + ? !$value || array_keys($value) === range(0, count($value) - 1) + : array_is_list($value) + ); + } + + + /** + * Reformats table to associative tree. Path looks like 'field|field[]field->field=field'. + * @param string|string[] $path + */ + public static function associate(array $array, $path): array|\stdClass + { + $parts = is_array($path) + ? $path + : preg_split('#(\[\]|->|=|\|)#', $path, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + + if (!$parts || $parts === ['->'] || $parts[0] === '=' || $parts[0] === '|') { + throw new Nette\InvalidArgumentException("Invalid path '$path'."); + } + + $res = $parts[0] === '->' ? new \stdClass : []; + + foreach ($array as $rowOrig) { + $row = (array) $rowOrig; + $x = &$res; + + for ($i = 0; $i < count($parts); $i++) { + $part = $parts[$i]; + if ($part === '[]') { + $x = &$x[]; + + } elseif ($part === '=') { + if (isset($parts[++$i])) { + $x = $row[$parts[$i]]; + $row = null; + } + } elseif ($part === '->') { + if (isset($parts[++$i])) { + if ($x === null) { + $x = new \stdClass; + } + + $x = &$x->{$row[$parts[$i]]}; + } else { + $row = is_object($rowOrig) ? $rowOrig : (object) $row; + } + } elseif ($part !== '|') { + $x = &$x[(string) $row[$part]]; + } + } + + if ($x === null) { + $x = $row; + } + } + + return $res; + } + + + /** + * Normalizes array to associative array. Replace numeric keys with their values, the new value will be $filling. + */ + public static function normalize(array $array, mixed $filling = null): array + { + $res = []; + foreach ($array as $k => $v) { + $res[is_int($k) ? $v : $k] = is_int($k) ? $filling : $v; + } + + return $res; + } + + + /** + * Returns and removes the value of an item from an array. If it does not exist, it throws an exception, + * or returns $default, if provided. + * @template T + * @param array $array + * @param ?T $default + * @return ?T + * @throws Nette\InvalidArgumentException if item does not exist and default value is not provided + */ + public static function pick(array &$array, string|int $key, mixed $default = null): mixed + { + if (array_key_exists($key, $array)) { + $value = $array[$key]; + unset($array[$key]); + return $value; + + } elseif (func_num_args() < 3) { + throw new Nette\InvalidArgumentException("Missing item '$key'."); + + } else { + return $default; + } + } + + + /** + * Tests whether at least one element in the array passes the test implemented by the provided function. + * @template K of int|string + * @template V + * @param array $array + * @param callable(V, K, array): bool $predicate + */ + public static function some(iterable $array, callable $predicate): bool + { + foreach ($array as $k => $v) { + if ($predicate($v, $k, $array)) { + return true; + } + } + + return false; + } + + + /** + * Tests whether all elements in the array pass the test implemented by the provided function. + * @template K of int|string + * @template V + * @param array $array + * @param callable(V, K, array): bool $predicate + */ + public static function every(iterable $array, callable $predicate): bool + { + foreach ($array as $k => $v) { + if (!$predicate($v, $k, $array)) { + return false; + } + } + + return true; + } + + + /** + * Returns a new array containing all key-value pairs matching the given $predicate. + * @template K of int|string + * @template V + * @param array $array + * @param callable(V, K, array): bool $predicate + * @return array + */ + public static function filter(array $array, callable $predicate): array + { + $res = []; + foreach ($array as $k => $v) { + if ($predicate($v, $k, $array)) { + $res[$k] = $v; + } + } + return $res; + } + + + /** + * Returns an array containing the original keys and results of applying the given transform function to each element. + * @template K of int|string + * @template V + * @template R + * @param array $array + * @param callable(V, K, array): R $transformer + * @return array + */ + public static function map(iterable $array, callable $transformer): array + { + $res = []; + foreach ($array as $k => $v) { + $res[$k] = $transformer($v, $k, $array); + } + + return $res; + } + + + /** + * Returns an array containing new keys and values generated by applying the given transform function to each element. + * If the function returns null, the element is skipped. + * @template K of int|string + * @template V + * @template ResK of int|string + * @template ResV + * @param array $array + * @param callable(V, K, array): ?array{ResK, ResV} $transformer + * @return array + */ + public static function mapWithKeys(array $array, callable $transformer): array + { + $res = []; + foreach ($array as $k => $v) { + $pair = $transformer($v, $k, $array); + if ($pair) { + $res[$pair[0]] = $pair[1]; + } + } + + return $res; + } + + + /** + * Invokes all callbacks and returns array of results. + * @param callable[] $callbacks + */ + public static function invoke(iterable $callbacks, ...$args): array + { + $res = []; + foreach ($callbacks as $k => $cb) { + $res[$k] = $cb(...$args); + } + + return $res; + } + + + /** + * Invokes method on every object in an array and returns array of results. + * @param object[] $objects + */ + public static function invokeMethod(iterable $objects, string $method, ...$args): array + { + $res = []; + foreach ($objects as $k => $obj) { + $res[$k] = $obj->$method(...$args); + } + + return $res; + } + + + /** + * Copies the elements of the $array array to the $object object and then returns it. + * @template T of object + * @param T $object + * @return T + */ + public static function toObject(iterable $array, object $object): object + { + foreach ($array as $k => $v) { + $object->$k = $v; + } + + return $object; + } + + + /** + * Converts value to array key. + */ + public static function toKey(mixed $value): int|string + { + return key([$value => null]); + } + + + /** + * Returns copy of the $array where every item is converted to string + * and prefixed by $prefix and suffixed by $suffix. + * @param string[] $array + * @return string[] + */ + public static function wrap(array $array, string $prefix = '', string $suffix = ''): array + { + $res = []; + foreach ($array as $k => $v) { + $res[$k] = $prefix . $v . $suffix; + } + + return $res; + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Callback.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Callback.php new file mode 100644 index 00000000000..7d384f25e61 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Callback.php @@ -0,0 +1,137 @@ +getClosureScopeClass()?->name; + if (str_ends_with($r->name, '}')) { + return $closure; + + } elseif (($obj = $r->getClosureThis()) && $obj::class === $class) { + return [$obj, $r->name]; + + } elseif ($class) { + return [$class, $r->name]; + + } else { + return $r->name; + } + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/DateTime.php b/tools/.phpstan/vendor/nette/utils/src/Utils/DateTime.php new file mode 100644 index 00000000000..cb59682fd81 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/DateTime.php @@ -0,0 +1,219 @@ +setTimestamp((int) $time); + + } else { // textual or null + return new static((string) $time); + } + } + + + /** + * Creates DateTime object. + * @throws Nette\InvalidArgumentException if the date and time are not valid. + */ + public static function fromParts( + int $year, + int $month, + int $day, + int $hour = 0, + int $minute = 0, + float $second = 0.0, + ): static + { + $s = sprintf('%04d-%02d-%02d %02d:%02d:%02.5F', $year, $month, $day, $hour, $minute, $second); + if ( + !checkdate($month, $day, $year) + || $hour < 0 || $hour > 23 + || $minute < 0 || $minute > 59 + || $second < 0 || $second >= 60 + ) { + throw new Nette\InvalidArgumentException("Invalid date '$s'"); + } + + return new static($s); + } + + + /** + * Returns a new DateTime object formatted according to the specified format. + */ + public static function createFromFormat( + string $format, + string $datetime, + string|\DateTimeZone|null $timezone = null, + ): static|false + { + if (is_string($timezone)) { + $timezone = new \DateTimeZone($timezone); + } + + $date = parent::createFromFormat($format, $datetime, $timezone); + return $date ? static::from($date) : false; + } + + + public function __construct(string $datetime = 'now', ?\DateTimeZone $timezone = null) + { + $this->apply($datetime, $timezone, true); + } + + + public function modify(string $modifier): static + { + $this->apply($modifier); + return $this; + } + + + public function setDate(int $year, int $month, int $day): static + { + if (!checkdate($month, $day, $year)) { + trigger_error(sprintf(self::class . ': The date %04d-%02d-%02d is not valid.', $year, $month, $day), E_USER_WARNING); + } + return parent::setDate($year, $month, $day); + } + + + public function setTime(int $hour, int $minute, int $second = 0, int $microsecond = 0): static + { + if ( + $hour < 0 || $hour > 23 + || $minute < 0 || $minute > 59 + || $second < 0 || $second >= 60 + || $microsecond < 0 || $microsecond >= 1_000_000 + ) { + trigger_error(sprintf(self::class . ': The time %02d:%02d:%08.5F is not valid.', $hour, $minute, $second + $microsecond / 1_000_000), E_USER_WARNING); + } + return parent::setTime($hour, $minute, $second, $microsecond); + } + + + /** + * Converts a relative time string (e.g. '10 minut') to seconds. + */ + public static function relativeToSeconds(string $relativeTime): int + { + return (new self('@0 ' . $relativeTime)) + ->getTimestamp(); + } + + + private function apply(string $datetime, $timezone = null, bool $ctr = false): void + { + $relPart = ''; + $absPart = preg_replace_callback( + '/[+-]?\s*\d+\s+((microsecond|millisecond|[mµu]sec)s?|[mµ]s|sec(ond)?s?|min(ute)?s?|hours?)(\s+ago)?\b/iu', + function ($m) use (&$relPart) { + $relPart .= $m[0] . ' '; + return ''; + }, + $datetime, + ); + + if ($ctr) { + parent::__construct($absPart, $timezone); + $this->handleErrors($datetime); + } elseif (trim($absPart)) { + parent::modify($absPart) && $this->handleErrors($datetime); + } + + if ($relPart) { + $timezone ??= $this->getTimezone(); + $this->setTimezone(new \DateTimeZone('UTC')); + parent::modify($relPart) && $this->handleErrors($datetime); + $this->setTimezone($timezone); + } + } + + + /** + * Returns JSON representation in ISO 8601 (used by JavaScript). + */ + public function jsonSerialize(): string + { + return $this->format('c'); + } + + + /** + * Returns the date and time in the format 'Y-m-d H:i:s'. + */ + public function __toString(): string + { + return $this->format('Y-m-d H:i:s'); + } + + + /** + * You'd better use: (clone $dt)->modify(...) + */ + public function modifyClone(string $modify = ''): static + { + $dolly = clone $this; + return $modify ? $dolly->modify($modify) : $dolly; + } + + + private function handleErrors(string $value): void + { + $errors = self::getLastErrors(); + $errors = array_merge($errors['errors'] ?? [], $errors['warnings'] ?? []); + if ($errors) { + trigger_error(self::class . ': ' . implode(', ', $errors) . " '$value'", E_USER_WARNING); + } + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/FileInfo.php b/tools/.phpstan/vendor/nette/utils/src/Utils/FileInfo.php new file mode 100644 index 00000000000..a102dad104e --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/FileInfo.php @@ -0,0 +1,70 @@ +setInfoClass(static::class); + $this->relativePath = $relativePath; + } + + + /** + * Returns the relative directory path. + */ + public function getRelativePath(): string + { + return $this->relativePath; + } + + + /** + * Returns the relative path including file name. + */ + public function getRelativePathname(): string + { + return ($this->relativePath === '' ? '' : $this->relativePath . DIRECTORY_SEPARATOR) + . $this->getBasename(); + } + + + /** + * Returns the contents of the file. + * @throws Nette\IOException + */ + public function read(): string + { + return FileSystem::read($this->getPathname()); + } + + + /** + * Writes the contents to the file. + * @throws Nette\IOException + */ + public function write(string $content): void + { + FileSystem::write($this->getPathname(), $content); + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/FileSystem.php b/tools/.phpstan/vendor/nette/utils/src/Utils/FileSystem.php new file mode 100644 index 00000000000..a95a7f74138 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/FileSystem.php @@ -0,0 +1,341 @@ +getPathname()); + } + + foreach ($iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($origin, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST) as $item) { + if ($item->isDir()) { + static::createDir($target . '/' . $iterator->getSubPathName()); + } else { + static::copy($item->getPathname(), $target . '/' . $iterator->getSubPathName()); + } + } + } else { + static::createDir(dirname($target)); + if (@stream_copy_to_stream(static::open($origin, 'rb'), static::open($target, 'wb')) === false) { // @ is escalated to exception + throw new Nette\IOException(sprintf( + "Unable to copy file '%s' to '%s'. %s", + self::normalizePath($origin), + self::normalizePath($target), + Helpers::getLastError(), + )); + } + } + } + + + /** + * Opens file and returns resource. + * @return resource + * @throws Nette\IOException on error occurred + */ + public static function open(string $path, string $mode) + { + $f = @fopen($path, $mode); // @ is escalated to exception + if (!$f) { + throw new Nette\IOException(sprintf( + "Unable to open file '%s'. %s", + self::normalizePath($path), + Helpers::getLastError(), + )); + } + return $f; + } + + + /** + * Deletes a file or an entire directory if exists. If the directory is not empty, it deletes its contents first. + * @throws Nette\IOException on error occurred + */ + public static function delete(string $path): void + { + if (is_file($path) || is_link($path)) { + $func = DIRECTORY_SEPARATOR === '\\' && is_dir($path) ? 'rmdir' : 'unlink'; + if (!@$func($path)) { // @ is escalated to exception + throw new Nette\IOException(sprintf( + "Unable to delete '%s'. %s", + self::normalizePath($path), + Helpers::getLastError(), + )); + } + } elseif (is_dir($path)) { + foreach (new \FilesystemIterator($path) as $item) { + static::delete($item->getPathname()); + } + + if (!@rmdir($path)) { // @ is escalated to exception + throw new Nette\IOException(sprintf( + "Unable to delete directory '%s'. %s", + self::normalizePath($path), + Helpers::getLastError(), + )); + } + } + } + + + /** + * Renames or moves a file or a directory. Overwrites existing files and directories by default. + * @throws Nette\IOException on error occurred + * @throws Nette\InvalidStateException if $overwrite is set to false and destination already exists + */ + public static function rename(string $origin, string $target, bool $overwrite = true): void + { + if (!$overwrite && file_exists($target)) { + throw new Nette\InvalidStateException(sprintf("File or directory '%s' already exists.", self::normalizePath($target))); + + } elseif (!file_exists($origin)) { + throw new Nette\IOException(sprintf("File or directory '%s' not found.", self::normalizePath($origin))); + + } else { + static::createDir(dirname($target)); + if (realpath($origin) !== realpath($target)) { + static::delete($target); + } + + if (!@rename($origin, $target)) { // @ is escalated to exception + throw new Nette\IOException(sprintf( + "Unable to rename file or directory '%s' to '%s'. %s", + self::normalizePath($origin), + self::normalizePath($target), + Helpers::getLastError(), + )); + } + } + } + + + /** + * Reads the content of a file. + * @throws Nette\IOException on error occurred + */ + public static function read(string $file): string + { + $content = @file_get_contents($file); // @ is escalated to exception + if ($content === false) { + throw new Nette\IOException(sprintf( + "Unable to read file '%s'. %s", + self::normalizePath($file), + Helpers::getLastError(), + )); + } + + return $content; + } + + + /** + * Reads the file content line by line. Because it reads continuously as we iterate over the lines, + * it is possible to read files larger than the available memory. + * @return \Generator + * @throws Nette\IOException on error occurred + */ + public static function readLines(string $file, bool $stripNewLines = true): \Generator + { + return (function ($f) use ($file, $stripNewLines) { + $counter = 0; + do { + $line = Callback::invokeSafe('fgets', [$f], fn($error) => throw new Nette\IOException(sprintf( + "Unable to read file '%s'. %s", + self::normalizePath($file), + $error, + ))); + if ($line === false) { + fclose($f); + break; + } + if ($stripNewLines) { + $line = rtrim($line, "\r\n"); + } + + yield $counter++ => $line; + + } while (true); + })(static::open($file, 'r')); + } + + + /** + * Writes the string to a file. + * @throws Nette\IOException on error occurred + */ + public static function write(string $file, string $content, ?int $mode = 0666): void + { + static::createDir(dirname($file)); + if (@file_put_contents($file, $content) === false) { // @ is escalated to exception + throw new Nette\IOException(sprintf( + "Unable to write file '%s'. %s", + self::normalizePath($file), + Helpers::getLastError(), + )); + } + + if ($mode !== null && !@chmod($file, $mode)) { // @ is escalated to exception + throw new Nette\IOException(sprintf( + "Unable to chmod file '%s' to mode %s. %s", + self::normalizePath($file), + decoct($mode), + Helpers::getLastError(), + )); + } + } + + + /** + * Sets file permissions to `$fileMode` or directory permissions to `$dirMode`. + * Recursively traverses and sets permissions on the entire contents of the directory as well. + * @throws Nette\IOException on error occurred + */ + public static function makeWritable(string $path, int $dirMode = 0777, int $fileMode = 0666): void + { + if (is_file($path)) { + if (!@chmod($path, $fileMode)) { // @ is escalated to exception + throw new Nette\IOException(sprintf( + "Unable to chmod file '%s' to mode %s. %s", + self::normalizePath($path), + decoct($fileMode), + Helpers::getLastError(), + )); + } + } elseif (is_dir($path)) { + foreach (new \FilesystemIterator($path) as $item) { + static::makeWritable($item->getPathname(), $dirMode, $fileMode); + } + + if (!@chmod($path, $dirMode)) { // @ is escalated to exception + throw new Nette\IOException(sprintf( + "Unable to chmod directory '%s' to mode %s. %s", + self::normalizePath($path), + decoct($dirMode), + Helpers::getLastError(), + )); + } + } else { + throw new Nette\IOException(sprintf("File or directory '%s' not found.", self::normalizePath($path))); + } + } + + + /** + * Determines if the path is absolute. + */ + public static function isAbsolute(string $path): bool + { + return (bool) preg_match('#([a-z]:)?[/\\\]|[a-z][a-z0-9+.-]*://#Ai', $path); + } + + + /** + * Normalizes `..` and `.` and directory separators in path. + */ + public static function normalizePath(string $path): string + { + $parts = $path === '' ? [] : preg_split('~[/\\\]+~', $path); + $res = []; + foreach ($parts as $part) { + if ($part === '..' && $res && end($res) !== '..' && end($res) !== '') { + array_pop($res); + } elseif ($part !== '.') { + $res[] = $part; + } + } + + return $res === [''] + ? DIRECTORY_SEPARATOR + : implode(DIRECTORY_SEPARATOR, $res); + } + + + /** + * Joins all segments of the path and normalizes the result. + */ + public static function joinPaths(string ...$paths): string + { + return self::normalizePath(implode('/', $paths)); + } + + + /** + * Resolves a path against a base path. If the path is absolute, returns it directly, if it's relative, joins it with the base path. + */ + public static function resolvePath(string $basePath, string $path): string + { + return match (true) { + self::isAbsolute($path) => self::platformSlashes($path), + $path === '' => self::platformSlashes($basePath), + default => self::joinPaths($basePath, $path), + }; + } + + + /** + * Converts backslashes to slashes. + */ + public static function unixSlashes(string $path): string + { + return strtr($path, '\\', '/'); + } + + + /** + * Converts slashes to platform-specific directory separators. + */ + public static function platformSlashes(string $path): string + { + return DIRECTORY_SEPARATOR === '/' + ? strtr($path, '\\', '/') + : str_replace(':\\\\', '://', strtr($path, '/', '\\')); // protocol:// + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Finder.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Finder.php new file mode 100644 index 00000000000..0027e7743ca --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Finder.php @@ -0,0 +1,512 @@ +size('> 10kB') + * ->from('.') + * ->exclude('temp'); + * + * @implements \IteratorAggregate + */ +class Finder implements \IteratorAggregate +{ + use Nette\SmartObject; + + /** @var array */ + private array $find = []; + + /** @var string[] */ + private array $in = []; + + /** @var \Closure[] */ + private array $filters = []; + + /** @var \Closure[] */ + private array $descentFilters = []; + + /** @var array */ + private array $appends = []; + private bool $childFirst = false; + + /** @var ?callable */ + private $sort; + private int $maxDepth = -1; + private bool $ignoreUnreadableDirs = true; + + + /** + * Begins search for files and directories matching mask. + */ + public static function find(string|array $masks = ['*']): static + { + $masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic + return (new static)->addMask($masks, 'dir')->addMask($masks, 'file'); + } + + + /** + * Begins search for files matching mask. + */ + public static function findFiles(string|array $masks = ['*']): static + { + $masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic + return (new static)->addMask($masks, 'file'); + } + + + /** + * Begins search for directories matching mask. + */ + public static function findDirectories(string|array $masks = ['*']): static + { + $masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic + return (new static)->addMask($masks, 'dir'); + } + + + /** + * Finds files matching the specified masks. + */ + public function files(string|array $masks = ['*']): static + { + return $this->addMask((array) $masks, 'file'); + } + + + /** + * Finds directories matching the specified masks. + */ + public function directories(string|array $masks = ['*']): static + { + return $this->addMask((array) $masks, 'dir'); + } + + + private function addMask(array $masks, string $mode): static + { + foreach ($masks as $mask) { + $mask = FileSystem::unixSlashes($mask); + if ($mode === 'dir') { + $mask = rtrim($mask, '/'); + } + if ($mask === '' || ($mode === 'file' && str_ends_with($mask, '/'))) { + throw new Nette\InvalidArgumentException("Invalid mask '$mask'"); + } + if (str_starts_with($mask, '**/')) { + $mask = substr($mask, 3); + } + $this->find[] = [$mask, $mode]; + } + return $this; + } + + + /** + * Searches in the given directories. Wildcards are allowed. + */ + public function in(string|array $paths): static + { + $paths = is_array($paths) ? $paths : func_get_args(); // compatibility with variadic + $this->addLocation($paths, ''); + return $this; + } + + + /** + * Searches recursively from the given directories. Wildcards are allowed. + */ + public function from(string|array $paths): static + { + $paths = is_array($paths) ? $paths : func_get_args(); // compatibility with variadic + $this->addLocation($paths, '/**'); + return $this; + } + + + private function addLocation(array $paths, string $ext): void + { + foreach ($paths as $path) { + if ($path === '') { + throw new Nette\InvalidArgumentException("Invalid directory '$path'"); + } + $path = rtrim(FileSystem::unixSlashes($path), '/'); + $this->in[] = $path . $ext; + } + } + + + /** + * Lists directory's contents before the directory itself. By default, this is disabled. + */ + public function childFirst(bool $state = true): static + { + $this->childFirst = $state; + return $this; + } + + + /** + * Ignores unreadable directories. By default, this is enabled. + */ + public function ignoreUnreadableDirs(bool $state = true): static + { + $this->ignoreUnreadableDirs = $state; + return $this; + } + + + /** + * Set a compare function for sorting directory entries. The function will be called to sort entries from the same directory. + * @param callable(FileInfo, FileInfo): int $callback + */ + public function sortBy(callable $callback): static + { + $this->sort = $callback; + return $this; + } + + + /** + * Sorts files in each directory naturally by name. + */ + public function sortByName(): static + { + $this->sort = fn(FileInfo $a, FileInfo $b): int => strnatcmp($a->getBasename(), $b->getBasename()); + return $this; + } + + + /** + * Adds the specified paths or appends a new finder that returns. + */ + public function append(string|array|null $paths = null): static + { + if ($paths === null) { + return $this->appends[] = new static; + } + + $this->appends = array_merge($this->appends, (array) $paths); + return $this; + } + + + /********************* filtering ****************d*g**/ + + + /** + * Skips entries that matches the given masks relative to the ones defined with the in() or from() methods. + */ + public function exclude(string|array $masks): static + { + $masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic + foreach ($masks as $mask) { + $mask = FileSystem::unixSlashes($mask); + if (!preg_match('~^/?(\*\*/)?(.+)(/\*\*|/\*|/|)$~D', $mask, $m)) { + throw new Nette\InvalidArgumentException("Invalid mask '$mask'"); + } + $end = $m[3]; + $re = $this->buildPattern($m[2]); + $filter = fn(FileInfo $file): bool => ($end && !$file->isDir()) + || !preg_match($re, FileSystem::unixSlashes($file->getRelativePathname())); + + $this->descentFilter($filter); + if ($end !== '/*') { + $this->filter($filter); + } + } + + return $this; + } + + + /** + * Yields only entries which satisfy the given filter. + * @param callable(FileInfo): bool $callback + */ + public function filter(callable $callback): static + { + $this->filters[] = \Closure::fromCallable($callback); + return $this; + } + + + /** + * It descends only to directories that match the specified filter. + * @param callable(FileInfo): bool $callback + */ + public function descentFilter(callable $callback): static + { + $this->descentFilters[] = \Closure::fromCallable($callback); + return $this; + } + + + /** + * Sets the maximum depth of entries. + */ + public function limitDepth(?int $depth): static + { + $this->maxDepth = $depth ?? -1; + return $this; + } + + + /** + * Restricts the search by size. $operator accepts "[operator] [size] [unit]" example: >=10kB + */ + public function size(string $operator, ?int $size = null): static + { + if (func_num_args() === 1) { // in $operator is predicate + if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?((?:\d*\.)?\d+)\s*(K|M|G|)B?$#Di', $operator, $matches)) { + throw new Nette\InvalidArgumentException('Invalid size predicate format.'); + } + + [, $operator, $size, $unit] = $matches; + $units = ['' => 1, 'k' => 1e3, 'm' => 1e6, 'g' => 1e9]; + $size *= $units[strtolower($unit)]; + $operator = $operator ?: '='; + } + + return $this->filter(fn(FileInfo $file): bool => !$file->isFile() || Helpers::compare($file->getSize(), $operator, $size)); + } + + + /** + * Restricts the search by modified time. $operator accepts "[operator] [date]" example: >1978-01-23 + */ + public function date(string $operator, string|int|\DateTimeInterface|null $date = null): static + { + if (func_num_args() === 1) { // in $operator is predicate + if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?(.+)$#Di', $operator, $matches)) { + throw new Nette\InvalidArgumentException('Invalid date predicate format.'); + } + + [, $operator, $date] = $matches; + $operator = $operator ?: '='; + } + + $date = DateTime::from($date)->getTimestamp(); + return $this->filter(fn(FileInfo $file): bool => !$file->isFile() || Helpers::compare($file->getMTime(), $operator, $date)); + } + + + /********************* iterator generator ****************d*g**/ + + + /** + * Returns an array with all found files and directories. + * @return list + */ + public function collect(): array + { + return iterator_to_array($this->getIterator(), preserve_keys: false); + } + + + /** @return \Generator */ + public function getIterator(): \Generator + { + $plan = $this->buildPlan(); + foreach ($plan as $dir => $searches) { + yield from $this->traverseDir($dir, $searches); + } + + foreach ($this->appends as $item) { + if ($item instanceof self) { + yield from $item->getIterator(); + } else { + $item = FileSystem::platformSlashes($item); + yield $item => new FileInfo($item); + } + } + } + + + /** + * @param array $searches + * @param string[] $subdirs + * @return \Generator + */ + private function traverseDir(string $dir, array $searches, array $subdirs = []): \Generator + { + if ($this->maxDepth >= 0 && count($subdirs) > $this->maxDepth) { + return; + } elseif (!is_dir($dir)) { + throw new Nette\InvalidStateException(sprintf("Directory '%s' does not exist.", rtrim($dir, '/\\'))); + } + + try { + $pathNames = new \FilesystemIterator($dir, \FilesystemIterator::FOLLOW_SYMLINKS | \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::UNIX_PATHS); + } catch (\UnexpectedValueException $e) { + if ($this->ignoreUnreadableDirs) { + return; + } else { + throw new Nette\InvalidStateException($e->getMessage()); + } + } + + $files = $this->convertToFiles($pathNames, implode('/', $subdirs), FileSystem::isAbsolute($dir)); + + if ($this->sort) { + $files = iterator_to_array($files); + usort($files, $this->sort); + } + + foreach ($files as $file) { + $pathName = $file->getPathname(); + $cache = $subSearch = []; + + if ($file->isDir()) { + foreach ($searches as $search) { + if ($search->recursive && $this->proveFilters($this->descentFilters, $file, $cache)) { + $subSearch[] = $search; + } + } + } + + if ($this->childFirst && $subSearch) { + yield from $this->traverseDir($pathName, $subSearch, array_merge($subdirs, [$file->getBasename()])); + } + + $relativePathname = FileSystem::unixSlashes($file->getRelativePathname()); + foreach ($searches as $search) { + if ( + $file->{'is' . $search->mode}() + && preg_match($search->pattern, $relativePathname) + && $this->proveFilters($this->filters, $file, $cache) + ) { + yield $pathName => $file; + break; + } + } + + if (!$this->childFirst && $subSearch) { + yield from $this->traverseDir($pathName, $subSearch, array_merge($subdirs, [$file->getBasename()])); + } + } + } + + + private function convertToFiles(iterable $pathNames, string $relativePath, bool $absolute): \Generator + { + foreach ($pathNames as $pathName) { + if (!$absolute) { + $pathName = preg_replace('~\.?/~A', '', $pathName); + } + $pathName = FileSystem::platformSlashes($pathName); + yield new FileInfo($pathName, $relativePath); + } + } + + + private function proveFilters(array $filters, FileInfo $file, array &$cache): bool + { + foreach ($filters as $filter) { + $res = &$cache[spl_object_id($filter)]; + $res ??= $filter($file); + if (!$res) { + return false; + } + } + + return true; + } + + + /** @return array> */ + private function buildPlan(): array + { + $plan = $dirCache = []; + foreach ($this->find as [$mask, $mode]) { + $splits = []; + if (FileSystem::isAbsolute($mask)) { + if ($this->in) { + throw new Nette\InvalidStateException("You cannot combine the absolute path in the mask '$mask' and the directory to search '{$this->in[0]}'."); + } + $splits[] = self::splitRecursivePart($mask); + } else { + foreach ($this->in ?: ['.'] as $in) { + $in = strtr($in, ['[' => '[[]', ']' => '[]]']); // in path, do not treat [ and ] as a pattern by glob() + $splits[] = self::splitRecursivePart($in . '/' . $mask); + } + } + + foreach ($splits as [$base, $rest, $recursive]) { + $base = $base === '' ? '.' : $base; + $dirs = $dirCache[$base] ??= strpbrk($base, '*?[') + ? glob($base, GLOB_NOSORT | GLOB_ONLYDIR | GLOB_NOESCAPE) + : [strtr($base, ['[[]' => '[', '[]]' => ']'])]; // unescape [ and ] + + if (!$dirs) { + throw new Nette\InvalidStateException(sprintf("Directory '%s' does not exist.", rtrim($base, '/\\'))); + } + + $search = (object) ['pattern' => $this->buildPattern($rest), 'mode' => $mode, 'recursive' => $recursive]; + foreach ($dirs as $dir) { + $plan[$dir][] = $search; + } + } + } + + return $plan; + } + + + /** + * Since glob() does not know ** wildcard, we divide the path into a part for glob and a part for manual traversal. + */ + private static function splitRecursivePart(string $path): array + { + $a = strrpos($path, '/'); + $parts = preg_split('~(?<=^|/)\*\*($|/)~', substr($path, 0, $a + 1), 2); + return isset($parts[1]) + ? [$parts[0], $parts[1] . substr($path, $a + 1), true] + : [$parts[0], substr($path, $a + 1), false]; + } + + + /** + * Converts wildcards to regular expression. + */ + private function buildPattern(string $mask): string + { + if ($mask === '*') { + return '##'; + } elseif (str_starts_with($mask, './')) { + $anchor = '^'; + $mask = substr($mask, 2); + } else { + $anchor = '(?:^|/)'; + } + + $pattern = strtr( + preg_quote($mask, '#'), + [ + '\*\*/' => '(.+/)?', + '\*' => '[^/]*', + '\?' => '[^/]', + '\[\!' => '[^', + '\[' => '[', + '\]' => ']', + '\-' => '-', + ], + ); + return '#' . $anchor . $pattern . '$#D' . (Helpers::IsWindows ? 'i' : ''); + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Floats.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Floats.php new file mode 100644 index 00000000000..ed78a55039b --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Floats.php @@ -0,0 +1,108 @@ + $b it returns 1 + * @throws \LogicException if one of parameters is NAN + */ + public static function compare(float $a, float $b): int + { + if (is_nan($a) || is_nan($b)) { + throw new \LogicException('Trying to compare NAN'); + + } elseif (!is_finite($a) && !is_finite($b) && $a === $b) { + return 0; + } + + $diff = abs($a - $b); + if (($diff < self::Epsilon || ($diff / max(abs($a), abs($b)) < self::Epsilon))) { + return 0; + } + + return $a < $b ? -1 : 1; + } + + + /** + * Returns true if $a = $b + * @throws \LogicException if one of parameters is NAN + */ + public static function areEqual(float $a, float $b): bool + { + return self::compare($a, $b) === 0; + } + + + /** + * Returns true if $a < $b + * @throws \LogicException if one of parameters is NAN + */ + public static function isLessThan(float $a, float $b): bool + { + return self::compare($a, $b) < 0; + } + + + /** + * Returns true if $a <= $b + * @throws \LogicException if one of parameters is NAN + */ + public static function isLessThanOrEqualTo(float $a, float $b): bool + { + return self::compare($a, $b) <= 0; + } + + + /** + * Returns true if $a > $b + * @throws \LogicException if one of parameters is NAN + */ + public static function isGreaterThan(float $a, float $b): bool + { + return self::compare($a, $b) > 0; + } + + + /** + * Returns true if $a >= $b + * @throws \LogicException if one of parameters is NAN + */ + public static function isGreaterThanOrEqualTo(float $a, float $b): bool + { + return self::compare($a, $b) >= 0; + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Helpers.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Helpers.php new file mode 100644 index 00000000000..31c9439a31d --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Helpers.php @@ -0,0 +1,109 @@ + ''); + try { + $func(); + return ob_get_clean(); + } catch (\Throwable $e) { + ob_end_clean(); + throw $e; + } + } + + + /** + * Returns the last occurred PHP error or an empty string if no error occurred. Unlike error_get_last(), + * it is nit affected by the PHP directive html_errors and always returns text, not HTML. + */ + public static function getLastError(): string + { + $message = error_get_last()['message'] ?? ''; + $message = ini_get('html_errors') ? Html::htmlToText($message) : $message; + $message = preg_replace('#^\w+\(.*?\): #', '', $message); + return $message; + } + + + /** + * Converts false to null, does not change other values. + */ + public static function falseToNull(mixed $value): mixed + { + return $value === false ? null : $value; + } + + + /** + * Returns value clamped to the inclusive range of min and max. + */ + public static function clamp(int|float $value, int|float $min, int|float $max): int|float + { + if ($min > $max) { + throw new Nette\InvalidArgumentException("Minimum ($min) is not less than maximum ($max)."); + } + + return min(max($value, $min), $max); + } + + + /** + * Looks for a string from possibilities that is most similar to value, but not the same (for 8-bit encoding). + * @param string[] $possibilities + */ + public static function getSuggestion(array $possibilities, string $value): ?string + { + $best = null; + $min = (strlen($value) / 4 + 1) * 10 + .1; + foreach (array_unique($possibilities) as $item) { + if ($item !== $value && ($len = levenshtein($item, $value, 10, 11, 10)) < $min) { + $min = $len; + $best = $item; + } + } + + return $best; + } + + + /** + * Compares two values in the same way that PHP does. Recognizes operators: >, >=, <, <=, =, ==, ===, !=, !==, <> + */ + public static function compare(mixed $left, string $operator, mixed $right): bool + { + return match ($operator) { + '>' => $left > $right, + '>=' => $left >= $right, + '<' => $left < $right, + '<=' => $left <= $right, + '=', '==' => $left == $right, + '===' => $left === $right, + '!=', '<>' => $left != $right, + '!==' => $left !== $right, + default => throw new Nette\InvalidArgumentException("Unknown operator '$operator'"), + }; + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Html.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Html.php new file mode 100644 index 00000000000..cad0baddbfe --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Html.php @@ -0,0 +1,840 @@ + element's attributes */ + public $attrs = []; + + /** void elements */ + public static $emptyElements = [ + 'img' => 1, 'hr' => 1, 'br' => 1, 'input' => 1, 'meta' => 1, 'area' => 1, 'embed' => 1, 'keygen' => 1, + 'source' => 1, 'base' => 1, 'col' => 1, 'link' => 1, 'param' => 1, 'basefont' => 1, 'frame' => 1, + 'isindex' => 1, 'wbr' => 1, 'command' => 1, 'track' => 1, + ]; + + /** @var array nodes */ + protected $children = []; + + /** element's name */ + private string $name = ''; + + private bool $isEmpty = false; + + + /** + * Constructs new HTML element. + * @param array|string $attrs element's attributes or plain text content + */ + public static function el(?string $name = null, array|string|null $attrs = null): static + { + $el = new static; + $parts = explode(' ', (string) $name, 2); + $el->setName($parts[0]); + + if (is_array($attrs)) { + $el->attrs = $attrs; + + } elseif ($attrs !== null) { + $el->setText($attrs); + } + + if (isset($parts[1])) { + foreach (Strings::matchAll($parts[1] . ' ', '#([a-z0-9:-]+)(?:=(["\'])?(.*?)(?(2)\2|\s))?#i') as $m) { + $el->attrs[$m[1]] = $m[3] ?? true; + } + } + + return $el; + } + + + /** + * Returns an object representing HTML text. + */ + public static function fromHtml(string $html): static + { + return (new static)->setHtml($html); + } + + + /** + * Returns an object representing plain text. + */ + public static function fromText(string $text): static + { + return (new static)->setText($text); + } + + + /** + * Converts to HTML. + */ + final public function toHtml(): string + { + return $this->render(); + } + + + /** + * Converts to plain text. + */ + final public function toText(): string + { + return $this->getText(); + } + + + /** + * Converts given HTML code to plain text. + */ + public static function htmlToText(string $html): string + { + return html_entity_decode(strip_tags($html), ENT_QUOTES | ENT_HTML5, 'UTF-8'); + } + + + /** + * Changes element's name. + */ + final public function setName(string $name, ?bool $isEmpty = null): static + { + $this->name = $name; + $this->isEmpty = $isEmpty ?? isset(static::$emptyElements[$name]); + return $this; + } + + + /** + * Returns element's name. + */ + final public function getName(): string + { + return $this->name; + } + + + /** + * Is element empty? + */ + final public function isEmpty(): bool + { + return $this->isEmpty; + } + + + /** + * Sets multiple attributes. + */ + public function addAttributes(array $attrs): static + { + $this->attrs = array_merge($this->attrs, $attrs); + return $this; + } + + + /** + * Appends value to element's attribute. + */ + public function appendAttribute(string $name, mixed $value, mixed $option = true): static + { + if (is_array($value)) { + $prev = isset($this->attrs[$name]) ? (array) $this->attrs[$name] : []; + $this->attrs[$name] = $value + $prev; + + } elseif ((string) $value === '') { + $tmp = &$this->attrs[$name]; // appending empty value? -> ignore, but ensure it exists + + } elseif (!isset($this->attrs[$name]) || is_array($this->attrs[$name])) { // needs array + $this->attrs[$name][$value] = $option; + + } else { + $this->attrs[$name] = [$this->attrs[$name] => true, $value => $option]; + } + + return $this; + } + + + /** + * Sets element's attribute. + */ + public function setAttribute(string $name, mixed $value): static + { + $this->attrs[$name] = $value; + return $this; + } + + + /** + * Returns element's attribute. + */ + public function getAttribute(string $name): mixed + { + return $this->attrs[$name] ?? null; + } + + + /** + * Unsets element's attribute. + */ + public function removeAttribute(string $name): static + { + unset($this->attrs[$name]); + return $this; + } + + + /** + * Unsets element's attributes. + */ + public function removeAttributes(array $attributes): static + { + foreach ($attributes as $name) { + unset($this->attrs[$name]); + } + + return $this; + } + + + /** + * Overloaded setter for element's attribute. + */ + final public function __set(string $name, mixed $value): void + { + $this->attrs[$name] = $value; + } + + + /** + * Overloaded getter for element's attribute. + */ + final public function &__get(string $name): mixed + { + return $this->attrs[$name]; + } + + + /** + * Overloaded tester for element's attribute. + */ + final public function __isset(string $name): bool + { + return isset($this->attrs[$name]); + } + + + /** + * Overloaded unsetter for element's attribute. + */ + final public function __unset(string $name): void + { + unset($this->attrs[$name]); + } + + + /** + * Overloaded setter for element's attribute. + */ + final public function __call(string $m, array $args): mixed + { + $p = substr($m, 0, 3); + if ($p === 'get' || $p === 'set' || $p === 'add') { + $m = substr($m, 3); + $m[0] = $m[0] | "\x20"; + if ($p === 'get') { + return $this->attrs[$m] ?? null; + + } elseif ($p === 'add') { + $args[] = true; + } + } + + if (count($args) === 0) { // invalid + + } elseif (count($args) === 1) { // set + $this->attrs[$m] = $args[0]; + + } else { // add + $this->appendAttribute($m, $args[0], $args[1]); + } + + return $this; + } + + + /** + * Special setter for element's attribute. + */ + final public function href(string $path, array $query = []): static + { + if ($query) { + $query = http_build_query($query, '', '&'); + if ($query !== '') { + $path .= '?' . $query; + } + } + + $this->attrs['href'] = $path; + return $this; + } + + + /** + * Setter for data-* attributes. Booleans are converted to 'true' resp. 'false'. + */ + public function data(string $name, mixed $value = null): static + { + if (func_num_args() === 1) { + $this->attrs['data'] = $name; + } else { + $this->attrs["data-$name"] = is_bool($value) + ? json_encode($value) + : $value; + } + + return $this; + } + + + /** + * Sets element's HTML content. + */ + final public function setHtml(mixed $html): static + { + $this->children = [(string) $html]; + return $this; + } + + + /** + * Returns element's HTML content. + */ + final public function getHtml(): string + { + return implode('', $this->children); + } + + + /** + * Sets element's textual content. + */ + final public function setText(mixed $text): static + { + if (!$text instanceof HtmlStringable) { + $text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8'); + } + + $this->children = [(string) $text]; + return $this; + } + + + /** + * Returns element's textual content. + */ + final public function getText(): string + { + return self::htmlToText($this->getHtml()); + } + + + /** + * Adds new element's child. + */ + final public function addHtml(mixed $child): static + { + return $this->insert(null, $child); + } + + + /** + * Appends plain-text string to element content. + */ + public function addText(mixed $text): static + { + if (!$text instanceof HtmlStringable) { + $text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8'); + } + + return $this->insert(null, $text); + } + + + /** + * Creates and adds a new Html child. + */ + final public function create(string $name, array|string|null $attrs = null): static + { + $this->insert(null, $child = static::el($name, $attrs)); + return $child; + } + + + /** + * Inserts child node. + */ + public function insert(?int $index, HtmlStringable|string $child, bool $replace = false): static + { + $child = $child instanceof self ? $child : (string) $child; + if ($index === null) { // append + $this->children[] = $child; + + } else { // insert or replace + array_splice($this->children, $index, $replace ? 1 : 0, [$child]); + } + + return $this; + } + + + /** + * Inserts (replaces) child node (\ArrayAccess implementation). + * @param int|null $index position or null for appending + * @param Html|string $child Html node or raw HTML string + */ + final public function offsetSet($index, $child): void + { + $this->insert($index, $child, replace: true); + } + + + /** + * Returns child node (\ArrayAccess implementation). + * @param int $index + */ + final public function offsetGet($index): HtmlStringable|string + { + return $this->children[$index]; + } + + + /** + * Exists child node? (\ArrayAccess implementation). + * @param int $index + */ + final public function offsetExists($index): bool + { + return isset($this->children[$index]); + } + + + /** + * Removes child node (\ArrayAccess implementation). + * @param int $index + */ + public function offsetUnset($index): void + { + if (isset($this->children[$index])) { + array_splice($this->children, $index, 1); + } + } + + + /** + * Returns children count. + */ + final public function count(): int + { + return count($this->children); + } + + + /** + * Removes all children. + */ + public function removeChildren(): void + { + $this->children = []; + } + + + /** + * Iterates over elements. + * @return \ArrayIterator + */ + final public function getIterator(): \ArrayIterator + { + return new \ArrayIterator($this->children); + } + + + /** + * Returns all children. + */ + final public function getChildren(): array + { + return $this->children; + } + + + /** + * Renders element's start tag, content and end tag. + */ + final public function render(?int $indent = null): string + { + $s = $this->startTag(); + + if (!$this->isEmpty) { + // add content + if ($indent !== null) { + $indent++; + } + + foreach ($this->children as $child) { + if ($child instanceof self) { + $s .= $child->render($indent); + } else { + $s .= $child; + } + } + + // add end tag + $s .= $this->endTag(); + } + + if ($indent !== null) { + return "\n" . str_repeat("\t", $indent - 1) . $s . "\n" . str_repeat("\t", max(0, $indent - 2)); + } + + return $s; + } + + + final public function __toString(): string + { + return $this->render(); + } + + + /** + * Returns element's start tag. + */ + final public function startTag(): string + { + return $this->name + ? '<' . $this->name . $this->attributes() . '>' + : ''; + } + + + /** + * Returns element's end tag. + */ + final public function endTag(): string + { + return $this->name && !$this->isEmpty ? 'name . '>' : ''; + } + + + /** + * Returns element's attributes. + * @internal + */ + final public function attributes(): string + { + if (!is_array($this->attrs)) { + return ''; + } + + $s = ''; + $attrs = $this->attrs; + foreach ($attrs as $key => $value) { + if ($value === null || $value === false) { + continue; + + } elseif ($value === true) { + $s .= ' ' . $key; + + continue; + + } elseif (is_array($value)) { + if (strncmp($key, 'data-', 5) === 0) { + $value = Json::encode($value); + + } else { + $tmp = null; + foreach ($value as $k => $v) { + if ($v != null) { // intentionally ==, skip nulls & empty string + // composite 'style' vs. 'others' + $tmp[] = $v === true + ? $k + : (is_string($k) ? $k . ':' . $v : $v); + } + } + + if ($tmp === null) { + continue; + } + + $value = implode($key === 'style' || !strncmp($key, 'on', 2) ? ';' : ' ', $tmp); + } + } elseif (is_float($value)) { + $value = rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.'); + + } else { + $value = (string) $value; + } + + $q = str_contains($value, '"') ? "'" : '"'; + $s .= ' ' . $key . '=' . $q + . str_replace( + ['&', $q, '<'], + ['&', $q === '"' ? '"' : ''', '<'], + $value, + ) + . (str_contains($value, '`') && strpbrk($value, ' <>"\'') === false ? ' ' : '') + . $q; + } + + $s = str_replace('@', '@', $s); + return $s; + } + + + /** + * Clones all children too. + */ + public function __clone() + { + foreach ($this->children as $key => $value) { + if (is_object($value)) { + $this->children[$key] = clone $value; + } + } + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Image.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Image.php new file mode 100644 index 00000000000..a557a1881ad --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Image.php @@ -0,0 +1,818 @@ + + * $image = Image::fromFile('nette.jpg'); + * $image->resize(150, 100); + * $image->sharpen(); + * $image->send(); + * + * + * @method Image affine(array $affine, ?array $clip = null) + * @method void alphaBlending(bool $enable) + * @method void antialias(bool $enable) + * @method void arc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color) + * @method int colorAllocate(int $red, int $green, int $blue) + * @method int colorAllocateAlpha(int $red, int $green, int $blue, int $alpha) + * @method int colorAt(int $x, int $y) + * @method int colorClosest(int $red, int $green, int $blue) + * @method int colorClosestAlpha(int $red, int $green, int $blue, int $alpha) + * @method int colorClosestHWB(int $red, int $green, int $blue) + * @method void colorDeallocate(int $color) + * @method int colorExact(int $red, int $green, int $blue) + * @method int colorExactAlpha(int $red, int $green, int $blue, int $alpha) + * @method void colorMatch(Image $image2) + * @method int colorResolve(int $red, int $green, int $blue) + * @method int colorResolveAlpha(int $red, int $green, int $blue, int $alpha) + * @method void colorSet(int $index, int $red, int $green, int $blue, int $alpha = 0) + * @method array colorsForIndex(int $color) + * @method int colorsTotal() + * @method int colorTransparent(?int $color = null) + * @method void convolution(array $matrix, float $div, float $offset) + * @method void copy(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH) + * @method void copyMerge(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $pct) + * @method void copyMergeGray(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $pct) + * @method void copyResampled(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH) + * @method void copyResized(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH) + * @method Image cropAuto(int $mode = IMG_CROP_DEFAULT, float $threshold = .5, ?ImageColor $color = null) + * @method void ellipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color) + * @method void fill(int $x, int $y, ImageColor $color) + * @method void filledArc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color, int $style) + * @method void filledEllipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color) + * @method void filledPolygon(array $points, ImageColor $color) + * @method void filledRectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color) + * @method void fillToBorder(int $x, int $y, ImageColor $borderColor, ImageColor $color) + * @method void filter(int $filter, ...$args) + * @method void flip(int $mode) + * @method array ftText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options = []) + * @method void gammaCorrect(float $inputgamma, float $outputgamma) + * @method array getClip() + * @method int getInterpolation() + * @method int interlace(?bool $enable = null) + * @method bool isTrueColor() + * @method void layerEffect(int $effect) + * @method void line(int $x1, int $y1, int $x2, int $y2, ImageColor $color) + * @method void openPolygon(array $points, ImageColor $color) + * @method void paletteCopy(Image $source) + * @method void paletteToTrueColor() + * @method void polygon(array $points, ImageColor $color) + * @method void rectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color) + * @method mixed resolution(?int $resolutionX = null, ?int $resolutionY = null) + * @method Image rotate(float $angle, ImageColor $backgroundColor) + * @method void saveAlpha(bool $enable) + * @method Image scale(int $newWidth, int $newHeight = -1, int $mode = IMG_BILINEAR_FIXED) + * @method void setBrush(Image $brush) + * @method void setClip(int $x1, int $y1, int $x2, int $y2) + * @method void setInterpolation(int $method = IMG_BILINEAR_FIXED) + * @method void setPixel(int $x, int $y, ImageColor $color) + * @method void setStyle(array $style) + * @method void setThickness(int $thickness) + * @method void setTile(Image $tile) + * @method void trueColorToPalette(bool $dither, int $ncolors) + * @method array ttfText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontfile, string $text, array $options = []) + * @property-read positive-int $width + * @property-read positive-int $height + * @property-read \GdImage $imageResource + */ +class Image +{ + use Nette\SmartObject; + + /** Prevent from getting resized to a bigger size than the original */ + public const ShrinkOnly = 0b0001; + + /** Resizes to a specified width and height without keeping aspect ratio */ + public const Stretch = 0b0010; + + /** Resizes to fit into a specified width and height and preserves aspect ratio */ + public const OrSmaller = 0b0000; + + /** Resizes while bounding the smaller dimension to the specified width or height and preserves aspect ratio */ + public const OrBigger = 0b0100; + + /** Resizes to the smallest possible size to completely cover specified width and height and reserves aspect ratio */ + public const Cover = 0b1000; + + /** @deprecated use Image::ShrinkOnly */ + public const SHRINK_ONLY = self::ShrinkOnly; + + /** @deprecated use Image::Stretch */ + public const STRETCH = self::Stretch; + + /** @deprecated use Image::OrSmaller */ + public const FIT = self::OrSmaller; + + /** @deprecated use Image::OrBigger */ + public const FILL = self::OrBigger; + + /** @deprecated use Image::Cover */ + public const EXACT = self::Cover; + + /** @deprecated use Image::EmptyGIF */ + public const EMPTY_GIF = self::EmptyGIF; + + /** image types */ + public const + JPEG = ImageType::JPEG, + PNG = ImageType::PNG, + GIF = ImageType::GIF, + WEBP = ImageType::WEBP, + AVIF = ImageType::AVIF, + BMP = ImageType::BMP; + + public const EmptyGIF = "GIF89a\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00!\xf9\x04\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;"; + + private const Formats = [ImageType::JPEG => 'jpeg', ImageType::PNG => 'png', ImageType::GIF => 'gif', ImageType::WEBP => 'webp', ImageType::AVIF => 'avif', ImageType::BMP => 'bmp']; + + private \GdImage $image; + + + /** + * Returns RGB color (0..255) and transparency (0..127). + * @deprecated use ImageColor::rgb() + */ + public static function rgb(int $red, int $green, int $blue, int $transparency = 0): array + { + return [ + 'red' => max(0, min(255, $red)), + 'green' => max(0, min(255, $green)), + 'blue' => max(0, min(255, $blue)), + 'alpha' => max(0, min(127, $transparency)), + ]; + } + + + /** + * Reads an image from a file and returns its type in $type. + * @throws Nette\NotSupportedException if gd extension is not loaded + * @throws UnknownImageFileException if file not found or file type is not known + */ + public static function fromFile(string $file, ?int &$type = null): static + { + self::ensureExtension(); + $type = self::detectTypeFromFile($file); + if (!$type) { + throw new UnknownImageFileException(is_file($file) ? "Unknown type of file '$file'." : "File '$file' not found."); + } + + return self::invokeSafe('imagecreatefrom' . self::Formats[$type], $file, "Unable to open file '$file'.", __METHOD__); + } + + + /** + * Reads an image from a string and returns its type in $type. + * @throws Nette\NotSupportedException if gd extension is not loaded + * @throws ImageException + */ + public static function fromString(string $s, ?int &$type = null): static + { + self::ensureExtension(); + $type = self::detectTypeFromString($s); + if (!$type) { + throw new UnknownImageFileException('Unknown type of image.'); + } + + return self::invokeSafe('imagecreatefromstring', $s, 'Unable to open image from string.', __METHOD__); + } + + + private static function invokeSafe(string $func, string $arg, string $message, string $callee): static + { + $errors = []; + $res = Callback::invokeSafe($func, [$arg], function (string $message) use (&$errors): void { + $errors[] = $message; + }); + + if (!$res) { + throw new ImageException($message . ' Errors: ' . implode(', ', $errors)); + } elseif ($errors) { + trigger_error($callee . '(): ' . implode(', ', $errors), E_USER_WARNING); + } + + return new static($res); + } + + + /** + * Creates a new true color image of the given dimensions. The default color is black. + * @param positive-int $width + * @param positive-int $height + * @throws Nette\NotSupportedException if gd extension is not loaded + */ + public static function fromBlank(int $width, int $height, ImageColor|array|null $color = null): static + { + self::ensureExtension(); + if ($width < 1 || $height < 1) { + throw new Nette\InvalidArgumentException('Image width and height must be greater than zero.'); + } + + $image = new static(imagecreatetruecolor($width, $height)); + if ($color) { + $image->alphablending(false); + $image->filledrectangle(0, 0, $width - 1, $height - 1, $color); + $image->alphablending(true); + } + + return $image; + } + + + /** + * Returns the type of image from file. + * @return ImageType::*|null + */ + public static function detectTypeFromFile(string $file, &$width = null, &$height = null): ?int + { + [$width, $height, $type] = @getimagesize($file); // @ - files smaller than 12 bytes causes read error + return isset(self::Formats[$type]) ? $type : null; + } + + + /** + * Returns the type of image from string. + * @return ImageType::*|null + */ + public static function detectTypeFromString(string $s, &$width = null, &$height = null): ?int + { + [$width, $height, $type] = @getimagesizefromstring($s); // @ - strings smaller than 12 bytes causes read error + return isset(self::Formats[$type]) ? $type : null; + } + + + /** + * Returns the file extension for the given image type. + * @param ImageType::* $type + * @return value-of + */ + public static function typeToExtension(int $type): string + { + if (!isset(self::Formats[$type])) { + throw new Nette\InvalidArgumentException("Unsupported image type '$type'."); + } + + return self::Formats[$type]; + } + + + /** + * Returns the image type for given file extension. + * @return ImageType::* + */ + public static function extensionToType(string $extension): int + { + $extensions = array_flip(self::Formats) + ['jpg' => ImageType::JPEG]; + $extension = strtolower($extension); + if (!isset($extensions[$extension])) { + throw new Nette\InvalidArgumentException("Unsupported file extension '$extension'."); + } + + return $extensions[$extension]; + } + + + /** + * Returns the mime type for the given image type. + * @param ImageType::* $type + */ + public static function typeToMimeType(int $type): string + { + return 'image/' . self::typeToExtension($type); + } + + + /** + * @param ImageType::* $type + */ + public static function isTypeSupported(int $type): bool + { + self::ensureExtension(); + return (bool) (imagetypes() & match ($type) { + ImageType::JPEG => IMG_JPG, + ImageType::PNG => IMG_PNG, + ImageType::GIF => IMG_GIF, + ImageType::WEBP => IMG_WEBP, + ImageType::AVIF => 256, // IMG_AVIF, + ImageType::BMP => IMG_BMP, + default => 0, + }); + } + + + /** @return ImageType[] */ + public static function getSupportedTypes(): array + { + self::ensureExtension(); + $flag = imagetypes(); + return array_filter([ + $flag & IMG_GIF ? ImageType::GIF : null, + $flag & IMG_JPG ? ImageType::JPEG : null, + $flag & IMG_PNG ? ImageType::PNG : null, + $flag & IMG_WEBP ? ImageType::WEBP : null, + $flag & 256 ? ImageType::AVIF : null, // IMG_AVIF + $flag & IMG_BMP ? ImageType::BMP : null, + ]); + } + + + /** + * Wraps GD image. + */ + public function __construct(\GdImage $image) + { + $this->setImageResource($image); + imagesavealpha($image, true); + } + + + /** + * Returns image width. + * @return positive-int + */ + public function getWidth(): int + { + return imagesx($this->image); + } + + + /** + * Returns image height. + * @return positive-int + */ + public function getHeight(): int + { + return imagesy($this->image); + } + + + /** + * Sets image resource. + */ + protected function setImageResource(\GdImage $image): static + { + $this->image = $image; + return $this; + } + + + /** + * Returns image GD resource. + */ + public function getImageResource(): \GdImage + { + return $this->image; + } + + + /** + * Scales an image. Width and height accept pixels or percent. + * @param int-mask-of $mode + */ + public function resize(int|string|null $width, int|string|null $height, int $mode = self::OrSmaller): static + { + if ($mode & self::Cover) { + return $this->resize($width, $height, self::OrBigger)->crop('50%', '50%', $width, $height); + } + + [$newWidth, $newHeight] = static::calculateSize($this->getWidth(), $this->getHeight(), $width, $height, $mode); + + if ($newWidth !== $this->getWidth() || $newHeight !== $this->getHeight()) { // resize + $newImage = static::fromBlank($newWidth, $newHeight, ImageColor::rgb(0, 0, 0, 0))->getImageResource(); + imagecopyresampled( + $newImage, + $this->image, + 0, + 0, + 0, + 0, + $newWidth, + $newHeight, + $this->getWidth(), + $this->getHeight(), + ); + $this->image = $newImage; + } + + if ($width < 0 || $height < 0) { + imageflip($this->image, $width < 0 ? ($height < 0 ? IMG_FLIP_BOTH : IMG_FLIP_HORIZONTAL) : IMG_FLIP_VERTICAL); + } + + return $this; + } + + + /** + * Calculates dimensions of resized image. Width and height accept pixels or percent. + * @param int-mask-of $mode + */ + public static function calculateSize( + int $srcWidth, + int $srcHeight, + $newWidth, + $newHeight, + int $mode = self::OrSmaller, + ): array + { + if ($newWidth === null) { + } elseif (self::isPercent($newWidth)) { + $newWidth = (int) round($srcWidth / 100 * abs($newWidth)); + $percents = true; + } else { + $newWidth = abs($newWidth); + } + + if ($newHeight === null) { + } elseif (self::isPercent($newHeight)) { + $newHeight = (int) round($srcHeight / 100 * abs($newHeight)); + $mode |= empty($percents) ? 0 : self::Stretch; + } else { + $newHeight = abs($newHeight); + } + + if ($mode & self::Stretch) { // non-proportional + if (!$newWidth || !$newHeight) { + throw new Nette\InvalidArgumentException('For stretching must be both width and height specified.'); + } + + if ($mode & self::ShrinkOnly) { + $newWidth = min($srcWidth, $newWidth); + $newHeight = min($srcHeight, $newHeight); + } + } else { // proportional + if (!$newWidth && !$newHeight) { + throw new Nette\InvalidArgumentException('At least width or height must be specified.'); + } + + $scale = []; + if ($newWidth > 0) { // fit width + $scale[] = $newWidth / $srcWidth; + } + + if ($newHeight > 0) { // fit height + $scale[] = $newHeight / $srcHeight; + } + + if ($mode & self::OrBigger) { + $scale = [max($scale)]; + } + + if ($mode & self::ShrinkOnly) { + $scale[] = 1; + } + + $scale = min($scale); + $newWidth = (int) round($srcWidth * $scale); + $newHeight = (int) round($srcHeight * $scale); + } + + return [max($newWidth, 1), max($newHeight, 1)]; + } + + + /** + * Crops image. Arguments accepts pixels or percent. + */ + public function crop(int|string $left, int|string $top, int|string $width, int|string $height): static + { + [$r['x'], $r['y'], $r['width'], $r['height']] + = static::calculateCutout($this->getWidth(), $this->getHeight(), $left, $top, $width, $height); + if (gd_info()['GD Version'] === 'bundled (2.1.0 compatible)') { + $this->image = imagecrop($this->image, $r); + imagesavealpha($this->image, true); + } else { + $newImage = static::fromBlank($r['width'], $r['height'], ImageColor::rgb(0, 0, 0, 0))->getImageResource(); + imagecopy($newImage, $this->image, 0, 0, $r['x'], $r['y'], $r['width'], $r['height']); + $this->image = $newImage; + } + + return $this; + } + + + /** + * Calculates dimensions of cutout in image. Arguments accepts pixels or percent. + */ + public static function calculateCutout( + int $srcWidth, + int $srcHeight, + int|string $left, + int|string $top, + int|string $newWidth, + int|string $newHeight, + ): array + { + if (self::isPercent($newWidth)) { + $newWidth = (int) round($srcWidth / 100 * $newWidth); + } + + if (self::isPercent($newHeight)) { + $newHeight = (int) round($srcHeight / 100 * $newHeight); + } + + if (self::isPercent($left)) { + $left = (int) round(($srcWidth - $newWidth) / 100 * $left); + } + + if (self::isPercent($top)) { + $top = (int) round(($srcHeight - $newHeight) / 100 * $top); + } + + if ($left < 0) { + $newWidth += $left; + $left = 0; + } + + if ($top < 0) { + $newHeight += $top; + $top = 0; + } + + $newWidth = min($newWidth, $srcWidth - $left); + $newHeight = min($newHeight, $srcHeight - $top); + return [$left, $top, $newWidth, $newHeight]; + } + + + /** + * Sharpens image a little bit. + */ + public function sharpen(): static + { + imageconvolution($this->image, [ // my magic numbers ;) + [-1, -1, -1], + [-1, 24, -1], + [-1, -1, -1], + ], 16, 0); + return $this; + } + + + /** + * Puts another image into this image. Left and top accepts pixels or percent. + * @param int<0, 100> $opacity 0..100 + */ + public function place(self $image, int|string $left = 0, int|string $top = 0, int $opacity = 100): static + { + $opacity = max(0, min(100, $opacity)); + if ($opacity === 0) { + return $this; + } + + $width = $image->getWidth(); + $height = $image->getHeight(); + + if (self::isPercent($left)) { + $left = (int) round(($this->getWidth() - $width) / 100 * $left); + } + + if (self::isPercent($top)) { + $top = (int) round(($this->getHeight() - $height) / 100 * $top); + } + + $output = $input = $image->image; + if ($opacity < 100) { + $tbl = []; + for ($i = 0; $i < 128; $i++) { + $tbl[$i] = round(127 - (127 - $i) * $opacity / 100); + } + + $output = imagecreatetruecolor($width, $height); + imagealphablending($output, false); + if (!$image->isTrueColor()) { + $input = $output; + imagefilledrectangle($output, 0, 0, $width, $height, imagecolorallocatealpha($output, 0, 0, 0, 127)); + imagecopy($output, $image->image, 0, 0, 0, 0, $width, $height); + } + + for ($x = 0; $x < $width; $x++) { + for ($y = 0; $y < $height; $y++) { + $c = \imagecolorat($input, $x, $y); + $c = ($c & 0xFFFFFF) + ($tbl[$c >> 24] << 24); + \imagesetpixel($output, $x, $y, $c); + } + } + + imagealphablending($output, true); + } + + imagecopy( + $this->image, + $output, + $left, + $top, + 0, + 0, + $width, + $height, + ); + return $this; + } + + + /** + * Calculates the bounding box for a TrueType text. Returns keys left, top, width and height. + */ + public static function calculateTextBox( + string $text, + string $fontFile, + float $size, + float $angle = 0, + array $options = [], + ): array + { + self::ensureExtension(); + $box = imagettfbbox($size, $angle, $fontFile, $text, $options); + return [ + 'left' => $minX = min([$box[0], $box[2], $box[4], $box[6]]), + 'top' => $minY = min([$box[1], $box[3], $box[5], $box[7]]), + 'width' => max([$box[0], $box[2], $box[4], $box[6]]) - $minX + 1, + 'height' => max([$box[1], $box[3], $box[5], $box[7]]) - $minY + 1, + ]; + } + + + /** + * Draw a rectangle. + */ + public function rectangleWH(int $x, int $y, int $width, int $height, ImageColor $color): void + { + if ($width !== 0 && $height !== 0) { + $this->rectangle($x, $y, $x + $width + ($width > 0 ? -1 : 1), $y + $height + ($height > 0 ? -1 : 1), $color); + } + } + + + /** + * Draw a filled rectangle. + */ + public function filledRectangleWH(int $x, int $y, int $width, int $height, ImageColor $color): void + { + if ($width !== 0 && $height !== 0) { + $this->filledRectangle($x, $y, $x + $width + ($width > 0 ? -1 : 1), $y + $height + ($height > 0 ? -1 : 1), $color); + } + } + + + /** + * Saves image to the file. Quality is in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9). + * @param ImageType::*|null $type + * @throws ImageException + */ + public function save(string $file, ?int $quality = null, ?int $type = null): void + { + $type ??= self::extensionToType(pathinfo($file, PATHINFO_EXTENSION)); + $this->output($type, $quality, $file); + } + + + /** + * Outputs image to string. Quality is in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9). + * @param ImageType::* $type + */ + public function toString(int $type = ImageType::JPEG, ?int $quality = null): string + { + return Helpers::capture(function () use ($type, $quality): void { + $this->output($type, $quality); + }); + } + + + /** + * Outputs image to string. + */ + public function __toString(): string + { + return $this->toString(); + } + + + /** + * Outputs image to browser. Quality is in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9). + * @param ImageType::* $type + * @throws ImageException + */ + public function send(int $type = ImageType::JPEG, ?int $quality = null): void + { + header('Content-Type: ' . self::typeToMimeType($type)); + $this->output($type, $quality); + } + + + /** + * Outputs image to browser or file. + * @param ImageType::* $type + * @throws ImageException + */ + private function output(int $type, ?int $quality, ?string $file = null): void + { + [$defQuality, $min, $max] = match ($type) { + ImageType::JPEG => [85, 0, 100], + ImageType::PNG => [9, 0, 9], + ImageType::GIF => [null, null, null], + ImageType::WEBP => [80, 0, 100], + ImageType::AVIF => [30, 0, 100], + ImageType::BMP => [null, null, null], + default => throw new Nette\InvalidArgumentException("Unsupported image type '$type'."), + }; + + $args = [$this->image, $file]; + if ($defQuality !== null) { + $args[] = $quality === null ? $defQuality : max($min, min($max, $quality)); + } + + Callback::invokeSafe('image' . self::Formats[$type], $args, function (string $message) use ($file): void { + if ($file !== null) { + @unlink($file); + } + throw new ImageException($message); + }); + } + + + /** + * Call to undefined method. + * @throws Nette\MemberAccessException + */ + public function __call(string $name, array $args): mixed + { + $function = 'image' . $name; + if (!function_exists($function)) { + ObjectHelpers::strictCall(static::class, $name); + } + + foreach ($args as $key => $value) { + if ($value instanceof self) { + $args[$key] = $value->getImageResource(); + + } elseif ($value instanceof ImageColor || (is_array($value) && isset($value['red']))) { + $args[$key] = $this->resolveColor($value); + } + } + + $res = $function($this->image, ...$args); + return $res instanceof \GdImage + ? $this->setImageResource($res) + : $res; + } + + + public function __clone() + { + ob_start(fn() => ''); + imagepng($this->image, null, 0); + $this->setImageResource(imagecreatefromstring(ob_get_clean())); + } + + + private static function isPercent(int|string &$num): bool + { + if (is_string($num) && str_ends_with($num, '%')) { + $num = (float) substr($num, 0, -1); + return true; + } elseif (is_int($num) || $num === (string) (int) $num) { + $num = (int) $num; + return false; + } + + throw new Nette\InvalidArgumentException("Expected dimension in int|string, '$num' given."); + } + + + /** + * Prevents serialization. + */ + public function __serialize(): array + { + throw new Nette\NotSupportedException('You cannot serialize or unserialize ' . self::class . ' instances.'); + } + + + public function resolveColor(ImageColor|array $color): int + { + $color = $color instanceof ImageColor ? $color->toRGBA() : array_values($color); + return imagecolorallocatealpha($this->image, ...$color) ?: imagecolorresolvealpha($this->image, ...$color); + } + + + private static function ensureExtension(): void + { + if (!extension_loaded('gd')) { + throw new Nette\NotSupportedException('PHP extension GD is not loaded.'); + } + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/ImageColor.php b/tools/.phpstan/vendor/nette/utils/src/Utils/ImageColor.php new file mode 100644 index 00000000000..66966620dd9 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/ImageColor.php @@ -0,0 +1,76 @@ +red = max(0, min(255, $red)); + $this->green = max(0, min(255, $green)); + $this->blue = max(0, min(255, $blue)); + $this->opacity = max(0, min(1, $opacity)); + } + + + public function toRGBA(): array + { + return [ + max(0, min(255, $this->red)), + max(0, min(255, $this->green)), + max(0, min(255, $this->blue)), + max(0, min(127, (int) round(127 - $this->opacity * 127))), + ]; + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/ImageType.php b/tools/.phpstan/vendor/nette/utils/src/Utils/ImageType.php new file mode 100644 index 00000000000..080ca8a09e2 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/ImageType.php @@ -0,0 +1,27 @@ + $v) { + if ($k === $key) { + return true; + } + } + return false; + } + + + /** + * Returns the first item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null. + * @template K + * @template V + * @param iterable $iterable + * @param ?callable(V, K, iterable): bool $predicate + * @return ?V + */ + public static function first(iterable $iterable, ?callable $predicate = null, ?callable $else = null): mixed + { + foreach ($iterable as $k => $v) { + if (!$predicate || $predicate($v, $k, $iterable)) { + return $v; + } + } + return $else ? $else() : null; + } + + + /** + * Returns the key of first item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null. + * @template K + * @template V + * @param iterable $iterable + * @param ?callable(V, K, iterable): bool $predicate + * @return ?K + */ + public static function firstKey(iterable $iterable, ?callable $predicate = null, ?callable $else = null): mixed + { + foreach ($iterable as $k => $v) { + if (!$predicate || $predicate($v, $k, $iterable)) { + return $k; + } + } + return $else ? $else() : null; + } + + + /** + * Tests whether at least one element in the iterator passes the test implemented by the provided function. + * @template K + * @template V + * @param iterable $iterable + * @param callable(V, K, iterable): bool $predicate + */ + public static function some(iterable $iterable, callable $predicate): bool + { + foreach ($iterable as $k => $v) { + if ($predicate($v, $k, $iterable)) { + return true; + } + } + return false; + } + + + /** + * Tests whether all elements in the iterator pass the test implemented by the provided function. + * @template K + * @template V + * @param iterable $iterable + * @param callable(V, K, iterable): bool $predicate + */ + public static function every(iterable $iterable, callable $predicate): bool + { + foreach ($iterable as $k => $v) { + if (!$predicate($v, $k, $iterable)) { + return false; + } + } + return true; + } + + + /** + * Iterator that filters elements according to a given $predicate. Maintains original keys. + * @template K + * @template V + * @param iterable $iterable + * @param callable(V, K, iterable): bool $predicate + * @return \Generator + */ + public static function filter(iterable $iterable, callable $predicate): \Generator + { + foreach ($iterable as $k => $v) { + if ($predicate($v, $k, $iterable)) { + yield $k => $v; + } + } + } + + + /** + * Iterator that transforms values by calling $transformer. Maintains original keys. + * @template K + * @template V + * @template R + * @param iterable $iterable + * @param callable(V, K, iterable): R $transformer + * @return \Generator + */ + public static function map(iterable $iterable, callable $transformer): \Generator + { + foreach ($iterable as $k => $v) { + yield $k => $transformer($v, $k, $iterable); + } + } + + + /** + * Iterator that transforms keys and values by calling $transformer. If it returns null, the element is skipped. + * @template K + * @template V + * @template ResV + * @template ResK + * @param iterable $iterable + * @param callable(V, K, iterable): ?array{ResV, ResK} $transformer + * @return \Generator + */ + public static function mapWithKeys(iterable $iterable, callable $transformer): \Generator + { + foreach ($iterable as $k => $v) { + $pair = $transformer($v, $k, $iterable); + if ($pair) { + yield $pair[0] => $pair[1]; + } + } + } + + + /** + * Wraps around iterator and caches its keys and values during iteration. + * This allows the data to be re-iterated multiple times. + * @template K + * @template V + * @param iterable $iterable + * @return \IteratorAggregate + */ + public static function memoize(iterable $iterable): iterable + { + return new class (self::toIterator($iterable)) implements \IteratorAggregate { + public function __construct( + private \Iterator $iterator, + private array $cache = [], + ) { + } + + + public function getIterator(): \Generator + { + if (!$this->cache) { + $this->iterator->rewind(); + } + $i = 0; + while (true) { + if (isset($this->cache[$i])) { + [$k, $v] = $this->cache[$i]; + } elseif ($this->iterator->valid()) { + $k = $this->iterator->key(); + $v = $this->iterator->current(); + $this->iterator->next(); + $this->cache[$i] = [$k, $v]; + } else { + break; + } + yield $k => $v; + $i++; + } + } + }; + } + + + /** + * Creates an iterator from anything that is iterable. + * @template K + * @template V + * @param iterable $iterable + * @return \Iterator + */ + public static function toIterator(iterable $iterable): \Iterator + { + return match (true) { + $iterable instanceof \Iterator => $iterable, + $iterable instanceof \IteratorAggregate => self::toIterator($iterable->getIterator()), + is_array($iterable) => new \ArrayIterator($iterable), + default => throw new Nette\ShouldNotHappenException, + }; + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Json.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Json.php new file mode 100644 index 00000000000..4e4cf238eed --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Json.php @@ -0,0 +1,86 @@ +getProperties(\ReflectionProperty::IS_PUBLIC), fn($p) => !$p->isStatic()), + self::parseFullDoc($rc, '~^[ \t*]*@property(?:-read)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m'), + ), $name); + throw new MemberAccessException("Cannot read an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.')); + } + + + /** + * @return never + * @throws MemberAccessException + */ + public static function strictSet(string $class, string $name): void + { + $rc = new \ReflectionClass($class); + $hint = self::getSuggestion(array_merge( + array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), fn($p) => !$p->isStatic()), + self::parseFullDoc($rc, '~^[ \t*]*@property(?:-write)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m'), + ), $name); + throw new MemberAccessException("Cannot write to an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.')); + } + + + /** + * @return never + * @throws MemberAccessException + */ + public static function strictCall(string $class, string $method, array $additionalMethods = []): void + { + $trace = debug_backtrace(0, 3); // suppose this method is called from __call() + $context = ($trace[1]['function'] ?? null) === '__call' + ? ($trace[2]['class'] ?? null) + : null; + + if ($context && is_a($class, $context, true) && method_exists($context, $method)) { // called parent::$method() + $class = get_parent_class($context); + } + + if (method_exists($class, $method)) { // insufficient visibility + $rm = new \ReflectionMethod($class, $method); + $visibility = $rm->isPrivate() + ? 'private ' + : ($rm->isProtected() ? 'protected ' : ''); + throw new MemberAccessException("Call to {$visibility}method $class::$method() from " . ($context ? "scope $context." : 'global scope.')); + + } else { + $hint = self::getSuggestion(array_merge( + get_class_methods($class), + self::parseFullDoc(new \ReflectionClass($class), '~^[ \t*]*@method[ \t]+(?:static[ \t]+)?(?:\S+[ \t]+)??(\w+)\(~m'), + $additionalMethods, + ), $method); + throw new MemberAccessException("Call to undefined method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.')); + } + } + + + /** + * @return never + * @throws MemberAccessException + */ + public static function strictStaticCall(string $class, string $method): void + { + $trace = debug_backtrace(0, 3); // suppose this method is called from __callStatic() + $context = ($trace[1]['function'] ?? null) === '__callStatic' + ? ($trace[2]['class'] ?? null) + : null; + + if ($context && is_a($class, $context, true) && method_exists($context, $method)) { // called parent::$method() + $class = get_parent_class($context); + } + + if (method_exists($class, $method)) { // insufficient visibility + $rm = new \ReflectionMethod($class, $method); + $visibility = $rm->isPrivate() + ? 'private ' + : ($rm->isProtected() ? 'protected ' : ''); + throw new MemberAccessException("Call to {$visibility}method $class::$method() from " . ($context ? "scope $context." : 'global scope.')); + + } else { + $hint = self::getSuggestion( + array_filter((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC), fn($m) => $m->isStatic()), + $method, + ); + throw new MemberAccessException("Call to undefined static method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.')); + } + } + + + /** + * Returns array of magic properties defined by annotation @property. + * @return array of [name => bit mask] + * @internal + */ + public static function getMagicProperties(string $class): array + { + static $cache; + $props = &$cache[$class]; + if ($props !== null) { + return $props; + } + + $rc = new \ReflectionClass($class); + preg_match_all( + '~^ [ \t*]* @property(|-read|-write|-deprecated) [ \t]+ [^\s$]+ [ \t]+ \$ (\w+) ()~mx', + (string) $rc->getDocComment(), + $matches, + PREG_SET_ORDER, + ); + + $props = []; + foreach ($matches as [, $type, $name]) { + $uname = ucfirst($name); + $write = $type !== '-read' + && $rc->hasMethod($nm = 'set' . $uname) + && ($rm = $rc->getMethod($nm))->name === $nm && !$rm->isPrivate() && !$rm->isStatic(); + $read = $type !== '-write' + && ($rc->hasMethod($nm = 'get' . $uname) || $rc->hasMethod($nm = 'is' . $uname)) + && ($rm = $rc->getMethod($nm))->name === $nm && !$rm->isPrivate() && !$rm->isStatic(); + + if ($read || $write) { + $props[$name] = $read << 0 | ($nm[0] === 'g') << 1 | $rm->returnsReference() << 2 | $write << 3 | ($type === '-deprecated') << 4; + } + } + + foreach ($rc->getTraits() as $trait) { + $props += self::getMagicProperties($trait->name); + } + + if ($parent = get_parent_class($class)) { + $props += self::getMagicProperties($parent); + } + + return $props; + } + + + /** + * Finds the best suggestion (for 8-bit encoding). + * @param (\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionClass|\ReflectionProperty|string)[] $possibilities + * @internal + */ + public static function getSuggestion(array $possibilities, string $value): ?string + { + $norm = preg_replace($re = '#^(get|set|has|is|add)(?=[A-Z])#', '+', $value); + $best = null; + $min = (strlen($value) / 4 + 1) * 10 + .1; + foreach (array_unique($possibilities, SORT_REGULAR) as $item) { + $item = $item instanceof \Reflector ? $item->name : $item; + if ($item !== $value && ( + ($len = levenshtein($item, $value, 10, 11, 10)) < $min + || ($len = levenshtein(preg_replace($re, '*', $item), $norm, 10, 11, 10)) < $min + )) { + $min = $len; + $best = $item; + } + } + + return $best; + } + + + private static function parseFullDoc(\ReflectionClass $rc, string $pattern): array + { + do { + $doc[] = $rc->getDocComment(); + $traits = $rc->getTraits(); + while ($trait = array_pop($traits)) { + $doc[] = $trait->getDocComment(); + $traits += $trait->getTraits(); + } + } while ($rc = $rc->getParentClass()); + + return preg_match_all($pattern, implode('', $doc), $m) ? $m[1] : []; + } + + + /** + * Checks if the public non-static property exists. + * Returns 'event' if the property exists and has event like name + * @internal + */ + public static function hasProperty(string $class, string $name): bool|string + { + static $cache; + $prop = &$cache[$class][$name]; + if ($prop === null) { + $prop = false; + try { + $rp = new \ReflectionProperty($class, $name); + if ($rp->isPublic() && !$rp->isStatic()) { + $prop = $name >= 'onA' && $name < 'on_' ? 'event' : true; + } + } catch (\ReflectionException $e) { + } + } + + return $prop; + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Paginator.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Paginator.php new file mode 100644 index 00000000000..aa4812c0a3a --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Paginator.php @@ -0,0 +1,245 @@ + $firstItemOnPage + * @property-read int<0,max> $lastItemOnPage + * @property int $base + * @property-read bool $first + * @property-read bool $last + * @property-read int<0,max>|null $pageCount + * @property positive-int $itemsPerPage + * @property int<0,max>|null $itemCount + * @property-read int<0,max> $offset + * @property-read int<0,max>|null $countdownOffset + * @property-read int<0,max> $length + */ +class Paginator +{ + use Nette\SmartObject; + + private int $base = 1; + + /** @var positive-int */ + private int $itemsPerPage = 1; + + private int $page = 1; + + /** @var int<0, max>|null */ + private ?int $itemCount = null; + + + /** + * Sets current page number. + */ + public function setPage(int $page): static + { + $this->page = $page; + return $this; + } + + + /** + * Returns current page number. + */ + public function getPage(): int + { + return $this->base + $this->getPageIndex(); + } + + + /** + * Returns first page number. + */ + public function getFirstPage(): int + { + return $this->base; + } + + + /** + * Returns last page number. + */ + public function getLastPage(): ?int + { + return $this->itemCount === null + ? null + : $this->base + max(0, $this->getPageCount() - 1); + } + + + /** + * Returns the sequence number of the first element on the page + * @return int<0, max> + */ + public function getFirstItemOnPage(): int + { + return $this->itemCount !== 0 + ? $this->offset + 1 + : 0; + } + + + /** + * Returns the sequence number of the last element on the page + * @return int<0, max> + */ + public function getLastItemOnPage(): int + { + return $this->offset + $this->length; + } + + + /** + * Sets first page (base) number. + */ + public function setBase(int $base): static + { + $this->base = $base; + return $this; + } + + + /** + * Returns first page (base) number. + */ + public function getBase(): int + { + return $this->base; + } + + + /** + * Returns zero-based page number. + * @return int<0, max> + */ + protected function getPageIndex(): int + { + $index = max(0, $this->page - $this->base); + return $this->itemCount === null + ? $index + : min($index, max(0, $this->getPageCount() - 1)); + } + + + /** + * Is the current page the first one? + */ + public function isFirst(): bool + { + return $this->getPageIndex() === 0; + } + + + /** + * Is the current page the last one? + */ + public function isLast(): bool + { + return $this->itemCount === null + ? false + : $this->getPageIndex() >= $this->getPageCount() - 1; + } + + + /** + * Returns the total number of pages. + * @return int<0, max>|null + */ + public function getPageCount(): ?int + { + return $this->itemCount === null + ? null + : (int) ceil($this->itemCount / $this->itemsPerPage); + } + + + /** + * Sets the number of items to display on a single page. + */ + public function setItemsPerPage(int $itemsPerPage): static + { + $this->itemsPerPage = max(1, $itemsPerPage); + return $this; + } + + + /** + * Returns the number of items to display on a single page. + * @return positive-int + */ + public function getItemsPerPage(): int + { + return $this->itemsPerPage; + } + + + /** + * Sets the total number of items. + */ + public function setItemCount(?int $itemCount = null): static + { + $this->itemCount = $itemCount === null ? null : max(0, $itemCount); + return $this; + } + + + /** + * Returns the total number of items. + * @return int<0, max>|null + */ + public function getItemCount(): ?int + { + return $this->itemCount; + } + + + /** + * Returns the absolute index of the first item on current page. + * @return int<0, max> + */ + public function getOffset(): int + { + return $this->getPageIndex() * $this->itemsPerPage; + } + + + /** + * Returns the absolute index of the first item on current page in countdown paging. + * @return int<0, max>|null + */ + public function getCountdownOffset(): ?int + { + return $this->itemCount === null + ? null + : max(0, $this->itemCount - ($this->getPageIndex() + 1) * $this->itemsPerPage); + } + + + /** + * Returns the number of items on current page. + * @return int<0, max> + */ + public function getLength(): int + { + return $this->itemCount === null + ? $this->itemsPerPage + : min($this->itemsPerPage, $this->itemCount - $this->getPageIndex() * $this->itemsPerPage); + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Random.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Random.php new file mode 100644 index 00000000000..e636dd07207 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Random.php @@ -0,0 +1,54 @@ + implode('', range($m[0][0], $m[0][2])), + $charlist, + ); + $charlist = count_chars($charlist, mode: 3); + $chLen = strlen($charlist); + + if ($length < 1) { + throw new Nette\InvalidArgumentException('Length must be greater than zero.'); + } elseif ($chLen < 2) { + throw new Nette\InvalidArgumentException('Character list must contain at least two chars.'); + } elseif (PHP_VERSION_ID >= 80300) { + return (new Randomizer)->getBytesFromString($charlist, $length); + } + + $res = ''; + for ($i = 0; $i < $length; $i++) { + $res .= $charlist[random_int(0, $chLen - 1)]; + } + + return $res; + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Reflection.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Reflection.php new file mode 100644 index 00000000000..e684762289a --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Reflection.php @@ -0,0 +1,321 @@ +isDefaultValueConstant()) { + $const = $orig = $param->getDefaultValueConstantName(); + $pair = explode('::', $const); + if (isset($pair[1])) { + $pair[0] = Type::resolve($pair[0], $param); + try { + $rcc = new \ReflectionClassConstant($pair[0], $pair[1]); + } catch (\ReflectionException $e) { + $name = self::toString($param); + throw new \ReflectionException("Unable to resolve constant $orig used as default value of $name.", 0, $e); + } + + return $rcc->getValue(); + + } elseif (!defined($const)) { + $const = substr((string) strrchr($const, '\\'), 1); + if (!defined($const)) { + $name = self::toString($param); + throw new \ReflectionException("Unable to resolve constant $orig used as default value of $name."); + } + } + + return constant($const); + } + + return $param->getDefaultValue(); + } + + + /** + * Returns a reflection of a class or trait that contains a declaration of given property. Property can also be declared in the trait. + */ + public static function getPropertyDeclaringClass(\ReflectionProperty $prop): \ReflectionClass + { + foreach ($prop->getDeclaringClass()->getTraits() as $trait) { + if ($trait->hasProperty($prop->name) + // doc-comment guessing as workaround for insufficient PHP reflection + && $trait->getProperty($prop->name)->getDocComment() === $prop->getDocComment() + ) { + return self::getPropertyDeclaringClass($trait->getProperty($prop->name)); + } + } + + return $prop->getDeclaringClass(); + } + + + /** + * Returns a reflection of a method that contains a declaration of $method. + * Usually, each method is its own declaration, but the body of the method can also be in the trait and under a different name. + */ + public static function getMethodDeclaringMethod(\ReflectionMethod $method): \ReflectionMethod + { + // file & line guessing as workaround for insufficient PHP reflection + $decl = $method->getDeclaringClass(); + if ($decl->getFileName() === $method->getFileName() + && $decl->getStartLine() <= $method->getStartLine() + && $decl->getEndLine() >= $method->getEndLine() + ) { + return $method; + } + + $hash = [$method->getFileName(), $method->getStartLine(), $method->getEndLine()]; + if (($alias = $decl->getTraitAliases()[$method->name] ?? null) + && ($m = new \ReflectionMethod(...explode('::', $alias, 2))) + && $hash === [$m->getFileName(), $m->getStartLine(), $m->getEndLine()] + ) { + return self::getMethodDeclaringMethod($m); + } + + foreach ($decl->getTraits() as $trait) { + if ($trait->hasMethod($method->name) + && ($m = $trait->getMethod($method->name)) + && $hash === [$m->getFileName(), $m->getStartLine(), $m->getEndLine()] + ) { + return self::getMethodDeclaringMethod($m); + } + } + + return $method; + } + + + /** + * Finds out if reflection has access to PHPdoc comments. Comments may not be available due to the opcode cache. + */ + public static function areCommentsAvailable(): bool + { + static $res; + return $res ?? $res = (bool) (new \ReflectionMethod(self::class, __FUNCTION__))->getDocComment(); + } + + + public static function toString(\Reflector $ref): string + { + if ($ref instanceof \ReflectionClass) { + return $ref->name; + } elseif ($ref instanceof \ReflectionMethod) { + return $ref->getDeclaringClass()->name . '::' . $ref->name . '()'; + } elseif ($ref instanceof \ReflectionFunction) { + return PHP_VERSION_ID >= 80200 && $ref->isAnonymous() + ? '{closure}()' + : $ref->name . '()'; + } elseif ($ref instanceof \ReflectionProperty) { + return self::getPropertyDeclaringClass($ref)->name . '::$' . $ref->name; + } elseif ($ref instanceof \ReflectionParameter) { + return '$' . $ref->name . ' in ' . self::toString($ref->getDeclaringFunction()); + } else { + throw new Nette\InvalidArgumentException; + } + } + + + /** + * Expands the name of the class to full name in the given context of given class. + * Thus, it returns how the PHP parser would understand $name if it were written in the body of the class $context. + * @throws Nette\InvalidArgumentException + */ + public static function expandClassName(string $name, \ReflectionClass $context): string + { + $lower = strtolower($name); + if (empty($name)) { + throw new Nette\InvalidArgumentException('Class name must not be empty.'); + + } elseif (Validators::isBuiltinType($lower)) { + return $lower; + + } elseif ($lower === 'self' || $lower === 'static') { + return $context->name; + + } elseif ($lower === 'parent') { + return $context->getParentClass() + ? $context->getParentClass()->name + : 'parent'; + + } elseif ($name[0] === '\\') { // fully qualified name + return ltrim($name, '\\'); + } + + $uses = self::getUseStatements($context); + $parts = explode('\\', $name, 2); + if (isset($uses[$parts[0]])) { + $parts[0] = $uses[$parts[0]]; + return implode('\\', $parts); + + } elseif ($context->inNamespace()) { + return $context->getNamespaceName() . '\\' . $name; + + } else { + return $name; + } + } + + + /** @return array of [alias => class] */ + public static function getUseStatements(\ReflectionClass $class): array + { + if ($class->isAnonymous()) { + throw new Nette\NotImplementedException('Anonymous classes are not supported.'); + } + + static $cache = []; + if (!isset($cache[$name = $class->name])) { + if ($class->isInternal()) { + $cache[$name] = []; + } else { + $code = file_get_contents($class->getFileName()); + $cache = self::parseUseStatements($code, $name) + $cache; + } + } + + return $cache[$name]; + } + + + /** + * Parses PHP code to [class => [alias => class, ...]] + */ + private static function parseUseStatements(string $code, ?string $forClass = null): array + { + try { + $tokens = \PhpToken::tokenize($code, TOKEN_PARSE); + } catch (\ParseError $e) { + trigger_error($e->getMessage(), E_USER_NOTICE); + $tokens = []; + } + + $namespace = $class = null; + $classLevel = $level = 0; + $res = $uses = []; + + $nameTokens = [T_STRING, T_NS_SEPARATOR, T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED]; + + while ($token = current($tokens)) { + next($tokens); + switch ($token->id) { + case T_NAMESPACE: + $namespace = ltrim(self::fetch($tokens, $nameTokens) . '\\', '\\'); + $uses = []; + break; + + case T_CLASS: + case T_INTERFACE: + case T_TRAIT: + case PHP_VERSION_ID < 80100 ? T_CLASS : T_ENUM: + if ($name = self::fetch($tokens, T_STRING)) { + $class = $namespace . $name; + $classLevel = $level + 1; + $res[$class] = $uses; + if ($class === $forClass) { + return $res; + } + } + + break; + + case T_USE: + while (!$class && ($name = self::fetch($tokens, $nameTokens))) { + $name = ltrim($name, '\\'); + if (self::fetch($tokens, '{')) { + while ($suffix = self::fetch($tokens, $nameTokens)) { + if (self::fetch($tokens, T_AS)) { + $uses[self::fetch($tokens, T_STRING)] = $name . $suffix; + } else { + $tmp = explode('\\', $suffix); + $uses[end($tmp)] = $name . $suffix; + } + + if (!self::fetch($tokens, ',')) { + break; + } + } + } elseif (self::fetch($tokens, T_AS)) { + $uses[self::fetch($tokens, T_STRING)] = $name; + + } else { + $tmp = explode('\\', $name); + $uses[end($tmp)] = $name; + } + + if (!self::fetch($tokens, ',')) { + break; + } + } + + break; + + case T_CURLY_OPEN: + case T_DOLLAR_OPEN_CURLY_BRACES: + case ord('{'): + $level++; + break; + + case ord('}'): + if ($level === $classLevel) { + $class = $classLevel = 0; + } + + $level--; + } + } + + return $res; + } + + + private static function fetch(array &$tokens, string|int|array $take): ?string + { + $res = null; + while ($token = current($tokens)) { + if ($token->is($take)) { + $res .= $token->text; + } elseif (!$token->is([T_DOC_COMMENT, T_WHITESPACE, T_COMMENT])) { + break; + } + + next($tokens); + } + + return $res; + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/ReflectionMethod.php b/tools/.phpstan/vendor/nette/utils/src/Utils/ReflectionMethod.php new file mode 100644 index 00000000000..2a8a55c6219 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/ReflectionMethod.php @@ -0,0 +1,38 @@ +originalClass = new \ReflectionClass($objectOrMethod); + } + + + public function getOriginalClass(): \ReflectionClass + { + return $this->originalClass; + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Strings.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Strings.php new file mode 100644 index 00000000000..19d20f80556 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Strings.php @@ -0,0 +1,728 @@ += 0xD800 && $code <= 0xDFFF) || $code > 0x10FFFF) { + throw new Nette\InvalidArgumentException('Code point must be in range 0x0 to 0xD7FF or 0xE000 to 0x10FFFF.'); + } elseif (!extension_loaded('iconv')) { + throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.'); + } + + return iconv('UTF-32BE', 'UTF-8//IGNORE', pack('N', $code)); + } + + + /** + * Returns a code point of specific character in UTF-8 (number in range 0x0000..D7FF or 0xE000..10FFFF). + */ + public static function ord(string $c): int + { + if (!extension_loaded('iconv')) { + throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.'); + } + + $tmp = iconv('UTF-8', 'UTF-32BE//IGNORE', $c); + if (!$tmp) { + throw new Nette\InvalidArgumentException('Invalid UTF-8 character "' . ($c === '' ? '' : '\x' . strtoupper(bin2hex($c))) . '".'); + } + + return unpack('N', $tmp)[1]; + } + + + /** + * @deprecated use str_starts_with() + */ + public static function startsWith(string $haystack, string $needle): bool + { + return str_starts_with($haystack, $needle); + } + + + /** + * @deprecated use str_ends_with() + */ + public static function endsWith(string $haystack, string $needle): bool + { + return str_ends_with($haystack, $needle); + } + + + /** + * @deprecated use str_contains() + */ + public static function contains(string $haystack, string $needle): bool + { + return str_contains($haystack, $needle); + } + + + /** + * Returns a part of UTF-8 string specified by starting position and length. If start is negative, + * the returned string will start at the start'th character from the end of string. + */ + public static function substring(string $s, int $start, ?int $length = null): string + { + if (function_exists('mb_substr')) { + return mb_substr($s, $start, $length, 'UTF-8'); // MB is much faster + } elseif (!extension_loaded('iconv')) { + throw new Nette\NotSupportedException(__METHOD__ . '() requires extension ICONV or MBSTRING, neither is loaded.'); + } elseif ($length === null) { + $length = self::length($s); + } elseif ($start < 0 && $length < 0) { + $start += self::length($s); // unifies iconv_substr behavior with mb_substr + } + + return iconv_substr($s, $start, $length, 'UTF-8'); + } + + + /** + * Removes control characters, normalizes line breaks to `\n`, removes leading and trailing blank lines, + * trims end spaces on lines, normalizes UTF-8 to the normal form of NFC. + */ + public static function normalize(string $s): string + { + // convert to compressed normal form (NFC) + if (class_exists('Normalizer', false) && ($n = \Normalizer::normalize($s, \Normalizer::FORM_C)) !== false) { + $s = $n; + } + + $s = self::unixNewLines($s); + + // remove control characters; leave \t + \n + $s = self::pcre('preg_replace', ['#[\x00-\x08\x0B-\x1F\x7F-\x9F]+#u', '', $s]); + + // right trim + $s = self::pcre('preg_replace', ['#[\t ]+$#m', '', $s]); + + // leading and trailing blank lines + $s = trim($s, "\n"); + + return $s; + } + + + /** @deprecated use Strings::unixNewLines() */ + public static function normalizeNewLines(string $s): string + { + return self::unixNewLines($s); + } + + + /** + * Converts line endings to \n used on Unix-like systems. + * Line endings are: \n, \r, \r\n, U+2028 line separator, U+2029 paragraph separator. + */ + public static function unixNewLines(string $s): string + { + return preg_replace("~\r\n?|\u{2028}|\u{2029}~", "\n", $s); + } + + + /** + * Converts line endings to platform-specific, i.e. \r\n on Windows and \n elsewhere. + * Line endings are: \n, \r, \r\n, U+2028 line separator, U+2029 paragraph separator. + */ + public static function platformNewLines(string $s): string + { + return preg_replace("~\r\n?|\n|\u{2028}|\u{2029}~", PHP_EOL, $s); + } + + + /** + * Converts UTF-8 string to ASCII, ie removes diacritics etc. + */ + public static function toAscii(string $s): string + { + $iconv = defined('ICONV_IMPL') ? trim(ICONV_IMPL, '"\'') : null; + static $transliterator = null; + if ($transliterator === null) { + if (class_exists('Transliterator', false)) { + $transliterator = \Transliterator::create('Any-Latin; Latin-ASCII'); + } else { + trigger_error(__METHOD__ . "(): it is recommended to enable PHP extensions 'intl'.", E_USER_NOTICE); + $transliterator = false; + } + } + + // remove control characters and check UTF-8 validity + $s = self::pcre('preg_replace', ['#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{2FF}\x{370}-\x{10FFFF}]#u', '', $s]); + + // transliteration (by Transliterator and iconv) is not optimal, replace some characters directly + $s = strtr($s, ["\u{201E}" => '"', "\u{201C}" => '"', "\u{201D}" => '"', "\u{201A}" => "'", "\u{2018}" => "'", "\u{2019}" => "'", "\u{B0}" => '^', "\u{42F}" => 'Ya', "\u{44F}" => 'ya', "\u{42E}" => 'Yu', "\u{44E}" => 'yu', "\u{c4}" => 'Ae', "\u{d6}" => 'Oe', "\u{dc}" => 'Ue', "\u{1e9e}" => 'Ss', "\u{e4}" => 'ae', "\u{f6}" => 'oe', "\u{fc}" => 'ue', "\u{df}" => 'ss']); // „ “ ” ‚ ‘ ’ ° Я я Ю ю Ä Ö Ü ẞ ä ö ü ß + if ($iconv !== 'libiconv') { + $s = strtr($s, ["\u{AE}" => '(R)', "\u{A9}" => '(c)', "\u{2026}" => '...', "\u{AB}" => '<<', "\u{BB}" => '>>', "\u{A3}" => 'lb', "\u{A5}" => 'yen', "\u{B2}" => '^2', "\u{B3}" => '^3', "\u{B5}" => 'u', "\u{B9}" => '^1', "\u{BA}" => 'o', "\u{BF}" => '?', "\u{2CA}" => "'", "\u{2CD}" => '_', "\u{2DD}" => '"', "\u{1FEF}" => '', "\u{20AC}" => 'EUR', "\u{2122}" => 'TM', "\u{212E}" => 'e', "\u{2190}" => '<-', "\u{2191}" => '^', "\u{2192}" => '->', "\u{2193}" => 'V', "\u{2194}" => '<->']); // ® © … « » £ ¥ ² ³ µ ¹ º ¿ ˊ ˍ ˝ ` € ™ ℮ ← ↑ → ↓ ↔ + } + + if ($transliterator) { + $s = $transliterator->transliterate($s); + // use iconv because The transliterator leaves some characters out of ASCII, eg → ʾ + if ($iconv === 'glibc') { + $s = strtr($s, '?', "\x01"); // temporarily hide ? to distinguish them from the garbage that iconv creates + $s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s); + $s = str_replace(['?', "\x01"], ['', '?'], $s); // remove garbage and restore ? characters + } elseif ($iconv === 'libiconv') { + $s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s); + } else { // null or 'unknown' (#216) + $s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]); // remove non-ascii chars + } + } elseif ($iconv === 'glibc' || $iconv === 'libiconv') { + // temporarily hide these characters to distinguish them from the garbage that iconv creates + $s = strtr($s, '`\'"^~?', "\x01\x02\x03\x04\x05\x06"); + if ($iconv === 'glibc') { + // glibc implementation is very limited. transliterate into Windows-1250 and then into ASCII, so most Eastern European characters are preserved + $s = iconv('UTF-8', 'WINDOWS-1250//TRANSLIT//IGNORE', $s); + $s = strtr( + $s, + "\xa5\xa3\xbc\x8c\xa7\x8a\xaa\x8d\x8f\x8e\xaf\xb9\xb3\xbe\x9c\x9a\xba\x9d\x9f\x9e\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xfe\x96\xa0\x8b\x97\x9b\xa6\xad\xb7", + 'ALLSSSSTZZZallssstzzzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTsraaaalccceeeeiiddnnooooruuuuyt- <->|-.', + ); + $s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]); + } else { + $s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s); + } + + // remove garbage that iconv creates during transliteration (eg Ý -> Y') + $s = str_replace(['`', "'", '"', '^', '~', '?'], '', $s); + // restore temporarily hidden characters + $s = strtr($s, "\x01\x02\x03\x04\x05\x06", '`\'"^~?'); + } else { + $s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]); // remove non-ascii chars + } + + return $s; + } + + + /** + * Modifies the UTF-8 string to the form used in the URL, ie removes diacritics and replaces all characters + * except letters of the English alphabet and numbers with a hyphens. + */ + public static function webalize(string $s, ?string $charlist = null, bool $lower = true): string + { + $s = self::toAscii($s); + if ($lower) { + $s = strtolower($s); + } + + $s = self::pcre('preg_replace', ['#[^a-z0-9' . ($charlist !== null ? preg_quote($charlist, '#') : '') . ']+#i', '-', $s]); + $s = trim($s, '-'); + return $s; + } + + + /** + * Truncates a UTF-8 string to given maximal length, while trying not to split whole words. Only if the string is truncated, + * an ellipsis (or something else set with third argument) is appended to the string. + */ + public static function truncate(string $s, int $maxLen, string $append = "\u{2026}"): string + { + if (self::length($s) > $maxLen) { + $maxLen -= self::length($append); + if ($maxLen < 1) { + return $append; + + } elseif ($matches = self::match($s, '#^.{1,' . $maxLen . '}(?=[\s\x00-/:-@\[-`{-~])#us')) { + return $matches[0] . $append; + + } else { + return self::substring($s, 0, $maxLen) . $append; + } + } + + return $s; + } + + + /** + * Indents a multiline text from the left. Second argument sets how many indentation chars should be used, + * while the indent itself is the third argument (*tab* by default). + */ + public static function indent(string $s, int $level = 1, string $chars = "\t"): string + { + if ($level > 0) { + $s = self::replace($s, '#(?:^|[\r\n]+)(?=[^\r\n])#', '$0' . str_repeat($chars, $level)); + } + + return $s; + } + + + /** + * Converts all characters of UTF-8 string to lower case. + */ + public static function lower(string $s): string + { + return mb_strtolower($s, 'UTF-8'); + } + + + /** + * Converts the first character of a UTF-8 string to lower case and leaves the other characters unchanged. + */ + public static function firstLower(string $s): string + { + return self::lower(self::substring($s, 0, 1)) . self::substring($s, 1); + } + + + /** + * Converts all characters of a UTF-8 string to upper case. + */ + public static function upper(string $s): string + { + return mb_strtoupper($s, 'UTF-8'); + } + + + /** + * Converts the first character of a UTF-8 string to upper case and leaves the other characters unchanged. + */ + public static function firstUpper(string $s): string + { + return self::upper(self::substring($s, 0, 1)) . self::substring($s, 1); + } + + + /** + * Converts the first character of every word of a UTF-8 string to upper case and the others to lower case. + */ + public static function capitalize(string $s): string + { + return mb_convert_case($s, MB_CASE_TITLE, 'UTF-8'); + } + + + /** + * Compares two UTF-8 strings or their parts, without taking character case into account. If length is null, whole strings are compared, + * if it is negative, the corresponding number of characters from the end of the strings is compared, + * otherwise the appropriate number of characters from the beginning is compared. + */ + public static function compare(string $left, string $right, ?int $length = null): bool + { + if (class_exists('Normalizer', false)) { + $left = \Normalizer::normalize($left, \Normalizer::FORM_D); // form NFD is faster + $right = \Normalizer::normalize($right, \Normalizer::FORM_D); // form NFD is faster + } + + if ($length < 0) { + $left = self::substring($left, $length, -$length); + $right = self::substring($right, $length, -$length); + } elseif ($length !== null) { + $left = self::substring($left, 0, $length); + $right = self::substring($right, 0, $length); + } + + return self::lower($left) === self::lower($right); + } + + + /** + * Finds the common prefix of strings or returns empty string if the prefix was not found. + * @param string[] $strings + */ + public static function findPrefix(array $strings): string + { + $first = array_shift($strings); + for ($i = 0; $i < strlen($first); $i++) { + foreach ($strings as $s) { + if (!isset($s[$i]) || $first[$i] !== $s[$i]) { + while ($i && $first[$i - 1] >= "\x80" && $first[$i] >= "\x80" && $first[$i] < "\xC0") { + $i--; + } + + return substr($first, 0, $i); + } + } + } + + return $first; + } + + + /** + * Returns number of characters (not bytes) in UTF-8 string. + * That is the number of Unicode code points which may differ from the number of graphemes. + */ + public static function length(string $s): int + { + return match (true) { + extension_loaded('mbstring') => mb_strlen($s, 'UTF-8'), + extension_loaded('iconv') => iconv_strlen($s, 'UTF-8'), + default => strlen(@utf8_decode($s)), // deprecated + }; + } + + + /** + * Removes all left and right side spaces (or the characters passed as second argument) from a UTF-8 encoded string. + */ + public static function trim(string $s, string $charlist = self::TrimCharacters): string + { + $charlist = preg_quote($charlist, '#'); + return self::replace($s, '#^[' . $charlist . ']+|[' . $charlist . ']+$#Du', ''); + } + + + /** + * Pads a UTF-8 string to given length by prepending the $pad string to the beginning. + * @param non-empty-string $pad + */ + public static function padLeft(string $s, int $length, string $pad = ' '): string + { + $length = max(0, $length - self::length($s)); + $padLen = self::length($pad); + return str_repeat($pad, (int) ($length / $padLen)) . self::substring($pad, 0, $length % $padLen) . $s; + } + + + /** + * Pads UTF-8 string to given length by appending the $pad string to the end. + * @param non-empty-string $pad + */ + public static function padRight(string $s, int $length, string $pad = ' '): string + { + $length = max(0, $length - self::length($s)); + $padLen = self::length($pad); + return $s . str_repeat($pad, (int) ($length / $padLen)) . self::substring($pad, 0, $length % $padLen); + } + + + /** + * Reverses UTF-8 string. + */ + public static function reverse(string $s): string + { + if (!extension_loaded('iconv')) { + throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.'); + } + + return iconv('UTF-32LE', 'UTF-8', strrev(iconv('UTF-8', 'UTF-32BE', $s))); + } + + + /** + * Returns part of $haystack before $nth occurence of $needle or returns null if the needle was not found. + * Negative value means searching from the end. + */ + public static function before(string $haystack, string $needle, int $nth = 1): ?string + { + $pos = self::pos($haystack, $needle, $nth); + return $pos === null + ? null + : substr($haystack, 0, $pos); + } + + + /** + * Returns part of $haystack after $nth occurence of $needle or returns null if the needle was not found. + * Negative value means searching from the end. + */ + public static function after(string $haystack, string $needle, int $nth = 1): ?string + { + $pos = self::pos($haystack, $needle, $nth); + return $pos === null + ? null + : substr($haystack, $pos + strlen($needle)); + } + + + /** + * Returns position in characters of $nth occurence of $needle in $haystack or null if the $needle was not found. + * Negative value of `$nth` means searching from the end. + */ + public static function indexOf(string $haystack, string $needle, int $nth = 1): ?int + { + $pos = self::pos($haystack, $needle, $nth); + return $pos === null + ? null + : self::length(substr($haystack, 0, $pos)); + } + + + /** + * Returns position in characters of $nth occurence of $needle in $haystack or null if the needle was not found. + */ + private static function pos(string $haystack, string $needle, int $nth = 1): ?int + { + if (!$nth) { + return null; + } elseif ($nth > 0) { + if ($needle === '') { + return 0; + } + + $pos = 0; + while (($pos = strpos($haystack, $needle, $pos)) !== false && --$nth) { + $pos++; + } + } else { + $len = strlen($haystack); + if ($needle === '') { + return $len; + } elseif ($len === 0) { + return null; + } + + $pos = $len - 1; + while (($pos = strrpos($haystack, $needle, $pos - $len)) !== false && ++$nth) { + $pos--; + } + } + + return Helpers::falseToNull($pos); + } + + + /** + * Divides the string into arrays according to the regular expression. Expressions in parentheses will be captured and returned as well. + */ + public static function split( + string $subject, + #[Language('RegExp')] + string $pattern, + bool|int $captureOffset = false, + bool $skipEmpty = false, + int $limit = -1, + bool $utf8 = false, + ): array + { + $flags = is_int($captureOffset) // back compatibility + ? $captureOffset + : ($captureOffset ? PREG_SPLIT_OFFSET_CAPTURE : 0) | ($skipEmpty ? PREG_SPLIT_NO_EMPTY : 0); + + $pattern .= $utf8 ? 'u' : ''; + $m = self::pcre('preg_split', [$pattern, $subject, $limit, $flags | PREG_SPLIT_DELIM_CAPTURE]); + return $utf8 && $captureOffset + ? self::bytesToChars($subject, [$m])[0] + : $m; + } + + + /** + * Searches the string for the part matching the regular expression and returns + * an array with the found expression and individual subexpressions, or `null`. + */ + public static function match( + string $subject, + #[Language('RegExp')] + string $pattern, + bool|int $captureOffset = false, + int $offset = 0, + bool $unmatchedAsNull = false, + bool $utf8 = false, + ): ?array + { + $flags = is_int($captureOffset) // back compatibility + ? $captureOffset + : ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0); + + if ($utf8) { + $offset = strlen(self::substring($subject, 0, $offset)); + $pattern .= 'u'; + } + + if ($offset > strlen($subject)) { + return null; + } elseif (!self::pcre('preg_match', [$pattern, $subject, &$m, $flags, $offset])) { + return null; + } elseif ($utf8 && $captureOffset) { + return self::bytesToChars($subject, [$m])[0]; + } else { + return $m; + } + } + + + /** + * Searches the string for all occurrences matching the regular expression and + * returns an array of arrays containing the found expression and each subexpression. + * @return ($lazy is true ? \Generator : array[]) + */ + public static function matchAll( + string $subject, + #[Language('RegExp')] + string $pattern, + bool|int $captureOffset = false, + int $offset = 0, + bool $unmatchedAsNull = false, + bool $patternOrder = false, + bool $utf8 = false, + bool $lazy = false, + ): array|\Generator + { + if ($utf8) { + $offset = strlen(self::substring($subject, 0, $offset)); + $pattern .= 'u'; + } + + if ($lazy) { + $flags = PREG_OFFSET_CAPTURE | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0); + return (function () use ($utf8, $captureOffset, $flags, $subject, $pattern, $offset) { + $counter = 0; + while ( + $offset <= strlen($subject) - ($counter ? 1 : 0) + && self::pcre('preg_match', [$pattern, $subject, &$m, $flags, $offset]) + ) { + $offset = $m[0][1] + max(1, strlen($m[0][0])); + if (!$captureOffset) { + $m = array_map(fn($item) => $item[0], $m); + } elseif ($utf8) { + $m = self::bytesToChars($subject, [$m])[0]; + } + yield $counter++ => $m; + } + })(); + } + + if ($offset > strlen($subject)) { + return []; + } + + $flags = is_int($captureOffset) // back compatibility + ? $captureOffset + : ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0) | ($patternOrder ? PREG_PATTERN_ORDER : 0); + + self::pcre('preg_match_all', [ + $pattern, $subject, &$m, + ($flags & PREG_PATTERN_ORDER) ? $flags : ($flags | PREG_SET_ORDER), + $offset, + ]); + return $utf8 && $captureOffset + ? self::bytesToChars($subject, $m) + : $m; + } + + + /** + * Replaces all occurrences matching regular expression $pattern which can be string or array in the form `pattern => replacement`. + */ + public static function replace( + string $subject, + #[Language('RegExp')] + string|array $pattern, + string|callable $replacement = '', + int $limit = -1, + bool $captureOffset = false, + bool $unmatchedAsNull = false, + bool $utf8 = false, + ): string + { + if (is_object($replacement) || is_array($replacement)) { + if (!is_callable($replacement, false, $textual)) { + throw new Nette\InvalidStateException("Callback '$textual' is not callable."); + } + + $flags = ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0); + if ($utf8) { + $pattern .= 'u'; + if ($captureOffset) { + $replacement = fn($m) => $replacement(self::bytesToChars($subject, [$m])[0]); + } + } + + return self::pcre('preg_replace_callback', [$pattern, $replacement, $subject, $limit, 0, $flags]); + + } elseif (is_array($pattern) && is_string(key($pattern))) { + $replacement = array_values($pattern); + $pattern = array_keys($pattern); + } + + if ($utf8) { + $pattern = array_map(fn($item) => $item . 'u', (array) $pattern); + } + + return self::pcre('preg_replace', [$pattern, $replacement, $subject, $limit]); + } + + + private static function bytesToChars(string $s, array $groups): array + { + $lastBytes = $lastChars = 0; + foreach ($groups as &$matches) { + foreach ($matches as &$match) { + if ($match[1] > $lastBytes) { + $lastChars += self::length(substr($s, $lastBytes, $match[1] - $lastBytes)); + } elseif ($match[1] < $lastBytes) { + $lastChars -= self::length(substr($s, $match[1], $lastBytes - $match[1])); + } + + $lastBytes = $match[1]; + $match[1] = $lastChars; + } + } + + return $groups; + } + + + /** @internal */ + public static function pcre(string $func, array $args) + { + $res = Callback::invokeSafe($func, $args, function (string $message) use ($args): void { + // compile-time error, not detectable by preg_last_error + throw new RegexpException($message . ' in pattern: ' . implode(' or ', (array) $args[0])); + }); + + if (($code = preg_last_error()) // run-time error, but preg_last_error & return code are liars + && ($res === null || !in_array($func, ['preg_filter', 'preg_replace_callback', 'preg_replace'], true)) + ) { + throw new RegexpException(preg_last_error_msg() + . ' (pattern: ' . implode(' or ', (array) $args[0]) . ')', $code); + } + + return $res; + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Type.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Type.php new file mode 100644 index 00000000000..f1a8fa1c953 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Type.php @@ -0,0 +1,269 @@ + */ + private array $types; + private bool $simple; + private string $kind; // | & + + + /** + * Creates a Type object based on reflection. Resolves self, static and parent to the actual class name. + * If the subject has no type, it returns null. + */ + public static function fromReflection( + \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $reflection, + ): ?self + { + $type = $reflection instanceof \ReflectionFunctionAbstract + ? $reflection->getReturnType() ?? (PHP_VERSION_ID >= 80100 && $reflection instanceof \ReflectionMethod ? $reflection->getTentativeReturnType() : null) + : $reflection->getType(); + + return $type ? self::fromReflectionType($type, $reflection, asObject: true) : null; + } + + + private static function fromReflectionType(\ReflectionType $type, $of, bool $asObject): self|string + { + if ($type instanceof \ReflectionNamedType) { + $name = self::resolve($type->getName(), $of); + return $asObject + ? new self($type->allowsNull() && $name !== 'mixed' ? [$name, 'null'] : [$name]) + : $name; + + } elseif ($type instanceof \ReflectionUnionType || $type instanceof \ReflectionIntersectionType) { + return new self( + array_map(fn($t) => self::fromReflectionType($t, $of, asObject: false), $type->getTypes()), + $type instanceof \ReflectionUnionType ? '|' : '&', + ); + + } else { + throw new Nette\InvalidStateException('Unexpected type of ' . Reflection::toString($of)); + } + } + + + /** + * Creates the Type object according to the text notation. + */ + public static function fromString(string $type): self + { + if (!Validators::isTypeDeclaration($type)) { + throw new Nette\InvalidArgumentException("Invalid type '$type'."); + } + + if ($type[0] === '?') { + return new self([substr($type, 1), 'null']); + } + + $unions = []; + foreach (explode('|', $type) as $part) { + $part = explode('&', trim($part, '()')); + $unions[] = count($part) === 1 ? $part[0] : new self($part, '&'); + } + + return count($unions) === 1 && $unions[0] instanceof self + ? $unions[0] + : new self($unions); + } + + + /** + * Resolves 'self', 'static' and 'parent' to the actual class name. + */ + public static function resolve( + string $type, + \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $of, + ): string + { + $lower = strtolower($type); + if ($of instanceof \ReflectionFunction) { + return $type; + } elseif ($lower === 'self') { + return $of->getDeclaringClass()->name; + } elseif ($lower === 'static') { + return ($of instanceof ReflectionMethod ? $of->getOriginalClass() : $of->getDeclaringClass())->name; + } elseif ($lower === 'parent' && $of->getDeclaringClass()->getParentClass()) { + return $of->getDeclaringClass()->getParentClass()->name; + } else { + return $type; + } + } + + + private function __construct(array $types, string $kind = '|') + { + $o = array_search('null', $types, strict: true); + if ($o !== false) { // null as last + array_splice($types, $o, 1); + $types[] = 'null'; + } + + $this->types = $types; + $this->simple = is_string($types[0]) && ($types[1] ?? 'null') === 'null'; + $this->kind = count($types) > 1 ? $kind : ''; + } + + + public function __toString(): string + { + $multi = count($this->types) > 1; + if ($this->simple) { + return ($multi ? '?' : '') . $this->types[0]; + } + + $res = []; + foreach ($this->types as $type) { + $res[] = $type instanceof self && $multi ? "($type)" : $type; + } + return implode($this->kind, $res); + } + + + /** + * Returns the array of subtypes that make up the compound type as strings. + * @return array + */ + public function getNames(): array + { + return array_map(fn($t) => $t instanceof self ? $t->getNames() : $t, $this->types); + } + + + /** + * Returns the array of subtypes that make up the compound type as Type objects: + * @return self[] + */ + public function getTypes(): array + { + return array_map(fn($t) => $t instanceof self ? $t : new self([$t]), $this->types); + } + + + /** + * Returns the type name for simple types, otherwise null. + */ + public function getSingleName(): ?string + { + return $this->simple + ? $this->types[0] + : null; + } + + + /** + * Returns true whether it is a union type. + */ + public function isUnion(): bool + { + return $this->kind === '|'; + } + + + /** + * Returns true whether it is an intersection type. + */ + public function isIntersection(): bool + { + return $this->kind === '&'; + } + + + /** + * Returns true whether it is a simple type. Single nullable types are also considered to be simple types. + */ + public function isSimple(): bool + { + return $this->simple; + } + + + /** @deprecated use isSimple() */ + public function isSingle(): bool + { + return $this->simple; + } + + + /** + * Returns true whether the type is both a simple and a PHP built-in type. + */ + public function isBuiltin(): bool + { + return $this->simple && Validators::isBuiltinType($this->types[0]); + } + + + /** + * Returns true whether the type is both a simple and a class name. + */ + public function isClass(): bool + { + return $this->simple && !Validators::isBuiltinType($this->types[0]); + } + + + /** + * Determines if type is special class name self/parent/static. + */ + public function isClassKeyword(): bool + { + return $this->simple && Validators::isClassKeyword($this->types[0]); + } + + + /** + * Verifies type compatibility. For example, it checks if a value of a certain type could be passed as a parameter. + */ + public function allows(string $subtype): bool + { + if ($this->types === ['mixed']) { + return true; + } + + $subtype = self::fromString($subtype); + return $subtype->isUnion() + ? Arrays::every($subtype->types, fn($t) => $this->allows2($t instanceof self ? $t->types : [$t])) + : $this->allows2($subtype->types); + } + + + private function allows2(array $subtypes): bool + { + return $this->isUnion() + ? Arrays::some($this->types, fn($t) => $this->allows3($t instanceof self ? $t->types : [$t], $subtypes)) + : $this->allows3($this->types, $subtypes); + } + + + private function allows3(array $types, array $subtypes): bool + { + return Arrays::every( + $types, + fn($type) => Arrays::some( + $subtypes, + fn($subtype) => Validators::isBuiltinType($type) + ? strcasecmp($type, $subtype) === 0 + : is_a($subtype, $type, allow_string: true), + ), + ); + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Validators.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Validators.php new file mode 100644 index 00000000000..940c3eb43f7 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Validators.php @@ -0,0 +1,417 @@ + 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1, + 'callable' => 1, 'iterable' => 1, 'void' => 1, 'null' => 1, 'mixed' => 1, 'false' => 1, + 'never' => 1, 'true' => 1, + ]; + + /** @var array */ + protected static $validators = [ + // PHP types + 'array' => 'is_array', + 'bool' => 'is_bool', + 'boolean' => 'is_bool', + 'float' => 'is_float', + 'int' => 'is_int', + 'integer' => 'is_int', + 'null' => 'is_null', + 'object' => 'is_object', + 'resource' => 'is_resource', + 'scalar' => 'is_scalar', + 'string' => 'is_string', + + // pseudo-types + 'callable' => [self::class, 'isCallable'], + 'iterable' => 'is_iterable', + 'list' => [Arrays::class, 'isList'], + 'mixed' => [self::class, 'isMixed'], + 'none' => [self::class, 'isNone'], + 'number' => [self::class, 'isNumber'], + 'numeric' => [self::class, 'isNumeric'], + 'numericint' => [self::class, 'isNumericInt'], + + // string patterns + 'alnum' => 'ctype_alnum', + 'alpha' => 'ctype_alpha', + 'digit' => 'ctype_digit', + 'lower' => 'ctype_lower', + 'pattern' => null, + 'space' => 'ctype_space', + 'unicode' => [self::class, 'isUnicode'], + 'upper' => 'ctype_upper', + 'xdigit' => 'ctype_xdigit', + + // syntax validation + 'email' => [self::class, 'isEmail'], + 'identifier' => [self::class, 'isPhpIdentifier'], + 'uri' => [self::class, 'isUri'], + 'url' => [self::class, 'isUrl'], + + // environment validation + 'class' => 'class_exists', + 'interface' => 'interface_exists', + 'directory' => 'is_dir', + 'file' => 'is_file', + 'type' => [self::class, 'isType'], + ]; + + /** @var array */ + protected static $counters = [ + 'string' => 'strlen', + 'unicode' => [Strings::class, 'length'], + 'array' => 'count', + 'list' => 'count', + 'alnum' => 'strlen', + 'alpha' => 'strlen', + 'digit' => 'strlen', + 'lower' => 'strlen', + 'space' => 'strlen', + 'upper' => 'strlen', + 'xdigit' => 'strlen', + ]; + + + /** + * Verifies that the value is of expected types separated by pipe. + * @throws AssertionException + */ + public static function assert(mixed $value, string $expected, string $label = 'variable'): void + { + if (!static::is($value, $expected)) { + $expected = str_replace(['|', ':'], [' or ', ' in range '], $expected); + $translate = ['boolean' => 'bool', 'integer' => 'int', 'double' => 'float', 'NULL' => 'null']; + $type = $translate[gettype($value)] ?? gettype($value); + if (is_int($value) || is_float($value) || (is_string($value) && strlen($value) < 40)) { + $type .= ' ' . var_export($value, return: true); + } elseif (is_object($value)) { + $type .= ' ' . $value::class; + } + + throw new AssertionException("The $label expects to be $expected, $type given."); + } + } + + + /** + * Verifies that element $key in array is of expected types separated by pipe. + * @param mixed[] $array + * @throws AssertionException + */ + public static function assertField( + array $array, + $key, + ?string $expected = null, + string $label = "item '%' in array", + ): void + { + if (!array_key_exists($key, $array)) { + throw new AssertionException('Missing ' . str_replace('%', $key, $label) . '.'); + + } elseif ($expected) { + static::assert($array[$key], $expected, str_replace('%', $key, $label)); + } + } + + + /** + * Verifies that the value is of expected types separated by pipe. + */ + public static function is(mixed $value, string $expected): bool + { + foreach (explode('|', $expected) as $item) { + if (str_ends_with($item, '[]')) { + if (is_iterable($value) && self::everyIs($value, substr($item, 0, -2))) { + return true; + } + + continue; + } elseif (str_starts_with($item, '?')) { + $item = substr($item, 1); + if ($value === null) { + return true; + } + } + + [$type] = $item = explode(':', $item, 2); + if (isset(static::$validators[$type])) { + try { + if (!static::$validators[$type]($value)) { + continue; + } + } catch (\TypeError $e) { + continue; + } + } elseif ($type === 'pattern') { + if (Strings::match($value, '|^' . ($item[1] ?? '') . '$|D')) { + return true; + } + + continue; + } elseif (!$value instanceof $type) { + continue; + } + + if (isset($item[1])) { + $length = $value; + if (isset(static::$counters[$type])) { + $length = static::$counters[$type]($value); + } + + $range = explode('..', $item[1]); + if (!isset($range[1])) { + $range[1] = $range[0]; + } + + if (($range[0] !== '' && $length < $range[0]) || ($range[1] !== '' && $length > $range[1])) { + continue; + } + } + + return true; + } + + return false; + } + + + /** + * Finds whether all values are of expected types separated by pipe. + * @param mixed[] $values + */ + public static function everyIs(iterable $values, string $expected): bool + { + foreach ($values as $value) { + if (!static::is($value, $expected)) { + return false; + } + } + + return true; + } + + + /** + * Checks if the value is an integer or a float. + * @return ($value is int|float ? true : false) + */ + public static function isNumber(mixed $value): bool + { + return is_int($value) || is_float($value); + } + + + /** + * Checks if the value is an integer or a integer written in a string. + * @return ($value is non-empty-string ? bool : ($value is int ? true : false)) + */ + public static function isNumericInt(mixed $value): bool + { + return is_int($value) || (is_string($value) && preg_match('#^[+-]?[0-9]+$#D', $value)); + } + + + /** + * Checks if the value is a number or a number written in a string. + * @return ($value is non-empty-string ? bool : ($value is int|float ? true : false)) + */ + public static function isNumeric(mixed $value): bool + { + return is_float($value) || is_int($value) || (is_string($value) && preg_match('#^[+-]?([0-9]++\.?[0-9]*|\.[0-9]+)$#D', $value)); + } + + + /** + * Checks if the value is a syntactically correct callback. + */ + public static function isCallable(mixed $value): bool + { + return $value && is_callable($value, syntax_only: true); + } + + + /** + * Checks if the value is a valid UTF-8 string. + */ + public static function isUnicode(mixed $value): bool + { + return is_string($value) && preg_match('##u', $value); + } + + + /** + * Checks if the value is 0, '', false or null. + * @return ($value is 0|''|false|null ? true : false) + */ + public static function isNone(mixed $value): bool + { + return $value == null; // intentionally == + } + + + /** @internal */ + public static function isMixed(): bool + { + return true; + } + + + /** + * Checks if a variable is a zero-based integer indexed array. + * @deprecated use Nette\Utils\Arrays::isList + * @return ($value is list ? true : false) + */ + public static function isList(mixed $value): bool + { + return Arrays::isList($value); + } + + + /** + * Checks if the value is in the given range [min, max], where the upper or lower limit can be omitted (null). + * Numbers, strings and DateTime objects can be compared. + */ + public static function isInRange(mixed $value, array $range): bool + { + if ($value === null || !(isset($range[0]) || isset($range[1]))) { + return false; + } + + $limit = $range[0] ?? $range[1]; + if (is_string($limit)) { + $value = (string) $value; + } elseif ($limit instanceof \DateTimeInterface) { + if (!$value instanceof \DateTimeInterface) { + return false; + } + } elseif (is_numeric($value)) { + $value *= 1; + } else { + return false; + } + + return (!isset($range[0]) || ($value >= $range[0])) && (!isset($range[1]) || ($value <= $range[1])); + } + + + /** + * Checks if the value is a valid email address. It does not verify that the domain actually exists, only the syntax is verified. + */ + public static function isEmail(string $value): bool + { + $atom = "[-a-z0-9!#$%&'*+/=?^_`{|}~]"; // RFC 5322 unquoted characters in local-part + $alpha = "a-z\x80-\xFF"; // superset of IDN + return (bool) preg_match(<< \\? (? [a-zA-Z_\x7f-\xff][\w\x7f-\xff]*) (\\ (?&name))* ) | + (? (?&type) (& (?&type))+ ) | + (? (?&type) | \( (?&intersection) \) ) (\| (?&upart))+ + )$~xAD + XX, $type); + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/exceptions.php b/tools/.phpstan/vendor/nette/utils/src/Utils/exceptions.php new file mode 100644 index 00000000000..30805ea3c48 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/exceptions.php @@ -0,0 +1,50 @@ + + array ( + 'install_path' => '/usr/local/src/phpunit/tools/.phpstan/vendor/ergebnis/phpstan-rules', + 'relative_install_path' => '../../../ergebnis/phpstan-rules', + 'extra' => + array ( + 'includes' => + array ( + 0 => 'rules.neon', + ), + ), + 'version' => '2.12.0', + 'phpstanVersionConstraint' => '>=2.1.8.0-dev, <3.0.0.0-dev', + ), + 'phpstan/phpstan-strict-rules' => + array ( + 'install_path' => '/usr/local/src/phpunit/tools/.phpstan/vendor/phpstan/phpstan-strict-rules', + 'relative_install_path' => '../../phpstan-strict-rules', + 'extra' => + array ( + 'includes' => + array ( + 0 => 'rules.neon', + ), + ), + 'version' => '2.0.7', + 'phpstanVersionConstraint' => '>=2.1.29.0-dev, <3.0.0.0-dev', + ), + 'tomasvotruba/type-coverage' => + array ( + 'install_path' => '/usr/local/src/phpunit/tools/.phpstan/vendor/tomasvotruba/type-coverage', + 'relative_install_path' => '../../../tomasvotruba/type-coverage', + 'extra' => + array ( + 'includes' => + array ( + 0 => 'config/extension.neon', + ), + ), + 'version' => '2.0.2', + 'phpstanVersionConstraint' => '>=2.0.0.0-dev, <3.0.0.0-dev', + ), +); + + public const NOT_INSTALLED = array ( +); + + /** @var string|null */ + public const PHPSTAN_VERSION_CONSTRAINT = '>=2.1.29.0-dev, <3.0.0.0-dev'; + + private function __construct() + { + } + +} diff --git a/tools/.phpstan/vendor/phpstan/extension-installer/src/Plugin.php b/tools/.phpstan/vendor/phpstan/extension-installer/src/Plugin.php new file mode 100644 index 00000000000..ec757351f91 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/extension-installer/src/Plugin.php @@ -0,0 +1,228 @@ + + */ + public static function getSubscribedEvents(): array + { + return [ + ScriptEvents::POST_INSTALL_CMD => 'process', + ScriptEvents::POST_UPDATE_CMD => 'process', + ]; + } + + public function process(Event $event): void + { + $io = $event->getIO(); + + if (!file_exists(__DIR__)) { + $io->write('phpstan/extension-installer: Package not found (probably scheduled for removal); extensions installation skipped.'); + return; + } + + $composer = $event->getComposer(); + $installationManager = $composer->getInstallationManager(); + + $generatedConfigFilePath = __DIR__ . '/GeneratedConfig.php'; + $oldGeneratedConfigFileHash = null; + if (is_file($generatedConfigFilePath)) { + $oldGeneratedConfigFileHash = md5_file($generatedConfigFilePath); + } + $notInstalledPackages = []; + $installedPackages = []; + $ignoredPackages = []; + + $data = []; + $fs = new Filesystem(); + $ignore = []; + + $packageExtra = $composer->getPackage()->getExtra(); + + if (isset($packageExtra['phpstan/extension-installer']['ignore'])) { + $ignore = $packageExtra['phpstan/extension-installer']['ignore']; + } + + $phpstanVersionConstraints = []; + + foreach ($composer->getRepositoryManager()->getLocalRepository()->getPackages() as $package) { + if ( + $package->getType() !== 'phpstan-extension' + && !isset($package->getExtra()['phpstan']) + ) { + if ( + strpos($package->getName(), 'phpstan') !== false + && !in_array($package->getName(), [ + 'phpstan/phpstan', + 'phpstan/phpstan-shim', + 'phpstan/phpdoc-parser', + 'phpstan/extension-installer', + ], true) + ) { + $notInstalledPackages[$package->getName()] = $package->getFullPrettyVersion(); + } + continue; + } + + if (in_array($package->getName(), $ignore, true)) { + $ignoredPackages[] = $package->getName(); + continue; + } + + $installPath = $installationManager->getInstallPath($package); + if ($installPath === null) { + continue; + } + + $absoluteInstallPath = $fs->isAbsolutePath($installPath) + ? $installPath + : getcwd() . DIRECTORY_SEPARATOR . $installPath; + + $packageRequires = $package->getRequires(); + $phpstanConstraint = null; + if (array_key_exists('phpstan/phpstan', $packageRequires)) { + $phpstanConstraint = $packageRequires['phpstan/phpstan']->getConstraint(); + if ($phpstanConstraint->getLowerBound()->isZero()) { + continue; + } + if ($phpstanConstraint->getUpperBound()->isPositiveInfinity()) { + continue; + } + $phpstanVersionConstraints[] = $phpstanConstraint; + } + + $data[$package->getName()] = [ + 'install_path' => $absoluteInstallPath, + 'relative_install_path' => $fs->findShortestPath(dirname($generatedConfigFilePath), $absoluteInstallPath, true), + 'extra' => $package->getExtra()['phpstan'] ?? null, + 'version' => $package->getFullPrettyVersion(), + 'phpstanVersionConstraint' => $phpstanConstraint !== null ? $this->constraintIntoString($phpstanConstraint) : null, + ]; + + $installedPackages[$package->getName()] = true; + } + + $phpstanVersionConstraint = null; + if (count($phpstanVersionConstraints) > 0 && class_exists(Intervals::class)) { + if (count($phpstanVersionConstraints) === 1) { + $multiConstraint = $phpstanVersionConstraints[0]; + } else { + $multiConstraint = new MultiConstraint($phpstanVersionConstraints); + } + $phpstanVersionConstraint = $this->constraintIntoString(Intervals::compactConstraint($multiConstraint)); + } + + ksort($data); + ksort($installedPackages); + ksort($notInstalledPackages); + sort($ignoredPackages); + + $generatedConfigFileContents = sprintf(self::$generatedFileTemplate, var_export($data, true), var_export($notInstalledPackages, true), var_export($phpstanVersionConstraint, true)); + file_put_contents($generatedConfigFilePath, $generatedConfigFileContents); + $io->write('phpstan/extension-installer: Extensions installed'); + + if ($oldGeneratedConfigFileHash === md5($generatedConfigFileContents)) { + return; + } + + foreach (array_keys($installedPackages) as $name) { + $io->write(sprintf('> %s: installed', $name)); + } + + foreach (array_keys($notInstalledPackages) as $name) { + $io->write(sprintf('> %s: not supported', $name)); + } + + foreach ($ignoredPackages as $name) { + $io->write(sprintf('> %s: ignored', $name)); + } + } + + private function constraintIntoString(ConstraintInterface $constraint): string + { + return sprintf( + '%s%s, %s%s', + $constraint->getLowerBound()->isInclusive() ? '>=' : '>', + $constraint->getLowerBound()->getVersion(), + $constraint->getUpperBound()->isInclusive() ? '<=' : '<', + $constraint->getUpperBound()->getVersion() + ); + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/.editorconfig b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/.editorconfig new file mode 100644 index 00000000000..5d66bc427b8 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/.editorconfig @@ -0,0 +1,27 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true + +[*.{php,phpt}] +indent_style = tab +indent_size = 4 + +[*.xml] +indent_style = tab +indent_size = 4 + +[*.neon] +indent_style = tab +indent_size = 4 + +[*.{yaml,yml}] +indent_style = space +indent_size = 2 + +[composer.json] +indent_style = tab +indent_size = 4 diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/LICENSE b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/LICENSE new file mode 100644 index 00000000000..52fba1e23bb --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/LICENSE @@ -0,0 +1,23 @@ +MIT License + +Copyright (c) 2016 Ondřej Mirtes +Copyright (c) 2025 PHPStan s.r.o. + +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. + diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/README.md b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/README.md new file mode 100644 index 00000000000..3a60ec8e171 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/README.md @@ -0,0 +1,106 @@ +# Extra strict and opinionated rules for PHPStan + +[![Build](https://github.com/phpstan/phpstan-strict-rules/workflows/Build/badge.svg)](https://github.com/phpstan/phpstan-strict-rules/actions) +[![Latest Stable Version](https://poser.pugx.org/phpstan/phpstan-strict-rules/v/stable)](https://packagist.org/packages/phpstan/phpstan-strict-rules) +[![License](https://poser.pugx.org/phpstan/phpstan-strict-rules/license)](https://packagist.org/packages/phpstan/phpstan-strict-rules) + +[PHPStan](https://phpstan.org/) focuses on finding bugs in your code. But in PHP there's a lot of leeway in how stuff can be written. This repository contains additional rules that revolve around strictly and strongly typed code with no loose casting for those who want additional safety in extremely defensive programming: + +| Configuration Parameters | Rule Description | +|:---------------------------------------|:--------------------------------------------------------------------------------------------------------| +| `booleansInConditions` | Require booleans in `if`, `elseif`, ternary operator, after `!`, and on both sides of `&&` and `\|\|`. | +| `booleansInLoopConditions` | Require booleans in `while` and `do while` loop conditions. | +| `numericOperandsInArithmeticOperators` | Require numeric operand in `+$var`, `-$var`, `$var++`, `$var--`, `++$var` and `--$var`. | +| `numericOperandsInArithmeticOperators` | Require numeric operand in `$var++`, `$var--`, `++$var`and `--$var`. | +| `strictFunctionCalls` | These functions contain a `$strict` parameter for better type safety, it must be set to `true`:
    * `in_array` (3rd parameter)
    * `array_search` (3rd parameter)
    * `array_keys` (3rd parameter; only if the 2nd parameter `$search_value` is provided)
    * `base64_decode` (2nd parameter). | +| `overwriteVariablesWithLoop` | * Disallow overwriting variables with `foreach` key and value variables.
    * Disallow overwriting variables with `for` loop initial assignment. | +| `switchConditionsMatchingType` | Types in `switch` condition and `case` value must match. PHP compares them loosely by default and that can lead to unexpected results. | +| `dynamicCallOnStaticMethod` | Check that statically declared methods are called statically. | +| `disallowedEmpty` | Disallow `empty()` - it's a very loose comparison (see [manual](https://php.net/empty)), it's recommended to use more strict one. | +| `disallowedShortTernary` | Disallow short ternary operator (`?:`) - implies weak comparison, it's recommended to use null coalesce operator (`??`) or ternary operator with strict condition. | +| `noVariableVariables` | Disallow variable variables (`$$foo`, `$this->$method()` etc.). | +| `checkAlwaysTrueInstanceof`, `checkAlwaysTrueCheckTypeFunctionCall`, `checkAlwaysTrueStrictComparison` | Always true `instanceof`, type-checking `is_*` functions and strict comparisons `===`/`!==`. These checks can be turned off by setting `checkAlwaysTrueInstanceof`, `checkAlwaysTrueCheckTypeFunctionCall` and `checkAlwaysTrueStrictComparison` to false. | +| | Correct case for referenced and called function names. | +| `matchingInheritedMethodNames` | Correct case for inherited and implemented method names. | +| | Contravariance for parameter types and covariance for return types in inherited methods (also known as Liskov substitution principle - LSP).| +| | Check LSP even for static methods. | +| `requireParentConstructorCall` | Require calling parent constructor. | +| `disallowedBacktick` | Disallow usage of backtick operator (`` $ls = `ls -la` ``). | +| `closureUsesThis` | Closure should use `$this` directly instead of using `$this` variable indirectly. | + +Additional rules are coming in subsequent releases! + + +## Installation + +To use this extension, require it in [Composer](https://getcomposer.org/): + +``` +composer require --dev phpstan/phpstan-strict-rules +``` + +If you also install [phpstan/extension-installer](https://github.com/phpstan/extension-installer) then you're all set! + +
    + Manual installation + +If you don't want to use `phpstan/extension-installer`, include rules.neon in your project's PHPStan config: + +``` +includes: + - vendor/phpstan/phpstan-strict-rules/rules.neon +``` +
    + +## Disabling rules + +You can disable rules using configuration parameters: + +```neon +parameters: + strictRules: + disallowedLooseComparison: false + booleansInConditions: false + booleansInLoopConditions: false + uselessCast: false + requireParentConstructorCall: false + disallowedBacktick: false + disallowedEmpty: false + disallowedImplicitArrayCreation: false + disallowedShortTernary: false + overwriteVariablesWithLoop: false + closureUsesThis: false + matchingInheritedMethodNames: false + numericOperandsInArithmeticOperators: false + strictFunctionCalls: false + dynamicCallOnStaticMethod: false + switchConditionsMatchingType: false + noVariableVariables: false + strictArrayFilter: false + illegalConstructorMethodCall: false +``` + +Aside from introducing new custom rules, phpstan-strict-rules also [change the default values of some configuration parameters](./rules.neon#L1) that are present in PHPStan itself. These parameters are [documented on phpstan.org](https://phpstan.org/config-reference#stricter-analysis). + +## Enabling rules one-by-one + +If you don't want to start using all the available strict rules at once but only one or two, you can! + +You can disable all rules from the included `rules.neon` with: + +```neon +parameters: + strictRules: + allRules: false +``` + +Then you can re-enable individual rules with configuration parameters: + +```neon +parameters: + strictRules: + allRules: false + booleansInConditions: true +``` + +Even with `strictRules.allRules` set to `false`, part of this package is still in effect. That's because phpstan-strict-rules also [change the default values of some configuration parameters](./rules.neon#L1) that are present in PHPStan itself. These parameters are [documented on phpstan.org](https://phpstan.org/config-reference#stricter-analysis). diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/composer.json b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/composer.json new file mode 100644 index 00000000000..bc72c58111d --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/composer.json @@ -0,0 +1,43 @@ +{ + "name": "phpstan/phpstan-strict-rules", + "type": "phpstan-extension", + "description": "Extra strict and opinionated rules for PHPStan", + "license": [ + "MIT" + ], + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.1.29" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6" + }, + "config": { + "platform": { + "php": "7.4.6" + }, + "sort-packages": true + }, + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "autoload-dev": { + "classmap": [ + "tests/" + ] + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/rules.neon b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/rules.neon new file mode 100644 index 00000000000..0def6d8784c --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/rules.neon @@ -0,0 +1,302 @@ +parameters: + strictRulesInstalled: true + polluteScopeWithLoopInitialAssignments: false + polluteScopeWithAlwaysIterableForeach: false + polluteScopeWithBlock: false + checkDynamicProperties: true + checkExplicitMixedMissingReturn: true + checkFunctionNameCase: true + checkInternalClassCaseSensitivity: true + reportMaybesInMethodSignatures: true + reportStaticMethodSignatures: true + reportMaybesInPropertyPhpDocTypes: true + reportWrongPhpDocTypeInVarTag: true + checkStrictPrintfPlaceholderTypes: true + strictRules: + allRules: true + disallowedLooseComparison: %strictRules.allRules% + booleansInConditions: %strictRules.allRules% + booleansInLoopConditions: [%strictRules.allRules%, %featureToggles.bleedingEdge%] + uselessCast: %strictRules.allRules% + requireParentConstructorCall: %strictRules.allRules% + disallowedBacktick: %strictRules.allRules% + disallowedEmpty: %strictRules.allRules% + disallowedImplicitArrayCreation: %strictRules.allRules% + disallowedShortTernary: %strictRules.allRules% + overwriteVariablesWithLoop: %strictRules.allRules% + closureUsesThis: %strictRules.allRules% + matchingInheritedMethodNames: %strictRules.allRules% + numericOperandsInArithmeticOperators: %strictRules.allRules% + strictFunctionCalls: %strictRules.allRules% + dynamicCallOnStaticMethod: %strictRules.allRules% + switchConditionsMatchingType: %strictRules.allRules% + noVariableVariables: %strictRules.allRules% + strictArrayFilter: %strictRules.allRules% + illegalConstructorMethodCall: %strictRules.allRules% + +parametersSchema: + strictRules: structure([ + allRules: anyOf(bool(), arrayOf(bool())), + disallowedLooseComparison: anyOf(bool(), arrayOf(bool())), + booleansInConditions: anyOf(bool(), arrayOf(bool())) + booleansInLoopConditions: anyOf(bool(), arrayOf(bool())) + uselessCast: anyOf(bool(), arrayOf(bool())) + requireParentConstructorCall: anyOf(bool(), arrayOf(bool())) + disallowedBacktick: anyOf(bool(), arrayOf(bool())) + disallowedEmpty: anyOf(bool(), arrayOf(bool())) + disallowedImplicitArrayCreation: anyOf(bool(), arrayOf(bool())) + disallowedShortTernary: anyOf(bool(), arrayOf(bool())) + overwriteVariablesWithLoop: anyOf(bool(), arrayOf(bool())) + closureUsesThis: anyOf(bool(), arrayOf(bool())) + matchingInheritedMethodNames: anyOf(bool(), arrayOf(bool())) + numericOperandsInArithmeticOperators: anyOf(bool(), arrayOf(bool())) + strictFunctionCalls: anyOf(bool(), arrayOf(bool())) + dynamicCallOnStaticMethod: anyOf(bool(), arrayOf(bool())) + switchConditionsMatchingType: anyOf(bool(), arrayOf(bool())) + noVariableVariables: anyOf(bool(), arrayOf(bool())) + strictArrayFilter: anyOf(bool(), arrayOf(bool())) + illegalConstructorMethodCall: anyOf(bool(), arrayOf(bool())) + ]) + +conditionalTags: + PHPStan\Rules\DisallowedConstructs\DisallowedLooseComparisonRule: + phpstan.rules.rule: %strictRules.disallowedLooseComparison% + PHPStan\Rules\BooleansInConditions\BooleanInBooleanAndRule: + phpstan.rules.rule: %strictRules.booleansInConditions% + PHPStan\Rules\BooleansInConditions\BooleanInBooleanNotRule: + phpstan.rules.rule: %strictRules.booleansInConditions% + PHPStan\Rules\BooleansInConditions\BooleanInBooleanOrRule: + phpstan.rules.rule: %strictRules.booleansInConditions% + PHPStan\Rules\BooleansInConditions\BooleanInDoWhileConditionRule: + phpstan.rules.rule: %strictRules.booleansInLoopConditions% + PHPStan\Rules\BooleansInConditions\BooleanInElseIfConditionRule: + phpstan.rules.rule: %strictRules.booleansInConditions% + PHPStan\Rules\BooleansInConditions\BooleanInIfConditionRule: + phpstan.rules.rule: %strictRules.booleansInConditions% + PHPStan\Rules\BooleansInConditions\BooleanInTernaryOperatorRule: + phpstan.rules.rule: %strictRules.booleansInConditions% + PHPStan\Rules\BooleansInConditions\BooleanInWhileConditionRule: + phpstan.rules.rule: %strictRules.booleansInLoopConditions% + PHPStan\Rules\Cast\UselessCastRule: + phpstan.rules.rule: %strictRules.uselessCast% + PHPStan\Rules\Classes\RequireParentConstructCallRule: + phpstan.rules.rule: %strictRules.requireParentConstructorCall% + PHPStan\Rules\DisallowedConstructs\DisallowedBacktickRule: + phpstan.rules.rule: %strictRules.disallowedBacktick% + PHPStan\Rules\DisallowedConstructs\DisallowedEmptyRule: + phpstan.rules.rule: %strictRules.disallowedEmpty% + PHPStan\Rules\DisallowedConstructs\DisallowedImplicitArrayCreationRule: + phpstan.rules.rule: %strictRules.disallowedImplicitArrayCreation% + PHPStan\Rules\DisallowedConstructs\DisallowedShortTernaryRule: + phpstan.rules.rule: %strictRules.disallowedShortTernary% + PHPStan\Rules\ForeachLoop\OverwriteVariablesWithForeachRule: + phpstan.rules.rule: %strictRules.overwriteVariablesWithLoop% + PHPStan\Rules\ForLoop\OverwriteVariablesWithForLoopInitRule: + phpstan.rules.rule: %strictRules.overwriteVariablesWithLoop% + PHPStan\Rules\Functions\ArrayFilterStrictRule: + phpstan.rules.rule: %strictRules.strictArrayFilter% + PHPStan\Rules\Functions\ClosureUsesThisRule: + phpstan.rules.rule: %strictRules.closureUsesThis% + PHPStan\Rules\Methods\WrongCaseOfInheritedMethodRule: + phpstan.rules.rule: %strictRules.matchingInheritedMethodNames% + PHPStan\Rules\Operators\OperandInArithmeticPostDecrementRule: + phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% + PHPStan\Rules\Operators\OperandInArithmeticPostIncrementRule: + phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% + PHPStan\Rules\Operators\OperandInArithmeticPreDecrementRule: + phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% + PHPStan\Rules\Operators\OperandInArithmeticPreIncrementRule: + phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% + PHPStan\Rules\Operators\OperandInArithmeticUnaryMinusRule: + phpstan.rules.rule: [%strictRules.numericOperandsInArithmeticOperators%, %featureToggles.bleedingEdge%] + PHPStan\Rules\Operators\OperandInArithmeticUnaryPlusRule: + phpstan.rules.rule: [%strictRules.numericOperandsInArithmeticOperators%, %featureToggles.bleedingEdge%] + PHPStan\Rules\Operators\OperandsInArithmeticAdditionRule: + phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% + PHPStan\Rules\Operators\OperandsInArithmeticDivisionRule: + phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% + PHPStan\Rules\Operators\OperandsInArithmeticExponentiationRule: + phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% + PHPStan\Rules\Operators\OperandsInArithmeticModuloRule: + phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% + PHPStan\Rules\Operators\OperandsInArithmeticMultiplicationRule: + phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% + PHPStan\Rules\Operators\OperandsInArithmeticSubtractionRule: + phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% + PHPStan\Rules\StrictCalls\DynamicCallOnStaticMethodsRule: + phpstan.rules.rule: %strictRules.dynamicCallOnStaticMethod% + PHPStan\Rules\StrictCalls\DynamicCallOnStaticMethodsCallableRule: + phpstan.rules.rule: %strictRules.dynamicCallOnStaticMethod% + PHPStan\Rules\StrictCalls\StrictFunctionCallsRule: + phpstan.rules.rule: %strictRules.strictFunctionCalls% + PHPStan\Rules\SwitchConditions\MatchingTypeInSwitchCaseConditionRule: + phpstan.rules.rule: %strictRules.switchConditionsMatchingType% + PHPStan\Rules\VariableVariables\VariableMethodCallRule: + phpstan.rules.rule: %strictRules.noVariableVariables% + PHPStan\Rules\VariableVariables\VariableMethodCallableRule: + phpstan.rules.rule: %strictRules.noVariableVariables% + PHPStan\Rules\VariableVariables\VariableStaticMethodCallRule: + phpstan.rules.rule: %strictRules.noVariableVariables% + PHPStan\Rules\VariableVariables\VariableStaticMethodCallableRule: + phpstan.rules.rule: %strictRules.noVariableVariables% + PHPStan\Rules\VariableVariables\VariableStaticPropertyFetchRule: + phpstan.rules.rule: %strictRules.noVariableVariables% + PHPStan\Rules\VariableVariables\VariableVariablesRule: + phpstan.rules.rule: %strictRules.noVariableVariables% + PHPStan\Rules\VariableVariables\VariablePropertyFetchRule: + phpstan.rules.rule: %strictRules.noVariableVariables% + PHPStan\Rules\Methods\IllegalConstructorMethodCallRule: + phpstan.rules.rule: %strictRules.illegalConstructorMethodCall% + PHPStan\Rules\Methods\IllegalConstructorStaticCallRule: + phpstan.rules.rule: %strictRules.illegalConstructorMethodCall% + +services: + - + class: PHPStan\Rules\BooleansInConditions\BooleanRuleHelper + + - + class: PHPStan\Rules\Operators\OperatorRuleHelper + + - + class: PHPStan\Rules\VariableVariables\VariablePropertyFetchRule + arguments: + universalObjectCratesClasses: %universalObjectCratesClasses% + + - + class: PHPStan\Rules\DisallowedConstructs\DisallowedLooseComparisonRule + + - + class: PHPStan\Rules\BooleansInConditions\BooleanInBooleanAndRule + + - + class: PHPStan\Rules\BooleansInConditions\BooleanInBooleanNotRule + + - + class: PHPStan\Rules\BooleansInConditions\BooleanInBooleanOrRule + + - + class: PHPStan\Rules\BooleansInConditions\BooleanInDoWhileConditionRule + + - + class: PHPStan\Rules\BooleansInConditions\BooleanInElseIfConditionRule + + - + class: PHPStan\Rules\BooleansInConditions\BooleanInIfConditionRule + + - + class: PHPStan\Rules\BooleansInConditions\BooleanInTernaryOperatorRule + + - + class: PHPStan\Rules\BooleansInConditions\BooleanInWhileConditionRule + + - + class: PHPStan\Rules\Cast\UselessCastRule + arguments: + treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% + treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% + + - + class: PHPStan\Rules\Classes\RequireParentConstructCallRule + + - + class: PHPStan\Rules\DisallowedConstructs\DisallowedBacktickRule + + - + class: PHPStan\Rules\DisallowedConstructs\DisallowedEmptyRule + + - + class: PHPStan\Rules\DisallowedConstructs\DisallowedImplicitArrayCreationRule + + - + class: PHPStan\Rules\DisallowedConstructs\DisallowedShortTernaryRule + + - + class: PHPStan\Rules\ForeachLoop\OverwriteVariablesWithForeachRule + + - + class: PHPStan\Rules\ForLoop\OverwriteVariablesWithForLoopInitRule + + - + class: PHPStan\Rules\Functions\ArrayFilterStrictRule + arguments: + treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% + checkNullables: %checkNullables% + treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% + + - + class: PHPStan\Rules\Functions\ClosureUsesThisRule + + - + class: PHPStan\Rules\Methods\WrongCaseOfInheritedMethodRule + + - + class: PHPStan\Rules\Methods\IllegalConstructorMethodCallRule + + - + class: PHPStan\Rules\Methods\IllegalConstructorStaticCallRule + + - + class: PHPStan\Rules\Operators\OperandInArithmeticPostDecrementRule + + - + class: PHPStan\Rules\Operators\OperandInArithmeticPostIncrementRule + + - + class: PHPStan\Rules\Operators\OperandInArithmeticPreDecrementRule + + - + class: PHPStan\Rules\Operators\OperandInArithmeticPreIncrementRule + + - + class: PHPStan\Rules\Operators\OperandInArithmeticUnaryMinusRule + + - + class: PHPStan\Rules\Operators\OperandInArithmeticUnaryPlusRule + + - + class: PHPStan\Rules\Operators\OperandsInArithmeticAdditionRule + + - + class: PHPStan\Rules\Operators\OperandsInArithmeticDivisionRule + + - + class: PHPStan\Rules\Operators\OperandsInArithmeticExponentiationRule + + - + class: PHPStan\Rules\Operators\OperandsInArithmeticModuloRule + + - + class: PHPStan\Rules\Operators\OperandsInArithmeticMultiplicationRule + + - + class: PHPStan\Rules\Operators\OperandsInArithmeticSubtractionRule + + - + class: PHPStan\Rules\StrictCalls\DynamicCallOnStaticMethodsRule + + - + class: PHPStan\Rules\StrictCalls\DynamicCallOnStaticMethodsCallableRule + + - + class: PHPStan\Rules\StrictCalls\StrictFunctionCallsRule + + - + class: PHPStan\Rules\SwitchConditions\MatchingTypeInSwitchCaseConditionRule + + - + class: PHPStan\Rules\VariableVariables\VariableMethodCallRule + + - + class: PHPStan\Rules\VariableVariables\VariableMethodCallableRule + + - + class: PHPStan\Rules\VariableVariables\VariableStaticMethodCallRule + + - + class: PHPStan\Rules\VariableVariables\VariableStaticMethodCallableRule + + - + class: PHPStan\Rules\VariableVariables\VariableStaticPropertyFetchRule + + - + class: PHPStan\Rules\VariableVariables\VariableVariablesRule diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInBooleanAndRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInBooleanAndRule.php new file mode 100644 index 00000000000..eed19aeac0e --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInBooleanAndRule.php @@ -0,0 +1,59 @@ + + */ +class BooleanInBooleanAndRule implements Rule +{ + + private BooleanRuleHelper $helper; + + public function __construct(BooleanRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return BooleanAndNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $originalNode = $node->getOriginalNode(); + $messages = []; + $nodeText = $originalNode->getOperatorSigil(); + $identifierType = $originalNode instanceof Node\Expr\BinaryOp\BooleanAnd ? 'booleanAnd' : 'logicalAnd'; + if (!$this->helper->passesAsBoolean($scope, $originalNode->left)) { + $leftType = $scope->getType($originalNode->left); + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only booleans are allowed in %s, %s given on the left side.', + $nodeText, + $leftType->describe(VerbosityLevel::typeOnly()), + ))->identifier(sprintf('%s.leftNotBoolean', $identifierType))->build(); + } + + $rightScope = $node->getRightScope(); + if (!$this->helper->passesAsBoolean($rightScope, $originalNode->right)) { + $rightType = $rightScope->getType($originalNode->right); + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only booleans are allowed in %s, %s given on the right side.', + $nodeText, + $rightType->describe(VerbosityLevel::typeOnly()), + ))->identifier(sprintf('%s.rightNotBoolean', $identifierType))->build(); + } + + return $messages; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInBooleanNotRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInBooleanNotRule.php new file mode 100644 index 00000000000..5187cf57bdf --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInBooleanNotRule.php @@ -0,0 +1,47 @@ + + */ +class BooleanInBooleanNotRule implements Rule +{ + + private BooleanRuleHelper $helper; + + public function __construct(BooleanRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return BooleanNot::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($this->helper->passesAsBoolean($scope, $node->expr)) { + return []; + } + + $expressionType = $scope->getType($node->expr); + + return [ + RuleErrorBuilder::message(sprintf( + 'Only booleans are allowed in a negated boolean, %s given.', + $expressionType->describe(VerbosityLevel::typeOnly()), + ))->identifier('booleanNot.exprNotBoolean')->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInBooleanOrRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInBooleanOrRule.php new file mode 100644 index 00000000000..cb06a34162d --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInBooleanOrRule.php @@ -0,0 +1,59 @@ + + */ +class BooleanInBooleanOrRule implements Rule +{ + + private BooleanRuleHelper $helper; + + public function __construct(BooleanRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return BooleanOrNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $originalNode = $node->getOriginalNode(); + $messages = []; + $nodeText = $originalNode->getOperatorSigil(); + $identifierType = $originalNode instanceof Node\Expr\BinaryOp\BooleanOr ? 'booleanOr' : 'logicalOr'; + if (!$this->helper->passesAsBoolean($scope, $originalNode->left)) { + $leftType = $scope->getType($originalNode->left); + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only booleans are allowed in %s, %s given on the left side.', + $nodeText, + $leftType->describe(VerbosityLevel::typeOnly()), + ))->identifier(sprintf('%s.leftNotBoolean', $identifierType))->build(); + } + + $rightScope = $node->getRightScope(); + if (!$this->helper->passesAsBoolean($rightScope, $originalNode->right)) { + $rightType = $rightScope->getType($originalNode->right); + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only booleans are allowed in %s, %s given on the right side.', + $nodeText, + $rightType->describe(VerbosityLevel::typeOnly()), + ))->identifier(sprintf('%s.rightNotBoolean', $identifierType))->build(); + } + + return $messages; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInDoWhileConditionRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInDoWhileConditionRule.php new file mode 100644 index 00000000000..d0db29629c0 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInDoWhileConditionRule.php @@ -0,0 +1,46 @@ + + */ +class BooleanInDoWhileConditionRule implements Rule +{ + + private BooleanRuleHelper $helper; + + public function __construct(BooleanRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return Node\Stmt\Do_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($this->helper->passesAsBoolean($scope, $node->cond)) { + return []; + } + + $conditionExpressionType = $scope->getType($node->cond); + + return [ + RuleErrorBuilder::message(sprintf( + 'Only booleans are allowed in a do-while condition, %s given.', + $conditionExpressionType->describe(VerbosityLevel::typeOnly()), + ))->identifier('doWhile.condNotBoolean')->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInElseIfConditionRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInElseIfConditionRule.php new file mode 100644 index 00000000000..550e9857da3 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInElseIfConditionRule.php @@ -0,0 +1,47 @@ + + */ +class BooleanInElseIfConditionRule implements Rule +{ + + private BooleanRuleHelper $helper; + + public function __construct(BooleanRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return ElseIf_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($this->helper->passesAsBoolean($scope, $node->cond)) { + return []; + } + + $conditionExpressionType = $scope->getType($node->cond); + + return [ + RuleErrorBuilder::message(sprintf( + 'Only booleans are allowed in an elseif condition, %s given.', + $conditionExpressionType->describe(VerbosityLevel::typeOnly()), + ))->identifier('elseif.condNotBoolean')->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInIfConditionRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInIfConditionRule.php new file mode 100644 index 00000000000..5c08894b4eb --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInIfConditionRule.php @@ -0,0 +1,47 @@ + + */ +class BooleanInIfConditionRule implements Rule +{ + + private BooleanRuleHelper $helper; + + public function __construct(BooleanRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return If_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($this->helper->passesAsBoolean($scope, $node->cond)) { + return []; + } + + $conditionExpressionType = $scope->getType($node->cond); + + return [ + RuleErrorBuilder::message(sprintf( + 'Only booleans are allowed in an if condition, %s given.', + $conditionExpressionType->describe(VerbosityLevel::typeOnly()), + ))->identifier('if.condNotBoolean')->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInTernaryOperatorRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInTernaryOperatorRule.php new file mode 100644 index 00000000000..4fe855a5ce0 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInTernaryOperatorRule.php @@ -0,0 +1,51 @@ + + */ +class BooleanInTernaryOperatorRule implements Rule +{ + + private BooleanRuleHelper $helper; + + public function __construct(BooleanRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return Ternary::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->if === null) { + return []; // elvis ?: + } + + if ($this->helper->passesAsBoolean($scope, $node->cond)) { + return []; + } + + $conditionExpressionType = $scope->getType($node->cond); + + return [ + RuleErrorBuilder::message(sprintf( + 'Only booleans are allowed in a ternary operator condition, %s given.', + $conditionExpressionType->describe(VerbosityLevel::typeOnly()), + ))->identifier('ternary.condNotBoolean')->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInWhileConditionRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInWhileConditionRule.php new file mode 100644 index 00000000000..2f1661a63fa --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInWhileConditionRule.php @@ -0,0 +1,46 @@ + + */ +class BooleanInWhileConditionRule implements Rule +{ + + private BooleanRuleHelper $helper; + + public function __construct(BooleanRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return Node\Stmt\While_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($this->helper->passesAsBoolean($scope, $node->cond)) { + return []; + } + + $conditionExpressionType = $scope->getType($node->cond); + + return [ + RuleErrorBuilder::message(sprintf( + 'Only booleans are allowed in a while condition, %s given.', + $conditionExpressionType->describe(VerbosityLevel::typeOnly()), + ))->identifier('while.condNotBoolean')->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanRuleHelper.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanRuleHelper.php new file mode 100644 index 00000000000..4ecba329926 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanRuleHelper.php @@ -0,0 +1,42 @@ +ruleLevelHelper = $ruleLevelHelper; + } + + public function passesAsBoolean(Scope $scope, Expr $expr): bool + { + $type = $scope->getType($expr); + if ($type instanceof MixedType) { + return !$type->isExplicitMixed(); + } + $typeToCheck = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $expr, + '', + static fn (Type $type): bool => $type->isBoolean()->yes(), + ); + $foundType = $typeToCheck->getType(); + if ($foundType instanceof ErrorType) { + return true; + } + + return $foundType->isBoolean()->yes(); + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Cast/UselessCastRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Cast/UselessCastRule.php new file mode 100644 index 00000000000..662975055aa --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Cast/UselessCastRule.php @@ -0,0 +1,81 @@ + + */ +class UselessCastRule implements Rule +{ + + private bool $treatPhpDocTypesAsCertain; + + private bool $treatPhpDocTypesAsCertainTip; + + public function __construct( + bool $treatPhpDocTypesAsCertain, + bool $treatPhpDocTypesAsCertainTip + ) + { + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + $this->treatPhpDocTypesAsCertainTip = $treatPhpDocTypesAsCertainTip; + } + + public function getNodeType(): string + { + return Cast::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $castType = $scope->getType($node); + if ($castType instanceof ErrorType) { + return []; + } + $castType = $castType->generalize(GeneralizePrecision::lessSpecific()); + + if ($this->treatPhpDocTypesAsCertain) { + $expressionType = $scope->getType($node->expr); + } else { + $expressionType = $scope->getNativeType($node->expr); + } + if ($castType->isSuperTypeOf($expressionType)->yes()) { + $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node, $castType): RuleErrorBuilder { + if (!$this->treatPhpDocTypesAsCertain) { + return $ruleErrorBuilder; + } + + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } + + $expressionTypeWithoutPhpDoc = $scope->getNativeType($node->expr); + if ($castType->isSuperTypeOf($expressionTypeWithoutPhpDoc)->yes()) { + return $ruleErrorBuilder; + } + + return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); + }; + return [ + $addTip(RuleErrorBuilder::message(sprintf( + 'Casting to %s something that\'s already %s.', + $castType->describe(VerbosityLevel::typeOnly()), + $expressionType->describe(VerbosityLevel::typeOnly()), + )))->identifier('cast.useless')->build(), + ]; + } + + return []; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Classes/RequireParentConstructCallRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Classes/RequireParentConstructCallRule.php new file mode 100644 index 00000000000..38c5e0339dc --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Classes/RequireParentConstructCallRule.php @@ -0,0 +1,143 @@ + + */ +class RequireParentConstructCallRule implements Rule +{ + + public function getNodeType(): string + { + return ClassMethod::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$scope->isInClass()) { + throw new ShouldNotHappenException(); + } + + if ($scope->isInTrait()) { + return []; + } + + if ($node->name->name !== '__construct') { + return []; + } + + if ($node->isAbstract()) { + return []; + } + + $classReflection = $scope->getClassReflection()->getNativeReflection(); + if ($classReflection->isInterface() || $classReflection->isAnonymous()) { + return []; + } + + if ($this->callsParentConstruct($node)) { + return []; + } + + $parentClass = $this->getParentConstructorClass($classReflection); + if ($parentClass !== false) { + return [ + RuleErrorBuilder::message(sprintf( + '%s::__construct() does not call parent constructor from %s.', + $classReflection->getName(), + $parentClass->getName(), + ))->identifier('constructor.missingParentCall')->build(), + ]; + } + + return []; + } + + private function callsParentConstruct(Node $parserNode): bool + { + if (!property_exists($parserNode, 'stmts')) { + return false; + } + + foreach ($parserNode->stmts as $statement) { + if ($statement instanceof Node\Stmt\Expression) { + $statement = $statement->expr; + } + + $statement = $this->ignoreErrorSuppression($statement); + if ($statement instanceof StaticCall) { + if ( + $statement->class instanceof Name + && ((string) $statement->class === 'parent') + && $statement->name instanceof Node\Identifier + && $statement->name->name === '__construct' + ) { + return true; + } + } else { + if ($this->callsParentConstruct($statement)) { + return true; + } + } + } + + return false; + } + + /** + * @param ReflectionClass|ReflectionEnum $classReflection + * @return ReflectionClass|false + */ + private function getParentConstructorClass($classReflection) + { + $parentClass = $classReflection->getParentClass(); + while ($parentClass !== false) { + $constructor = $parentClass->hasMethod('__construct') ? $parentClass->getMethod('__construct') : null; + $constructorWithClassName = $parentClass->hasMethod($parentClass->getName()) ? $parentClass->getMethod($parentClass->getName()) : null; + if ( + ( + $constructor !== null + && $constructor->getDeclaringClass()->getName() === $parentClass->getName() + && !$constructor->isAbstract() + && !$constructor->isPrivate() + && !$constructor->isDeprecated() + ) || ( + $constructorWithClassName !== null + && $constructorWithClassName->getDeclaringClass()->getName() === $parentClass->getName() + && !$constructorWithClassName->isAbstract() + ) + ) { + return $parentClass; + } + + $parentClass = $parentClass->getParentClass(); + } + + return false; + } + + private function ignoreErrorSuppression(Node $statement): Node + { + if ($statement instanceof Node\Expr\ErrorSuppress) { + + return $statement->expr; + } + + return $statement; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedBacktickRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedBacktickRule.php new file mode 100644 index 00000000000..76e401ceef5 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedBacktickRule.php @@ -0,0 +1,31 @@ + + */ +class DisallowedBacktickRule implements Rule +{ + + public function getNodeType(): string + { + return ShellExec::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return [ + RuleErrorBuilder::message('Backtick operator is not allowed. Use shell_exec() instead.') + ->identifier('backtick.notAllowed') + ->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedEmptyRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedEmptyRule.php new file mode 100644 index 00000000000..d19f5ea20d2 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedEmptyRule.php @@ -0,0 +1,31 @@ + + */ +class DisallowedEmptyRule implements Rule +{ + + public function getNodeType(): string + { + return Empty_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return [ + RuleErrorBuilder::message('Construct empty() is not allowed. Use more strict comparison.') + ->identifier('empty.notAllowed') + ->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedImplicitArrayCreationRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedImplicitArrayCreationRule.php new file mode 100644 index 00000000000..cee777ce991 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedImplicitArrayCreationRule.php @@ -0,0 +1,65 @@ + + */ +class DisallowedImplicitArrayCreationRule implements Rule +{ + + public function getNodeType(): string + { + return Assign::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->var instanceof ArrayDimFetch) { + return []; + } + + $node = $node->var; + while ($node instanceof ArrayDimFetch) { + $node = $node->var; + } + + if (!$node instanceof Variable) { + return []; + } + + if (!is_string($node->name)) { + return []; + } + + $certainty = $scope->hasVariableType($node->name); + if ($certainty->no()) { + return [ + RuleErrorBuilder::message(sprintf('Implicit array creation is not allowed - variable $%s does not exist.', $node->name)) + ->identifier('variable.implicitArray') + ->build(), + ]; + } + + if ($certainty->maybe()) { + return [ + RuleErrorBuilder::message(sprintf('Implicit array creation is not allowed - variable $%s might not exist.', $node->name)) + ->identifier('variable.implicitArray') + ->build(), + ]; + } + + return []; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedLooseComparisonRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedLooseComparisonRule.php new file mode 100644 index 00000000000..70b8514f1b8 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedLooseComparisonRule.php @@ -0,0 +1,48 @@ + + */ +class DisallowedLooseComparisonRule implements Rule +{ + + public function getNodeType(): string + { + return BinaryOp::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node instanceof Equal) { + return [ + RuleErrorBuilder::message( + 'Loose comparison via "==" is not allowed.', + )->tip('Use strict comparison via "===" instead.') + ->identifier('equal.notAllowed') + ->build(), + ]; + } + if ($node instanceof NotEqual) { + return [ + RuleErrorBuilder::message( + 'Loose comparison via "!=" is not allowed.', + )->tip('Use strict comparison via "!==" instead.') + ->identifier('notEqual.notAllowed') + ->build(), + ]; + } + + return []; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedShortTernaryRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedShortTernaryRule.php new file mode 100644 index 00000000000..fac42790d15 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedShortTernaryRule.php @@ -0,0 +1,35 @@ + + */ +class DisallowedShortTernaryRule implements Rule +{ + + public function getNodeType(): string + { + return Ternary::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->if !== null) { + return []; + } + + return [ + RuleErrorBuilder::message('Short ternary operator is not allowed. Use null coalesce operator if applicable or consider using long ternary.') + ->identifier('ternary.shortNotAllowed') + ->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/ForLoop/OverwriteVariablesWithForLoopInitRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/ForLoop/OverwriteVariablesWithForLoopInitRule.php new file mode 100644 index 00000000000..f710474e2f9 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/ForLoop/OverwriteVariablesWithForLoopInitRule.php @@ -0,0 +1,77 @@ + + */ +class OverwriteVariablesWithForLoopInitRule implements Rule +{ + + public function getNodeType(): string + { + return For_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $errors = []; + foreach ($node->init as $expr) { + if (!($expr instanceof Assign)) { + continue; + } + + foreach ($this->checkValueVar($scope, $expr->var) as $error) { + $errors[] = $error; + } + } + + return $errors; + } + + /** + * @return list + */ + private function checkValueVar(Scope $scope, Expr $expr): array + { + $errors = []; + if ( + $expr instanceof Node\Expr\Variable + && is_string($expr->name) + && $scope->hasVariableType($expr->name)->yes() + ) { + $errors[] = RuleErrorBuilder::message(sprintf('For loop initial assignment overwrites variable $%s.', $expr->name)) + ->identifier('for.variableOverwrite') + ->build(); + } + + if ( + $expr instanceof Node\Expr\List_ + || $expr instanceof Node\Expr\Array_ + ) { + foreach ($expr->items as $item) { + if ($item === null) { + continue; + } + + foreach ($this->checkValueVar($scope, $item->value) as $error) { + $errors[] = $error; + } + } + } + + return $errors; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/ForeachLoop/OverwriteVariablesWithForeachRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/ForeachLoop/OverwriteVariablesWithForeachRule.php new file mode 100644 index 00000000000..0cf620c37ee --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/ForeachLoop/OverwriteVariablesWithForeachRule.php @@ -0,0 +1,80 @@ + + */ +class OverwriteVariablesWithForeachRule implements Rule +{ + + public function getNodeType(): string + { + return Foreach_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $errors = []; + if ( + $node->keyVar instanceof Node\Expr\Variable + && is_string($node->keyVar->name) + && $scope->hasVariableType($node->keyVar->name)->yes() + ) { + $errors[] = RuleErrorBuilder::message(sprintf('Foreach overwrites $%s with its key variable.', $node->keyVar->name)) + ->identifier('foreach.keyOverwrite') + ->build(); + } + + foreach ($this->checkValueVar($scope, $node->valueVar) as $error) { + $errors[] = $error; + } + + return $errors; + } + + /** + * @return list + */ + private function checkValueVar(Scope $scope, Expr $expr): array + { + $errors = []; + if ( + $expr instanceof Node\Expr\Variable + && is_string($expr->name) + && $scope->hasVariableType($expr->name)->yes() + ) { + $errors[] = RuleErrorBuilder::message(sprintf('Foreach overwrites $%s with its value variable.', $expr->name)) + ->identifier('foreach.valueOverwrite') + ->build(); + } + + if ( + $expr instanceof Node\Expr\List_ + || $expr instanceof Node\Expr\Array_ + ) { + foreach ($expr->items as $item) { + if ($item === null) { + continue; + } + + foreach ($this->checkValueVar($scope, $item->value) as $error) { + $errors[] = $error; + } + } + } + + return $errors; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Functions/ArrayFilterStrictRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Functions/ArrayFilterStrictRule.php new file mode 100644 index 00000000000..6760c7d563e --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Functions/ArrayFilterStrictRule.php @@ -0,0 +1,162 @@ + + */ +class ArrayFilterStrictRule implements Rule +{ + + private ReflectionProvider $reflectionProvider; + + private bool $treatPhpDocTypesAsCertain; + + private bool $checkNullables; + + private bool $treatPhpDocTypesAsCertainTip; + + public function __construct( + ReflectionProvider $reflectionProvider, + bool $treatPhpDocTypesAsCertain, + bool $checkNullables, + bool $treatPhpDocTypesAsCertainTip + ) + { + $this->reflectionProvider = $reflectionProvider; + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + $this->checkNullables = $checkNullables; + $this->treatPhpDocTypesAsCertainTip = $treatPhpDocTypesAsCertainTip; + } + + public function getNodeType(): string + { + return FuncCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Name) { + return []; + } + + if (!$this->reflectionProvider->hasFunction($node->name, $scope)) { + return []; + } + + $functionReflection = $this->reflectionProvider->getFunction($node->name, $scope); + + if ($functionReflection->getName() !== 'array_filter') { + return []; + } + + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( + $scope, + $node->getArgs(), + $functionReflection->getVariants(), + $functionReflection->getNamedArgumentsVariants(), + ); + + $normalizedFuncCall = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $node); + + if ($normalizedFuncCall === null) { + return []; + } + + $args = $normalizedFuncCall->getArgs(); + if (count($args) === 0) { + return []; + } + + if (count($args) === 1) { + $arrayType = $scope->getType($args[0]->value); + $itemType = $arrayType->getIterableValueType(); + if ($itemType instanceof UnionType) { + $hasTruthy = false; + $hasFalsey = false; + foreach ($itemType->getTypes() as $innerType) { + $booleanType = $innerType->toBoolean(); + if ($booleanType->isTrue()->yes()) { + $hasTruthy = true; + continue; + } + if ($booleanType->isFalse()->yes()) { + $hasFalsey = true; + continue; + } + + $hasTruthy = false; + $hasFalsey = false; + break; + } + + if ($hasTruthy && $hasFalsey) { + return []; + } + } elseif ($itemType->isBoolean()->yes()) { + return []; + } elseif ($itemType->isArray()->yes()) { + return []; + } + + return [ + RuleErrorBuilder::message('Call to function array_filter() requires parameter #2 to be passed to avoid loose comparison semantics.') + ->identifier('arrayFilter.strict') + ->build(), + ]; + } + + $nativeCallbackType = $scope->getNativeType($args[1]->value); + + if ($this->treatPhpDocTypesAsCertain) { + $callbackType = $scope->getType($args[1]->value); + } else { + $callbackType = $nativeCallbackType; + } + + if ($this->isCallbackTypeNull($callbackType)) { + $message = 'Parameter #2 of array_filter() cannot be null to avoid loose comparison semantics (%s given).'; + $errorBuilder = RuleErrorBuilder::message(sprintf( + $message, + $callbackType->describe(VerbosityLevel::typeOnly()), + ))->identifier('arrayFilter.strict'); + + if ($this->treatPhpDocTypesAsCertainTip && !$this->isCallbackTypeNull($nativeCallbackType) && $this->treatPhpDocTypesAsCertain) { + $errorBuilder->treatPhpDocTypesAsCertainTip(); + } + + return [$errorBuilder->build()]; + } + + return []; + } + + private function isCallbackTypeNull(Type $callbackType): bool + { + if ($callbackType->isNull()->yes()) { + return true; + } + + if ($callbackType->isNull()->no()) { + return false; + } + + return $this->checkNullables; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Functions/ClosureUsesThisRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Functions/ClosureUsesThisRule.php new file mode 100644 index 00000000000..4f41d26b428 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Functions/ClosureUsesThisRule.php @@ -0,0 +1,52 @@ + + */ +class ClosureUsesThisRule implements Rule +{ + + public function getNodeType(): string + { + return Node\Expr\Closure::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->static) { + return []; + } + + if ($scope->isInClosureBind()) { + return []; + } + + $messages = []; + foreach ($node->uses as $closureUse) { + $varType = $scope->getType($closureUse->var); + if (!is_string($closureUse->var->name)) { + continue; + } + if (!$varType instanceof ThisType) { + continue; + } + + $messages[] = RuleErrorBuilder::message(sprintf('Anonymous function uses $this assigned to variable $%s. Use $this directly in the function body.', $closureUse->var->name)) + ->line($closureUse->getStartLine()) + ->identifier('closure.useThis') + ->build(); + } + return $messages; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Methods/IllegalConstructorMethodCallRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Methods/IllegalConstructorMethodCallRule.php new file mode 100644 index 00000000000..1dba6ed6d24 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Methods/IllegalConstructorMethodCallRule.php @@ -0,0 +1,34 @@ + + */ +final class IllegalConstructorMethodCallRule implements Rule +{ + + public function getNodeType(): string + { + return Node\Expr\MethodCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Node\Identifier || $node->name->toLowerString() !== '__construct') { + return []; + } + + return [ + RuleErrorBuilder::message('Call to __construct() on an existing object is not allowed.') + ->identifier('constructor.call') + ->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Methods/IllegalConstructorStaticCallRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Methods/IllegalConstructorStaticCallRule.php new file mode 100644 index 00000000000..fa747d6a2b5 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Methods/IllegalConstructorStaticCallRule.php @@ -0,0 +1,92 @@ + + */ +final class IllegalConstructorStaticCallRule implements Rule +{ + + public function getNodeType(): string + { + return Node\Expr\StaticCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Node\Identifier || $node->name->toLowerString() !== '__construct') { + return []; + } + + if ($this->isCollectCallingConstructor($node, $scope)) { + return []; + } + + return [ + RuleErrorBuilder::message('Static call to __construct() is only allowed on a parent class in the constructor.') + ->identifier('constructor.call') + ->build(), + ]; + } + + private function isCollectCallingConstructor(Node\Expr\StaticCall $node, Scope $scope): bool + { + // __construct should be called from inside constructor + if ($scope->getFunction() === null) { + return false; + } + + if ($scope->getFunction()->getName() !== '__construct') { + if (!$this->isInRenamedTraitConstructor($scope)) { + return false; + } + } + + if (!$scope->isInClass()) { + return false; + } + + if (!$node->class instanceof Node\Name) { + return false; + } + + $parentClasses = array_map(static fn (string $name) => strtolower($name), $scope->getClassReflection()->getParentClassesNames()); + + return in_array(strtolower($scope->resolveName($node->class)), $parentClasses, true); + } + + private function isInRenamedTraitConstructor(Scope $scope): bool + { + if (!$scope->isInClass()) { + return false; + } + + if (!$scope->isInTrait()) { + return false; + } + + if ($scope->getFunction() === null) { + return false; + } + + $traitAliases = $scope->getClassReflection()->getNativeReflection()->getTraitAliases(); + $functionName = $scope->getFunction()->getName(); + if (!array_key_exists($functionName, $traitAliases)) { + return false; + } + + return $traitAliases[$functionName] === sprintf('%s::%s', $scope->getTraitReflection()->getName(), '__construct'); + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Methods/WrongCaseOfInheritedMethodRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Methods/WrongCaseOfInheritedMethodRule.php new file mode 100644 index 00000000000..5f800e50284 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Methods/WrongCaseOfInheritedMethodRule.php @@ -0,0 +1,86 @@ + + */ +class WrongCaseOfInheritedMethodRule implements Rule +{ + + public function getNodeType(): string + { + return InClassMethodNode::class; + } + + public function processNode( + Node $node, + Scope $scope + ): array + { + $methodReflection = $node->getMethodReflection(); + $declaringClass = $methodReflection->getDeclaringClass(); + + $messages = []; + if ($declaringClass->getParentClass() !== null) { + $parentMessage = $this->findMethod( + $declaringClass, + $declaringClass->getParentClass(), + $methodReflection->getName(), + ); + if ($parentMessage !== null) { + $messages[] = $parentMessage; + } + } + + foreach ($declaringClass->getInterfaces() as $interface) { + $interfaceMessage = $this->findMethod( + $declaringClass, + $interface, + $methodReflection->getName(), + ); + if ($interfaceMessage === null) { + continue; + } + + $messages[] = $interfaceMessage; + } + + return $messages; + } + + private function findMethod( + ClassReflection $declaringClass, + ClassReflection $classReflection, + string $methodName + ): ?IdentifierRuleError + { + if (!$classReflection->hasNativeMethod($methodName)) { + return null; + } + + $parentMethod = $classReflection->getNativeMethod($methodName); + if ($parentMethod->getName() === $methodName) { + return null; + } + + return RuleErrorBuilder::message(sprintf( + 'Method %s::%s() does not match %s method name: %s::%s().', + $declaringClass->getDisplayName(), + $methodName, + $classReflection->isInterface() ? 'interface' : 'parent', + $classReflection->getDisplayName(), + $parentMethod->getName(), + ))->identifier('method.nameCase')->build(); + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticIncrementOrDecrementRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticIncrementOrDecrementRule.php new file mode 100644 index 00000000000..4e87a885845 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticIncrementOrDecrementRule.php @@ -0,0 +1,61 @@ + + */ +abstract class OperandInArithmeticIncrementOrDecrementRule implements Rule +{ + + private OperatorRuleHelper $helper; + + public function __construct(OperatorRuleHelper $helper) + { + $this->helper = $helper; + } + + /** + * @param TNodeType $node + */ + public function processNode(Node $node, Scope $scope): array + { + $messages = []; + $varType = $scope->getType($node->var); + + if ( + ($node instanceof PreInc || $node instanceof PostInc) + && !$this->helper->isValidForIncrement($scope, $node->var) + || ($node instanceof PreDec || $node instanceof PostDec) + && !$this->helper->isValidForDecrement($scope, $node->var) + ) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in %s, %s given.', + $this->describeOperation(), + $varType->describe(VerbosityLevel::typeOnly()), + ))->identifier(sprintf('%s.nonNumeric', $this->getIdentifier()))->build(); + } + + return $messages; + } + + abstract protected function describeOperation(): string; + + /** + * @return 'preInc'|'postInc'|'preDec'|'postDec' + */ + abstract protected function getIdentifier(): string; + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticPostDecrementRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticPostDecrementRule.php new file mode 100644 index 00000000000..d0e08099f6b --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticPostDecrementRule.php @@ -0,0 +1,28 @@ + + */ +class OperandInArithmeticPostDecrementRule extends OperandInArithmeticIncrementOrDecrementRule +{ + + public function getNodeType(): string + { + return PostDec::class; + } + + protected function describeOperation(): string + { + return 'post-decrement'; + } + + protected function getIdentifier(): string + { + return 'postDec'; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticPostIncrementRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticPostIncrementRule.php new file mode 100644 index 00000000000..400d82889a5 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticPostIncrementRule.php @@ -0,0 +1,28 @@ + + */ +class OperandInArithmeticPostIncrementRule extends OperandInArithmeticIncrementOrDecrementRule +{ + + public function getNodeType(): string + { + return PostInc::class; + } + + protected function describeOperation(): string + { + return 'post-increment'; + } + + protected function getIdentifier(): string + { + return 'postInc'; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticPreDecrementRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticPreDecrementRule.php new file mode 100644 index 00000000000..9d583560077 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticPreDecrementRule.php @@ -0,0 +1,28 @@ + + */ +class OperandInArithmeticPreDecrementRule extends OperandInArithmeticIncrementOrDecrementRule +{ + + public function getNodeType(): string + { + return PreDec::class; + } + + protected function describeOperation(): string + { + return 'pre-decrement'; + } + + protected function getIdentifier(): string + { + return 'preDec'; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticPreIncrementRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticPreIncrementRule.php new file mode 100644 index 00000000000..d5d81f2a51b --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticPreIncrementRule.php @@ -0,0 +1,28 @@ + + */ +class OperandInArithmeticPreIncrementRule extends OperandInArithmeticIncrementOrDecrementRule +{ + + public function getNodeType(): string + { + return PreInc::class; + } + + protected function describeOperation(): string + { + return 'pre-increment'; + } + + protected function getIdentifier(): string + { + return 'preInc'; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticUnaryMinusRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticUnaryMinusRule.php new file mode 100644 index 00000000000..d3db7df519c --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticUnaryMinusRule.php @@ -0,0 +1,47 @@ + + */ +class OperandInArithmeticUnaryMinusRule implements Rule +{ + + private OperatorRuleHelper $helper; + + public function __construct(OperatorRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return UnaryMinus::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $messages = []; + + if (!$this->helper->isValidForArithmeticOperation($scope, $node->expr)) { + $varType = $scope->getType($node->expr); + + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in unary -, %s given.', + $varType->describe(VerbosityLevel::typeOnly()), + ))->identifier('unaryMinus.nonNumeric')->build(); + } + + return $messages; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticUnaryPlusRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticUnaryPlusRule.php new file mode 100644 index 00000000000..78313d8c132 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticUnaryPlusRule.php @@ -0,0 +1,47 @@ + + */ +class OperandInArithmeticUnaryPlusRule implements Rule +{ + + private OperatorRuleHelper $helper; + + public function __construct(OperatorRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return UnaryPlus::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $messages = []; + + if (!$this->helper->isValidForArithmeticOperation($scope, $node->expr)) { + $varType = $scope->getType($node->expr); + + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in unary +, %s given.', + $varType->describe(VerbosityLevel::typeOnly()), + ))->identifier('unaryPlus.nonNumeric')->build(); + } + + return $messages; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticAdditionRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticAdditionRule.php new file mode 100644 index 00000000000..80de1463baa --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticAdditionRule.php @@ -0,0 +1,69 @@ + + */ +class OperandsInArithmeticAdditionRule implements Rule +{ + + private OperatorRuleHelper $helper; + + public function __construct(OperatorRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return Expr::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node instanceof BinaryOpPlus) { + $left = $node->left; + $right = $node->right; + } elseif ($node instanceof AssignOpPlus) { + $left = $node->var; + $right = $node->expr; + } else { + return []; + } + + $leftType = $scope->getType($left); + $rightType = $scope->getType($right); + if (count($leftType->getArrays()) > 0 && count($rightType->getArrays()) > 0) { + return []; + } + + $messages = []; + if (!$this->helper->isValidForArithmeticOperation($scope, $left)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in +, %s given on the left side.', + $leftType->describe(VerbosityLevel::typeOnly()), + ))->identifier('plus.leftNonNumeric')->build(); + } + if (!$this->helper->isValidForArithmeticOperation($scope, $right)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in +, %s given on the right side.', + $rightType->describe(VerbosityLevel::typeOnly()), + ))->identifier('plus.rightNonNumeric')->build(); + } + + return $messages; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticDivisionRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticDivisionRule.php new file mode 100644 index 00000000000..e95b3d624d5 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticDivisionRule.php @@ -0,0 +1,65 @@ + + */ +class OperandsInArithmeticDivisionRule implements Rule +{ + + private OperatorRuleHelper $helper; + + public function __construct(OperatorRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return Expr::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node instanceof BinaryOpDiv) { + $left = $node->left; + $right = $node->right; + } elseif ($node instanceof AssignOpDiv) { + $left = $node->var; + $right = $node->expr; + } else { + return []; + } + + $messages = []; + $leftType = $scope->getType($left); + if (!$this->helper->isValidForArithmeticOperation($scope, $left)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in /, %s given on the left side.', + $leftType->describe(VerbosityLevel::typeOnly()), + ))->identifier('div.leftNonNumeric')->build(); + } + + $rightType = $scope->getType($right); + if (!$this->helper->isValidForArithmeticOperation($scope, $right)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in /, %s given on the right side.', + $rightType->describe(VerbosityLevel::typeOnly()), + ))->identifier('div.rightNonNumeric')->build(); + } + + return $messages; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticExponentiationRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticExponentiationRule.php new file mode 100644 index 00000000000..1992b84a7b0 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticExponentiationRule.php @@ -0,0 +1,65 @@ + + */ +class OperandsInArithmeticExponentiationRule implements Rule +{ + + private OperatorRuleHelper $helper; + + public function __construct(OperatorRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return Expr::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node instanceof BinaryOpPow) { + $left = $node->left; + $right = $node->right; + } elseif ($node instanceof AssignOpPow) { + $left = $node->var; + $right = $node->expr; + } else { + return []; + } + + $messages = []; + $leftType = $scope->getType($left); + if (!$this->helper->isValidForArithmeticOperation($scope, $left)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in **, %s given on the left side.', + $leftType->describe(VerbosityLevel::typeOnly()), + ))->identifier('pow.leftNonNumeric')->build(); + } + + $rightType = $scope->getType($right); + if (!$this->helper->isValidForArithmeticOperation($scope, $right)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in **, %s given on the right side.', + $rightType->describe(VerbosityLevel::typeOnly()), + ))->identifier('pow.rightNonNumeric')->build(); + } + + return $messages; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticModuloRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticModuloRule.php new file mode 100644 index 00000000000..5b5f3c32679 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticModuloRule.php @@ -0,0 +1,65 @@ + + */ +class OperandsInArithmeticModuloRule implements Rule +{ + + private OperatorRuleHelper $helper; + + public function __construct(OperatorRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return Expr::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node instanceof BinaryOpMod) { + $left = $node->left; + $right = $node->right; + } elseif ($node instanceof AssignOpMod) { + $left = $node->var; + $right = $node->expr; + } else { + return []; + } + + $messages = []; + $leftType = $scope->getType($left); + if (!$this->helper->isValidForArithmeticOperation($scope, $left)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in %%, %s given on the left side.', + $leftType->describe(VerbosityLevel::typeOnly()), + ))->identifier('mod.leftNonNumeric')->build(); + } + + $rightType = $scope->getType($right); + if (!$this->helper->isValidForArithmeticOperation($scope, $right)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in %%, %s given on the right side.', + $rightType->describe(VerbosityLevel::typeOnly()), + ))->identifier('mod.rightNonNumeric')->build(); + } + + return $messages; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticMultiplicationRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticMultiplicationRule.php new file mode 100644 index 00000000000..353df4c67d7 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticMultiplicationRule.php @@ -0,0 +1,65 @@ + + */ +class OperandsInArithmeticMultiplicationRule implements Rule +{ + + private OperatorRuleHelper $helper; + + public function __construct(OperatorRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return Expr::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node instanceof BinaryOpMul) { + $left = $node->left; + $right = $node->right; + } elseif ($node instanceof AssignOpMul) { + $left = $node->var; + $right = $node->expr; + } else { + return []; + } + + $messages = []; + $leftType = $scope->getType($left); + if (!$this->helper->isValidForArithmeticOperation($scope, $left)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in *, %s given on the left side.', + $leftType->describe(VerbosityLevel::typeOnly()), + ))->identifier('mul.leftNonNumeric')->build(); + } + + $rightType = $scope->getType($right); + if (!$this->helper->isValidForArithmeticOperation($scope, $right)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in *, %s given on the right side.', + $rightType->describe(VerbosityLevel::typeOnly()), + ))->identifier('mul.rightNonNumeric')->build(); + } + + return $messages; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticSubtractionRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticSubtractionRule.php new file mode 100644 index 00000000000..5559d60fe08 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticSubtractionRule.php @@ -0,0 +1,65 @@ + + */ +class OperandsInArithmeticSubtractionRule implements Rule +{ + + private OperatorRuleHelper $helper; + + public function __construct(OperatorRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return Expr::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node instanceof BinaryOpMinus) { + $left = $node->left; + $right = $node->right; + } elseif ($node instanceof AssignOpMinus) { + $left = $node->var; + $right = $node->expr; + } else { + return []; + } + + $messages = []; + $leftType = $scope->getType($left); + if (!$this->helper->isValidForArithmeticOperation($scope, $left)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in -, %s given on the left side.', + $leftType->describe(VerbosityLevel::typeOnly()), + ))->identifier('minus.leftNonNumeric')->build(); + } + + $rightType = $scope->getType($right); + if (!$this->helper->isValidForArithmeticOperation($scope, $right)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in -, %s given on the right side.', + $rightType->describe(VerbosityLevel::typeOnly()), + ))->identifier('minus.rightNonNumeric')->build(); + } + + return $messages; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperatorRuleHelper.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperatorRuleHelper.php new file mode 100644 index 00000000000..6de54cab150 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperatorRuleHelper.php @@ -0,0 +1,92 @@ +ruleLevelHelper = $ruleLevelHelper; + } + + public function isValidForArithmeticOperation(Scope $scope, Expr $expr): bool + { + $type = $scope->getType($expr); + if ($type instanceof MixedType) { + return true; + } + + // already reported by PHPStan core + if ($type->toNumber() instanceof ErrorType) { + return true; + } + + return $this->isSubtypeOfNumber($scope, $expr); + } + + public function isValidForIncrement(Scope $scope, Expr $expr): bool + { + $type = $scope->getType($expr); + if ($type instanceof MixedType) { + return true; + } + + if ($type->isString()->yes()) { + // Because `$a = 'a'; $a++;` is valid + return true; + } + + return $this->isSubtypeOfNumber($scope, $expr); + } + + public function isValidForDecrement(Scope $scope, Expr $expr): bool + { + $type = $scope->getType($expr); + if ($type instanceof MixedType) { + return true; + } + + return $this->isSubtypeOfNumber($scope, $expr); + } + + private function isSubtypeOfNumber(Scope $scope, Expr $expr): bool + { + $acceptedType = new UnionType([new IntegerType(), new FloatType(), new IntersectionType([new StringType(), new AccessoryNumericStringType()])]); + + $type = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $expr, + '', + static fn (Type $type): bool => $acceptedType->isSuperTypeOf($type)->yes(), + )->getType(); + + if ($type instanceof ErrorType) { + return true; + } + + $isSuperType = $acceptedType->isSuperTypeOf($type); + if ($type instanceof BenevolentUnionType) { + return !$isSuperType->no(); + } + + return $isSuperType->yes(); + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/StrictCalls/DynamicCallOnStaticMethodsCallableRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/StrictCalls/DynamicCallOnStaticMethodsCallableRule.php new file mode 100644 index 00000000000..492aa604c42 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/StrictCalls/DynamicCallOnStaticMethodsCallableRule.php @@ -0,0 +1,65 @@ + + */ +class DynamicCallOnStaticMethodsCallableRule implements Rule +{ + + private RuleLevelHelper $ruleLevelHelper; + + public function __construct(RuleLevelHelper $ruleLevelHelper) + { + $this->ruleLevelHelper = $ruleLevelHelper; + } + + public function getNodeType(): string + { + return MethodCallableNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->getName() instanceof Node\Identifier) { + return []; + } + + $name = $node->getName()->name; + $type = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->getVar(), + '', + static fn (Type $type): bool => $type->canCallMethods()->yes() && $type->hasMethod($name)->yes(), + )->getType(); + + if ($type instanceof ErrorType || !$type->canCallMethods()->yes() || !$type->hasMethod($name)->yes()) { + return []; + } + + $methodReflection = $type->getMethod($name, $scope); + if ($methodReflection->isStatic()) { + return [ + RuleErrorBuilder::message(sprintf( + 'Dynamic call to static method %s::%s().', + $methodReflection->getDeclaringClass()->getDisplayName(), + $methodReflection->getName(), + ))->identifier('staticMethod.dynamicCall')->build(), + ]; + } + + return []; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/StrictCalls/DynamicCallOnStaticMethodsRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/StrictCalls/DynamicCallOnStaticMethodsRule.php new file mode 100644 index 00000000000..c0ae18b34cc --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/StrictCalls/DynamicCallOnStaticMethodsRule.php @@ -0,0 +1,76 @@ + + */ +class DynamicCallOnStaticMethodsRule implements Rule +{ + + private RuleLevelHelper $ruleLevelHelper; + + public function __construct(RuleLevelHelper $ruleLevelHelper) + { + $this->ruleLevelHelper = $ruleLevelHelper; + } + + public function getNodeType(): string + { + return MethodCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Node\Identifier) { + return []; + } + + $name = $node->name->name; + $type = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->var, + '', + static fn (Type $type): bool => $type->canCallMethods()->yes() && $type->hasMethod($name)->yes(), + )->getType(); + + if ($type instanceof ErrorType || !$type->canCallMethods()->yes() || !$type->hasMethod($name)->yes()) { + return []; + } + + $methodReflection = $type->getMethod($name, $scope); + if ($methodReflection->isStatic()) { + $prototype = $methodReflection->getPrototype(); + if (in_array($prototype->getDeclaringClass()->getName(), [ + TypeInferenceTestCase::class, + PHPStanTestCase::class, + ], true)) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Dynamic call to static method %s::%s().', + $methodReflection->getDeclaringClass()->getDisplayName(), + $methodReflection->getName(), + ))->identifier('staticMethod.dynamicCall')->build(), + ]; + } + + return []; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/StrictCalls/StrictFunctionCallsRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/StrictCalls/StrictFunctionCallsRule.php new file mode 100644 index 00000000000..f959fc91306 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/StrictCalls/StrictFunctionCallsRule.php @@ -0,0 +1,96 @@ + + */ +class StrictFunctionCallsRule implements Rule +{ + + /** @var int[] */ + private array $functionArguments = [ + 'in_array' => 2, + 'array_search' => 2, + 'base64_decode' => 1, + 'array_keys' => 2, + ]; + + private ReflectionProvider $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + + public function getNodeType(): string + { + return FuncCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Name) { + return []; + } + + if (!$this->reflectionProvider->hasFunction($node->name, $scope)) { + return []; + } + + $function = $this->reflectionProvider->getFunction($node->name, $scope); + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs($scope, $node->getArgs(), $function->getVariants()); + $node = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $node); + if ($node === null) { + return []; + } + $functionName = strtolower($function->getName()); + if (!array_key_exists($functionName, $this->functionArguments)) { + return []; + } + + if ($functionName === 'array_keys' && !array_key_exists(1, $node->getArgs())) { + return []; + } + + $argumentPosition = $this->functionArguments[$functionName]; + if (!array_key_exists($argumentPosition, $node->getArgs())) { + return [ + RuleErrorBuilder::message(sprintf( + 'Call to function %s() requires parameter #%d to be set.', + $functionName, + $argumentPosition + 1, + ))->identifier('function.strict')->build(), + ]; + } + + $argumentType = $scope->getType($node->getArgs()[$argumentPosition]->value); + $trueType = new ConstantBooleanType(true); + if (!$trueType->isSuperTypeOf($argumentType)->yes()) { + return [ + RuleErrorBuilder::message(sprintf( + 'Call to function %s() requires parameter #%d to be true.', + $functionName, + $argumentPosition + 1, + ))->identifier('function.strict')->build(), + ]; + } + + return []; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/SwitchConditions/MatchingTypeInSwitchCaseConditionRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/SwitchConditions/MatchingTypeInSwitchCaseConditionRule.php new file mode 100644 index 00000000000..ba7c92f3a0a --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/SwitchConditions/MatchingTypeInSwitchCaseConditionRule.php @@ -0,0 +1,60 @@ + + */ +class MatchingTypeInSwitchCaseConditionRule implements Rule +{ + + private Printer $printer; + + public function __construct(Printer $printer) + { + $this->printer = $printer; + } + + public function getNodeType(): string + { + return Switch_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $messages = []; + $conditionType = $scope->getType($node->cond); + foreach ($node->cases as $case) { + if ($case->cond === null) { + continue; + } + + $caseType = $scope->getType($case->cond); + if (!$conditionType->isSuperTypeOf($caseType)->no()) { + continue; + } + + $messages[] = RuleErrorBuilder::message(sprintf( + 'Switch condition type (%s) does not match case condition %s (%s).', + $conditionType->describe(VerbosityLevel::value()), + $this->printer->prettyPrintExpr($case->cond), + $caseType->describe(VerbosityLevel::typeOnly()), + )) + ->line($case->getStartLine()) + ->identifier('switch.type') + ->build(); + } + + return $messages; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableMethodCallRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableMethodCallRule.php new file mode 100644 index 00000000000..d55fc7894d0 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableMethodCallRule.php @@ -0,0 +1,38 @@ + + */ +class VariableMethodCallRule implements Rule +{ + + public function getNodeType(): string + { + return MethodCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->name instanceof Node\Identifier) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Variable method call on %s.', + $scope->getType($node->var)->describe(VerbosityLevel::typeOnly()), + ))->identifier('method.dynamicName')->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableMethodCallableRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableMethodCallableRule.php new file mode 100644 index 00000000000..dd891a9e8c5 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableMethodCallableRule.php @@ -0,0 +1,38 @@ + + */ +class VariableMethodCallableRule implements Rule +{ + + public function getNodeType(): string + { + return MethodCallableNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->getName() instanceof Node\Identifier) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Variable method call on %s.', + $scope->getType($node->getVar())->describe(VerbosityLevel::typeOnly()), + ))->identifier('method.dynamicName')->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariablePropertyFetchRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariablePropertyFetchRule.php new file mode 100644 index 00000000000..760bff697e8 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariablePropertyFetchRule.php @@ -0,0 +1,94 @@ + + */ +class VariablePropertyFetchRule implements Rule +{ + + private ReflectionProvider $reflectionProvider; + + /** @var string[] */ + private array $universalObjectCratesClasses; + + /** + * @param string[] $universalObjectCratesClasses + */ + public function __construct(ReflectionProvider $reflectionProvider, array $universalObjectCratesClasses) + { + $this->reflectionProvider = $reflectionProvider; + $this->universalObjectCratesClasses = $universalObjectCratesClasses; + } + + public function getNodeType(): string + { + return PropertyFetch::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->name instanceof Node\Identifier) { + return []; + } + + $fetchedOnType = $scope->getType($node->var); + foreach ($fetchedOnType->getObjectClassNames() as $referencedClass) { + if (!$this->reflectionProvider->hasClass($referencedClass)) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($referencedClass); + if ( + $this->isUniversalObjectCrate($classReflection) + || $this->isSimpleXMLElement($classReflection) + ) { + return []; + } + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Variable property access on %s.', + $fetchedOnType->describe(VerbosityLevel::typeOnly()), + ))->identifier('property.dynamicName')->build(), + ]; + } + + private function isSimpleXMLElement( + ClassReflection $classReflection + ): bool + { + return $classReflection->is(SimpleXMLElement::class); + } + + private function isUniversalObjectCrate( + ClassReflection $classReflection + ): bool + { + foreach ($this->universalObjectCratesClasses as $className) { + if (!$this->reflectionProvider->hasClass($className)) { + continue; + } + + if ($classReflection->is($className)) { + return true; + } + } + + return false; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableStaticMethodCallRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableStaticMethodCallRule.php new file mode 100644 index 00000000000..963f01d09a2 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableStaticMethodCallRule.php @@ -0,0 +1,44 @@ + + */ +class VariableStaticMethodCallRule implements Rule +{ + + public function getNodeType(): string + { + return StaticCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->name instanceof Node\Identifier) { + return []; + } + + if ($node->class instanceof Node\Name) { + $methodCalledOn = $scope->resolveName($node->class); + } else { + $methodCalledOn = $scope->getType($node->class)->describe(VerbosityLevel::typeOnly()); + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Variable static method call on %s.', + $methodCalledOn, + ))->identifier('staticMethod.dynamicName')->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableStaticMethodCallableRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableStaticMethodCallableRule.php new file mode 100644 index 00000000000..2cfebaca86e --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableStaticMethodCallableRule.php @@ -0,0 +1,44 @@ + + */ +class VariableStaticMethodCallableRule implements Rule +{ + + public function getNodeType(): string + { + return StaticMethodCallableNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->getName() instanceof Node\Identifier) { + return []; + } + + if ($node->getClass() instanceof Node\Name) { + $methodCalledOn = $scope->resolveName($node->getClass()); + } else { + $methodCalledOn = $scope->getType($node->getClass())->describe(VerbosityLevel::typeOnly()); + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Variable static method call on %s.', + $methodCalledOn, + ))->identifier('staticMethod.dynamicName')->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableStaticPropertyFetchRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableStaticPropertyFetchRule.php new file mode 100644 index 00000000000..bc4759928b3 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableStaticPropertyFetchRule.php @@ -0,0 +1,44 @@ + + */ +class VariableStaticPropertyFetchRule implements Rule +{ + + public function getNodeType(): string + { + return StaticPropertyFetch::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->name instanceof Node\Identifier) { + return []; + } + + if ($node->class instanceof Node\Name) { + $propertyAccessedOn = $scope->resolveName($node->class); + } else { + $propertyAccessedOn = $scope->getType($node->class)->describe(VerbosityLevel::typeOnly()); + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Variable static property access on %s.', + $propertyAccessedOn, + ))->identifier('staticProperty.dynamicName')->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableVariablesRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableVariablesRule.php new file mode 100644 index 00000000000..f78e4ef422f --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableVariablesRule.php @@ -0,0 +1,36 @@ + + */ +class VariableVariablesRule implements Rule +{ + + public function getNodeType(): string + { + return Variable::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (is_string($node->name)) { + return []; + } + + return [ + RuleErrorBuilder::message('Variable variables are not allowed.') + ->identifier('variable.dynamicName') + ->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan/LICENSE b/tools/.phpstan/vendor/phpstan/phpstan/LICENSE new file mode 100644 index 00000000000..e5f34e607a1 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2016 Ondřej Mirtes +Copyright (c) 2025 PHPStan s.r.o. + +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. diff --git a/tools/.phpstan/vendor/phpstan/phpstan/README.md b/tools/.phpstan/vendor/phpstan/phpstan/README.md new file mode 100644 index 00000000000..49bed4fd7a4 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan/README.md @@ -0,0 +1,118 @@ +

    PHPStan - PHP Static Analysis Tool

    + +

    + PHPStan +

    + +

    + Build Status + Latest Stable Version + Total Downloads + License + PHPStan Enabled +

    + +------ + +PHPStan focuses on finding errors in your code without actually running it. It catches whole classes of bugs +even before you write tests for the code. It moves PHP closer to compiled languages in the sense that the correctness of each line of the code +can be checked before you run the actual line. + +**[Read more about PHPStan »](https://phpstan.org/)** + +**[Try out PHPStan on the on-line playground! »](https://phpstan.org/try)** + +## Sponsors + +Want your logo here? [Learn more »](https://phpstan.org/sponsor) + +### Gold Sponsors + +Matt Mullenweg +Mojam + +

    + +### Silver Sponsors + +ShipMonk +Shopware + +

    + +### Bronze Sponsors + +TheCodingMachine +    +Private Packagist +
    +CDN77 +    +Blackfire.io +
    +iO +    +Fame Helsinki +
    +Belsimpel +    +Togetter +
    +RightCapital +    +Shoptet +
    +ZOL +    +EdgeNext +
    +Route4Me: Route Optimizer and Route Planner Software +    +Craft CMS +
    +TicketSwap +    +campoint AG +
    +Crisp.nl +    +Inviqa +
    + + + +[**You can sponsor my open-source work on PHPStan through GitHub Sponsors and also directly.**](https://phpstan.org/sponsor) + +One-time donations [through Revolut.me](https://revolut.me/ondrejmirtes) are also accepted. To request an invoice, [contact me](mailto:ondrej@mirtes.cz) through e-mail. + +## Documentation + +All the documentation lives on the [phpstan.org website](https://phpstan.org/): + +* [Getting Started & User Guide](https://phpstan.org/user-guide/getting-started) +* [Config Reference](https://phpstan.org/config-reference) +* [PHPDocs Basics](https://phpstan.org/writing-php-code/phpdocs-basics) & [PHPDoc Types](https://phpstan.org/writing-php-code/phpdoc-types) +* [Extension Library](https://phpstan.org/user-guide/extension-library) +* [Developing Extensions](https://phpstan.org/developing-extensions/extension-types) +* [API Reference](https://apiref.phpstan.org/) + +## PHPStan Pro + +PHPStan Pro is a paid add-on on top of open-source PHPStan Static Analysis Tool with these premium features: + +* Web UI for browsing found errors, you can click and open your editor of choice on the offending line. +* Continuous analysis (watch mode): scans changed files in the background, refreshes the UI automatically. + +Try it on PHPStan 0.12.45 or later by running it with the `--pro` option. You can create an account either by following the on-screen instructions, or by visiting [account.phpstan.com](https://account.phpstan.com/). + +After 30-day free trial period it costs 7 EUR for individuals monthly, 70 EUR for teams (up to 25 members). By paying for PHPStan Pro, you're supporting the development of open-source PHPStan. + +You can read more about it on [PHPStan's website](https://phpstan.org/blog/introducing-phpstan-pro). + +## Code of Conduct + +This project adheres to a [Contributor Code of Conduct](https://github.com/phpstan/phpstan/blob/master/CODE_OF_CONDUCT.md). By participating in this project and its community, you are expected to uphold this code. + +## Contributing + +Any contributions are welcome. PHPStan's source code open to pull requests lives at [`phpstan/phpstan-src`](https://github.com/phpstan/phpstan-src). diff --git a/tools/.phpstan/vendor/phpstan/phpstan/UPGRADING.md b/tools/.phpstan/vendor/phpstan/phpstan/UPGRADING.md new file mode 100644 index 00000000000..52294909653 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan/UPGRADING.md @@ -0,0 +1,338 @@ +Upgrading from PHPStan 1.x to 2.0 +================================= + +## PHP version requirements + +PHPStan now requires PHP 7.4 or newer to run. + +## Upgrading guide for end users + +The best way to get ready for upgrade to PHPStan 2.0 is to update to the **latest PHPStan 1.12 release** +and enable [**Bleeding Edge**](https://phpstan.org/blog/what-is-bleeding-edge). This will enable the new rules and behaviours that 2.0 turns on for all users. + +Also make sure to install and enable [`phpstan/phpstan-deprecation-rules`](https://github.com/phpstan/phpstan-deprecation-rules). + +Once you get to a green build with no deprecations showed on latest PHPStan 1.12.x with Bleeding Edge enabled, you can update all your related PHPStan dependencies to 2.0 in `composer.json`: + +```json +"require-dev": { + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-doctrine": "^2.0", + "phpstan/phpstan-nette": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpstan/phpstan-symfony": "^2.0", + "phpstan/phpstan-webmozart-assert": "^2.0", + ... +} +``` + +Don't forget to update [3rd party PHPStan extensions](https://phpstan.org/user-guide/extension-library) as well. + +After changing your `composer.json`, run `composer update 'phpstan/*' -W`. + +It's up to you whether you go through the new reported errors or if you just put them all to the [baseline](https://phpstan.org/user-guide/baseline) ;) Everyone who's on PHPStan 1.12 should be able to upgrade to PHPStan 2.0. + +### Noteworthy changes to code analysis + +* [**Enhancements in handling parameters passed by reference**](https://phpstan.org/blog/enhancements-in-handling-parameters-passed-by-reference) +* [**Validate inline PHPDoc `@var` tag type**](https://phpstan.org/blog/phpstan-1-10-comes-with-lie-detector#validate-inline-phpdoc-%40var-tag-type) +* [**List type enforced**](https://phpstan.org/blog/phpstan-1-9-0-with-phpdoc-asserts-list-type#list-type) +* **Always `true` conditions always reported**: previously reported only with phpstan-strict-rules, this is now always reported. + +### Removed option `checkMissingIterableValueType` + +It's strongly recommended to add the missing array typehints. + +If you want to continue ignoring missing typehints from arrays, add `missingType.iterableValue` error identifier to your `ignoreErrors`: + +```neon +parameters: + ignoreErrors: + - + identifier: missingType.iterableValue +``` + +### Removed option `checkGenericClassInNonGenericObjectType` + +It's strongly recommended to add the missing generic typehints. + +If you want to continue ignoring missing typehints from generics, add `missingType.generics` error identifier to your `ignoreErrors`: + +```neon +parameters: + ignoreErrors: + - + identifier: missingType.generics +``` + +### Removed `checkAlwaysTrue*` options + +These options have been removed because PHPStan now always behaves as if these were set to `true`: + +* `checkAlwaysTrueCheckTypeFunctionCall` +* `checkAlwaysTrueInstanceof` +* `checkAlwaysTrueStrictComparison` +* `checkAlwaysTrueLooseComparison` + +### Removed option `excludes_analyse` + +It has been replaced with [`excludePaths`](https://phpstan.org/user-guide/ignoring-errors#excluding-whole-files). + +### Paths in `excludePaths` and `ignoreErrors` have to be a valid file path or a fnmatch pattern + +If you are excluding a file path that might not exist but you still want to have it in `excludePaths`, append `(?)`: + +```neon +parameters: + excludePaths: + - tests/*/data/* + - src/broken + - node_modules (?) # optional path, might not exist +``` + +If you have the same situation in `ignoreErrors` (ignoring an error in a path that might not exist), use `reportUnmatchedIgnoredErrors: false`. + +```neon +parameters: + reportUnmatchedIgnoredErrors: false +``` + +Appending `(?)` in `ignoreErrors` is not supported. + +### Changes in 1st party PHPStan extensions + +* [phpstan-doctrine](https://github.com/phpstan/phpstan-doctrine) + * Removed config parameter `searchOtherMethodsForQueryBuilderBeginning` (extension now behaves as when this was set to `true`) + * Removed config parameter `queryBuilderFastAlgorithm` (extension now behaves as when this was set to `false`) +* [phpstan-symfony](https://github.com/phpstan/phpstan-symfony) + * Removed legacy options with `_` in the name + * `container_xml_path` -> use `containerXmlPath` + * `constant_hassers` -> use `constantHassers` + * `console_application_loader` -> use `consoleApplicationLoader` + +### Minor backward compatibility breaks + +* Removed unused config parameter `cache.nodesByFileCountMax` +* Removed unused config parameter `memoryLimitFile` +* Removed unused feature toggle `disableRuntimeReflectionProvider` +* Removed unused config parameter `staticReflectionClassNamePatterns` +* Remove `fixerTmpDir` config parameter, use `pro.tmpDir` instead +* Remove `tempResultCachePath` config parameter, use `resultCachePath` instead +* `additionalConfigFiles` config parameter must be a list + +## Upgrading guide for extension developers + +> [!NOTE] +> Please switch to PHPStan 2.0 in a new major version of your extension. It's not feasible to try to support both PHPStan 1.x and PHPStan 2.x with the same extension code. +> +> You can definitely get closer to supporting PHPStan 2.0 without increasing major version by solving reported deprecations and other issues by analysing your extension code with PHPStan & phpstan-deprecation-rules & Bleeding Edge, but the final leap and solving backward incompatibilities should be done by requiring `"phpstan/phpstan": "^2.0"` in your `composer.json`, and releasing a new major version. + +### PHPStan now uses nikic/php-parser v5 + +See [UPGRADING](https://github.com/nikic/PHP-Parser/blob/master/UPGRADE-5.0.md) guide for PHP-Parser. + +The most notable change is how `throw` statement is represented. Previously, `throw` statements like `throw $e;` were represented using the `Stmt\Throw_` class, while uses inside other expressions (such as `$x ?? throw $e`) used the `Expr\Throw_` class. + +Now, `throw $e;` is represented as a `Stmt\Expression` that contains an `Expr\Throw_`. The +`Stmt\Throw_` class has been removed. + +### PHPStan now uses phpstan/phpdoc-parser v2 + +See [UPGRADING](https://github.com/phpstan/phpdoc-parser/blob/2.0.x/UPGRADING.md) guide for phpstan/phpdoc-parser. + +### Returning plain strings as errors no longer supported, use RuleErrorBuilder + +Identifiers are also required in custom rules. + +Learn more: [Using RuleErrorBuilder to enrich reported errors in custom rules](https://phpstan.org/blog/using-rule-error-builder) + +**Before**: + +```php +return ['My error']; +``` + +**After**: + +```php +return [ + RuleErrorBuilder::message('My error') + ->identifier('my.error') + ->build(), +]; +``` + +### Deprecate various `instanceof *Type` in favour of new methods on `Type` interface + +Learn more: [Why Is instanceof *Type Wrong and Getting Deprecated?](https://phpstan.org/blog/why-is-instanceof-type-wrong-and-getting-deprecated) + +### Removed deprecated `ParametersAcceptorSelector::selectSingle()` + +Use [`ParametersAcceptorSelector::selectFromArgs()`](https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ParametersAcceptorSelector.html#_selectFromArgs) instead. It should be used in most places where `selectSingle()` was previously used, like dynamic return type extensions. + +**Before**: + +```php +$defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); +``` + +**After**: + +```php +$defaultReturnType = ParametersAcceptorSelector::selectFromArgs( + $scope, + $functionCall->getArgs(), + $functionReflection->getVariants() +)->getReturnType(); +``` + +If you're analysing function or method body itself and you're using one of the following methods, ask for `getParameters()` and `getReturnType()` directly on the reflection object: + +* [InClassMethodNode::getMethodReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.InClassMethodNode.html) +* [InFunctionNode::getFunctionReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.InFunctionNode.html) +* [FunctionReturnStatementsNode::getFunctionReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.FunctionReturnStatementsNode.html) +* [MethodReturnStatementsNode::getMethodReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.MethodReturnStatementsNode.html) +* [Scope::getFunction()](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.Scope.html#_getFunction) + +**Before**: + +```php +$function = $node->getFunctionReflection(); +$returnType = ParametersAcceptorSelector::selectSingle($function->getVariants())->getReturnType(); +``` + +**After**: + +```php +$returnType = $node->getFunctionReflection()->getReturnType(); +``` + +### Changed `TypeSpecifier::create()` and `SpecifiedTypes` constructor parameters + +[`PHPStan\Analyser\TypeSpecifier::create()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.TypeSpecifier.html#_create) now accepts (all parameters are required): + +* `Expr $expr` +* `Type $type` +* `TypeSpecifierContext $context` +* `Scope $scope` + +If you want to change `$overwrite` or `$rootExpr` (previous parameters also used to be accepted by this method), call `setAlwaysOverwriteTypes()` and `setRootExpr()` on [`SpecifiedTypes`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html) (object returned by `TypeSpecifier::create()`). These methods return a new object (SpecifiedTypes is immutable). + +[`SpecifiedTypes`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html) constructor now accepts: + +* `array $sureTypes` +* `array $sureNotTypes` + +If you want to change `$overwrite` or `$rootExpr` (previous parameters also used to be accepted by the constructor), call `setAlwaysOverwriteTypes()` and `setRootExpr()`. These methods return a new object (SpecifiedTypes is immutable). + +### `ConstantArrayType` no longer extends `ArrayType` + +`Type::getArrays()` now returns `list`. + +Using `$type instanceof ArrayType` is [being deprecated anyway](https://phpstan.org/blog/why-is-instanceof-type-wrong-and-getting-deprecated) so the impact of this change should be minimal. + +### Changed `TypeSpecifier::specifyTypesInCondition()` + +This method no longer accepts `Expr $rootExpr`. If you want to change it, call `setRootExpr()` on [`SpecifiedTypes`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html) (object returned by `TypeSpecifier::specifyTypesInCondition()`). `setRootExpr()` method returns a new object (SpecifiedTypes is immutable). + +### Node attributes `parent`, `previous`, `next` are no longer available + +Learn more: https://phpstan.org/blog/preprocessing-ast-for-custom-rules + +### Removed config parameter `scopeClass` + +As a replacement you can implement [`PHPStan\Type\ExpressionTypeResolverExtension`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.ExpressionTypeResolverExtension.html) interface instead and register it as a service. + +### Removed `PHPStan\Broker\Broker` + +Use [`PHPStan\Reflection\ReflectionProvider`](https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ReflectionProvider.html) instead. + +`BrokerAwareExtension` was also removed. Ask for `ReflectionProvider` in the extension constructor instead. + +Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createReflectionProvider()`. + +### List type is enabled for everyone + +Removed static methods from `AccessoryArrayListType` class: + +* `isListTypeEnabled()` +* `setListTypeEnabled()` +* `intersectWith()` + +Instead of `AccessoryArrayListType::intersectWith($type)`, do `TypeCombinator::intersect($type, new AccessoryArrayListType())`. + +### Minor backward compatibility breaks + +* Classes that were previously `@final` were made `final` +* Parameter `$callableParameters` of [`MutatingScope::enterAnonymousFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterAnonymousFunction) and [`enterArrowFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterArrowFunction) made required +* Parameter `StatementContext $context` of [`NodeScopeResolver::processStmtNodes()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.NodeScopeResolver.html#_processStmtNodes) made required +* ClassPropertiesNode - remove `$extensions` parameter from [`getUninitializedProperties()`](https://apiref.phpstan.org/2.0.x/PHPStan.Node.ClassPropertiesNode.html#_getUninitializedProperties) +* `Type::getSmallerType()`, `Type::getSmallerOrEqualType()`, `Type::getGreaterType()`, `Type::getGreaterOrEqualType()`, `Type::isSmallerThan()`, `Type::isSmallerThanOrEqual()` now require [`PhpVersion`](https://apiref.phpstan.org/2.0.x/PHPStan.Php.PhpVersion.html) as argument. +* `CompoundType::isGreaterThan()`, `CompoundType::isGreaterThanOrEqual()` now require [`PhpVersion`](https://apiref.phpstan.org/2.0.x/PHPStan.Php.PhpVersion.html) as argument. +* Removed `ReflectionProvider::supportsAnonymousClasses()` (all reflection providers support anonymous classes) +* Remove `ArrayType::generalizeKeys()` +* Remove `ArrayType::count()`, use `Type::getArraySize()` instead +* Remove `ArrayType::castToArrayKeyType()`, `Type::toArrayKey()` instead +* Remove `UnionType::pickTypes()`, use `pickFromTypes()` instead +* Remove `RegexArrayShapeMatcher::matchType()`, use `matchExpr()` instead +* Remove unused `PHPStanTestCase::$useStaticReflectionProvider` +* Remove `PHPStanTestCase::getReflectors()`, use `getReflector()` instead +* Remove `ClassReflection::getFileNameWithPhpDocs()`, use `getFileName()` instead +* Remove `AnalysisResult::getInternalErrors()`, use `getInternalErrorObjects()` instead +* Remove `ConstantReflection::getValue()`, use `getValueExpr()` instead. To get `Type` from `Expr`, use `Scope::getType()` or `InitializerExprTypeResolver::getType()` +* Remove `PropertyTag::getType()`, use `getReadableType()` / `getWritableType()` instead +* Remove `GenericTypeVariableResolver`, use [`Type::getTemplateType()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getTemplateType) instead +* Rename `Type::isClassStringType()` to `Type::isClassString()` +* Remove `Scope::isSpecified()`, use `hasExpressionType()` instead +* Remove `ConstantArrayType::isEmpty()`, use `isIterableAtLeastOnce()->no()` instead +* Remove `ConstantArrayType::getNextAutoIndex()` +* Removed methods from `ConstantArrayType` - `getFirst*Type` and `getLast*Type` + * Use `getFirstIterable*Type` and `getLastIterable*Type` instead +* Remove `ConstantArrayType::generalizeToArray()` +* Remove `ConstantArrayType::findTypeAndMethodName()`, use `findTypeAndMethodNames()` instead +* Remove `ConstantArrayType::removeLast()`, use [`Type::popArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_popArray) instead +* Remove `ConstantArrayType::removeFirst()`, use [`Type::shiftArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_shiftArray) instead +* Remove `ConstantArrayType::reverse()`, use [`Type::reverseArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_reverseArray) instead +* Remove `ConstantArrayType::chunk()`, use [`Type::chunkArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_chunkArray) instead +* Remove `ConstantArrayType::slice()`, use [`Type::sliceArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_sliceArray) instead +* Made `TypeUtils` thinner by removing methods: + * Remove `TypeUtils::getArrays()` and `getAnyArrays()`, use [`Type::getArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getArrays) instead + * Remove `TypeUtils::getConstantArrays()` and `getOldConstantArrays()`, use [`Type::getConstantArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantArrays) instead + * Remove `TypeUtils::getConstantStrings()`, use [`Type::getConstantStrings()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantStrings) instead + * Remove `TypeUtils::getConstantTypes()` and `getAnyConstantTypes()`, use [`Type::isConstantValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue) or [`Type::generalize()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize) + * Remove `TypeUtils::generalizeType()`, use [`Type::generalize()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize) instead + * Remove `TypeUtils::getDirectClassNames()`, use [`Type::getObjectClassNames()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getObjectClassNames) instead + * Remove `TypeUtils::getConstantScalars()`, use [`Type::isConstantScalarValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantScalarValue) or [`Type::getConstantScalarTypes()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantScalarTypes) instead + * Remove `TypeUtils::getEnumCaseObjects()`, use [`Type::getEnumCases()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getEnumCases) instead + * Remove `TypeUtils::containsCallable()`, use [`Type::isCallable()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isCallable) instead +* Removed `Scope::doNotTreatPhpDocTypesAsCertain()`, use `getNativeType()` instead +* Parameter `$isList` in `ConstantArrayType` constructor can only be `TrinaryLogic`, no longer `bool` +* Parameter `$nextAutoIndexes` in `ConstantArrayType` constructor can only be `non-empty-list`, no longer `int` +* Remove `ConstantType` interface, use [`Type::isConstantValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue) instead +* `acceptsNamedArguments()` in `FunctionReflection`, `ExtendedMethodReflection` and `CallableParametersAcceptor` interfaces returns `TrinaryLogic` instead of `bool` +* Remove `FunctionReflection::isFinal()` +* [`Type::getProperty()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getProperty) now returns [`ExtendedPropertyReflection`](https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ExtendedPropertyReflection.html) +* Remove `__set_state()` on objects that should not be serialized in cache +* Parameter `$selfClass` of [`TypehintHelper::decideTypeFromReflection()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.TypehintHelper.html#_decideTypeFromReflection) no longer accepts `string` +* `LevelsTestCase::dataTopics()` data provider made static +* `PHPStan\Node\Printer\Printer` no longer autowired as `PhpParser\PrettyPrinter\Standard`, use `PHPStan\Node\Printer\Printer` in the typehint +* Remove `Type::acceptsWithReason()`, `Type:accepts()` return type changed from `TrinaryLogic` to [`AcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html) +* Remove `CompoundType::isAcceptedWithReasonBy()`, `CompoundType::isAcceptedBy()` return type changed from `TrinaryLogic` to [`AcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html) +Remove `Type::isSuperTypeOfWithReason()`, `Type:isSuperTypeOf()` return type changed from `TrinaryLogic` to [`IsSuperTypeOfResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.IsSuperTypeOfResult.html) +* Remove `CompoundType::isSubTypeOfWithReasonBy()`, `CompoundType::isSubTypeOf()` return type changed from `TrinaryLogic` to [`IsSuperTypeOfResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.IsSuperTypeOfResult.html) +* Remove `TemplateType::isValidVarianceWithReason()`, changed `TemplateType::isValidVariance()` return type to [`IsSuperTypeOfResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.IsSuperTypeOfResult.html) +* `RuleLevelHelper::accepts()` return type changed from `bool` to [`RuleLevelHelperAcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html) +* Changes around `ClassConstantReflection` + * Class `ClassConstantReflection` removed from BC promise, renamed to `RealClassConstantReflection` + * Interface `ConstantReflection` renamed to `ClassConstantReflection` + * Added more methods around PHPDoc types and native types to the (new) `ClassConstantReflection` + * Interface `GlobalConstantReflection` renamed to `ConstantReflection` +* Renamed interfaces and classes from `*WithPhpDocs` to `Extended*` + * `ParametersAcceptorWithPhpDocs` -> `ExtendedParametersAcceptor` + * `ParameterReflectionWithPhpDocs` -> `ExtendedParameterReflection` + * `FunctionVariantWithPhpDocs` -> `ExtendedFunctionVariant` +* `ClassPropertyNode::getNativeType()` return type changed from AST node to `Type|null` +* Class `PHPStan\Node\ClassMethod` (accessible from `ClassMethodsNode`) is no longer an AST node + * Call `PHPStan\Node\ClassMethod::getNode()` to access the original AST node diff --git a/tools/.phpstan/vendor/phpstan/phpstan/bootstrap.php b/tools/.phpstan/vendor/phpstan/phpstan/bootstrap.php new file mode 100644 index 00000000000..889755e6008 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan/bootstrap.php @@ -0,0 +1,144 @@ +loadClass($class); + + return; + } + if (strpos($class, 'PHPStan\\') !== 0 || strpos($class, 'PHPStan\\PhpDocParser\\') === 0) { + return; + } + + if (!in_array('phar', stream_get_wrappers(), true)) { + throw new \Exception('Phar wrapper is not registered. Please review your php.ini settings.'); + } + + if (!self::$polyfillsLoaded) { + self::$polyfillsLoaded = true; + + if ( + PHP_VERSION_ID < 80000 + && empty($GLOBALS['__composer_autoload_files']['a4a119a56e50fbb293281d9a48007e0e']) + && !class_exists(\Symfony\Polyfill\Php80\Php80::class, false) + ) { + $GLOBALS['__composer_autoload_files']['a4a119a56e50fbb293281d9a48007e0e'] = true; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php80/Php80.php'; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php80/bootstrap.php'; + } + + if ( + empty($GLOBALS['__composer_autoload_files']['0e6d7bf4a5811bfa5cf40c5ccd6fae6a']) + && !class_exists(\Symfony\Polyfill\Mbstring\Mbstring::class, false) + ) { + $GLOBALS['__composer_autoload_files']['0e6d7bf4a5811bfa5cf40c5ccd6fae6a'] = true; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-mbstring/Mbstring.php'; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-mbstring/bootstrap.php'; + } + + if ( + empty($GLOBALS['__composer_autoload_files']['e69f7f6ee287b969198c3c9d6777bd38']) + && !class_exists(\Symfony\Polyfill\Intl\Normalizer\Normalizer::class, false) + ) { + $GLOBALS['__composer_autoload_files']['e69f7f6ee287b969198c3c9d6777bd38'] = true; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-intl-normalizer/Normalizer.php'; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-intl-normalizer/bootstrap.php'; + } + + if ( + !extension_loaded('intl') + && empty($GLOBALS['__composer_autoload_files']['8825ede83f2f289127722d4e842cf7e8']) + && !class_exists(\Symfony\Polyfill\Intl\Grapheme\Grapheme::class, false) + ) { + $GLOBALS['__composer_autoload_files']['8825ede83f2f289127722d4e842cf7e8'] = true; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-intl-grapheme/Grapheme.php'; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-intl-grapheme/bootstrap.php'; + } + + if ( + PHP_VERSION_ID < 80100 + && empty ($GLOBALS['__composer_autoload_files']['23c18046f52bef3eea034657bafda50f']) + && !class_exists(\Symfony\Polyfill\Php81\Php81::class, false) + ) { + $GLOBALS['__composer_autoload_files']['23c18046f52bef3eea034657bafda50f'] = true; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php81/Php81.php'; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php81/bootstrap.php'; + } + + if ( + PHP_VERSION_ID < 80300 + && empty ($GLOBALS['__composer_autoload_files']['662a729f963d39afe703c9d9b7ab4a8c']) + && !class_exists(\Symfony\Polyfill\Php83\Php83::class, false) + ) { + $GLOBALS['__composer_autoload_files']['662a729f963d39afe703c9d9b7ab4a8c'] = true; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php83/Php83.php'; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php83/bootstrap.php'; + } + + if ( + PHP_VERSION_ID < 80400 + && empty ($GLOBALS['__composer_autoload_files']['9d2b9fc6db0f153a0a149fefb182415e']) + && !class_exists(\Symfony\Polyfill\Php83\Php84::class, false) + ) { + $GLOBALS['__composer_autoload_files']['9d2b9fc6db0f153a0a149fefb182415e'] = true; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php84/Php84.php'; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php84/bootstrap.php'; + } + + if ( + PHP_VERSION_ID < 80500 + && empty ($GLOBALS['__composer_autoload_files']['606a39d89246991a373564698c2d8383']) + && !class_exists(\Symfony\Polyfill\Php83\Php85::class, false) + ) { + $GLOBALS['__composer_autoload_files']['606a39d89246991a373564698c2d8383'] = true; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php85/Php85.php'; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php85/bootstrap.php'; + } + } + + $filename = str_replace('\\', DIRECTORY_SEPARATOR, $class); + if (strpos($class, 'PHPStan\\BetterReflection\\') === 0) { + $filename = substr($filename, strlen('PHPStan\\BetterReflection\\')); + $filepath = 'phar://' . __DIR__ . '/phpstan.phar/vendor/ondrejmirtes/better-reflection/src/' . $filename . '.php'; + } else { + $filename = substr($filename, strlen('PHPStan\\')); + $filepath = 'phar://' . __DIR__ . '/phpstan.phar/src/' . $filename . '.php'; + } + + if (!file_exists($filepath)) { + return; + } + + require $filepath; + } +} + +spl_autoload_register([PharAutoloader::class, 'loadClass']); diff --git a/tools/.phpstan/vendor/phpstan/phpstan/composer.json b/tools/.phpstan/vendor/phpstan/phpstan/composer.json new file mode 100644 index 00000000000..8b51d4b7166 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan/composer.json @@ -0,0 +1,31 @@ +{ + "name": "phpstan/phpstan", + "description": "PHPStan - PHP Static Analysis Tool", + "license": ["MIT"], + "keywords": ["dev", "static analysis"], + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "autoload": { + "files": ["bootstrap.php"] + }, + "source": { + "type": "", + "url": "", + "reference": "" + }, + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "forum": "https://github.com/phpstan/phpstan/discussions", + "source": "https://github.com/phpstan/phpstan-src", + "docs": "https://phpstan.org/user-guide/getting-started", + "security": "https://github.com/phpstan/phpstan/security/policy" + } +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan/conf/bleedingEdge.neon b/tools/.phpstan/vendor/phpstan/phpstan/conf/bleedingEdge.neon new file mode 100644 index 00000000000..01fee972d82 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan/conf/bleedingEdge.neon @@ -0,0 +1,2 @@ +includes: + - phar://phpstan.phar/conf/bleedingEdge.neon diff --git a/tools/.phpstan/vendor/phpstan/phpstan/phpstan b/tools/.phpstan/vendor/phpstan/phpstan/phpstan new file mode 100755 index 00000000000..7a08ef4850b --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan/phpstan @@ -0,0 +1,8 @@ +#!/usr/bin/env php + + +
    + AI abilities sea level rising... as way to rise type coverage for class elements +
    + +
    + +PHPStan uses type declarations to determine the type of variables, properties and other expression. Sometimes it's hard to see what PHPStan errors are the important ones among thousands of others. + +Instead of fixing all PHPStan errors at once, we can start with minimal require type coverage. + +
    + +What is the type coverage you ask? We have 4 type possible declarations in total here: + +```php +final class ConferenceFactory +{ + const SPEAKER_TAG = 'speaker'; + + private $talkFactory; + + public function createConference(array $data) + { + $talks = $this->talkFactory->create($data); + + return new Conference($talks); + } +} +``` + +*Note: Class constant types require PHP 8.3 to run.* + +The param type is defined. But the property, return and constant types are missing. + +* 1 out of 4 = 25 % coverage + +Our code quality is only at one-quarter of its potential. Let's get to 100 %! + +```diff + final class ConferenceFactory + { +- public const SPEAKER_TAG = 'speaker'; ++ public const string SPEAKER_TAG = 'speaker'; + +- private $talkFactory; ++ private TalkFactory $talkFactory; + +- public function createConference(array $data) ++ public function createConference(array $data): Conference + { + $talks = $this->talkFactory->create($data); + + return new Conference($talks); + } + } +``` + +This technique is very simple to start even on legacy project. Also, you're now aware exactly how high coverage your project has. + +
    + +## Install + +```bash +composer require tomasvotruba/type-coverage --dev +``` + +The package is available on PHP 7.2+ version in tagged releases. + +
    + +## Usage + +With [PHPStan extension installer](https://github.com/phpstan/extension-installer), everything is ready to run. + +Enable each item on their own: + +```yaml +# phpstan.neon +parameters: + type_coverage: + return: 50 + param: 35.5 + property: 70 + constant: 85 +``` + +
    + +## Measure Strict Declares coverage + +Once you've reached 100 % type coverage, make sure [your code is strict and uses types](https://tomasvotruba.com/blog/how-adding-type-declarations-makes-your-code-dangerous): + +```php + + +## Full Paths only + +If you run PHPStan only on some subpaths that are different from your setup in `phpstan.neon`, e.g.: + +```bash +vendor/bin/phpstan analyze src/Controller +``` + +This package could show false positives, as classes in the `src/Controller` could be slightly less typed. This would be spamming whole PHPStan output and make hard to see any other errors you look for. + +That's why this package only triggers if there are full paths, e.g.: + +```bash +vendor/bin/phpstan +```` + +
    + +Happy coding! diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/composer.json b/tools/.phpstan/vendor/tomasvotruba/type-coverage/composer.json new file mode 100644 index 00000000000..e32de7ffba1 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/composer.json @@ -0,0 +1,24 @@ +{ + "name": "tomasvotruba/type-coverage", + "type": "phpstan-extension", + "description": "Measure type coverage of your project", + "license": "MIT", + "keywords": ["static analysis", "phpstan-extension"], + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0", + "nette/utils": "^3.2 || ^4.0" + }, + "autoload": { + "psr-4": { + "TomasVotruba\\TypeCoverage\\": "src" + } + }, + "extra": { + "phpstan": { + "includes": [ + "config/extension.neon" + ] + } + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/config/extension.neon b/tools/.phpstan/vendor/tomasvotruba/type-coverage/config/extension.neon new file mode 100644 index 00000000000..b2a58b699c2 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/config/extension.neon @@ -0,0 +1,79 @@ +parametersSchema: + # see https://doc.nette.org/en/schema for configuration + type_coverage: structure([ + declare: anyOf(float(), int()) + # type declarations + return_type: anyOf(float(), int()) + param_type: anyOf(float(), int()) + property_type: anyOf(float(), int()) + constant_type: anyOf(float(), int()) + print_suggestions: bool() + # aliases to avoid typos + return: anyOf(schema(float(), nullable()), schema(int(), nullable())) + param: anyOf(schema(float(), nullable()), schema(int(), nullable())) + property: anyOf(schema(float(), nullable()), schema(int(), nullable())) + constant: anyOf(schema(float(), nullable()), schema(int(), nullable())) + + # measure + measure: bool() + ]) + +# default parameters +parameters: + type_coverage: + declare: 0 + # type declarations + return_type: 99 + param_type: 99 + property_type: 99 + constant_type: 99 + # default, yet deprecated + print_suggestions: true + # aliases + return: null + param: null + property: null + constant: null + + measure: false + +services: + - TomasVotruba\TypeCoverage\Formatter\TypeCoverageFormatter + - TomasVotruba\TypeCoverage\CollectorDataNormalizer + + - + factory: TomasVotruba\TypeCoverage\Configuration + arguments: + - %type_coverage% + + # collectors + - + class: TomasVotruba\TypeCoverage\Collectors\ReturnTypeDeclarationCollector + tags: + - phpstan.collector + + - + class: TomasVotruba\TypeCoverage\Collectors\ParamTypeDeclarationCollector + tags: + - phpstan.collector + + - + class: TomasVotruba\TypeCoverage\Collectors\PropertyTypeDeclarationCollector + tags: + - phpstan.collector + - + class: TomasVotruba\TypeCoverage\Collectors\ConstantTypeDeclarationCollector + tags: + - phpstan.collector + + - + class: TomasVotruba\TypeCoverage\Collectors\DeclareCollector + tags: + - phpstan.collector + +rules: + - TomasVotruba\TypeCoverage\Rules\ParamTypeCoverageRule + - TomasVotruba\TypeCoverage\Rules\ReturnTypeCoverageRule + - TomasVotruba\TypeCoverage\Rules\PropertyTypeCoverageRule + - TomasVotruba\TypeCoverage\Rules\ConstantTypeCoverageRule + - TomasVotruba\TypeCoverage\Rules\DeclareCoverageRule diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/docs/required_type_level.jpg b/tools/.phpstan/vendor/tomasvotruba/type-coverage/docs/required_type_level.jpg new file mode 100644 index 00000000000..cd219abe8b3 Binary files /dev/null and b/tools/.phpstan/vendor/tomasvotruba/type-coverage/docs/required_type_level.jpg differ diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/rector.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/rector.php new file mode 100644 index 00000000000..224fbf73e06 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/rector.php @@ -0,0 +1,18 @@ +withPaths([ + __DIR__ . '/src', + __DIR__ . '/tests', + ]) + ->withPhpSets() + ->withPreparedSets(deadCode: true, codeQuality: true, codingStyle: true, typeDeclarations: true, privatization: true, naming: true) + ->withImportNames(removeUnusedImports: true) + ->withSkip([ + '*/Fixture/*', + '*/Source/*', + ]); diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/CollectorDataNormalizer.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/CollectorDataNormalizer.php new file mode 100644 index 00000000000..e05c09d15fc --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/CollectorDataNormalizer.php @@ -0,0 +1,36 @@ +}>> $collectorDataByPath + */ + public function normalize(array $collectorDataByPath): TypeCountAndMissingTypes + { + $totalCount = 0; + $missingCount = 0; + + $missingTypeLinesByFilePath = []; + + foreach ($collectorDataByPath as $filePath => $typeCoverageData) { + foreach ($typeCoverageData as $nestedData) { + $totalCount += $nestedData[0]; + + $missingCount += count($nestedData[1]); + + $missingTypeLinesByFilePath[$filePath] = array_merge( + $missingTypeLinesByFilePath[$filePath] ?? [], + $nestedData[1] + ); + } + } + + return new TypeCountAndMissingTypes($totalCount, $missingCount, $missingTypeLinesByFilePath); + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/ConstantTypeDeclarationCollector.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/ConstantTypeDeclarationCollector.php new file mode 100644 index 00000000000..4296b613518 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/ConstantTypeDeclarationCollector.php @@ -0,0 +1,77 @@ + + */ + public function getNodeType(): string + { + return ClassConstantsNode::class; + } + + /** + * @param ClassConstantsNode $node + * @return mixed[] + */ + public function processNode(Node $node, Scope $scope): array + { + // enable only on PHP 8.3+ + if (PHP_VERSION_ID < 80300) { + return [0, []]; + } + + $constantCount = count($node->getConstants()); + + $missingTypeLines = []; + + foreach ($node->getConstants() as $classConst) { + // blocked by parent type + if ($this->isGuardedByParentClassConstant($scope, $classConst)) { + continue; + } + + // already typed + if ($classConst->type instanceof Node) { + continue; + } + + // give useful context + $missingTypeLines[] = $classConst->getLine(); + } + + return [$constantCount, $missingTypeLines]; + } + + private function isGuardedByParentClassConstant(Scope $scope, ClassConst $classConst): bool + { + $constName = $classConst->consts[0]->name->toString(); + + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + return false; + } + + foreach ($classReflection->getParents() as $parentClassReflection) { + if ($parentClassReflection->hasConstant($constName)) { + return true; + } + } + + return false; + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/DeclareCollector.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/DeclareCollector.php new file mode 100644 index 00000000000..ad677eeede1 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/DeclareCollector.php @@ -0,0 +1,51 @@ +getNodes() as $node) { + if (! $node instanceof Declare_) { + continue; + } + + foreach ($node->declares as $declare) { + if ( + $declare->key->name !== 'strict_types' + ) { + continue; + } + + if ( + ! $declare->value instanceof LNumber + || $declare->value->value !== 1 + ) { + return false; + } + + return true; + } + } + + return false; + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/ParamTypeDeclarationCollector.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/ParamTypeDeclarationCollector.php new file mode 100644 index 00000000000..a9d4a494380 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/ParamTypeDeclarationCollector.php @@ -0,0 +1,72 @@ +shouldSkipFunctionLike($node)) { + return null; + } + + $missingTypeLines = []; + $paramCount = count($node->getParams()); + + foreach ($node->getParams() as $param) { + if ($param->variadic) { + // skip variadic + --$paramCount; + continue; + } + + if ($param->type === null) { + $missingTypeLines[] = $param->getLine(); + } + } + + return [$paramCount, $missingTypeLines]; + } + + private function shouldSkipFunctionLike(FunctionLike $functionLike): bool + { + // nothing to analyse + if ($functionLike->getParams() === []) { + return true; + } + + return $this->hasFunctionLikeCallableParam($functionLike); + } + + private function hasFunctionLikeCallableParam(FunctionLike $functionLike): bool + { + // skip callable, can be anythings + $docComment = $functionLike->getDocComment(); + if (! $docComment instanceof Doc) { + return false; + } + + $docCommentText = $docComment->getText(); + return strpos($docCommentText, '@param callable') !== false; + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/PropertyTypeDeclarationCollector.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/PropertyTypeDeclarationCollector.php new file mode 100644 index 00000000000..893a60ba4a6 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/PropertyTypeDeclarationCollector.php @@ -0,0 +1,93 @@ + + */ + public function getNodeType(): string + { + return InClassNode::class; + } + + /** + * @param InClassNode $node + * @return mixed[] + */ + public function processNode(Node $node, Scope $scope): array + { + // return typed properties/all properties + $classLike = $node->getOriginalNode(); + + $propertyCount = count($classLike->getProperties()); + + $missingTypeLines = []; + + foreach ($classLike->getProperties() as $property) { + // blocked by parent type + if ($this->isGuardedByParentClassProperty($scope, $property)) { + continue; + } + + // already typed + if ($property->type instanceof Node) { + continue; + } + + if ($this->isPropertyDocTyped($property)) { + continue; + } + + // give useful context + $missingTypeLines[] = $property->getLine(); + } + + return [$propertyCount, $missingTypeLines]; + } + + private function isPropertyDocTyped(Property $property): bool + { + $docComment = $property->getDocComment(); + if (! $docComment instanceof Doc) { + return false; + } + + $docCommentText = $docComment->getText(); + + // skip as unable to type + return strpos($docCommentText, 'callable') !== false || strpos($docCommentText, 'resource') !== false; + } + + private function isGuardedByParentClassProperty(Scope $scope, Property $property): bool + { + $propertyName = $property->props[0]->name->toString(); + + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + return false; + } + + foreach ($classReflection->getParents() as $parentClassReflection) { + if ($parentClassReflection->hasProperty($propertyName)) { + return true; + } + } + + return false; + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/ReturnTypeDeclarationCollector.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/ReturnTypeDeclarationCollector.php new file mode 100644 index 00000000000..f6f63b8419e --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/ReturnTypeDeclarationCollector.php @@ -0,0 +1,48 @@ +isMagic()) { + return null; + } + + if ($scope->isInTrait()) { + $originalMethodName = $node->getAttribute('originalTraitMethodName'); + if ($originalMethodName === '__construct') { + return null; + } + } + + $missingTypeLines = []; + + if (! $node->returnType instanceof Node) { + $missingTypeLines[] = $node->getLine(); + } + + return [1, $missingTypeLines]; + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Configuration.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Configuration.php new file mode 100644 index 00000000000..f2fd6f2b982 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Configuration.php @@ -0,0 +1,76 @@ + + * @readonly + */ + private array $parameters; + + /** + * @param array $parameters + */ + public function __construct(array $parameters) + { + $this->parameters = $parameters; + } + + /** + * @return float|int + */ + public function getRequiredPropertyTypeLevel() + { + return $this->parameters['property'] ?? $this->parameters['property_type']; + } + + public function isConstantTypeCoverageEnabled(): bool + { + if (PHP_VERSION_ID < 80300) { + return false; + } + + return $this->getRequiredConstantTypeLevel() > 0; + } + + /** + * @return float|int + */ + public function getRequiredConstantTypeLevel() + { + return $this->parameters['constant'] ?? $this->parameters['constant_type']; + } + + /** + * @return float|int + */ + public function getRequiredParamTypeLevel() + { + return $this->parameters['param'] ?? $this->parameters['param_type']; + } + + /** + * @return float|int + */ + public function getRequiredReturnTypeLevel() + { + return $this->parameters['return'] ?? $this->parameters['return_type']; + } + + /** + * @return float|int + */ + public function getRequiredDeclareLevel() + { + return $this->parameters['declare']; + } + + public function showOnlyMeasure(): bool + { + return $this->parameters['measure']; + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Configuration/ScopeConfigurationResolver.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Configuration/ScopeConfigurationResolver.php new file mode 100644 index 00000000000..7b4cec24f92 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Configuration/ScopeConfigurationResolver.php @@ -0,0 +1,58 @@ +getParameter('analysedPaths'); + $analysedPathsFromConfig = $originalContainer->getParameter('analysedPathsFromConfig'); + + self::$areFullPathsAnalysed = $analysedPathsFromConfig === $analysedPaths; + + return self::$areFullPathsAnalysed; + } + + private static function getPrivateProperty(object $object, string $propertyName): object + { + $reflectionProperty = new ReflectionProperty($object, $propertyName); + $reflectionProperty->setAccessible(true); + + return $reflectionProperty->getValue($object); + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Formatter/TypeCoverageFormatter.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Formatter/TypeCoverageFormatter.php new file mode 100644 index 00000000000..04d7c3946aa --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Formatter/TypeCoverageFormatter.php @@ -0,0 +1,56 @@ +getTotalCount() === 0) { + return []; + } + + $typeCoveragePercentage = $typeCountAndMissingTypes->getCoveragePercentage(); + + // has the code met the minimal sea level of types? + if ($typeCoveragePercentage >= $minimalLevel) { + return []; + } + + $ruleErrors = []; + + foreach ($typeCountAndMissingTypes->getMissingTypeLinesByFilePath() as $filePath => $lines) { + $errorMessage = sprintf( + $message, + $typeCountAndMissingTypes->getTotalCount(), + $typeCountAndMissingTypes->getFilledCount(), + $typeCoveragePercentage, + $minimalLevel + ); + + foreach ($lines as $line) { + $ruleErrors[] = RuleErrorBuilder::message($errorMessage) + ->identifier($identifier) + ->file($filePath) + ->line($line) + ->build(); + } + } + + return $ruleErrors; + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/ConstantTypeCoverageRule.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/ConstantTypeCoverageRule.php new file mode 100644 index 00000000000..b53da098c88 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/ConstantTypeCoverageRule.php @@ -0,0 +1,101 @@ + + */ +final class ConstantTypeCoverageRule implements Rule +{ + /** + * @var string + */ + public const ERROR_MESSAGE = 'Out of %d possible constant types, only %d - %.1f %% actually have it. Add more constant types to get over %s %%'; + + /** + * @var string + */ + private const IDENTIFIER = 'typeCoverage.constantTypeCoverage'; + + /** + * @readonly + */ + private TypeCoverageFormatter $typeCoverageFormatter; + + /** + * @readonly + */ + private Configuration $configuration; + + /** + * @readonly + */ + private CollectorDataNormalizer $collectorDataNormalizer; + + public function __construct(TypeCoverageFormatter $typeCoverageFormatter, Configuration $configuration, CollectorDataNormalizer $collectorDataNormalizer) + { + $this->typeCoverageFormatter = $typeCoverageFormatter; + $this->configuration = $configuration; + $this->collectorDataNormalizer = $collectorDataNormalizer; + } + + /** + * @return class-string + */ + public function getNodeType(): string + { + return CollectedDataNode::class; + } + + /** + * @param CollectedDataNode $node + * @return RuleError[] + */ + public function processNode(Node $node, Scope $scope): array + { + // if only subpaths are analysed, skip as data will be false positive + if (! ScopeConfigurationResolver::areFullPathsAnalysed($scope)) { + return []; + } + + $constantTypeDeclarationCollector = $node->get(ConstantTypeDeclarationCollector::class); + $typeCountAndMissingTypes = $this->collectorDataNormalizer->normalize($constantTypeDeclarationCollector); + + if ($this->configuration->showOnlyMeasure()) { + $errorMessage = sprintf( + 'Class constant type coverage is %.1f %% out of %d possible', + $typeCountAndMissingTypes->getCoveragePercentage(), + $typeCountAndMissingTypes->getTotalCount() + ); + + return [RuleErrorBuilder::message($errorMessage)->build()]; + } + + if (! $this->configuration->isConstantTypeCoverageEnabled()) { + return []; + } + + return $this->typeCoverageFormatter->formatErrors( + self::ERROR_MESSAGE, + self::IDENTIFIER, + $this->configuration->getRequiredConstantTypeLevel(), + $typeCountAndMissingTypes + ); + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/DeclareCoverageRule.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/DeclareCoverageRule.php new file mode 100644 index 00000000000..314c0b59785 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/DeclareCoverageRule.php @@ -0,0 +1,116 @@ + + */ +final class DeclareCoverageRule implements Rule +{ + /** + * @var string + */ + public const ERROR_MESSAGE = 'Out of %d possible declare(strict_types=1), only %d - %.1f %% actually have it. Add more declares to get over %s %%'; + + /** + * @readonly + */ + private Configuration $configuration; + + public function __construct(Configuration $configuration) + { + $this->configuration = $configuration; + } + + /** + * @return class-string + */ + public function getNodeType(): string + { + return CollectedDataNode::class; + } + + /** + * @param CollectedDataNode $node + * @return RuleError[] + */ + public function processNode(Node $node, Scope $scope): array + { + // if only subpaths are analysed, skip as data will be false positive + if (! ScopeConfigurationResolver::areFullPathsAnalysed($scope)) { + return []; + } + + $requiredDeclareLevel = $this->configuration->getRequiredDeclareLevel(); + + $declareCollector = $node->get(DeclareCollector::class); + $totalPossibleDeclares = count($declareCollector); + + $coveredDeclares = 0; + $notCoveredDeclareFilePaths = []; + + foreach ($declareCollector as $fileName => $data) { + // has declares + if ($data === [true]) { + ++$coveredDeclares; + } else { + $notCoveredDeclareFilePaths[] = $fileName; + } + } + + $declareCoverage = ($coveredDeclares / $totalPossibleDeclares) * 100; + + if ($this->configuration->showOnlyMeasure()) { + $errorMessage = sprintf( + 'Strict declares coverage is %.1f %% out of %d possible', + $declareCoverage, + $totalPossibleDeclares + ); + return [RuleErrorBuilder::message($errorMessage)->build()]; + } + + // not enabled + if ($requiredDeclareLevel === 0) { + return []; + } + + // nothing to handle + if ($totalPossibleDeclares === 0) { + return []; + } + + // we meet the limit, all good + if ($declareCoverage >= $requiredDeclareLevel) { + return []; + } + + $ruleErrors = []; + foreach ($notCoveredDeclareFilePaths as $notCoveredDeclareFilePath) { + $errorMessage = sprintf( + self::ERROR_MESSAGE, + $totalPossibleDeclares, + $coveredDeclares, + $declareCoverage, + $requiredDeclareLevel, + ); + + $ruleErrors[] = RuleErrorBuilder::message($errorMessage)->file($notCoveredDeclareFilePath)->build(); + } + + return $ruleErrors; + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/ParamTypeCoverageRule.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/ParamTypeCoverageRule.php new file mode 100644 index 00000000000..ed300eea6ae --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/ParamTypeCoverageRule.php @@ -0,0 +1,105 @@ + + */ +final class ParamTypeCoverageRule implements Rule +{ + /** + * @var string + */ + public const ERROR_MESSAGE = 'Out of %d possible param types, only %d - %.1f %% actually have it. Add more param types to get over %s %%'; + + /** + * @var string + */ + private const IDENTIFIER = 'typeCoverage.paramTypeCoverage'; + + /** + * @readonly + */ + private TypeCoverageFormatter $typeCoverageFormatter; + + /** + * @readonly + */ + private Configuration $configuration; + + /** + * @readonly + */ + private CollectorDataNormalizer $collectorDataNormalizer; + + public function __construct(TypeCoverageFormatter $typeCoverageFormatter, Configuration $configuration, CollectorDataNormalizer $collectorDataNormalizer) + { + $this->typeCoverageFormatter = $typeCoverageFormatter; + $this->configuration = $configuration; + $this->collectorDataNormalizer = $collectorDataNormalizer; + } + + /** + * @return class-string + */ + public function getNodeType(): string + { + return CollectedDataNode::class; + } + + /** + * @param CollectedDataNode $node + * @return RuleError[] + */ + public function processNode(Node $node, Scope $scope): array + { + // if only subpaths are analysed, skip as data will be false positive + if (! ScopeConfigurationResolver::areFullPathsAnalysed($scope)) { + return []; + } + + $paramTypeDeclarationCollector = $node->get(ParamTypeDeclarationCollector::class); + + $typeCountAndMissingTypes = $this->collectorDataNormalizer->normalize($paramTypeDeclarationCollector); + + if ($this->configuration->showOnlyMeasure()) { + $errorMessage = sprintf( + 'Param type coverage is %.1f %% out of %d possible', + $typeCountAndMissingTypes->getCoveragePercentage(), + $typeCountAndMissingTypes->getTotalCount() + ); + + $ruleError = RuleErrorBuilder::message($errorMessage) + ->build(); + + return [$ruleError]; + } + + if ($this->configuration->getRequiredParamTypeLevel() === 0) { + return []; + } + + return $this->typeCoverageFormatter->formatErrors( + self::ERROR_MESSAGE, + self::IDENTIFIER, + $this->configuration->getRequiredParamTypeLevel(), + $typeCountAndMissingTypes + ); + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/PropertyTypeCoverageRule.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/PropertyTypeCoverageRule.php new file mode 100644 index 00000000000..27dcec2cff4 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/PropertyTypeCoverageRule.php @@ -0,0 +1,101 @@ + + */ +final class PropertyTypeCoverageRule implements Rule +{ + /** + * @var string + */ + public const ERROR_MESSAGE = 'Out of %d possible property types, only %d - %.1f %% actually have it. Add more property types to get over %s %%'; + + /** + * @var string + */ + private const IDENTIFIER = 'typeCoverage.propertyTypeCoverage'; + + /** + * @readonly + */ + private TypeCoverageFormatter $typeCoverageFormatter; + + /** + * @readonly + */ + private Configuration $configuration; + + /** + * @readonly + */ + private CollectorDataNormalizer $collectorDataNormalizer; + + public function __construct(TypeCoverageFormatter $typeCoverageFormatter, Configuration $configuration, CollectorDataNormalizer $collectorDataNormalizer) + { + $this->typeCoverageFormatter = $typeCoverageFormatter; + $this->configuration = $configuration; + $this->collectorDataNormalizer = $collectorDataNormalizer; + } + + /** + * @return class-string + */ + public function getNodeType(): string + { + return CollectedDataNode::class; + } + + /** + * @param CollectedDataNode $node + * @return RuleError[] + */ + public function processNode(Node $node, Scope $scope): array + { + // if only subpaths are analysed, skip as data will be false positive + if (! ScopeConfigurationResolver::areFullPathsAnalysed($scope)) { + return []; + } + + $propertyTypeDeclarationCollector = $node->get(PropertyTypeDeclarationCollector::class); + $typeCountAndMissingTypes = $this->collectorDataNormalizer->normalize($propertyTypeDeclarationCollector); + + if ($this->configuration->showOnlyMeasure()) { + $errorMessage = sprintf( + 'Property type coverage is %.1f %% out of %d possible', + $typeCountAndMissingTypes->getCoveragePercentage(), + $typeCountAndMissingTypes->getTotalCount() + ); + + return [RuleErrorBuilder::message($errorMessage)->build()]; + } + + if ($this->configuration->getRequiredPropertyTypeLevel() === 0) { + return []; + } + + return $this->typeCoverageFormatter->formatErrors( + self::ERROR_MESSAGE, + self::IDENTIFIER, + $this->configuration->getRequiredPropertyTypeLevel(), + $typeCountAndMissingTypes + ); + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/ReturnTypeCoverageRule.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/ReturnTypeCoverageRule.php new file mode 100644 index 00000000000..c7e7fbaa674 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/ReturnTypeCoverageRule.php @@ -0,0 +1,100 @@ + + */ +final class ReturnTypeCoverageRule implements Rule +{ + /** + * @var string + */ + public const ERROR_MESSAGE = 'Out of %d possible return types, only %d - %.1f %% actually have it. Add more return types to get over %s %%'; + + /** + * @var string + */ + private const IDENTIFIER = 'typeCoverage.returnTypeCoverage'; + + /** + * @readonly + */ + private TypeCoverageFormatter $typeCoverageFormatter; + + /** + * @readonly + */ + private Configuration $configuration; + + /** + * @readonly + */ + private CollectorDataNormalizer $collectorDataNormalizer; + + public function __construct(TypeCoverageFormatter $typeCoverageFormatter, Configuration $configuration, CollectorDataNormalizer $collectorDataNormalizer) + { + $this->typeCoverageFormatter = $typeCoverageFormatter; + $this->configuration = $configuration; + $this->collectorDataNormalizer = $collectorDataNormalizer; + } + + /** + * @return class-string + */ + public function getNodeType(): string + { + return CollectedDataNode::class; + } + + /** + * @param CollectedDataNode $node + * @return RuleError[] + */ + public function processNode(Node $node, Scope $scope): array + { + // if only subpaths are analysed, skip as data will be false positive + if (! ScopeConfigurationResolver::areFullPathsAnalysed($scope)) { + return []; + } + + $returnSeaLevelDataByFilePath = $node->get(ReturnTypeDeclarationCollector::class); + $typeCountAndMissingTypes = $this->collectorDataNormalizer->normalize($returnSeaLevelDataByFilePath); + + if ($this->configuration->showOnlyMeasure()) { + $errorMessage = sprintf( + 'Return type coverage is %.1f %% out of %d possible', + $typeCountAndMissingTypes->getCoveragePercentage(), + $typeCountAndMissingTypes->getTotalCount() + ); + return [RuleErrorBuilder::message($errorMessage)->build()]; + } + + if ($this->configuration->getRequiredReturnTypeLevel() === 0) { + return []; + } + + return $this->typeCoverageFormatter->formatErrors( + self::ERROR_MESSAGE, + self::IDENTIFIER, + $this->configuration->getRequiredReturnTypeLevel(), + $typeCountAndMissingTypes + ); + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/ValueObject/TypeCountAndMissingTypes.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/ValueObject/TypeCountAndMissingTypes.php new file mode 100644 index 00000000000..c539839de58 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/ValueObject/TypeCountAndMissingTypes.php @@ -0,0 +1,75 @@ + + * @readonly + */ + private array $missingTypeLinesByFilePath; + + /** + * @param array $missingTypeLinesByFilePath + */ + public function __construct(int $totalCount, int $missingCount, array $missingTypeLinesByFilePath) + { + $this->totalCount = $totalCount; + $this->missingCount = $missingCount; + $this->missingTypeLinesByFilePath = $missingTypeLinesByFilePath; + } + + public function getTotalCount(): int + { + return $this->totalCount; + } + + public function getFilledCount(): int + { + return $this->totalCount - $this->missingCount; + } + + /** + * @return array + */ + public function getMissingTypeLinesByFilePath(): array + { + return $this->missingTypeLinesByFilePath; + } + + public function getCoveragePercentage(): float + { + if ($this->totalCount === 0) { + return 100.0; + } + + $relative = 100 * ($this->getTypedCount() / $this->totalCount); + + // round down with one decimal, to make error message clear that required value is not reached yet + return floor($relative * 10) / 10; + } + + private function getTypedCount(): int + { + $missingCount = 0; + + foreach ($this->missingTypeLinesByFilePath as $missingTypeLines) { + $missingCount += count($missingTypeLines); + } + + return $this->totalCount - $missingCount; + } +} diff --git a/tools/composer b/tools/composer index d537432e4e3..82d38d3df74 100755 Binary files a/tools/composer and b/tools/composer differ diff --git a/tools/phive b/tools/phive new file mode 100755 index 00000000000..da56a9d34e0 --- /dev/null +++ b/tools/phive @@ -0,0 +1,1104 @@ +#!/usr/bin/env php + '/vendor/phar-io/executor/src/ExecutorException.php', + 'phario\\executor\\executor' => '/vendor/phar-io/executor/src/Executor.php', + 'phario\\executor\\executorresult' => '/vendor/phar-io/executor/src/ExecutorResult.php', + 'phario\\filesystem\\directory' => '/vendor/phar-io/filesystem/src/Directory.php', + 'phario\\filesystem\\directoryexception' => '/vendor/phar-io/filesystem/src/DirectoryException.php', + 'phario\\filesystem\\exception' => '/vendor/phar-io/filesystem/src/Exception.php', + 'phario\\filesystem\\file' => '/vendor/phar-io/filesystem/src/File.php', + 'phario\\filesystem\\filename' => '/vendor/phar-io/filesystem/src/Filename.php', + 'phario\\filesystem\\filenameexception' => '/vendor/phar-io/filesystem/src/FilenameException.php', + 'phario\\filesystem\\lastmodifieddate' => '/vendor/phar-io/filesystem/src/LastModifiedDate.php', + 'phario\\gnupg\\errorstrings' => '/vendor/phar-io/gnupg/src/ErrorStrings.php', + 'phario\\gnupg\\exception' => '/vendor/phar-io/gnupg/src/Exception.php', + 'phario\\gnupg\\factory' => '/vendor/phar-io/gnupg/src/Factory.php', + 'phario\\gnupg\\gnupg' => '/vendor/phar-io/gnupg/src/GnuPG.php', + 'phario\\manifest\\application' => '/vendor/phar-io/manifest/src/values/Application.php', + 'phario\\manifest\\applicationname' => '/vendor/phar-io/manifest/src/values/ApplicationName.php', + 'phario\\manifest\\author' => '/vendor/phar-io/manifest/src/values/Author.php', + 'phario\\manifest\\authorcollection' => '/vendor/phar-io/manifest/src/values/AuthorCollection.php', + 'phario\\manifest\\authorcollectioniterator' => '/vendor/phar-io/manifest/src/values/AuthorCollectionIterator.php', + 'phario\\manifest\\authorelement' => '/vendor/phar-io/manifest/src/xml/AuthorElement.php', + 'phario\\manifest\\authorelementcollection' => '/vendor/phar-io/manifest/src/xml/AuthorElementCollection.php', + 'phario\\manifest\\bundledcomponent' => '/vendor/phar-io/manifest/src/values/BundledComponent.php', + 'phario\\manifest\\bundledcomponentcollection' => '/vendor/phar-io/manifest/src/values/BundledComponentCollection.php', + 'phario\\manifest\\bundledcomponentcollectioniterator' => '/vendor/phar-io/manifest/src/values/BundledComponentCollectionIterator.php', + 'phario\\manifest\\bundleselement' => '/vendor/phar-io/manifest/src/xml/BundlesElement.php', + 'phario\\manifest\\componentelement' => '/vendor/phar-io/manifest/src/xml/ComponentElement.php', + 'phario\\manifest\\componentelementcollection' => '/vendor/phar-io/manifest/src/xml/ComponentElementCollection.php', + 'phario\\manifest\\containselement' => '/vendor/phar-io/manifest/src/xml/ContainsElement.php', + 'phario\\manifest\\copyrightelement' => '/vendor/phar-io/manifest/src/xml/CopyrightElement.php', + 'phario\\manifest\\copyrightinformation' => '/vendor/phar-io/manifest/src/values/CopyrightInformation.php', + 'phario\\manifest\\elementcollection' => '/vendor/phar-io/manifest/src/xml/ElementCollection.php', + 'phario\\manifest\\elementcollectionexception' => '/vendor/phar-io/manifest/src/exceptions/ElementCollectionException.php', + 'phario\\manifest\\email' => '/vendor/phar-io/manifest/src/values/Email.php', + 'phario\\manifest\\exception' => '/vendor/phar-io/manifest/src/exceptions/Exception.php', + 'phario\\manifest\\extelement' => '/vendor/phar-io/manifest/src/xml/ExtElement.php', + 'phario\\manifest\\extelementcollection' => '/vendor/phar-io/manifest/src/xml/ExtElementCollection.php', + 'phario\\manifest\\extension' => '/vendor/phar-io/manifest/src/values/Extension.php', + 'phario\\manifest\\extensionelement' => '/vendor/phar-io/manifest/src/xml/ExtensionElement.php', + 'phario\\manifest\\invalidapplicationnameexception' => '/vendor/phar-io/manifest/src/exceptions/InvalidApplicationNameException.php', + 'phario\\manifest\\invalidemailexception' => '/vendor/phar-io/manifest/src/exceptions/InvalidEmailException.php', + 'phario\\manifest\\invalidurlexception' => '/vendor/phar-io/manifest/src/exceptions/InvalidUrlException.php', + 'phario\\manifest\\library' => '/vendor/phar-io/manifest/src/values/Library.php', + 'phario\\manifest\\license' => '/vendor/phar-io/manifest/src/values/License.php', + 'phario\\manifest\\licenseelement' => '/vendor/phar-io/manifest/src/xml/LicenseElement.php', + 'phario\\manifest\\manifest' => '/vendor/phar-io/manifest/src/values/Manifest.php', + 'phario\\manifest\\manifestdocument' => '/vendor/phar-io/manifest/src/xml/ManifestDocument.php', + 'phario\\manifest\\manifestdocumentexception' => '/vendor/phar-io/manifest/src/exceptions/ManifestDocumentException.php', + 'phario\\manifest\\manifestdocumentloadingexception' => '/vendor/phar-io/manifest/src/exceptions/ManifestDocumentLoadingException.php', + 'phario\\manifest\\manifestdocumentmapper' => '/vendor/phar-io/manifest/src/ManifestDocumentMapper.php', + 'phario\\manifest\\manifestdocumentmapperexception' => '/vendor/phar-io/manifest/src/exceptions/ManifestDocumentMapperException.php', + 'phario\\manifest\\manifestelement' => '/vendor/phar-io/manifest/src/xml/ManifestElement.php', + 'phario\\manifest\\manifestelementexception' => '/vendor/phar-io/manifest/src/exceptions/ManifestElementException.php', + 'phario\\manifest\\manifestloader' => '/vendor/phar-io/manifest/src/ManifestLoader.php', + 'phario\\manifest\\manifestloaderexception' => '/vendor/phar-io/manifest/src/exceptions/ManifestLoaderException.php', + 'phario\\manifest\\manifestserializer' => '/vendor/phar-io/manifest/src/ManifestSerializer.php', + 'phario\\manifest\\noemailaddressexception' => '/vendor/phar-io/manifest/src/exceptions/NoEmailAddressException.php', + 'phario\\manifest\\phpelement' => '/vendor/phar-io/manifest/src/xml/PhpElement.php', + 'phario\\manifest\\phpextensionrequirement' => '/vendor/phar-io/manifest/src/values/PhpExtensionRequirement.php', + 'phario\\manifest\\phpversionrequirement' => '/vendor/phar-io/manifest/src/values/PhpVersionRequirement.php', + 'phario\\manifest\\requirement' => '/vendor/phar-io/manifest/src/values/Requirement.php', + 'phario\\manifest\\requirementcollection' => '/vendor/phar-io/manifest/src/values/RequirementCollection.php', + 'phario\\manifest\\requirementcollectioniterator' => '/vendor/phar-io/manifest/src/values/RequirementCollectionIterator.php', + 'phario\\manifest\\requireselement' => '/vendor/phar-io/manifest/src/xml/RequiresElement.php', + 'phario\\manifest\\type' => '/vendor/phar-io/manifest/src/values/Type.php', + 'phario\\manifest\\url' => '/vendor/phar-io/manifest/src/values/Url.php', + 'phario\\phive\\abstractrequestedpharresolver' => '/src/services/resolver/AbstractRequestedPharResolver.php', + 'phario\\phive\\abstractresolvingstrategy' => '/src/services/resolver/strategy/AbstractResolvingStrategy.php', + 'phario\\phive\\authconfig' => '/src/shared/config/AuthConfig.php', + 'phario\\phive\\authentication' => '/src/shared/http/Authentication.php', + 'phario\\phive\\authexception' => '/src/shared/exceptions/AuthException.php', + 'phario\\phive\\authxmlconfig' => '/src/shared/config/AuthXmlConfig.php', + 'phario\\phive\\authxmlconfigfilelocator' => '/src/shared/config/AuthXmlConfigFileLocator.php', + 'phario\\phive\\basehash' => '/src/shared/hash/BaseHash.php', + 'phario\\phive\\basicauthentication' => '/src/shared/http/authentication/BasicAuthentication.php', + 'phario\\phive\\bearerauthentication' => '/src/shared/http/authentication/BearerAuthentication.php', + 'phario\\phive\\cachebackend' => '/src/shared/http/CacheBackend.php', + 'phario\\phive\\checksumservice' => '/src/services/checksum/ChecksumService.php', + 'phario\\phive\\cli\\coloredconsoleoutput' => '/src/shared/cli/output/ColoredConsoleOutput.php', + 'phario\\phive\\cli\\command' => '/src/shared/cli/Command.php', + 'phario\\phive\\cli\\commandlocator' => '/src/shared/cli/CommandLocator.php', + 'phario\\phive\\cli\\commandlocatorexception' => '/src/shared/cli/CommandLocatorException.php', + 'phario\\phive\\cli\\commandoptionsexception' => '/src/shared/cli/CommandOptionsException.php', + 'phario\\phive\\cli\\consoleinput' => '/src/shared/cli/input/ConsoleInput.php', + 'phario\\phive\\cli\\consoleoutput' => '/src/shared/cli/output/ConsoleOutput.php', + 'phario\\phive\\cli\\consoletable' => '/src/shared/cli/output/ConsoleTable.php', + 'phario\\phive\\cli\\context' => '/src/shared/cli/Context.php', + 'phario\\phive\\cli\\contextexception' => '/src/shared/cli/ContextException.php', + 'phario\\phive\\cli\\generalcontext' => '/src/shared/cli/GeneralContext.php', + 'phario\\phive\\cli\\input' => '/src/shared/cli/input/Input.php', + 'phario\\phive\\cli\\options' => '/src/shared/cli/Options.php', + 'phario\\phive\\cli\\output' => '/src/shared/cli/output/Output.php', + 'phario\\phive\\cli\\outputfactory' => '/src/shared/cli/output/OutputFactory.php', + 'phario\\phive\\cli\\outputlocator' => '/src/shared/cli/output/OutputLocator.php', + 'phario\\phive\\cli\\request' => '/src/shared/cli/Request.php', + 'phario\\phive\\cli\\requestexception' => '/src/shared/cli/RequestException.php', + 'phario\\phive\\cli\\runner' => '/src/shared/cli/Runner.php', + 'phario\\phive\\cli\\runnerexception' => '/src/shared/cli/RunnerException.php', + 'phario\\phive\\commandlocator' => '/src/commands/CommandLocator.php', + 'phario\\phive\\compatibilityservice' => '/src/services/phar/CompatibilityService.php', + 'phario\\phive\\composeralias' => '/src/shared/ComposerAlias.php', + 'phario\\phive\\composercommand' => '/src/commands/composer/ComposerCommand.php', + 'phario\\phive\\composercommandconfig' => '/src/commands/composer/ComposerCommandConfig.php', + 'phario\\phive\\composercontext' => '/src/commands/composer/ComposerContext.php', + 'phario\\phive\\composerservice' => '/src/commands/composer/ComposerService.php', + 'phario\\phive\\compositeauthconfig' => '/src/shared/config/CompositeAuthConfig.php', + 'phario\\phive\\config' => '/src/shared/config/Config.php', + 'phario\\phive\\configexception' => '/src/shared/exceptions/ConfigException.php', + 'phario\\phive\\configuredphar' => '/src/shared/phar/ConfiguredPhar.php', + 'phario\\phive\\configuredpharexception' => '/src/shared/phar/ConfiguredPharException.php', + 'phario\\phive\\curl' => '/src/shared/http/Curl.php', + 'phario\\phive\\curlconfig' => '/src/shared/http/CurlConfig.php', + 'phario\\phive\\curlconfigbuilder' => '/src/shared/http/CurlConfigBuilder.php', + 'phario\\phive\\curlconfigexception' => '/src/shared/exceptions/CurlConfigException.php', + 'phario\\phive\\curlexception' => '/src/shared/exceptions/CurlException.php', + 'phario\\phive\\curlhttpclient' => '/src/shared/http/CurlHttpClient.php', + 'phario\\phive\\defaultcommand' => '/src/commands/default/DefaultCommand.php', + 'phario\\phive\\defaultcommandconfig' => '/src/commands/default/DefaultCommandConfig.php', + 'phario\\phive\\directurlresolver' => '/src/services/resolver/DirectUrlResolver.php', + 'phario\\phive\\downloadfailedexception' => '/src/shared/exceptions/DownloadFailedException.php', + 'phario\\phive\\environment' => '/src/shared/environment/Environment.php', + 'phario\\phive\\environmentauthconfig' => '/src/shared/config/EnvironmentAuthConfig.php', + 'phario\\phive\\environmentexception' => '/src/shared/exceptions/EnvironmentException.php', + 'phario\\phive\\environmentlocator' => '/src/shared/environment/EnvironmentLocator.php', + 'phario\\phive\\errorexception' => '/src/shared/exceptions/ErrorException.php', + 'phario\\phive\\etag' => '/src/shared/http/ETag.php', + 'phario\\phive\\exception' => '/src/shared/exceptions/Exception.php', + 'phario\\phive\\executor' => '/src/shared/executor/Executor.php', + 'phario\\phive\\executorexception' => '/src/shared/exceptions/ExecutorException.php', + 'phario\\phive\\executorresult' => '/src/shared/executor/ExecutorResult.php', + 'phario\\phive\\factory' => '/src/Factory.php', + 'phario\\phive\\featuremissingexception' => '/src/shared/exceptions/FeatureMissingException.php', + 'phario\\phive\\filedownloader' => '/src/shared/download/FileDownloader.php', + 'phario\\phive\\filedownloaderexception' => '/src/shared/FileDownloaderException.php', + 'phario\\phive\\filemigration' => '/src/services/migration/FileMigration.php', + 'phario\\phive\\filenotwritableexception' => '/src/shared/exceptions/FileNotWritableException.php', + 'phario\\phive\\filestoragecachebackend' => '/src/shared/http/FileStorageCacheBackend.php', + 'phario\\phive\\git' => '/src/shared/Git.php', + 'phario\\phive\\gitawarephiveversion' => '/src/shared/version/GitAwarePhiveVersion.php', + 'phario\\phive\\gitexception' => '/src/shared/exceptions/GitException.php', + 'phario\\phive\\githubaliasresolver' => '/src/services/resolver/GithubAliasResolver.php', + 'phario\\phive\\githubaliasresolverexception' => '/src/GithubAliasResolverException.php', + 'phario\\phive\\githubrepository' => '/src/shared/repository/GithubRepository.php', + 'phario\\phive\\gitlabaliasresolver' => '/src/services/resolver/GitlabAliasResolver.php', + 'phario\\phive\\gitlabrepository' => '/src/shared/repository/GitlabRepository.php', + 'phario\\phive\\globalphivexmlconfig' => '/src/shared/config/GlobalPhiveXmlConfig.php', + 'phario\\phive\\gnupg' => '/src/shared/GnuPG.php', + 'phario\\phive\\gnupgkeydownloader' => '/src/services/key/gpg/GnupgKeyDownloader.php', + 'phario\\phive\\gnupgkeydownloaderexception' => '/src/shared/exceptions/GnupgKeyDownloaderException.php', + 'phario\\phive\\gnupgkeyimporter' => '/src/services/key/gpg/GnupgKeyImporter.php', + 'phario\\phive\\gnupgsignatureverifier' => '/src/services/signature/gpg/GnupgSignatureVerifier.php', + 'phario\\phive\\gnupgverificationresult' => '/src/services/signature/gpg/GnupgVerificationResult.php', + 'phario\\phive\\hash' => '/src/shared/hash/Hash.php', + 'phario\\phive\\helpcommand' => '/src/commands/help/HelpCommand.php', + 'phario\\phive\\homepharsxmlmigration' => '/src/services/migration/HomePharsXmlMigration.php', + 'phario\\phive\\homephivexmlmigration' => '/src/services/migration/HomePhiveXmlMigration.php', + 'phario\\phive\\httpclient' => '/src/shared/http/HttpClient.php', + 'phario\\phive\\httpexception' => '/src/shared/http/HttpException.php', + 'phario\\phive\\httpprogresshandler' => '/src/shared/http/HttpProgressHandler.php', + 'phario\\phive\\httpprogressrenderer' => '/src/shared/http/HttpProgressRenderer.php', + 'phario\\phive\\httpprogressupdate' => '/src/shared/http/HttpProgressUpdate.php', + 'phario\\phive\\httpresponse' => '/src/shared/http/HttpResponse.php', + 'phario\\phive\\httpresponseexception' => '/src/shared/http/HttpResponseException.php', + 'phario\\phive\\installationfailedexception' => '/src/shared/exceptions/InstallationFailedException.php', + 'phario\\phive\\installcommand' => '/src/commands/install/InstallCommand.php', + 'phario\\phive\\installcommandconfig' => '/src/commands/install/InstallCommandConfig.php', + 'phario\\phive\\installcommandconfigexception' => '/src/commands/install/InstallCommandConfigException.php', + 'phario\\phive\\installcontext' => '/src/commands/install/InstallContext.php', + 'phario\\phive\\installedphar' => '/src/shared/phar/InstalledPhar.php', + 'phario\\phive\\installservice' => '/src/services/phar/InstallService.php', + 'phario\\phive\\internalfilemigration' => '/src/services/migration/InternalFileMigration.php', + 'phario\\phive\\invalidhashexception' => '/src/shared/exceptions/InvalidHashException.php', + 'phario\\phive\\invalidxmlexception' => '/src/shared/exceptions/InvalidXmlException.php', + 'phario\\phive\\ioexception' => '/src/shared/exceptions/IOException.php', + 'phario\\phive\\jsondata' => '/src/shared/JsonData.php', + 'phario\\phive\\keydownloader' => '/src/services/key/KeyDownloader.php', + 'phario\\phive\\keyimporter' => '/src/services/key/KeyImporter.php', + 'phario\\phive\\keyimportresult' => '/src/services/key/KeyImportResult.php', + 'phario\\phive\\keyservice' => '/src/services/key/KeyService.php', + 'phario\\phive\\linkcreationfailedexception' => '/src/shared/exceptions/LinkCreationFailedException.php', + 'phario\\phive\\listcommand' => '/src/commands/list/ListCommand.php', + 'phario\\phive\\localaliasresolver' => '/src/services/resolver/LocalAliasResolver.php', + 'phario\\phive\\localfirstresolvingstrategy' => '/src/services/resolver/strategy/LocalFirstResolvingStrategy.php', + 'phario\\phive\\localphivexmlconfig' => '/src/shared/config/LocalPhiveXmlConfig.php', + 'phario\\phive\\localrepository' => '/src/shared/repository/LocalRepository.php', + 'phario\\phive\\localsourceslistfileloader' => '/src/shared/sources/LocalSourcesListFileLoader.php', + 'phario\\phive\\localsslcertificate' => '/src/shared/http/LocalSslCertificate.php', + 'phario\\phive\\migratecommand' => '/src/commands/migrate/MigrateCommand.php', + 'phario\\phive\\migratecommandconfig' => '/src/commands/migrate/MigrateCommandConfig.php', + 'phario\\phive\\migratecontext' => '/src/commands/migrate/MigrateContext.php', + 'phario\\phive\\migration' => '/src/services/migration/Migration.php', + 'phario\\phive\\migrationexception' => '/src/shared/exceptions/MigrationException.php', + 'phario\\phive\\migrationfactory' => '/src/services/migration/MigrationFactory.php', + 'phario\\phive\\migrationservice' => '/src/services/migration/MigrationService.php', + 'phario\\phive\\migrationsfailedexception' => '/src/shared/exceptions/MigrationsFailedException.php', + 'phario\\phive\\nogpgbinaryfoundexception' => '/src/shared/exceptions/NoGPGBinaryFoundException.php', + 'phario\\phive\\notfoundexception' => '/src/shared/exceptions/NotFoundException.php', + 'phario\\phive\\outdatedcommand' => '/src/commands/outdated/OutdatedCommand.php', + 'phario\\phive\\outdatedconfig' => '/src/commands/outdated/OutdatedConfig.php', + 'phario\\phive\\outdatedconfigexception' => '/src/commands/outdated/OutdatedConfigException.php', + 'phario\\phive\\outdatedcontext' => '/src/commands/outdated/OutdatedContext.php', + 'phario\\phive\\phar' => '/src/shared/phar/Phar.php', + 'phario\\phive\\pharalias' => '/src/shared/phar/PharAlias.php', + 'phario\\phive\\phardownloader' => '/src/services/phar/PharDownloader.php', + 'phario\\phive\\pharexception' => '/src/shared/exceptions/PharException.php', + 'phario\\phive\\pharidentifier' => '/src/shared/phar/PharIdentifier.php', + 'phario\\phive\\pharinstaller' => '/src/services/phar/PharInstaller.php', + 'phario\\phive\\pharinstallerexception' => '/src/shared/exceptions/PharInstallerException.php', + 'phario\\phive\\pharinstallerfactory' => '/src/services/phar/PharInstallerFactory.php', + 'phario\\phive\\pharinstallerlocator' => '/src/services/phar/PharInstallerLocator.php', + 'phario\\phive\\pharioaliasresolver' => '/src/services/resolver/PharIoAliasResolver.php', + 'phario\\phive\\phariorepository' => '/src/shared/repository/PharIoRepository.php', + 'phario\\phive\\pharregistry' => '/src/shared/PharRegistry.php', + 'phario\\phive\\pharregistryexception' => '/src/shared/exceptions/PharRegistryException.php', + 'phario\\phive\\pharservice' => '/src/services/phar/PharService.php', + 'phario\\phive\\pharurl' => '/src/shared/phar/PharUrl.php', + 'phario\\phive\\phivecontext' => '/src/PhiveContext.php', + 'phario\\phive\\phiveversion' => '/src/shared/version/PhiveVersion.php', + 'phario\\phive\\phivexmlconfig' => '/src/shared/config/PhiveXmlConfig.php', + 'phario\\phive\\phivexmlconfigfilelocator' => '/src/shared/config/PhiveXmlConfigFileLocator.php', + 'phario\\phive\\projectphivexmlmigration' => '/src/services/migration/ProjectPhiveXmlMigration.php', + 'phario\\phive\\publickey' => '/src/services/key/PublicKey.php', + 'phario\\phive\\publickeyexception' => '/src/shared/exceptions/PublicKeyException.php', + 'phario\\phive\\publickeyreader' => '/src/services/key/gpg/PublicKeyReader.php', + 'phario\\phive\\purgecommand' => '/src/commands/purge/PurgeCommand.php', + 'phario\\phive\\purgecontext' => '/src/commands/purge/PurgeContext.php', + 'phario\\phive\\ratelimit' => '/src/shared/http/RateLimit.php', + 'phario\\phive\\release' => '/src/shared/phar/Release.php', + 'phario\\phive\\releasecollection' => '/src/shared/phar/ReleaseCollection.php', + 'phario\\phive\\releaseexception' => '/src/shared/exceptions/ReleaseException.php', + 'phario\\phive\\releaseselector' => '/src/services/phar/ReleaseSelector.php', + 'phario\\phive\\remotefirstresolvingstrategy' => '/src/services/resolver/strategy/RemoteFirstResolvingStrategy.php', + 'phario\\phive\\remotesourceslistfileloader' => '/src/shared/sources/RemoteSourcesListFileLoader.php', + 'phario\\phive\\removalservice' => '/src/services/phar/RemovalService.php', + 'phario\\phive\\removecommand' => '/src/commands/remove/RemoveCommand.php', + 'phario\\phive\\removecommandconfig' => '/src/commands/remove/RemoveCommandConfig.php', + 'phario\\phive\\removecontext' => '/src/commands/remove/RemoveContext.php', + 'phario\\phive\\requestedphar' => '/src/shared/phar/RequestedPhar.php', + 'phario\\phive\\requestedpharresolver' => '/src/services/resolver/RequestedPharResolver.php', + 'phario\\phive\\requestedpharresolverfactory' => '/src/services/resolver/RequestedPharResolverFactory.php', + 'phario\\phive\\requestedpharresolverservice' => '/src/services/resolver/RequestedPharResolverService.php', + 'phario\\phive\\requestedpharresolverservicebuilder' => '/src/services/resolver/RequestedPharResolverServiceBuilder.php', + 'phario\\phive\\resetcommand' => '/src/commands/reset/ResetCommand.php', + 'phario\\phive\\resetcommandconfig' => '/src/commands/reset/ResetCommandConfig.php', + 'phario\\phive\\resetcontext' => '/src/commands/reset/ResetContext.php', + 'phario\\phive\\resolveexception' => '/src/shared/exceptions/ResolveException.php', + 'phario\\phive\\resolvingstrategy' => '/src/services/resolver/strategy/ResolvingStrategy.php', + 'phario\\phive\\retryinghttpclient' => '/src/shared/http/RetryingHttpClient.php', + 'phario\\phive\\ringdowncurlhttpclient' => '/src/shared/http/RingdownCurlHttpClient.php', + 'phario\\phive\\selfupdatecommand' => '/src/commands/selfupdate/SelfupdateCommand.php', + 'phario\\phive\\sha1hash' => '/src/shared/hash/sha/Sha1Hash.php', + 'phario\\phive\\sha256hash' => '/src/shared/hash/sha/Sha256Hash.php', + 'phario\\phive\\sha384hash' => '/src/shared/hash/sha/Sha384Hash.php', + 'phario\\phive\\sha512hash' => '/src/shared/hash/sha/Sha512Hash.php', + 'phario\\phive\\signatureverifier' => '/src/services/signature/SignatureVerifier.php', + 'phario\\phive\\skelcommand' => '/src/commands/skel/SkelCommand.php', + 'phario\\phive\\skelcommandconfig' => '/src/commands/skel/SkelCommandConfig.php', + 'phario\\phive\\skelcontext' => '/src/commands/skel/SkelContext.php', + 'phario\\phive\\source' => '/src/shared/sources/Source.php', + 'phario\\phive\\sourcerepository' => '/src/shared/repository/SourceRepository.php', + 'phario\\phive\\sourceslist' => '/src/shared/sources/SourcesList.php', + 'phario\\phive\\sourceslistexception' => '/src/shared/exceptions/SourcesListException.php', + 'phario\\phive\\sourceslistfileloader' => '/src/shared/sources/SourcesListFileLoader.php', + 'phario\\phive\\staticphiveversion' => '/src/shared/version/StaticPhiveVersion.php', + 'phario\\phive\\statuscommand' => '/src/commands/status/StatusCommand.php', + 'phario\\phive\\statuscommandconfig' => '/src/commands/status/StatusCommandConfig.php', + 'phario\\phive\\statuscontext' => '/src/commands/status/StatusContext.php', + 'phario\\phive\\supportedrelease' => '/src/shared/phar/SupportedRelease.php', + 'phario\\phive\\targetdirectorylocator' => '/src/shared/TargetDirectoryLocator.php', + 'phario\\phive\\tokenauthentication' => '/src/shared/http/authentication/TokenAuthentication.php', + 'phario\\phive\\trustedcollection' => '/src/services/key/TrustedCollection.php', + 'phario\\phive\\unixoidenvironment' => '/src/shared/environment/UnixoidEnvironment.php', + 'phario\\phive\\unixoidpharinstaller' => '/src/services/phar/UnixoidPharInstaller.php', + 'phario\\phive\\unsupportedrelease' => '/src/shared/phar/UnsupportedRelease.php', + 'phario\\phive\\unsupportedversionconstraintexception' => '/src/shared/exceptions/UnsupportedVersionConstraintException.php', + 'phario\\phive\\updatecommand' => '/src/commands/update/UpdateCommand.php', + 'phario\\phive\\updatecommandconfig' => '/src/commands/update/UpdateCommandConfig.php', + 'phario\\phive\\updatecontext' => '/src/commands/update/UpdateContext.php', + 'phario\\phive\\updaterepositorylistcommand' => '/src/commands/update-repository-list/UpdateRepositoryListCommand.php', + 'phario\\phive\\url' => '/src/shared/Url.php', + 'phario\\phive\\urlrepository' => '/src/shared/repository/UrlRepository.php', + 'phario\\phive\\usedphar' => '/src/shared/phar/UsedPhar.php', + 'phario\\phive\\userfilemigration' => '/src/services/migration/UserFileMigration.php', + 'phario\\phive\\verificationfailedexception' => '/src/shared/exceptions/VerificationFailedException.php', + 'phario\\phive\\verificationresult' => '/src/services/signature/VerificationResult.php', + 'phario\\phive\\versioncommand' => '/src/commands/version/VersionCommand.php', + 'phario\\phive\\windowsenvironment' => '/src/shared/environment/WindowsEnvironment.php', + 'phario\\phive\\windowspharinstaller' => '/src/services/phar/WindowsPharInstaller.php', + 'phario\\phive\\xmlfile' => '/src/shared/XmlFile.php', + 'phario\\version\\abstractversionconstraint' => '/vendor/phar-io/version/src/constraints/AbstractVersionConstraint.php', + 'phario\\version\\andversionconstraintgroup' => '/vendor/phar-io/version/src/constraints/AndVersionConstraintGroup.php', + 'phario\\version\\anyversionconstraint' => '/vendor/phar-io/version/src/constraints/AnyVersionConstraint.php', + 'phario\\version\\buildmetadata' => '/vendor/phar-io/version/src/BuildMetaData.php', + 'phario\\version\\exactversionconstraint' => '/vendor/phar-io/version/src/constraints/ExactVersionConstraint.php', + 'phario\\version\\exception' => '/vendor/phar-io/version/src/exceptions/Exception.php', + 'phario\\version\\greaterthanorequaltoversionconstraint' => '/vendor/phar-io/version/src/constraints/GreaterThanOrEqualToVersionConstraint.php', + 'phario\\version\\invalidprereleasesuffixexception' => '/vendor/phar-io/version/src/exceptions/InvalidPreReleaseSuffixException.php', + 'phario\\version\\invalidversionexception' => '/vendor/phar-io/version/src/exceptions/InvalidVersionException.php', + 'phario\\version\\nobuildmetadataexception' => '/vendor/phar-io/version/src/exceptions/NoBuildMetaDataException.php', + 'phario\\version\\noprereleasesuffixexception' => '/vendor/phar-io/version/src/exceptions/NoPreReleaseSuffixException.php', + 'phario\\version\\orversionconstraintgroup' => '/vendor/phar-io/version/src/constraints/OrVersionConstraintGroup.php', + 'phario\\version\\prereleasesuffix' => '/vendor/phar-io/version/src/PreReleaseSuffix.php', + 'phario\\version\\specificmajorandminorversionconstraint' => '/vendor/phar-io/version/src/constraints/SpecificMajorAndMinorVersionConstraint.php', + 'phario\\version\\specificmajorversionconstraint' => '/vendor/phar-io/version/src/constraints/SpecificMajorVersionConstraint.php', + 'phario\\version\\unsupportedversionconstraintexception' => '/vendor/phar-io/version/src/exceptions/UnsupportedVersionConstraintException.php', + 'phario\\version\\version' => '/vendor/phar-io/version/src/Version.php', + 'phario\\version\\versionconstraint' => '/vendor/phar-io/version/src/constraints/VersionConstraint.php', + 'phario\\version\\versionconstraintparser' => '/vendor/phar-io/version/src/VersionConstraintParser.php', + 'phario\\version\\versionconstraintvalue' => '/vendor/phar-io/version/src/VersionConstraintValue.php', + 'phario\\version\\versionnumber' => '/vendor/phar-io/version/src/VersionNumber.php' + ); + } + + $class = strtolower($class); + + if (isset($classes[$class])) { + require 'phar://phive.phar/' . $classes[$class]; + } + } +); + +Phar::mapPhar('phive.phar'); + +$rc = (new Factory(new Cli\Request($_SERVER['argv']), new StaticPhiveVersion('0.16.0')))->getRunner()->run(); +exit($rc); + +__HALT_COMPILER(); ?> +nX= +phive.phar+vendor/phar-io/filesystem/src/Directory.php�9?�g���4vendor/phar-io/filesystem/src/DirectoryException.php�9?�g�=v�"�+vendor/phar-io/filesystem/src/Exception.phpf9?�g^�$��&vendor/phar-io/filesystem/src/File.phpf9?�g�C�+r�*vendor/phar-io/filesystem/src/Filename.php� 9?�g�Vs�)�3vendor/phar-io/filesystem/src/FilenameException.phpm9?�gbfo���2vendor/phar-io/filesystem/src/LastModifiedDate.php�9?�gT���[�(vendor/phar-io/executor/src/Executor.php9?�g�#�j~�1vendor/phar-io/executor/src/ExecutorException.php�9?�gg�13�,vendor/phar-io/version/src/VersionNumber.php�9?�gkJ}�r�6vendor/phar-io/manifest/src/ManifestDocumentMapper.php�9?�g��q���.vendor/phar-io/manifest/src/ManifestLoader.php�9?�g�q�̤2vendor/phar-io/manifest/src/ManifestSerializer.php�9?�gM�ۂ��Evendor/phar-io/manifest/src/exceptions/ElementCollectionException.php9?�g0_Uj�4vendor/phar-io/manifest/src/exceptions/Exception.php�9?�g��P��Jvendor/phar-io/manifest/src/exceptions/InvalidApplicationNameException.php19?�gFM�V��@vendor/phar-io/manifest/src/exceptions/InvalidEmailException.php9?�g+�ˤ>vendor/phar-io/manifest/src/exceptions/InvalidUrlException.php9?�g/�Y��Dvendor/phar-io/manifest/src/exceptions/ManifestDocumentException.php�9?�g'�� o�Kvendor/phar-io/manifest/src/exceptions/ManifestDocumentLoadingException.php�9?�g!��գ�Jvendor/phar-io/manifest/src/exceptions/ManifestDocumentMapperException.php�9?�g0`'�i�Cvendor/phar-io/manifest/src/exceptions/ManifestElementException.php�9?�g%#mߤBvendor/phar-io/manifest/src/exceptions/ManifestLoaderException.php�9?�g�4o��Bvendor/phar-io/manifest/src/exceptions/NoEmailAddressException.php9?�g4�q:�2vendor/phar-io/manifest/src/values/Application.php�9?�g5f��=�6vendor/phar-io/manifest/src/values/ApplicationName.php�9?�g��-vendor/phar-io/manifest/src/values/Author.php9?�g�vendor/phar-io/manifest/src/values/PhpExtensionRequirement.php�9?�gw�(��<vendor/phar-io/manifest/src/values/PhpVersionRequirement.php$9?�g��"�&�2vendor/phar-io/manifest/src/values/Requirement.php�9?�g 1�q��<vendor/phar-io/manifest/src/values/RequirementCollection.php\9?�g�X?��Dvendor/phar-io/manifest/src/values/RequirementCollectionIterator.php�9?�g�� �+vendor/phar-io/manifest/src/values/Type.php�9?�g�<�2�*vendor/phar-io/manifest/src/values/Url.php�9?�g�#lͣ�1vendor/phar-io/manifest/src/xml/AuthorElement.php�9?�gn���2�;vendor/phar-io/manifest/src/xml/AuthorElementCollection.phpS9?�gS(�7�2vendor/phar-io/manifest/src/xml/BundlesElement.phpz9?�gf��}�4vendor/phar-io/manifest/src/xml/ComponentElement.php�9?�g`$Sg�>vendor/phar-io/manifest/src/xml/ComponentElementCollection.php\9?�gT6� �3vendor/phar-io/manifest/src/xml/ContainsElement.php�9?�g�a��Τ4vendor/phar-io/manifest/src/xml/CopyrightElement.php 9?�g}�t)T�5vendor/phar-io/manifest/src/xml/ElementCollection.php�9?�g�砲L�.vendor/phar-io/manifest/src/xml/ExtElement.php9?�gK����8vendor/phar-io/manifest/src/xml/ExtElementCollection.phpJ9?�gQ���4vendor/phar-io/manifest/src/xml/ExtensionElement.php�9?�ga�z�j�2vendor/phar-io/manifest/src/xml/LicenseElement.php|9?�g\t�TX�4vendor/phar-io/manifest/src/xml/ManifestDocument.php 9?�g9�S�3vendor/phar-io/manifest/src/xml/ManifestElement.phpn9?�g��p��.vendor/phar-io/manifest/src/xml/PhpElement.php9?�g��wO�3vendor/phar-io/manifest/src/xml/RequiresElement.phpK9?�g^��ܗ�)src/commands/composer/ComposerContext.php�9?�g�x���)src/commands/composer/ComposerCommand.php�9?�g��`^��/src/commands/composer/ComposerCommandConfig.php9?�g�l� Ť)src/commands/composer/ComposerService.php 9?�g,�����'src/commands/default/DefaultCommand.php,9?�g���e�-src/commands/default/DefaultCommandConfig.php�9?�gt@�k��src/commands/help/help.md� +9?�g�$4w�!src/commands/help/HelpCommand.php�9?�g�J�D��6src/commands/install/InstallCommandConfigException.php�9?�g2��=�'src/commands/install/InstallContext.phpN9?�g�|S���-src/commands/install/InstallCommandConfig.php{9?�g��k�'src/commands/install/InstallCommand.php� +9?�g��;��!src/commands/list/ListCommand.php�9?�gD|��'src/commands/migrate/MigrateContext.php9?�gPX'�e�'src/commands/migrate/MigrateCommand.php�9?�g�� �"�-src/commands/migrate/MigrateCommandConfig.phpt9?�gt��W¤1src/commands/outdated/OutdatedConfigException.php�9?�g�-4��)src/commands/outdated/OutdatedContext.php49?�g�#>��(src/commands/outdated/OutdatedConfig.php99?�g� �� �)src/commands/outdated/OutdatedCommand.phpQ9?�gPK��5�#src/commands/purge/PurgeContext.php�9?�gf��P�#src/commands/purge/PurgeCommand.phps9?�g�(Ҥ%src/commands/remove/RemoveContext.php9?�gQ'���%src/commands/remove/RemoveCommand.php 9?�g0��M�+src/commands/remove/RemoveCommandConfig.php9?�g���/@�#src/commands/reset/ResetContext.php�9?�g�E�Ŵ#src/commands/reset/ResetCommand.php09?�gr#�\��)src/commands/reset/ResetCommandConfig.php9?�g�I��Ť-src/commands/selfupdate/SelfupdateCommand.php�9?�g�gY�U�!src/commands/skel/SkelContext.php29?�gX�_Mʹ'src/commands/skel/SkelCommandConfig.php9?�g�!�E�!src/commands/skel/SkelCommand.phph 9?�g�V����%src/commands/status/StatusContext.php�9?�gt�&��%src/commands/status/StatusCommand.php� 9?�g�����+src/commands/status/StatusCommandConfig.phpI +9?�g<U氝�Csrc/commands/update-repository-list/UpdateRepositoryListCommand.php�9?�g�8H�j�%src/commands/update/UpdateContext.php�9?�gz�f�%src/commands/update/UpdateCommand.php9?�g���k��+src/commands/update/UpdateCommandConfig.php 9?�g�@�$��'src/commands/version/VersionCommand.php 9?�gO���src/commands/CommandLocator.phpq +9?�g��X@�)src/services/checksum/ChecksumService.php89?�g 2w��+src/services/key/gpg/GnupgKeyDownloader.phpt +9?�g��ݴ)src/services/key/gpg/GnupgKeyImporter.php�9?�g���~�(src/services/key/gpg/PublicKeyReader.php�9?�g5��W�"src/services/key/KeyDownloader.php�9?�g�}.e� src/services/key/KeyImporter.php�9?�g�j�$src/services/key/KeyImportResult.phpa9?�g��AG��src/services/key/KeyService.php 9?�g���U�src/services/key/PublicKey.php�9?�gr:F?�&src/services/key/TrustedCollection.php�9?�g��a~ �(src/services/migration/FileMigration.php�9?�g�t ;Y�0src/services/migration/HomePhiveXmlMigration.php>9?�g�?겴0src/services/migration/InternalFileMigration.phpY9?�gm���@�$src/services/migration/Migration.php�9?�g�X'mY�3src/services/migration/ProjectPhiveXmlMigration.php�9?�g;9̴,src/services/migration/UserFileMigration.php�9?�g`�p�0src/services/migration/HomePharsXmlMigration.phpl9?�g�]]&�+src/services/migration/MigrationFactory.php�9?�g�r�A�+src/services/migration/MigrationService.php�9?�g��H4�$src/services/phar/PharDownloader.phpV9?�gx�^��#src/services/phar/PharInstaller.php� 9?�g 5�JԴ*src/services/phar/PharInstallerFactory.php�9?�g��� �*src/services/phar/PharInstallerLocator.php-9?�g��V��!src/services/phar/PharService.php�9?�g�s���%src/services/phar/ReleaseSelector.php� 9?�gm��*src/services/phar/UnixoidPharInstaller.php79?�g�n0ޱ�*src/services/phar/WindowsPharInstaller.php�9?�g��G��$src/services/phar/InstallService.phpv 9?�g����*src/services/phar/CompatibilityService.php� 9?�g�>�~^�$src/services/phar/RemovalService.phpu9?�g��%�<src/services/resolver/strategy/AbstractResolvingStrategy.php 9?�g+ז� �>src/services/resolver/strategy/LocalFirstResolvingStrategy.php]9?�gqM;B�?src/services/resolver/strategy/RemoteFirstResolvingStrategy.php^9?�gr��ٴ4src/services/resolver/strategy/ResolvingStrategy.php�9?�g-W$���7src/services/resolver/AbstractRequestedPharResolver.php09?�g82�Q�+src/services/resolver/DirectUrlResolver.php�9?�g7�k��-src/services/resolver/GitlabAliasResolver.phpG9?�g��v㵴,src/services/resolver/LocalAliasResolver.php9?�g��_���-src/services/resolver/PharIoAliasResolver.php� 9?�gI�D�X�/src/services/resolver/RequestedPharResolver.php49?�gW�R/�6src/services/resolver/RequestedPharResolverFactory.php49?�g.�?L�6src/services/resolver/RequestedPharResolverService.phpW9?�g�z��N�=src/services/resolver/RequestedPharResolverServiceBuilder.php�9?�g�+��&�-src/services/resolver/GithubAliasResolver.phpt 9?�g@�Zt�6src/services/signature/gpg/GnupgVerificationResult.php�9?�gkL�5src/services/signature/gpg/GnupgSignatureVerifier.php89?�g�e '8�,src/services/signature/SignatureVerifier.php�9?�g>P���-src/services/signature/VerificationResult.php49?�gJO���src/shared/cli/input/Input.php�9?�g()D�U�%src/shared/cli/input/ConsoleInput.php�9?�g�K�﨤'src/shared/cli/output/ConsoleOutput.php� +9?�gy�T:|�&src/shared/cli/output/ConsoleTable.php�9?�gr�d#;� src/shared/cli/output/Output.php�9?�g^���J�'src/shared/cli/output/OutputFactory.php�9?�g[Q�@�.src/shared/cli/output/ColoredConsoleOutput.php[9?�g;�b�'src/shared/cli/output/OutputLocator.phpg9?�g� A��src/shared/cli/Command.php�9?�gǯG�*src/shared/cli/CommandLocatorException.php�9?�g&�t��*src/shared/cli/CommandOptionsException.php�9?�g<��@�src/shared/cli/Context.phpz9?�g��4nN�#src/shared/cli/ContextException.php�9?�g/�U�#src/shared/cli/RequestException.php9?�gGM�ꪴ"src/shared/cli/RunnerException.php�9?�gm;g��src/shared/cli/error.txtR9?�g���'�!src/shared/cli/CommandLocator.php�9?�g7��o6�!src/shared/cli/GeneralContext.phpB 9?�g����src/shared/cli/Options.php�9?�g����6�src/shared/cli/Request.php9?�gu�7N�src/shared/cli/Runner.phpV9?�gE0��N� src/shared/config/AuthConfig.php�9?�g,d���#src/shared/config/AuthXmlConfig.php 9?�g�S�঴.src/shared/config/AuthXmlConfigFileLocator.phpx9?�gf6�ô)src/shared/config/CompositeAuthConfig.php�9?�g�1z���*src/shared/config/GlobalPhiveXmlConfig.php@9?�g_�fK[�)src/shared/config/LocalPhiveXmlConfig.php�9?�g��q�ٴ/src/shared/config/PhiveXmlConfigFileLocator.php)9?�gWQwŴ+src/shared/config/EnvironmentAuthConfig.php�9?�g�Vo��$src/shared/config/PhiveXmlConfig.php�#9?�gh} M�src/shared/config/Config.php�9?�g���5�&src/shared/download/FileDownloader.phpj 9?�gFcz���-src/shared/environment/EnvironmentLocator.php�9?�g�����-src/shared/environment/WindowsEnvironment.php�9?�g��� |�-src/shared/environment/UnixoidEnvironment.php +9?�g�Rs���&src/shared/environment/Environment.php=9?�g'f)��'src/shared/exceptions/AuthException.php�9?�g Z +P��)src/shared/exceptions/ConfigException.php�9?�g �.�´-src/shared/exceptions/CurlConfigException.php�9?�g�?c��'src/shared/exceptions/CurlException.php�9?�g α���1src/shared/exceptions/DownloadFailedException.php�9?�g˫)$�.src/shared/exceptions/EnvironmentException.php�9?�g��bb�(src/shared/exceptions/ErrorException.php�9?�g I��W�#src/shared/exceptions/Exception.php�9?�g +�<���+src/shared/exceptions/ExecutorException.php�9?�g.��1src/shared/exceptions/FeatureMissingException.php�9?�g����2src/shared/exceptions/FileNotWritableException.php�9?�g�g )�&src/shared/exceptions/GitException.php�9?�g +�=��5src/shared/exceptions/GnupgKeyDownloaderException.php�9?�g��m8�%src/shared/exceptions/IOException.php�9?�g +�˸˴5src/shared/exceptions/InstallationFailedException.php�9?�gu�.src/shared/exceptions/InvalidHashException.php�9?�g`o��-src/shared/exceptions/InvalidXmlException.php�9?�g��}�5src/shared/exceptions/LinkCreationFailedException.php�9?�gH@!g�,src/shared/exceptions/MigrationException.php�9?�g �g�3src/shared/exceptions/MigrationsFailedException.php�9?�gtshg�3src/shared/exceptions/NoGPGBinaryFoundException.php�9?�g� G?�+src/shared/exceptions/NotFoundException.php�9?�g�A�)�'src/shared/exceptions/PharException.php�9?�g +2eh״0src/shared/exceptions/PharInstallerException.php�9?�g�B��/src/shared/exceptions/PharRegistryException.php�9?�g�Qz-�,src/shared/exceptions/PublicKeyException.php�9?�g���L�*src/shared/exceptions/ReleaseException.php�9?�g |���*src/shared/exceptions/ResolveException.php�9?�g �Db�.src/shared/exceptions/SourcesListException.php�9?�g)z�/�?src/shared/exceptions/UnsupportedVersionConstraintException.php�9?�g��R�5src/shared/exceptions/VerificationFailedException.php�9?�gV��&� src/shared/executor/Executor.php9?�gZ'&�/�&src/shared/executor/ExecutorResult.php"9?�g��8�� src/shared/hash/sha/Sha1Hash.phpL9?�g���|��"src/shared/hash/sha/Sha256Hash.phpR9?�g�$�U�"src/shared/hash/sha/Sha384Hash.phpR9?�g�-G���"src/shared/hash/sha/Sha512Hash.phpS9?�g��Cc��src/shared/hash/BaseHash.phpN9?�g���qQ�src/shared/hash/Hash.php9?�gB.�� �6src/shared/http/authentication/BasicAuthentication.php�9?�g��B-��7src/shared/http/authentication/BearerAuthentication.php�9?�g/��'I�6src/shared/http/authentication/TokenAuthentication.php�9?�g/E�� src/shared/http/CacheBackend.phpL9?�gS�4�src/shared/http/Curl.phpu +9?�glBi��!src/shared/http/HttpException.php�9?�gfd�_�'src/shared/http/HttpProgressHandler.php�9?�g� Y�_�)src/shared/http/HttpResponseException.php�9?�gmȑA�"src/shared/http/Authentication.php�9?�g�����src/shared/http/CurlConfig.phpG9?�g��� �%src/shared/http/CurlConfigBuilder.php9?�g �����src/shared/http/ETag.php29?�gPxp�r�+src/shared/http/FileStorageCacheBackend.php�9?�g�9[i�(src/shared/http/HttpProgressRenderer.php� +9?�g��F��&src/shared/http/HttpProgressUpdate.php�9?�g'mKdĤ'src/shared/http/LocalSslCertificate.php�9?�g��ۥ��src/shared/http/RateLimit.php(9?�g���$�src/shared/http/HttpClient.php�9?�g1K �e� src/shared/http/HttpResponse.php�9?�g|���&src/shared/http/RetryingHttpClient.php�9?�gp+��@�*src/shared/http/RingdownCurlHttpClient.phpn 9?�g� ��T�"src/shared/http/CurlHttpClient.php�9?�gC�DU�+src/shared/phar/ConfiguredPharException.php�9?�g/4@ý�"src/shared/phar/PharIdentifier.php�9?�gl��I�src/shared/phar/Release.php9?�g8�E�ȴ%src/shared/phar/ReleaseCollection.phpH9?�g�cG�!src/shared/phar/InstalledPhar.php?9?�g)7�src/shared/phar/PharAlias.phpv9?�gm��y��&src/shared/phar/UnsupportedRelease.php9?�g��-� �"src/shared/phar/ConfiguredPhar.php3 9?�g�o��src/shared/phar/Phar.php�9?�g��=@j�!src/shared/phar/RequestedPhar.php� +9?�g�0�$src/shared/phar/SupportedRelease.php�9?�gO��1�src/shared/phar/UsedPhar.phpv9?�g�<����src/shared/phar/PharUrl.php�9?�gb�,g�*src/shared/repository/SourceRepository.php�9?�g*�d�д*src/shared/repository/GitlabRepository.phpI +9?�g� 2t��)src/shared/repository/LocalRepository.php�9?�g� im��*src/shared/repository/PharIoRepository.php( +9?�g{���'src/shared/repository/UrlRepository.php9?�gV$t�*src/shared/repository/GithubRepository.php� +9?�g����ۤ,src/shared/sources/SourcesListFileLoader.php�9?�g9�>K�1src/shared/sources/LocalSourcesListFileLoader.phpC9?�g�n�CҤ2src/shared/sources/RemoteSourcesListFileLoader.php9?�g�sSD�src/shared/sources/Source.php\9?�g �[ �"src/shared/sources/SourcesList.php� 9?�g!dN��+src/shared/version/GitAwarePhiveVersion.php�9?�g%��BǴ#src/shared/version/PhiveVersion.php�9?�g��L++�)src/shared/version/StaticPhiveVersion.phpa9?�g`V�kشsrc/shared/ComposerAlias.php^9?�gI�����&src/shared/FileDownloaderException.php�9?�g���f�src/shared/Git.phpV9?�g�5<�Ӵsrc/shared/JsonData.phpM9?�g�讛 �%src/shared/TargetDirectoryLocator.php�9?�gyH>Of�src/shared/PharRegistry.php~&9?�gc�� �src/shared/XmlFile.phpm 9?�g8�j��src/shared/GnuPG.phpX9?�g0���src/shared/Url.php/ 9?�g ���Ф$src/GithubAliasResolverException.php�9?�g��Q��src/PhiveContext.php�9?�g�4��k�src/autoload.php�S9?�g1 "w|��src/Factory.php�F9?�g� +�(��conf/auth.skeleton.xmld9?�gR�&�ٴ conf/auth.xsd�9?�g���:%�conf/pharBat.template%9?�g'����conf/phive.skeleton.xml�9?�gR��conf/pgp-keyservers.phpG9?�gAfd��Wmo�F ��_�v�$e��t�:s�"M�����)��DY�Hw��$h�߇;�X�d7��O�D>$RO��� B ��դx@s��P� ��`)�1S���O�ý&L�{� aZ�[�0 ���K����S`�|{;S|���1�� �,_$<�(q)`>�Ф���ۚ{��ڛ_�b��'(t��\�����x��܅Y�r�[ �-݂�zr��K^�"���rε�x0�w`Z�oUP��lx�l��l��I�f �u����1���@��I6ɨ��.Kd���M S(L.��6��"��1��13�'�Q�W��M�EW�@H1L���� S�k `6�Q?ȟ�!KX�R!���zBZ�L)v?�1�ȭ�j�i�W�Dcw�%�<]��”qQ��{dYh9�׆3d.��aI�q��V��;�î�˴X)�B�Y�!�L.�;魓�X�'��m�[fW1�4��$`x�N6���9ca 5h�39^7�B!�i?z�u�j�?�?� �����ys����?��+���o�Va��k}H�7�5�(��­�ͣ壖{"y�ˍ ���k�������o +A�l���نW��Z!���M�z���w��b%�c�QqqV������,��u8��?N4�� ҷ���m�= �0���~ō:����:�����Air�4��] +���/��u�Z&o2OD�#�hI,X�lZA0=K2�x�Ln�q��E�� +�����Ls� �I] ȃr����@DL��w��(��n���hy�U�b��(7�y�X����h?b�G�7���Ͳx� Eȱ +�@ �=O�����8��Mp$�<8�pɠ�������z[�h���GMs�玟MKE6����U�w�>)c<=��D�ŝ߇�"�q��<���?]�PMk�0 ��W�ЃZʮ��m +� z�SC�K ���>��iɺMG��}=>���'�Z���.���*N��6o���}kkܟ�� ajMqg!�Yϝ�2>C��>�N3��0��C�v��i���ۏ�8�ΰm�iqh �Qq��'�Ģ���ęqei�IXX�Z��������徫9��ju��*����$%�� �Iy*� +�r1�$9�M����I�D~�FJ���4"W���MJ��/��[o�J���S��6"����4iKu"�i�pt�6���޵v�!��w��|�6 /�z�7;���߽O�����ܧ �4�����H�M�|����[��� |���r4�3쒵�_��l2��g����&�l9������^�I�$�a*}�J�f�+iH�>����܃_����)���*�(i���n&3����*�FJ��3Y�� KzRJsB.pS՘� ��/\��r�����5'�$��Cp���J��Z���Ů�tZ�Ss]#��8@���*A��P;f[�&�£�� ��O�}�A�����_?�Y�[�O\�OJ�]�����i_����%��&�����4��Bm����M)��?EZ�@����x���u��L��Y?� Exs��z�v�U˴� :P 4_*{�d�:�L��Κ��c�S�z��ڮTu���ζxg�ʧY�-��9/�T5�X�I��5v�ڴ�#�r�X�E>.ۣd,����J��~gmnW�+�*��Ҹ{�4v؏I4��ΙWp�pxs83δ���c�p��5k�^���Eg�M�|���;��n��⩦�Q-���Ns{��98�r���6���<��dak+|� +��&ԫ�I��#\�o�8�z`0��N��� 3�U<���a��''�Ji�B(��� F����?;�txʕ[8��u�p�̱��z�u���;r} 7�C��U�X���|j[� �8�^_b��o���T���}�_�)�߬�����m���ayw�uY��=���/�(PHIM�I,J�(.)�L.�/�,H-V�U0Դ��K�M-.HLNU�H,�̏q��I �,.I͵��J�I,.V �ԹV$��d��)�V���+ D��j�u�MK�@���#��xM�z�BAQ�� +e��������"��ij���eٝ��}f��*+�I(t�xvR�y��m:� j� +����.�R���3�q ��� z~��,$�O�?�h8lN��,Bn.5ʹ�W����9+'��� o�����^))���`i ,��Ϯ��%���#ĀK�o�)�t]B�.�!s���C�7 ��[PN��p������:�{F]%�0 �pM�KkH;���L8B���zj�FN����ݙ���3��$W�&D�(��{M�Y��9(� �e� w ��pĵ3��'U$GۓI�oU�7����h.n5�`e���f���� w�/�SAn�0��[CF$Æ�v�����h�����" ��LR�{AEbHYM�C̋���hgt�UW:��AҌ#����S���ȝUf�e�B�V����,6��g���׌z����Y��|�̰z�\5 ���0��5B!g�x/d� ����Br������ݮNr+�l�X�W�< +;����ؠ$g�C�ς,Cv���(d��(� 8��FH{(B۟�)����I�3�Ta]3s<��0zh7Y�e�L7 �x1E$i�rV;;���Xw�ħ�������ݛ �:e���uD��ʨ'J�ڇ ���b����cw��(�D��_�e���m�Ӓ.ȟQ׃���#�)nc2��� + ���N�cd�q ΰ�9=�]�~({���'�(4� �Y7JPD��]�7�/�c���߰��U�1 +�P �=���`�gq��"�K�*��Ѥ�xwq���|�K%�FeQ<�n>MeO� +������|�A[�́�G�[$�W�mF�S����Ӫ���7}��?O�0�w�JT({ U�@0�\�4��:���*�叓6 �%���ݽ���M,�<��r!�5��l>�G)<\2&R���7�|Jpb `E� <8�#Tz__-�Ӣ:p�p%L�q�[;9"�6O��>�i�%��Vf'���(�hG���% �r�Y=@3����Wh��4�mO��m�|{-H �M��,�U>0oK]é�VԧD�����Y�IU�P�p;�P���I�����Q�֘tt2�޽ҹ�y׵��c��B���!�%���`N�ž��/e�#��ЧpS�U����'�Y�~�\m��6��>��,��nbImKN.w�������$9���@˴�m�TH���E���(�b��{n>I����b��<��ݱ#;V7T���(^��5��i�=�^}�BЖ�֌��z+��G����݋uC�&o��jcM���!�|� yO;r`�)j؎2.�?����̣T�1c��ɞ����t�oX�7� �)o��-���P{�F�0,�`���TcX1o��t۰�˳��pI����򜦓�H����\k���{����dG ���f����j1�1D�]'&p�ف��X˝��լڼe��E��aJ$ܡ������˃�+�*��`�®�wJ>��0�)�>J��� h�:�C:�'ip�Z�&�f�@.o����C���+3���G���D0:*-濌�������� =R�k�N���C��(ڴ'pfi�@�µӝ�d�'CL�z!�Q:Ư-"ԛr�(��/�c5\r�uO�����/��Q>9�#>\���ҤUE�o�ž&'�z����KR9)���F�%������`���~�U�!���u 8_/��U�e��믃����׳�#,W�8�ю��[t��#��3��=�{��k�ʛO�}�|����]� ���� 6��x�Yg{�j���8�W*j��w/[,<�o��<�"K��=u\�� ��${�,��b�[�*x��h{�X&&�4�6!t�,�R}wXN����C/{�n��\4�b�0[��ϊif�b��!y��Z����e�?���0�=�,+f����f�- ��X��]yJ-Wm���cQ���U-��W�q� �O���>"L���{B�*@�@6[ϋlZ��eaGY���O<��x�yvF�d-̽{t��Cqfy +� n8m����*���r�0xCDN8/1So����b��h�m#��b3Q?�)�����I��*{��3�M� �µ�:��W��x�R����@��ydM�;�kR��C�P��Cױ� +�[O"��es�Er��0� �&A���y�$ ��;�$2%� �d��ҐI@���g�L�l�¨t�h����M��\O���}�4�tȺ^GhxN̐=\c������J���E� �<�f��D�� z�Ұ��/�� 1��`��f��H�r{�@M�`|��ʑ*Z�g���F��K3p|i�$��fJBȹ�O���1�nȎ뮡'r��\>�:b ���.��<[��p /d���\� T�ݱ����2"]4�q�G [Ƽx�̿�6_���V4 ��̦��F�H��X�.����+�I=eFr�f��\�F�H�����y�P���2���D�;v��)�<��i�+җ���>*H��.����`%�j��#tgs/��Xj�]`�g��T�<���%J��.��|d֚�$J�L�=��H��t�Ƚ�{.�KM2�������פ�=aȣ�yT�C�IT?PPb�0e���f���=�B��/Q7��/������C6a��ۘd��C�E":�]�w��1#/��ۘ� 1]��W_�&���e}���#!;3pt��� ?3��KϏGNȑ�s���^�C�Y���G����~��չ�;)����>�Ϗ��E�m@>��똩��.�ZzG���<� Ֆ�v(�]��;��$_ΉC:�5�*�#7GBmz��kz��2��!����% >+� �?�F�/���Z�ۻ��Y�e�+�kց��4 S����L��Fi�(U�Q���Cl�8 ~�C��s�’�KK�N�pZ�"���#ƾKŖ�������� [����~o BU1j�5I�n�X�3�l9<=뜲��q'w �<*%�3�n��>����6�,;g%�3�1��av�n�mS���r�����2�&[��u��OUp��� �[��֫���\W>ﮪo� +�E�u��.ܻ��!�"k�b�M���R�G.v�q�&[�4�_���\��]T�y3����8�@��>�T��t�j�ЈFh�F�k�.��@[v 7�������qQ�JX�-\��q!���4�Yh)��{3��;tI +�]�'�3�� +l}V'B� �P�3�dH�� �r'��ܵ�[��|�C�@��+�?��V>MEƸ'�pQFF-�.L����l�MM��Q��3Λ����m ;f� �pe޼���m& ��Q9���*��z� v;7�F�h�$��χ��:���e�B?2�B�=�U�]@��"�$�_�?�B��H�]D�aC����������.�������P��*�լ�7qO�b��V�����w;�����Ag�0���?���Z��KX�34�.r�|f�y��l�|���z���Wl����D9{�����9����W�H�!��CV�˦W"�q��(�$ c�ү_̵��ɮ��e��[�=� ��[�J��xB?���z#���({6��<���7𩦈r�Gƣ�RUɞ7�A(Waui�]Ȩǝ�s��{�:�s�픐N��ӈ����B�[�8[�K�\�i�Y h��|O��W��:��Q�h̶�W.ѝ�:d�̣.�#)]�o��"|�����:�AbY^�`�e�d�T���Kt:�� ��6^2�*�^�N��y�w�L�j���LL"oU�U�!����ZHd��h>�[�jv�� +젱S|�^��se�u��,m���Ξ2?�����;��� +�S�U�ʏ����D�9�ڱ=�5�_ՠ�����uJv��� �P艶_�g�9x�S-=9 v!Ɛ�<�� ++�c�O\-)f�\K�@�m8Չv�j;z�/�y��a� % �{��W�1��L��֓*[V��d��T���?���fWYZ����F���z(6��F�9������������@8�*C�v�rM��F���"�K_�*���y� �J�1���\���s�A�V�����?0�7RHƟF ��M�RHT�M#���)��S�����nQ���\�X���Y��S��dͣ��1�m�T��*�//��=�f��o �Fn7�h �[��+W�f]��a�qӾ�Q�q�%��O�n�^����J8㽙� l�BxڤY +�j�~���/ݶIw�{��"+��*��'� �S�!?Q�يx�u��.Q���t�F���*��U���;%�8��q�O^�la��r��H������W��F���W]]F/1z}�Bh(�^B��]FW�_F�1�xF: N�X��3�̰8�gęay��3�-�h�%Z<#� �t�E���D�-��=j_.��b�?@l����\aT÷��_Qe��(�����`(��7����^�!W�F�V*C���A��b9cwS/Ǟ76U�{.�������v^/Hjh9/5��(* �y�Z��|�\�Ҽ6��tc�Ej�-R4'�o^ϋyVwl� Q@� +��b~���Q:�޶i���,A��{�[[lդ� 8L%�t�]��U�Q��� V8O�s�e5��>��pm��� �-�=�@��%@��&��)}��س1|��F�y�'��p�o ����7+k�G����� �ANR ?I�RM�� ��J��ʮ��D�F棞�_v����Fx�e�,��~�C��M��}�'�>;27d�l3>�D�d3R/xbs�2����,PYj��6��3s�MV "�M�����2���hoH,�ܷ��/���� ��؈ �h����l����H�B~����A����{��_��̼Ԭ��mK��Y��_��p�gH/����9zΧ��x��y�/�x�E�1 +�0 �=�Ȩ������$����P����t�*ᝒ�ic_�V1l�*k�t�)��R�y��4�����E};#Jv��p��>uT]o�0}ϯ����@�=��V�(E�(*���L�.�R�-�)���>9_@H���>���{��V&�T!�F��Df'Q��z��5jIc�YB�D�c�����t}4�b������Kq����O�06B�>�_q�8��.�q���&8�y/�RhT��A���s����ӽv�j 4��{{� +�i/�b�� tVr��qjs�,�/tk_��ڨ�n��]��T�����]�(�eo)�a��"�(�}��c��ԃ�� tL�t��r�����`�Bj0��;�D���z���QD�b�x� +��qW�zǹ��� '�x�{6~|�5��o���P=7������-Ɩ��2���h4��Pk�@R�`�����'O��W��Z +M�xn}�?`���X��j���&Tv�x6.�0�&���kYSq;!��:.�6�"��<�-�F�������j����M+�b�y��)Ǹ����n�L��F�h��#x��Ry�|�u:��"�+%gs��8Bh7)4 g���y��g2u��� ܂�IPa��B`w,N���)�Yj�7�bLB-�fI�+ W���x>�+K���:�e�^,�9�y9�%\�d�EؕSY�a�&q�r�[��Rd|=�)R�`_E�܁P��64M�$�����U�w*۷��8�� g@�V|�!��+�ߚr=�lUcɱ�_��c5l _��]�<5�����h:�Y����h�&2��т����;{�?�ks�6�~�ތ;�|�%�n˖'vs��5�8�I����1 �hG���|$�Gnz�b��}b�͛t�B�~D8�Br�˅ܤ(` ��u��EJ|��5����=|��t2Qm�E?� �~\�~F�E���3��q#$Ƴ;�ї ��BP?�TםN�����eMP\��� /��)r <�Xr��, aI��4��e����G���T��>���6=HX��X1�u��GF9��@���.�ӄ�P�Jz�D������6] ��i�12�A��~�r?�/kF$M�d,��D��Ȓ,\�L�n&p�E�J8$r�Ҍ��@с�~Ǐ��]v:��Sx�L8�nPx +�r�L$� VޫTiQ�� K&�t'U��Q�������rD���J��$@�7,��lQV� ?aB�̗ne����4[�=C��&5@�2]�SsW�D���&%�|sl��Y1.1 C4PMi4j�� 욈 +��M���n�<�u�`!�<�;�9��4�Se?-���s$���&|���&6d�:9��(7 �� +G-��r!�^m��')�5F�[ +�M�x���nE1�z��L��tM1*���S� +HW��R��"&�_������>}�����pg�?�3ឝv�Nf�f'���+����DӢ���G�quu�ʹ�8:&�R&��9���&YQ"O9e�Q$����<ߪ�m�h)��y<��|ol<�F��*8�m�Q����#*����p��y/T�=S�#0�߇ K#�繖2�L�"Q�xE�b��[����j���<%\�G�J +�2Lj���N�Sx[�k���"��Է�$/B�Kb��؃�G4dDfk��@�w��׼BաG$������\���1�%���!v�S��m�Y֚a,��V��઒��-���=D:|��Cao�}�kF�J�����;VewGr�,���{�'���V<��2F��)z�J����u"$����F�K��M1� ��q�BBc�ĩ�B�~dq�-�I*�*�H�5�{ N"ʰiғ�p���~- + �J�q�Kg��$c��ɺp?6OT�O��,3��[�ƹ�v~��|nc*��N]-U��o�x���Ӆ� �mb��0���oFs(i�����������������ggg�n��\��?���`x� .�� /?]��:�<�\��0�`w�5��os=mmU�oŤ�BP=��F�{pcXkA�b�_'64AÅΑ4a��H܅�U�e���)�xI�\ЄY��g ��4[>��#Q�X�k"ֻ���Sl�)ܤ���꾷J��-��&�˸oUD^̯�x�ۧoe aɑ<5��`\���ި�)��V��W?1��K"$% �!c�܈�[Qn{�b�,�DH��.�6��?�N�r��r�%4n����nTSLn�'Pyp?��ё7�����&��xu(4�篏��Œ�8V��e�c������{c��?��Tn��aЀW�qr��V ļ9� +���M�\i��+6�-��P��;��|�Q��d������X��a�:U9��fހrp��2;���=m��yd-#Hn4o�� ��ɋ�H�4j���K��T����( �h!G�V�򑗰R�N%z��k�.Հh=_�f�]NW~%L8[��tm�TyV�B��.����I�#�?'��X�ϿfQ�z"�J��ÿ�{�P�t�e�pX90��s�eQ䘭<��/��>I߫�[��~p>��9�n�~ݍo9�`mNu�B�:N�a�i��5n�;m���3S�Һ�z2j<���Ë_#*Zn�΅� E&���Kܾ&�˛�����?h�:��v�����v��=i&��L��m�Y��_��X_Dp1ܷ�T�o���9 Ճ0�/�-�Z�T�����3�Z}����[R�v_py��M:��!��d����"�e׫q�����/�y4�n ��D25j�IFa��Ŷex)bq��*��0�]�x�R�[�Ms��B�*�tF;`��>��s:Wbj�-b�!�Vt���w�rtC���[u.,�x5J�TWo�[���c�p�'�-��v�]�=���IJ�4���0�4λǝ�{ѫԨ�]+m3>ʥ/�Su�z_�EH�ZE��^=R��>8�\;鶛Vh���4�;��J���bX��J����0+�O��-�~_T|�%2m����z����g�W��T�x��!�N�_�Qu����~/�Q�I�$�.r�Ci%&4r,����9�jݑi��H";���߷z�*�3���R�\7� v��ˈn�S��*�`mI����i8�#> �j�Ŭ�r���"ø�ˆ��n�`�"ckx��}�����p��ũ��E&���ؙT!eW��E3�/8[0V�6i>3h���#�hS���$d\�"��?�]�X�No`��`<b�%���s^!c��-%FO��>=OՑ��b�y ��E�nw o��5e�t��� �֩yFn���6��#ٚG ڟ0Z= ! �4�t�N��9z���קں��zP��~���o%��}9 &Xyo;k�^�t�����#�br��w�z�mǾ�;��!�jq(�F��T��I����|�|P�mP]kA |�_1��&$�5v�|��@)�>�ޞ�'��]$]�)�����S=I��A�ͧ�W��rTZ�+'��J�x�\��U� +?z6t� l�Q���>�Sy�Ij\�*`�.�w*���7�6Q�n��|���Kl���_h$��N�m�+���xO���w��z<�sQxO�Ɯ�J=(�zG���X��Ct.r��)�i?�>=<~�>NV���G�>Z��iF�{�>�be�DH��)�� q �1�Y@�R�f���z(b����C�4�����O�:6��Q�t9Rjf��Q�w/��� �R�Q6���*pi=��ῖѶ�,���6woM.V'���m�Ao1���+�!RwK��+Ɇ6%�J���8!�Y�ll�ؖg�i���ѦI"|�G�{���Mh�x�\�fg��>'�x[M�����n��s����"v�f)?ğ?8��a\`����]���� +���6������ܐ���O�s�T��d����s^�)�3�Nt���1C-�뽇��9��UPh�� ��פ.�+$�$�GǛ�������r1X�6VK� Z7����-6N-thEb� �Ė� +n�@k�D�� +���$����c�䂂��C+�k����?���Π�����K�`��r�ޡ���e�E��Q�"׳���]X�դx�]��Z�y�|���>�RYU'�'��o.0�7�L{=#9F���������)����I/�m�-�m��n�@��~�9p���J��D��T��@9EB���G]�ng�ШɻWkB{����~{�9TJ2��Q����9P�)|�o�� ��*��aK������ +��?=�D��:��n��Yt�I4��Ѽ8\_�4’ +����5$0���< o+t%X6�"�����B���`Ǵo���p���\$U��V���%�~�F��=k�Z��C`|I��Q氦��YA7Yf,�_�PIV����Ӡ]����.� ;�����E��H��F��w(�)B��*A�f�5ڷ�ݦ�l`�8�Z��ڴ��h��-����G� ��3a��h�(�t<>����������f�u0��8���s_���t�.� +��I8!m� ��m�P�ݸ��t:��}4���rb�x����a���?uQAn�0��s�A6���n�$H�E[ A.i`���E�&�%e�(�����ڊ�Q�ٙ�����=JV���E���;��~8�&� #<�:�҆�;��}9)F�<6P�fH�#ak|m���oO�Xs] C����ƍ&�} ��7�؜��+��� �(d��[����`���i�ް��Ș lk$&hg��� �n�����˶� SA�bbj�wŚ!�f +���!��������.Q�ı��T��ٴ�+$6���`[��V�*�v)�(hRJSX9VR�z���/M� ��tkM���D��Ȧ +Xn�Dſ� + X��#��I卥#��"j���-$��C��S��R�ѓ⋀E� ň�g��>�N�;g��V���mqS%Dɿ���&l����,c>�= c�'�Eq��h�f�*m��Ne�AI�q�b�n�%�Hf�i��"KQ��N�*���T�������7%�A��V̥D.��V'��T�~����;�:X��/������Dٲf�<43��ċ+i'M������e��_�������E�= �0�=��F��89t+ +N9��� G.����������R��\q�VŷW�(�.t:.s�){дr��D5)yp�G6�1�9J�*�`�c[��MJ&���$iDBnF{~���Eʱ +�0�=Oq���U�����SA����둻JE|w +]?��YG��X�ag�r�F':� \a�t���pG��y~�{n�C�p͙ +.�XYj�>x��8��;�Ƃ��4�H s�H��4}b=�&�jCi��;F�'�*���ۛ����zY��8F l����g3{l5F�b����J�E�ig2ѫ$� :�AM2Rw�2�k--ؿW�<����9�.ׁ�A�?f ���ѩfN�p|��ݿ�?}�Ao1���+�!�lTqm�ET���R4��Z8��UU�;�6�4|����o��M�#Z6���I���}�^׳j:�0�]o:�6!�(B��=�m���%��/+ ұ��V�X�;��L���"=��3��L��b���| g �İ� �#��_ :���[> �O��n��o +j�X{R(����&+�8X������ -� +����H�� +�U�q�Vٺ�3+�'%D�ŸTBϖS>d�9�p�J�}�3�tQ.J���^�(�R������k���7��JM�]N=��p�Se���N#�}.�8�6�3l��֨ ��O��3�Ԫ�!ܚD�{ w"��m��~�P���_kfk�"�3�g�ZٸU����&���|&a�l&���T�5gj)��Qi���L��� +M�D�\xBk����c�:��b�l�ڼL�=�*��bL�PH �DŽ +��_k��;��:M�s��w��Y󁋺Q8�j�t�/}��1���v�?N']���]=�m�vT�XS�7��fS��BeR��Q&�oQ#�'��{���Ӧ�1���S�q )�� �@�gs��`^�;�X**��+ha�O! Ƀ���_l�Ap>��v#[H��ծ����y ��KXn�^lv������_h��0�Oj*p�F_5i� ���;�*@�Zz�\N��&��s��۞\/mI��k����τ ��Nyt�>l���9��)}���XmS�6��_��p��ㅼp�\�L�2�%)�؛X�#�����{G��IJlG����g�]i��Q +!1�� +�i ��S�zp❶��[��"*`Bc* %\B2����dx�\Є�[�����9C8� ���%��ٸ���78&BR��W̐CW��(�$���� ٌQ�Du_2B�dq A�>q:�$BLd��I�gD҄B#s���o�?]\�\(Sy�2"���*|ƙ���@*TD��!HBT���HI�@��V!@�����>�� e�M��8�tN$�n��2k��\�U��/���y�Č���W�P�Z��� 2UXNjk�`��M6��Ǫ�!`�r��8����L$��oC"��O�qL�d,P���� aB�,��F{w� ��k�>���~��8����$��k螚���A��T���xie�dēW� ��c��JH�Y���4]��Z�3�pu�fA����U�DH}� ��CS��s�@����� �FJg6���p�gPM 2K޿k����u��k��X�����ɮ�z0)�LN�r�ǰ��9���:�)ʯ�m]/����� p\+�z��תa-�+q�U"ǎ֍��R�dF������ +�68����*��>֍��Fׁq�ĵ��}G�����X��<�Md���j�����=-5m�kP��X�� g��i8��~gg{{ �`o��kg��!��5�(���:r- y��%���D"������5��ok�����o+�KN�or��E����o�N�i�MN�O�66��c���9���ic�;k��1�z�-���������]-85G\yܔP ��n�qq�;�����eGmms�G4����֦^4�~mN�g>����W,����V�7�Wϡ� y� |�RR��~#�� %/[/ ���9?�Sw�7-���� �fE��n��WG ��L_��Fj�A�B�7�ds��m�u��s�dJ�(�E�pN�V���Vw̼񠗣TjץLz����7�3���y�meA �,�M=�B�t� +yo:#/_�v,���6Q�d� �!� �7�㘜�]b�*��1��� �|��Ψ�֦^FTn�z���lA��5���L[�{��C,<�8E��s��|P��\wsZ��w'���0��J�a���h\��*�ZN�Z5ό�S1�[�vM���A� q��$�� �Y�E�e�t��ް=P�:�����[�,��c��������Ag��xr6:��s�=z��ѫÔ��>'��,��]�����I�P�8��d�ɇb�Ҕv> +��D�$���npv���4��YZ�y57&�*��P����U]o�6}���<裎��&q7���i_,���+��Lr$7h��>P�e}ر� +0��x����Ë��%�H���F1j��G����;o���p3 K�I��mL�G|F����d[]��Pq�� ��2.��x5[�wC���gDF8��)*���W2�(d����Q����ޔ3c���,�!J������c���0�\#0 � � �� � ��׏ף��#*clbb`I4���3K ��d&cU�"U����' ԒP� t�jфh �����(¸�%J��o-���gO����J,5|�:�R(�a;�JQZ"9��=e:K�(����G�W����~ I��Y����Xn���B�+@�'ǃ7�>D$��vۥФ�C۪rr&x�>(��<�w^�[�Lo�p�eA �]�w? �����`�]����t}o��1��&P/ S 8.̭�� +�T���urЍ4?i��\(bw7�Tbm穗��r-&��z�Զ���9k�M�;��x#Q#TS�M��^�fw��vI�~&xC��� R9���r��7�P�wr���?��s͋�y�������V�L��i,���Ѭ�fΆ��T9d����u� +,8Sσ�N�mTo�Q:�ꐇ�J?����A/V���+��It���N[�Qͫ�eG9[����9I�V�ʽ���^��ծ�� �/u�:���ڮ�Ρ���~N�:���KK��h<�����kz�pU�m��L��Q��s�o�UM��0��WL�I(�l8Bӭ�^*u+�J\6(2fW�������|�*��yof��x�=Ň�H"ВJ0�|�+F .8���$D��<�%�V($���0h@����Sĥ�q�"A��������H�%�[nP�x��Ƃ�B� ɏH,�AƇBb�� +�-�}-6  ��� �$,�>�"��"C�Ǭ���i'��Q�%�*q�}�j�Pe�\e����w�����*y�'*��DH�U�*�4��\��W-�e�/�ފ��/9Ri��>��AZ���|U�yE�v�g�eMZ6�kܹ�;8��pf��ι��П3�"g��+i�<���1bۺO���[o�_���+X�/#�\1��w�TG�`�܇ȕ~=<�A�E�|0'��j ӋM�p��ϲ̖Ǥϓ�V5���چ'�Wv�́'AP*6����u�I����m��5�Ӓٹd.����#�G�YK��-oMc�v��E�W�V� �5d���ѱ��G]�g;��X���)4��[�alOk������ ��Wr���Ru�\P/�zQ�/���I%|�q@(ZW��zf���}4��ﵹ�7i�UD�Or4?ACt)�����y4��gow��WZM6R �*�s���}��jA ����!ۤ1��v�$�4PB!%�����ޡc͠�ؘ�w/��C0%s��Ӈ�ϯR�б �<ʦ����3�:�5�I� ~�>c��g$RC\�WOz�<�f������*��@��2����X_t|y�Gn)�'�.���cc���1>oX7[9a�x���ߣ�zƺ��A��7�tޱd��u�-��r��2c�y?�����{x����b�ɰ����|�b�aקּ�Tr,�.v\#�6B[Ή�4k(g��e۲�i`:�`�#ŕ���L�wd���³W2�6x�uW�j�d��l4 ��c<x}gu�/�C�w]�z����k9�����>����� H �S�����9l���zi��W_o�6ק�"N���I��s�M�� + �t���$GRq�"�} EQE;ي�M�u������쭨TX6Db�����C? +Tp���ͫ ^�mMli�@"5�-��D^�oׄ�-*�:˻��QһZC^�^2�ˆ�?QjgD2|���_Wx1�/�!JS��#�(�L��w��E�1�%ʻa,�mՆ���LK�i5�����K�5¶m(�����%2�@ٖ�є�9��B����r��V��_��zC�DÞ(���ta{�k��c���D(y��;o2Fv�)1��2�Z婿�T��o��1�G��eJ��?v��LiI(�7D*��m-��l�^�-+�b%$ez�̲�!JAo�lw��5%|�D�ih9��c�U�W��hN�o晙X��@:PN/�P�ǺACˋ��$5��s��(�^숸}�{-��))Y��yl��{�b\��T�C5��-8��ݑ:��iY�`��;����/���Xf#$��� �}���y��{!Zں�Lv�3���T���H����l4d��Y�����c#���OP]֐�z�Q�js6�ڸZ| �3���F����r�t��+^Y� C ��oWp��������PC� C`��S{�q,J߬4 � �>YL���,k\�VC7����p[42uԚ!��=staOO���P/���G�L�Bpi��mK?��9$���%G�>����uQ,��|5L� �3��K�0lY۷o,�z�-�H\�8Pk|'䴫�0���:b�U��� RU�嘞�<�8�BO��D�� m��Z]�yK�vE `m�LЋ�]c��̭}t�!rߧ�y��h�����]i �j��U6y����9�s!�Py2��� �G#1��Û0�fy9P;'&%F��B�+����x�����U���ױ��>2�;�� �?l�fgbn�[���I$����Y��;�����D�&��t|�K +oz�Ϙ����y'H���!kOV �B��(P��<�M�Q��`v�ZC��`n{0zʤ�M�?$C�������&0�^�~`��/����iT��&�� v� �h���]Y8+�x���E=vŁ+{v ����0�opI�)�?J�a��wG[k�X�?=��������Q����t �n� �}�N��^�M7V�����3�������)�œAo�@���s�]���&���VTjQ�rDB��8^��]͌I+�G�8N���؃�]�7������D��8d*D��������r�M�28�O���#�Y!�p� _�Ϸ�mM�� �ڋ�.�”��=��C��XN�=�-��IE�c���Z���:b8���Yl"���PxN�l��m�&)����l����*0hCPw΁@��YC^����6�1DG(�,�־��ˏ����: mPa����]T��j��б!0��>�i�%�h�0�y�u�мI��D�^�y��"������{���DQ��yk�u���%�m��� u� +i)?���bҎ=�V��}0]K^o1F�,�O[��_Z���l��q��g��YX�刎O��������=���R��0�a����z/�<6ȳ�4�ɮL �����uy��2���e@����H���w ��<�X6��������XKo�6��W������w��$N�h�H�&��]�4���H���E�{A��(Y�c'�.��y|���G��DB�# +m�̽y��a���to{p�P ��I������ķ����@N{.䳢�� +�Tq�3F�TF�QO���~��c��9ц�a� +�t�p"�B2l��zH �-ڌScI��"�������K��$��1�*�����F�|!TJ | �!���9����ŧ� +*��I��%�S]����$`�Ǵ�T���w�NRԒD����`��j�T� +��?��s��Q�rs�E�~���%�us���U��xd͆Hd%I�fͽ����^Entk_u�P"�vL���Dj�҉ΤT�5�P�)(0d��z#b��9ܢ���P���\�#Q5T�dw���� �RVQldsF#X)�N\R�A� ��{[�?��i�����a��j�>縹l�`r�A��5��ח�V8�v��!5��!��*�,En�й�# q|.�!������'�b����2ju��Yb:c� +\��4T�Wu�����gTa�G�)����}��a��X��E*��+v�&S���������4��^Q4ܾ�Uu�� 8.�4|*�ɱ��o0깏B����gT���\2���`4���>ƆE���Fc}����� �6��׉;���T(�pm���:M6������ +�:�vj�Ͳr�7 `��[nW�ި� G��:2��0�.��-�������������������o�~k��������9�:6��f���f�{�A+T�#�H�*��m3&*wϛ6����^ u���J���y�&�9z�ZI� � �����^�~�lv�B�|lj����i��:g��;k�v( +� �u�KKA ���+�"��]�ˊ *���dv�=馓q�ˬ/X�ԗJQ���4U�1���� +�q�; �{{x���Jb��Pu�w�E~�!���6�,��*�αwq^�q�H���aJU�l�34|��{^���������t�sI� ^p]����*>� m�z����w��\��RB� :�I"�1D�\{rɺ�����"���]/f����h�i�;r��Ј}��k�>6fy��s������P���&! �X� %i��j�Y}���a&!�Df�'�YN���"�Wgm�_H_�� ���>]�AKA ���+��J�xn�Z�XP�(Hv6� Ng�$k-���V�DŽ�%�ovQCE�>��Do/���8��h�&��x +Q��Ĉ�Jb(ɪ<�Q���8�ثR�_��ȏp)��H��XL1#�/ƪɆw�ؐ���7؈w�ј�FF� �9 J=[����f! �X�+%i��z�Y}���cf!�Df;��$q��H=�8�6��"H_���_�� �$q4g�-��t����u�OKA ���)�QEϭ��XP�(H:��g3�$k��V�P��KozZ���c��;�U�?�{a�1�v'�p/`��ZI 1���⾣:�O��Ҳ�A�����ʲs��]�We\$��n�RU>[l惆O��� 2R\��S�g�+�K�m���'�-vP�i��ի,��~�]� +���oБNY�!��ړK�}��d�W���w3���=��S�6�#NJ ���/n���cc��17���aP�� +E��o�`���R��.���go��fBLd�f=I�݂ߜ��퐾$%ß�>�u�OkA ���)�1 !�g�m��PC[ +io�"�j�"��A�� �߽�'`�QO�'=��c:΅�O����G# ���u���Tz��Hس��=�l��I>ŵ)㦐>��cI�|�9�8�=o�CH�'6,� \��qm�����H�G�3 +��j�l���/��!F?���t��dVg���F +�z�V���(���>�oW_�W�}1P`G�N��/sc^'ˌ\;~i�2)��2��Hir�Z�Hwm�id��S�6�Y�� ��V^w�`���f�� +ϒ�M�����e��JA E��w�"��ߎ(���$S���թ��v�ߥGGa\&��\���t �D�7̫D��†}�mN��V�:1��b(T��mG�2?^�J��;K�4��*ϝc#n�*�$��puÔ���|5�4|��{��������jqT�¹$^O�>����*>� m�z����O��\��RB�-:�I"�1D�\{rɺ�����*�X�.Og7����҆w�X����� �|4fy��s�?vv�R�V(�I�1�u�y���%&!�DfXqg9=���7gm�_җ�#k�[~����RMO�@��W�D�^HShP�BU�*B��8u�����^�qH>��3�ͼ}o���"B��ƞ(��;}�(p��q2�'Ї_ ��H V9�, _��K�)Gѣ�Y�OL�B�gS�����Y� {�.��GNp� #J��w���Xօi,"��px��,��;�ʓ�P0>�2-* ,�����B^9�Eh�vd� ��z1SF��wc�0�%�|��^:�Uޛ�ۙo�����e�_KA���Sԣ�(y�K⟜(h1�雭ug{��ޜ���^<�c��.j��c���?�K��#>.��Q�~ �ЧL$C��(=� �n��;����$`�^������8��8oJ\d�'67,�)�ֻ���c�s-�Iלذ��pV��R3�� ��QT��I��(D;Ģ��z����Ui��觜ߊ�tN�jDҾ�Q<=F�#~%n��ۛ������]�ql��%���6������E"������V%r�E��'�4r�Y��b3�/%N#��V�7|vjg��ƚ9; ����O� e�]KA E��W�G����MEAE�GA�٬�� ��U��.[�B}L87���Q� +��*o�W���� �ߞ��N�:1��b(T��mG�2?^�J��{+�,��*ϝc+n�*�4��puÌ���b=�5|��{^�������zq\�¹$�O�>����*>� m�z����O��\��RB�-:�I"�1D�\{rɺ�����*�\�.��7����ʆw�X����� ��|4fy��s�?v&A�g+y��4��w���<�\��b"3��y���6�/ ��7k�[~���]�QKBA���8�)b���%F�E`�B��sݥ�����T���4��9|3s����eX6� +_�o�C����鍪�~�>ޝ�>0� SQ�o��<�^(��E��4�}���2=<��x ?��`L%�d}����X�D=E֩4�>�r`Ɨ��ao1��^������:RlI`���b��W�Ij�a�d�h纊԰d2|�oTU&�N�"��2��]�N9Z���&n8��~W?�/u�OK$A ���)�QE�=ϸ:ʈ�"�Q�LUz:lu���e����<�嗗Ǜ�׾"q�����I�'�l8Ï�Y8= +8�C/�N2C ���t�멭��oR���$`�^���d�;�!M���77̩)_�?��?�q�k2R��� s�.j_�����%��@�{�� +҄Xԛ�G/�޳]��ݘ3�gЉ�Y�!ڕ6�K�c��d�g������jy{���vmxO�-���/N؊��1+c��X��s��J�����0c�ϔ%-�fX}��Naf!�Lf�-ˁ$/Rjl���8k�o C��3t��=;��7�pm���������N1���)N��M�z�ʏ��ԢJT�B�&�I�±]{6 ���7H����̜�g�g_}�Q�28����<{��Kg�����Uꈙ6 �)� ?K +���Y=�(�����A�KA�:� �qe�>r��3 +�/���i��=��h�����M�—��7�/��0_��{��jIR�-�������[� %cV���F+����̅�v�o�"c��n���^����iT���$�)��q��j-%$%]C���t���GO���fY�U�>�|� Q�p� ���̆Y� ňK�V �-3�w��XR@��d��>�% �(q �u���^ &�l�P)���F�iG�u�.xr�6V����M[�V������6��7�x�X����5�T�n��p���?�|?�5�S�̇�1��﹵Q�ؽ c'��N[~z����gΏ��C}x�k;�OK��jX�qk��f�b�'�>m򝶴�G������\��s�v:�ĩ rP��7J��#��18�qR��D[� �JJO�+�z0{R1֭�q,2OJ�c�(y-u���e�_r��V��5zϱ\j�X�Ғ�i�}n��5Lꀬ��<a0�b�Iut�=����iŏu~�]w}��L�Y�Y*����'B������Y������������W�˓u{�龇�({��5ʃ��ɞ���e��FڃY`e7Ō$S �*�v��~xe?�kXyo�$ivz6i~����lp�w�R�߂�-<�Z2�sB��O�!f��{;>D�H_�?}�Oo1���s� �5K(�*R[UJoU�wvת׶Ƴ���w��h��{����=���}��-2�����C^Ex�w�L�& +&�2 +c L��,� �Z!o����LAQ��ڵ/l�J`�ǰbG�h��$�sdG��p��i1�g�c�>RC �8�� +䃥K�#qY�s��IR@���N����l<�TEc-�SФ�F����k���%�C��ާ�����SB�Ӑ +�!7���F*�4������Og��j��_�T ־q�{KY{�1��UY2�(}�h�NA'y��l2��P, +ݚ:X��I�̍�骑��"�c�����R~�8E�^���h��=g�K��lIv�m#�V�]��^��F�y ����?���p�&�M�w�{��i�< ڬռ�!OweK&i؝����_#�$�.�������Ƽ'�g8���I(���u�q���J�a�z���wm���Ou��d��~��Oo1���)��B ���$(U�ڪRzK#d���������w��,�@J�fϛ�{~;��+j�� l�,d�1��w�٠�A~T&@i,� � ��W����WE�� 74ڙ�[6�J ��eB�����%�H1�t�?�8��.U�>cD�Q�_L}��y���;�U��N���$)(*@;6�(�C��c� +��ւ>Mjk4R@0T:��G=�U@X�4{_��o� մ!�ب� ;/,`c�I�Y#hW`�� #Uc�J�i�,�a.�J�SI�(�]�$t�0��U�}S{�5����Ȑ�n�T�'�O[�.f�Zlx{5�B��@�V��>>%Zx6k%W��av�7$gR�i�1�w��ǥ5��  �(G-�i܃W� ����q�N`|�'+�"�a���)��P�w>�ڙ�~��h��i8K��?F�Lg�����o����� ���"GGf$I�]I�ŵ�O�O���B��������Ao1�����C�F\IZB�"*A�T� )r��Y gl�lj*���v{Yy����o�:u �`�FY�;��S�����lb0���g�>|F���->vV���}KY� N�uLO�w�b��x#LX�_I4ca�i��� �N�H[��[�;*$X�a�L]��] +W$��e���Z����EV�ۢQr��6 +�#�%�3hU�3�seo�G�"��p�t<�{��{x��V�6�����h|��E �^;hm,�"��bC};3�vO9YG��͍)�<�L�}��?7��3V��@�:�Sdb�7��˃T �ՠ:N�V W5un~���h��Ӹl�wh �� 69���>��>=� 㞭~W���UX�7�ž�Y�g��~�;ˎ���i4~5��g���_��g��W��)����{6���A��0���s� vQ� KY�VEj�J�E�ę$V�O@���^9 t�,U}���{o�^������A�Q�J~��S�x7�'�I�V��6:�Gp9|-����g�:� w 4ڍ�'�E)0RcxdK�6hK���Uz��e���3�D���T�"�V��伡�pM\ThmO[[-Q +h3P� +�ǡ���1HI��ƀ��j��@�m�B��N��@p�tl�>m7O_��"����(p���ep�R�ĉW�"P.�n:��bE������IR����`jh�l�B����(� +��8���AE���r���ð~�Խ$�^Z�VLR�}��N1{3tAү��= 3�^���ײ�����|��V����x�OrAr~�ȿ�GΪ����Ե�.a^�߅�Oo�@���s���Z���R��*��4B���WYϮfg���w�ց��������{��㏾�P���������=����(�2����X�`x�n ?J�K��"�� 7�څ�{6�R����� an="K��b�Y~<�8���*�Q_0"�8_�|��y�M�yS)��6��$EhG�&��8�}v R"����_�&�5) Z;��G}�U@���s_����w� U�!�ة� �^X��H � .�FЮ�C;ÌT��+���FY�R��8էu$���v��d���L��V L�-VH^�Ɔ�?�TX,��d���U!@���Y��Ǒ'��'�ݷ�[����$��V �Uސ�Q��aHZC�S�;�w���5�_��v�����eN�wK��*��`������`�AiZ�NwT�N�d�*:��u�h[���h���9�s�?�D�n���t�mx9�#�U����.rtdF��jw܊z�p|�hI�[ޮ���y����Ak�@���mܘ^k;ubRjHK!=�x5���w��Y�P�ߋd)N���&��o�yZ|�uD�Ƒ�(�X�[}������M +L� �u �I�·�d~|!o+Nz]�e�!>��׊��V<�Α�ɢ ϫ]�~]�������<>sf�"�V����!xDz?��6{� +�%L�*v�5H��>�֌*;�"�����a}�@j��":��8Z>��6�����MTۆ֤8QBi�y�8Y��Mc)d1 J�ڙ��"�7/ +�(�K��� +~0�L�:��6kd�cӞOf-�IW�i^�y��r�w�wx�;g ����ۭ >�d��7��Qӗ�>m�Y7�USȻ��������Us��w�=�Y)����r1�,~ ��n�&���_����7m��n�0��z�=� n�^#;��*��(j#�ƚZYDi�X�r���^P�b�7-?�̎��lc�"��)u���+�a���fy2$0�e#�RHك��W�\��WԲ&����gc?Xn��`ƚ�I��C���5M��������5:/Q� +�0v���6��Ut >o�������=�u��]���0���J�8�����#��6�E/��U�����������碈R]�A;tPIw� +v�7�cc��TԷ3J4n�Yt�_�$�EX;/�|Y�^�����ٲX��r�w@���bO�j��$ +��b�R��`4��E��Lo�w[�-z�;�l��a��8K�V] §���������<�v��s.�*Y�=ۋ_�]���<^�oG@������ٚ�֟T?��K��n�<�Xe�Y]��s�i���J��jT�.ˉ�7lv�i�n�g�_�dc�������?�S�o�0�_qU�uPF��Vi�&��^&!ǹ��۳�04��>�%alyI��}ٞ|����Sώ$/yk��5�덓Q?�><�� �@�p ��/�p���g��@�W Խsc��V%C*{p�4­�;:�0N�,;�_�8�#f�3 1���?,�li�X���[t�Jh�� �8���9H��Q�8��vgp�P�@��nE�G ]W &�` +��&��s��狇�E����R0l����� s���11o����Og�hQ��Bb3�q�\}F����{����h�N��R �a�Q�`��=N�mQ����ꆐ)�P-#0,��n �Ӧ�� +�m���c|.� �o0��놵�@ 4t؉#/ݦV� /i�}+�W�98ݡ�K+�������"�3��{3��f���I��L�+�Ϫ��_۠hB$EvbH Z'���'ǡ!�� +��.�G ��0�c�&�F��IL1w´� �˚�ϱ��S ��:�u(,r�)�H��[���1�h;V�p\�'6 �Β�1��$���t1¿-�<�7Iv�B�s�HN �@��������ղ��mX� ���>{Q�C�V6��O��v��iv�N�7�*�*ڼ�i��]��.���������d��� J��R���g4g�s�����t�˨X�}b5鼍���?�I9ge���������-��z�h�vH���N��|�~u�Oo1���)ޡ�$*���?�FETTQ� )r����mfƉ*�$%�����{~Wos�ђNh�&�maO��x=�l��|�Y�q �";1��������ܑ�E�����$�� #?�;�����S\9�4[��-ݜ㑖N�]�*$��}a��L):oIVk�["[E�b �� /�%ѝ��I`=�+!���t`OQ �$kg��9r �� �v��x?���xWG iX� [�hY�hQ�-[��i*� >��Kg�D�&���a~�MS����D9��>OQMNJ��T������YXhM���������� f'8�Z�U" o��6't�,{t%� ? ���x����J]g5�W7G �O�ז��.VdG�����.dE�M�����]��NA �����x!I��hb�l�i\f�NWB��n�Ƕ_�?��&���}Cʽb*��m��� +��� x RPKÐ�LjH5��2�=R���]8�y�{�M0�|�����LH#Oק����!V��bB�ܲbRN�i�Sn���n���F�� +>ESY�����%�F�6 �Ўn�s, �u�-��8Dn� +�Kxw�{X�O�Ew�`�vTPI���vb�+�U��⣝�����󹿱s���f/�ي�����_}�Mo�0 ���<��i�]�4KtX�m�ݲ``d�&KE'���A��:�o_>|Ly�ї2R�AX+�-{O��0M&�F��rmt�,�r�^"/ݯ�huNA�h� ����R`���Ȗ�ɠ�C,�Ȗ���]F�1���h��jb����ܗ��7t|".*��,[[-1 +h3P� +�M-�C���1HI��ƀ:�ƴъl �6w\�hg�� a �j�5}_���o/��lCJ�a�L�ve��R�čW�"P.�n;��bE������IR������Pڼ.���cQ0(�q^[A�x�$���B�7(t�+o�"+�0�V�?�o���̢�2�N�jG�`G��%�%���[��X���1{�o�����i������0�=�I���Ng����ĵ�����N��&��l��̙�f{��'W� +�^8 ���̸� u�+^'���V#O[����+��Ђ�pi}���ˡ�vﷴBG���?��Mo1���+���|��*R[UMoi��w��k��1U��7�,%�����3~����Q�� LZ��`�ۃ��ɠ�J +P�A�^��+�G�x�~S� + r�A��:�aZ�-݆Ol����`���d��_�8��-.TR�`D�a�=L|��y���k�e��=�FK���l�YaZDq��>;)�h 藠ImH� d Ǖr� ޠ ++�u��u6��~{�P�6�Tk ���9�IJ����"k�r�n��YUa�J���Y�L��8Է"Z���v�&I�Ӂ�`��Q� Vh%�� �J�'���ue�L�Q!@�u�����,��Ԟ+�M��}⦪gZ)A���jd��d�H�]�z����}\үS���� ��N��wmo�s�~�7n�a�/( lh�u���4�k�y��V��c���΀V�P�Y8gF�l�p���o��|��$����L�rtdF+��X��̀w>�g�,����{y����t_��m�O��0���s�DtQ���nY�V�{��X5v�lCW߽r��[^~��7|��I-�:γ�>��59 �%�4A�E�J� ʡ�aK��O���¨���Oа϶~g��<:��1“�7�w64Z�� z�aN+��(c���Q]�dkM���z#��b�Q>����ƳZoٵ�^,�W�2h y +i�$GP���^Y�C�I8�VѮ�7�>O~�'Q�i�W�c' +�^T`�|s6�$H[P�N?1bC����˒$���2�-&���x6�:^L���,k~��Ș-�{�|+8K��sX����i��V0b*���q\�� +O� ���0 ++��Y6ϛ�ﴗ#�m�㹋�}z$�Ӓ�Խ ��Nõ�\`�ak�������N���l��� �R�4=�)F�b�s��m̴d=�#��-򱉓��\.���U�EU�s~�f�ޭ7�b8���e��4Aahw+kvB�������Ok1���)ޡ;� ��n'li� ��� +aV;��JB3�J�{����%������[L6�g�)�B4;���X����1� �3� QV�_-������X���N{�cv�U,������*XS|����=����;u� g�e�H6qL����ljB8Ж�JA���A���,3ۧ���1�a^@��;�A. 1O�.�$�$����;ߗ�����Q�5ԒbK���s��:�к�Ē �Ğ�uΚ@K"Ç����x������㪛��3�3��I1�LH���P��^��w�i � �-;/�E�*&�í0�.{�Mb������|���9i�Ւg�S#�L��+����#Y�=� �8و�s�P�$-�ѽ��E`�0���FG:�@���C���T�5�WŸD���o�ŏ��(�Mâ7l��O�{�zl�Eؘ��&�JO�t.�5i��N�:�W�m�Xd�iMl�3�� �����j[�04�MC!�I���KȚ0�6o�ɑ������+��N�@uG��n�W���}��jA���uXk�C��b; +1$&`a4[�2�3L�F��V��C�4�|U�]���e4����J�������x_ϫ�� +�x邢 ���!��޹�~|sZ��T8����%l;����T�XF'�XL�pE����7 ��̍S N�� :5r��r�9�d��N�$؈�I��J� �����9XG�C��EG:OQ"H�J�,$�B�tJ��t_W���huH�:g�9E�8� v�:ؘ���x§�o��V�zjv���ͫ�G��� M��#{����(&n��� �<�A��2��U�s�D�����*�ȣ�h4V� E ������ލ�^ߍc��BY�\�م�.ꓢ����k���Ak1����P�҄^k7Mb\hC!%�BkgwE�#�� %��ȱ[0�R�4|o��-��1�c��L�o��)��������_Ǡ�CdE�bH=��Tnӷ�$�g�s��J���a4���E7��S,�_m��/�p�R $�ȕ �z\�1sʑ��.�D"Gl�` I��J�TKE�nR������G��1xe�S��B�3�Ȥ����]���j}w�n�vm�H�-)��/���6�k�i��3|�x�΅�X3y>�oᜏ��U�r[G�X ��X:Ł<�9�u�G_�7i lw4�l��I�=�^a�E�ٽ�خm��@���ٝ�������.��߉Ǘ��+��7e��n1 ��z�9�� ���n�f�"ڢ@z �Z�W��H�nP�݋����I�>G��+CAG!y����`��VH�7��]_8\��}L��(^ �����S~��9��v�e�\�$��,4�"LxH���b��~u�_ut{�gZy���TI��C� �rIt +>��G�|�V�6���!d6��jYt��k�@�kJG��b VB�>��-f�DI��'�f;���]�x^NR�4l���WtQw���&���\%B�h�εc?��4��s!yU�y,��m�h$�6�Da2�kĝ����RW)��w�PE�m�|:S�OL%dUL�3jv���0���횬� ��5���ݽ�����j�@��z��P�҄^k'Ml\hC!!�B�F����;�kJ޽�#��P����|���O� ��8�\&���O�N���j^��8�}g�6!PT��;�7��7�pҳ{v��.ڶS���u�ґ�� ��W��~V��)�xCI- ����4�B�����cۓ�;�Ռ�����n�1�n�}�v�fp� �ig KbXi|�I��Sǔ������7����:�ڧ�)��P����kl�vМX�C4 �k�9/�zN� �7/ +�(%��(YIk�=��+K�0�S�Oa�8k� b�3Z�[깬>"+I;r�D�! +�e���-뵎��.gYnV�� O���?pL���V<>7���~�����>�c�*c�y�q��V�� �<@���Zu�����w�xj�U���T�}��j1������ݐ&��ib��@ +�Pd��%���lS��e׻.1Iu��o4�~Ϳ$�P� F��*��/�%���Oլ�8-p��g4>|F2�� �;#���a�P����i'~���p#LXÿI4cn��z=��k�:�#�MVoߨ#��gQ��W�˱K���1���MK9K��͊���W�Zb=+q�1�S�O�[o�tl{ilHo:uQF,��%^U�1���+�%��0������ׇ���6�K�C-ċ݃i�<1C�Iu��ٰ})���g?N�}_W��<���ݤ6~�[n/�_�T�n�0 }�WpX��iz��v]� +�v�Z`i(2 U$���C�}��ĩӋ�,^)����A�R ��3)�^9�p +��^t܉�w��)��<8A 6�߹�K{-����Qe����Y����A���< ��� �����(ų.��TxV��O,���׆s�;�Nc;�i6ƴb �8��0)Hk�Դ`K����p��Z�� �ZI4A���\��� N�� ��2��r0���R ��RxH��ja +K�9pP�ۂ$��)��GF��;!��_/� +�����P� ������ᒑ[�n� 2w+��փ\�V��02t�k��m�<�Zx߲{G�p֋��N��N �C5w)���W�� ۳М�z&!Jl�]�5V�/�������%�h0�ӑZF�36-Ws4�E�Ӕ�hg� +e�XWL��M�����3��-��*�+����&zx���S8鵝j�,q��qR�=U>�^�ظ�m�k���W1��q�V��S>8xƤ 򀫀tl �$�Fzg!�J��Z�.����k{9����Ke���mF�"�(��~�J�z�l����/i�Zǭj�njmo����Qf �̡�&�J���pT���xF�ͶZiLJ�9�%\���D>❬p��D��%>�R�.�,��'��m~4U I�����j�Z3���i�|��TK��]�MkA ���+�C!v���n>qi ��� +E����� #M�P���8v�M���.��ѱ�Tx�V�����Y��� w~�p��1(�A��R�#����;I�Y��a�ޥ�V�0f~��"��H�)�T��������<�� �q傥��y̜r�C��0��[%XCA��'���Rѝ��T`#��1��mt �EA�T&���92)�%�f��p�z|Z�S�4l$Æ]Џ_�al���4��>u�K�� M��<�p�GR���V�'�K��3��_��c�������&��/�ld�q� +[-�O���r`����O��gG��h��.��w�e�OO#1 �������XWZX���e�G$�f<�hS'��-��W�Bŧ��g�M~�������HMb�W{/���y3vg��x��ŋ!wx���ϱ#�S�5;��]�7�B�_„���/�)&^����iKW'x��W��qG��5�K_(�D�� �b�����[��&q^-�nw��� ]M a��@����˲�3��$�J�i�~��~:��4��iX� +�h�nfQ�U�6$��J ���6�3�~IZ|����΅�U1{�Y�%�MsJ�U@oF�*�+J���U�tC!�Qs��o�%dUL�/�h/u4���Ղl�1�Q�k����t���MkA ���+�C!vHz��|��RH����ݡ��A�� %���k'`�s��G�û�,}A�!���\c�G.l���Euv\���hhcbDC!u�_{һ��3Il���–]����0ǵ +�&��duÒT����O�8��dI��GV,m\��p.���n �v�� +�!�k�G�j;�۬�ю)!��Nt���Qڬy�r�����+�f;w�ZyXO��mxO� �hoq�M�>5fy����];g���V(�a�� +�̰~r�Y։OAcؓ��we�S hG �4:�۬��9&%�v���}T��I��E�~�;��F��Y��-�R�k�*�<։��Hx���R���Kk[1���gQ����M������vU(cݹ���#1�5%��ȱS0tS�4|s�q4��nj�}$�I1 �~�&s�{�������1�PЇ��Ԑz|Ioӏ�$��bg[�&卆a4L�W*��H��jsR����~��� �yI� >qeż��y̜r�C�uX��[%XCA��'1 �jI���cR���k�庶���F�>�,$9A�L��x�ݻ��Y|�_��m6�aM](/oq�u��+��g���s'����a3�|�Rp�b���b1�/c� +��~��@��<�*�)c`{�d�Lߡɰ��Q���7���b`����w��'G�㏦��³�W�7��_5�Mv�VMo�6��WLYH +�8�ڱ�&�EH�b�{(`���"J�*9Jbt�� J�-Ɏ�]�'k4��oFs�K�� ��`d�NsZha�8�p���� +� ,���Ș����gJ�h�,���F+#�A�c��(�k���h��%3 +���Y��<��YL�oX��K����@]H�;^�Y�L��o�9W`*��(I�}�(CHK)���:o)8*� T�M�Hh5�B"�O����/7���[��b�2F��,$�ֵ0�gA�c���p��� �r����o�E����'���í���cf�3[H��Rq�.��́�/�� Td{;��U��K.�\"3s4F�~��p���/-΅"4��ݎ�0BQ:.��А�P�E��������f0�0#*�h8,2f΄��r��Ë��pT�Ó�zb�ͭ��*�O���{w_�#�[\�����cǜ��x�l�)DG��h�\܏ܭ���\�/ � ���g/ +.���~� R]�$�v"����5X�5H�Q`Q���;�Cu�n�-=E;j�����+4|�䲶�'��0��=�Ȕ�:�=G�xst2��m85�����f�V���t*5K���E-�=����m����:v��3�D�y��3�:�����eK���\��*qj�M�O��d���,j٪��S�K�9�{�� �V�|^� Sr�� _��u�F���- �hc��A>� +lj�ji��b[�kK��� e�����L-8����j�Q�H<�׫�1 +�� ���.F�����Bً<���_�Rls�3�Fُ;��qc̘�.U";Z�m\!U������-]��CT���K7�pQ'�өD�� &� \���6��e7c��C [c|��~���x[];�'-�v���w�#�ϴfMꌫ�׍iΤ�Z ��� !|���z-���ʣu?3UÙ&`�Wh�h���y���d7_L���z �,�m���#�e�I:�yt��=Z�Su�B���9��>Zȅ�B-����&��k���oO�0���S�I�?��ut������\��Xs�ȾP���>9u�4�&�w���y�s{�!Os��Kf0�d�{��hag�8v��]*,$B" 93:��)3���-S"AK���+�o�X�!���(��d�'�pΌ‹U��8��W̒` +>c��mup��9�\b;p�f�1�Z���B���VdĪ m���I�!)�^u�RpTA�D���Ъ�Dfnʼ���b9w�J�2� � ���6�R G���p�c�t��bڜql�Aa>~��K�PѸ�/t�7����Pܹ��(��Z� +�t���J +�`X��7�%L���v4�)3����f>x68팃2{����3 [�#��F<0B8��nS�vy_J��S�(ReEަ[�Z�o`�(�����ф܁�5�H���c��M�Zñ���o�H�£}��4e�.�R��ܢ�� (ܴi�9��L�����[�ZN,(M`�@+�(��N����� W���3�`DG{'Ѹ�z�n�¨��M��k}��j�Jkـ���3-z�p\�BƳ�#P�����zc2�ؘ�Ï��v��ckWf� -�d4*_Yo_�?�Yx���V"BYb��Nn�s�:�z� � +��\��4��i�� ��P��=Ӂ���Ђ�,ߟJTkJa2�������]���^}q%������ð{�G�e��)� ���j1������uHz��4�qi� ��� +E�ήD�#���mJ޽�z퀛ҹ��f�O3��\BE6�L�h�V�.��>Lg��Y�3<:/�} xA2Yk�;�o��}M�v�.��)J;�Mf�"�EYs���ׇ����s<�ڈz��Je���p�\���� �Mk�O؎��( W��5�u�1���%f�#�]�Gў� �ssk�G>G +d���i3�}�]��V����bc��}U�xu��b�l 6V4^�`Ӓ$c��~������ޥU��XA[%������ԭ���;��.�'��#�ӏ腸Ѿ2i��z��W 鍎�O&tTN��Ó�l�y)�LqFVۃ��I��?s����*'����nHOV��������6o������P&^��LK�ّ=:�]�Oo1�����ݪ��J�R��"�#r����wl<����M�2�����~�>�#m�F��?t�Ip���\����A"!�-��c�m�O�,��D/ v�<�m k�h\�υ �h�'Lma�]�ˎn.�D++,�*L�и�>SʑN���`�O��AG�;��Zªj*���%�'�5F��Б�� !p��`5$�@�d��h���z?_<>-�S;7�[�� +� ooQ�MP�T�#���ޝ+�v ��ѩc\�"�F�j($�H��~+q'8�����bp�+�Q3֤˻�iڏX��������ys��n����5�܇�Ͷ�v��,�|��v�K_ͫ� U�]KBA���W��*�t��u�"�b�3�]Zw��9~��8������<�3��.�f�pO�x�z�,�7�� xw^�����LE�,�����DT)��t����D�c���ES*�g��<��n�oH�S� �\0�K0�.sʁ;k���M���Y�� +�1�6�{Go9 +��&�-�Oq�����?��.���jޝ:SG�= j/.����]yIm� ��K屉�e�dKGe�֧�L�i�:ZW���9r�P��|Љ16����I��s�A9ւk����uT�O�0~�_q�:�Teh�[�6!��$d�Ks�k{�KM���$uC�����w?}���J�F�Yiy��|���g'� �p_CE�� +��ۚ6�!��p�%Ъ�u��E�b���A�T�x���?�x1�%>*R�a�θ?���G� F ([�vV=6�w��.��Uc �A=� i��@�ra�����7�aC����,�~.���60����b(��ZX�I �g���]ه|�Y�F�J#��*ܸ_mF�Y�p��� �Y��Y��b��[{�n���Ⳡ-n,�2�?���L�p�Qa`-1lHc���>�F �D��ϳ�Я�yC�x��}�hH�\x�β�FK����p��Ut ~go`��cw�.�H{���������i��X�;dg6��OSN�K4���p��"�.�mVT@+��i6�p�x;�ۙ-��I��QB��@G�6nح�~q�z�����̋S�8*��&�� +�+��ŗho+�B�6/���>0/����r��!?��EE�\([R�9��D�0�d0NV^�&�g�5��*�ߥ��Ҷ��λ�ˏ�ނ� ��f��� �K dWyQc�q�7�l�a��t7��*{m��fo�4�kZ���t{1�K�ʬ���> 3�p�5U��P�`�_<�����KQ�k��QF�Ђw�'���84XR���3ֽI�m·�Jػ�(f ØA�`��U�=L۱�������uRMoA ���)��W ��)j#�CHh�����Όl/U���,�M��?���<y��U�m2Z�r��:�d�M� /�aȌE0 A����97;�'P!f>�ls����r_�r�H�0V�p���~��,q�X�r�K$s��<�#�KA{'d6�x���GO 9BVZ ����ht�`\�Pb��A��agp_�=-f��ylU �\ �Cj�8 S��A�x�%i��Z� q�@J#<�~Umd�$%7�Gcqy`�b��j�t�D3�%���f���f�(�Kg�ef �GХ Ǣ��N�M�.Lw�����L �S���{z5n{A�/7�h�J���`��ޱP��]�Y��b���F��V[�U��V��ϸ���@'�³sF�(ڢ4��V� Z�a�\wV�Nf���T V�N��K���w䯉�b�5�[�z�'�Ӏ��~ �}�[���3iw�P���$wk��$^{�N����ww�z�[��TKo7�ﯘ*�+H1z���]�N\����Asg�lV�ffV�P���>�zH��� ��}���ߪ�� u�c2Z�eW!�If��8�1|* CnJ�P)p9<f�o"/�ڑY�N��,�沈����J�����&�tK|Q,FYx�5\q'��� +]U��f��2/�8�6��#�!��tݿ.�F����J���JT��1� v������U&��*��p 3�)@2pE�J> �ۯ+/n��v�J��Ъ^��#m��Δ�ܱ�:}��_Hl�����,���E�4�"]*fX�u�i��1���r<��"X��ްxp^W��(A�^7�U�R�G���C�ZK<�44Mڐ~�<��t���Q��5�l,�pM(5Y��_jd�̳��Ԫ/�&��-��L�9�X��n)�$ɯ���n�h�s����l/��0�[��qҒ�W��. n��P<C��i�F��3��>����F��vG���*��f��gX����w�� + ��#;��%��4����i��=�8��S��45��u��ڜz���L�dG�_A+�?ӷ��d��2��#�h�m�D�ܵT_��:���49�?�<���հp���IAn���ԉO��+�XV�Mn0k��� �C�B���i��T�R��U�$�0�l�;���������=�)���$��YA+�&�B�mH��LS��[�;R+OD|њy4y+<���C�8 �@��{�nuk?�?���{�'�c{����c`�����Bן���G_��}Q�n�0��+搃l�1z�u�H�� ���M�"�I�+�A�/(K��$卻3��\ᐑ��S�+�[~q�F�d6N0�c�r� *� ϰ9� U�u����ū炑�>{C����M��Z��uF� h'+apKy,B7X�‘u�"�d�ְW�����7��!���<�G�V�L (�[_ +V�L�4�@������͏���T� �؋�L��e�+.�1|���i�.�,1����$���٧��y�H-B�W�E�yc�2�R��T�ဍVO��o���Zx�"�5�v�wÈ� +����5��S:��ڪ�´ʑ�^�� +���NG�)�M:]�T���'��9'9$�����}H�m�Ok1���)�!�Ibz���6���Rz +�Y�l$*KbF�&�|�"�I]�4����iq�lB�Ɠ�D�8���9�^����lZa��)z�N�H2b�{�F���s41=�{�S��GO�KV,H��������%͎������d��A���!�k�E��Q�-���y��w��2\�l(�.�<�2F����׻����m��˖2����Y�a�E.�5b&v�ʳ*І5�a�[������yUO���= >7q���51�� �+�M�X�$h�{��� �$-�)3Όwi^핡�Π�)c��&�2�<9 :٬�r�J���_��B���m�%�ɢ.��{R�G�?�΃��)�KKz�:�!����R��V�n�6��W̡E�Z'E�C(�41���S�b�4�S�3ܵ���^ E�jk��K�<>μy�(nشX�����;����ָ����^W����pkB{��w�UQ�O��_� +`S������  T��z�J{h�6`%FxVcc��W���:�x>Cy*�@m@� �[OC�^N���2�1X�BcBM{��ԬD�LHWEѡ2�O�A:ˠ�@Q�(`| �`E�wؒ_�th!��$�Y�e�]j���/�`�5� E_����Շ�?�m>��ۻ�� +y��;ų��8�e)&�(����^'��cpp���^��2�+ �2��6)CgԖ%�m�� ��U���6�0��.$��ܱ����}g �V����h'·@_��3]w����)Г��][�����A�Z9t�9[�C7�3�R�`.�c�*�,�\٣  + ���`��U4�OU� ���>�8�Z�Y5�c﬿�P�BMc�,�SGFA�90�)ō���{t#�+ڀ ����Y�-��CQ7e)�����^y�L]zQѩ ����R�t�N�6D����G^��u�ō`u[Q���1���Ϝkx�Ӌ���h�o1����@����ȷ +�oϲ� +��t�p�_<|�!`c΀q0���#$[;�TC���TRF϶�Xo~��Gc4$}&����1��g#̋�/L~��V�����@-�c�"��>�:Ġ� ߢCA�>�'�����)\���i���!�ޔ�(�����?��u��f�v�D��T� �^zF�e�Q��S��C����3 �F��ao��-B"� ]3Y|�s��@rN%��;�d��uʻ��&J7��M@M5�o��7��a�vg�w�C���E^���N�TrQ���vv�NOs;sx����Z�1�v���!�_�RJ>!�f���`j�!�TXҡ ��g0U 洃����Ɣ`�s�A�E[Q?y��I͞c������Sck�m����Jx�_���0��\,j�Jz���t����;�&�Ӷ��Z����Y�wߘ������֝����,P�{h�h�Z��sJ%���0*@dG�q�Fl`�SS��X��� �N���U?� xZ����-�t��t~�T�Y�����^^]�s�"��,G�ŋ*W_�Mo�Yj���c|�U3M���Ѳ��mR�n�0��+�� /�^��u�]��GM�-"I�#�A�/(�Qy�̼m��GW:$���Jr�/���}6J��]�.U�Fi� +p�3��R�h����[��նd�2�'o��0��9`,����t4���"�T��8� +3W:�NS�0�5�պf��Q��������<��i�$�@Pfc}%XYӃ�$a�h��=�?��� ƥ`�E@��A� +���>��K���)�01����$,K�����(I�}��|K��d8�n��}��i!#TjH�����FUNS��k�:��&0�v1� ���)oM���b�y�L��Ko���"�Ϛ]�F٦|�z�����c�����^K_k��ُt��w<�q����7fߌ8���a����'�!Y3���*����t��w� �7�y*���m��y��wz�N;^�%n������4kC[_&��/���t�%i7��Nv��0��ʬ�ݹ��V��E�AO[A ���+��8'P�(���*�T9�~�ž�j�G@���)G�����rC�X��y�����]_�.��Y��) 14������J�[���8����+�[���� K�ow��"��9�yG�B�y⎥�ۖk+<A5!j�.�ɵۿ�;����R����"��1��Gr�z�V���"�?��ج�?����gr�ɐ��fq�^<�gxөGF�tD� �F�F����7�xhdB,d�M5�RV:�T�J� O���m~��\���S�����S��M�"q����M�iwNۮ�Q �q�����Oo�@���s�d�L�^�iQڢVm�T� 뱽ʲ����(߽��@�?s����޼��U���C��([�w��$�����$��VY��:+�Bו}��Zb�mY)�L޲'x��������b�}��8�Z��E�&���������9��������|�"(j��t� ��!/����6� �#�K���������U3�]L+TX�@ne�E9��V���j6&仕��K����B��Y��0Iji|y�(��wȌ�a����M��} O�n�ң��8��E�k=*�\`��� � g͋"ӏ�2�wt�椾�W!6?���m_6Ť5���m� nSE.I� R�ZtP�rpOkI�(���S��� J���sR�~�a忶#��?�V�X��k4�S���C�i��6�?"7�9dK�~;�\�(M �Ϙ��c��p�� CC���%]�ZH�+�($��2e �B��f4@�(_ +M�C(�{����xry51�2��hx +B�6�0��W���J�2@D�u���I�*!�tE乸�"2�tRe(��0�:�4��^���_�ߧ�����2�1 �rm}�s��L<�����\%��L�ꏔ��Zi�o?P��r]#�l�3z;qLx�%1���{��[���� +~{������# ��I8�N��3��D*�N��RpΕ&��Ό_�����=�`���4x��D�{����ѰS��"�g�r�6cR;��'��J�c�����6#2B] w!��lH�N�\X�. �L�ϳ�i����<Րt��_�Z�[��tU/�n�-�x���9|�隢;<)A��3*L�u��6nc� KU 6����j��0?�o���^I񠠥6�d���șj �@�t�/��n�� ������G�N%w��$B��{O�*=��Z�xS�;Z����4����t�V2W�i%��*M!5���e3�y$���)*�1���)Lk��@J�$-p�u���LF��jlf���h4�׻�Pg�>J׻�^�V�njn P������, gb��8��U��k���"j���4Ɖ�D�{�5.� �,0P]sE#^ �s�f�I&�0�%����]���R'���iE��D����n�%�K!��"�꽵���(�5o�D�2S�ƴ�M���"?�|x�$�PW�S�*��>���8>ԣ��(�������K�ҳ]�7��5z��b,���3���}�nc�*7&��,�?~4��jsXc�]�Y4Q�t�]%R�V��� +�F��DFY͖�=�p����R��!t)����/%DrYO*�Է��̉k���%� +Y���%�-���u��y2��ۉx��OoL8���R���ϳ�� +��^��%�}v� *y5�B�@0��JYl��f�Ѣkl?mڎɨz����v^����;\ �N��z~��\iIydվ��G@�Ёl�ʔ�k��r9�zG7����Q�_���m�u%vE��2�g���n�^ݎ��w��㐧nv۶�7��۶�ub�sZ�����$��6�͝+�ڢ�3!\s��}���XB�6 C�Gc�����U��7n;������^0me���b���m�y�]�����Xه�"�`�oNY��[Iu�8֦K��]9k���ٕۙȱbY�W\���~W�ջՓf�z��䓈5�Iu�q��=7���*7Cχ/�O'��;�1�v�N�(�O�k|�n? g��?v��VMo�0 ��Wp@��E�nצ��e-V�(��v(P�2 �%M��C�� Kv��K�#������H9���Ov��,>$���8�cxș��qf@mAfp��-����XI��l�[�i�Z |�D�Dm �-��}��rk|&�2"� ���ܴ*W(GgD�@���=�Vj���l������;k�( +��D&uA,�b�#1[�����fuu��rP1� ;b e���)���:�F��"P���O#A +4�P�����XUdE�鎮���X,?3��J���b�~:�yQN��a,�|%���b��X��V�=��"���c��8�����e댔f[b�hu<�&]ר���ȉyס�W�U���:b_�H�E��،�*��ȫr�|Ms����3 +Y)�S�����ڸ2p�dQ�*�B�6�pow���m���hTI��{�\Ý,���CX���~��0*p&X#�r�m�}"���eZ����'g��,��A�̓��Cm����I����I�y�"S����������G��$��C����.� �іZ�[$��i�>����K����6���J�O�p�t�P�)���Gl溾��?4C5�������3�ToR�e u��B�:��q�q+�OL��6�[C��R'�Lj���-�o°��VX��K�g0�sFl�%�a�=݃vNΠ�>��Vi�7'�V�:v.�V��5>�۳u�t�t_nqG�+�K���AO�0���O�CRؽR��V� �H�X �΄X8�eOZ*���rHۤ�Jln���͌s�Õ9I-<������+G��{6J. x.U@�4A8���S�t��QL�[y�Z2R����O-�y����|s>�i<Ĕ�"���&��� ܸґu����ְW���/��%����ܺG�V�L (SX_ V� �4�@X(Z6y�����],�4ƥ`,E@�§�X*.���`k/ �曖/#* +NH�S)���5%I"����ֆG�X��GI"�*��VUDW��T�ဉV�M�#���7 �1m`BL���y�L8 ��Q��4m�Э`?/����Շi� � ��k%w]��Hk�Zr���2{T=�a׸u��Q��4.�l�)\� `O�-����ğ�m�z�k<��wL�$k�4��ª� �X�j%§�!�����U�Y;���i��^� c|���l����9=��fD�@S�ey3vi�+[{�U`�.g�$+�2[�>�N�N���<��\Yx +V/(���X�?ڬ���-o�ث%�+��D�{�r���,����,���m�GQ���6��.m��~�6�N�UPMO[A �ﯘ[�@�6�mQ����T���/�겻��Q���5��O�x��x񡆊�}"ᩚDo�mWY/��������!*��QQI ��}��|�02���$n�a�g��̸I����bA��j}��:�<��I-R�gX��pUC�R7"(w�%���`Et/��,0�!%��zc��9+#��Y,�51)�9�v��z����p�V��,�aK�.�-`ͼ�A<×�`��ezb����䮬Ə̝�-�Z���ę�Ҳd��;���[��a��q�o����J1���~Ⱦ�†�K.����R��� ��'Z� ?�@��٠\\b��c��|L_ݫ��T]o�0 |��������^�&m�uX�}a�֢Pd:�H�H'���A��v6L/���㑢|q� +J-<&�^I~�_i�:�F�FpW(�\iE��g�9|-��"� �~y�.�µ7o�0?�3����V��Y��1,q%��0�K�pA���+Z�1A� �5�ժd�I��z�!/�y��ZI4��Ln�F��f N� ���]��x����� RUa\�� �չ0���8O��A�l_�yd�� �����W�FQI���!3���p>��]��B���5d5މ�L�|Rk/v� ���ӸA�5����G#�� +�#-���:� ��V0‰����%5�~)ٕ< �*�'Թ�5K�[%q@���@� #�R����<�Mՙ�ډk7 ��/WZɶ����b_JNj ����͸��5��R����iHu�6W�I��ɼ>�v����}S:l[�T;{���Ӱ}��� �P��I��Ve�*���dN��-YpII�-��qpH!��J&�5�wB� J��K�/���!���1�W.��i3H��[�B�t� ���Wp(���̥0� +>8��� �l�sZaO�g@M�O�̍�����B����f�_S�E(|+�)�-��{��E�}���ǿ���v�\#wn+I���<����&�W�w����%-�+�N��x�қV�>if?�׼��\k��c^nMn��s�ħ�N +�EX!Ȭ��x�f���W���K�)�7�i����m�Ok1���)����Ibz����@��c��jg-QY�Y�P�݋�vhuͼ�==-?$�б����V��ݻ颚�*���:A�<� eE��h݁o+��&���vVQ�)>����)�⬂%����r��xu� �$�(� ���Kc�l�<!(t01hv��1���s�P���a^݋�;�A.�1�I] �H�I��q��Cs�}s_Pc0��8��sr��G�Z�K�a��]"ϫ@{�D��h)?ħ�GUe<����eRn�~O�kb���+��fX(����G*/�B,��݁�11ޝG��4Z� �!���vkb̓��-hO���VΤ��Y�e��U�U/�7�%���G�CͬC��oV��T�W2n_M/6/�E��NA���uB �AE F�$x$1�L/�qvf2��O��nE�]��ꯧ��g8�������Lr{ӟT�A��= jdS����FΎyʧ�;���>J$<?��`jJ���2�� ���e�L-L�"̲ϔr��l�Zx�j*�W�� +��6�����R�:��(�8Dd��g:�s/���u��V������o9X=����K��]��U4 I6���,����IU�`D�֪3Jn�bͻ��R�N��l�79PCQW���~�Q�n1��+怴I���$��*P�h�".M��޷Y�6��M����v��z�<3�͌={ۈ���L�$l���m�4;����|omBc�&D͂�ඵOt��+��l��`d�xϞ��i��Xf�=]V��iM�Q��X��21fi\�6R��:!��a��U��i0�������S;k�'��M���ёN�'K�~��uy���[��V �:��i�E5�VZHW>�̆`B��|��^Q��n[��aѿ�T����E���yb����62U�8�n��Z�p�F�� �r<+��r֠��t���;[��C���<�ě;r|�*7Lv�I2�=��|�"d�Y�i/zQ;B��0Z�|�a�o��4�C3������;�c +�0�@�XL����u\On�&�d=���Q��g�X�����C�>����_���Ok1���Sd�$1�֎��84�$��C���Y�ZY��7���w1%�褝��{�N?��C��ȀqЊ�|�H�������6�����Jx���4 �Ao+�L��%X��F���`*������*pv+�Hb--|È������4����7�]�x�p�PFc@�i�h���-]�I��^�7( a��n���],�V�$��J2Ԓ��Բ��Zs�“�A!(W������� +ᡒ��=5��X��Wb�5��d"�2��#��X8[�-����{`a�ӽO�)�R����p���D��1ZA�JeX����!*Ά*���C�s��]κ\�S����{|t)P���sf �c�'ʗ�JRk$;w�}���MF;�/-q����K�>t���a�t� +����җ� +;Sw�C��C"<�8�L�G����ȧ�ߎ\K����}�V����i��3��ޙ��/��X[O9~ϯ8Qe�h�V�IKS����Hز������=k{h�_�s�\Q�y���������qC�#=�% �\?Ũ�w����nvaQ ����H b]�^,�D�O�.# ^Ї#�>2��� +����>�� q<�)��)���p��q��B <�@p-�}��T���B�� c� 5�r�@�B��T�� �BXS|�|�'���e���@�T��0��#��y% "�]��p�B��""�D�ZD:�DJ�4|����/.�g����'g���Hx`̂@$\�֌9�8�s�5r�j�+��ȍ)�K��"]�t���� +\#o'��NW���3cu��Rp��h 'b�2`�U��Q����v�ww�Ú�4�Wl"��. 8f;�tM4�N\�>�T����Dljn0 +�\g��TC��%*��(�(�4��%MS�͊)2 �� ^�m��J�Z���9N� �`��6Edh�&�&`��ኇ��O��z�ja��Ȑ�g�6�Sñ� �:�5�\{�껠6$W��yScHm� C�/MB_�"C��(|� ���G�4t� �s=y�2��+�C�DG��*��M?�� �Z �Y�L|� ��GŃ�:�ɣ�7?�:��D�ӎl. �#ʅ*��� +DJ�TB���|�ؽ��µ��oz��_�PfzK��.���zŶSy�b�ilc�0��flK�-�#�Q������M_J�����L�+X������ ����sŻ靑����2~Ll�#�k�z•&�ah>�ք2�ֻssv!$� �� +v�k�Ԭ�?�0��Me�X����޽����Ai�*Z�����nUh�6�K6ͫ}���ބep.�]*�l��{\��-��oT�jQ=����͢jo� yY�i#H3��'S[}��k l������S^�ܤa=�&��;�1UE65+8��k�<��o�� LaK [s)5)�JR��,��Ke�f�Ҕ�9Z���u-�6�8����?Qi�̓i��U"���*�\��˛W��̣���[�-\��U�]KBA���W��)�t��u�"�b�3�]Zw��9~��8������<�3y(��&-Ӎ(��z,$w���� ���AЄH�bY�,|����DT�9l�����ȉ�m�"V��r���2k�`Ik+l�+�Ę�%�_(�H�j���ú��r>g�zB���o��%!��d�Z 9 P"Y!��O{o�j���u�N��[�� +� .���]y�-;������$�%)���<�N��u��bX�P"���I�cc\�"X���s:(�Zp�����_�R�n�@��+�@�D�BӢTE�RԴ=E������]��BQſWk 1�CG>xfߛ��ړ��P���0g!��E���m1�n� �{m*c CP$�+X�f�7���[2�Z �| ���*� I&��/�M��<�R���3F$��ap�>XL@P�흐YF�ĝ�'O 5B�}TOhk4:F0���(1ލ XT��6�iy_泇ǧ��� &��(���^ K��ARx��4���!�m�T��FXԊ�����q�EN��N�����j�e�*fXDZ��7M�j�`�A' 3k��?��p�kE��o�JƶI82k%��و��8;���_��(g$ߎ;B�Kk���� E-y Hu�'::"zr�����Ҥ��������4�?���vr�j:�����7�(��`�Mٓ�<��5�g��+�.2�)$�(�A�����?��x���IȸU^�m8��n`�}�]�UP�NA ��W�[�@�6�Dբ­�*g֛1��ބ��߫�&���~~����K� +����j���]a��4�����tAц��Bb�-����3��1�e'a��~�kI��H��3��W�C}���)�yIj��g�L�U� +��A����$,{ˢ{��Y`��c�?�Wv ��2Bj���BN�(�I���a�������m]5�� [R4A�kq�m�V�k��3|n��]�5k!�x�H�b��Թ^?B�y �;N,�9���9I?y�7�G�oƩQ|$��"��׻�>�j +�ǔ��G���'�A"��O��^~��U�K�#\\b�{��C����?�T]k�H}ׯ8 -�����M�MkR(iHv�]���J*� �a'���2#�և�]�Ϝ{�=���]i��kf(���܋&{�!;O�N���JX�& ͌�*pW� -D�J�#��!�>I�T3�����`F��z���r�Z3���B� .���JW���)�d��3b�2�#�� \E(|]������%Y(�0'��C��,a#h�ެ�o�C�(�U�a�,ra[.ʱ�� ����w�����q�]�̍z���'��a.�3�6B��O��a�{*���I��]� �4߼��M�T<�2� ���o��ׇ����NV?��N�ը�`�4ґ��AeMt����v��Z��Ҟ�����]���[�l��A�0=o;3��G�͇��m�aY�� )>]���qy�����2��*�}w!��Ȼ�cT0� +�����陸w�f�c�D��#���[ߗ�eI.,�5�f]��# +���p��� 5il��M����B���}V^��Ϝt/�> �����{���ʡ�ȳxn�9�Z'd������z=�������������zX�n��(��^����tO�������G7�P�����b!����� 1PF�v�����0x������e�e{��Hc'b���܈�Xq߃��3!\�� /�y�U�{#g}Ί�Hh[�I����}�'����D�Q��]����,���L�x��Ytڛ�gp +�Bx�-�q�v1�囬�?P�\��vx2yg^���'}R�o�0�_q<@D��:(�˨V���G$d�Kbͱ������CHiG�'���|������2�p��� <��ϣ��$x)u�\�Kbp9<�z�������d�ܒE�f����f�,.���u��1�p+ki��H0 ���/=:o0��(g���fG��s\"�1�:�Xm�B��U���c�e@�i�7���t��ZF�&��a/d:�0���8��&��\v�<VV�TO��{�n:2��՝6�:�j�]*vt� +�� ��r;L]UI���満?`�$��I����O#�(�<�d��2�}����Ir'���dG��Ų���[��UQ6�l`��] �q3�S��Q��_������4���#�/[�^NX �0}��p旐k��7q5�����$I�� ,�$���Ѷb�[a�kK'}���=� +��8¶�u�����-u����:��/U�]KBA���W��*�t��u�"$�b�3�]Zw��9~��8������<�3��.�f�pO�x�z�,7��� xw^�����LE�,������R>��=��}���@� +&T"O������!��&QO��r�D��4��)�@P�aS��׭�"'�c*P�h�`����( �T6�>�!r`�����2�f��Yw�XL)v$������Ϋ�v�%��2l�ϕG&҆%�e,�yZ?26���hU�z�ȅB���^���@"xca=��r��,�͏��T�N�@��+ !;J@�6�T� +!h�C���z�X�v֡V���Nb;�{�����7c���-�(�p�w$��W��S2�.F��{N )b��y0<���j�����*��n�F�UB��� ��i�^��S�����= _�D��۸��Ec ��4�;Z��8� +>>G�J�@��Z�D��3� +���1X��ք5�������>����\x� )q��)|�����lJ'�Iw�/"- +d+$�s.ܣy�;2����/-�2�^�D5�"�3� ����^�� + Ԟa��m��7���z-\�27:�U��h-<©���Q����*D��(�sxH��krFgGP��}u��� 7��,=��gXJ��"��|]�����¢ �^��,|�q���@�� +���F۴j�q(d���6���`L�����C���>>ne��i�;��9�޸*��4��yz���U� ��������ֵ�D�q2�%�w%5%��g#]�w��7o`Ɠ5��R������¯��|2�L(��,��&��PMo1��x�vWmW҆�U�T�c������myf"����ٴ����y��cn>Fђv*Qɒ���"���jZL�5~��#XFTI:����� ���J]a�<�S�%aܨ�i�>��[�]�֊�*�/�S� ��h"��(�| �$��%$ ?�1��w��=����`}�V� �ёb���~����Y~{Xf���%�+Fk��E-�V $���'MС=U�^m��҄{��]x62- +�3~�4a�U�m��� +��5�;��8��=�|��2��)!\hgGhZ�~��F�{��X�t�,��R� ���F�|.r��� ���&�����Q�pV1qY}�:w&�H���տ�mHi�o�Kz/e�޽r���Q��\L +{�2nmTZ��4�F��͐��%�JI�7%��)�S��W[o�6~��8RX2�d{(4���K��$E� F:��R$G������d��� ��!�y.<�w>^��7]h�1�`b���VTk��_����dxWp k.�� �Z�m��x>�T�6|S$Y +/�Dx)����,��������L�����z��f��}O:bz]�a��£o��M�5Ћ|��QV@�M��q���� �_.g�5d]�L2�p��ڳ���������h-�`��y몼i�y���6�w/t4�e�5$h���+��bm�eh����\�r ���O���8��'��&I�-�W��d�X&pE�Q; �a�u �%���Ht�hh��m����*��u�������S��ce���8�>�l�y����}�?=lXд��DRd~O�U+�L�~r��:�I� + +����-��6��/� n�`�Sɴ����ZZ6q������+� ��������R�ޞ��~�H��c��q�*��q����T�)���ӧ?d�>"<�� �=����'(����w��|�l�Dk�Tۣu�Ğ����n�>��UP�n1 ��+�v`$�v���A�6�s����r#!�$��8F�/v0O��P��Ⲇ��}"ቚDomWY�?M������!*��QQI ��]�/|�0*���$>��O�E2�k���b�I��~>m�b�oH-R�w�Y��=pUC�RBPn�K6��ފ��"�������:E�Y1wE��b�3�Ĥ����q�������zxj f� [R�Q߽��6Z� �������g.�3k%ϸ $�e=^d�\���z����3 �e�Ư6w�'R�����8��c)�9�R����~�G��l��:�:�~���cc(a�%�� �tE<78�@�5�c�z F���g>�o����S�o�0��x�JDA���u�VC��j�X)2� y±�����'C���vX�Cl�ߧ��'_z�QŘaҒ��c}h�~'�<�� �@�bW�CIk�%���8�eZ��n�g�_��+d 0Tl�fq\�rwa� ����X#�07n|��y�q��A;+L�Z�F��1H�P�ƀ��� i��l�RB�v�TaM����&��緑jLJ%�Qr +-�aCR����լ�ˏ���U�4�C�x��� ��ї�QX��A�h�B�� +��U����ق��+�w:p�V CO�>"CԈg�i��Jj��sT�l�������8�60_/ ��,���ZKz�&�=J\p���]]�O�f�����0:��^hu��o��׳(c��~��aL�a�9��(5�K���R��w�*k|Gi�2� d�7,*5������B����>����E?=h�{>^�~��~V�uj���o<�TΈ�����7J�� �?�ϲ��G� ��W;[�g� �6�>�kD�d���U�o�8~�_1�Z�սB˶GYi�TG��I��qbձ#{E���Ov����P���7�}3�~͒ b� -�iC� �ݟ�4�0��DX� � ,d��<'����\g'#� A�#x0 +�/��;�pˌ��m�{�lk�2K�)x� ���p�%�L�s�b�Z�ۜ��e�o�%�\J����[ +��"��&e$�B&�Y��������/~������X�"�p�9�V�#pW�o�R��� 3K���An]]���}�G�8��N��g�o�mf��]�Jfc0��cq��_D��4͉mei.+�&$�O�0������o�����A�%���(�:M��"�$��Dp�����f0��3���V;�wR9�̈#��ܛ�A3Η���:z�Cao�tHw�>�� +�O]��|+�ȽqݱdrNa�`�hXA�X�&����v�v���\ʨT�}����f>��E��G��<�Z�O� ,�uSx�2;����%ٯvZ�./t봐��V� W%��9aM�E\���D��:�)lH4��^0�$#�&=�j�`ehy��ݟD�Mx�B��{]��^�~�z�aKo��l��N����P�8�+,��3JG1�� _Ͱ�5eu�,��N}�<"(4�1����Q�^�sh�h�a����.�Q�H] 3$ϔώ7��������<5Ӟ�h\�iq���Z��8�e��"_�@O�Y�]OrW�̍�)���[}��B��A�E��XO9c��Cޣ���d����@��l��v��B��-�M��ʘ'�� ��/J � �G�TE�W���BE��t���&_�ˮ:��c�j�㦎��Ϊ �[=��L��;�J����V�o�6~�_qX +�dy�C�Ɍ dA��%Z:K\iR�Qv�5��@��$w[�����ww?�x�.K3�1L�OF�ȼ�}�4=&�ى'�r�5� cڀZ�Cʷx�A�1W�^�$5�G\k��`�3jCpɴīU�~�,���$|�5\Rm��� U&�:�1DJ�W�Q���7J�Iֹ5٭��JB�r���d�@F[��"��v��~��B����1��S� c�q���͓�u���n�̓l����!e�V- F&����KF630���uò�!������\���o2�b�Y)_�ѥ�-b9|9W���'���H0"Xfr����ReQq�����/�����L[��&ˍm�ne�o�A8V�y�ul)��X&������v��+��� ��>L���Zv^_#%��<2~�`ס�0�&��M���ǡ��� +**�:�B���zk�g�S"BgM[�{����y�ao���/������n'L�[I� �qU�7S�g��8�&B�����0=o��@�5�ݪ�~໇�ҕ�ݍҋ=�tP��hr-ۢ߼�e��Vbd�kR+ն_l�{H�U�h�*i@B1:�#��t +�|��R<��Ns�O���GKy�����#ى��I�1�u1i��/k>]ʥ<�~�� G�\\�Y.Fp +G�T��N3�����] ������(�����=/&��Xk��r����`�?�nc����J#�R��F�� ��g,��ί�]�\��EL�}�4��0������j��u�%�U����O��������^3���.Ħ�ysVSU����&w~��?�m�x�ə��\�* A�DT����z��q�hEu�-^ʨ>j?�wN���e3 �g�S�4�/s~� +��ci��]G������} ߧ�^�wT] �����S0L�-E����O$3��.ԙ�Ґ�Rs�6�A�}s�����~l�]\;һ�����Ò��Z�o���UMo�6��WL+ʦ��c7��E�E�],���[D)R �x�b�{A�����K}���y��~�� +b5��j��=�d��d��r���*��-�@�ڂ��璿��|�Z��w���ep�%��@�7ik� +��������V9<���(�7jHÕ9��&U r������|�X�M��4ؒ`���g$ �[�+�\�jAh^9�}����Ow��o̖ha� +nZ,*`�m �5oT�Sű��DbE�FF�D}���"�$i��%�C�/֋I;�Mz4 ��j���t������T�����{�Ok��֪�PԮ����ȟz�_I��Ӎ<��ZIc5rG%a����1��Z�-��? ��|ׯ��� ���՚��%�1��k���<�o�5��u�=NF�'�܍&R;gH���ଓ���VfӘ�L��|Ho�'�3�AfA����Xu*x��F�a\�P�QB�ڄ�}��^�=�k[j�7pfL�a{辜�vG�ic��C;�| �O�8 +q/�E!�H�X4��d-�U�Xu��I/Mc�p��^ij,ژh���%2p\��L�ۃ~1�#��.�K�y����5��IG����'�(���(WmꙞ�惘�4�eϋ�-0j� ��:��i�-3p�%�T2���S�|��M���� ��abN��̧^���"Ls�xk��Gzr�0����͐��jZ���g�Re�<�蓕��CZ���瘧�J�I��L79L�o�|l�≀�tZ$�{}���r�܎�ܞ+57}\OǮ{{v����l�Q��U�}*.V%����C!�eo����j�tX?�}P�nZ1��+��$��B(- +j$TE��!!c/}��ٖwU���hU��O��ٙٝ��M�'l��ha�[=e��7É� F�ܰ`ρ��l�"������ z�"�Sᯍb��x_"�C��Lm�4�]���f�X�Ί���HL� +�s�)�@�=\�Zx�i*r1\�m�.�_��Q�}*�UN�9���ϭ���֏U�_L�8Z�g9{�Ǒ����%u�\�ו�&ږ$[GxnlyJ��"c:��,o�mm�c\�"���Uz���5�ӊE/p��Up�~�F�l� �Iiݧ�:��@�d=��rs�U�M��9��.�þ��ۭKQ�tN��� /A껩����x�mT������:���-�����f>c��%��D��U���u�Oo1����I���J�P� +T �T8�&�٬�k[3�M#��^�D +��y�����oJ[P��$ �e4]������sRFHM����%2)�1�n��|���r�_5�� ;R�A�fq�]�֗�܉g�\+_�D��<�%����Gf�uzn�W1�?pb������f��H��^j2>��'�T+�a�qP$��]M�|_ [�O)���ҏ:������K�:I�q2zUM�S�M��a����X���<9��p�2�MC������Ƽ�X�-���� �9���ٽ}TMO�@��Ẃ�P���@D�H!UHh�ǫ�w���Ш�W���>%3o��{��7�H�KF[G��Ww0h�~I��,�<��B&$��`9�<�b�gԈ�6��A��&�p#����,\2Rx����������:��� .m�2�Am$V@`*��#�-�&��� \���R�Wh)8*� T��`Nh5#�Y������n�����V���\��1 ���,L�M�\u��%q�����H��a�!g��/5#�(�Y �&eW�(��Da$����/]�_p>��՞�_��*�jR�1$��!��:���ʵ��I�A� ��"���G�S�u�V�<��r��Q+3„�j~��M��ί�٠D�4M�l�h�L���CV*^� ��\+��.��s�ʓr���g�S�����N���w���=+0N�iG%z�d���S�}/{I&�xt�D_��4F�ôŏ�Mh�N�a`~�^�)�!��q�б�B��9x[~�&�+I���~šmF]����@���E'��O$+�Z��9&����-�i�x�9���ة��}Uҽe���Vmo�6��_q�Vr�f��i2/� +m�4À$�$F�y�g ��iٖ�%C�I��u�**HQ*a1vlI�/*tӏ�$:F0��9�H!��JX������C�bf����`�e�V#����-;8V�����C��#��Gᘄ�_�F 'n��**4�BoB� �fK�5��Kc� ��V +��[+�����R0=�J�pO���w�yv����C��� s� %������ޙ�Ji�U�G�%�JH�o����]��$�j�ui陁�V,&�scyI +������,J6v1�"��spS��qf�R�tftF9� �p�$,��}�<��J�^e�I0�@*j�&Q�+���T f߱�l��� �#��^)��-�iրU��"���Ã4ڱ�%����vl�`Fk�^=��=��H nI�h����Ӎh�i7�=����S?�=��ڼ��z��u��xC8�ȵ���U�cL}���7�G;��#w�]�C��vV2R�KEAq��/�)g��m^����ISt�5]�sE�ap�4nH�Nt;��*���_Z���Tm�PY��~�2E=��1j/��[Ƨ�p����dl�(�3f�J<�+�юr�*��!xc�u����ڦjA�L����U�wt\�C�)�޷:*3�, �y�6m' � �k���>�~Q�,+^�[޾�7�����Qb��V�G��Ƥ헿�H�8��&wc�ƝJQ3e���y�i���P9�2�\ +4��3>�-�|��ӄ�zE�ݔ�?��I�� 1_5�q�� r��1�Z�I� ���}��sn�C!��7w���֑ѳp� +�'?�q��`��?�qұl%�y�*��;i��f�¿@�#�̯����"���lr��^8�v\Z{�O���Z6��Z��.l��y�:�af,��)�4���Ϳ��?i�N:��y��=��j1 ����q7�,�6�6��@)�����ѬE=���݆�w3�T7����i�����bJs�����}x���v.��0r&��u���Ot�a!�>+�c���B����&u�>��M����x��9�7j���ڸ��R��fA�"��7/j���'��rF��>ә#�XƢSp.r��)��t^�}�;��x��G-b��� �[ 8�'�,o�i$�2�ʻN�DVC$ܧ�w�i��u����ԸȡLӼO5�D�C槵������c�8� �C�9m��q*�Rs�v���/� CB"%K�E����D�(�ks�P+��e����.�_�����mO1 ��ߧ�&�>���[ +�[�m���BBi�׋�K���M|�)�PZ�!�������N,9��]� E���.�W���!�}ꍓQ?�>��A�4�"p�3� ns����S랼Z� ]ك/� |��,�3���'���8���p.��0p�%z8��0q�C�4�� L +��j^���/���Rk�zح�DC�d���5p!��+�������"�� +�\0�A��fa +k�9p(�l�%��i[�(1�@rB"���_ۇ�D�IRR���@r^��I"� ��- +a�+[�p 4L0����o0��a�.�d�<؝W+�GYm'���k%_2x|���Rr���Jz "��P�����V��~N�djE&�{�>����.������-�Y�xfG���l�ʑ֊e���-_X2\r�s���G.�yU��|�� 3Q�M.�񎶩{���v��+�.�BOʚH��ZR�Xh ��U1 [r*�H��F�Ҋ8s�(�\�ɸ ���®b)w�(CK�����.�t�#!��(3��,�*�;Z���mbĶ�G�X�]��@�-�%���i#�j\Rl�T�( �m�����j��&pL:�Z��\�&}�5[�qZO�C�W� ����=YX���i�减�!<Ռe�Z�7T�� ^���`�7�''�fi�/{��c��zN���T�k�0�_q�Cڐn#n�l���1 +����9U$MwNF��!5��CՋ���u�Y_��P�4"`N��{^{��(�A/��R���Ax\wJ/�4���:�z�rY��`�a00��'U�~Z�efX b-,�`�.�+L����ak��r�U�.����`�дƀܺG��-!h۸����7(a�q�x?n�W?gWQ*5�J0�A��� kXiV��yrm���]˃̊��N�p�~�)������� s�{iQyX'��湺��gkb\��2�� +������3�A��v���Up+�[�F�7��գD�6�A�}[-w)�t��#��ѣdL�>�p�A����u��i�6�u�(e���J�T��xŞZ\2~���H�����|��V�A��2Z�Sړ�9rW)�St� +(�W���?�=���>~���[�F8��=����56�5��4�8X\�:���ʏ?P���c��{������$����; ��v��\�V��#��z���VMo�6��WL�;n�N�f7M� mjlR���id�IbH�+���P�����Cy�%r��ޛ�P?��@�q.C�H��ٕ���|4;�)f��Y�hTO�%kt����!m�׷�_�((΂���o��h��u-�H5��3�� ���Al��V���L�S^ �?]�х��=�Iy����f��hp�t>��E�%��J���`�ATqQق�c���=�=�W��c�84 ������=I� RR�Á1����AUPU;E"M��‰�É &0F�ΕN0�����Z��0��G�D_R�c�5|�ÝJu�Z�}R6U� l�< ���.BW������V�8i���Q'e��c��満��3�d��n��D8��R�)r�HQ7od���#�/�'<n�։���P h�)���2Ϊ�D��u���v���iZ\�,��'�F�'Ƙ�h;-�V�d +a�<]J��� c�A;id� +���ZQe������}���%5�����@��p��F U��{�T���m�Oo1���)�ĂHh��o�)JU����B��v׊��� T�G�J�����x,O�(H[ŔGa�e#�@q�~0��� C��MDi,�D�_b]�=�fhw>�T� �|dG�d�{&���bG��y[�|�'ڪ(F9|���x��䃥d�r�w�fۈ�x +��R��Z�Kzr[��E�q����������C;�uuw���>��bR+�AE&vYT�`�����7� ����̩Š4a]+^�_�L�L[#\�G:�v���삥9����d0��+Nc뇄Oj`�WB�U 6�:��Z�Q6N�n�l�wQ�ђwӝ}pB��K-n歎م��^ަ��~�tl?���{����߫��X���N���v3�Hy;���4����?3�x��?����o,x7z�UW6N:c�1΍_�W�U[o�J~���$ۈ@��H�iҞ�U��OE���X��XU�۫� ;P)�3|��7W_]�\A������Q�dK�f�.��qc��3�̀"ڂ��!g;�P!>JUj��-D4��@���Em \-�&iOS\N�b,#�C��Lk�Q�B�8z ���j�8+�i~�l��9΁v�=�3�� 0�I]ˤ���H Ž��}��x���λ��9��'Rf�X�ž��O�H�)�i��,�@�Exȉ��?��̃��[b�;+�(�% �yen�����X,~�2��J]� �}�yPN���pF�`�?�5� +f�1�숆��=|���Ui�#a�Nm����>����2}$T�0> N�0V;�e�`W2'��<=uШ�Xk��T�a�&'���_,+,,�T^�t�����Y9�hSϻ����h������c��s�d���F��J�q�z9�K?�ш���r��b8�X��o̤FBs��У�5#% ,��_���j��;�V�p}T�|��2��P��*N�ғ͵܃���w��W1 +�I�qk� �b �t" 㳁F�J3Q��Mȸd�� ׫��*��5\_C�[� +4�����.:P��r>Lv�c��0|s�}�I�ʑ�*�{�4Z��0V4l%K'CC/���/L�h�>�Ʌ�›��"�'�Rs%�e��;����pf|�WCQ���X� �V;c�d�Q�0��l�ܵw�H��W�S.���{���i?�y���.y��&y�~��~�?!U/���ܗ3߽��M k�y�i6����X�\ �g�CR�h1�mE=Fç��9�=�AO1�������xT1�!���鶳tBi�v�1�w�+x��o��{���,�3 �d6�%m�rw3���Ha��5{$������ +=���ͼs���1“�aOY +f:�W�zl��[�t���PƬ\��������A2W��\΂/1C�n���W�hφB!p�c>h���<�B82�����b��]v�zc�� ,�?-�8�8Hg��&���by��>PI�6N�U���*�A(��`M�s<���o��<�M0�k�g�?�p��ve������ک�Q�EP�NB1��+����xT�`$Cģ�)}���6�}�㿛�=�����Lo�Ϩ�+4(*��]�L��r81�s�s�z.�9� [Q�k�42��[�W �w ���=�L�D�mO󨢛 lhk���x���rZ̲ϔr��+�Ux�h��k��� uܟ{��(�:��*�x���ӱ�=-��͢;�SoG[Pq�� +GV�—Ԉ#�T�"�M�*�:��[Y�����1�����CN�$�4��m`����{ ��+j���g{j�W��*MЉ�2�}�Qo1���WL�H� �k��%*jUE����Ϸ�Y5��^�P��^��P�y5��̎?�6�&mS/ +-K������ ��5�� |���l�@����c�j=]�#;�'��/b�+v4�����XP����1��0��6������� �*��7|� i M�����hr�`\�y��x7@��"ach��}���-�3�+&�lUDm���i!�|�5A��PyX8���&<����gw�QQh�b������|����w�~Ӎb'�g��F �J��dT� s��G��"l�!U�h4���˥�. +'-������Lp}]�#�w�[���6���1�_�s�y�,{z=����5��+oQyoO�$�;�HfٹW��d�w�ž�o�_��t�Lzx�y��]B������UMo�F��WL7" In���8uDp���\+r(.��%f�R���=�%�Q(��ov޼�y���g9$+��2�/����~����e��ʤ�T*i lA���=���{��,���0���J�+�5p#��ns|�%���3n��R|�n���]��s���5Y���j6u�Ϛ�fi��Mv�V2F2�R�;a�� � +�A�K<�����_��U�0� a ��ʅ �����.8F�ur,�* �C���)��k��< +�xQ�2���,�y���Y�M�A+a �'��:�۫k�zT�#S��.��p�޿�wǥ����mF�3�&`����Ɂ��Ł���I�6��3�F* |���&�;�0�A$( &`5H2V(9�^����HM��7��/����4�����#��M��!GN1����P2�p'� zO� IVHBs�6�*2�l49�p�y�}v'^L�unW����q7�$�5�/ J]�`��v3�J1�G퐾�ǿn� +���hM#�A��%�:��VkZ�h0=�EB)}�d������&��YsX{|8�� ��$L��pU;Y~3�I�XFQ��{m��p�!��煨#��1]�{�$u�)����ѵ7+�]��RG�����ˡ�����7� �]�c��T��:e���W�=m���^�Fk�)���v��"����g��E�)�� �U�f>�'� +e�a���T]k�0}�������I�i�l�`�cڗє��ױ�- �:%���!Ď��������sΕ|�]'"�)3�[2�Ӓ���k0�.� �9b�" ��c"68��@�(�5b��<�F"�L����,\1#q����^���+fI0 �0GW�.�t�Q����+IF�rR�V �J��k�� Σ��v�u�Oo1�����."A��?�E��ZUHɍ���kű��,5��HZ5���⦅�G7�I�d�f���3�(�Dy���AO����el�'���Y7X4nGW +�bӁݶT��g�/^�'b�i4]��W�&}��Zgq:�+����b��D1y*B�`abv�V"��.2�!lZ�a��E흡� .l"?kq1��<�L�9�w���������F �:ú|d���I)�sl�L����3� a�h�ǟݏ �jsyW0� 5�> �2^�n���E��(��`��a�ӌ���0�Eb��B�x���f��\ ��]{g�a���h?��k좳'D����rr�Z�0>)������F�j���ѡ3��X���f����VEu9ْ�mU����!�R���7�i��߹�%N��/���W��SY��0~���²�!���u��]�t��B�X�<�EeI�����ȉ/Z�m�$�����+�(� �=����C�z���b� ���C�4���1�Ku��ڊ;�N�%C,xG��;{X +2�ɺ�m��9�0��0�k$X�.�q�C�4�B&i ��j��/����K���d����h<�2��J��fN��G�M����n�y� P�1.C#<�ʟ�0�Fq �{[�D�6�,/"#*�NH��RЃ��N$�����{�qw�U{ -i��3 � ��B�:P+T�4Vh���,f3�A�d��#u�p�� �)��j�M�Cu����6����5����������p�°n��:XM�� + 6ᵺH��� �œ��q�2k����k2S֛5>)�>N��^ �C⏄�l�,��?�U���U��Lw��3O;.�6��/��I� ��|$`� rہN�G.����R�\����vN��;��2��;���v�<��eȩ���y�~�RMo�0 ��W�P v���5I�ni�؆�a��,Ӷ0Y(*�1��R7�>}�D���i�ֵ*�Z�II�r��_�)���e��Uj��'����تN3H+�zRMː�ޑAx������ ޔ�����6X +�Jx��  +ܸ֡uc!S���I��-���{K�-B�9L��ZI4A��R'XY3�Qx���}�}\��>o�b�$�[��*������8��6�D��:I�ʌ��;![Ak�-md�e��{�q�{�.#d�eR ���v� _;�I5��>1����0�:�^�?2J�$����n�5�)H�W�Ԫ� ����P���lv^=$�w�L� r$w�%[��b��[�G.����Q1�#�����k�Yh�x��P\���9���.x>�ż�Ai�>S@ȁ 0�k���$��g=5ͯ�F�V�ӓ��ć�$��uP�Ur��Ab8Z2��A�QƗ3P��I�����ُ�%bp_�`gUuF1F'Kxe ����, E=ip�ſ�4�q��.�H��V>h~�~U��n1��~�:p��B"��%�D��O����=��׶�{vY��;�$���O�U]^���“K������+��7��\���[��1&BTˊ2�!�=]̊�R���s=�s&|H6�"V��r��ay_x�>ǖ+m�G������P��DM�=\��q�������8�wJo�e!�<�Y�%��&�B�G:�s�6�w_�w�j.��*V�&�Ei7_���;��u +��6Y��MM�9>�2�R����uRt��f��T]�����n�I'�mZ���<[qQr���-��}M�Ew��xU�ﯰ/��c���kO���~�~2�RM�A�ϯx�$,����kĀ���M����LkOWS]���.��$�!��W��R�а �<ɦ��w�'�/�M�|Va�/��h}`��Dj����m�����W�� 7�k��7��OV�X�F����ۆ_���k��)�=�X���]�K +<A���h���D��;QX�h��N�#:x�13|lE{2/�)0e����}X߯>=�F��:2�(����� v�:�>ˠ��9F�W�zΉ�#]˷2�EU�h��X��7Z �W�٬��a���-���QD�h$�Z�g�A#Z +�B�Ϡ�L;t�@Q����1/g���!����$O��Q��Eu��A!�a���ך[Q.���XAv�R�0�3&����4�C���#�$ +����~��_����:O*?���_ƈy(�~^>8R8�����kf=-���Ep\��^m��rv�ӑp�ڸ����ڊo����TMo�0 ��W�P�v���5�ni�X�ݰ���ZI ����^HN���E2�G�=�x�j� 2Zd�߾)���i��1 Kc CP$��pט53HS�d�F��%�'���*��$ �^.��� +'�Džb1��Gl���������b �*�� �E+�xW��H��l��W���ht�`���J��n��b���M�}�M�?�_�T��4J`�*�]-�`c���ٷ������3�V�Ai��F��ϓ"�,k�7���[\�k���L[� w�����������,�����2RDg��jY:]�(G��޾bF(-9X*���45��&��#��w����[�ƴ:��yƱ�:�� ��a��s訶��U{���qK�u\������N,�Jop�8ܔ#X{S��6Z��GCA�-���a�b'�!:�2�N�I�+s6!TUD�����_}�(���U4t��^�c�z���mSMo�0 ��W��!v���5i�n�� +l]�t��,ӱPEH:Y0��Rb'٪�L��=җW� P���0g!��U�y���f� �����X� ����q)�Ƈ-�e#����C�j�{C�KE���{\�| ,�Q�a����C��1��@{'d�V<��'��n��w���ht�`\�i��x7�`Q1���&�?��=.�b�$L%�Q ��]/�`c���ٷ���:��S+�4�S����$G�Y�r��t� �b����02N�]t��7[\��������V1�/F��?̒�6�߂�b8���.�C�^+�k^\h%2�/��Z ™��i� ��5����U{�B���PaAG�,.��Gn�=�x΢����Yߴ{���drܯ/�j�r�?�i8��kϋ ��ۣ��Ғ�O*���:���D�zn�5����ᖝ_���*�?m���o�bko�c[VȬ��n!��a���O9eW�j� 7X����ַ�QN@<�!����Vi�����T{�����ϵw��Uޑ%ۊ�q&�7�Qڬ��<�<�+^$��1 ƥ�omLO�)����]���h��`Z̏��S}���TMo�H ��W0EPKAlc�u��6M��EѴ�H�#J":���c��^�lىӏ�b�����ș�?c�D� c.�d�V7e�G1˦g���*r$ +� +>6��I=�*� S�(䶀��#�v�EV� �/���I��s���%��v�p!C�26Ct��`| 6xeZvXv�o�6U��={B;���|�5J��CthaE����~u���:����(��@I���֤ hj^B���rhy�yӢDc>6�߇�zGfY��>���lD����Y�YgD�]h1�����C5�J���E������� .W�a(�T��ȴ2�p�6v3˞ g�S�CB얎,T��=��� ^�;��U��pj��b�(}N�A�Ŗ�h��QS�o��j�M^���|Sߓ�֍��q�A�Q�O�p����c����o��`�{��Q;�Qۉ���%���_3� ӣ�'TTA~�ț��IT��}dU� �������z�&��qn�c /j�+����am��h�Ҁ��A^�`BP���c_D9ɏT3�2M�F� +^�k^���u^�/�(���4� �iL�`���bT��|�����I�E��;���=��0��,��\�-���5������S�ѧ�]$���8 lj�>C�����. �n��G&�_/�a!W�ʧ���X���|����_̲?�E���A��;���n�0��z�9�` N�^k�uk$����$�AS+�-M˕ �Ȼ�%��ϡ(o~��]Mޅ:�$m� +-K���o�q6*2�R���X�����}m�t��%�>�٬k�@�xώ��*��X"&��V��UI�!h���pG 1&�f�䃥B��;a�j�s�o=CjB�X }LO�5�\$Wy�(1� ,�H�ڵ�>.�7�n�U;��J�S���,*�3RC���7� ڗ�ȣ̩ Š4�V����F�Y����̚��[����3f[���b���V �:��szn��…F~�MR��AlV�hT��)˥�. +7Z}^0<�<��]�t.�ؗӎ��/�N��)���J�|������Iv?����]���}MrD�  Ŭ�';����������/��{��Ot9]�̽��z��ÿx�-���=����l�gk=1}������SKo�0 ��Wp@P��#ݵn�tm� X��N](2 S$���k���Į��k��t�E�{�����(!C�a옔� �Jt��I��t�k��J#(� ��M�x�&�Җ+R��!� \�Ax������N��� Gp�S�X �B�s�\ ˢD[j� L�&5�ؒ� ǖ� ����#���J�q��悕5GPja�p�����r��v�6Ƹ K� Sn˅,�޼�Ii��r/2b�����~߼HE���� ��j�+�H�nH��pER ��Z�h#�i�$¯����p!����dK+/�'���:��>����T+ْM&��TI����ڤ&�����^?O��U�c����3XX����P3���}�ԟ� +Y@��z<�!?�uq�Ag�\�r�Q9�m�x0�\]�q�Y���(�̻{�;(�!_���J�'�N�:j��PԦ�q ��N��rAv �m��x�>�)q��ԧ�.aM��v��. �"�b��ס޳]�!sH�K�m'7�Yc��Z����������X3�t�� _~��8�T� ��!ex�N�*υv�*�0��� ��z�]��x[0�VC���W{\�3]Y�Ї���X�G ��ܳ�E���gv-Dj�Ҽ��ÿX��9��˯�ߥV�n7}�W�9�5�}�,Չk�AZװ��%�AqG��)r��J�{A��b�M� H�\Ϝ��ٯi�B�\2�Y#�}��i�K8����A�A��X�k�M�?��K\�toD�Xx�B�$�zBc ΘQx�*��p1�%�Y�\c�Ψ<8O�u*� Sp����jC��+m�&�LJ��w'-GEB���0+��@*��V��������fy�L��l�,�A$(���M���Ig�#p�)��� ��#�&�|��<"��(#���3Pj���?-����,n���h�%#�׿青�Eh�����η��RĊ���W4b-и �DjĖY�1u%f�����e�%����3���]u���}O7m\v��M/�@e{jIuU(��J +^��ȵ"k2n/�> � �꾏U�Z� K�Z��@+e/5s��#�t�s ��Bu�:8�^y:J�x�y�.���b�.�;z����ùM���d�bBbt��c�j����L��Qҕ9�yT� �Y�jc1�ü��&���Z4�v��vr)�ٕf�iPjN1�#�0,��B�h|��T&e�V����0���j�1`��f�j��4���W+��F�832<ώ�T�K:�3H�v�c���R�M5�Q��ҝ.-3Α�O �ץ�q|!�>5ܧ�$�Q. VW���:�S����� !FG��t�hi�PqQ�2����:r �S��HG�'��L�7�����Ѻ��N��'�}�V&�3�\V0�.�?�o����)r��=�h�6�KP���3$+T�>�a|���@"�8�r9�m�/w$��1�J���󟺦;�^�����|KO���0~r�9f�S �J�:e��u��9�q�������X򏌔��L)KS��!���J�o2)?�8�4^]����oX��g��e�C�dc����iA eQYO�V���>��u�ZT�ŎQ�]�l�ug�²��kp¼���3��V��Q`�Mһ�ӫy�h/]�V�؟Yo�z�Q�J׬�e1#܋&�k��W�Ï���N�ؑ?�y5��2Z�Pz:�2�b^9Ó������j�?���V[k#7~��8g3c��}X +q���:l mR�K�Țc��, Gg옒�^4����m�����w>��$N B�a�����t�?����d�+VVJ#(� ��/����2�;��I�c�@��+�ߴ0/H��J���e�}���p)+a�3�Hp�ʅ�$N�&�!���I-S�䊄���c�U�5�*���J�qʬ,m+k��ha�p��=>���x��PYc ��p)���v�c`߼�)Ii��勑t��_bA�9Cd6���e�� K��[���2�ب��Q�c�Rȸ�`�}g�!/���"&�H�����U�Z�x�4>�����"�li�U����ć|��%��&�b�=���������9�2�D�~���C � �1�λ2.%�� G��=*��YiSe�.��O����d��(�aawg#/�}Q�w�P�Uݛ�R1t�Q�P#��k�[=ȭ7\���Q�p�iWr�<�����={�bt����S;�@9��w\�_��g��G�g!����)��������"´��{-t�9 ���WU��ap�do����k�0���W�C�vHm���Y֮e���uc/s�ϱ�"��9Y�ߋ��k�ل^�����O�w� P��ȔEa���C�x�&��r�`�Z�1��D������P�w\�p`�i2��{vW�/b�0Gv�\��i�Q :�H1�� ,C�K��ՠ�6�N<�S�g�����������"�q��-��n +�F���}?�iu}���&Y�`Ң�#�&�����$�G߱&о�K�pK1�&�k�W�g�#3����r:%��Ն�J�$Δ�cSG�[[��fV), +wZ��a�O�%��ũ��|S�a�}C�ø��� �, c��<&�؁��hg�r��ņ�K'��,���Ȭ�>��Vp^��j�2��W(��6X:���!}w�7�+�1}i��HO+=�G}PMo1��x�( �J5RTEJ�^*!�eG5�5�����^�*T������ɇ�F�d�*� +[]�.R�W��Ѱ�_[Nh�8!Q�/-o�����Nx�*J[�x�'g�OM��4[�滚�7x��I���3u$��0�m�e"��a�W�e�AR����9ط��vl�'�&��(���$†i{�=?=̿�γա��F�5 5�c�ز��\>�N,���TyTx���%��F�Ï���:���I�s$�� ����C�6F.9��j�]�Τ(�1J4G|\�n�آ��ͰX���Jg��jw�W}x~�\�v�op����Q+ҋ�r�7,���+�w��_6;K��9�I�Rh�}��������������i�������J�7���W���ɾ��QMk1�ﯘC�Ǝ�v\�iB%���T0���+*K�IkcJ�{����R�N�{3o捦|�H�T��Z�e�{ +w�I1�[�jm:� �p5^���q������(e��l ���?�c�T�����*� ���Q �/�c���7��7��VA:Y���8t���B�yPOh�%�@жv�Q;;�7$a�i�y_���iT^,6"b'��Ha�c����eI�N�+� +6����F�����4"�\\o�$�*`<`��[��NJ�4)5=뭈��������qB_�ԡ�}�2Z�n�L`��Άȭ�噁���R�dh���M�d4�i�Nm_��3��f����b���n�J���E��H�+$�|��g�'u����hֈ�`eO���ņ�j���w⠝-��t�t�b��2�L���Ǡ� �% ��gn�>�N���+~�U�n�8��+��SK��`�u�5���lP7�K���FQ�$f(��&���,�r��e/ˋ(�ͼy3C���+�(� Lٓ����p��e��l������PA18Al����i5bn�R��C*3�@���g8d�r����8������ + ι]�t�C�4 ���ƓZV�7��[_"�� #{@k%�0�2����ʚ18��6 +��ݟ��W7����K�a+r�;.�a�| >�g[�D�6o%�%F�������O����4I*q��)�i���ϵ������#����έaOB?M�3|�Z�5Jo �I�F#�����aXv�6�# lCՂ�/�z�dMX��i�i����%�m�x�]� l�F�u�i�3���PH0Pq>���`nu� ^�[�=�Wl�/��Kk5 � !�V+�y����E�%�y���P,���~�Zx�X�:���EmҔ4�� +YB���(�[��H��\a��7}哙�k�r��V蛬�Y��1�p���pz����ʋ�Nx|��<���kBᑾ�´�?�NYt�8�?=�?H۝�^�^�`�\�4Lf[R� +2ʬңa4W�ˀ0�' '�Nx8~x����4��N�� OA��������-��d�����~cA�����J�v�rx���[�O)x�VF������-�n�ፅ��k���=�o�V���h?��c��� +����^�~�ڂ������2+h;����Å'�f8�>b���6ׇ)ʦ��&��Vs��>%�}��n�0 ��z +Z�.���Y֮h�� �m@��tDD��N }�A���;TI�����h#�h�NX�$2�*����SY��k��b��!�@ Q'�����ot!���@aJ�O��~�I&:y�[�75NG���f!��;��`���]�Ct�A�L�h�JH��B�M��=g;2��|�F ?��P3–p��=�,�TgL��i�����v$$���&�`B}�|��� Gm�V�Y��M�R��ܗ7� yi�����#�>D��'r�س�;f�J)�43�����2=�v������? + � h��3��2\D����kd!ߍ�� �@u/��E��x�B��Jl�(��]"��oBqJ˫7\\=�_�_�%����:�j^H"�*�р{�R�D�J���܆�^5�����k�Zl��4,P?��9�M�f�а�p �3$�:�U�K#�̅%��8p��~��� w�����[�4��Ԛ���_S�ȯI��Vg(hr�[Ѕfw.��*����t�Oȗ�a�ƭ�������3tuBVp�`�r�u=���/M��N��`��IƟ*դvA�*�O�K�ٕ�w�X��1�Z�z��/�J$��|�GO���Ol�$�^��f�|��ͱ������cj&�-$��Tb�V����QS�p�*��:���~H5K{x�ޢ?�Vmo�6��_q(��SI�3*��(�7���m��ָ!�0��g�Q��nnd!Q��"��(���P�;� +L���e ��n�KF�k�s�*b��)��F�1|q�_�W��ַ֔K��� ѐ1��� ^�)��䵨E�"kS��8�PKB>D݋/�"�QTk��3h�7��;��w���^������@���_>��oW�k������ޏ�'?*$�cA�o��kM�Gqr-����_ɿB���d����Dk��ڐ�\��1��-�8;��QM��Y�+�s��\X�TlG �Bz�e4�� ��X+��=����~�R^0j���Ɇ���$1l�Jf�sx:�� �zS2z辧'�[S;����a���ktE�З�� 4&S��*�>�r��7�] ;���6n������c� �Ć.��b:�ݏ)�|����b�jр_�������u-�P�l���j6)<�����dσ���jø�<7B���eF �͓���`Y��n8�M���-����IP��<��7��g���m�{4Y����r�D����I���r��;B����P��9N���K��?���6c�mL�-q�x芓�6���d��l���71Lp�� ���9۶vqO�.�/흍3*I.�O�� Nky:�n��mBܢ�܏�kܙ錓9/! �-5#��~?��I�2�����Y?���$�9����7a������o�!�� ��(r�z�LOͱX�'����&o��]�Z���iR�'p} ?���6�}t�[ڿ��7�|�ߤ3��~m��Qw�59F'����M�5э������I����W��w�F��VQo�6~ׯ�.,���1��d��xh#Zua��):L&5��c ��)ʖd)m��E��ǻ���;^�ϒ "�)Sj�����e�ǿ.���Nᯄ4Ĕ"���)2�YB�w2�)zN �|�J ��2�7*��)�7�����b�p�9*�ҥ�&K2�Y�LD��0�V��J{�H&A��4��n�)q�D,՚�bY�L#l�n߇���a>��\`&a�LCD���l�$`l�Z�#p�!���Qg�#���r�2r��H� ��gˏ�>>-?M���LJ˦r�Э|�|���']�8�����0 ���X��E5Դ�RaC�3E�ą�G�� �Q��,�&��'�''�k,���?�z �2��Ki1Yޥ{�MS��.�b�R����cf�h���e�i wr�1C+J���6�� �OO�f��mխ8S�a�'=�:��p�%Q����9-�]�U΍T#���UJ�P��r =GOj�i~> �z�D�]0�h�F�O��^�V���\�6������2����_���:a�<���.�&W����������Q��~��t�7h ^���T��T�x�~��V��4��A�d��d� K�����vS5$�a���-7�Ŋ��v�q͖��-ܹ�Hԃ�.�v�K����A����e���_�3��� =|i�Ώ���NAԇ�q߬iM��w{��3��]s��RBV(�Z(Ź�� �e� ;�v�g��2\x���7�*7V@r���V +�Lύ"��FW*ډ�����L"G��x[˖�]����u1\���-�ߓCb���퉺J5�'�,M1��q��@��rv���j�-y�t�z�v��'�G�s���¥�I��E&�Slo�>O������Hgge�r��i�'�����ן��BN�(Oi�ZV�\Q�~�&��0��K(�8�R^� +<���8A������S)Hu�/��e����k�-ݬ��8�� Z� �,�QMס+�TeE�2�P6�vV<�kq>����6�U=� k����p~����2��aڶs���?��� &�lU@�a�E9�,%$�����]�E�L��P��&<��߻e��I���tdž�Ah�^�Ȥ�oG�s�˟�T�L�D~��5�,�7� ��q�(�[۰wvCV��ث<7J�t�M�㱝ƻ �I��zmX������j�� �k-i_��3��T��o<�q��7&�@w����.>_N{�qY�v�y��eWh�=;\ =a�me5��ls� ���8�ώSXڢ�M_3��>����J�!�>Gg�Co��r��f�D�˹�m= =*o��0�Py�R����� r2$����>����3b�p��ǃN�ώ��Ir��$��'c��v�i�}—�?�TMo�@��W�!�P����(i#�*�F�R4������;EU�{��ҠD�!>�����7�=}��LQ�FouI���'Y��A��V`n��� +aץ������<��E��19��|p���)���l��+hpS���E��"�S������T� 0�+�Y���%� Z�+��l�S����X?�D��Bt�BP[�[����/�L/��`Z�� +V.*��j �/�bC`B�9r?�$�h�K��}��I��"0��2��W[��*��bv-ɫ��� ���°F� ��H����T_����$"G�5*�����dM��9k`^y�<��[�(WF;��np�VLz� G�6g;d)}���~�����M�kk�yɏ���!��!r�AF�)���AR�w4��؀v�駪� �fV#gQ�=����z�2�����8`A���-Kޚ��]X-�Yτ� 2�P~\�>V�?�w���_�w���1�}������Z�c�ܳ�mNh�^+����ߛ�X^?�v�F��j�L���۶ mW��?s<��V���e�t��m��n�0E�����B +�@���� b4@Pq��19��2$;3�c��Br�U�����ŗ�xv��k5 �~ٱ�~��̫�� +���E"#( +�!�x�žo*���\�v��v V�_#��,�X�$��N���6�%�@ ߸g�B�����%� %��I���E? +�Y`��c�;��'e��fy% 9]�D&e�F��������5�YG�)|�S{�u�^s/�Ჟ�o�D����#y�?��W������(������i�1!���f��b�Ur�Q������>��Ʈ7�_�O�j�擙eò�q��M3�>�5���zI�O��b �^����,˱n�iX�@z�6��VH8�l6��\{��W��?m�Mo1���+�D�P�@iH�HQA���Y���blwf����j�So���;�o�)��" Fj���c�~�2�V��]��&(mB��b�7�ք=�*ꉧ\����ȍi! �9�CLiƒ�P����֨Y-p��h!4���4�Dt q��r2 ukY���Bր6m��.��CRPH�,;��� �V�>��セ<=�X?w���5lt`%��O�` YW^s+�*�W�w���ְ��_�E�U�"�� +�lXQ[As܇�]��a{$|�WZ�j��?�ߊ���u �6mr]�\k��|}Z��!��@Wzz�'����U���&�As!�d������yt�]�v��-�Y��h�O��q\��zA�g�g�Y�=P�NA��W�!!�؄���EsDB��ZO�af�ӻ��ߣ�1�~ԣ��W5Vt �+g�T��ڮ�]^������QzI�4T����1��3�=�ԝ�&fa���m���j K����c���)V\�f�3~p�bَ��+KM���C(�Tփm�ߋ�"�)!|�O�$������ޤ�S�D߈Q���~>���Z�OR�`�a�:i/v؊E���AJw�|�g�>���Cy�d�d���≭�Q�feꍛ�:��:I@?�0��a��'�،ݤx SW�Q���W�E������R�n�0��+�"���5�]�A�(���@AS��(M�˕�Ϳ�%�yݓH�汣�O� �H[ŔGa������b��� %�5&�6�`"�b���И �g��>��<6�\�bG�l��I,���W�������RQ�r����q���&���U�� �U+�c/��3�!ԭ�����F���j�k%ƻ3K*6���w_�o�7�� &�lUDe�^�*l�4�>��5A�j�<ΜZS J�w�{��I��1�r:)#6N�I��UVZ���W�qA�Z�BUbYP�vC ����$�}�� �e��F1\k���布6%�GO2��w��}d�䞞$�����o��C�����,Qc�8��{�C�ת=��j"9:Xv,(�h���0.˽�si�o��:›'M!)��=x!��=޽��G�M����Ӯ����3G�7��G�O�}k+8/�bp0��۫>�ӳW�F�G��������h��RظǼ(���D�E&i�W7� U��>�����Q]O�@|���J��#��R(�T!���J�r�ħw��:!���Ȏ +��{9�wf��ǟˢDN��T���{]�$'�Qr�O���� ����4��s�nI� �<�kv�B�� g_� ?�Upl8��{sbJ3#�L�U�8���iY�KO5&�1(�Y����� -��{حz���R� ��F] �(=!,�޷���E�� ��Q�� w�Ѣ+��/�bK�1�"%�<���n ���G��(I�7"�꘬ޱ��D�$=*�\p6ecuB�*���oQ�8��q�4�+���; +Zk֣���(�WlG��#57�o��D��yآ�}��;�yl����tG}G.kM֧W�3� q��=%o*��[�Wz���>aڔ?�2����u7G�a�0F�اٮ��0išs����GM_ɍ���t�b_�'x�� m�^h=&�|S��j�L��۲Q��e �Nn�^�*���B�g�B�w\�[�u�[R#>�*��H3 q04bv#�[L��b��� [�fi�y#�_)�x1��=%ϭ�Qo�0���)�U�j[�i]���`M�O�&׹6�6w���wGN�Ҵ{?5�����ݹ'oB�@ma�BF˽<���l�����4 3c CP$�gpS�%PG���Hf^ +�:�3ro�r?���D�����������T���VHp���P��b � +�� �i%����R"�*kAo�c�5#7����xw��b���e}�������ELU�R ,Ca����FJ�h�}EA�bm9O�z@J#ܔ�����"�$�8r9�W��G�]d��vw���Q�h�����g�(�E�v��t�ٔ���[�U! �a�;ȇC8](�Kc�_:�U���q �P�Лu�GIsZM�����w,TiIw��$�Z��z�z��n��5c����ԸJ;V�G۟�1��-���و��- +3��E���T\�6Ͷ��"��ܚ]��W���6WZ��n��hoGk��jN� 7O�Q�!l���9`�2��0S���!J�2����ʶ�԰l��D���[w����b�1Խ��;��$����elj�������U��Ե��@-�a�~�� ���^E��.� ٴ�}�� |����H��\�/^�.�}��_]�9'����V�'�6 +ۓR��8bϽ��I��N#�����~�R]+1�#{�N�Jk�z~Ͻt�f��YO��RKo1���*尋��B($J�HQA��"�X�������*�����C}�g�{����FI��\���g��$W_�A��d��r���'� +���[Q/C�q� �e��m� µ7�� +�����{���%f47�����C�?�몦X{J�0���A���,;�a�x{`O��Y +Bpa�Ũ���'#���u;wws�0�MP�1��bm��-�X;��ɼĆ-��ro���BRKx� �şm"�,�wZ�S��W$0�;#����Gk<��(N#o@�$I��ވ�>�L���A�J�L�l�N�wC�T&��?�;�W���-��MBOŚ��(�w�A�}o��Y,�`Stx~�1�rc5?�9 ;�t.R\�Ѿ��S����1 o��gv��ɵ��Y���1�#�[ �t>�UF����Td:L�p��U�<Ы���������ήh�]�QT�.�b���<莖�Sj?�\o�ܿ���� ���_v��j��o�[��VQo�6~ׯ�"qܾڱ�,M� A$ P`�:[Di�;��]��@J�%�h1`_,Q��}���苟\� G�aꙔ��:���$ ��ByX(��<8A v�Z�yqmݖԲ`HeWd~��|Eb� ^Λ��gg��s�Y �b��ٸt�C�4 ����I�K��뀷�� �E�5�]��J���,,�+k��ia�p���o>=�WQ�a#<��W�0���8���$� m�H%F��;! +Aw����I����#z���AZ lA�V�Ο�se��Y������y�-���{lkg��hrWs�$$?�_%z�<�w�o �h0�˵ 0��?E �>��"��Z F8���Iҵk��*��V�H�:n�m��G�1��Ѣ�6v�\+ ���P6xy��x�Rrz�K����?�3�I��pV�5�������=�ߎ+��"i�LpB��l\'�q� +{�j黮�pV�"����"�LÝi� _9��k�i�ŝ�1u�OwK�VҬ�sk�bԧ�Dn�N�������nZ���~N�џI�5���7��e�&�y��b N�K�ߨ�&I}�:<,� ��t��(��Χ��%b��O�a�oޚ��Es�_[�h8Ͳ�^�w���1!-�/B���G6��Ռ�װ��t���*���!�8��S������~_oI�Q���m�Ak[1���s��6�&��m���J0v��"����(���Ϯ)��E�vi��ݝ�F���+: +� MԄ�}�c%}{=]���a�ϑ='+�C鱎���è�/�(�-&a�;Ʉ��S��d��]�yGﮰ��Wc��H VzY��X��DM�;��Mx7X=~,��~H �OzS'����/��K�BM䕰g:��OO��ۇ��Y�Wt��,�p`��V^� �Jw��pٿ�V���|d��b6>qCZҞB�҆\?�m`�ec���:����o�����g���:��C��t�����{�ޜ�6T��9.�iJ�L?l���9EOo�/�-�/��T�N�@��+搃Q�(4@A�*�R�Y��i7���q����+�8"͒�P���������K9j�� LZ���.?e�d�O�/%(� P�X��T��h#��L�R �ܰE�b���,.[��u�g9^��3�TR�B���׾�����栝�Y%��[�{� %BQzӽf�h���B 9{ޠ +K�Usn�8���|WK5��T+ ����V$%H>��5�vyy�X����Fx*?��D�I�� +&��� ��� g���J��5�I�>\/C� JzE���f�4��u(�N��A�Ғv +ݑ�E����Wo�|�[�_��s�6׍!��s3�0� �dp�T� ���s�H�s�([���� �Imq���Jzz5džz�V�4�4��C鿳�8����{0�c��FL=���!��f}��Vf{0�9�q?��C����_ai� ']��۞�Ɉ���}%)��μ"�Q^�rGx0*��_�X�rG�͇����Q����U��D-���f��j�.����Ӭ�Z_�.�8& ���5� �R]o�0}ϯ8�� +���|� 1��TU��I�qnk���oh��� +�C�d'��c�߇2 'mS7 +-�e(N��Fٰ������(�%���X� <���� b�Þ�S)��>�#|��� ���bG�u{��iz��U�>SE�ql?�B�K �rh�ͺ��`��3�$���G���F��� +�%ƻK*jCۆ��~�xX-�TLJ%ت��Ŀ^�ck�����W� ��m�a�ԆbP��X*��ߚFFY���K�YQ���%Eok�qm4�W�~�Z1\e�d���M���) Geo�[uNQ��@�H,�JB�ȃi�t�Wkk4���T(T�����>�zw�����2�g&�ɤ��;�uz�Q��%�W�M��4�<�N�/Ӟj_��;�a�� @��$�ɱw�U�|4�y����d���-�/v�B��{=W֞��Tx&Ўt%�=��^�.��$���L�l�Ҵ} +����ݢ�r��P�Ζ�,�t���S����`6��Y�?譒��h}����b�a�j|�GMg�K��-#�$ւ:ζ����"2�R���d�Om�s)����ɻN:,�&_J�����s�ٜS\�!M�*�hO��%ب�?_�E� ��z-�}a5i�o�բ7�X�����m���Q +0����1܅�E�� m�j�� +⃦�t�1J/�e��Eʭ�b&��)na��g<��R�5]0wGV��x4���=&0� G�$ ��t���H e�{I͙,����pF<��*�K&$��M����#�z��K0��.�V~eVy���)�S<�(�*_���5� ��G��b +S�L)MU ›���pR�y�ض�����z�#a����P=e ���-���C[���X�r��jd��َK�Ss+�z�NU��_�l�����;�͘�!�C��3�Ú� ?��r� �{�09z��� r/m]�RKT.��UP&�nl7��[�a����r�EZ��=�+�!7)#܇W.��v�Pf��۲e-� ���L����i����0zε"T���@f�q�e �!�޳yG��ݹ��zʜCKp��Sh�-mg���LJ�a�E�=��ԟy?F��A�[\3�A1Tߵ�V$`/q?�����&�a@z��s��Wh��<-�Y�X�A���M�<�?�,:��ղ|��!Ԍ�a�l�3��R�Q��2�ci�g^���������F���^�= +2�jA<�� ���ɼ:]/�~�f<��D��ƶ�S8z��C�"��L��ѿ�TQO�@ ~ϯ0Zӊ����Rc�!� ��^���9q��l_K5���%�d@��Sr��ϟ�9�ˢ� �U�) -#Y����v/��$Ё�aȍE0 �"��eaf��@�8��̤Hu��!|��� Á"�G��� w`�c�b��O���.�ʢD_Z�@P.흐��K�SO B��b�hk4:F0.�4Ub�ہҢb���y����d�u8��*aR(��b� �\���Hų���$�%NM�K�. E����H/IǾ��̠��bt����U�s7S�d�4 St2��XFx]�;s���j@�i(d܄{I��b�3��$�])������cE�����N�f��֢�x[��)A؞5�>*Q�����5�Q�h��c��%� mgJT{I�v���J�Lk@�Y� !��R5��e� ʩq�������FԠ'�@n��V��ߺ�Hf������E${o�Ӥky8L���kx�oߵa�ߏ)犛p�F�<���G�~��k��� ����6�����'>� s%�[���X�n���.KW��"nr� 3o�F&�t��z�+�;��pw/ �h���G +�sp8_��i�΅�#��R���q@�+� ��[ ��/�p���T�o�0�_q��� Z���26DY�nBe��6Uƹ$V�m�(���';����������ᳯ>�LC���X1B�:��w���Y �G�O�VU��E.8$��^��#WҒ)8�%�C�{��DG�:���a��:r]�;�ؕ��6#��mĔu]Qf���9�D�qsXUj�������;"S�,�Z�bꀭ��3�m��$�Z����FH�����"��v2�֟�jʜ�Z5#¥�v�p�eG"��^"��]��|(��v��0� =�Xje��|g����p��)RKm�NYp�kǭ �� �����2H���E����1R]��^���%[��xa3^��C-~&A���� ;���ZRsƆBRT��*��N��} �{���)�ovtY�?�ΰ�a71�Nχ��ykL��?���w���:�!a�ų={����\��� ���wT?-��/=P�jA |߯��<��$�v�4!��RB]�T(�=ݭ�Z�H:S���.�>j4����c� ��.͕���s#{�ڄ���+|�l��Т:j���G�������L+|R!ܗ(��n�F��/�uG���>�s|��[[����Vh""J�Tŕ��W��OU�Џ� �w�؅�X����\e�V(��t������EM��2�NV��;K��=_'g�+ܷң�� =�p ��s+��4Ad\�s�edU�wN�+���c�s�4]n��Ӛ. +��Fx�B�#��p�'�Ҷ��U��Fe4;���8?&��8pKhz�P=gl%+Ҟ uc\'X]�*��$iu�\]��]�P�1nc+ &������F(O���,E2$�cG�;(�+\Z�D� 1�|�V4����F�����C���gc�c�őP(�9�c�ᜄɾ�DT�]�f�_ �>9Zket#]�K?�#��#��pRS#z�(������ +ݑ�dc�r�.>�"�;���tV�6�d����m�tP8�Av��I6�/n��B��Z���QQ̚��D��l�!�M�23y��Z��j���bH�/�N2����^d��9N���R|y���ߐ3'�,yL�E9���G�>�������g�Q�ؑ�&�r�l�'Sbe�&�ض��~�^��9B�/�.-�M(��ߊ���kq�X�H�e���w������yj���d���V�n�F}�WL #�V��7�R|�� +m,Cv��V�P\x���]���^�&Q�I +To��9�hw���t�!�@0B�X�ەF�>������17q�� hFT�1�cÁ q���4��u�&�p#�|F�.I���ύ;><��˙��0A�KS�t�Qi�)� !P��$V�)~P6F�! XwOт( ����\I�@f�Y�?����C7-� �1��`Bn�^‚�l*ި��@���#� �f�}̨�F�#�[�[���-����������z�#Q"��!D �w�\�[�5�tL� p0!&,x�1=9g���4Mf(mw�NsZ�f �*i��~bub�ϴ�h��|ut2<(�~�n��qw0�� g��/׃��ݟІ��zw���z�r2X���j�����Y*�9��s��2ܮ� EtqQ-�[a���+��K{OjJh�'2��H,���3���M�l��y���w��4=5�w ��e���Ro�Ճ�1�i��a��)��HewYN�4��6���+TL�.����_�SaO�0��_�H�R4i�Z�`�mH�����*׹4��NB���$�aB��)w~��;��S��H�Kf(���f�)'�&�� �7��H�$����*+�S�?��B��(�g��gqȌ��y�:���:��QA��I�YN:��`*��1/�6�n�E���R���=Z +N��J�Y2'�"��,a%���.Χg��g��2�2�P2�D�M/JP +��y�V��N��bK�9ㄫ��s}WM�n*�$ +�)�c�'��$�K�ln�r�$�d�b��Ւn�\�ȋ��O�:L\��~9�:9==���'A�: p�b����V�F��#�f�2���F�1n�kx��U=�UbL�]�ah��Z��v������x�wn�$?u��]Fc��H^{�������mN{�P�0�5�����C����Kv��Pg�t� 1�!�c�B%����ٝEEɵ,��H\�Uo�ZV�pCM��*%�;������vhw^ +����;��q����% +찷��~m +�}�#���~���w5���\aTS��������P(W�!��5���~���n +��5�i�d�a�~F�m����\�aI�����o&����Ak1���+ޡ�8��^c�Ik\jHZC����] �%13�M(��e7��>������⦄��|tBgj����[!��y6���+��1���H`Eqb� 6��tYat,syn�����M�{t�� '�n������x��Sc��:,�(ܖP(�H�.��9��,zX�# ,�.F�i����))�S�e�s�@�䔰g�G�n�\�zX Qc1 ��;E����j�l6��܉'�\+_U��H��Mp����E����U��H���wg�3���t��M���/��F��j��S�OF�vO����5������:5y"��'�ON�v����d~%�e�i�Nf7�[!� /��{'/u�Ӕ�;�����Ak1���+�ЃLB���&165��ԥ�@�j�Z���x�m�)��e7v ����6b�͌�J(h�''4R���������񬺺�p�o!*ژQQ���6�'��08\���0�c�H&�&���b�$�u}֗ }�`G�S�.�3u$���⺄B\�F���s6�ug,z +\����R�M��)z�J��eyt9OP9%��V��o���{���-�P�*���OM��_���N1���)F�(́�˒@Q��H ��T��d-��5�e� +�^9�@��Tߍ矙�-����A�R ��3)�c�;��n=�ڍp�+S��'��N�:W؊`�Z7'5�bY�Od>ka��é ��u��pЄ��J��%���ƹ�Z�1A� �5LjR�%���p�0-����ZI4A���B��� N�� +�Eݷ�������ja�s�P ���Y�A�8�-I"H��-�## +�NH��\Х-Nd4�*���6#�tp��1��B�X +�'Bާ��H��Q$���V[�lh����J|d4YH����\9�JnV�/�,-����Ct�ދ�{�`U�*k/ �5Q\u�䮓&�>xXFE���_�i���^o����9z��� ��lC^-�����i����� �5�a�{��g�2�j�5~� �3�`�Z�jkl]���(>nǣƨ�4��V�>궏��Y�.Q,��<�c����`E����K2�<�$)j���ܝ���*�)ji�������4���X���`F��_�=�g'�:�����)|,��9�U-��s����nZ1��~�Y�A�BiQ#E-R�]$d|�r��53��w� ��F�Z���3�'_R��B,cW��ӕ����،�����^ YV����ፁ�Ɯҁ��Q�|�pl|EV��刳����p:�'\[Qo#|Ì �4f�IH)`Y+p��:+����Ġ B�C��^��w�ǚxk�S@ +ha�q�=>�ߟ����U�[���9 +�{m@ �Pf�਺ �L�[�d²��@/�y�?6&˻�"��H�97(cy�\���9�H����ïI��=�`��~%��@6I�ȯ���|��b�����Y�% ��e�9�*>`����u�QMk[1��_1�lcbz��֩Ih�����e���Z"K�j��P���{���ĥ'�jgv4����3jv���T�}�����ᬚ�*���KA#�!�Ԑ�y��e��L�Ye� 7ĕFƇ@�� +椑�cY�1�yEń">rˊy9^,�Ϝr��5\���j-i9�$�yFӆwR��A��$ݐI�c��T[�]��t���|ݭꍙ'Î +j){-����|I�:�K��򤊴��1�<�mz�yX�U�Dcm��2E�'�� +r� +�д�u���$�<N�J)̪��1�ʗ���?�5.Rߞ�)�hE�|���1�l{���KOz";O:�b߾B�TΒ��s��\��p��`���Г�z8�6I} MF����LJl�k\l�X�ɋ��1���� +�bo�PΪ_�o]�AOA ���+��p%I����� �]�q��L��1�w��@Ɐ_��:��>�"l��ha�o��I�&�� x�,�9X�mQ�k�42�Eʟ�w^�s}ܖH� 6�SQ�̖H���Ut=Ć�V�m�5T0��0�>SʁZ6Vp)j�m��ȟ�}*PO�����-�Q�:��UNq�� +����=�˧Ͳ]�So+�X~���C���.U��c�$[GX{[V����"�ԘF��ˣ���25�+�E�JG=7@G�X .,x��)��"~��6�k#��;�{� �&S�m~m��N�1��~�9B ��OZhD��E�� �r� ��خw�U}������~;���w5Tx�d�D[t�]�*ezq<���| Q�����j���p�g{bV�S��Aq�q�2�>���M�2�������)��fܲ�a"��U ��&� l�p%k�ˮ�ɋ�Mi�@�zJp��#��c"�Uik���S�D+�&r������?��G�}0 V���ًۨ:��қ#\���&�5�ZG���a���Y��1]��α���1.Y|��N���Sf/Q�2P�2E7���]�S�����̊).�/������,5ś�A�l�/��S\��]�MKC1E��wiKiqݪ�R� R�˂�y��`^2������Uۂ��̝��]��` ]�v���Lrs��Qߠ�7ς���E�������ј�|(��W��� ��O**��i�9������e�D-L���gJ9P'�� +.E-�i5� |L� u�9��;�B�X��X�ȁ��L����b6YͻU�b�bg�oUرzhW^R[���Tyd�mH�u���e���Y���i����1.X��1R9s�^)V�� +nr��� +.��|�]��N�@ ��<�P�Aҧ���8��%����z�ON��(B����|�[��B�)Ti�=s!4��̸.xH(����Ǯï��|�םU�F����s>2�ORc���%���D�Qő�Nȩ5�4#�l���W*$\c���B�JM��������aX�s\�I��夯,C;� ld/��J�P:�ש�L'�g�-J���p� ~4�m#��P�KNu=O�7]�AKA ���+��C�H�W[�Z*D +�(�t6� Ng�L�Z��.�m=x +I��͛L�Ϩ�+4(*��]����pbƕA�W� d+��`���F=1O� �����^"�!��A�S+�f�s>���k�آl#��%��� ��3��ac �� +oZMRN��I��д!���wt`G�86IvV9�K�@�>�����r�xY/�U�1�V��5���سzhg��V���lyl��Q��V��2��?�6<1���4]s�v;���&���qU�f�%��?n��(wO?a�>�vءi��zؒ�����-.�1^�N̏��V�o�6~�_q-�J2��gɌd+Vd�:�% �:YDhR;Rv�5��@�eYv��zɻ�>~�����<�!A.ah, ngv���\D���pC�+R!�����§L��l�b�� �Ef!�ܐB�E2��d \2Rx=��g ^����X���\�z�:�rԹDgL%���$��d*�;M`3���x�� �P��%�B�r�� ��������Ϸ.�ߘ͘�53�Sbaka3�n�F��N�-�[��G��1���xE�L��q�w����f���> c͸o� #�u�����δ�I(�vg-IT���͍%�-pɌ�_Q!19�����%.QY�Կ����W�����3n�n>'�b�D���A9]̥�[�ٌke,܆Q�}'N�ӫ�&�p]C���۽�dIrC�‘�wS-���"�J�� ��U��{�������mFz�(q���Ǩ��{i�%��T �Ɋ��Ce +�{m�Z�Rp+�"��+�}���u������L��Cݒ"���ш�)'�P��s��~�b�d$�i��C��:vZ�^�w�}��'y��� +H�r�0�F1j�s�z�5���qc��1 )�LCA���]�*<�E,e��.3���@�Ή���h�%p�m�����=n�6���%*��fcQdʂ� "�RŶ��J{�R��Ғs�-��挢�L�R��0)&Pp$�b�wy�V���k[�5f2b`O4$L�X���� �m^�RQ*���@�uA(�]F�J>8E���APj�MP�D)�����a|���R�ޞ.&���r�5�v[���b<�EET]tfɊ�r��ɥ�e� +�*bF���a����u�z3H"jW�(L?���Q$�!aT^�� �[�h�!4ua �К��h +�d�oЮ��˥g�������E�o�eD��줢)l����T��h�>�M�D8]L��^�R�9����E�χ��� vR��@)G�C��~�d_�úqv9� p�gxn��^xv#�֢>�>|�g�h2�<1�����f�a���v�L��y�;I�+o�V���G�Z}���<�ڡi�������0Cg������m� >ESZ�;����� } �wC �;�DE8kG�������j���U�@�w\�w�O[_�Kz�/~���nx!C������MZ�����|�z�礥�nE�p�uѦ����W�n�8}�W�.,'v��e�)6@6)��/m��X""�*I� 6��/�u���ˢ~����9�3C��,� �0%}� Ճz�P'��w|��|N��M���|��#L�Ϟ�~8�w�!�O {D�$���|Z�Ex6�;��(a�'�(�T�Y�!�Rԁ@X!gJ�i���n�\�Jfy�BX���S"��͸�E9B�"� �K3�������RCa*! +�DBD�] #XR����%�E��|�12G���cB��n�~�҉��Rsc�^B�35���LP�f�Q%Rd��|*���������9���S���x^�)���Q*��8>8��@� Ϛ��]��#"^L�z$e��q ��'�������� ��iJõ懇�3�D*��7�N����Ό���~ +I��n�|�3��V��0\����[u͈��_p��Ii ��`\�^^&:�|�XB� >)P�d�k56,FeÜQŏ��H���� �������l ����x�Yl����KD�7���I g�Jܴ@� ���mCg� �p2���������MZ:�.TK�q���v��Bw 2��2k#Eט�q>G��5�5W�]���0: әFY���ѿ�@��D��n%�z�U����9�����jF +T�`��ݯx��]S�-;Fѵ�*����N4�� +�usG��D�m����p�s���� +ٯ��iq����F�t/����߮��<�)��F�t�>���]���o��a��ڃ1,8�Z֖I���Re��l6ڝ[k�[y�NK���ihs odX���5���+� )�,�Z|mk�ֺ�ȩ@���9nT�W���5,}x#�`���q�����t�c�}��QkN�[U�z��~G���qx���<"J��ȱ3���;�0K|�P�Tx�0�\�aB��6���D���W���H�� �Y���t�.�}"��M^�IX���~5�_ �a��V?l�4{ �&�*������nn&����{�P�% 4��N�{_�_>e�?��AB� �}:�z~�h���E���~�*��9pn.�Ψ���LP��E�}?�ޠ�V�˕9��&%��yTʬ�ҖT � +((�ԡ��f�{hZ�_(%���c`孼��O�8���O\Q� ���I Sf:P� +�����m�I���P�.��ʎ�:NR�v��c$4�����{��y +1�ı/$'� �S�E7���lu` ��D��$��q l +�s��u@c���\�0�����K���}��.�=���HH�(�� s� ��O�<�,M�BDc����g�qa.F8]�WEX;�Hf�����a�Ɍ������oFLj$8^�N�>T�{lRJ����(���-�}��;�(AB�$�s���f� �L�OË/Ї�� �ف������.O/��pr5�8�>�6�s�GWW����? ������/����x<8?�>�؂9�L.&Ї�Z����`\b��k<:� �G���`t6T��U���ōjײ����q8�9���Ї�6��-��8�����EH2��������I~��P]d2�d ��c��l���x��J� ���J6��fX�Ix~^�[�R�6~��a����H�A�8�-L�+���V�{Z�m�([� 5uj�p K�р�����gg��H �O� + +7W*�EH8�F� +j +���Ӈ�Ѥ³t���F�֝���]�3�{@��l&���UQAdI�~�߄#���IF%Y����Ljẖ̸z���ޞ.�+�g������ų����A��bΖ%���-���ʠ��fX��"syi��z�N�� ��k�h��� �f�z`N�Ib�j���K&*�e������t�o�����6� ]�yeM^Y�WV�V^;ww��r�|�J~ո4Н�L׽�i�꩘��j�{��#t�E���@5�=]� �8؃{��L(��#���e����rVkKRѸ�{��Hl�t� ���RO�����ƛ�7��<%�w�A� �Nْzn��q��RD������s��[YB};���f� ���"E-@O�=��ܙ=X�G@W�Q �%X��UĄ�5Dz�Q�jn��� ��G�a +}3O�f����h4 ���;�~�]˫Ed�W�[.��~ ����y-3��g{�x��ߌ�W�Í_į=�"i��7�Q�����6e#U��ݐ�n���+$N]|}�����ey����s*�J���yqX�e\�mY��׈��2�h���7cc�=���:��L��@���A�*y��Ǐ�~-���rD�M�S�y^�X��wW�r Z5f�!��~[ B��^��(L�'XS��nv�`ɉ�:իni�Um�Qd���������n�߸���^�[I�q�ϩi.�t���L#�i����NY���ZI�4������m��f}�{1i����z��Գ0k+��V���z{wM��gE켨%}��s�<�(n~|B�?��k�z�)x�t��3&�v�+;�K����� +���@�����H[Z ����rf�z��-��e{,%(��1��@25B��x�3����% [���c$�E�wKo�6l�[��\����Yo:�\[?jT y�CVk�j>7صu��i��o��ބ4�Ѭ�XPQa���ڒ�WS6[��[���Z]�6S�6�����\��L�.Ԗ��+�>��|�+{?[���X`�l̑�Y�b����mE2��"q�����CW[�\�(�kst�����ryS��Q�*\���c����~z^YZyFLj�j���Bd6}fǣ�� i��>�E��=j�du?�Ρ�Hj-����B��g�Ro +�7DXEj�p��_ަ�6z��§BΟ^neº����X_�u� ˹vR�"�"@&��*˶}�n�h����Z�Nr�����t�n����'p9���^ꦪWS ��^�4'q�xa�}�m�RͲ��s��Q�JCA ��W��C[���Vm- "�z$o_^7��]v�Z����V�If23��6�����̃�Y���1q��N�xd0³��F1��VMo�8��WL�4� '�^���$�bt��C�m���"J�Zrd���/�[Rd ���&�o�{3���,� B.������h����μ�ȃ<&�B,$���1C�cxH�/=("nu�3b�<�k�n$S_ѐ�+f.W���c��+fI0b��l��̒ u&�Sp�ȈUN��*�;m��8��>������B�ڤ��Vc�$2���-ν�������A�(a[f!�̅l%@N�չ�\G�䉧X�6c�!a�^.�y^n/�]f����Yg�L�m�����ILQ���d��uNɧT�j�5�4+�ˍj�?`2�r� |J�;'d4)�3#6�ξ��3�/~a�����drN�\VP����?=�C�:g�$�H����v��v`�`⶟�^�Y�  ^T�hI���H�L�p ++�e3Ͽ9���v:�'��UbZ�g�hK��o��/�x4; ��$G���bQ@e��r�=)7�>x��:W����-u ����- x��c�ĝ4z�4dE{�a��!xU�yii �,�{ +��p�f�����SF������a��K+���KgNC�z[,���i�S��JC����f���V^���X�6L�h�R��q���eɐ�z�&���zA�#��0���bVp�������T$��E�7ָ��O����`����~߿���Ŵb���&���6��[AV�S��̑9�c�t���k�*�����G'Q��� :??���$j^���9��K}��N��O��g����S�n�0��+��K��N�$n�Z)�����$�\�1��{AY�ɩ4�&I�o�k��n���g�L�Dj�=\��,�ܚRUq��J���%� p�[@T�eGj%�'��49_��"k�h�w{Ǵ�V���e�rKp!�JB��s��Gi�g +��}�}�a���<�Wk�gm�zq��^8;2��ݖ�v�9Bm��P]����q� +9�K�[��Wi� �}��$�<���W����C��Y�����[�f;���Ʒ#��i쾣/��E���^)�΅|ڑ�F�0�-=)S}S��-m�l4�� ��x�G"p=~^�~6M��F3|V�}����NqW>��� �fkR����2Uڿ�\ø��t��C>t��A�ϐ#X*��ۗ�K$^��[����H��;Sw��C�Ԋt��5��RMO1��Ẃ�$�C�6@C#�"U nm�&��ت׶ff����^y� @U��em�7�޼����2Td2�D�[}��Lr�~<5��w^���� dd�Tí�+:1�1�)?�_:���%G�O�Ob8C�4[�瓊.���(�1�j��L��Yv�RT������_��X����:�� �N�������:q��S<��`�i��}��_��]�V�`�Pa����U���@��Z�6U�ȧ&bC���:����KdjL+�W�E$��ZO��E`����+]���)�~ �Ɂ�*0��mN'���ȷE���� +��w�LM_�}a����~�aŖ�m�.����l���Z!3>�.�:(I_ P8�О̛:K�b��z����1�%T�A�`(׉ ���P��~��5���Cya��YZ��a���A�ӽ�'��m�qZC�u7��/Ky����o�~ *������5��S� +{�R��"Vn��۲k B}O�]P�j#1��+�Oȃ\�^�a��!�s��B���X"�$�=q̒_4�C�N�]=��.�c��D�x�O��,�Λ�9;28£���d*��c�� ���E���7N1� �Jd\��\T0���=̧Ϗ��D=E�� fr.�˜r�J�6E-�4��M���gzeo9 +��>�-�O�90 ���n��/7�j5SG� :/�Y�a��AkyIC� ��C�3i˒�2V��2�/25f�O��^�Ey;~�dj� $���Z +���6,R����;�7���\�����C��6��Ɏ%'�(J!pW�����8,�aV_aJ�F=�5mN�� W��0(�Hݤ��o���uQMo�@��Ẃ��HP�@hZJT��B%Js�������Z�cST�W�uhs��x;y�Ǫ� 'cQ( *lt����͇l� � ���[P�(��%7t�@���� �-R��'q�-�g 0Aqt�9��9M�� et�j��S�*+�t9�TxS��p$��Z��`��mِ � +/;T�n�%  Ӿ�w��Ϳ��qTkLKT�c���+�g-A���k1��'����B��`Y�,��v#�$�ùtǖV���k��e��?����μ *�N�(�d�$�bp� �v���μ+x �K��.ʿ�a�� +�]��ݎ�FѱW 7�=��'��zc�@Q;��i%�Fӧ���gZ����k����U���2P��hԥC���l|�ы���v���r����W2��g�[Ҹ�� ��<� z�M��n۱ ��� �մ;4k��dQ��%j���e��^��m���Q/�4�N�_�?�SMo�0 ��Wp@��A>���f]�+Pl:�;(���ʒ@�ɂ��}�'n��t��G>�'��/=䨍bL�0iy���p�1�$�~}�YR�� ��\�V8J�A̜�0-K�Tg��-µQ�Y\(�x�hϣ�x�� +B��7���"��+_zt�`��9hg�iQ��#�u R"�1���mH� d Ǖrvޠ ++�u�w7���pK5¤Tk �����$%H\�A����9d1%͸��P�#�o'�3�P�˓ҽ�Q�o~�o��R�= ߥhA;��w�&��TQOA~�_1Mh8J�*jB�Ѣ��>XC��9n㲻ٝI�o�8�8I�>@��f�������,$$:�=;)x�+K��K�u��a�I�T҃E�`R��䂎#(cWN�2�X���i��B�B�=���t1��>N�4E�5\QNN����f��U��:a4;9��8_6�fpF��J��vh%iO uj�Y��=�BҲȻ� G�P��2,�C"��%������N�l$w#�s��}����.&ҋ��^Z�΀��j�B� �JϾ���I��������$Tj�*�L/��B�a��='͗9g�S99������:�@��g���M�w7���S���V�$g��X�y�Ρ��z|�؟\>���)��.a&Ya �L��<��܋��n�  tUa�U� ���4�O���M&�� ������R~8����y% +g�>�v�[�> �4KQlR�D� ���ԭ��*�d +��q �'���T�hUɆ�s�!E婷���*��Ž�3�?�I�*��uz*I<��$w��u�6\p��r�G���/i�����S܏��Yj}�Ʀ@}�Wд����-n� �~�!5>�fgˬuЂ��a��0#�� �r~)Yd�US#�?W����A*{D���� +�ն�ڕ��Ϻ}BG�C�J1W���ΚÛ7��6K]3��g���-� �Y[o�6~��8�Z +�e{�7]�a�.h� �-��hQ#)����>��(�"e����%E~�~�ѫ��6�FFR �������:^L�N'p +�[*aM������vK���̎+�? ��*��ވ �F�?P( ����rU?��rw�"RQ���X��W�^�̷9��$K!�tU(.dE�;.@m�c�X�z7� f�fk.vDQ�� gH$ž�9�����ۻk eS[���HH�,ia +�TmAi�%/D����l��ʜ$�["n��F#�ɤ���,є�ʇ��Ewq�z�J(��#��d.h����?�x�p����oy�����s}�|����2�{� +w�K&������������;��gR B3uK�ԢL�J�$ +F�,����ٚn� ���)\�vL�����sA�D!�$f�~��tO��AͶb�h����!1�DE5O-&fA�!:q%���h�/8��.��:���TPD}���D<�K��Q6^S�8�j���6yt�IEC�'��8�w�g�R�oE�1>�=�i[��4���r��-�aW��?���R��H�O�}d �v p^���� *�P �!@�M��� a���9l���@U�l�Y�OπL�a^�ϗ�@����h�7O�a���ި*]ES-�t���3}J$Mk2�u�ٱEǶ�(? �M�2W�ys`6� �|I�4�D��� pS�`��ARA| �`�"� 4��!�xb���c���j1r��X)t��@���Q �a�D�S8��0��v�x�G���ؖ��R��*�M��✵���!�VK�@�"/_�s���3�0wP�����Sd�l� �4�`��e��{�[�(��l��=8_���r�{�7+�Y��m��6~->�P��ߣ��2��btmqS�s?ֆ��'4s"0S��%�W[�Roz�:_v\�}�w�œ���ռz�+�I&MFr�o�� �a?���hz���w����+X�"K�f�������/� �9�7��f��2|�[�e ��֗� $M�6[��M����1D�}lI 8��U?� ��6w`jѐ�ft@�K�H���q齥�`�U�K��T��c�LF�9!�S[�Z{.�����콧����"�k.�$[�Ӷ�ş��hzvf���)��������0�x���+������;T��v +��/{�9T�[&����]� ]Ӥ׫U�^\\@V0v���mw�k�`F:�y��d�k�!����u�?�ؠjZ�.�10���O���,][{�tmY��8����T>�^W�,�� a�1Euz�9�3��QZ*�^е��R�j]� ,� +l�wb��7s�H��V��|W l�G�v�d�Ωi<_R���+��C!��1 9���EwxУ�f_�Ku�ћh��Η$�1K��8s3�=����5��e���n4���L㬷�s�E��rϣ��_��g.��D��I¶'Cc��ͰWVSM��k���+o�� �C��%a�9�d\�\o!+�� +Й��-���׭�a0���(��{]N������4>�lw;i3b��s��ł�ո��a?������ ��������~R�oc%d�-��?���i��{�B0�����toHz�۟�j����t���/�%�o̭��-4M�{���}�G� �wH�*0�ɽ�PH����B���d�1|��Z�[}+~��\=�x�-.>m�HV��~�����j]�eK��1����mA�K�d!�O8�Z*hA����C��kuue��3��L����/ o���̺˃_6�tTbԏ�x���_}�^zrVJMٵ�`���+��iJa��{��֜~���:��!&�3}�`zOo���Zy��8�8��!!R�ˍ���.��Q�FS�A�q_�J�x���Xmo�8�ί�*P���r��v���M���m��K��8C�c[���N��';BR��O�?�3��h���&5#��b�� +N��0�� [�n �p� +S!��,���8��oA�i��"I "ށ/V!|�L=�%G�*<�,�c<9��0G�)��-��©I j#�S1p�ȊINں��\[�a�K |���PSm3FB�C0�Cx8 箯Fg�o�2 g�QX�2T�������p�7lm+�G�kލ �g����L>����yϵrdsNQ0�OUyU���b)��j���)�+�cP����S�?>�z'8�E�f�&oڈ�������R�RΚ�:kΧ:{q��ia�ld�K��*ע�'X��h B�~�NR����� ۝��Xإ��3@ R h����w�ų�p��&)�*XV�� �k >�����ofEڗ8�� =���H�]jB�Q�o��vE�) ��&��Z֦J�H���4���m ӟgr)t;��&�䔾����GYT�h���@�f��P�I�֜�wT =��޻��b7q�����q����LL���ȌOw�ƽyS"��s7�-fF[f�h� T�~*�)3�3���u�1)CO��4K�~y�:��u���3���9���j�5���?�R�K*� ����:^�/���~�n���(H�C�F<�v �.�Rj���w}1��*��s���l�1�0���Ŭ86f[/�6��Ntu�G:˘�,q� ��x +Q�i^Ʌlt;:��c��]Α�=rb��0�N��N���-�"'����*��[�r�� ��B��r��P�yB`C��5��RZ��ݞ!7at���O+}�Z�� ���C�v���}�1#6x�{ksG{��u���b�:Y�ew�0�h�v�������� ��ȧ�"�P�Q��}؜Y6Z�&&sp �:vpGxO�y�aӠ��w��8jl������Ch��;a>���q�����<]�F�TI�f=(���?��K���Vr)������(=띸|-g���Q{�Ƿ����zj��U�o�:~�_q*Q� �ޫ�'h�����v�jto�*��5c[� m��ծn�������oޙ�@�\2��#+8��֠K�N��� �<���BH��0K��5�� xL��Z�� b��{��%S�ђ�f����7�a�s�H0�X���߸3�Am$zG`*�Y1/H[W|�(GXR��{o)8*� �B�#���D��7!������l�S��(g� ���l�@�y� ����-_G�����rf?�o��q��R�Wg�P�����AH�m�*�G��9��z��fZ�\p�f��D +T�{��b���צq��0��=��QeGa������F�I��q)��Tq��K����p�قS�@܀8l�*Q$U���yʯn��[�8��NZ7�W���2dw�[�q�g�� ���G��Wn�';˪�����+�LFab��k� t6t���-k��&�ns榊�6�]ǼD�[V�Q��NOv�@ +ZtF{9�Ǽ�|!װ�,9��g�s��i�^k^���n�Q���W)����W�? r/����'@k����x�3�e��'c{%Q��sl�q�O'G�&#����0�Z6q�%���h�.��g�]4B����sǵ��0��'���;��K7*� �ˬ? ��enFV�e� ;�zO�����i��ۿ��Rb� +�'^�q�}��W�&�Z�hE�J�$z���ο��H[<��\���{��m|Ɵ�w>ݮ� +���9����_1ǧ������Z�ʣli�ܝ~Y�����IuϽ�ǃFگ�A#�ϺQ����l.������;�E��QMk1��W�C!��$-�y�6mp[C��R(Z��WT��̬]S���n���ԹI�>����e4�e*D�;���L2WV�����;/h} xA��H-:��+�q����S��'�������*�Y�tS�W }�`E��6��Ę���&w�R4ac����{M,φ_C;Bۇ��>��w��c�xcէ8Ad����yw����j1H�Ŵ���4^�����k�K��\j���M��lᡳ�L?����e��� Q���L\�-�Z�+c\�"Xĭ�7�.9����@����+oMz��,��&eb�>�W{Qڔ�S�g�a|��5S�8�O�v��e��|�����Ey�1 ����c�vr�5���6�> ��A��z��9S�����R8����T]O�@|���J��M�׆RH0M�A�PPt9�����:*��ʎ H����������?]� F�a�3)�^9�?��ݠ� +����(��<8A 6��Tͱ@��[�"5K�2�32=-�{8d�t�yn�xrc� +�J`��~S8u�C�4@&i ���l�W���S�$��V�@k%�xeK�`e�8��#�.��~��hT�1N�Bx��_ka �)pa�ۜ$����r'0"C�D�N �C�H7r_�ed� �H�&O���Ry�ݷ�XQ����D|��ڸ/���]�Y�*� �q���ù"�li� �D�Aj�=�+ۅ��\�5\2��C��7�Z�ZpJ�9�,Xɪ�)�.�j%���� �l�;�_�N����;x�I�]\��E�iL���]tvK�KP����l�[���Z�K�>�fcpu�� ����z��H!~��>����LXωS� ������5C��}��S 4�T��f֐�U�SF��fcd!�����Ƞ ��hT�k��-�u�7A��E�m��,�}�|�>w���Ֆ0�����?�?������ڟ�����DY^������p}�{ʜ+�h�{c�>�M|3�2�V�����2��7T׾O�L�6˄��ύI�ٮpc�"a��ذ���V�n�6}�W�$N��SGi�� �� �}�M�,biR��|A�/��-�N�]>Y�˙9sH���,� F��Ð�)�S�dH�O�agpҁxLA�4�"Ȅc� ܧj�g(U���a%bE%ưR�����N"H�-:F,�2!�S�n�ׂ�a�����H� �9��~��׊��m3�Q�[�"�CNm<Z��3�P�*��J�dC����C��m�밗�1uv%f��(#4H-����V�c�TΚ\3�����w`prWK�`�F������̩�`�.V�a�`r��� ��A�*U2��B�����V�+�c-qv1�3t�k;�����*�rȹ3`p�: �������C�ت�{�a�҂��M��b�K��B��-��w l[�5�3����y3AZ!���;��@��RA7v��Q{f�����U7_��A��n����Y���ث�^��ס�*���.��]l?���wU���7Y�������೅�.�-!�2�;%�)U��jWB�ʧ����Rڐ����,��id�u�9����t)�5�NHVK<�j'��h�E�C =��4���sk��y�r�}F5��^p�h�K" �csFE�,�H��]t}��_8}���Z�)X�n�K����.��b�K�D?0��!T�{�y�ȵ��?�e��yy̑���Z�ʽv�J=���J�3e����YF��&�_��݃U�@[�ը�/�����x��#!G�K�C�G��A�GN�EP���;�3�.[{��8�4�ϗ��_�? +6/5�ŵ⑍�7�2]�F������Ih�5�%lQQ����7dT�P���/�X�R9}�WtRP3��8ɾ손%�Y�J�„d�Py��Q!K�R�/��߷47����[��Q�Q��Ea��it i�G�����A��ׁ=� ��1�@�4��u�g�߁D�DEK�'!��wᓖǂ�G�d���F���?�`�#f�3 ���&_8��U$� +��J�棘�6��3��B�q,��w+-��� p9Vzʈ+كH 33��D�����r8����Q���@�M� �s +��ፊ��� ?r�#�M�|��� �#��A�+) ����w|q��柃����fxquY^ޞ^}�MWƱ�-p`Z����Kܐ�m�*�T[ ���ł<�S�OI� ����,B�LO� �K�P6V ��JzB���v���ɟ�U��ޤ�֮6����D�ו� �F��m�HsI���"�Q<�n�Hd֎Y�)T� b��1���^��3.p�4���\�OJ/� د)���'�3rƵ�S��:��=8�1��߲ͮFZ���;� 5���A'݌G���y^B+��V2�n��~v,��XX�D��N�%�߃# +��Wq�d��`&H׌�[u��S&���� �����{��SF��Ă�0���e�uv ����� } �涻����M�;*�(����Ӊ +��E�~��b^›r��'�H���a�2���"�Xt��0�T?�g.�$�N����|��7{� Y#�Z&���e3�o����k�ҹ�b�d��J�ެ2�P)%���2��K�s�4��Ϊd��U?�*ׅ�s}~q7�ί� �� [��:��ֺ +�����x�8��&s���\%�{>(ߔ~�rҞ�&V���lZO���h��k�X���^�W�d���@{�t�Ke��b  �\����5�O ѽE�KL9�&9?V{b돩[�ޫ�~�Q9�Zfo�����,s���4z�‹�Y�ܐ�j<-l- �e�^��Das��V��lQ1r"ٮk8�|� �&H�\2���ut��ro_�3dk��r�݃����8I4�˜)4�T�6k�Hn)���7�� ��4c\�u���uA�OmO�>KʛX�6s�jS�rU�gc��* ����*����y�Cm�M��N�*B͈�I�v��Bnۃ.6'�h�7m�yi|�n��X���c���=�l{zn.$�}����iL�I��w�r��4�Jv�17���7���X�j��1��\onX����V:�<7�����Qy��ތƵL�T�\'�.�E���Ɲ\u����遣���Z���'�X�yg(��o�|�IɄj�a^�</,�*��}0\�?���O�(�S5��>ߓm��!�!�?D��J' �P�X�]�aU?�V��FO��&�-��=��4�:v�����&��S��|�=q^lC�n>���p�*��t�R�~�|��ӏu‡(d��|����EL��@ $����jdE���NW�i������~�J#��d�*�2;����l�G�h]�����2������ť!&}�6��$��;�{j�S+��lD^��K�l�г<�/Am�$�rs'��5=�J��ߧ��E��JA���uLB0xN��1 �ǀLf{��ٙa�7?��.�s좪���=d�Q� ��@���w=g�����LF#�y�,ȶ(R�����e���^1pC,J$<?��`fK���2�Tt?Ɩ�V�m�3�T0��0�>Sʁ:#l��R���VS��§T��P�!���w����8֩4V9�1r +�ӱϽ������[Ճ����T,�]T������8�K�yb�mH�u���e�v�G�Ƹ`E�hկN�rw�+��*q�5Up�̷�E��JA���uLB0xN�h� �c@&����ٙa�7?��.�s좪���=d�Q� ��@���=g�����LF#�{�,ȶ(R�����E���{��!K$<?��`fK���2�Tt?ƆvV�m� �T0��0�>Sʁ:#l��R�»VS����T��P�!���w����8֩4V9�1r +�ӱϽ�˷Ͳ[Ճ����T,�]T������8�K�yb�mH�u���e���G�Ƹ`E�H�����(w'�NJ�l�79PCQW��|�E��JA���uLB0xN��1 �ǀLf{3��3�to~�]6s좪���=d�Q� ��@���=e�����LF#�{4,ȶ(R���=��Uʧ�;��!K$<?��`fK���2��t?ƚ�V�m� uT0��0�>Sʁz#l��R���NS����T���t!��������86��V9�1r +�=��{]V����_uSo+�Y~��Ɓ�C{xI]q�� ��Dےd�+o�2m���AՕP���nqt��3@G�X 6W��������e��E��JA���uLBH��1 �ǀLf{��ٙa�7?��.�s좪����g�Q� ��@���w=g�ۛ��LG#�y�,ȶ(R���M z�2�s���!J$<?��`nK���2O*�cK{+�6�Z*��EXd�)�@�6Vp)j�}���_�S*PO�����;w`GQ�T���9����^����vխ���[�� +*��.�pd��^R[���<5�6$�:��۲N��#3c\�"X�%�N�rw�+��*q�5Up�̷�E�AO1���+��xE�$Ɛ��� ��m,mәe!��nE���޼�fz�]�e�pO�x��z�,�7�I5T��yA�� 2E��v��� +g�<�S�N�3}<��x ?��`J%�lw�G�����D=Ed�zB����޻cp��R���r�D�B�:�sO�e����Ug0�Vq��:�w�8��^rǎ�r}A��d[�ba�-������1.ZT̙����_:*�Z�����Dj)��O�0�� E�AK1����ؖ��U��bA���fgM0M��l�"�w���� ߛ7o�����e�rp�����j�0��M�� (���·� b���7��1�8�M��*XZN��.zV��{��h� ��c)����B�D�A�T���V3�o�Cf�'4m�p�=��$����G�!�)J$+�S�n�=mכ���_5So��A~��F�C��[v��K�I�HR�#��m~>�0���s�l{�7=�9;*�A��R��ç�2�m�Ok1����"������T("أP���&4��̬(��e�����o�͛�C� �3�X�3�&�D<�� 5( �j�v��IgA����@#� ���ݻ�L�9����1�9м�գ���Q�Y�x��2�|��&��SB� +&ɮl%f�1\� �����׽��3��Bs���0D�ppt�̽���n٭��Gͨ_���щ�t�9��L�n��*�8iC�Z��q�H���������P���B���I�I� +��7�S@jK�L���(�؆ +3L +�o��KO��P_�u�Ak1���mR�^m'uhK =�X;�]Kbf֎)��e׻N(�.B3O�7�O��(��$��8����&�$ :����%�W���� ��n7��OQMoI����l�u�(��;�fs����6�Gk�U���}7�&$������ �����'z���=���oHak$�E�^�E�KkA���+�"JΚ�(�r��lo����0�냐�v㱋���z~�\BE֛L��V���Hno��b:*0«cA͞��d�"��:>Ф@�X�t���;�c�'o�'e�M�P^�IEwc�4�l�����\�����3„ +6�\����b�:B�z��޹=[ +B�P����H��L�>��^,7�e��SgG#�X~��‘�A;x�m�� ��!I����u�����F+�����YM�iy���[@'�P �W��䩡����U|?E�AO1�����@<��TcH�HbJw�N�Mg0��nv9�˛����6���\�������g&����td0«gÁ��l�"��x>�Ġo,S�,��7�]���`��m��؟�IE7clioE�F���� �޵;�B�X��X��ȁ�L�~�y�\�lWݩ^L�U��b�eQ�#��v���.Ug婉�!��6ޖu�����#���(w|�I)V��%�&j(��~�o�E�OO1�����@<��"*�<������n�tf��w7����y��mv�LЅz,���S&���O�x�0�����gd]����=�:�<�S�;'�>�J$�?�c�K���<�,� ���f�:�*��Y�e�)�@�:Z���m#��_�c*G��`��[w��"|�R����ȁ4��]�u9_����L�4�z��"��i�95�L�g䱊�&��VN�e�t�(e�f�Sl��N�CҖ��h(�瀎B�26��9PMQ�K}�E�AO1�����@<���$FI�HbJw�N�Mg0��nv9�˛����6���\�������g&����td0«gÁ��l�"��x>�Ġo,S�,��7�]���`��m��؟�IE7clioE�F���� �޵;�B�X��X��ȁ�L�~�i�\=oWݩ^L�U��b�eQ�#��v���.Ug婉�!��6ޖu������eur�;<�+��q�5Up �̷�E��JA���uLB0xN�hH0 �ǀtfz��ٙa�7?��.�s���?�f�eX6� +D�7����rw;�V�Q�ޜ�>0� SQ��|S�O,R>��3�c���@� +fT"��}c�~�-�I�S�3�\0��1�.sʁ� (Z���}���p� +�1�6�z��p��u* �Oq���q�|��^֋��vٽꋩ#ő��/�-�^�+/�-�a��T�T��L��qT�i�/2�*H�(J!����vy2�; >)G+�]-��� G\ͯ��E��JA���uLB0xN�h�$ �ǀtfz��ٙa�7?��.�s좪���=d�a�*<-�軞3���pZMFFxs^P����LE�jl�?�M�ޱH�\��S ��%2��O.*�Q�<�_���clyO��"V�r�L.�<��)haR�����"�ϩ@�nC��o������N�!�)���0��}�e�X�n�ݪL)�$�^~�����A;xIm1 ��yREjX2��QY�]��iU�@"X�oW$ny2��;�'�h����ᨂ��U}W?E�AO1���+��x%I�!��3�����6�Y����U�8/�͛o��eX6� +D�7������j8��� +#�8/�}`xA��H56�xR�w,S�,��)f�����̩D^������[ޓ���Gn�`.ga�]�wFP�0)j��VS��‡T��Q�!���w�� Ga�X�Ґ��ȁI��>��^����nU��GX/�]lq������I��<�"5,� c㨬Ӯ�Ȭ�L �あ��MX� �� �I9Z��"�&n8��"~U��E��jA���uT�HΚ�D�s�8ۛi����?B�=�&�c��Q5y�>�"l��ha�z�$��� x�,�9X�mQ�k�G�3���/�?����x.��l�SQ�ĖH��U�U�8ĆvV�m�+�`"Wc�}���A�X����wME���T��PB��������8֩4V9�!r +�#ө�[-g�ͼ}�So'+�X~YT���myI��.U��#mC��#��-˴�����~V��/,��gG����R�ۛ�M�PT���2��E�_KBA���S�GQz��#�B�G!ֽsݡ�������ǽe���̏s���gT�-�-��Cϙ���?1���5 �-�Tc��@#��1O�\x�=��C���`�'Lm�4�^�QEwC�ikE�F<Ӟ +�rf�gJ9Pk��\�Zx��T��� +��}p����Q�:��*�8Dd�p`:vw/���m�h_u��[�� +*�_U8�zh^Ҿ8�K�%��Dېd�+o�2m�F&Ƹ`E�ʻ��'G�@'�X 6W��������e��m�AkA ���+�!ۤ1��N�$84Ж@z yV�:�$�S�����q ��HO����Ki +*���Gj���PX�?��n:q��gu���($�\� ;�r���$l�ȏq+�q)�b1ł$�rs�_U|s�gސZ���ܲ`��²4�s�� A����$lZˢG�CXè��?�:�I!�Y^�BN�(�I���~��������YC�=)���+�5�.��V<���y���򌧆�1���;�*c��t�s>�*��������Շ�f�*=����d��$B��U����m�n���<�6��a��9�I�m4���y�.�l�n��ϖ'A!�d���n����m�7ώ{�y��J�{r{wE�KkB1���g�"J�ڗ�V("إPb2� �MBf�J�{���.�pΜ�f��]�%t�K�F�咉���5( ��<���YA��u�H#��1O�R���L�%fA�O*˜���p�G����A�x�B L�*V��Z|�C�@� GO�.���/6�E���'Ͱ������AZxNM1��y�����6���e���G&J����I��j�.�ej�]� ���Y(Z��&�:�) +�&~�o�E�Ok1����"J���V� +E{JLf��l2�������v�q�͛���˰d�.4`)�Ȼ\3���p��#�ޜgT><#�"HvΟh��9�)_��p���D�s��0�DZ�yb�a�=5��/�P��{a�]���F�haR�⏍����T �P5!������ E&�X�Rk�)��i&�<����f���W��L��5�z��"��i�95�L�=�TE]gm;��&��̔2A3c�d��hWC�=t��q�I�΁j�¸�_�[�E��JA���uLBH��1 �ǀLf{��ٙa�7?��.�s쏪����g�Q� ��@���w=g�ۛ��LG#�y�,ȶ(R���M z�2�s���!J$<?��`nK���2O*�cK{+�6�Z*��,�ϔr�N+����T�/�)�'�mp��:��(�u*�UNq�� +��t�}/���u��V���[�� +*��,�pd�Ю���8�Kե��Dېd�o�:���̌q���pur��@'�X vW�M�PT�~�o�E��JA���uLB0xN�h� �c@:������0ݛ�w��s쏪���=d�Q� Tx Z��=e�����LF#�;/�}`xA��H5����ƠW,R>��;�c���@� +fT"�w�����16�#QO/�r�L.`�]�wBP�`S��w��"�ϩ@�nC��O���[���N�!�)���0����u�X�m�ݪ��:RHPy9gq��W��Kj�e�T]*OL��%�e��U���c��a��,��sw ��+���|�7Up�����E��NA���uB �AE Fc�x$1�l/3qvf2�����fW�c�����}v�@�{��[��sf���O�x`0�����d*�Tc���G�b����S�l%2�O.*�R�<�^�Q�wC�yK��"�y�S��Yv�S� +A��MQ���5� |J��>���V��( �TR��90 ������,�����ꊩ#ő���,�p��myI�b6U��c�a�d+Ge�6�G&��@"|�]kw^�,���I9V���&n8�� +�̷�E�KkA���+�"JΚ�" A0G!���f��3�t�B�{�M��.�����m�5�` D ;}�S&����td0›gAÁ��l�"5X{��Ġw,R>�����D�C�� +�D���󤦛16TYQ�O�R�\��]��R�ac ���ZME� +S�zBӆ��޹;�B�ؤ���)��Y!�}�e�X�n�ݪL�U��f���V��%��\���S�$[GX{[Vi�df� V� +��<:����R�ۋĻhGQ��|�E�AO1�����@<���$��HbJw�N�Mg0��nv9��{�͛�m��` D ;}��Lr}5����`�Wς���E���|��A�X��Y��+n�� ���**��i�?ϓ�n���ފ��x�� +�r�gJ9Pg��\�Zx�j*�|H� u�?�sv���N���)��Y!��}�i�\=oWݪ��z�8ZA��ˢ +GV��Kj�#�T�+OM� I����Ӯ����^�G�N�rw�+��"q�5Up�̷�E�AO1�����@<���$��Hb��m�Mg0��nv9��{���mv�@���[}���r}5����`�W�� /�T����O z�2���ߝb`��+�q(~pQ��J���֩4�>�1r`����=������[Ճ�#ő���.�p�����X�M�yj"5,�,c㨬Ӯ���H/,)xu����'�X v�79p�Q��|�E��JA���u��I4"B�c@fg{��ٙa�7?��.�=vQ��_O�Ϩ�[h$Z�雞3���jbn� ���Y�p � ۢH ��tc08)� �{��]�D�S�� +��D�W�����1vTYQ�/�Q�T.�<�L)ꍰ��KQ W��"��ϩ@=��B��k�݁E!plRi�r�c�@V��[���nٯ��[�� +j��.�qd��^RW����|k�mI�u���e���G&Ƹ`E�"�f���Q����b-��K��@-E���rWv=�(��I�<��I���Xc����2�E�_KBA���8�*����%FB�`�$ĺwl���ef���{\�|��9s�7�� +j�� uԄ��۱�^_uGհW����� G+�C�`xG� +'�4���G0t|w��ѥOS��$���󠦛>��vj��!�X�¤�B�Dj�p���Ʉ׍eѿ‡,�@�41�����Ȟ�8m�l�qN}�HN ;��)�4�Ξ��v� ̂3읢f���{�k�57� >�g�a�ܖ�8OX'�:}dTU>:U�&mJ�bT��(�4�IM'�<��0��(Պ�E�m���d���U}W?E��jA���uT%gMb"J�CNBg{3Mfg�����w���XEuTM�Ϩ�[�'Z�點3��Mb��^= jd[����F]b����W�\%���TT0�%�lwѣ����Ί��x�=L�b̲ϔr�6+���n���p� +��}p��6�Q�:��*�8Dd�p`:vwϫ��e�h_u��[�� +*�_U8�zh[^Ҿ8�Kե��Dېd�ko�*m�E&Ƹ`E�F�kvi9P�89ʭ��b%�^-nr��� +���6?�S]k�@|ׯ��HƎ髝�i��Bu �|Z[G�w���v(���$K�dC��ܓ������O.s����cL��_��n>&�h<�`?2E�RA8�� +�3�ū��;�^�Zg �L�7_�0��3���o���gCX�R+a���ᚪ���Z�1�0)Hkثe���a����*�d==Tk%��2+�7��5Cp!lLJ���b���8 ;A�**ga +;�pX�l�%��i��82b��Dx΄�? E�Q�S�ed� HR8� �~=�$�(;!r^^���Ҹx!�M�O�HjA�=� ��\��J�'�q�=i7a�*0���YCO���2�Lj��H��rx� �h��r�a�W�wA����X,5�k9�`]7�"q��OЧ�a+���d�h&hQ,'I��Z�66d��!�l�.�!��l��ȹ7`pב�ͽ��Np�A/��^ˑ���� ��3owG7��]0��`\�ë�����<���1��֪�a���C�b4ò=IU��Z���ښ�w������-V~.�qѧ H-�P0����M��&��'�'�'��67i�����q��?�_o�����R՝��{���.����Q�1���W��}� .�*���&F7�R:���M{ K ��t��+:os���{��q�CER O��^I^��Q��ꏊaY���Z��&�'<î�P��h��u�65�'�x� ��'y oh���o+�`A+X ��c���Վ�Ӕ@SAZ�^�"[��{��5a��|rO�V�L (��~+XY3��$a�h߼�x?�Z̓T�k�؋�J�֋*���>��%Aڪ�<,��RpBj������QQH-B���d�� ��� +�%�;�L�&ɤ��j'�p#�v+L5*�i�8<�md��U�����xf+ʬ�+�$�����KiM`%��b�&������^?�J�M*��]~LN�\0�� q�t ��}�Ϡ�"JI!�����V��扣7ϥ'����x��$���O�LoX�-^b���o?�|x��s�Krmp�����)f�%�\>�5����c�m�[k1���W�B��&�k�K�ͅ�J ����0��F�Ihf���^$��R����f�ӏ�F4l:J\�&g�N�"���pZ�GF�j��u� "%Ehqk݂�+��������� q�<�#��� +N)y>�o�� 8Č�$���{N8�m�,��!v�A�� ^����l +~ + jm�u0��9�s��0�oCz u�"vL�X8^��˫/��lUSK�% '�Z�`��Bs��d&4ۖǕ��H�qk)݄e"Ӫ�%sy�+Ò��ߡ������{!19����Z穃�H3K�k ~T�����K����;g J��ެ �2xe��Y�=��<<� y^��O����:C��4�e�i����G��~�3�),7~A�k��գ�969�5j +�&�v��^��߶�kF�`\�Ե�_��Wƿ��9zGG���oVc���v�B�Wa,-���̾����}PJf��G��{���{�ʪ�m�[k1���W�B��&����m�@)���d�l$�HB3k'��"����Eh4��7#�}�&�!�T��%Y-w��?�폪��_�e��,#�$-n���a��1�)�{#�u�.��) �L%O���������L�X�qM%��&pM��D(�@/��: ���B�B�9�������`}҃�>�#ń��E�}�O�L'٪4&F ��X^բ +����%MСٴ<��z �Jn�J7�G�Ȩ�:�\^��0����PLt��D�{�1Y/���Z땃v�S���O�У�o����+���Y %V��ڐ�� y)�ﱧW��)^��J$]���k�]�������Q�,�� ����bRX0n�\9�d�ɣ��I�9�l +B:�w J��D�6�-m��bl�ն�_�fX�����9x�ڟ�'�{C�����B�Wa,M��^Ͽ����}(�Jf��Ó�q���~�s_m沪�}�Ao�0 ���<�`i�]�4�Zth�a(�a�-3�0E�H:A1��r�&��dQ��#�=��]��l@�J��Օ>g��uc&##����/�����ҕ�Aq��3�'�P�>q$� �� 9Ң=��:��aI-�z�pO=1��XXd�)�@E;�)*����r~N ��}`_�E��(>�oP}�cȁP��vCߗ�ۻ�˻b5,�v(�yٳ���WZ��Գ%��;�<17$-��C~H?�Dcz)sE[��P܊~��1[QF�`�� +ݣ8�hCQ��o0�`�E�2R|*�R�취Ż1�Z�oO��ʦ(ʽ���=��uy.�j�s��3}��®��潬T��-/�F�,`UO����L�s<����>�j��"�#.��ڔ�߶gaWg��Y���4_����m2'%[~���2z���w�y1u�Ak1 �����n �f�m�%%�R�c�h<�Xı]I�%����I7�қe}OOOۏ-5�3)��U�������z.NN�-�a��C#u� �I��<`!v���<$�*��I �:SyduÖ���p��G�p�=d.Tp�3+�v��j�qm�;*#b-�2�^��~� +O�i��ͽ�Y"cH��>�K-gh���‡E��nw�u�G-�<��@�Q�ՋG����:kd�:#_�BOl�"�>�����E6!Hq֩7n�~h�%œ\"��ľ���Z��/�/8�����yڄ�o:����:����ϙ���(�TO�}��%�Z�&�]�Ok1�����:�B�!�4�Hi Cr,��v�+"KB3ׄ|����M�ی�~��hq������̕hvV�u�X.�Lksvjp��� Z�N�(+b�e�^yn0(nc�e�����:ƍ���Y ʁ�Vc=o�� ��"QG?�猅����%��s�B�f��5f9���1��{أ{Q{g9Å6� ��a�䙄��x;�{�����xWP�`ڑbK���ދl�v�2��>[���8� �aIdˎ�}�=l�6���+�� ��̡���i] �I7$�^��qPg���?ʡ|j�H��; QRg�y�9n�څ%�lcn�� k��ҝa,��~za�����M�#/��?u��`��������������փ�}�N9*۲��k֧]���G}� � ˚��w�]�OKA ���)��V���ֿE� "�GA��lgp��lk���j=�[����2����a�Hx�&�۫�+���x�&��X��hcbDE%1��!n��aP�K�K��ȏq#�q�(���bF��z}�O�<��פ)�;��P���r��{!(7�%��ugE�x_m���W��9+#��;Y,�51)cy7�=.�wO˻~�`�v�h�����.Z���t��4��靵�g<�Ey>2u���|"U�2 �Mg��E?\�0΍�_��@�b�{|�e?46l�}����qy�+�C�:�8�M�Ɨ�r�]�OK1����f+R�j�*D +�(H��m�qff�E��eW�����e~�evUcEC!{����`����g�;=v8�*&E�2!)�Ci���&��ԝ�M4��7„�����3/L���y��� �i�ՒgG��7��~�����9�����7u�Ak1������LB��ۦ1(%��V(��ٕ�2�YS���n�Bs��{�ޛ�� +:�����4z�i�����j�.���bE#VRC�q��fb��Q� K��g�M"yb��-��u{z�w�q�n�Z$�Y����u �sI<� �೘�v�����6+,0�1%�����g��(}�g��e���*c�0���oʹj.f� ��b}���h6��yT��;U�pB�\ y�} ��?�l��b����#���K�_�ئ�я�dT1=.�k�bԴ�B�sڸ���.���[�Y��%���o�=�ZV�7�z�`�a�ꅅ����>�n�~�?�VQo�8~ϯ��x���K���I�^��3k];�'�j��~�I +����'������1W_�$��dCKFpZ�K���g��4`� +!�����I"6�)��t�b�:!yn�B��L}GC��Q�]�O1^7a�KfI0����-�i��N%: 0׊�Xf��� ��%�LJ�o�-Ge�Zi��HhՄT"��[��~�F����O�F�ebaw\�VP䒷:3��H�(��6ea�03������ ��,A�uz?�Nj�|>YD�^�O���a�"���]o��:����1�Ov���h|;��V�L�O{�YU���c��f���V����3#h����� <#�Z_#�c� + %�j�"�����V����.�4l�õ��!rɬ�(3~�F�f��@��-�Fl!�2#��1�S] EMx�_�lҩ�g����B� >!J##t�s�O��|��K?�5:Q��`�E�g�F�9��k���\|)^h�ŕ��J\�Z�E���r�o��n>��w���%_=:=ǟx<+���( ���� +Ԓ��b��}r��g�ca�R���Ak�AB^L�����L����H�)3�V�/?��Z�E�N��LJ� +�4_���_ʴp�I��Y��v䏦��#wFhH�g�}!�.u�ߵ��صk�����Δ�҃b&G��]z�.8��b�S�q��&9�GQ���R��w]7Х�������9ztlU���3v~Bbx�}������ ��['w�A��p�f)��kA��bZ�{�5���u�P(��c;����ɻ!V�������)(��5ܿ�^\��=� r_����ȯD=g?�5� O��{�~��W*H���E�]K1E��+�c[J�ϭZ-�D +�Q�iv��$df����eW�>������˨�*<-��^2���pf�#�^��>0� SQ�[�<1�eʗ�?�b`�x(��(~rQ��J�ž�'ߍ��=�z�Xs�s�Ev�S܂�X�����ME��O�@�nB���[:x�Q>֩H}�c��$���S���Y�^v��TWL)N$������ɫ���%5�2l���S����2���&�u��cu��[��Hk�|M�g�X�?�/�m~mQ�n1��W�C�(jĵ P��R PD� �ڳ��c[�q� +����ԓ�3o��{�}�]�eH�/*��w}�\^�Zl����_�/}`��L�H#�Ο��Ä�K�Y��)z��;�����-ؒD�����7+<�@E=E츲`[��mv�S܀�haRT�C�$���I��1�`�No�� ���qLr$�)��Sa�<����w����$L)�T`}��b��Wm�K�b&�Y�t��0���!}��t���2��N5�%�K�Q��?;�u�`�ѴQ�D�' ��78%o7݋��G_���z��^,��%ۮe(�YI͖� FIG�P,#KA_sHd�6�c 3��*#��Є���d�S�0�s�V�!�N��v�:u���X�o��EMnr�iڰ�׶K +W��7R +��W�E��NA���uB �AE c�x$1�l��qvf2�����fW�cW�����}��` �D ;}�s&���O�x`0��gÁ��l�"�X{>�Ƞs�S>������D�c�� +��D��.󨢻!6���l#�����\�Y��R�ac����5�+|J� �>�����Q�:��*�8Dd�p`:v���|�Y��:0�Vq���巋*Y=����/��RuA�h�la�mY�m���1.X,U�+INQhqr��C@'�X �W��������e��m��j1��z����]���;�Ӑ�@)�X0�v�U$13k㖼{���%{�43���ξ�6�!,S%���J���s=5���O�l| xA��H<�~G�}�]���VQ�� _����U0�i�Η ݌����6bA1f2\�s�)�@�66p)*�u������6]p'z��Q�����V}�c�@V;O���������}���*�V�xyeQ���Z�K��\j���3I����Z~H?��L��xEWȐ�>�fj�]��u +�n;m)�w�!���F��,�H�m����~g�p昚�d�L��S�޽�V+��(wN���]�T����ͻW\��(e/��,T�lC��AU}5x�A������;��ݧ�¹�\.ƃ˖�鐩�������֙9)��Nn�1��Լ���Vmo�F��_1'%g���S���8�KHc�1��Nidm`1��X��؉���j1�|�R���>��+3s�k��a7D k\0⊅x�1~� Z�n ����OB �C����>iA��i���2����o!��b&8�#�������=��G�A��38���eĘ�!�@@�.�#����g +�)`�0w�]�C��c �O�7$�z�q +�ש�d�ә!�R�D�����.�����t�ӄ�\��.�[��y�\ V�ؘ��Fd�j%\"#.@���r�9���o s� ���xbN �6m�9��wS9cs�BL̻��2���d|;V�( ��v��6���Lui٦c�����ڋ�̰��+սm8s{�أ���P�>�M���ˍ9S:V ,C�����z4sL�PFO8�ݘ�ѧN/n�����ȕ%�1�������p�+��I���)���?h��qz�B�F>Y��-�~� �+� J��Y��h,�Q1�O�0�'��$��C�+��T�  �����v0�"��q�!%^��YjV;\1)�����K�W�Ke�k� �$��'�;#L����K�f[���p�N��ovz&�tH�l��m�Ak1���+�!ۤ1�&N�6�$PJ�9�X;k�*���]J�{�ƛ�nzz�{3Z�_в�$P'�k��TQk�@~ϯ��D��O��z�,-���� �f4K��0;ѓ����1��ګpy���|�|��\|˳RR�b/��Ld����T0�c�!Y_0�i/>>�zz� vUA�ް���~H�9�"C?����� � +6�9L�3 x=���~��0C?ʴI�p�7���:/�b�$i��la��ӎpŨQ�D}�� ��TuF� +=�yr��L9۶6��(s���sMY����1�>�9I��mn@{���]ٸ��L��Iy����Xpޤ3~�d,���AY>�H�����Ǵo��k+t��2�*9�sX:�6�^���;#�[q���;��?��q���ۧ�f�I6Co��6�V��<�`�����.�'�.l��J' �@�׆�r/V��hb�Tn0������7�������Wỗ��~���V�r�6}�W�4$=��8N۱L5��Ԟ4��v���"�&��Y�r�$����7�K:�E�Erݧ�Ag���<�^�RL# �%�ع}�h��ԛ�y�w97�q�� hFT�9_�%�\�G���� � I�3��oH��)#����A��nq���L�%Hpj���:ר�@&SH����Udj�w���Y!$kv�{��=x�b������.J�����E�ry�u >� }�B�!.�d]��� W�v���k;],O69����S�ؠL�>��u��wVm@� �(U�`��j��vnc��eĥ <���鐱 �_���l� ˸�&�4��Sų?{ n��]���~�1>�Y �S-��(�Ó��V�APo����^��1���UR(���?0!�c8l��=�� � +�%p���������d��zp�Q�zkl����ՏZ)%���d����۲�� ۬��t����f{4#]���T��&o��k�� �i7A5!��K7����T����Ζ��ť�Ab���?��26_\�'�g�/����<Ѥ�M ;��h�V��m��QГ�c���}>tݷ;{�hn0A�rS<�����=K�S��Ao��=>h�株��� �I�FXK��|M +"�6|8�wb]֣����<:^���W;Y�9lݕ���dB1 ��[�m)-��B�7e�y��`G�Z���Y���L���z�k���_�:�}��m�Ep|�~��[�{_��T[o�0~ϯ8} ��:�c�Zi��h�&!�k�m�����>9$%�H�/��'|r����-Ϥ$�x����~�mGІ�\yȔFP� ��$WK���D���I�[��i�xQ[-9H�mŊ@٬�<� ��*������'2���.��s�}�&Y�dď{߷��z�^����J\d(ؓH�B�q���g����bF,z� +��`��0� ��ڬz8v�7A4ȭ��e�ʙ�����YF�y=4,��ZZH!����-��s�A ��&i��3��t��ar��)&z�;4�o�y�$��G��t�&&����>j���G�sۏ[�K�O~\U��~�`�Ҵ���!cyfڭ�t�{���� l�����1L�8N�l��Gi��� �~�o�_��Oo1���)�!@4��B(m��HQ%�r���f`G�ږgU�?I��G��O�a1���b����$EX��]h�!>'^V����k�o��?�T02�Ӥ��/�4��J#���'5�0��`�H!:�B?� ^���$[�!A+¢qv�Վ-y!�_�T������VL���������MF�Ŵ2���Y6^4ǚ�����d 6�w��75I4�pW�4 �ۍ ���w���5M�QS�<�Έ��(�r͊� z=LV&��fl���+�� ����0Qmس_����x*��M��b�x�����/���l� �o#�z�O�l��m�|.�&?�[B;���;���Y�ّLhOڶȷ��]����;��m��d��I����H��T�h��!�<�V����y&��x>������z���>_����KA ���W������W���RA��q$��v�ә!�m)��.�ZO^<&y���L/�Ϩ�RSq�f����dxV�*��쥠����LjH �^�<��+f)�U��0pC\kd���V0%�|�:ԓ�/�x��Xpˊi94��Ϝr�N�5\���j-i�1�M +� �׽Sq Cb�tC&)��Sal�w����l��4�P}0�d�QA-�ۋk��<� _R���R}�|\E�p��KOz�^���U�Dcm���,ςp4|T��U����� k����Ƹ�?�Gl��9b��g�R����0L��������s��4�܇q����M���rWf�ix��� |oܭ�/��=?;��G"�r�g�<�Κ|�Ey�r����&�ҙg(�3�Q.���J�U;������z`4�>�IR��Re9���)��۫�:�������h� +�f[��]2 �q��4쐽 ��鴕If/�N%X�y1YAY�xŘs]ۉP����ZŸm�^U%�͊Xl�����5�Z΋W��o�+T/*l�� q��Y���U^d=�o���O��x��0�6����[���n��AJT����x_S,kCt�D�%ا�yӚ�W��J�ʖȖ���/�O�V���|q����?������n�z5_��od�� +��%� 3���w}2��HL �I}������ҡ�����Q럛��$��1Q���E��� ޓ�7�V�o�8�_1��KRA�}��Rh9�^+�h��C[!�L�u���,���O�IH'�_Z�����L|}## ��(t�Q�7s����߽�sy��9�DTCH� �2 Bx��/H4FBn]F\߃���3��Ae4\��v���pЁ.�6�p��\�\p+#�B2��@x��F�El��Y�B��˜1���V�Q�F�<jE ��!�k�����a4����UR���� �P���6�D`l�Z��G�E��|�p�B-���� �Dz�k�ɵ����q<=�>�M^���������i��+i�Mf��`8�da�}[��v�B������n|�,�z�D3W� �.�T��0���(�8�Y��}���Sldlz��3�5L)_b�G�b����� Еd�Bn4������9ܮ�*ߜ_&WR�51-?���� 2<��C�D\7H�=Py ��]�Z�5Ї���D� F�=��y�D��-�U�)�%��S�r�2l�iY�u�1��`�R��}�5�ԵU� �����h�WŠ+ց�� YB �x̘w��k�Z +;j�|�X�<(�B�oE�D嶗hڝ�e��;�C�$����^Of���H�lm7_Bk�&A�;�V+�حa�+�,��g2�+�L��i��� �B�G�f�e�,�1< ZT��t���@��f�~�{�Nb�k�L��l5��C�f+�m�������8���{��=Ӟ���+�e�Q�Z�u,*��|f�ع�v��,��1{�٘� �-��g��֮W�N�ܨw���Hv�c;i�*-��)@�c��ڃ?ş1j!� �+8�rLH�'jM��z^���*X���K?�|�_*`L��!b4�QPs%���ɚPF�L�z �Ts�g���H� p�@F�=��!�Rd@�ꘙ ���-Scy�.�R����cK�>���(��>(���P���F9x+�>�mQ�H�]�ǀ{�?cTۻɬ<������g��p�U�ꃖ�<7�,�FrkX�L供��Y������0R�v����E(���3���oʵ����@���o�]Z)'Yv��4�f���j���@�=��G 4���cS}1V>I��[��V�BH�%p�jo�a2� ���T����Y���8�!�yle6�[����o��!^ �B��ݧ�8�s��X�o�6���6x�8v���S'�<1�� ��fC�-�4���cc��������֭������x<��e��#�Da��b���6E=z�w'8���iX0��4�D� �M��pc�n[&B�;%~�D|Fe4�%J����?Ƌ��3цט���z�p�&)ʔ�e"b�RŞ3#�. �� +L���8ZZ�ܜQ���T+b�=H9��f���n���ǻ�U�3 1�B4�L�0�f06x-3E��w!:��P��"�&DM�'��y��i뗠�2��v΄A����}�ϸ���L�̸I���[eb�x˫�6����S�����x�*&��_5�H._Py���[��)]x�+1x�V8]�2C���r�5�3ů�Iǜ�0�V)� +����g`prWk���X�[Z�-)UlM B����NS���Ur�P�k"b��%�6龊�["��>��8�2�'m�А�_.U-NE ް3�HbTF���Z ��W��3g�J�|N��FeԄ��~5 �l��W�����kh���/�jG���ٯk7��E�&(�Q�����Y�ܭl��/�2P:� |-A��O��$J���7�PL-wA�6A��:l9���%�eF����6C�JۉZ`8WGE9y�BO�zjT�&K-�S� �h/���(� M��/2��c�n�]�?�Ae�Kn�t��m���� �٩��.��W�z�uz� Q&��ژ�_�θ���u��� ���Ÿ�>�xJY������x ;���O�B�]�� ]ܤH ƿ�Q,)���{i��Q�h�~�R�؆.c��kn�� 5be +���9;���g�_�j �<�4&ÆV�/��W�T���a�AҀ#��T�%��^}����T���g���.gۉ�C�=U�" �A��a.�'��5�0�{|��B9ez�&屛8o{�]��k�VsmK�,5 ��<V3HX�E���tBC2�����FL Y~hͦa �v�J]! �huV3U ~;�.g��4�D� zP��xQG΋W����}x�~�"������؟��o7WT�_�� a-Y��w[� fj����;W�a��2��R�ΎFac\���xI��b(ڇأ��A�nv��!E �w���������[Yu���aTi~� �G��`�8�(�~� B�za�aݛ�n"��7��������;o���I ��|�ʰ��%�4�z��cM�=���_�9�߱�J������Z�5����.�* �C0|������Z��|���2��06F[oG<�WHۑ�zl���-/ݣuS����@�#c +� �-�R!�I��YK���f}�WՎ�����z(w{޵z��ƈF�IW��J���I�o������c7C����ТAn�6r�vm-=�.������*&�� u1ߝ�W֐'5��av3�����o'��|<��O�O���'� ����ZmwH�|��Y����V��]��Pk��0��—ľ��<vt��n{��C٥?�O�G��U��1�>#�� +�а��we�����4���>v�_��! �Q�Ŷ�����͟� + @�+�Oq�B��"(�B�ؑZ�V?!�M_� �T(��Q��~bVt�~�� |�R��U�<�|vփ�go����;}1��P������>�B`��j8(��Di�y�� ���R<�����y�l^.�bh��'�������a3k%u�:>��v�0^ﯻ��v��Q�3\&�X�4��6)��!�jO��^�WG~*y�a�H�RE�^*���c�Maq3}y�>p�(@x�}�H�'f�h=��Um����������?m�AOA ���+����P�`4!�D����ٮ�8�L�]����U�Ɐ�}�:��>�&l��ha��z�$���Č� ���Y�p � ۢH ֞w44����߼b��pS"�6��NES["ͫc=���OTYQ���R�T��<�L)ꌰ��KQ W��"��w�@=�iC��K�܁E!plR�Z�/�Y!�����a�||Zv�z0�V�����'�j�Y=����Gp�>"�L�[�la�myH��#c\�"X���[[����G�;��k��$�6�RT�I�4��*��> �ǴJ�� ����Rf���/� =�KKCA ���+β-Eqk�T,��R����Np:3$�-E��r��]'9�2��������HM8؇�*���x�.'�EV����^ ��:�.Ţԓ�.Fa�Ʉ���'�)�^2�o��EC�Slh���g�EBۥ���ޫ�J���{㒧���L�a�e�X�n���̢7��a���G���t�4g�K�����@XG/��>|d�g#i� e�I���v��m�C�nL8�F�k��ܷ�u�AkA ���+��6&�׮�� ) �S!��j=��A�Ƙ��^Ɖ�Kr�����s���fj���XI?}�w�r���Ȋ��Ջ�����H'�ԣ�.fa�/� _��HL��iݟ������R���g|��+=�5V*5Q��P� ���g�oE`�0N)!��7:q���c��7.y���+��p��q{}��޴S�`���� 8�EX �e�@e8G�t��I������>}�snҗ�=�r���9��Hƶ���<�uP�>q�8���u;�Z�h��?�/%u�MrG�|���J�����y׹�?}�Ao1���+�!@4��)�*RUEMnU�w`��ςP��^y�%i�vo���=��ɇXE�l ���5�]O���}\�x�l��:�M�$���}e|]�q,B<��U���c.��ё��� ϳMw�.�v��PRK��f�$u�Y�"��8A�� ^�nj �΁��@+ƶv撞����a�6Ȟ�?DtL�q�|l�}Y.�>�eTSL+R)�����G�4�O��0��*� +O{N� �"Y��f#㢨S~�79&�^��l.B����iG����q{���n'�#=��,���QJ��M�Ep��$�����5�`�o��Y�h0��@ҁV��)+Q�!���U�X��E+�g�K=*�ޙp�п�!�������w�p����7������7�^_񄵖N���/p��m$c_m�-�����Ϩ���T���0��+�@tQ���Ң]i�Z�����qb�ؖgB��{e���֗$�{o潱2�� +9J-<�����Cz�<�f�Q#�Y(����� �`��V��g �N^� +���7o�ka~�g������>��1�p#��0�K�0�zc� +��i @&i {�)�z� +>[\ lK�A6�Z+����Z�����(��y/�����)HEc\�� ��kaG�p0O��Aڼ�<Ɍ�#9!� +��=&2Ͳ���g�qu"�}| �i��zR����-�!�B�f�Ԃ��Xh�y`�� `2�� <f �Ϋ�`��s �ЪB�U�~��Mޥ�D�p��+�:�������6��.@t�F+ ���@��Z���A�Ue�7�u ���r�Z��s���Y��[�j|a݅+��12��C=���oD��밒����:���U[�iu��`b�<���q{h;�W����K=�K:��������$�l�T�����k$�U��p_�����T��2�5�S��N����z�j�i�g���m�Ak1�����1��N�4��PJ�=�X;�*Kb4kJ�{�ڛ�.Bo��73�Ϲ�h�R�S񶳗����|��C���tR�I ��S/G�q)��<�����^#�K���� +V�����}���5���bB�x`ŪL�:��S\���§h*����K�פ��� !���Wwϱ0$vId��5r`*���i��}���c�XQ�`֓�D��s�8���:|I�z�O�4��E:p��O=�&�7�t�*e�P�r�G;����I'���`�4XIQ��seW9��WT)K7y� ֙�ӧ��R��}�n��.��O���fg���A=Wu�w��۷�Zzu��Qَٳ����8e4��N�W����n�0��|�9�`n�^k'u�h�"⶧M�,�4IpW6���^P�&�u�~3�Z��MDE��D#�d���!_���l�0���2j��u�w��ӹB�� +�!�m#�1�%Ox��NI �<-7�����)V��,V{|��\��D +�QB� +&xIv�JH�~ ���9��=��5�`}�N� ~��H3ao����tsu}��Ψ��4ZpЌ��U8Xi �<�6� U�b� +�uya��ʌ�1P)�b��H�� ��"$9�@���E��H��ä@Ƒh�-Ý�{|��-��l+'̤���h��>�1�I�X�Z�"P��G� ���R��7��$�r�/�3�O/���}�%�����4���|��\ +ma�L��r�5̥H�:W����}�m��C�-S{9SlK ��ClTmO���9翊>A5��1�:O��\R7œUV�R�(W��_I�lv +`��8��������:�95]��p��8�WCCǎ��ҧ��:U�w��bJ���S�d��5�[�".v]ڌo�6�5-��@�,�DDe�Z[f�Q�uWi3�@[ . �s� <���؟��͂l���I8ڨ��JT�<�*���f�m���>���>�L��y`����Y*l�F�1� +� +7�@�����5�^5jv92 p�Ƨ�q�� ��N�8�Bc���8�+GA� � �֨̐%C�r�rڒ�bNM���M+�y1=<��S� � �B)я�,!߫��?�O��?�N�F��sS���YH�25�� ������R�yK���&�W^b�P�������GY�(_8�Ct��x����zm2K��2��L�?�g�����o�U]o�0}ϯ��*�k)��j�J]U�iO��qn�5ǎ�(���'�$$�t<,�����s�Irs�%D�%3Z2�ӊ��٧�4� ��DX��D2ft /���8����ވMB�>|6 +�d��pÌ�ź�G8��̒` +�b�nlyc�%�L�Sp�ȈuN�آ�6@ B�K ����RpTA�X����j�Df�w~�������ޕ��(a;f!�� #� J��x�s���Jɓ@�m�8�K�̣���An/�]g_}��’���� $.��0����oL�-U'�>i��s��1s��h�Ъ����Z� ���`�-3��Q'��Ό�2B�r^L�&�(w��Vmp�� 6��4�*��O+6�Qn�A� ��E��,_K���Y��V�L�),�yÊx�tx��� ���3O�_��+��ܕ��1+�k�}$πv���p�s ����;������g�bؿ.cqTm�r����U�p�U+�U�������+��U�h�[-g�e21k��H�#29�"߳��c�L��� ܑ[k-;ɝ��avY������;<��_,���A�w2XC����)��wxF�p�$��5T!���H��1����z7 ��;����K;l�e 3דIƍ��0��E��6Fsf��İ��ԛ���k*{�Z��N���<�)&�o ���帾��N]��2���g�;>Fp��mu�,��� �Ѹ7��CW�����[��U�O�0~�_q��h+J�וB�TM��{��\�B,\;�9�������W�����ƹ����|=9K�B��5��{�� M>���h��ǂ A$�X���b��xąN��x�-�x΍B�*�zDc N�Q8]���O�`�KFV0ט���?L�8A�Ht@`*��5b�Zm(#��l��R/�Z +�����Y1+�:�D"#�������\\~�_�P^���� #m�0���1X'�tj8�a.y(�BJG�����?}E�A���o����c���Odq�_]��4$��/�"k�Pv\2"��_)���y��`4�t��6J�ʊH�q9:sbĚY�Q����c���n��!��f���J���פ͕���ֲ^�Gt󘁓t)�(U܅��{�sL��y�{j�����46��*z�%�]脉/H��B e0��IBo�gS��7���2��0��~ ߐ��-�����ij]n�lB%���5t���.��><�7�m��^�K��e� �Ԩf�v����66zC�� �}y�K�(O�*�"�^�iB�e��߲�S���yWrw��nʴ{��*����;�����a���_��_�^� #����ݬ&��&fT4�_��)jmeIV�R,������ k��Ͱ�oV� ��jY'ϕ?���\ ;�К�ܧ���gJcF� �Y�"֧�vG�A{�H��4B�>�ͭ�����t ?��5B=8���0��������R�}�?/�S�n1|���Jy��ڄ@Q%RE��S���Ϊ���5i���+w��^�xfgf�j�9j�{,d�<�K@�_f� �0 Kc CP$���P� �gP"n|x!�*z���!|���F�+E����y��C��B���#\q}0 E@,& (���N�,�x�J�'�a�ݨ'�5#���Vb�B��ac����fv?�%�ҘJ�Y1䆷Z�ó�$�gI#h�ז/2���Ai��Bѝ�U&r�e����Hl���/�L[� ��'��;n3�`q�Nꣿ��`Ӎ"�(�r:d6J�6 �< �Hv�v�.Z��Vq��?�`�vo���j�JJ���aج��H��L�� ��tj���w,��*�Rc�R'0l<'�C��?�0.G¤t�c�z֯bO��T��:��G0�v@� �i���d�MV���tF��w{~�`a8�j���t;��vFԙ�/Tc!=��ʊb�)aB����Y����В�����lOl�,!���|0�b���d�pttj�}}���yHRM0-��d��v�89-�)�Ě-�Ƽ�<͂9�T��JÏ�g��<�j�@k�is�Cs�������ϳ�z#�g��:Lu7���R��z"|nJ6�ugE�'�,�.%�����8PV�ȫ7.�5�W�;�f����w�Eo5�� �hX�����-���Z: �P�]�#��+i��p�ܔ��#�8I�/V�lI�([�->�n�8��r��3ْ�u�]�[Gj����{��O�#���h�O��V�n�F}�W�H���Kd9v\�IP����1�9�^�;K�D�/�]��qw�rΙ١��y��SM�c�N���W����t09���J�P��9��m�V|:@mqe�ʩe��#\:����<��3r�/���4���<'� >r�g�m\y��� A&Cj�wj^z�M��u�9cQj�t�=Xk���2 �^�+k�I+�����OW�7��!TM���&A�����������ҥ��f���� KA)�6'��~���\& ���9G�t[ʹx�l���e͗OfEZe�����k��N�T�~Q^��� +�[WA��_�x�}�a��LNNp�"��b�O�)p �S+����t��s��-�����L}��Q;�����],��im�?��ׅ���U�x΂�� +C���C�ve��&���� ��Ƕ�h�5U��fY��K��Z�ģ�9ɽw�,���yaS�#> _�ޒ��H0d�]�+Wx��F��p�ԼE�v@܄x�<-���q�E�1R��+��!w��@�6�`���x]A-�u�ձ�aR����0v���+�㾨Z=���-� 2 7Wޑ��i��Kv���� +K�Q8�b��m1�"���L�"쭆EN�w��ؔZ��E- ��q0�,�L�En+@"�%z|��2�=��w��mNnB����C�Tpzl\vhw�K��푱Z n�L�M�`�6��f��Ӡ@�kO���n�U@�z�.;�w�`7{��I�f��!:%I�ѩ�e�����G��>,������l�>���o9��i�y�a,�P��c��L�Q +�� ����T�dR�����1�95>�,�{��Ԍ�Ea��eq=��ͨH�Xdvm����q���.��+�k7�_�V�Z�V�M9��� +�Ø ��p�K�G��*~?��)VW��(Ϸ�M&���e �S %� +9I��@���(͂w��0��_�S���\�.�}�̖N���:��T�k�0�����!?�צ͚���,$�> �"�cQYҤs�0�)��4���Iҽ{����/&7�"��b�� +N+�t���Q4�FЅ�\8ȄD �:�y.�8� ��[�� b���*�{��Zrpì»u}�8���̑` +��D 7���3�Am$z 0�׊�X�������RJ���GK�Q9�2m FB���!l�B����t��z�P�`����0�����ti9�i]�0R�@gG���>럡#�(*�G*G0��>=����'��l�u: +��T���(�6��ͣ���;�"l��(��d����L.�h'H�=��H,P��e�؊����.�m� � ����޾+��:� +���}����|�V�[r��x�Ĥ���[��!�m�{��c� ��wr���*��^/>;y��1��*�AK�G���-(�]b㤪گL[d<��]��ɫ��\�?fnIV�M�$�t���2��� ��>q�����?к`�?�0R��M�4��=�_�mU��,r8;5-�Y���-��m Ӫ?���Ը����T��c����ne|�w=�o�z�u1��A=NU���I��߷j����?宒�������if,Riթ��C^=���7uԖ��U��\��֧���E܀۳׻��$��C��Umo�6��_q(RH2�xI���c�M������b�6�t6��$ˣ�C��@��%ۉ�/�wϽ=w:��p9f�YL�Y���4H���:t�� �����0�@O�s<��Ը�fiŔ;H�>Y�p!���#8gV��>�8<�1>2r�)��Z8�Z02ܠ6�"0�C����p�Rp�E[paRH �ʻז"CEBM��1'�:#��\ࢴ�����f|���g� T���qp>y҅�2��)�"�fH�e�8����"�(*�ǥ2��X�ܤ_J?�����*W���w�$����(�$# +׷h4 ����T��2���@�ӁќY�s&���:�Rn��3�p���J\ˤu��<�g�.��Xa5b��p��������� ��$q�g|�� g�������[z�O��p#��p�����;d4vV�i������h�,㐴+��q$U8)0Z�q��V�[]]��_wy�$�!��u�_�qa���P�m���w�z/��I3� +Ņ1K�y��/�x��V��SX�,F(��XLs��M���_�ŖA[���Ӛ]aպG���u:�bF�[� �Vs&E�_���xJ��l7笌���g���/Έo�k-�3�ً`J�;6�a3Lb��ݡp8K~i���T��5�fK�Rܴ�ߙ,�%��W��cH �)\������!&κ���VKC�ʑ��"�*�Flm���{�NN?�����{��}|�Z��㓽p��'��5�\Ҵ4���d�e��+��ߝg�[�{�lK��y�`ukv�g�����M5���7�c�ʦE���@���9��յ��F;�]���k���pV8�V(A|�(�W y���R�n�0 ��+ޡ;HZ�6iע� + E�,; (���)�J� �-�>�qS'i�m�"��#}q����ULy6Z�d(?��Y/C�*13�`"�b���2K:�� n|X��W�\�Ďpm��I,�]M_ާ%���TE1�� �ĸ�/��P��RB��;a3��sl?{�T�Ym-�N=����"�����]������U������~|��cR)�JE�&n����HI棯Y�/_,�eN-(� ��;����y�i�bĄ�#�x^�,��9�7\�گ �z=\-������M��,�Nj���_b��O�����Tڻ(\k�|�"Z�zj�Ƭv:�OO;h~���06�}l3[�6Y��R��F���5rP�u�FO�M��8s�Gj.�׏�\S*��N��,>�m���Җ�3'������y�.4��!�8���`���+�����ި�ќd�6/#z�����Pd�>����j)��q�g���u�j�ޙbN�R�jAy�����ı�������8G�b/S����o��6��V�o�6 ~�_��N�w/M�k���C��^zE��L��"y"���d;?��0��K&)~�G2>�P%� zJY�V�"UI<�~0�Nzp��a� �f(� �9L ����׮��^���� ��������f��E�4C�n)��s�\�EI�4 m�Y�z�yn/��y��`���=Z��2��s�(�� JC�+M����Ǜ�& +��!���E9�� <���r��gqI\�"���ܧ��q�8�eU��}A�s����u��m���dkֆ�� +��:{����tNV�\���[G|RP�+�џ�T fh�L�G_���K��Yr�M 'Pz�t�A�A+��qfh舄�]�,��� ��`̡}��¢O�lcq`��U�I8y���7]|6o����S�I��o���B��6���C�L�t���}�0�L 9�����Xi +l���m���}J�T,�3��4Z��v䔐 Y<Ხ�Λ�5��@8Ƶ���e�n:EVI�)�E��@�7�+Lk��p_��tMk��hk��n�j��( ԩ��D 0}8I�n+��,h�y��Y��Y8����-�����fh��U����u���/;l�C�{���œowp6�_z�M�QKBA���W�GIzM+K� ��w�C��;W��ǽe�83ߙ3gf��g8�� +��������z85���/^* ��T���ˑ� zb��ȻW �%2�.Z1�y���W����� +E}�>e�>�:�p45�_/��H�@sx�[�!rUӢ$c^�����Ȳ(:[��q{Ŷ�eQXoD`���^X6$� �hjd�6yl1��e�����`}0 �v^+��`��g����' Ml> �v6Q�NG�q�|��u��p}7@p{�� o�|4���9�q�ʨx<'LG������?�ĩ&���� /(V�) +i�ӵ'�Ʌ��B(�p9��!�[�mS�n�0 }�Wp@��ARc�M��k,@�K �@��t-L��NZ���A���d���9<��7�j�1̠o���^�]�����p��Sn!��[���R~�s +č��~�����ѐ�Kf$n��<���x`�8��s4pi��F����d ��d�!'el�p� P���B@�twh�#���D��WrZ �G����~ws�s�*�Q�N�B�m� c8qJ��x�r!D*�%��dZ�"�����z.&���"�2�G��.�rb��"\!�\���fŧ{i�y�`��o��hh�%��W,F<�3�da���s���'#Sӆ!��R��Xy}p�dT��G��$E�{��M�o����iT���`4�Q]�ޮ_k�:?A���m��DJZ2yD~p�ͧȢ�7�h��s��-�#�R���Z*�"�lt�̭�r�p,�W=ؚ24��� hG��شAA)���ڬ�������G��\tw�3 +���e�q��7n��|| �\s�K�hS&���;�����5*+Y�A����Aʍ��.[���K�Ʉ[�~b¬�0K���C�2s�UhP+�I�����f% �����As z��y��ÆR�Nj��� ���"�Ν +N:75� 8*�w�ܘ��d8�N&ʟm����Wh5B��3�b5�"M�n�?�c�O,ז�����|z��R]o�0 |�����/�^��KWdX�a(�f/�(2 S$���E�� �n�~`z�������'W;(QjA�y&%y�;���c>NF�p_+��ʃ�`+��U��Zču;R�!�9\�A������&� NW���ī���0�L|_��ڡu#�)AZäV�-�N��%�� +Z�|T�h�$��Lei#XYS��(�E��1��Vx(�?ha [�5pl��@Aڲoy��A�D����_m"�$ >�22*�2KA$v�Ӳw� W��i�V�5�� ��Jt5N���0?�?$���� �F�:Z�eG��p�7NN� �/p�tsa��|2�\Jk������f��|��p�{}vHC�l ���u�k�V#��x2Eȁ̱����#S��-���(i^��8����# UA��ߒ���g�jACe�ҵ�:��/-V�����S��k�[0�}s��|<�"f��������ݺ:�[�A;����,-�FN��&���'��T�o�6����P��d�q�Վ]gY�h�ـY0��"F�����%[��-k1~�Eݏ�����W8�PjA��'%���:���t� ��B1�J#('ȃ��K���<�*�ʺ-�U�!�)\�A�A ��g�dp��<�g8�-> +�J� K$���b� +��i � L�O����n���@�K�Ah�$FP&��^Y3�Q0“�M��������u(U�� ��]/�`�|>�g[�D�6k(�##��NH�/�����J�i�p:;R�����ǟ?_k\���(�Z0�mU�?)��W0 `�$~[�e�6W��ԓ�g;0�M�&���A����=��[:��ץ�G�����>4i��ۤ5�p΂ �y�-�z�B����/�n:���Jt�?�[���,-]j%8 �W��L��tRWm���D���̚Y$�x�¬&�t��J�޼�7��=�����m<�[��o=)�J�tz�r��r��H�Ѽ’� ����Q9$���F���f3xז>�JD0�9�c��xc�Pr[� rKP���|�X���+Q����b�\j���R���X��9x�?�f�N�/��N޵�F�K2-�I���h�B���J��k�‡ �aϕ����P�W�}��uJ�q��3���x�˖����Y������� �h=Yڹ�f�R�o�X��.kub�i{�KY@æ^���4��Q�S'���7�/�5:���A���_�`�ы����ob�0~y���~�ݿj鐓t�Hl��_����� +Y@��bt6/N!,�����v��[ ��}����*� 5��j4�=G���N1���)R~�zK +��FmJ(�AZ9��] c[�lB��%��j���љ9ߙ�w�CFR O��^INy�(��uI�����R�JT��as\�jI���̺�WE�h�N�!|��ܑ瀡��N�����0�����'��c� '�td��(�0�5�բb�Ccxa=�$�֐���J� er��+k�4�@X*Z�}_&g��|G������Tx� +�%8��� m���qO� I�*����z#�$�¦t�4�ׁ���\y�l�z�$R��Q��Jx��n�e ��d��?p�B+�ߦ���ߧ��x6�\N1B�2wƮLk���~������'��#�Z +&��we���g o����'����+#�v��5��$��Y��i��ۏ;<<.c�X����� +�&w�s��e���n6T��j�m��SO�����&���L� \ ʾ�5��?E���U�up�zfw�Q���V} S���=m��/����˛��`L[��XA��Y�l���l^bώ�b��|n�} I��Z˷�ǐHeL/y��2�TC��]U��Z��S�Ί��,�D!!����r�����y��4B�/����7�+���X�#L��x�A����1w݉J'�}s;Y�|c��a��1�g�'Ú�MW�f��w�jz�a(˳l Ͱ4̲���/_Y�q��+�N�y��U�բ��=���a-q�0�I�TQ"���J&��{��� +�uE�W�H���X/���q+ +�����h��< |4��a-ZR;h�����<E�AO1�����@<��"DcH�hb��m,mәe1��nv<��{���mv�@���[}���r}5����`��� /�T����#O z�2���ߝb`��+�q(~pQ��J����CjcHTq���'�Xɿ_����T�n�0��+�� I�}ܚ8u�m�>�:�M�%"4)�+�F�/(ˎ�8@y���;��%/ߗE�����NI^�$?y�^D�~�>� �R��#���Hv���x���L&�keb�Gl*��qF�������F��T�O�0�����H�Ĥ}Y)�m�M�|ۦ��\׶�NK5�ON���I˗$��{��}��2 +�b�Y +���%�?Mz�I'���A.�t`�=�� +���*�gcW,g��X$pɚ��B�@��!k��6���waBSt^���TÙk.la�XE�3F{���vu�+�� ��T +Ħz@+)H;�s�s���.XE���վo�σ�dRU�|��� �n]�2XJ_��)Y�5�O"�sr�]�<4?�����t������(�4�����I:�G׽6`0ߎ���h���k@f\��J�Q:�Zaz��d�Z�.����9�ӌ�� UD��)1�{4���1� T2��Y9'���l�ы"��9�qFA��;8�t�b� a�z&�-�z�C�e/�GV<�-��l���Ё ���&}�WG6_�Y�W;�qu�r����!M+Ÿ>���Vx�Gޞ3.�_ل�݆���U�.x.� �O�u_�JR� ��!n�'p��]��Zx*ڠi�*�vޠr�t���I�e����A㻸���GG�rA����r���*+H�M0> Oى+�5��Z���h�ŖB����v�����2N>4�l�0������JtW� �֞~-� m=w�\>R�9�,�ﯶ���&UC��p4{'��̥�o�7��A�J%`j���z�t���&�&���u_)�:8|�U�*��ڷ_��u�̵3����^�i�oC!����?_���0*�ͺV8��ZO�uR[o�0~ϯ8�*� (��(F�V�Z+@ڥ�qN�Uc[�C+��CHsi����v�/�T� BʉF�Xͨ]ڽB3���^ۃ6,f f�PD[�1<$l��d���� > `��WN�3jk��h�����<�a�"�2"����Ҝ +#�(���k""�RX�V����7R�M�s����挢0L�Ro�eRt@q$a�p���n'���ʂلX�3G-�`�lօ72������ �A�ExH���a6��祦(�0�󽱸 ��Fj���� &�p�+���=�rb ,�^�-Pw�+5��z�6��D�D����WV�m�E8�Y��U�3���HU����_S��7��M��B� +�ʰ\R)��)�~������,t +ђJ���}gn9���T��[�Zc��5�3י ق�וŘ��6�l��΀[�Dn6DD9b��bvj�f���޻�\��= ?��Oi:,���;L�9���͈ZAy�YzEl��6�k�ut����#x�x��`׷��dq?���OƳ��~//�4��bi�^���������O/ ��)xe���JC���t�F�jwo8��J��1�U�O����L�K��i�x�`-1�]}Oٰ۪���3���w]|�i!%7 ��;x��Z_o9��� <�u��8 �ݤi�]�5W\�{�A ����Ҭ�Ij���$�iFc�m�pF��E��#%���"+ �$'#�MԽ�(�/���d'�{F%�h�@%D(�+���|>Cqɋ���LA��p!›����P^��|Y�~��b7�$RQ��,Q�+Y�Y���Qa)$�)A���BV��T��*��fwM���D�l�ņ(����Dx��h����˫�+��(�2���HH��{a +�Te���"AHxZ�<1�AY��CFį�����hTJ-K��@� ����?K<�l��Θ�~��^�L��=S�@�Q��h�/��&�"��K+�-�9MZ���s�J���jG�헉#��u\��?G�� � ̛�Ն��[<] �����T*����?�g]v� +͵��&�V����n�����ScoW�����N-��X�4���3_ �f���W}�H��gFcM?1�.$� UEa4�N3����b��l0�Y�`��[]�Z��n��l}Ӝ'�0�k�N��!V����>�h��n2��"���� �V�������}{w�� ?��Xf���9��@�^f4O��O�x.]A-VF� ]3�J��([�0��n0��&;�" �+c�OW�U+�w�2vX����Y�e�邤i�Iô�5��ȵ�@U + M�F})��� �6�h�Ƒ�N�+�i���^�5?�+�a{6;9�3'p^A6 ��ÑF�j���L��u{W�΂@�F��dd�ħ6U�(�Ŗ0��;�LV\ I2�:��g�bU�8�f�.N5��s���ٱ|v�������u���vM2w ������ &�S���@=찕�,�}��$kt2���Aa| +���)�R�T���L��PO�κp"��L�s+C6�_j��sg��\�4~R���Eߚ=�ύ�vϊH�B���EA2e��2�K���:� 6�eӅ� � +xj��1J����C�fmA����-^�� n���d��?TQ�B(�/v���L��$벱)V��5�tT��Y`BWS0��o U�+^�t���j'-4&܏��5�� ��E."N��%UJ� B{�Ζr���]��ռm�0�p>*W��u�a_ٝR�J�5H�G��� G�U��6�|{�n�vO�� ����ٕ�"�1[����@��ߘ�z�v� )�^�@h�WV|[� +{W�MaЋ~��.,�w�)�E:6,����0OF��<�[��?���̏��F�M��<]���*��L+_u���J���s��[e;�u�.?}0�"G��n��W-G��p9����cE���y>l_�����M������T�Z�C���{"ϭO���m Ñ���'оkw��0�5_�Րn�d4TM@'�xg�;���@O0O.�` ��o�?��;�l�q�S"C���8���<�T�͛���&i����q8�H�*u�(�[��A�'��P��޽O�R�;X����]^�����u�n��_v2m��i�`$T�4Un����)�T m��\�p� ��E��[*zoZv�ƒ���v��\:�7" �eD�f��-B�Z�\�KPE�9P��&���%a���(�B[�*���;������Z- Т��{����)s�G�vW*k��m��1�x�:��J\�{5�v� ��;KM�8B� T���: vGS ���д�xt����|n +�� ,������{�|+��k�$�L�q�S}���1�J�C�r��}�s�k'�:p���y��Ϡ��|�%I�ܵ>�Ѯ��ʅ���m�wS�(���$�������~����! ?��KJ��o �ϳ������vf��BS*Ѣ�}�q�Eo���L��F��D��[ �v.햬����f�n�6�+�#��j�^; ��[�]:(��e�+��t: �3�0^x7�'�9�*�W4W2�����~ E�&GL���{�� &��y}��5;k͒�*������z��V5r�9u�����&;ht��ě�-�a�$I�r��L���,�io��5) +�e�xUARk��� O3�, +]*��1mt�2����?_�qŌ��I�iо�5`����K��Ÿ��C`�U���B���'���T�c�M�I���������&<��Qx�F���pj�ٵG6p��`B"(��?�aOU O�.��!9�@Wb��7j�6z5�>`@� �x V�����u��t���o�F�� +}@bA��{�k� �Jhx� &�)�mb�����@ͤvr��!�a��)��ݫS%Q;�������}�) +��+<�}_oO�U�D0���H�� #8)N�}xg2��DM�Q���a�z4�r#� Ȝ���;��u>.���^��.�qs�%*�A S�,u�Z¯`���<�Xg6���lI�:eu�ֶ#��'�w����R%Ϡۭ4�1e��گ2�_�lftkB:~C�Y�0m��u��$��� Pu�`N�iD�xދ�a}5z]�!.�^C��%_������j����bz�+��L�u�v�����A.#f�]��*�5� ��`ɤ�� +����sc��6�#�}�:�s"�>� U�g���5�����_Iˌ��_���Hd(���&�Y٘���Vmo"7�ί��PwA4�:�jC8r�)j)I>T���Zgl�á*�����H��������3_�Lj A.��ؑ�f�5�?w�~�]xH����a�@/`��5�� C\i��b�ļ��B�]2�-9�`V���>K�S�q� �`�-\�r�Ҥ����T\+�b�I[W8��(EXx)����Cj�튑ЪF"sk��l������ׁ*K�RF�a�r_��FP +�w�[��uR��o)�BgG�����2E��wwz)��7�&�dk\+G0Of=^O��Mo�>���/�q�P`ֲ��+ng�M8r5�9 �k�\{E������^�d���nkfìÙ����7��ב}u������V���g��+TT˿��FH��:�U�3O��%s��[�n.��f�e<,+֌�ފA�]�T;*��n��\��bo՞�->{a1#Kк��Rpp�H�t�=Z�����9̵.��R���� :���|a���A��� �c"�^š�PQ.�=�����fY�[�i��[�k� CP^��!a_�:���\��V�(�ڡC������.N:�=87�r�b&A\��%r<�FO0A57z+��aQt5{Y^�^� ��Sʫ��wzcԫ�t`tH�A���~� �Z����n�rO+�����y�D��)|1WBOr�ao2���i\��;��w�i� ��t��q�*enz�u|��>���6~��N�Dz�M�]�b��X"�Ww .7�nvָ^���n����ԝ� ��{&]�)��V-�J�!r`�����{Z-�ϛew�TL)�$������ޫ�v�%��2l�ϕ�&Җ%�e��Uz;}dbL+���r��cl (�Z�c�2����MoA ���+|@JR�-\�Z�*q!Mf��#&���$���w4��JQ)�͞�~絧oc�D� �P���_��(�7�Iq~R� |k�@�<���B����Y-�qǮn�vWLA��*05Lx����c�Õu��#&d�JW��M�=f �`)�U��r|�A���`{�L{g��Qxm�C�ha�p��}�Y\��ΣZc���(�쵰���4����"�Pv�� 2k�h,�mc�&,ۍL�"�qi��n� ��E �{���Fd��C�^�J�c +��V�Y��l �$�(ߍO��Q �B̏� X���951u�0��`Є5&-�X�S�X�Q��FR>?8��t^�~md8j��u�p� +����5��;J�������h�%��5�' +[�y���J�� 9dq�m6�Ce����i��A�=�䥿�P8�jF�A������c��\mo�6��_��� +I��@/w���״1v�4��`�Ҭ�5WTI�/8���"��%%��J3|�/Ù!�����5��<�W�KZWEꒈR�U5�2褰� HV�QD(-S;*3*3Rg�U��QPf[� {��<��ʠ��lؕVb��W**����,˶m](�맏 F�|�����RE�� +@f���e��^N��Ӄ��ߠ4�<<I�ߓfG嗗͎���%��J�B �����r��[O���Ȟ��--@�����+��J��X;�wͮy�7�.F�J_*����€��8I,u�v����vD@��{��V�V�'jE �w��S�1� �}�1h[�w�%�~���랡k�G���(.� ��h�#������I]��� ���#�b�� +��I�z D�@�RI�)vpM��K�J��Zq,�������N��܏��Ģ3zyYp���%g�[մjҍ���|e�WV����#��ɴr4�� +��=�����]8����!�&f��UL�2�Fko�۹kڳݲq�'�㘂�\3XB����� +�=O����ن4I-h�!|�S+Ox��N�� �;��š�R��d nI��xX�}o���A�4�O0N�{<vb�Lgu�I�U�u �X��x���4 +��� �L�?��!�^SF�C`��E4�A9m�}å�L��ʉ�hn<7��Љ{l�T=F���(���44�s(#�J��4��ҤT�B�����q�,(g9V����K����cp7l:'����~�V0��� +�@�T��k�������� �[��Ǝ� =GJ��� � +(�Z*�R��(D���%-SS����gH��~;�'@��� +(T+�bb��h^�LF������-� ���=q��>���������F�w��x��=�F��r= �����~k��p� ������/���b��L��z����tE{\��n���@��<��L}^�e��2}�Q�D�Z��j D��TJZG�5���V[:��٧�t���u +��WJB��9�����Dgg�{�!���B@�\� j20�@����@ʫH����BRUm�fB~����~Jc;uE�#��-yT��|�5ϵ�{3�d��O)��WT��k@.��F�D�ș��2}xڻ0��QF�p�q�������#�u�H�[0���_f.旣N����U��n� �iݞ�b���ñ����4jS�h+|L1����=Ա�}ÅZ~�3��������w �n�1I�p��t���2B�c���.� �A!� X�D,>�5��L���\D��{з���E̻�tVA~�3��k��؍��#M5�b]*0�z@�p��+R�t�֛����N��J�Pk#�D�vj mS �F �.@6z=1��v�ĥ�젱�Jk�c��Ʀ�e���5`������1�?a�'@M�>�i�E�%W�7�~ĄI����,�Y'G$��ܚ��ť�i�@ԄEF_gN=% +��-a��3_��0�z�ķ�)z��Ƚ:��{t���OX�?$�K���ߒ�'D�H�E'4��\p�~_�w�F�(g�BK�7B�o +(����7+W&Ͳ3*Cyx-�?R�����(��uf�����cK�L٫iX��©�5 }D�h��bD��t�k�%oER���y��s*�ec/t���#��JV�P6��\�F��A+�N��N�ۉp�y ��O���i�n��M��n�Sl_r�D��X��sL� �<=.g����g����'�iּ�US]Ӛ��-o�������[S�.�gT�L*���JGue`pwb�U��� ��o`{�cz��6$���5!>� ���B_z�b�0��X�2�z��h�sf�ˠ'�lz1���+?�:N��W���(/\ ]��W=ER�t����Ep�~�1Y�s����e��khx��k/#�BK�v s�J��b��TV�׋:��N4���ظ�[_mS�.�w������X㻴x��h�$�.cܥSWc +���F�?�P����-���o�kF�8�R:�?7�_ z�t`㣃F��C��ba��̄ي*vY~����d��ƌ@1cH�M��;L�& +>jY,0 �Uc�V��)8cP��:�U���^�X�ۭ��X���Ao�N4�+H��M�oJ��@�G,�$H4�-�M��� +n3��j�9DG���ɇ��H:����,��`�w� ���`.:��rH��p�� qN��7�_�y�=8�;ٯ����� !�'7�|����hh M��#��7|L=�L,F?�$��TC������k����!h]�$����SL:K!�m��@�<(��'���y�Z$w$��� �#�#/_�8���Տ8���a���~�!�z�r�Ջ����g��� ��hhQ������@MqQ�ɢ{��c��C�wҬ{��H�XTt�e�\�=�q&�˶y�����[b�D+�b�4t�U>[%t������Ղ�CyV6ϑ,/!8�������Q%\��;k����1��p���X�8�9��"����m�eK:�-��cl�-z�*gvY����f�e=̭��HG)��̽9ۻ8��s�ȹ����ÅDeZ "rC����rx~!v���B�>̩��������ο�m���Ѻ�w2ʀ�nu�ܑ,pǐb���f�����;\1Bxv�8�_���E��Τ���w �/M�?$I��N�?����Ϧߒԩ * ��:9[_]e�e#�)�_���h�����g?>���.��ko��������뇢�:vN��5�7���<��<;3�4vm�{!�K/�h���E�%��(��H��/�uA����DH�y-���?LO������� �d)&HA�$��\����q�[�VkI&ɔ���7)�n�KA^P������hgd7THF3�J��h��uy��B$4[�$�$g7�̹�|�s"�@�e���]]a�,�La�2�*Y��"*��1��t�?�������JoL��$�T��Z� �L��T�y� I�h�|��� ��&@.ה̿i����BafB����_�'�,�D�BV ��E�)/�����{�Ȝo D�o �/�˳��s� �,��� �'{{IJ� �\��#�����ɫ;�IV���K���ْ��6�x���@�%���^�R�^ڣ���.�yʾ]�?J�#�<�ֻ��<�@��A7��I���*��E����(oR�t깾ֺ�e"'&� ��%�f��T�=�E��n��2�#��V\�9�;��Y˳���=7z�J|6$,Bgh����gxv�5ꉗ��R�S�3̩�o1� V.db+��P>j��f���tzP�D��G1�x �Z,��+(r����̄4$�3�P�|�W��%���"�{���.ԉ�YQ߁���e�x�����`w�����ȷ����Z�ң��W��z~ۤ�j�B�OL��gk*����*�oh�����J�� ś�����oLIE@G�h��4r����~t�� I�T�q��j"CT6$,,���=^ ��"�=bJ��o�#~|󋖾���=��_��� W۵�,*�yz�M�R���n�? g��H�3.d�̲�\r*a��q W�#�9�z3�}���}kz�TQ��{����4l*O�5?��]M�]=������Y�a ��r�� &S�D��̈{�a�3����*��t=���΃�� aC�ƈTp�j���^�|e�h�;�+��QB�xn��M� ����:�Ns�_j����Z���ל߲l�N{+6�-�>H�l�>f��l���@�����>h.�,��Z�$��h���o� ��n��Ӊ���Q�!]�n��uy�=̿�ﳴ�o1�/�\�7�SFEc�;g$1����r�c�J@ C�(,[��; �@h ��c���/^�O[�?'U5�<$6$�E� +�f����4��}P��E���L�ӆ�E�P!ܸ�Y�k���K�^,�|Ɲh-���^}�[�4u� +� Ylh�D��c�b���)�hd�a��!�GU�zʕU�Ƌ��|xԤ�8uӑ_y�juD�Z[�e pB�O̕5tuX �B��^���[9҈��ɯl%�Hև��Mn sL~Tͤ�"]�A�!��C<�� ̈ 3�P�w��[��>HY���7�Q3�e�� +�i��74����b.3#���� ��pP�h_�{|���e�s� [�T��=�.庳��/�f ��y<6e�!6؃"��m�*��%jQu +��U�dIS��b-e!f��Ś�#��R��xn�Oɿ�壳�~�ݶ� P�n��%�I��w^�i ��lkF�n�μؒL�IOO�.b����Q� y��?�]3�(�R/T�}̪���&�M��ž7֛�z7�@ 0]O��:�o8^ d2mx�|�X�f�'���H^=D��ncn�1�M�S�`2����`t�}�&0&��K��8#�0��,���&��B����� ]�6 �ekRi^F��p������ΌX-YAݶX��h���2�������U�j�5H�� +6u���ۧ�^��P=u=�y�߭���"j蛂Jv�R&���9k�iDŽ:",�C^�״f����c��((3�90��50�iU}0��zp��T �1l�\�4�ި[ׅbw�G���� �8�V�̌��H�_hìX�� +L{����|�m�Vl���#��YY��l�QYr��ْ����5$��� :#�A'� !͈D�Txg��?�t3�E�)UC��:��7@N�P������'6�r&����;RL~���?[�)7,�� �(�^R��:I4���LZxSwU�(��p�*͟�i�VS�3�}�uh`c��*>�6�Mi�l?n���^�6E���*�٩�iL����!W��g�!��tQ��V�T �WA`��eO��#x�b��&�}��q��sl��j�h�o]8�t��:��ߚ���^gPz�M�a��e�{�"AOo��h���0��7�����Y7��˧�W�y�o��}�15c�0#����,�y��Xƴ�< F� +�@���Žv�������z��,��$�ѭ���b��;s�\�Ɯ��C:T��&�]���7��OO�@����&=�Ѱ5E 4��P0�6�#]��nv�����j�1�f2/�y���%l�:�gW�K�Qп�t��q�L ^�c��f�t���y:�`��L�|��0�$A��+��EH�:����(P�lH�EO%���0\&�y:����0���$ �N' +��� +��T���{�nz=S�� +�;�X���h���#�K�mS���J;���_5�n�k�`��\��bPX*Iy��qv�/u� +K�SЋ�`i�����9@' +�#��&�b�e)����-9ӆ8rX�?��n~�O ?=��hf֯f[��M:�dq=��'���o��x�ƀВ�=�G+���4�6�~��(x�T=o�0��+�5�t)��dj�!)��L�%"")ߑ����2�Xr�t#�������5��m�Z^-WR�7�������÷�v���`S�ѻ�sѳ�u�m�T�u���2P��W�+���ϧ*6��LJ@������.Ac�K)"P��r ��\(��@K�X�Q1�� � !��GA����G���6�?����84w �9�r;����L~�/s��^�(��:�W�Cybj��z�2_�1�ݤ�#�!,�G K�Z�2{�d}5�?s��H�Za��ǻ��KxZ`���e"_9cb��п�'1�? |eSl3,��]~^a�0�/6��d�v�'����Zn����6�E � �1�����cK[!�ɜ����c,�ž0���;sHM��W�OK�*�(PPRV�p �w��q�s�uUVVRP���S�N�@��+��|K�V�z�BP +���B@�RilO�-�kw��K��� $@A�O����{oތ� �N�9��$�#8� ƟF�����.��y~6;�]L��y7?��l9��lv� lY++� ��;�E��uk2dQ�����w��f6���@�N�YF��`��jC��h㎢Z�u�Ӵ��&���MvS=�w�>{D��ߏ*Za�e�T�-9@���b�p��� V��u��d#��2khH*e�O�N�~�=wP���F�&��`���WV�R)l�!��P,��4d*�@����JjP� �m�%��ZC� E�c#H'�J2�f�Y~��'�p5ͧ��U�/?�e� ++j=S��s����r~ul��*��:մ����F�:����Ž�������u ���C;M�!�NB��h�XUtBCp^ m� ���B�[��~^A��Ԛ��nz`�{��mg�D�3��5��������s�6�+�-�vYҽG#�dĢ+��p?��#Ԏ��h�vq -+#>a€�i~�?�s�<���]Qk�7��Zj�)�Jn +eЇ��:�'��$]�¬����M�G��P�G��v�;���/��/�(��*J-)-�S��RPPPP�N�,��/H�+H/��/JW�A�����&�敔�%���p�Zs +g��40���&�6cӮ�k`����.��^v��nc����h��??�߈0 �2rݛ�DCʋGBMB \ No newline at end of file diff --git a/tools/php-cs-fixer b/tools/php-cs-fixer index 859a4c38b0f..997a14782e1 100755 Binary files a/tools/php-cs-fixer and b/tools/php-cs-fixer differ diff --git a/tools/php-scoper b/tools/php-scoper index 4145e1791fe..14b18a08bdf 100755 Binary files a/tools/php-scoper and b/tools/php-scoper differ diff --git a/tools/phpab b/tools/phpab index f8e426260ef..27d91caf950 100755 --- a/tools/phpab +++ b/tools/phpab @@ -150,758 +150,840 @@ spl_autoload_register( ); Phar::mapPhar('phpab.phar'); -define('PHPAB_VERSION', '1.25.9'); +define('PHPAB_VERSION', '1.29.3'); $factory = new \TheSeer\Autoload\Factory(); -$factory->getCLI()->run(); +$factory->getCLI()->run($_SERVER); exit(0); __HALT_COMPILER(); ?> �*� -phpab.phar8vendor/theseer/directoryscanner/src/directoryscanner.php�"y�t^A �P��7vendor/theseer/directoryscanner/src/filesonlyfilter.php� -y�t^�A�f��<vendor/theseer/directoryscanner/src/includeexcludefilter.php^y�t^T�B�l�1vendor/theseer/directoryscanner/src/phpfilter.php� y�t^��FA�'vendor/zetacomponents/base/src/base.php�Yy�t^��\��0vendor/zetacomponents/base/src/base_autoload.phpNy�t^H��¬�Lvendor/zetacomponents/base/src/exceptions/double_class_repository_prefix.phpVy�t^6��w�7vendor/zetacomponents/base/src/exceptions/exception.php�y�t^ CTs�Avendor/zetacomponents/base/src/exceptions/extension_not_found.php6y�t^~�9 �<vendor/zetacomponents/base/src/exceptions/file_exception.php-y�t^����5vendor/zetacomponents/base/src/exceptions/file_io.php�y�t^�O��;�<vendor/zetacomponents/base/src/exceptions/file_not_found.phpJy�t^,T]DX�=vendor/zetacomponents/base/src/exceptions/file_permission.php� y�t^�D�g7�Ivendor/zetacomponents/base/src/exceptions/functionality_not_supported.php>y�t^� V&J�Fvendor/zetacomponents/base/src/exceptions/init_callback_configured.php�y�t^�: ��Dvendor/zetacomponents/base/src/exceptions/invalid_callback_class.php_y�t^ -Z»�Bvendor/zetacomponents/base/src/exceptions/invalid_parent_class.phpEy�t^����@vendor/zetacomponents/base/src/exceptions/property_not_found.php�y�t^�"�yA�Avendor/zetacomponents/base/src/exceptions/property_permission.phpfy�t^W>�D��?vendor/zetacomponents/base/src/exceptions/setting_not_found.phpTy�t^�H[Y�;vendor/zetacomponents/base/src/exceptions/setting_value.php[y�t^���3vendor/zetacomponents/base/src/exceptions/value.php�y�t^���.Ѵ6vendor/zetacomponents/base/src/exceptions/whatever.php y�t^�8�K�0vendor/zetacomponents/base/src/ezc_bootstrap.php�y�t^U–~P�+vendor/zetacomponents/base/src/features.php�.y�t^� -'��'vendor/zetacomponents/base/src/file.php_Hy�t^��� ^�'vendor/zetacomponents/base/src/init.phpVy�t^uko��Gvendor/zetacomponents/base/src/interfaces/configuration_initializer.php�y�t^�E��8vendor/zetacomponents/base/src/interfaces/exportable.php�y�t^B���5�9vendor/zetacomponents/base/src/interfaces/persistable.php�y�t^9�J �+vendor/zetacomponents/base/src/metadata.php�y�t^���b��0vendor/zetacomponents/base/src/metadata/pear.phpy�t^��<���3vendor/zetacomponents/base/src/metadata/tarball.phpy�t^�� g^�*vendor/zetacomponents/base/src/options.php�y�t^�y*Ll�)vendor/zetacomponents/base/src/struct.php?y�t^�t����<vendor/zetacomponents/base/src/structs/file_find_context.php� y�t^�-�њ�?vendor/zetacomponents/base/src/structs/repository_directory.php y�t^L�8'U�<vendor/zetacomponents/console-tools/src/console_autoload.phpry�t^��i�>vendor/zetacomponents/console-tools/src/dialog/menu_dialog.php�y�t^1�w�ԴBvendor/zetacomponents/console-tools/src/dialog/question_dialog.php�"y�t^� ��t��Qvendor/zetacomponents/console-tools/src/dialog/validators/menu_dialog_default.php�y�t^+��Xvendor/zetacomponents/console-tools/src/dialog/validators/question_dialog_collection.php�y�t^�O��N�Uvendor/zetacomponents/console-tools/src/dialog/validators/question_dialog_mapping.phpy�t^���F�Svendor/zetacomponents/console-tools/src/dialog/validators/question_dialog_regex.php�y�t^���{�Rvendor/zetacomponents/console-tools/src/dialog/validators/question_dialog_type.phpCy�t^(���9vendor/zetacomponents/console-tools/src/dialog_viewer.php" -y�t^?R28��?vendor/zetacomponents/console-tools/src/exceptions/argument.php�y�t^�X� δRvendor/zetacomponents/console-tools/src/exceptions/argument_already_registered.phpE y�t^�/wm'�Svendor/zetacomponents/console-tools/src/exceptions/argument_mandatory_violation.php�y�t^ ���Hvendor/zetacomponents/console-tools/src/exceptions/argument_too_many.php�y�t^*Y�۴Nvendor/zetacomponents/console-tools/src/exceptions/argument_type_violation.phpsy�t^u[�:M�Cvendor/zetacomponents/console-tools/src/exceptions/dialog_abort.php�y�t^�"��@vendor/zetacomponents/console-tools/src/exceptions/exception.php�y�t^P[� �Jvendor/zetacomponents/console-tools/src/exceptions/invalid_option_name.phpy�t^�d��Lvendor/zetacomponents/console-tools/src/exceptions/invalid_output_target.php�y�t^�]vmw�Ivendor/zetacomponents/console-tools/src/exceptions/no_position_stored.php�y�t^��RGy�Mvendor/zetacomponents/console-tools/src/exceptions/no_valid_dialog_result.php�y�t^�X���=vendor/zetacomponents/console-tools/src/exceptions/option.php�y�t^�}��Y�Pvendor/zetacomponents/console-tools/src/exceptions/option_already_registered.php�y�t^�-/ߴQvendor/zetacomponents/console-tools/src/exceptions/option_arguments_violation.php�y�t^~x��Rvendor/zetacomponents/console-tools/src/exceptions/option_dependency_violation.phpy�t^r�����Qvendor/zetacomponents/console-tools/src/exceptions/option_exclusion_violation.phpy�t^hV��m�Qvendor/zetacomponents/console-tools/src/exceptions/option_mandatory_violation.phphy�t^�YXpA�Kvendor/zetacomponents/console-tools/src/exceptions/option_missing_value.php�y�t^F�^�Fvendor/zetacomponents/console-tools/src/exceptions/option_no_alias.php y�t^�����Hvendor/zetacomponents/console-tools/src/exceptions/option_not_exists.php$y�t^�6E��Svendor/zetacomponents/console-tools/src/exceptions/option_string_not_wellformed.php y�t^��0���Mvendor/zetacomponents/console-tools/src/exceptions/option_too_many_values.phpwy�t^����Lvendor/zetacomponents/console-tools/src/exceptions/option_type_violation.phpy�t^��D/�1vendor/zetacomponents/console-tools/src/input.php��y�t^�&�8�:vendor/zetacomponents/console-tools/src/input/argument.php�y�t^�h"��;vendor/zetacomponents/console-tools/src/input/arguments.phpb!y�t^�JES�Jvendor/zetacomponents/console-tools/src/input/help_generators/standard.php9y�t^�B�j�8vendor/zetacomponents/console-tools/src/input/option.php�Oy�t^S��~��Evendor/zetacomponents/console-tools/src/input/validators/standard.php�y�t^ x��=vendor/zetacomponents/console-tools/src/interfaces/dialog.phpT y�t^*/Z;�Gvendor/zetacomponents/console-tools/src/interfaces/dialog_validator.php�y�t^� ��5�Kvendor/zetacomponents/console-tools/src/interfaces/input_help_generator.phpy�t^t��Ӂ�Fvendor/zetacomponents/console-tools/src/interfaces/input_validator.phpyy�t^eutov�Lvendor/zetacomponents/console-tools/src/interfaces/menu_dialog_validator.php�y�t^���T�Pvendor/zetacomponents/console-tools/src/interfaces/question_dialog_validator.phpy�t^��&cִ:vendor/zetacomponents/console-tools/src/options/dialog.php2 y�t^3�Y�?vendor/zetacomponents/console-tools/src/options/menu_dialog.php�y�t^�1v�f�:vendor/zetacomponents/console-tools/src/options/output.php�y�t^�0ِI�?vendor/zetacomponents/console-tools/src/options/progressbar.php�y�t^t�e�%�Cvendor/zetacomponents/console-tools/src/options/progressmonitor.phpF y�t^� ��Cvendor/zetacomponents/console-tools/src/options/question_dialog.php�y�t^W�ia�=vendor/zetacomponents/console-tools/src/options/statusbar.php� y�t^p�~[�9vendor/zetacomponents/console-tools/src/options/table.phpL"y�t^seK��2vendor/zetacomponents/console-tools/src/output.php�My�t^�W��?�7vendor/zetacomponents/console-tools/src/progressbar.php�:y�t^dm|��;vendor/zetacomponents/console-tools/src/progressmonitor.phpZy�t^���q�5vendor/zetacomponents/console-tools/src/statusbar.php y�t^j ��rM�?vendor/zetacomponents/console-tools/src/structs/option_rule.php�y�t^ ��Avendor/zetacomponents/console-tools/src/structs/output_format.phpky�t^h�+-�Bvendor/zetacomponents/console-tools/src/structs/output_formats.php+y�t^�D����1vendor/zetacomponents/console-tools/src/table.php)sy�t^�sB�1�6vendor/zetacomponents/console-tools/src/table/cell.phpy�t^+(Կ�5vendor/zetacomponents/console-tools/src/table/row.phpw/y�t^� -h%ڴ8vendor/zetacomponents/console-tools/src/tools/string.php�y�t^���F)�phpab/Application.phpJ&y�t^� ���phpab/AutoloadRenderer.phpc#y�t^� -����� phpab/CLI.phpQ^y�t^��N��phpab/Cache.php@y�t^� "9��phpab/CacheEntry.php�y�t^���д"phpab/CacheWarmingListRenderer.php�y�t^sy?���phpab/CachingParser.php�y�t^PI!��phpab/Collector.php� y�t^6��6�phpab/CollectorResult.phpT y�t^�-���phpab/ComposerIterator.php6y�t^��`�phpab/Config.phpC3y�t^� (��ܴphpab/DependencySorter.phpmy�t^o�s�:�phpab/Factory.php� y�t^����8�phpab/Logger.php�y�t^ � �Ŵphpab/ParseResult.phpgy�t^W�ߪ�phpab/Parser.phpFy�t^�`Ӱ�phpab/ParserInterface.phpy�t^���phpab/PathComparator.phpfy�t^��ha�phpab/PharBuilder.phpgy�t^= #�״phpab/SourceFile.php�y�t^��%��phpab/StaticListRenderer.php�y�t^z���phpab/StaticRenderer.php�y�t^���$Ŵ#phpab/StaticRequireListRenderer.php"y�t^LX��O�phpab/Version.php� -y�t^��j&Ӵ"phpab/templates/ci/default.php.tply�t^/]i�phpab/templates/ci/phar.php.tpl�y�t^�~���� phpab/templates/ci/php52.php.tpl�y�t^ ^@�N�"phpab/templates/cs/default.php.tpl�y�t^�Bw#��phpab/templates/cs/phar.php.tpl�y�t^��2q$� phpab/templates/cs/php52.php.tpl�y�t^�&�N˴phpab/templates/static.php.tpl�y�t^p휺��"phpab/templates/staticphar.php.tplWy�t^T�.�ִ�Y�s����b��k CL^�_j?�[!QI��4F�����6�ۻ{:��&��)�nwo�{�{�_~[<,N��ޝ�;�ċU�RhL���Ç?�����?��D�B/z�I*��?���3��J�Z���p��'��{����4 ��4�#�"��C���Ʉ�7� �L�d.Z��'��La��4�x$�E2��Â'� M��$~ -|�^�8� ��9�f0�#? >!��<=Wz��n�i��$��z)Rēz�,����M喊�4��V& �$�@�S\8�KZ᪓� �<9;� �Z�K� ������(�~�;7��]����U{�kҊ=\R��f�zghh6I ��0 �]����g�3T�v�L��0*#�"\x�PY��`�r��۬��ͨ�vD%�8��i��0D���!�d��e�t8]��]#�F�@%됓:C��Iq�G�������epmY]iy�ٷz�9`X�4��a-\��Z4�R�j8�㫡���H�n�̶�W��&�-��j�ݕ��L��e��$��!]т������L�d8���;n�WE{��`�kC�ff�ѬE��t�5ehٺC4z����+ �t� �ܒ��Z�V'�3bh]��u��e�|g|Zx�Go�i[u��OpӮ��E<����Z"�]���i��jp��U"�bf�����邑=�����C�.�������l�ۘ��I�N��Df)��}��}-��?NNH�����d��J  &x�!%�f8e�+��9,>��H�~5��� L��$kr}_��i�*M M(��P��W��_�����bM�m/~��-��j�����Ά{e1�i�Fܱv@m�D��,���Æ9�,34����hM�s-�{ Ձ�T�5+�*CP¶�TB�0����e��ȅ�[/ �q��~�C?˝�^&|AK4N��X���~���� ����!�͋-�ߪ��oڭ�f�y �^���� ��_���a)l���1�; ��@��ԑ�WπOØ2���(�A{cȬY��[�y�>?�Y�aݍ���������޽-�%U�,���%`��U~Tvͣ����藡���M��~�-�³')`�B�ה u�g�po�Vn�(��@���'G5Jq[ 5��a��-�y�rD1G�߯��U�q�=5K��s|8���z�#�9�^�_̕�Ŝ���o�Ϻf�,��痽��*�kX���R�6�h�B,B�3�����]Tv̓g���N(�1�bT�Ю ~kK�)[�4�m�'��է�Ԡd}8�s��nע8�c�d=�Ơp���L/ �~��Pu�@Q<���PŽ�C��qS kW|<�1�ɪ��2jwɡz�� r� ��7C�S�Hת��V���ci9��UP�c���7�.AZ -T�(��C��K)��QbS��ϟ��"}�,,#�l_+��Y�Qf�ڑ���OGK�ٶ����ꧥ�#C��E1?|_R�Q⯺H6�b�]��:`� ���;�/�HUV�j��z+�ނs�>A�e�m����e�<8���q*� ���������%G���'��yv�k���ʞ�������x�y�U`��e�9ا���L�:���ݓSka�B�+ -W�I�J����/O�� f�e�6���M��|Wq� -��wo�Į;ɭ��Đ睌���`}kVh��g��tD�LUI�Rz������ m���HsFͽ1�>k��� ��=�xN��#��V�����&�gv��S\���ґ鈵�Ee���ڎfw�2jr:����k�u���i=g���[ ��|ٹ�_����_�����oůU�K�#_��ͫ�_&X� ��yxʡf�4R��t��~_�U�|ƫK� yi��������.ٚ�3I�֌� d��EI{s�t����g�� U�x��ɷ��Vao�6��_q(V�.ܤ+�ei�E�蘀,y��4@���蘈,��,��wGɋ�KlK��w��G�e�ٍ�߾�[��ݽ�כ��>����� �2�I?��Qʒ$[�<�&(�*�L=���q�jF���pI �S�X0\�m��(&�<�C��~:�Ĭ�g:("v�3���1]p�&��.ȇw�/<̜Q�nӐ[�8h��Z�s��sN�;g����WE�/z��'�t'�y��Xڨ���eU)��ųfܱ{��r�8�^1��M�%N��:f3@ؽ��x4r��+H7J(e���?F# -u�%�Y6�%@����U��s��a[�Z�8��ȑ8 {,нs�G����Q��2=֭24��-Q��V���~�u�u�񞰖�W6�����82 _�{��&�2.J�z��/ọrW��z#���-��.��5/,y�>�H�t� <�A�x��/ٺƊ������]{���ʻ�/n��,�wH��@�˻�=�����kդ�;5�F8)QfxC���� 3}z��i��o��X���9Dm��c�d���w���B��O^��Pkٖ�3������r������џ�V]o�H}����Zɮhҭ�e�� �x$ ^�I#�e ��k��Ѫ�}� PS'm�Uk)�~�{����z78~�r@/�)vw*�^W4LF����^�y�ǟd�\�x#�ORU%��>[v�G�|���͆�uIJ�R����Z�4++�-�*+ryJu))˩,j�H���r��hU�mi�mV��P�Y�m�4[e��,�S(I;��YUɔv���R�TkQោ�ͦ���kJ�<ʹ]i���z��{�J*V��H�]���j�bY�hQ�T^TY"��A%+i��O?p��B�d#��TG?@��=^:4�6���7�6״H��+Cz��ǨKE[QI��M�/�)���%�Oї���:��J ���b/.[���B������ԍ�L -�y -��=,ۢ���)@�iA�HY��[tC�k�r'�g��t�)�^y�he��$��`_�!#������̥����`~��iL��sY���X�㐏q��gv�gf�@f�W�>�CE��gs����sY�}�[��?�> �AL���q`5���Lh�Bg�O{�=_���:�!m��a̝�g���|΃��N����|��#�@\b̏)�ڞ�`�H���v8fk�=քC�.����oxHϢh��_������d[x��? �AN�=�ϑ����vt��E�fx0Ѯ��8�y����k��Xx���D��E�,�mK���A���"�?��1 ��<�? �`HmX����79�� ��~5�]N�Qv�Ś8[��@'�k"*��{ɒ��=~�|�ii�]�Lk�<�:� ~i#�B�o�l�k��-SZ��� ��7ʦ%�o���™��w;�l'�O�Z�m�fJ&شwQ"�\*#5��2��q7���t{�������68�� G.y�'Dǃ�9k]R������!x�w0Ц�$s�M� @�?Q�3� �$rW��%-�>�p�ey��Sy,?�g� n=u�JNk�Kj}�H�:�ʍD -o�E����:6O\eI�ɍ5�0"?��LK�x��R� �M|���V�@_{{T�%���d�1�c��/HP⮷z��wB���,�[�'O���Ci�~J$�fH�k&��J_N� ��!���JlT���iv^ڬH_���w��PTC��҇ӫ��o����L}���V�}���p4BOP��\�ӯ�w�N���W�/�����O� -�{�:/���v;uT��?��uj�C����I�=f�J1���Z�Q 2��ZЖ��D���JaP$�����;Bɲ(p��b�9Z���G�f�<�h� G��_�j�ʇ����?lh)����?Q�ŝ�q�/[�p�c��Vб���U�����0%˓{_�~�`�4�,�)�,�J�OE�pb�_��Vmo�6��_q(Z�������"K�M@�~w��{��RҴH�/�jj��`�>�O2y���� c]S%kY����Z�,��*_�M�JJʌ�ZR^R��*��d��I�HU�j��fK�2_�6�SY���DX3�$�e�˛Ff���}�a�l�$p�B=�����rmW��l>�q�_b�ImA�*�v[7ȧI��M��^�L���Siuh����g�̞E�i��;Y��C4�:�� ��ZD�?D}��J۝,C������vI#�<)� 0g�� S�enl�N���Y���I\��wX����GZK]H�D�,3��IJS���&�e�H:Fj�iP �C��{��:�m�����Uv�V׃L�$�Ytm���^��w�K�9��&��ED��sY(��]��Qȧq���-`��<�l����UȄ� $�\yxp�~ę�����ܟ[ �f�Ǘ<�fX]h�c -f�d���֞r�G7��G��8�K�Vvq'��P#��pF:E� dz���'~�]1?"��=�Ō��0] 8e֞z�s��]2'ҙ=��� =�Ċ9\/�g�����d[ �k =�ɵ��9���}IN��<�i(OEģ8b4�0/Xx�&>�C\,�'�mi9P��XOc�5�� �x�����k0�HmX����79�� �Ѹ� s]/�q��iŚ8[�!@� 5�|F�d�gs�ϙ�0- 4�5lbJ+�B����� ϱN�\b떃r������^q|�lJ��|p*bgѳx��$���I��l��B%�9OZ���<��(�O]�,���@tY�#���h*\�-D����1YR��B����+���<�4)Kt��G#mj�$�6� 4>�ߤѽJQ��r�&[���z�!��@['�Ia��>������-3���N�u/+���.��D��M���:5_̈���b����e5�>;�����I\J�`Zy������fP@���y�m&� zL�mU�U�����z͡�%z[a�*�\ˁ�����]�lhӖi�O���� ����&/7�Ω�tk6���g�oK������{��{U�;�� ]&��z2_�ٌ'�w�Y%�V��x2��m�O����N����ΞпuD��m���5���(Ȳ.�o±�39yB�N���N��*H�x�,�I��I,Z'ׯ���T$���I*�I�+��h��C�,�r.�<� -q-%����9?�)���'a���U��˜0�$�"�*�LB\:�D�sB'�r��l�,�i8��E��e�݆ X�I�~����^h]'KE�E�bFG�@H���O�pRC=m�_��y�q��e& �B~�E�b�E�f+����G$��D@��djA����B�����[�V݀�&鬧I��^\�i=�]�,n��2L�ǣ���8�Q�B ��H�U -�g��i%��T0M����mɨqr-ί�ד����p~���w7���`prqs޿�qzyqv~s~y�^����8�o�g!�e����H��4D�ʉ�O�%�l!��4y�l̤�%w2�m���<�P� 9���9)U�S+��]� �� �� D?�) �����y0������k��Ȟ����%V��"|�2�M&��A�,KVKP��Ha��N| =s�NZ�P��-y���KA��4m Jc"���HA�B�� �nq��+o4��]�G��E�rqֿ a��n�W�%�H4̌Ƌ'߼��{�������Y�L�-�,�E�L�c���d"=��^���W�˳w�7 ��� '�NF���Y�}������� }VzO���h-���#b�xFj��U5!JP+4w�{ -����]� -�����i��wA��`{��^�� ]�ܑ�D�������2M�`��{�$]w�p|��g��c�� �Y e2G�7�K b�x, -��Ι�艗QT%���0_���L-��P�1�X���ŀI-��̱�iv�y�̔\�y��DN�e�w4��hY�K�`q��h_�Q���M��Bv7�2]�J2M��[%��/�8��� 3������ʓɂ�a�khzG�\#:p]�P�r9G*�R�H��l�D�$_���lA�떁����2�L�K�@���F������+�%�5)�0��f�(�"��6܅��� �,��S�10�Z��������~�r���UH.���L#���1}ko��ߎ��o�G`������ ����^n�&sw@.?  -�+W���EN:8$�4�e2ѡo�1����1@#qB��a��Z��1� a�Jà�9S(��e���dWF].(R��^%����F��> - m��S]�$˒q�dL���@���(p~TA�52�\M�^ً�r�"8��� pdjZk8����@��5��,���}U/Q{P�Fi���� �y�!�9���R��Ƭ!�8L�̗):�t ��1=bDUDۃ��1F�l=1���?�a�L�0R]Ntƛ``��M4�i�M��k��� ��PDW���Iv9����V�eb�(I"wC�l����5�[6eY*��B\� ��dO�R� ���(�7��9W=Y �0��;���p�qZ�3��Æ�+d|��c�@�d�� -}���y�q(��Ob�d���q6���+gaQ�Χ���舕�Z��<~�th� ����,�}�nx���C�/V���R�������lj�o������/����/�|;^��E�+�vD�F�u�&P�מ�6ȍy�B̕S��F‰5=�$d�ф�j�,�+ �a0pAf��Ɏ� �� �lK4z�������|�o�>��������C��qTlo��o�����ܳ�m�'/�Zw�2z��~z���D����v����-����dڲۦac�F�rX�]�T���CMB˶�e"�/���L��h�>=�|?,�� �l� �~�n���3�b���T5E��l�\?=�\�`='�ǻi��}�G`&�Pz� @��x�}���;I�{���%�8I=�AR�SӞ��7� ���v�m�Qy0�p$����J�ffT��@�@8`��S�Wv��{Tt;��Xղn!�%�=`@t���O�R�lwN2���47E@��8�Q2����)��Sf�m�X�8�D3�q ��T�h y����@������R�El��%����e�0����=ú]�����-f���s��>{��}���ּ%����o�ác1MT]`k�a�����M��{I?ڦ�`�{���~�<�Uz��A:��Wҥn��x" ��񻭵}����l��|�5 ͤ�� `����v�ڨ��2�� ���ԭ�5����3�[����]�:���Mq`�-N96��`>�b.�֑�)����e����-��pw�+n��^�A�e�c2ֳ��a��[�� �K��/��jL��*{�?�T[L�A�b�ĥ�7hL����|7�öPƆO - 8�>��c�#@W��`XY�ƀ\}���yv�1!@hb@�µ�瀭/v��%� 0J%'B)̆ꈒ����>����0�d�lMѾ�X�?)P*�[����U�U�i8�鬾�Z�]5u5Ѧq����IF�>�k]͞�,�z͎S��$k�D �/ ��І0b�;5���yo>8d]t�=U*�D�Z>%� p�wt�Fbr�N���i�FV�H���� T�{5l��:�<���,������]�����2��V=��b;�e�b�FB�r���!U�o�*F�N ;�|)�H�0b��3�bp�} -�J=������e>���4�ތ�K���|��-�6x�h3U��~s]h��6��l�"��Cȧ���&���a.������s�j*�~3�Y�e��Pd^Tk{��.È�o�S�� d��r=���`��z -o���9C>A�V ���Mx[�:w9�������?�ˎ�P����]� ���J\��`ب��a���b}P�� -���5��t� ->s�w���D<>>���;�)��v$Z�x�o�]�վ�L�}c��~'S�`�ڌ{>��A���.e�7�f�1ƤݱzMh/ĺ�d%�1 6E���4)6zY:�5���q&Ӝ6�9n���j�����c[�U�d;���s�R$yL�"��EȤY�Άۘ�}]D�&�oMU0��>�aR�L���`m��!��e�c0�U��n�w�����i�?�������;0�7`5�;[��A�cE�MA�q�L@����w&@��� Zczꕌ�h��`2{�Vo�@� Ѻ�FUg���S �V����V����b���6�/����H��� �W�� � -� >J�/rc�R9�A����Yg7>��54?ҵp' p��J�6���u�~��� vե�)ͮ2�Y<�wv�� L�����/s�'+ �/Z�ʌ����JcϨ���*��1��ip��!.CZw�h�k� ��6��|\�wDK�]jXZ�$UG��8 ��T]�1�U��g��N�R�[�Rz��K��}~tEW ���ġ3�\�Z�� t��@�ٵ1p�Ú�E��y��V�!淪Aag�[>�r��<�#�l)jb�Ub�$~>�f �'�+��Q��(x~o)o� S]�q�Ӊ�̚��u] -T��ﱷ������&W���v�/4 ������@BVχj��lZ�:�y|����ea�ԡ��D�1 ~V���`pT��@%��:ƈ�'E��Du+�|�V�$;f�>r��Z��^�]��OtwtJ�" �����.u�����8 ��9�9/ -H���@t;ı��Ֆ�u� }&�ޖ��^�A����6%�ly{z�oN�u�Q��ma��V�J���v� ?9�� +TVh��hkó�Û�W����Mpq�S�������{��cmZ���&�NYZ���xMRq�[�F��t[�oJNͯ�i\��.�W|6/���!rS������?��������8Ƈ�䔅�m{�[#������([�Ә{/=�-�_�z�س�%�<���� �E��y[�&Q�a���{%I����M����$�]h��@��v��k�NnjPK%�^�Ia�s\�)�;l�F+�פ�����]�����UE��p m=�Bc�NT0��n�"�fj�՞�}�[��깛k�*wɒ�d���׈�U].X �&wi���_a� ��mQ���f�A��������E�oU�\� �5�=�x�6��*�&�כ�� ` ʘZPF���m�e==���A����,��xHw�h@_�n���d�LA2?<��Km�#�L�Sj����\QJ�9�����X��e �:�ȝD����+�"� �U,)ަi�ҹ�`AӅ}q���D�U�����t��썎;L���DZ�@���.R��̱|�g�e�o��+6�B�q;�|e�������D�f!�&���[ٛ���gtk�S7�yy���x�!��+K�6��8�p�C�+Ԑ��=\l#���z&}�7�Y��y9�eq�r�Q�_!�aHE�{s�],�@ i�Ȓ2�?~�{\�����%�˩,��WQ[<��� �9Y�ۀ��z���e�n�Z��� ~=.O4U��6+�nZ*�7_�R����]�~��e��b���J0JV|6 ��Z�2Z�KJ�K�kF�=�%�����Щ���nEg��#^d�U8��J.0�AT�yfC؞֪*�;�� -(�( @��"D�e�>�h5a hb1&��M*��ȃV�Y�2+|-��|U?o:�z�k_�����ܙ�%���Z�NL����7�u���b7^K(5 �"� �Ҍr�I�+,*�-K8�u*ӯ�93�xu��|� J�u{fQ��d��}�"�� �,��O��@/��v��������F�b:���� ���rn��W��m�P|���� -���YN�w���K��/F�a�k�� #炙�I�#�K�U�;�jk���ȒJ9�+{�/ �0�*��ZG�����rH�+�q�L� ��|�?��|^��N'7x�΄к]�$�V���i[�p˯��4��x.n�5wt0�^|�����*^��=~Ʀ�N ��`�L靃���́����x�`Y����> 2�:4�\��:ף�x����N~�^��"eQ��М�n-�+i�e�����VT�����x��w ���els���.��\�,Qp�dY��ASС��M_Te">����I���}=ͮ��#c}#�9��L\RzP��Y��� v�-Q���V��f���n����n�8���Dnr��Z�����+l�6,�A� Z�DeRKRQܧ�!%�JCQm�$>��́�!��}q,F��È<�i�E.h�$�2�4��B}�* �8���'W�-�մ�1��D�+*�,E�jAw�hyO�-�aԈ> i�\Kv@���5��L�Е�X�j� g �����jP1}D�H%�76M�� ����b�2*�3t]�%ˎ����TGV���I%Z���ܺ�\ϢlR�d�cL� Ȥ���wr�Ft�|{s�ƪO�L�ФTp�x����-u�(������c��@�AS��6"Ү�ڨ�/!G��?���� �O�̂6��Vv-~à[�g��RX��J&�Ƈ3�F�ƚ�ʬ�](�E%��<�j���Lע�!b�],�kt3�Hݐ��(���1�����#���v�څ����d�^��]�^�%����?�j>&�%CW�\H�F�LE!��SC��6�b����YI3 �x�M� OL��Ud��qb�6�2�W����C��?����ȧ�}P�7n����t���[����<����H�.%�YJz�ak�[������{K�<�~}W��]�&ރ�����Z���\Aʚ;8C�8��9�������jg������p�}�Ӈ�k�)��w�{v�:*3����W��(]C(�E``�/ش1�9:���Jb�x��%� U� ;�3%fja�����r8(���s��O���F=� P����b�"�{p;*x��_�tm��披�z��r�U��^�ts|��� ��w�sI�k��oF�ߍ��T�o�0��_qB�Z*F�}l�u�ZSCW�Se�#X vf;K����;'���� �ؾw�޽�dzrYv����H�h/�V:�D�_�\8�4ռ��B8w��q���fq����$�^ �� ����D�0oΰ��R��#S�L���a:�}��������M1V�+�E�"��+�� R�?���W� �)��Q��K:���>‚�D�)N- -P�V��� �1yiʵU�҃�5Z�T%�1�t�q pLK\צj��n���wb���pP����NC�J�A��gt��2�K���B -Ct�n��j�oA̜ "P��=�s4��ޗ'IR��@����I��\�����cnu�ΑZ?*eI��DIUIA.�B���Ш`��������� v��,Z,��� �D�Qw��8���0�}�ϾLogp7��Nf��7p1�\�g�鄾F0��s����H�Q*|*-��J+�َ�b 얶O�D�J=�W"G��O�aRJ�+帵����+僩x�� �)�Lr?2�XX��:$I�>G�$a=���{�/�d��-����%j�b�e�]S�����ѲhnZ�X�U��p��� � -�"[�Q�db�@s$�߬S�i���>y��6f���ݡـ�?�pA����������l��`��PE�O���� �Q��Fd4%�n��֥�|~�n�}R̊���?�l�lƜ�l��ԞJ����2�{x�Q����� �߃�^hD懒ҥ~r�R�/�='�}r�{��9��=�������H���:��j�"ߴ�V�V���Y5�=F�~�9t�wxl:��٧��TaO�0��_q��(Ui�}�+�jնV"e�O��\���l��M���ݤ�M#R�ƽw�޻;���*�0W�2!������_2��'��J/�1# -w�o��4��U>z\1N_�Jm�4�T�2a�G�#�WԠ$:��P*�)�E\[:(6�e�Di� B�����j)�t�D� �4��# 4J?@J�X�W� $���j̘N�D���YnA5��EE��NJ4�ȘM�,i]������5c?(���q���@����ѩG�l RY� �d�6]"VV�`�{t�n[�8޷IT���K����>���I6M3b��H�,�$����y49&��Vh ����<���*b�YL\ ָ�F� �&�e6th� �n�^L�(��������̢\��Y4������v w㛛�|9�D������z��-��6����!����C@��J�S��b*�����S��MK�'S!��$Of5�2����C��Ƶ�Ʉƣ��q�=mݖ|&�\*�I�ౝ�0d4��0�������5|�2�v{o��w�-;�N�*��ρ�ּ6ڸ��ɤ��$b��4TݾWo��:ϸ��*�$�ɢL lO���2xQ����W�/��Tt1��V!N���������\�բ�o@��۴W$��[g��f���Ƀ�j ���f%����y�IZK�ӭV�+��"���nD������o����*q�H�t�� ���������!/�a��uvϋVVړ���9�?oaa�=�r[~�X�s��n�� -��spq��Tao�0��_qB� -��i�umAE��j�~�Lr����NSV��wg���v�"!���{w��tR,� �v��Hf�VNH%U -n��?�Saq��PY��D��.U2|��p�q&�%8g��1�aN{��1�Ez�*a@���O����ě���[R��Pi� J%�Dri��T��{" 4� -���X+#ӥ])4v) �7c)Ѩ!c7����u��Zʞ�=�F�X���hS�Z��V�ȣs���w�}��.ˋL -{t�n[�8��I����K����?��s�aVU��p_�4l$����I4|O�̍��Z���R��|� V���LT -4���?��R�As�_?ؒ}n�@�Dv-O���:Y#��{w����y_0ً�?���.~�pFp���V�'���&�7"�! ��9�[�/�C�m�o@�^�5�7Bs�(�n���a����/J����Ǎ���joW�T�e�]��t|�M��� h_�� ���]|x��vk�/ �=��]Ee�x�̱v <7��-�m���+��7����,e�]/���:X'����TQo�0~ϯ8�i�J�&�e��KY��JM��G7�&�;���ݤ bB�PU>��������)�?:���BȤ0� . -0%~�ΙF{>e�.dӚt�3s0���e��ȵi�B�ˍș���� -�@�� -j�vU_m �]F`�B�Q=H]�x�F�����s�w8"�rS�����֔��9��Y\P�vD,Pa�TnUf��*^�d+P�7T/�R�yOF��eI�Vn:)՝c��DV���5� u��éC�l B�h|��\&�D�n*�D�Н�} �x�%�+�1`N -���0c��PӼ���m'��HU��D��������17�B�ɭo����XC�2�"�km]���V��[��`ئg�z�$}��lc�G� �(�y�D���rq��]�\q� ,�0[�Q-b:�!��-�*�/ƀd�§FYĔ[G1�S��NK�'�`��<#y�ذ�����J4�j�mk5��iz��Ua��8��_1�*-�8R�G�z���ktT���~�Lbk;�8��j���8�-���� ���{3o�o�����k�a!J��� )d�����oX�i+V�C�k#���dM���)� ��( k��W�v�c��B�2c6 -���kP�Zi�����b�\(���r�yťi� �6�r�����3��8$� S`�h�S�v��e���Y B�Be�P��t���k�T'�n -Q�y��,��O�E�{�R�TŘ�ߘ�$�:} # �?�������2�6����2�EbU] -&S��=��?IԖz�J�;fM�˜z]�M�%� �8��]�y����]�^��M%�Z�|�|o���.?�x�vK�G�Z�d*��<;��@n���<;��<��,琫�\ۡ���DC�m�d�������y�^�t��?�������.| ]�svݹ��{��Ô�� �,���]��E�V�� -T���� [m�<�[sf�Ǩ`�U�k�jD����9��}r�0\f'�O��Wm V =�0�Nݦ��w����g�E�L1R�LH_� -ȗ(Ԇ�[A���!��4���R��c`m�Bf�/�P�YY��g.��3:�y6[G!�����:�D����#����&��xj�.\����U��Q�C�b�/l�f�7���{���6�ӧԵq�W~��sr�� -d[�0����4xPZ�h���i�BO��.(���N�m;�o���+��ЖZ������O� ;W_-��+��t[����QJ��?�i�\������{��J��i�]]���Vzhɥb����[�gOD/���lv�� wz�чq���{�^����U�n�@}�W��J\Dq�ǤmJ.��T1i��h������14��;���� J���3s�̙��Y�ʽ����c�"DZY!�T ����E��h��X�*�ZG�[�D�( -B2���P��8�ED�^�J����O4�2Zȴ���(-�uF�A�P�b"����|rqK���X5�TҮ(FPi�KJ%�Xri��Tt�9" 4�s���7F&+ �Rh��̩ޜ[ �-��Nܖ�^7�lZ9�c?(��~��� �y��N:P�BY�>;�S���,O�P�C7��jǻ&�^��@�V@/�@XF�`em~UU �#<�& ��o��4�zK�[̍J�(H���4��b"'V�X�TT����2��Q�m�f;h8|U��`_�>��KP��/�]K����\.�ۤ����?�~��l�}��ËeZ�/Vz���n{��wEҘ�?NN����\�v����''۶C�h��N^�@.���W��z[����Vao�6��_q��e�C��)��%��\�C�8��5�@*��0�^����IjA�J�2��$)�yG�lwۢ֍�Z){��d �#"��~�>. ���z�S�����P�b��2�Eby�I��C��{ ��m���sR@������?���b��u]��#<�&�;��{��, -C��Re�,1[�V�`�W`��l�\3VS]���Em0�*��L�_�]�:�(}���yA�ȃ�A4��p5]��_.�*X,��rF0_�d>;�.��~�C0�L����!Ln%n -C"�����x�OrK[��\�%Gy*�X" �_�q�Ql��D�1�#�֙���Gm��K�`��)����:���>���x�����!�aH�k�_4�$���S��CG��Ba4�},)�c՞��fպ�y�č�t�<�M��ǐٿ(y�/���t��rdD���S��p7|����`���w��Mw�۾v�~��ō��w���5y{�����ߍ ��;�1��Î�w� o'�уA�w�{�W��T]o�@|��X�>D����jJP�F&¤Q���^�)�ν;ǡ����M��V�%�?n�fvg��Y���h0`S�AȴrB*� -pk��� ��ZeNj%6�m�Һ��q�O�3�� damH�#� -� ��������A�D�l�"z�d��/&�"�ϥ��H@#ݚ�H �6��"*��ro����@��09{�t�5�X;ЍBcײ��l%�vb잸ۖ�nu�Z9r�c?��-?� -`P����zt)������+;�/3�%ae��Beݺ;�A�[��>��V@����p���ڹjEMӄ� �)��btM�M��G�an���j����/� *R��%i݈�;��@*CWŐѶ �q�^��I$�� �l���7N!N{p>N�tw��jv����|>N�$��.f�e��g =Ma��3�{�\�d�>W�M�R���(O�NK�'[a&W2#{��E�P�'4~P*4���ZK"s�G)��e�_޺)�F�~d*1��MH J}�*���.�o����G�Q�M��]S.9�f�5r�8hi s�\���%�FX����{-G�?Oޡ ����8|^ -9x�| ����L�9�(l޾I�RtL�ƈ8��%��qI�{R��1(,�gD{�f�y0yd���}x�:'���¾_����Jg�h����w�Xn��<�0�L#xi9wa���q삳��o�T�n�@|�+V�R���cR5%T���b�(O�q^�)��{>ǡ����&��D !�������rQl�^0�`s�!H��PZ����KQb����,[ �05z���b2{�X8e4�L�%q0�O%Q���3�bRI?�Y�ZX���t"<�t�@�h�hd�����aժr�"�3�H-b�ڕc���G�e8����3>Q�Gj�6T�J��}�5Q�$Q|��@iz�{! �� -��oi��U�Ɓ�5�r� -:o�V�y+���ǒ׭�+�M3F�������'8�����s����qP����LrIX^dJh�э����!1+�!o̺[�1��犳 ��z,�౱i�Z ~Rg�x��D���aYR�~W�"�-��TI�"���y�~P>����q��]�!��i�D��-�� ?��$�0���$�܆�%�N��'�2�Ű���"� -��"��9L�;F��� ���§² R����t��j�4s* �j�$��i%R��<��KR��Uɣ-IdB�ȕ��}��۸ْo������/����ԧ����o���� Q���܊�ni�ޠ�Iuv�ӥ�W) ��d���-r��Nha�{hG��@� ��EfQ$[�i�'B�IP� ��v1�y�c�C����{�4��ǟ!�rZIGn@c}� ��Ґ+r��S�>�}������;��"jzS��u���s/[��]��;L0�{��!�;;{E��k��ù;鎶3F_�4#��;�ap����v������Tak�0��_q�A���c۱.Mf6�ӕ~*�|v�:�'�u����;�N� Fg0ƒ��{����ZUQ|r� LU� ��Bi� �+|��a��O�(U6e��~\ -�&��WF��_Ba��J�v��7dT I���ajj��v�L�|FS�n9�[2��e� -7�e�T����Sǁ�����P�\I���Z�y@ƤB�V�K�dF��V>4���چ�|!���,,<�ǂ��@�a������_�G�q�������l�_YjzP9��⅙�M7�V��z���=2+ �b�[�D�y�sU�6����h��;7u��oDOu:�������m-=� 46o��6�������'�V���:���������ڵ8|�Zː��Nv<���~8��%�k���ULow���u��d��/�ipCc6�A�/�D�K6=j���}R��#�#j�6�=�F��st�9� �TaO�0��_q����k�}�i��V��ډ�!>!�q���l��C���9I �M�R��{~��?��Y9F��`!s\+Ǥ�*� ��9�"T,��f�r��Y;�rQ:�pzE� �a�_�1GUw��̃L� ��|3����'��Y��u�h����`���Z['T�ߺ�0x`x�۠�fXog*� ��]�A�6kn2�ڑ�3� -�@b�0>�c.{����2�ϤRܫ����7��/H�����+�Γ�<�f��c�t��[�����S��KR�`H���P\��diz>��S��i�48�2��Tao�0�ί8E��DY������6��*2�tU?U���U��mJ���}gi�I��ݻ�����.� �L��B�\I˄2[ �~� ~ժFmw�� ��l��X[�$�C��Jp�3��'G5�tK�ƶL#�T�ӎ�t1zD J��V*�;Z�Ke�,׈Jkf)��O���b���3a�<"� -[P�0�*��bY&\iV��tPy".Qc�t�sU�� ���M!j��rR��@�t�CYҺSM/�@uߌ)|# '���S�K�oG�S�]�He�1���Lt�XU��I�{u���Qk7=`^ -��a0�����> ömg��)������:���Dzȹ�%C���M=^��Ċ�5q-Y�&�� @,ZM���e���czi�@��Pۘ��(J!NGp�q:��x�yy�����:JV�<��5\,��x/zZ@�ܹ�/qr9��Q)��ډ ��u�? �[�9���N�dް!W���z�BT¸�"��=*a���>��m�o�'j���r��{��!#��(�П���Y��|���Z �rM[m��!yJ� ��xh��sE3Ω�42��}A��Ŧ��Ƭ5ӻ�����:W��l�<dt��5!R���p"Ej�}����d���I�f8��?|��x�?�.�����ܗ�����`�#:)��Փ�_BG���b��GE�y7���տ��RFݚ�Js�n�?�[���7��8�T��SMT-��q��M�W��2 ���X��$�C Ԃ|KSj!�sNN���OQ�Ϡ�h�;�*ۇ�_�Z ���E�Τ ���`�S)o�rB)�����6ud$(��O�䷃}(�B���]V�݈x i�}�?���D�2 ��r|���Y�?��F8.�B��Jo4�������?tb�U�Wp{�v�{ �A[���>�E|��b4z���4^���_2��{,_�6��9X,�-� ��!� �m ��6��g��}p\� ��T]k�@|ׯXL!�q��Ǥ4u��"�!O�,��#ҝzw����D�дc��3;����WA8�0��,R���J���g�b���Q��J�*[>�X9����vNhO�]��,f�4��H�+�;���X���"YM����У��R�V�����A�2�� b���9@����z�/aG�=>��ő�F�=�H �6w�#*�eҧHE% �@��0�w���`d�w����eE�6�J���ؖ�OK^�� \wŘ�"�?�?���� 'y -�����o�Uao�6��_q0 -� ���Q�+���z�\�=JbSy�~�[?%��OB%S�I����$I\�����g�ʤ��gg������#@͖ C�A6IaU;�#���nt^�v������_㬜%� -���V��n1��_g�AT����~������O���� - �΂�2�������n�Q�TIҫȃ^�V�B�O #��7��m�ryW��;�ZwdЧ'K���@�� �+�V��+� ��i\Ex��|�0�a袏4�?`�8�5PEa�D5c۷��{�$�7�ɢ����wx��O�.��i�K2uQ�Y�i�%׋��rP�i�{p�r�Eoh���^9�v�1��m� �����'�v��L��D��t0�&:� -����O��ϱ{x���K��i�JN�F�!�]����$7��� @Gv������#�t[�#-��|� �y.��(A*��<4X�3�L�{#���*4v+k��b*�,��]�P���u�S9a݋1�O��)�� -c��A�~u8y�+��4������Uu)��|t��P�0~��5� ��zs� ��h��:W_�q۶���#m�8P�?�����b>��%��m�!��{5��Ě�����Fy�֐⪘r� &8m�Q����n ل��0Ia��]���)��W-?��>��K��m -�;�Y.��W�傾f�,>s�����) IF��6L��JV�? 얾O��LndF�Tш��;4�P�h*i���@�d�J:o*ˡ?p ��O�� ����'v�C�X�� Tq����{�~f>J���a�lmCևv� -w=~�������B��wf'���h����2fԾ��8���t��P�����ۀ� �?�pC-p��5��$�_fr�D�Q�$�"�FT����ÅEǞ^p _ >�פ_c���VWHg��� �Ξ�h^'sFg+�H�����\���p���XPr pݡ[{4���Bs����.On{Q����{vwW��1�WS W�����蒗��{� L��5? 5b��4*�d����^L{����A5e9=w �>x_�3?��Q��C &o[��t�=c�~�E0x�5���Fz�!�]:�1R5�9$:V�c��I�Zʯ/:"�lA��Gs��u��F�O�K�ftf�[��|ZY(=��BO��t���l2��g��4~�� $��<8:��h6:O�(�Ĕ�F���z�N� �4x��c��T�n�F|�Wl�>؆*}����m5B���Kp>�ȫ�;��h�)��=���@E+�&��ٛٝݟ.���egg3:������QklA�d�O�� -|W�ȏ쯟47�8K�R!%�wF� �St �j��c��S�i�Z��;Y�֧�W��, �y��n�澍�P I��fÒhǜ�oo���k:���st&��1�:��T*ύ\�*2�DD�� �sQ�]�{S��\gه�4�o/Rv�LO�Bk��Q� �c1�;���?� 4O�� ]��������8UtA�n*��N�Q����aL��k��r��a���叨��y�e]�-U"�t��&��;Tv����'�{[q��_���}O�+����R�t05*,:���b!�0��e��m��/P6�z4_�h��ӛ�n�[��f������V����~s���[���^m���-�ִ�~�o��Ղ%�U��x�F*�� ?M�-c�B����<[��`*�Ƣa_� � �����T2�j[�S� �� �d�҇��!Y����m�����{���|H�M#~t������ԕl� ����bG��qm�z���b��9 �-a -�M&J���q��x���(��~HYx���VU��U�4?)tf�Rh�Vp�DЕqA�H#�́�����j�,��$H���rm� �q��,�nR'��Y�y�|أ�YP�$������{I�U�%�.�����d���Ȩ����5�F�{2Xt Bd������"Jk�i��#B��k�Y���;�O�m��|0�{��@� �;�KT*�V�Š,wGs.G�W�j�1F���m$���_ e����?7��h�8��N̆ц%�׎��)�҇o�?W�Et���ϱ�����e�yh�N���QO;D/FA����� 5[~��~�|�/�}i� ̿r��޺��w+�_ѯX��m�U�‚�0��aA����� �>�.~���Ua��F��_1B�������5!w�Z=� �F�(B����]ww�C����Y�;��U>��g޼7�v��m�����"� JtVֹ��*%��� MI��9m�%A�*{"'}]��"J���n;2Zj��b�{�I�iބ�I%2|�f�a%�L�s��4���!�QZg��� *3�[��=�"��J���R)�|�Jn��9��ʝ�@�Q~��1��$#��%)��} ‰V��J(]�*v�L��u;U�ފ�����;we��h�Vʙ�#�@,�j���^��7��{q$m<7���LVtAl_�J�,d��k��c b6^ ^)ݰ�.z������M7M3����"�$����<�^�t��A��9t v����H��Ll�� O0 *,���b�ٮ3���^��Q����M��&)%i��O�$��d���Ê>N���|�LSZ,�v1�KV�b��M揜�K2��D�PJ~�,�S�����:�vN���ڪ �tQ�BRa�j6K%�^9����+L�8�ں[�3����< �s ܛEq^������:���:� w:Y���:�Gq�*W�n0��^h��%�������݃��!�,M���d9�]-���t�0YN�sD��Ҁ��>e��쀆tI?|����;��i����j�w���F��#�ǵ㾝�����.� - ���tL����ḟc���5zH�G��qſE�dc~���V�����@��������-��J��ê�E�e�敊;��0pty�=Yc�=�x(�e�n�g�k�r�ՌpZ���h�2�_����q�E|�"W�kviiD����u�v@�B�XGD�.�O�9��O�_�Zmo�8��_1kk������M�l�\��&E�n�� Z�m"2�%�����H�%��I?nu(����� �_�DӨ�{�/�Z�P&LH!'�L9�/�oL�kΒ4� -��)���4��T܇$ԝ/"���p�,X��:L��Jh_ ����1����0�Y�c1Jl "�I����Du�k��ۻ��+��4�ʌC"�b�`�0F(����f� 3- ����>i��*�i�B�XME��ݑ*�k'�2�nZ�u�V������H�Wݗ��4�i�6�O��[� H�с/=%(. -6����G[��9P�?-H8���U�p\�,���`�$�I��X,�L � �Iϩ�{���\��B�1�d��Bk���m�>�{�<�Q�Y ����j�{mE����U�3e -Y�+����'1^��a�>���nU�D,��Mx�t��^����#%�S�W�Cȷ�W��a-�n '_D�ޘ E����]�.�8mh} Ĩ5��g+�W?w_v_��mu������,o �{��:s��7�Mi_�A;�~0��=T%�R!ӻh� ����B��@$���Ƞ� �Tܿ��q1.��s�زp�0N�G� {>g1P�����?�X�Y��FB�D�@�Ap�x�™���!:���0�01䩄��-TϤC�j�<��O��!�YH�-��w�Fqf�8�Hv �0 k�#��ܼNrXź�W+1��r��|��B&A�eOu����m�; �mG��n��[�(TbI�wR�m��ʯ��sE jo5v4Cw�1��Zi������D�6(���m�Z~�X*�e�$��3�[�9�L�̼Ų��vA$����T���Q��?�^���HȞE. }�j�ۓ{��~�c�/��-X�/y�#��%�bˍ�@n�KK����������JV :��=n�{�$g�u�f������H{kJ���;�w�|����ʔ�I��������yh�О��U�ܟ�YRq�ݨ��_�]�r�Y�@�ۚ��,�'�!�˄���e��)۩���<Œ�+:A`� -�0�,=ԁ��̞�l�a�^u�\�k��.(���v�b�`�,22�SNZ����������!���T�D��C`U]X�@�ZX�V -^���z e�5�-�B_�&����W&ӅJG��0����)��P�Y3�Wy�������C:Ĭ� �4�m�s�:�|}����⠙1im�L���FU�}mq)�Pĉ�U�Ɂ촙�>��[E~Ɵ Y֜���4�^f"�y���7lƟ@�\�5���r�Q�v_4 ���s ��A�8]�-�)s&��On�� -cL���ք��� �*&���,.���s�t��. ����2W�6��~E����4@Gt/L!� ׸[fwr��ƛ2����O�uA�Pn��v�5�c �t)f$���"�J3U^��EЋI���������W7�ۼ��C1O{�R����"���\ϛ��~p�#�Z|{0��ds�TF:�l5|���S��1�n {h"�B��b�^�Lu1@�`�mǗ�} ?�c�-��]C��.�{{S��$v�D���Q�г� -Y��㰴����-:j�T�pA�����A�K�靬|e�l՛A*Kp��s˕ޗC=G��)��e`Q�8!�U,e����3mR33m�Q1k����z:�>�ɤV�儢<�LR8̘���b�����r����`�+ -L�1>��iSa"W& چ��Kg��x�����! }��7*�3�1Z����W%�<�7�P�#ͧ ��<��5Qg��q0Pz�¡���3�h���X��� �]b�M����J�������f)�/>�+K���U�մ���M���r��xQ��'�֙R�y*R��4;�Q���,Y6w�Δ�n�T��ʚ .3���#�6���e��/�� ����yU8��/P�nl�/&������q/�l �%�o��Z�|oc^��T��ŕG_w��o���zmFy �6�J�S��YL$��}���d(����lvr7�u���/~?`�Sd,M�N3�HaV�&yM�� -"�L��Hr9dl,�������d�(��a$�:@`�s�I�J�G6P< #ܚ�,J��������,��l�\g�l��t��LΣ%�w����d�l�Z�i�Iq�����_��o�#և �h_���i���Y�欐����S �9� �-�qē�Vk����OH:�9��D -K��4�s\��36�����W�Ր��4���D�������6k���BJ��Q<��_V��1_�IP���*�'3WK���J��tw��������j��ޞ����xu���_�������뻫�1��e7���n���{v~������;� `l%���F�Q:�dp@m�r�KD�(�Y�g���/"KPY�"[DE+��c�T�6hj+93J��6�����>�������j�L�L��zD:�r��=m��C�~�B�nΤ�E@f +phpab.phar8vendor/theseer/directoryscanner/src/directoryscanner.php�"|NgG �P��7vendor/theseer/directoryscanner/src/filesonlyfilter.php� +|Ng���;l�<vendor/theseer/directoryscanner/src/includeexcludefilter.php�|Ng~���Ť1vendor/theseer/directoryscanner/src/phpfilter.php +|Ng�\�n;�'vendor/zetacomponents/base/src/base.php�Y|Ng�����0vendor/zetacomponents/base/src/base_autoload.phpL|Ng=I� �Lvendor/zetacomponents/base/src/exceptions/double_class_repository_prefix.phpT|Ng1�MԤ7vendor/zetacomponents/base/src/exceptions/exception.php�|Ng +�F̖�Avendor/zetacomponents/base/src/exceptions/extension_not_found.php4|Ng�߃���<vendor/zetacomponents/base/src/exceptions/file_exception.php+|Ng�i��>�5vendor/zetacomponents/base/src/exceptions/file_io.php�|Ng��k��<vendor/zetacomponents/base/src/exceptions/file_not_found.phpH|Ng(���k�=vendor/zetacomponents/base/src/exceptions/file_permission.php� |Ng� ���Ivendor/zetacomponents/base/src/exceptions/functionality_not_supported.php<|Ng����[�Fvendor/zetacomponents/base/src/exceptions/init_callback_configured.php�|Ng�̉��Dvendor/zetacomponents/base/src/exceptions/invalid_callback_class.php]|Ng�ݛ��Bvendor/zetacomponents/base/src/exceptions/invalid_parent_class.phpC|Ng��3c�@vendor/zetacomponents/base/src/exceptions/property_not_found.php�|Ng���w�Avendor/zetacomponents/base/src/exceptions/property_permission.phpx|NgWj�&��?vendor/zetacomponents/base/src/exceptions/setting_not_found.phpR|Ng�� :��;vendor/zetacomponents/base/src/exceptions/setting_value.phpY|Ng�A�]c�3vendor/zetacomponents/base/src/exceptions/value.php�|Ng�����6vendor/zetacomponents/base/src/exceptions/whatever.php |Ng���*��0vendor/zetacomponents/base/src/ezc_bootstrap.php�|NgNy�֤+vendor/zetacomponents/base/src/features.php�.|Ng� +/��'vendor/zetacomponents/base/src/file.phpHH|Ng�'���'vendor/zetacomponents/base/src/init.phpT|Ngk;�5�Gvendor/zetacomponents/base/src/interfaces/configuration_initializer.php�|Ng*(��8vendor/zetacomponents/base/src/interfaces/exportable.php�|Ng;`mZ�9vendor/zetacomponents/base/src/interfaces/persistable.php�|Ng5��a��+vendor/zetacomponents/base/src/metadata.php�|Ng�v�bM�0vendor/zetacomponents/base/src/metadata/pear.php|Ng�L��[�3vendor/zetacomponents/base/src/metadata/tarball.php|Ng�\�+�*vendor/zetacomponents/base/src/options.php�|Ng���K�)vendor/zetacomponents/base/src/struct.php=|Ng�l �Q�<vendor/zetacomponents/base/src/structs/file_find_context.php� |Ng�JZ[Ҥ?vendor/zetacomponents/base/src/structs/repository_directory.php |NgC� ��<vendor/zetacomponents/console-tools/src/console_autoload.phpr|Ng��i�>vendor/zetacomponents/console-tools/src/dialog/menu_dialog.php�|Ng5lj�Bvendor/zetacomponents/console-tools/src/dialog/question_dialog.php�"|Ng� � ���Qvendor/zetacomponents/console-tools/src/dialog/validators/menu_dialog_default.php�|Ng+��Xvendor/zetacomponents/console-tools/src/dialog/validators/question_dialog_collection.php�|NgwO��N�Uvendor/zetacomponents/console-tools/src/dialog/validators/question_dialog_mapping.php|Ng���F�Svendor/zetacomponents/console-tools/src/dialog/validators/question_dialog_regex.php�|Ng���{�Rvendor/zetacomponents/console-tools/src/dialog/validators/question_dialog_type.phpC|Ng(���9vendor/zetacomponents/console-tools/src/dialog_viewer.php" +|Ng7R28��?vendor/zetacomponents/console-tools/src/exceptions/argument.php�|Ng�X� ΤRvendor/zetacomponents/console-tools/src/exceptions/argument_already_registered.phpE |Ng�/wm'�Svendor/zetacomponents/console-tools/src/exceptions/argument_mandatory_violation.php�|Ng���Hvendor/zetacomponents/console-tools/src/exceptions/argument_too_many.php�|Ng'Y�ۤNvendor/zetacomponents/console-tools/src/exceptions/argument_type_violation.phps|Ngr[�:M�Cvendor/zetacomponents/console-tools/src/exceptions/dialog_abort.php�|Ng�"��@vendor/zetacomponents/console-tools/src/exceptions/exception.php�|NgN[� �Jvendor/zetacomponents/console-tools/src/exceptions/invalid_option_name.php|Ng�d��Lvendor/zetacomponents/console-tools/src/exceptions/invalid_output_target.php�|Ng�]vmw�Ivendor/zetacomponents/console-tools/src/exceptions/no_position_stored.php�|Ng��RGy�Mvendor/zetacomponents/console-tools/src/exceptions/no_valid_dialog_result.php�|Ng�X���=vendor/zetacomponents/console-tools/src/exceptions/option.php�|Ng�}��Y�Pvendor/zetacomponents/console-tools/src/exceptions/option_already_registered.php�|Ng�-/ߤQvendor/zetacomponents/console-tools/src/exceptions/option_arguments_violation.php�|Ng ~x��Rvendor/zetacomponents/console-tools/src/exceptions/option_dependency_violation.php|Ngn�����Qvendor/zetacomponents/console-tools/src/exceptions/option_exclusion_violation.php|NgeV��m�Qvendor/zetacomponents/console-tools/src/exceptions/option_mandatory_violation.phph|Ng�YXpA�Kvendor/zetacomponents/console-tools/src/exceptions/option_missing_value.php�|NgF�^�Fvendor/zetacomponents/console-tools/src/exceptions/option_no_alias.php |Ng�����Hvendor/zetacomponents/console-tools/src/exceptions/option_not_exists.php$|Ng�6E��Svendor/zetacomponents/console-tools/src/exceptions/option_string_not_wellformed.php |Ng��0���Mvendor/zetacomponents/console-tools/src/exceptions/option_too_many_values.phpw|Ng ����Lvendor/zetacomponents/console-tools/src/exceptions/option_type_violation.php|Ng��D/�1vendor/zetacomponents/console-tools/src/input.php��|Ng�&ԣr�:vendor/zetacomponents/console-tools/src/input/argument.php�|Ng�h"��;vendor/zetacomponents/console-tools/src/input/arguments.phpt"|Ng�ث��Jvendor/zetacomponents/console-tools/src/input/help_generators/standard.php�8|Ng� :SJB�8vendor/zetacomponents/console-tools/src/input/option.phpJO|Ng��N��Evendor/zetacomponents/console-tools/src/input/validators/standard.php�|Ngx��=vendor/zetacomponents/console-tools/src/interfaces/dialog.phpT |Ng/Z;�Gvendor/zetacomponents/console-tools/src/interfaces/dialog_validator.php�|Ng� ��5�Kvendor/zetacomponents/console-tools/src/interfaces/input_help_generator.php�|Ng^��]�Fvendor/zetacomponents/console-tools/src/interfaces/input_validator.phpy|Ngbutov�Lvendor/zetacomponents/console-tools/src/interfaces/menu_dialog_validator.php�|Ng���T�Pvendor/zetacomponents/console-tools/src/interfaces/question_dialog_validator.php|Ng��&c֤:vendor/zetacomponents/console-tools/src/options/dialog.php2 |Ng*�Y�?vendor/zetacomponents/console-tools/src/options/menu_dialog.php�|Ngw1v�f�:vendor/zetacomponents/console-tools/src/options/output.php�|Ng�0ِI�?vendor/zetacomponents/console-tools/src/options/progressbar.php�|Ngl�e�%�Cvendor/zetacomponents/console-tools/src/options/progressmonitor.phpF |Ng�� ��Cvendor/zetacomponents/console-tools/src/options/question_dialog.php�|NgN�ia�=vendor/zetacomponents/console-tools/src/options/statusbar.php� |Ng�p�~[�9vendor/zetacomponents/console-tools/src/options/table.phpL"|NgieK��2vendor/zetacomponents/console-tools/src/output.phpZM|Ng�ᬲ]�7vendor/zetacomponents/console-tools/src/progressbar.php�:|Ng[m|��;vendor/zetacomponents/console-tools/src/progressmonitor.phpZ|Ng���q�5vendor/zetacomponents/console-tools/src/statusbar.php |Ng[ ��rM�?vendor/zetacomponents/console-tools/src/structs/option_rule.php�|Ng� ��Avendor/zetacomponents/console-tools/src/structs/output_format.php[|NgXU0��Bvendor/zetacomponents/console-tools/src/structs/output_formats.php�|Ngb!-�1vendor/zetacomponents/console-tools/src/table.phpBt|Ngr�e�5�6vendor/zetacomponents/console-tools/src/table/cell.php|Ng(Կ�5vendor/zetacomponents/console-tools/src/table/row.php�0|Ng� +/���8vendor/zetacomponents/console-tools/src/tools/string.php�|Ng���F)�phpab/Application.phpJ&|Ng� ��l�phpab/AutoloadRenderer.phpc#|Ng� +��� phpab/CLI.php�_|Ng@�]�phpab/Cache.php@|Ng� "9��phpab/CacheEntry.php�|Ng���Ф"phpab/CacheWarmingListRenderer.php�|Ngry?���phpab/CachingParser.php�|NgOI!��phpab/Collector.php� |Ng7��6�phpab/CollectorResult.phpT |Ng~�-���phpab/ComposerIterator.php{|Ng�㻂�phpab/Config.phpR4|Ng ���)�phpab/DependencySorter.phpm|Ng{ɢ�p�phpab/Factory.php� |Ng��#�T�phpab/Logger.php�|Ng,�I9~�phpab/ParseResult.phpg|NgX�ߪ�phpab/Parser.phpnT|Ng�����phpab/ParserInterface.php|Ng���phpab/PathComparator.phpw|Ng�`=Τphpab/PharBuilder.phpg|NgN61_�phpab/SourceFile.php�|Ng��%��phpab/StaticListRenderer.php�|Ngy���phpab/StaticRenderer.php�|Ng��}xȤ#phpab/StaticRequireListRenderer.php"|NgLX��O�phpab/Version.php� +|Ng���j�"phpab/templates/ci/default.php.tpl|Ng /]i�phpab/templates/ci/phar.php.tpl�|Ng�~���� phpab/templates/ci/php52.php.tpl�|Ng^@�N�"phpab/templates/cs/default.php.tpl�|Ng�Bw#��phpab/templates/cs/phar.php.tpl�|Ng��2q$� phpab/templates/cs/php52.php.tpl�|Ng&�Nˤphpab/templates/static.php.tpl�|Ngn휺��"phpab/templates/staticphar.php.tplW|NgY�.�֤�Yms�8��_ї���f���%��q@Հ��&���Uʱ��H�$�R{s��J� �@����U_�����ӭ����������_��t:SЈ���ݻ�����OG0��,b(���#���}q��/Z��20�J����Q�c�J%���J9��%��)ɗ"F��>e�x� sق�T̀ �ϗ +���h@In�wܐ���� �]�^��r6�tHC҅�k妑���`H�N�qC�hxkf����3�<9~H;��kM��?�b���C��{ +��rM���3�D �_��^P�r@���[�R�tB�l}ա]�Π��t�� ��p4p�[���5��ۘ�!u�u�� �Q!�ŽvRg쓡6��iU��2i8 \y^�0��vHp/0č҂�:-�~�{=���rP͟�Eݐ��xR�mB߻!�ć�3H�p��s�'���j2�+Zp�'a��@]��5q��#}� �#=B�K`�%WzE��o=���iB˧�C��o�[���q�8 �e)�[Ƶ@{�t��6>lB� j���A0��-�������h�zYuS����%�#�P���R͸0ˮ^e���n5$Z�*|���4�]]��{��7Sjq�nOS5[ޟ�|�V3����N���d�E#�3 ���8���Hk6ul���B��Ҏ�p�4� ��4}D�߾H�sXDj�έ�}���E�I�)mI�V�\fJ�}��"g��8[&y����z)��I+T��+�]�cu=��IV���a$� ~�����?�")�� z��\�����/�v���a�UX)1S� p��^JO۫�� +c݊�j/ ����C�*����*��0��ES�Ck�)�L_��u�8U("�;�˳Tb�}�~KS��f��L~����$ D��`G�W�*�+�lر�D4ݚ�����Z�$�''P-�G�&�1,�4�ɒ�y��$4���gi��k18�mȏ�X����0Ɯ�}ً_�ڀ[�P� �u~�'[jrj�Fܡ�V-D����z�`�����u?ߺ�{7*v��2..@�%��嵈�:x*n�Պ���u8�~�6k��*�#��� +�=�X����o�%�ܒʊW��F%nk����3�;{�yѰP���,���s|�W̭�y1�烊y�7?��kԫb^U�����aU���_+���.-;�����^T��׋�5�6�F!�^3��o�Ϥ�YT��3{�y��c5ò�>���Xpbt��H_)O\<�F��U��js��UP�f/�t�qU��d�&,��0�a�o� �])N��֦�n�F��t�q� k[}" 'Ȕx�Y�J������I6e�`F�4��=�}��j�ͩ�>&�9}(k��e��w]R[�ZP���3�FJ��q��P�ߴ3��D,����M��������G��]�q���\��OG���O�����e��DV�E9?|[R�^¯��4�r�]��^u��̓���S�lV��k0L�1Bo ᨿO�b�Ju��wp���%K^�8���qv��pa,��:{)��ؒԿM����BfZ�g�#�9�����0��u`۶�y�O�Ge� +��Żg^�����x,{����4��[�f�Q��@̗L56��M��zVqf Л�o���]u�-EE��;���ÃթY����w����妚A-k���v6����7�s����]gHZK��P��.�A�@���%wC'�XS�#���Y)���ʖ逹E咛��n��#��)|����ֵn��O��`c|s�@~��.�u��J����W ���(�X��� +Y"�����/D.�n.b�4��V���7�}E���w�P���������-���3I��L�?�ۃ������f�����7����E�|9�7�VQo�6~��8t+fn�{YSt�%:& �I% �a`�sLT& �N��i���t��/"y���x���/�Ūwrtԃ#�����E�~3�W/_��������Q��t��� �]���-���Y�A���У���� �c�}p�z�5�L k�� x�v ��km����uK?�;`]��u��m�\7* #�r+tK��r�V��BX�a�0�]gﴹ�ƚVG?���^oy�g�<���Tc[���p�6 V]��h�)el� 7ha�=tڇ��ش�Z�N�%��`�;.;6+g�u��!����f�D��;@e����:X��N��?@:�佗�~������H�Œ}4�-� �u~Gd���c!�,�i��kf�����L�C�N�b sg�E���;�p�+3��&������by�M�y����P���e� P3�.hA +]������� + �dU9�$��Z2.�Y&��g��Td��w3N�ƁNg%%\f�g��D �VyY�:¨�IM&��S*I� 7�����0%<�d��F���*ESYňc�!�Y�%��2�iV�b�y��))��VP1 �� &YY>�10��n(i6*�&\u�$�1��QN Rɬ�����yG��2�W)Cƣ���Z�JҬ�"�f�D@�@�u�!�5'�H��#��GBRYK�IyA�͉8���$\-��Lf�h�q6�R���4��h% ��LRV `�.��g� EҚU)g9!�_E�(F:�!\N���JOq.�r�i.�w2�q��,T伤��I��tI���T�=t�2�V��ӡՂl�{��z�5�.�a팼_��|�� �n̩�Bz�ƺ �h@�B|�,���Uk`�;��IA� z�y �C���m���X���g;T_���������m:�}L=3��� +�g@�zx��U�o>)��¦7��F�w Lź���A��[���I�vM�"/A�`�j�{;���\:g���Ce�L'�����T1��ɾZ_w����4�?�����N�f��>��o�L��oo0D����#I�<���~x��9��)����6<|�������#�;T�J�/p����w�? �%~�s���Wb�U翚���aS[��C�/�VQo�6~��8t-�n�{Y�t�%:&�HI' �=0�9&*�E' �����ԸN�6C�Q"��>����zY�ww� ��o��\:;��ի?_�~��Y�0�����5�VZ�G��^��uTU��ؠ��ro�a�g���)�A�� ��И�-0��PZ�X�jFp��� o�v�2�Z�Bz��ǔ�F�R�a �5W���R:pK���*s��%F���5�h��M� �qk�,zR�)V�ƁE'����\��^)m�*pԢ��j�R��8��u�ŪTMQI�B�� 6Jo�ҳ��)��*B��Z�b�B��=��徱`�-��C�d��m@ظ`��f��`��h�BOl+ô��n:�-��MOd%o�}"�� �.�m��Lm��8�V&�@�V]a kV�"�Y�ki��Ӭ���y�U>��O/�&Z�lD"���'�,b(��OiB������9��S������!g��z�1��F㔴�sH(#��ݍb��LD������{r2K#v"̙����$4J!�N�c�a�%Ж:~��9#'�x>�P|>悊� p��IP�vJc� �yn���HD#??c�� +~���9�^?�E3A��ͳ��g�0��9'I�:�B�bJrv�q�a+Fp6%bJ�,�b/\�����Xl������`!#�)=&YL�l��(';!��~ m��E��}�a�朴Ít��:�(9��|�8�D�9��'����N��dղ�(/��DY,��7��Z� �r�Ɔc�c7��]u���c7U�@�fc�@�~`w0����X"G������������[[-nj��T�m���+�����yӞ)m�*qء��j�V��8���IT�j˺P�'��҇��i���%�_�.�ʔ� jH��:5�[��M�Ъ�n  +�9L1C|�]l��Da�<��]����>�M�K�B��@]ۢ�Lc��8��&�B�V�a+k6#�Y����l/����� �������ֶ��  �X�#N� +�qvC��hrB f��� �&� ��b�ING�d\��H/�������3N�ƁNg)% �#ΣLR"�@�8��]a���&���)�$Ɇ]h�g`c�O�LF#�R�������c�!�Y�%��4�i��|� q�)IN�f�1 7$� &Q�>�10~��Hi4JIw]���rK���*� �d�A�HL���'�Y�EȐqO� ��$�4J!���5�B�v|�✓���=��GBR�K׌%�yA� ��8���@\.��HFCo�q6�R���(���h& ��LR� `���p��\�$pͲ����דJ1���� �@�Њ=q��CHNcyx�q��˃d!#�)�&YL��y�9d�ũ�ghw�l "�';��/�)ʏ�-�gm��MQ��b��Ɔ��}�걫}����&\ժD݆�F"������i�zLS�r��~H���� �Bk����~dqt[��C�sU��зxޙ����ƪvh�C[8�� e����h��~ĬT�-, �Ìm������-q;�����Z������«��ݩ��[�E��l2{�~r��><��q� �ϒ8E�6��WX� +�~�a��g�7(]�� +A�)�֢v�n@ig®��'��l�2,�����ӯ�~{N +��7�e�JXmu��� ���#�\)�2p��C��iJh6fL� ο9���u�1m��>��2����r�mp|y�nV��Wx0�k�����fݼ����=�� +��������B"�/�CO-���}���g/�r������sBW +X _�2F�D϶��5,>E)Ѥ��À(+(��R���p�^��w��|�(���.nF�/��)��J ��w�Ma��Ze,!ӌBF�(?-&-~��Z0��ygK���J�9�����P���7��������o�|w ��^_�^܎G7py g������� \��Ӌ8�o��>P�T��H���4 ��်b�$W4a3�@F�yA���� +� VT,�D�J y +[2�UJ��mn���"��������1�B�9�����S�ݥ�M�������r�jV����]R�੄�����(%���C��}�%�, ������$��x�����CK��H��)͓ $ �ܡR!^����p���ϊ�������K磫��/W��ގ.nƗp ?����W/o�޶��ѵ[ڎnX���\Q#���i�h���)m���|4���<wvk@�dJ�i�W(�0�G�Go.�ގ.n�~��)4Q����*��C���T�AM�8I%��� �:F9x?����y��J�{�pQ,���M��<��HMOό�2�$�H0zܽs.6}X/X�@�S��� ���o�D�1 �*�}')���Η�`��ՠscȋ,��.hVd��u���%5���M��:��.>g��Pñ�9`�����<H���;pS��,|S�ܭ�H��/D�)˘ڀ�D�\��R�UI���QUy�ܠ�IW��_i�������dQ���As�L�Pd)Lщy:�A�Rt�T� �⒡�Ι��q~'!cw^وd��1�:g�S"�d�Z�0��d��t�>Ôf<����F�"�t2�l�����P�Y�?!Y� iz�0����~_��\#��Dh�,Wt����5�.����F���'���#�Y�۱��ߴM��{"�J��3�2㾜�� ��7�(�+2�8h9]�:���/4��|&�f���� |�4w�����Z���V�g�!<�� ڙ3�b�� r؞�ʨ˕��\�W;��Ѳ#����3L{܌��z*%OQژ�|�����b�5��"�,2�\G�[��b��fEn� I���{ ��M�Ԇ�?>H��u�D�A�{�A���=���,������So֐K&LAU!�ɋ��K�3�� ��&�L0J6��4T�:�G9YR =�T�ԝw96�w�f�eF����9텽�"m�qw63kj�,�\��j@ �0�<�7������cv/D�*K+��#IՕ��т™�u� ��QoM��LP�n@{j��Bd��A�����L�g&� q�HUL߾E?&��8�� +:gRQ<� �"��h��� ��u>��|��c�L�����c�����>D������G(�`9T�r��o,���f��e�m6�����_�Ȯ_��x}��/���4F*v�>��� ���(ogLH��_c�I�m�I���]s�vKx^�J�{ \h���HL+A�%.ۃ��z��������������>��_�~v����--���A��8��hϓ��U��4����S��O�}rOX� +8���5�=��w�g���4,�h�ߎj�[������f�H`ҶiF�H�~_Sу���/>=L�o?~z� w`�e3t/ޏ�0k��L�S4�"��,׏?|�q����Ӹi��C����;}���й�|>x�,�w�ܷ���K��$�bI=YN�pF�_PZU�X��o�f�Ӄ„#�߃Si�Y��F�(P5DF�����U�y��N�p�l٠�0����$�2��Oѳ�Bw�e >�nw[tFr}Q��Ӈ��O���aǂ�V4�� �GN� +���L�\goDZ�T�$\�o�O%�|Nń +����%�*���t�ݹ�r���9ˌ/��[���Lv���l��Xf�����ܖ6��c[��V:\2?W�>�V�#V��q�4���]��P��J j �Z�=k �:>���Y�_��R0�WoG&��Z{�?���>��ʼ�B��V%��`���Z�,g�`���)���qA�B0�:С�'CO�gQ<��W�h�g��ߓ.41ܐ}��d���B���r�(19�W��>�=��G>���4�FAOq��X}���6����q�iK!��dIż=�Z�]�u-����q�JPmu����ɮ�Ї�{�n?J�7�֚Oj�D;E�6���˝y`�'fs>�h�1���$���ƺT�hxߥ��H<��I�N���*Fj�*��mg<1�-��-�������_h&�Z����њW(��q�X�s��-��ᾷ�6FIleL��UyX�G�rp�}J�*E��ʟ��i>�Wς�����Ϝ*�Y�^�}c�M��7S} �؇�K�gY^gk�șxB>v��I��'�� �������~l���ꕞ���zro8��,���U7 ���Ȕ� f�X��ZYLT�uq�'�z�>�����nʋ�J��4��['�|-�]"� +��vLɝ� �o� \I,�6���Kc���2�`-��x�Bv���+4��f�� �v:�q�Q������4���7��l��5a������X�67Ept�+��K� ֙��1���&z/��dMue�kŌ�J�3�"v���\R��&�"�-��)�^���Z��ة�t�aT$�&K�%i��I��R�a�^����*�)���k3�}xѤ?��c O~���5��\Ϸ@5��#�)[d�*��Kp�1��������w9S�T���V�[���"�� ��>�[�qg�;���k�BO���DZCHp)�hu��VD�r��q{�a��8%l-�k�dkm��}~5�U�~��ϝ=�� +�{�� +~G��.��4�\閳���A �_�ZL) ��b������U�n��e4� vե/S�]e��xt����v0���AG���Y��p�R�Ûv7�ƞWM�*��c�����C&4�q&_�\]�6�� +s_�wO��$/��)��P&�a�����x���l.�륤�t�����*&���d�ijQ����zQs��#� V��Z{ cص5p1�5�N��y6)<���z���n�ꫮ�����dKQ���h��_��4,R�)޿�y�<��r�i�t�~:��aX�� ��Ak���7�=��Ƣ�}ru���+��x �������}>�{�Bp:ׇ��q�_Z�y��=���.��0��JV>t���1����1"�ô��0.�6�Wo�ʚ�Ȭ��CQ/k�Z�e�kl����ԕG �*r�L��i�5�@{1gN��hG�[�������8q��^��.�v����f;�^mS���QcQo{[�eX�k�؀#��V E�0`���n�L�G}ۆ� 4i�ho˳���W����vt}q��_���]K��ێ�i�K��Dii[T��5cHe��ҍ���ԾE(E9��m�� �������B���V�AkK2���n� +���� �8�E+㙩r�,mh�G�5�a�-��A�?ɍTt���䷸"wh=����!��x�l��(-���� ���g����ܫH��W!��I��Є�uv[:�Zm�5A��Ꞃ�Ι4Li܉t�t"zGzi� [0�AK:}Ͳ ݴ�\B[��Pb_)b�L�K\��q�9�z��AT��qmh� Ή��"��諳��TW,�T�%\���V��r�����-��ַe_�-I��~*��w�N�Zߺ +�ԗB� �zT�a�"hU0��7�o@��7� @ �8�H�V��:oiG�~�/a�!e��חݡ��F�� ��� .��^� +L�S���ml)Ձ9���ğ�}�K"(��V�E���7�zWE��. +#)�M�r +u1�\"�u��}Iz�M��4=��? �'}L뫻���/�W���8�dfTa��ܱV����{l:���v*���7����� @��|]v�-.7��t�@�m�������C��@�!��� /ğB^�����b[i,�a�Wz��D��Bв����^���0����eS3V�� /���(C��M���:"���<^��*�����K�,H��R[�1�ؾB��2o7p�]��� .���n��Y�����r������]�~�IK�5c���Z0�E���yֶ��j\Ry\��2�س]�~M|�8����b���3*/O���͂��$�� ��y��$e"XuK�y��qI�>p���ҩ��,)2�q^�5�l��1DWl�L��A�άm�5����y�=�b��6�<����ۙ����-j'��45/����|��nmK��Ay���@1�'L��Ƣ�����0M��ud4���n���'^������3�Wo��~΋iF+&”��j���f:*��zc��5u6��tV%�)v~���������ӷT�X�m����{�����~��(!L�F�����eQ��$)��E[+B�;�ZS��cdI-�h{�/ q �e�Ȧ���j?�,�R �rه�����ף�����������-6"����qR�Vu ���Z���W{�Uv��Y��{;}/g_|��$U�ft�|aZ���s��:;��6��3�w�+O�7�?ŷ��k���t-X��鶡��u�is=V�ʗȹ?���Uw+Reۮa����,��VUFn�EX?�*v �.�����}������(c�S��va}C�e��+.%�נY���yW�kT�CW����m]�X�X���e�y��4BC��=x�,$���z��W�ѷ��G��9v><{u����Ms�H����._�Q^��q�MB0Ԫ6 .�ĕ�H-i�bfv�eL~�� `{$NP���O� ?�R���\��fU)��@F������+\|EK���V�$�5�DJ�R���HcZ$*� ���e�t1J��PK�W��ZX+㰒�Xլ T;"`a��$�"����x���>v�� 6�K�RX�(����,�9`B�ʬ} Nh�@� Y@��ֈ�dPIƖB.�d�cw��-+ت�I�(�����u)��� .��yzv���׸�jK:�SJ�AH_�J�L���n�c𳁨���>P�� 7�(���Q��l���*SDm�ѷx<�%�?��5��"k���0��j �u%R\Un\�|�|����,dq�Զ��&J�(�3%}��F ��|%qr ������܏��F�EjM��E +ʢƂ�P�d�Mf-�k��Tb-؏�u�W���ȗv��ށ������waͪ �XD���Ȫ����������\E��!��4��s����?}B�s���� ΝuD��=|�R�_?�NE�r��RY��ەՏ��Q�7�^U4���;�� +Vf{k(OG~B��+���.�^��^p5yb���3�~U �Q.��e���թ�w�^�fo���S���߲.x'����صLw����LqRk� ����c��߶ڀ�X +cU�0}+���6��P!?��]��t��Nb��������=wv�UG~�.��ޢ!�x_;�K;����^��Rȁn��������;O �;�N� !��){�o�?��{vg��I��r�D�G2���7�2��Lz�#�ڙ�*ɦN����<�9Ex�Y� ժ=���{�V�i��q{A`%~��C!�L�)�(=V.�A<���s��:���^Zʄ\:��=6o�;���-:�D8Q�T�l�$�S�Լ���Ʌ���vŽ���v(�� 2��`kb̐��tKh���� 1_�"M��@�ª� ����>���מ�����2�=��n���} �_���:����~�a�����T�n�8|�W �N �T�1=�ug\a�ۢO�JZKDe�GRQ���~ %9n�.z1���rfg��[]�(�L"L�5#Wґ�B�p�����j���5Y{�ZY�9|4�����J"�����YZ.�T��i�+F�v�%�X�F`W�ty�Fl�${�2�+�]ƈ�qʠ�A�a޳t6R�@��lW�v��B��Z�*�JX��|�NPQߚj�Sf.⁆K2��+}0��T+��J��z)�r��툇�N᠚^ʙ�ތ)>��^��%�\@��:�~�{:@*���;8� !�����<�{u�1�'Q�$(H�ڝ�~P@圾I��mc +���)�Aa�a5_�����=䓬�Z��� d�ֵ�)�5�~~aLa�B�5� YN=�8ғe���递�0��,�*��,]�S|Ym��|�����n�ޮ)6w�oַ��j�N�Yb����ַS�p��6^�2�O.��4��g���՜���Q�,*��g�D�� �kA�@-�…H�m���>�Ӕ�T~���}>���J�I����G�_ы0I���~ w�Z��b��lB`�9��~{�ul|�<̉� *AN��*���0d���%�(�2��)�������eqœ +�c�%�M0W�:��΂ �}V�S�:��6h%��~S^dd�#��w�q�VT��sO{��[a���/ �w� ���G�C�����)�ҩ?���]#� �۷|p�jh7}��^��:t&�O�a�nn~b���"IBb�x�>�qu�?�K���9��f�E ���Y�*� +��x6�[�C~Ǐ���8��u�q����7ѿ�T�n�8|�W �u��*��4m���׳��mѧ���Ң�#�*�C��@Zr�w����3����7�lm�|<�0ƜAk�BM����i� �6R �'�KҞJ���VȚP�m�#�M�K�p�i1�@�Kr0�"�84��S:޴�8�=#D���O��(�/W��� [V _���DǡF�٣3�;��A�%��B��ָ&��*��(Q�s\����|�v���b>���C�`�3m/�HuߌK|!��דW�:����g� ݈� h=�d�6�5�i�b�eB��9&����l�]I +��8 "�Fu�*ϻ���T�ĸ*���e1{�z�|֊���?[vTb���V�EP���%�����9��ˈ�����eC�� �:�Xg�0-�%�.ֿ�>��uzw7]���;ܮ���jY`5�t�-"_,?^�8��@�EƁc?�<����8+�Kޒ�-K(��VT��� ����k�Gc=�.���F�G艶aG�[!�G��G��G?y.�`*�y�����V���e��f�l1�41i��a�L��fYC(�7����ܾCq.��{ �'�%yI:���<��a���I�t�q���� ��g����7佨���7���u���׶��G��]g�io����2�6i�NkKy�g�a��N4�c���AO*ٶZ&��{9��H�{��Ǘ���g������[�`k{��{-]��/��=�S�T�p���տ�s}~<�#��W��Ш�gB�Z8���1{�޽���T�n�0}�WUH몒L�я/�Dv?���<�}���iV��P�x!YE� ��J= �7������gB+2��n��,ml�o�H��r{�{��]��}�7�MY��)w8�9v�sKd��< +{�V�4I{z���`uH G�����j��[dM�Kp�w����!��aG�����ʞ��}O�_�����m��T�n�@|�W���F!n+��7u��$R����bo�U�;sw�[���⤩���U�n�8��+F�؁�*zt6۪�����Ӵ����%"�%�*i�_��b�u��b[� ߼�G��Z�*����W�L +˸ࢀ- �-{� ��D�w)˥@V1cx���0��J��JB*w�e�p%�3G����IC +rh�QKݝ����R��� +MT��f�D�~��$�;^y|�M��-�%l� Z�o��,Ϲ;�U�b'u� q@MӹәIu�yQZ�V�6%W3`㤤WC1�#�������1�G��I~5{��-=hԯ�&]�{i�ڳ�|��2Y��3�yt����'�[����;�f{���Z5ömg��;����d/���W��=�ZTd 4��pM9��`JU�v�N6��4�iD�)^\obH���}��3��(~�VM�8�፿���jvx����ys�|��]�]?�{�K���0��.�1-�Y�q�����~�{2f� e�����N�[�'v�Q$@ZK�͈��)�����w_�����'��&v{�%yS�oM����=�_���ʹ�%������w���f�@0�xx3�$j�'�@�����aP@圾MӮ��M�)�Qa�!�[����/�䣬�Z��� +�`Zׂ�]M�Y����/$:#���ܣ�h��!]Z6(� J�� ŋY��"��9>e��6�����_���2��w���l�m�96+,֟=��l�~�":h�E(��Iŕ���W�)YM\�G�dٲ�P�od�bh2��~�L�E#\��߆kK�>�Ռ�T~�B����4e�S%�4 ���?>��a��K��B �o�:��"�`EePC�)sD�,�aҞoGP�$s�Z�B��t��_�i^��k��#Y<�t>�"R�7Ý�֙�; Iݳ�ɀ�o53��7�,�f� +�d ���;�<pGM���!k}�c$l;�a��O�ユ����·^C�u=�$����i� ��`��5nn^��b��@5=/�B��j|!8E�C O�k|2�c��12�����Qb?��sէ����ж� �j���;�,��H��Ƙ��N�)z�k�7�U�r�8}�+�0� dX��#�lK��e�  ���#䋭�-y%9�m��;�m )M�I�t���\��wy�����Np!RW�2!��a��?0Cn�t&�J�kN�J��̘<���$ E��C�9� a�V�d�p� +1� �]2" %ɡ�F�tu���*��b�5QFҚ>0'����b< +���G�T8�P +��& T�+���H��Y +!WJg>�39�\�-��B���ID�N��� �T�ͱVa��Zʞ�:=|&�R���7��ă��j�{���@*��Ў� !�U���I�ѵ��}�KM���v`^ +�j�� $�� (˲�|�}��Q|���<��m�M ��)M�BS��,�S��2%��t��e���Vȸ�Ц��~�v)k��%�|���9��6> ��y���������l8Y��9�3�����b<��1��p��!�O�{ aҠu���!\>)�sS��J]%�+��2,&�ꖴo�|� LFHE&�������<���qT����m� `�U1� ����^�Ǭ��Iд�/ZZ8�kUJ� I�% ���V˔2oCGǼ[{��&n�޸��eP�ZS�\�A�_,� +�֖dto�v���^�')i�.�5`�T:إV9i�9�^[� �{p$Y�*����9�,s��/�rfe��M������Q�"��Q�GڄYW������?:���p��p�z6^���t��?��Ꙙ�����J?4 ��~���$8#c���͌�im5ۿ�x�W�侦_�����X����d���zd�"7L),OЩK��.춸����*��m�p����3��]p�͇ww Τ�����;Ss�V��¥&vs�Ԡ�^-���qT��^�������T>~J0������x�d� +aU��i����b�<�n|�����}m����ɦ�km?�n�d�� �ǻ]b囼� ~����43���s�I���������3�ѭH�Zw�w���T�N�0}�WU{�U� �4(��P��2�r���"�=�!t��>�M +h�yI��sϹ�v� +D�A�f�$�RX�9lA���34�Ej���v;�6����R6}NI��%3& ੮yJ�P+=�X�� $rc� 3Y��y��8��Q��4� �����Z4_�Vj�{F�\U$� ������x2ņ��q��Q������ԏ�H �e|/\l��|!�)g:s�S���煅liSp+'%�uŘ=qw���ʺ��Fuی!~�6N�Ix�#[xP�]��O=�b[iQze�6� ��R%g"��V���oI����@n�n��Q@a�EQ�4!���R�Q�0��'�y2�z��[Q�1����2��`J�th�E�I�-� +���)��C�q�8��b���w��jq���x��W�4�b��b~���<�b����!���!�ۂ4�Yi'Bjp�O�ޤ���e�u�(J���(��k�r�Dڏ�"]q�5`"C�+n}��������s��GG���xj�E��2'E����[���DA7�O�8w�ײh +r�r! c)s\7W7ش$h��9޸I���(��'*�gK";  �K^�{�Ha��Sk� ���!aK�1�+�Y�R��KE�8m��ݷ���l��Č�~��k�_K����i'��p��@�����ݣ�&aG�w��K�9c�Y��b�Fxi9wa�Sϸ v�����T�n�F|�W ��� ��MEB�`* ����0y��C�����)�(�|!@������oo۪���<���V�D�*�*���'˩���>P~���(eg�X?��:� +yM�F4%ge��Ӂb�R^12}t=�Fw���^f�t�`�أ�A��y#��i���* s���Ș�v�OWk��B��zq\%�6�8j* +񭩆��6M� �d +�;���HY9�^����1��R��8�=�m��Iw����a ���zɯ�W�vU͆���7�� J;t����a��\7m-���]z����D��� �8-��(�r��M���c +��ڔɨ0�����l�������j�����p�� Զ��t�5�޿`S�_z#NT��h;F`j�����>+� +�-3�� �Y�-�9����������r�O�vwX���}��f�m��~��?��X\���/B��'�4�3�� .ٖs9J��T�Q�(�76ሴl��X RjiąH���_m��w-�������ې�$���U���c�_n�Ϣa�����G[|ҍ���Uȩ3'�.Y+�y�|`7���C������}���=Sm���O��_�;���[L�@�R���X��ˏ�{a}��c��u�˝Aq���i޵d����*�˓�h#�(�/-��-5<�'�}�T�|���s^O�?a�M�8+�OK����}F6���� ����ډ��zn�G�p�ǫx��7��c���=��T�n�8|�W ��>��cz��u�p� D�}*��JڋL�H*�[�� Ғ��CN/�I�pgg���m�6I��S�H�PF{-����M�'Ǚ��{j�XR��H�-rn����b4T�� }��qo"ɢ%U3rS��,��t���\�7W�t�Fs@����J��:o,�##���{��́�9ү7�l�B)M��8.Ћ��kq荽Ci,�($\M D���c!h�"[�ʴ+U�az����΁m��ߌŸ#�x�78�n�r�zh� �uA���+\�:�&����MD��m<:�O���e��2���*�u�;�����삋�(���E��3|ζl>m�yq{�Xo�U��-����l�m�967X������ ,�f ~hma,$����ik�2��ZVR�BC��bT�m I�v/.�@�@#{�q�B��m>��]K�.P�xŅ�a>Ҕ:o*�i����U�5z �i2&�)��&��kkz )ArL�1�a�%bMjx����=n� ���ʇ�Ru6��� +O�#��_ ~�഑|O J�K���������������@<�k��a�u����˾��F�a-��e�U���W5�y� ��ѣ��dY���g��������� �]|g�A��� ��B"��^ �Y�[�$��7��ٖ�x>�՛X�c��=��T�n�8|�W �I �U�1=\�6N��>Dn�> ���HIEN������8h{��E�3�����oLm�t>O0ǖ��� V�*��@��;�(S7���oaI�u#��%�ZA����KR� +x�+#dM�u�{a [ݩBD��*�^�SYhE�-Zm�,_w^[4#De�ZR�-��(����l�A�M������5;��~C�-DQp�Z4`Uj��D�R%l4Kmn-W���YW�Y� %�Nɸ�x��k��n�r�z,��Ⱥ ���%�}A��tv�:�[q �=:G��Xe��ԭiX(ѣ��;����D_� ���4 F���2M��_���R�*����f�o~{�|9B>�����:�T��˜���n��Ѧh?+��=�j�nj�S�K6%��I�Vѡ�*G���n�g���ß��|^]]�v�l�c��~�>;d�]���ݗ��+۽_���dAGc�m���T�tӔC��%gHr��PU'*B�o���0d[v�X� +4ܲ�-��Gm˱�o���U��q3�G����T������o��^�y:�����Z�Pu_[�+p ��ø�N�pa������p���@��G���T��p��%e�5�Z+�m'}p�9���Sx1���w,�N�o���EhHU� IO������2�߲S2f����?�)�� ."|P�����OxfC���)�� �&_:����[�j��QZ��v=��Y:�g��3\��Y�'�ɛ?��T�n�8|�W ���T�1)���� +W�E�6�SASk����T�ȿ��Խ.wz�D������յiL��� �X�� 5{�Xq ��|+}�ڐ��B���Z�K2^i�l�s "�%�U�:�FȆP��%D����E�>G�Yh������ +���^[�GF��u�ޥ@I��6_��Wm�W�qTaP��o�à�7쵅�*R����v���T [�R��Uu�&�eR`���w$��z���G)'��f\�3Y$�L_��74��ί"�����b�.C1��L�ˈ�=�H�/#��� ���i����e� Ð�Xo�m�M +��rU���/�#���,��+KvcZ%Ů%�b�E����1X����85�W˦��-@3Dth�(��3�]�yy��|�~�i����͢���,7Ż|�o��5ŗ��3/�]��oȂ� "�� +���d��¬�.9CR�D+��EM��ٸ�l�\0�Ap�Vu�Ǒ +��Om���7F�o�*�U<��#�D�uM�e�|���nտ�^�y6-��K�Œ[=0�����B�5����f�:� �$6_q�ԝ����Ya�W��?�ӵҽ'�?$?���5�R���54<�=�‰�#�,��h|��aE�0�\C�#>���7�,��=˘��W9�x6�Lj��pa����o��B���y�?�|�#r<^ՌwT��I����c�X���c�}���[�_�`��~p�}�ԟR��Z�)ת�h ��@ �X�N�r���:6�Eg��� &sʦ�� �'���2�9�^��;r!��ۧ8�/�k4ѣ%�B��|sܚ���wj�؂����/W�X�����g<6�w���d���~��v��րAR{�N�N~?���o$�hH:�^�L� +�ʼ �������� i��7��:�߼��:�,UKF��o�Ҙͼ���N�ɽ�o���yԕ6퓍}`�[5�$�lv�8�nNKz�ӳ=�uu������0T��j/�Y1C�- 1~ ;W�����>O���_�T�n�8|�W ����*��ں�� +W�@�^ѧb#�%��#�(�"�~�JJh��"�ܙ�����lm�t�H��F5���@J+]!� ���@�sA�jk��t�\�l�2EC�/�gU��\"�,5#7�Гc�w��7st�d�9��Ck�P�S7]0���s�:�%�3 �v�Ϯ�8�F���K�*�������q��T155P�`\+�D��\�����kv�Vv 죔|3��)m08�n�r�zl��b����W8��f��l�F�-�M@���,m��(LkE����!��6����H��N�@a4 +�C��i�����ޥqU:)L?gW�m�~�z�j�|� {�w�q��#��Ft�0�b�د4z���\D��F�ԤǖM*�$�h�84[���>��,���l�i�e������v��s�q��~���n�c��j�-"�̶/�*���w�E�A�~ry2MS qVF���BT��t�QŨ�-;YˮU>�A�D�Zd�|���mڑ����*��܎��S�NS9�f��[���K�H�En�Uqgz��fͷ�'�4� K>.:�XK.���5B�A��1N������"�1���Ұ�g|�|��]Jy��U�w�u��p��L@������uE��?K%���-��ha��[r�"N���D��"��t�{싼 Sc�.���Ӆ$�����=�=��A^�,9���� b�?�z����,��n�Qe|����������o$�}r��{�� �U�n�F|�W � � ���M�F�`* ���y y��M)����#)ɪ����������׷uQ����.q�JFj�'���� �HߓㄽW:�Le÷۔k��FZ�s��Q��g�& g5�#1ߒeܙFgp�������h������[�n��(����2W�����9�_,W�[lT�r�3����rh������,S��J(�1� +��rN6���wV兇i5[W�:VB%��q]�!�7ؙ��rĺc��l�P~�¹/h�_� �v�ƣq|�2Ci���KE: ��>G|탘�X +T`6��@�7 +(����mۈB���y<0�?�on��/��W=�.�9X��Q�3�w��.UJ�QR+����J��Jf*h7���IɆ�{��hPphL��l��� + c�DOΎ�i�Az�w�՜��JQ����y`&�f[)'�:��P�J��RN���6�Ȼ���Jf*�x��#���&g������ݪ�����0�/N4#����i5ڂ5?��A6��ԍ�D�FoT��n�����w_k��$����|AԿ Q��2�(T��s���u�ߴ�0z@B�K���mR/ein�M wv�Ի�,U�^�9p�*Tq8/�,z�o"�e8S�/�3�!��թ#c�$�Ԗ3g�!���� ��6����q��7�4ޞokN=gAĞ��ˣ�*K�J�]͡���t�C�8�7�NC!߾�����t��$��ɯ����E���%�YHz����� .�취 Ρ�7��v�b��i�)&f�'�^V��il�\�o�����**Տ�?*�\�k��L��O��(9�[�owA[rN�ZVC�L��z��h)�@�ʢ� � ���T]c�Y�d(ˁ,_a��$�K�j�����sc%p/���i����ߕU]o�H}�W�H����>�˶4M�h+�bڨO�`_���3ޙ1���;���I�5�̜{�����*�������!�� ����r�H> +K_EQ��cB��Z!)����Y&�,�p�Cf�HrB�7��p�k� +����1j���V�hmPj��5r];mP�!2CT�r6b"_~�\ͯo���ǧҶ8J�H���Ң��;6�@���֢�TmJ? e¤�3����,wЍ"csY����ķ�0�-ܷu;]wTNXwbL�e�o�7�܃���p�ΣK�����cu��R!�eUH����z�����^�g� +��t��r窫(j�&~�P�,�F���7��混��Ed- �SKC)�;��*d"�� ��m��K��H'U6a��#pj�Q�~@i�m� +�;4�Ř�C|���x�������gww��j~cy�����|5_.b,o1[|c�_�ŧ H�� �2LBH֓ғ4�3pV:�lE����PY-2B��d���ȔҲ�B�(d)���e���g�C%��\���a��#�D�tF*���}�ݪ�Eo�˨?�/e��6�QhrR���w��X�-����-yg��H�i}��e�)q��_����L^�����zX< �3���ZYg��Y(j^)��gr�DIaW����F��H� ���8� ��k��;M�5V��.���Yؾ��������Y�R>R +�U�[�w.�w�-�FX8#�C��C�i}^�@�7Nj�Y�@�Uq�E����s���F��^-�k�b���>;�cJ^w� 7�0Y�/�q�#�ߛZ%���C�{;z�ŤjrNp +U��l�)�`�[�!��µ;2r|7�-�;l�� Ճ0F�F-���O��&��oJ���6 ��P��G���d�(���6��c8��v�>hs��, �љ��VCX+3�w�% �N��|��ڊB������Ư����)��uq��YCW"��-�sd=L��c�{obFC�)#P��) Q�MUu�izT��F���ʱ�B�B���m�`����{�T���vP����a��d�����d�eO6f��#_�CG��Eb��.a� �&Ě Jt����R��K)1uM�Q�;,���x������u��sT��Oj�t�v���i����� �����l}�FK�I�l@��wN��������!��pP~ur+ Es�E�����F��6!�ئ���ߥ�]O ̄�<ҁ��Јő��o�އ����/_�ذ�^�l��<�O?O����qJȆ�o_���R������������V�`�+�"D��8�X��g�!1�a�e��P�x�;�gq��.���2�u�ur����T�n�6}�Wl���Yֺ�� ��rWdEa�Ե�E&�K*�3���R�b�Ч=��9��{���olm���$� r-��$lUC��BcCh�����V�8�����Wfg�&�]� r�*I:�����VȚP��� &ܘV��+�1�7c��$��І�3L�F{V��Fsd���hkQ��/V��,*�R�#�J앯�k�7�p�Q�*� ���E!�T .�� �=��j��ĮV6V�JqӋqG⾬78�����u׌ ~'v��yz���#h����h�Cӟ�A_$Y�!��6Jhѝ�o5R�#1/���V�a��]�ݠ��{�:���}*���p�����j6/f���Yy�rL�[�Tbs���QRlB#�a~qLq�Jc��+]M��x9�������qB�i���ݴȋ >�_�W�0].��U>+�X�j1��W�b^`q���> �����51��`�0T�'�/��kY��,I�U��U+*Be�u��%�)� t�F픏�r�o�?��O�)��,�7i/�,�W!Gδ| �[+�C��N�ぐ2tIJz>eɫR1.Q*�bG#��7��l���"�� �����)i��|9�Z-���bv7]NW���e|�$j���>J�j��c��O����бb������pt��i���:�Y�.���g��c$�.�Ϥ)�Y�B�Xf�0��^ ����V� %_jB0��yg���W�$�F�7aIp���R*apoZ�G�F�� +���ò���!l��֦�kH8�6���K_~[�(�l�q� �'��IJO�6Yi$�A0�5I�m�!��嚩R���1�-�N0� �)����� �Zmo7��_1�J6T)M?`׭�ľ��F�(���I�W��JV����ew���K^>��g��p�|�p�ܟM��A�����'��q���?F�1�W�L�PC�0�{` �������/RM�rlL!\�L��p)�s1�:�LĨ@ +$k�`&��Q�Qf���!�(� +�{CD }s7xu c�X��kg�1,����r �a,�8��5K���T3�*�0S��L��O��B��S���(��UpF;�Э�������.��JS�/{/�c�֨�߶�N���-AH���)���Y�p&"k�������A�f � �� ��05&=���E�Y{RM�!���������/{/����֠�+�a��� ��(AH؂��N��~.`���b�%k(P��bȂ�\�4��������v1 ������͇;���������n�ë��׃����n����O�����u��)*��TQR��ĸĦ�q�ϒN1�cA��$c������!E5�&V1$|ƍ��&�Jla���,z$(ZF�����g���M�}�*����ľ8���}��Ǩa�f*c 1vsM1rkf�yp����O��#�/�/+p M%�t��R �O��u�89 M>hT��s=h݋�i �^�.� ���\��ɠ n6;w� ��t��1�64঱|�-�_ ��{�{�pn��Bۚ�P/�:����\���U�{s���.J�;�~ՆvC����v�߭L.d�'�,{"��T.��,�1&]&�O�`��=�8�Y�|��L�Ixַ��ϙAФ��8��Ȓ��� ���G\���^�}��e��ц z� ���M��� ��k4�� ��ǹ�j���+(ƾ���\�ɔ�����J��(�Q�7x�Rl[~�x�����6�mr� G��������Ϸ�]}����~*5� �6�]����t�0�~��W����u��/(���w�r���[f�Q���E��K����>��T�G�̃ ��adڸ�"^)Sl�W � �=�H_uL�N��\� � 1_�y�k/��� T��|��V�fC���0�t�v ����j�"�.�~9k}���ǒ�v�xB�ϕQW{_y\��T|2{��'����n�"�c)�^q��.e����s���C��l�_��"�� +ߚR�5�m�/� ���`qB������;_i'��*��It�NN�43�u�f�"3�|iϮ�=�-@�,�CI>�{2#K%ۦº`㧣x�P����fG���{w�#����Cx�9ꂤc��S_r�W7��ܯ��1��z󍔰�ar�Ⱥ%���bԲAz�VY��6��e��”hH���|>�ifîӴ���fR�6!��ޜu����bʣ)ܾ�%eP����u����9x�T��I ��mQHn�v�t�� ���%�����VZ�XD��ܶeWD�J!�6|��� ��X�8NB�袉����xp� ��n�(�HR� ������Ͱ�~�=s�#�����ް3fV\�h�z�ӉG����:i�:ަ /��8��3h��lo�p�S�j�1����O��w,ڳg�حW�hy,0����]���Rl�~���1V�#��'�V_��t�n��T���%��)�� �V�c���Nn�ڂT�:b��ۋ�7�bΕ��l�3�m�2b��xz�1��rW��.�I�{p%U�n���t�M�}�^&�{`+�`F��5�F�-k -�fK�/�n43M�:A�bށ6�C V�b�Ç�������'��_g�Q�� ,�hw7�nJ��і|�G��bV_��S�tL)�|x�eq�iC�@7��9ua�s>묎L}-@����)�W���;;s[����]]w�YnM��6*Aѩ 9�x��� =�S/�K܂�M��6Fh��P���E��w$� �o��o^3�����ٺ7�LT��.F��*}vc�H��)MdL;� q�f*˿�T��:{��5�6�L�~ߦ��?�Ԋ>�ДG�0E�0c\$K�*6�<�#SV丂�Q���ʛ&b鲠MxWQ6��'�#H��� ��1j�����f�� FX�yբ�(B��ɡ��F�/A#��# +ԩ�&,_�h."�p�F�� !�O|Pt)���0�?K����D�������s��ԗ�3 �6Y�ZO+�l��O�O�#� +/�v$�*S�v!O-C��6�f 3�ж�%���W���N�zrx_s�*�3;R�Ok�0d��N O�V��#5<����������ib�oa�Ɲ��Q+�!��Z�}n e�Fwo�ǝq��3�B�����Q��'�'B��ߍ��"*GA ׆E(�µzS#��Cv�2(���r�_�<�o7���x5��nMvoop�4���ƵI`{7׫Ӏ��$�G�ɱ������c8��\���8c��g�����ɳo�e�(��G����EΥ�Xv��b��å��5�^�Z� +%�����1Բ���[iX)�!W�j1���P:������L.9'�o�^�����(i!��� X ���J��\i`E!�hV��s�W�n�|�t!�rUm�X,-����,E5�BR._d��� +6���$T{f��o\$���'pd�����=8~J�WlRY� o���yeAH�ժ*�9����3&?z jf����P�t0����:ɲ�z=a��D�E(̾??{�����?N��-�%74��Zh^�l��J��Yɡdk����/$���B.Ƹ�H�԰, (Lk���HB��p~yߞ^�_�����wo�z�O/.N�\�����p������o.��k8}�#����7/���]r ���H�� ���H�)����d*���ȡdrQ�����Z��T\��A�`��R��%�2��G����EP��%p�F�[�w��ڪ��-��^�Uk�8/*�� �2��G�z�V���^�T�� M9Y��˂K +n��0(���y-sg[�n2�TYC����|K&�R�E`�*;�c� +�,���R��3s!� ��ڈ[�?���rU����!� +� Ja�V�~sɍF�mGB�eM�n�Y!4ϭ҂�c�Kf�����&��s��yM���ɑ}���I �#8�C���f�#��&�/Fp �O=�-4Qd�b��UHB ^Itx��Vs�N~�������/�o3�K��"7�v�����E�u��6�-ӂ�z����b�8��|�5�9)4�w3�9���`6+�m�i�;���gF��䫏��I������YX���7� �|&$C�#��L��h��Jc�F��<-���Q[0�vj�LȨlj*��o�E� +����,(��k�+��̌![��R����A&�?�܄q*��r���4m�Ҭ�\�� +ɞx-H�3K +����-qo�ʛ�?Ǥ�~+/4���� YA�k��D}1�Z~gq;k�^:��ULH�)���r&a��X�� �0KF���5�� +1'��+K�xfpx�.��-��(g;&���6���gF��� �����K��чI����x�V<���5�i�����#~�cX���1�M�n�a�rZ ,���l�{7"�G��?�!�r��h���F��7����8�2 ���`/ �[�w����q!�� �~MF��������(����y���ܒO�k�p�v�G���XHLm����k��95p~��F�Kxr��'O�x�m����4>��(=�� +f],51�����y�2�>�4!&�������ۋ?^�zwzqz��& �O�媖�믇^�R��ӔP|4��{�����7ƇG��$j�,7�.��Ɛ� +��@b2� +��;�O��\�U�R�K�֒>� e��j09t�*��ڸĘ����&��y&�8&Ї>�'[�ȹ�$�q�Лq!][v�)��Z��)�lc`��X�Ɂ�J�ZH��ƥ�X��,�B!����N�_?<$޲I �7���7qT'�5� t���msșp��� `���6^B \lpxi�Tq1Yp���퀀�F�K�DԱ �ۑ������xVA��(|���6|^�M;i�2:2 e��%F�듎D�����{)���sϲR���<���/� z�����2<�����-�����e�#Լe�!Vw��r��.Y�cU�H%��sE�|�k�A�ߐ.Rz.��(8U�h$�w\�p�V�bF.JZv��A�W�����sK��|��v[, �����uN;��Qgq�u����β���E��ޥ�.5փ���Q�zH�(_G�8g����'�X\�I�=昊�2�w���=����~���3���I3��l��j� +W����vLe�Y��"��V��JO\��t|�鸑���y�nRC���|,�N��fK�B,�����M�(�sDE�����!m�i������)��a��C�a�0�4��u�6�89�xu��MD�u�Ĵ��?p�pX<~��qt ���t +sV�Q�;��SMF��o��Q���� Y']ObjLt�������E����>�_tS���g�š�G�>�'cp)3��=�C����0&Y�9���t��u��+�o+P0���s�i���8H��J���n +��8X�s̤&x:搄�<ąw�UU�CȨk� ��'ۙ�?�0>ֆ��>z�$>���l��"5iUS]AF]0��{a,L�e9?9��w(��!��}׸��g���z���ㆆ����l���]A�w�wXh�m�����6�o7P�9�K +/�.9V�\߇3���Y����z��r��+�ig;��[�VK16�J�"�0L�b�c���-93�~~Z�jR�� SAFr�cHo���G`���#�v ��T��~d�x�(�ʋ?3�����n�� +�S�~��v�#��Op]4>Sp�r��u<�$Qל�cr��r��"��۠���.1�n�LE��B;���wƙ^|�A��Λ��ߌ�6�)Fމ�w�ZeXɇV�Zc��ܷOb��/ڥ���^���6����lv��ӎ��~ͮ�7ro���.8�����Eg�)0�uyh>�ۚ��&�矱1q>' B�Q_fUٍ�:�#84��ρ�4ĝm�mv��ۿ���1j1��2<����ԉ|��E�}hk��sk�#���Å��҃(��B2w��G��,]K�e�lT�c�o���!sO�Ū��B]A� ��gj��+�$V �j���?�CSsx��]L�G�Ԇ{,��/�Fe,l��u/���ݵ.��.$w7�\���6]�:���!�S}���s��ۧ�ڷ������_t�!��o�%:�8�1�tX4tOq�]��@H�o�i=�U��7(����So�p��9Z[��[h�=Z0��G\���_�uܠ�O���? U�g!3��\q��-f��T1u\q�c����]���"���{���D \����7o� m��S���5"�~�{�m����SR*/oD��hVP�)~j���**�$��G.��C=Sզ��=%���n;f\�A|�b�f��st��]D�dr�?�Xեv�T� �x�~2i���J]�D��1]���N�т�n��a�����s]�t��&��Z���|�8�Q)b� b�0��}��縚�@����I�?I�ɒ��(��,�Yw����K[�E��w��]����=H�� ��Q��=�/Kf�ZT�ǐ��q'�����;$g���Y��мdV�,ťWa��5Q|t�/G���$cÉ� ����S�-n�Dk��~_�i>��u4��O+���$']&�ڍ�Skٜ�]ij�#�r�LbN�A"��K�2�t������d4i��b�d����mSiGs���jƚQ]�(�;����"���K����}����Iaq�� Y�_��Jk��(��4~�Ƅ�d?pI; ����������xKY5�4�,H�7��σ���R�hhCd��m;�ѪN����:\��8m��Q�N�{c;j��>���=�)�F)�;l��;Pf)���A�Ū��]�O���&۷�F�a�tjs��QJ#��)�d�`"����pJ�pk�4��5�8��87��c�n}�N +6���-�� �f?���Ν?�Ʋ��i�N�rr��l�zCI=3d�����>6V�\X��O=�L��J �2�0ep۷���&���3��H�>�K�å��7}�C��&�}�gU��H��a9��I#d� _1� p�7�����V�k"x�Xa�"���S#���⩚��na����e3w0� �u-e��S�:���Q5cl/��Re+.����cd�0���v@T[����!�{� ; ����֚��e��W|Vs���fU��_Ѹ�P-�&9�G���F�>�m�Im�;n���b���뿌?�d���(j���Ѿ䙠k���jE:��B�F�,�ڌNz���'Lj�/�]___�Z��`u�⬛�S������亷���?�t��?>��a/� ۇ9w�ıw�-��*����]�|M�R�Gȵ�!�����ه�]�'w#�n���^��� Y� �y�9���e�wZ�X*�e� +�$,?=�@c��;U��)�������G�}����Xmo�D��_1�*%�B����!�"Zt�q:!Tm�c{�f�쮓�P�;�}q��\��RǞ��g^����UY�&��=8�!�V� %T�D�O�;fq��.���x�Ge1���b�DX��m�A�ѵʘZ�`��B�24���6��&�fĪvڀ �q���1�ћ���_̯!��g�=�`+\ ���,��{f���n��K�{�������%�����#i����~(\��2BO�ZՔ|�Z�Y�r� ���fB�7h|#Th��Rb-0��k�|IYR=��z�ۊ�G2E-�lb}L&�v�@�X1��W���y�>W}=8���������5�R���+� 2�l�%�`R| +]�sЫ?����?�R V,%��'_ۜ��eΤ\1�h�@�U.�ڄh UHtZ]�cX(��RC���V� +)�d�&J� +L+�-�u�(�aB�sR3��2�ΐ��I N���M�9:^ν��!���DJ��J�'F�1"�p�ᕿ�D +�P�+)8X�y���f�Y��+�&��a�>�Z�X��tz&�, a� ���.A���c�}�L��`x��=�����㎑�����й��s�1�j��%���y�5I��_S���Td��^`[ +^6���Pz2�{�@*�Lw���Ң�G��Vƞ�rk����Ⱥ�*�nH�q0���P9� 4���@��}�G_~��N"z-o'�<��M��imњJ���w��eЀPM�8�b�3̅B Z�� +1SS��Tw���1�TJ��Ro�^ؚ���׵�`��֍��Ѿص���w$�H �9h�t�ϋxDOܗWD�6�=}�, �a����0c�n����=�>\^�3��n���滜bml��MԚ�&F�����J�d\5�Ɩ�����e~k���֕V��A�C��ur��(�O�Dȇ�b|Oz��Ԣ��z,?��:'�ha��lK41V �P@��#6{���ȯ.����H��V���ɚ�l:�� 31Ǵu���*�F�I�]�Fl�C8K�f\F��Eo�,��c��y�Z�O4�B}�r�}� [C�-xB����C�é�3Fǰ���$7s����w�$��{f�Xl�t u���J��i���J������8�^=����}?�빹Ci�;� �K���V�ޖ��͸���;�;�>8_D7�\m�N�^�:E� ƣ�{:ƺ~� �Bm�ټm��#)��X~����F���4��Gb"�C"�W�J�zx1��%`�U����E�|�4�_@j�RY�Ԫ�����v�.��}>n�y��� +i�gI#�zrM��tF��I�|2G��,�BZd�բk����������[�ؼ��� �‡�o�.�f9$Aa-���t���T��k溕���Cm�<�AZq�1Mo#�ð�W�3(��V�{o�z�T]��6|ׯ�pg�V�Ǧh�\Ψ��NN�<��J�E�$u�K��^��l� +��/�ŝ���,��:W��E�6� Қ(ذi;}��E�[kn/"[S�,4%�E2�#K2���ص�#Զ������� �^כ F��5��֣��(��a��C!ZOԓ�a�D�~��W�whXg��pđ�ȱC�8`����Jq���`�X�g! �^���uϞ�.Ž�|�ح�}j���b‘x�l�x����E��0���|H-�^��u�2�j:��y�ѽx��C�3;�I��`i{�Y��Sw�o��/�=$� r+��eD����we9��Jd�+��r��X��m�o_�^M�OFS�������i�� Z�ɿlS�� FϑM�L�0G�Ҥ��f��R` Dv�j]����~]W������O{|^�߯��������j_��5v��_�j�a �ؑ=9�����I�"M����ɥ�Hr�Z�v-�����n8�=�dl�0 +�{�9Ri���j��;'�C�J[�_�_������_�����w�݇���w�[�w_�[���q�Ɂ�:'"��<��J�܃der�w����2U�*Be�ŕ�ȵ��XeJ4�r���}�mޑw��_�J�)g��[�_�Kp���ϋ��F�LNj�5�� A����()����T�a� 9�T3�A^��BmK��p� /��^r�M�R8��PF�k%���&OalD�*~$3��:���w�r��M/��G��i���ذ�\g}1��C$Y|G����$y�K��Uю�6|�W bW+�c�M�\Ψ��.NN�<kj-��H�\��-��iɧ�����m�;�ٙ]����.�g� 3,u�P�i�M���zG�et�5 m���gHe�b���T�p�jFa�r �X�Δ$�L�r�Δ�aM��h�?�����ќA�gn�H�s�_o���;�u��KNu\⠥��:�`�W����WSm�ַ�H,�\�/�Ne���؃aj���6J)��p����z)#�}3��[�5x5��ԩ���jz��[:�XA� ��� ����k4���A���9����(I�ݏ���7 +�E�y~8��έ��Aa�au{�.�5ٗ|4 ��t�s���\�UJBC��_�)ٯ ^�6�u�C�&=�l �ÿXJ]- +��+�[���V۟7�����_�����{�n��W��f]`��b�9V��Z��k�ك��"�����r���C�J�Rp��^+4d��*Feا�p�[��dJ4�Ւ"b酶y�Ƿ����)=x���ԉ���yz>d��[�\�2��a��1a���}�e��h�!5������5i�#:�O��ctS*"P�$��f�;+I*���S��I��?[�gO/�Q�WIb|���*k��N�e�; {�e�����T�8cA��ڞe���$�ڐ�G`���v�i*K!$<��e�m9@���u�F+�;��*���Lr2�ɾUq��������s�Es@�'Ǥ�#�()��s�nJ~��[!o}ByO�I�:S���Տ\N�%\��]�]�Ƒ8��ERע ��y��+*e��H�[G�����s�2=;qY��M�w��u��V]o�6}��83 +�\;�Ӑ,i�|��R'��u�KAIW��H*�7���$�n��zH���{��oo�4�;8�5�PI˸�2�M �o���@�]2� +f̰op�C��"X�/�s����m�4�Z2b�+��xv�G!#�P����Ȕ�"jVi��#X��2�� ��w?��O.�s��#n*;�Pr�¦ܠT���`Q�]h&�e�t�q���#Wa���Ij�JIڤ<sW��I�T���Va������k0���q%�г�7�֧������RY�V�A_B�-�D��\p&Co]W��1>�NT���@����lM�Z���FeY��w�t2j*�L.������ã��A +2��)���,�Y ���?O���K��[.���6M �����I��� J�y���&�.ލg��'���s|�ߏ���� �����^N��� ��O?9��'���۔4�K�]J�;<)Z�&�+5K&���<�`2)XBH�i/��tƍ#ր��g���2�t��F#os>:WNF����?F#VX���,��Q���g�{����Ѷ�^��CQi %[�41�0uU�),P��%qi,�"��…�r%+uN,�* ʔ<�V�]M���;�9��pFP�HP�??�8w���('�%�$RUnFq}��ᇀ���z�I�Ck�u�����}����,��)���)����F B�4�Ȓƫ:�dS������9�"���ML�E��ڡȬ?�����zعA�� �����G�%�t��_�Č�n҉re  1N�D�#Cܢ���R�Eh=������V&��;#�?/�Cą }G}���k����77�{Ê +�m�j���Ym��}rҰvr���������e��1Oخ�˔��c"Z���)� Sl��_�X����i��20!N6�|mn�>���AR��ye|ﯜny4���Asb��"��/Q� +a�C�T�rW����_u\���M�HY��&��.�{s�ک���|O���� �̉�����0��S ���m7�S0����ѯ���q�)T��;4T���UMB��O����FjW�>�6;݃ ����vY~EdB���[� ֯�J7����eh� ��:��41&�l�Ӭ�WaSǔe���Z�L�5�����aJ�ٱ� 4���2����Yi�9�gK��۵|�� ��lܕ\�����CO�k�<��k� (dn� ��2k�1��H�=1.ܫ��-V��w9�f�!��^��Q���r�L8< �;�j������ab�r�'+�����ڗ����6؜aa�DU��Oz�(�DC���ӓV3�sEh�T6���򍃒 fr�`�����i����!�9g>���|��|)�Q�o��F����ۡ����A�z='������dR%�� ~�Q��=��:OE�m�֠4��ԏ������~�w��� S..M�� 7~���[�B�ď��Kp(B }h���r����8�"���]��BzppRFȓ�ƙyy����S� 5y�߃]�5�31H���Ki� �z=������{��<��8�u��ڀJ����w��i����x�%%$>#�����6y�$�n=ۄ��yv/�μ��/r��d�RV1�m�_"4…�6����1����>�+{Rz���~٥����I�0]jnX��n��s��;{Q�2}�ӮqR\|р̿q����F�RA�\�[�m�m?lK�~�9K܋������#�:攬�Mi��p�&��uy���ʙ6��M��hm��u�)�, �GڍSh��Yw9�L�J���k�M��Y6� FmW�=�uC(�;�c5,���F(���5Y�d���&�t�?͞�g+�2¶d�oL��Yw����e��&�nH�6dz�'��~@��~]%���y��F��e�� |�n5ێ덭��e�5}'�P*B?�灋��֨�q���?V䣕lWS�W�����]#�[�[��i�?�Wmo�6��_q ��)ȧ��ӺyA��N�k��0����J�I�����%�v^�.�&�`�^�{�;R���Ӽ���`�'��0�s0)��EM���)���!�W�1)�8j��Y_�����t����`,�@Ep. �aR@g0>�B!�)�ZK�Tex�f�� +x�p��2Fc"�~t9��A¸���.�(�3)��iXH�����ȁ�D��%b �Q�n$�b�Ԁ\R:ey0�P��U2�t\�5���P�}1��wR�B> +�cRg��Ww�=g���4PhZy��(7�D2�9C9k���|�N��R蠀L���' + 5&���"@�o �<��Ó�����(8�&'�A�_S�l ��E8��?G��� X(f���[k]I�IҪdU�L�m��1�3�p�o��x> '�.?N����j0� ��py'����dx9��9 F_��o���>3))��\YR������*�ϒ�)b ����8'��kR�3rRӖX (b�,c�IJ[�-lU���1�n]ٞr?\{}�!F�I���[����l=���U���e�R�� \j+��L�Z��N���8HLh��Sl=�p"�\��CJb�������gȄKҥ^���8j��p��'�,�.�E�--�x����쳍����-O|P�{s� +Ʈd�3~V�/����5�ݛ��Z[�(BC��q#T����8ɜ�n�p`spI�A�g$��7�i$�6��L��v���Ϯ5��!fʾu`:=^�M�Ѕ�a��f��&���ʏL�7�>hW���O��xjCt������+2��+�mËy�x ��KkVX��:T�nt�Vw;G���G�ю+�ťx��M������[��o��B{W��W�Q��c��ceA����&"����t�ج�����T�?w�<��O�ĕO�C��L9b���q��d��ݻQ���꫉nG�0U�Ü�I-��/�I7L �Eh]�ܟ���UI^#�����W������1]�X����ոJJ�� +W��՛4�I�"�R�x{԰[��n�k�>,�N�(��k��r���w�z��������mk��oN�k����/95K��JU�vgS�؆$�v��ZR��7j�����}�K�R��� +�3m,��j�����<��D�auxW����Sb���,��0X �F{���$���5@�p )j��wZ� ��z�}w棆k�E9�+g�Ľ�W)04>�Q�SkQ�� ;�^����� �{"�n4E~��L"b����/s�� uu�B�I���Cc��[���}���A� +R[������ ���>,��9�T�b�:]�ӱ��.�;P�l��"�n͂��9�."��㪄˦� +,$Jf�}m_���m/��G�~��'�}�� ��˗�:�j�xW�vz��*�<|Hկk9�W_�RV>����!|�� ���q���V�+%'9f�����6�5� +,���������%�ߠߨu���&N�5�-�u��^���Qm �^���zH�[GN#���m��q�_�Xmo�6��_q˂!R���eN�Ig�u��m�E@K'��Lj$G+�߇#)Y����M������y���y�d���a ᜧ��q�E &A����L�Ef���i�i����(4�`����X� �ddfL!��\���?:oC.BT ��T0��T|�� u�� +q����Ѫ^��g��ʇ\;9 a�M&�fR�@$�0�d���E$����L��` �B�81 g�Nx��+���0�).� �̽+5�}0��*M.?�<��X�=���>��SV��r�s�wf��@N��3Xi�]e���+� �udT��x�c�����l�a����n�a����l8:{��ԋ�)j +�̹�&�,Ky�&)B�f�������7\�G$�K +�A���< � �f��`0ڃ���`tW����pտ��ǃ�\\�����`<�����Ï$��`�����]�� ��S<1���<qţ�3 x�H��s#���ͅ Քkm���R>��RJ��oe���XpC�(�����.ˍ�Qw�v������ľv[en�y��,vY����[�Ia�m�sw[l��b��5�b��J���� *f�j}i؃�w��QD%$2 md3%3T������/n�F��L"�L��m��k�ϔ4��皎[+lk�����*fr��Sn+�c��9 !Rr +Lx1w*J�#���c�K% U��bJ%*`iZ@ƴ+<1�EQ��W��k�� S4�� ���ʴ�Dž�X���x�D�Y��;�b(���g� �b���� � +�$0�"I +�;� +SbTt�����7Hni��g��j�� �8�D�sSl�~�/�FA���z(5V�z�䓔�"��]_WPx,+�=o� �g���ER! 8�of��z'��5�.�ƨ\,�l�N öZ��"O�m-��R�J�m�;���b�J~�S����jX.�>4X�/���� ���]�x+o-gL�)Y��㞝 �;�]� ��y�|N.�H#���z�����dx��v�v����Р�Ƞ��E�t��E��Mu�{�?%�%��G��^��woPX��{|�z=W&~�i�C�R�[.��P��7�du��o=?i� �T]o�0}ϯ8�xت�L{t[+"�-Ӟ&7�M����v�����$� B�%J|Ϲ���y�+%�I� \r%�`ɲ���5?�2g���B��$-p*ʹ�+B�����P�,�c%�?�heAJ�G+�F�>��M�A�3B���!�l dD�~�Z�'sl���m��� +�b�N�l��( +��E �[e� � ���Y���pY9�N���X{+�bc{�1�Sةv����P�)>����Q|�}W��p�w�:���T��v�]Nځ%r�蚅�zp�#G \ $j��@m�A��Q@�~�$]��"荕)��ar��̗���Q|8@.dM��З� �� ��9��P���/�)��%:Îe9�h;���&=�l��I���C{� i���Y�fS\����5.g���:�gX��d�4J`�֦ �xGC9��*tuo8/�F��W#��/%��dl �: ���J٫�k��"c}�/G�wEp:�'�����{P�Am�h+�r� +�.+ɨD����|�@�ʓJ�޿�:� +�WI�4�C�#m�$V�|�^��қ/G��g%�Z0�O͆2X�V�d�+I �������1�X�C�m��IzhYL��� Z�N�)L�x7N���L��e|{;�-�7)�o�z>{?]L��Ͼz�?���C v�me|��~R������Jǒ�H�HTy�9A�7d�LTdJ��X �2�\� ����ImqF�V(�<���p����$X;��r�'I0E��>[�R_Γ^��ԙZ8h +d�f�M��BKIµt{c�e��Lh��=��`�G�I/l��� m�lgn�����B�w&T�u^�!���o7h����W���% �U��,��d�c^��Pu����!X���B��Guڡl�j�;yx�큅�_��%��x�Z+:gAQ�\��^aŎQ�e(}�B���^>4��+��~��s=VĺV"�o�1��C ���8\�Ű���%���N�F}q������);0w1�>{��'ݾ%W[��:���]̶}1Vx��i��4�w���4��6�d�_���=����Ym�W�m�Y�TX���U�zpq���֡#(�.�{ݼn+m\���� �e{a����I�AG��h�@��PKr�Π)H���*46>�>|� =^^���lеlw�^���#!��Y��!�6bd�Pq]��G��y�}�x�m�; ��?�r;��(���7W��V�o�6����m�V֏��M�X`��. +�L�%"��To��>��3��-��ݻ��_?�Yي��t��L +�\p�����5ͨ����W\�?�-pQ7��Д��.bX"�b�25*�kY� ������HH�d���B*���2RA�3��� at &r�'���r+����kG ��d`2����VR& �И+� +W� T��J,M&˵�if@ւ��x��[*�u(F���HX˪��úiF~'�-���sh���4wO:]t�k�@�i��Qi� `�(s������p�$�K+��r�{ �4BdƔ����>�z�R�Q`݌/G�x��}�� �&r��]qE ,׀e�s�˜ ����dr�s�↋�g�u���Hۖ���; �S�d�8>�/�x���x�u�m߇��p2�b���r:����I �kN�l�o��U����c�, ���~R��P��J��.��g��H+L R�@�MDI��� +�E9/�q��6�[���%�{��Β����#��22%a0�"w+���j��t�V��ب��3�2Hh�i@P���p��Pz�b9j���z4$��_O�����~�{ �ԡ�H��^i��.eQJa��~�$r�L +m`~w;Z�'��l2���l��+^�UҮ4��I�5أ?6ؿc�]sj֥�)(������\h�6�v���p=�!��T`gJ�{%��2�̃=]G�v��UΤX񴲓��PK�t�&�8k�\b����)�觱.m��٬��/��vߊ�%q{גp��|Znl*wt�J9�Q�¢!�!����� �TCV�`X�@��{���D�q)�@�ϩ�ػ ׉A����M+�j�C�e�<mF�R"(���f:���� ���U���_ /:0Ϸ�KR��͚{JB��}� :\<`^Q�q��dڠ!(�>�L4��ʸ���jᯅ4;��HI�BC�L����;.��x>O��fs��M�'��l�������������5*��R� H�'����Z�;�K���g �,*V �JZ���6\��j`2�7�8Qik�,��J>���OAHS��<��P�Ұb4r_Y%i�T-��%�~�8+y��@����۽��Y�܍�"'y��?���!�[��?��<�|�?VEe�;��g @7��=�D�"| �,�ͱ�ڠ���/������C�����v��{b�EHZ�0�bWb[t�R�+���3A�xI*��[�Rf�)&� ��ʘ�M��C�Hki��m0�T��2 � +�*��!��AtsM� i7�CO$���lR� |D�֧:Qsԕ81(IiU�*�0B����򍮯��Q���{ _�Ч���K�9ʬ��c�=B��c&*;9^"�q���EݱB;�eq�D�U�1�5�Uk���Xp�} +�JR�,D������ �@�}�׸b��,�yVD^#�Z��M��3�]��y m�5�xJ�QUf�#VU�w�r�Щn�T�ֽ�/����nV�X�'��7����{�-t8_���I�(�Mm�����(���$��/Y?���{=������t�'�/�᎕e�*����M�C�98墳 �9���K� .e�b(�r�|.���.݌�, �=���0���XKo#7��W� +>HY +r���8�+�D,��faP���-�C�-+��E���`O��<��bU}_=�.�m6��{7�w���q��������2şP�7��rQʴ��J}� +�1i%�2m�21{�>�\��p)`t��8�\Ĩ@ +$i�`'�;V�un���i�Q�;FO��V��n5�����V>���a {n�`�\�^�GH�ǜ�f)p�H������ S1����fk@�*���`E�,?c�S�52��T�����T�\�~���� + ��p��J���4�k,�>G��"��R�Dd��w�S�_��&ȀYW@&�m�� +`kLv6����)��N��̂��O���������w^�HQkP�{�ư>˲�Gl�"�lO�Y�,�\�^q��fB�:P� +R�` ׵ R� ��0_�ǫ�|9�_櫿�}^�/W��W���v w�p}�����w�%�}��ů$����f���s�� ��S<1��)�@\�(� #��R&69� l�*����&bH��K)M�-�B�|�X�H�|.��L�ć'ϓٌ�FnP����RH���v��V%L�\YF�!�iM8�أ O�QA���B���P����8+I�����<�:K�A�����#�+ 5��e"v�!B���R{L1"e!���kܲ'.s�8^9_C� ���ІdB�_?�\w��8)��<�����vh�2�6Ѱ�+4�s��ӫ?�$����L{5�H^�H~��� +M�a\�-��E8�Ic�LK��kDJ_�"9�>�T2�Q:��bn��ͨ�� + o,�9��� �����I�c�!bi�^��&�k�Y��nS�ڕ)_=^�C%MEX�(�6hz�ē��������!l��1�G�d�4i\;���7a��d? m?��~D*�����j;b3����`�m���7�$���� ��Z@3�&B8��� +*/��yz�r�k���-F�6�*9\{b<��B_�T:M57js��Z�WR#̩��{�ru3_x��^��8�9NТ7w� ��(L.n� ���k� �^���obXr.� +Y�-�^��bF�Ӌ �[?(���4�<�&�Fz��2=�).L2��B�K�^�;��(wIk�~�C�WkD�sgR� �ȸ�~E�i��#��L$� k����oR:r�Oz�|���$��yV�g��_8�Q��QG���J�*�����?qT�5>�w�V�C����T +д�@,�Z�uz(����T�X�B�eb�v,.�qNY1窬3��w +W��cf�wQ}EQxJE�N�żc#��zDxĬ�P�:���MpNj<}]�_�(>,E��AY�~d��B��[�殊U�@��ھ%���Πm�׵Â���)(,�F}7�|�ͫ���K(� ����)k�tv}{�|x��>smtC�����?-�ݼ�gM翺jVm�8 +Z˭W�Boa�G�o�� :� +9!uq?������+L��x�z��M�!�ZM��S7���T��b��(5�HSݢ��&m����;���d�4��$g��j�`�<4�[���=~l�d{��mH/��%�;��WS�p��.-��pI�}*�X=�UsȰcm����w,^�N �~/�/k���}���O�_L���������Bc��Դ������D#\-B���i��kzJq�k�����5j;H�_�[k�k��?�[F������b��YYo9~��� X +di0�Nl�c;Xa3r�R��Au��\���mY��/�G�j�� �i��G�u�꫋���!��0.�X�I���R +-S�w��p)�8K� +��i=:K��G(4�`����X� ���l�B� s3������r�)�����TN���H��l��(��-���|ry K�Z��kG�1l�I�$\�F�XJ,�9�f)p��jm!B�+�b:j$���Ā�T:��`NG�}�h�8�5�2�G���cw�4���O�7�%����୥^�-i �Xr|�03�Dr�����R��2F_=� ��G��nf��c���x�ٌ��w$�jN8�8���ή���I>����\a �-�,Ky�)B�6�?�&�~.`���b5$j PuRi�� ׵ R��]�`2��/��l_&��|�×��ۋ�|r=��[���^M擛� n>���+Q�k2�r��|�B*�dO�+h +:V��t�_�R&V9[!��#*�5��X LĐ�57R�H[g 1�>c����4�2��=N�c��Ba�j<�K! ^ﵗPhY��̕E�� 5�ɏL?M�B���>�����.s��F�PY�)w�D�kT�J�vg��\+.�܌G� �k)�-��A� [C�zW.�6Lڒ)!��=��$������'�����hq��_!��(���G.s�"�<(��K.|�B +K�o�S.:��Mf���`�Hp4Jf��v�8�Y��� ���d�.�7ֵp�\�`����?�0�:K�6������A���X�� _�����8 Qz +�*�yj�˰���)X�'������)i0��r��޶�~r��w�dJ�mˬ �S�����~����5��:��D!nJz�&W%o-_�<�e."���H +mT�~��C8�&�%0�ҾH:�Du|�pX�m�(��,O=�s �=��pR�:��-��Z�h�˪�CZ���#Ky�c�0�d�X��i`�Boa�(@a���K�$��<�rn����SYg�-�u$y.�LwH�0]*�"I4��\�0�B�J��6cz6pXPxe���)�5l����/k'�����Ldn�H[ ��»Mv �.}�g��������h^��2_B��"w �]�~z��y�%G�e�z~1��"�ʕ]�<�)$okg�a��,���� ��(���:+�,7z��7�$�!pT���F��&@8'>�<�z��Չ�5��Q�Q������Oi�늜J���F�_�XH�'4�H������j2�G~w9��xv|�2@�aw T���C��~����kP�W��3�OF%��ȍ��+�Tu�zЃQk��23R�>���V�^0"��ʸK�bK%JC(�$zɟ����� ��*�����ʹﻘ�?�_�0����Q���`�U5��@QY�`Τ�|�n�� `����)D +԰f1���1+��\�!WiBGp�4v�g�� ���c�/�@m���K1����vmym��܉ug�״ \e��jڢ �9I�T�C�$�����Ѱ�*䑽79�ղU����LbйvЕQ?��2J���u{�(�_��@2��~ �l�&�E�������C��t\��P 4S�F�?����Њ:7���l�(�(i�JH�{�>Vh� Mv�������'��n C��ܳu7�Q�$�����]SFmVy�i�c�E +}�.������\A�\H�ҏ9���I�������Z��j�Q��p'u����J���W�T�`:l� [��&J`7�ۘ��� >ie2����yA.�^T�b��*J�V��Vj �D��Mչ�\߻.�u���D{o� �X=�U��p�ڠ[d��Ef��z;�T�tx�[o +���]v�����'��G���Qg��ׅ��i;�/��� +�*5�5 ��-\ӈ��Ԗ⪨��{��nW�m �w�]���� ��!�������࿽Y�o7���bN +�P���Sױqƥr`) � 0��ъ���#��uA��� �}i�G���DY���2�|S�����hGp!s�D+'��*�B��&gZY����wR�:{�KQ���e*�6�����X�{������,�� +a��n# ….U*�� +N��P� h�ĭ ���fy[���K��5*g�sD?�Z\���R�̟J��0��t+p+ia��,����T��Zj�fC��`&LJn'�����BcW�,ȕ�E4�z�Q�Ӱ�ep��u�>�����_����i�O�{-�����b-�!�T��u�K���U:�!��R�]�l��p�MV�Ǔ�f� 6x�M6�.N�_�����?�:�%�|T9Z �SJ�)�nAE.q�#�bC�DqH#�Tو�m,�f��E�mh�s4<���|���/�#�|�����|>��>�-.��pu gW�w��˫��.�t�q��r�n(� + �Ca� m@RD1m�S���%���ȥL *+E���{4�#����Z B��˵t\T�Xw|�]��� +ݵ�:g����R&Q:��r"�L�Ul����:d�TbV��kj2�PZ��x�9����#�X`�Q��Q����hG��R*L�4(.�0�=Mr +� 4n;�v3h�܍��{��b#�ҢQ�$+L�0��B�/�pkA/�)�*a�؎�슡M��[�y��j0,��9Z�7x� �wL�h\�Go"��z�A�#�u�c�� +m��ͷ �� �vv5�t~��9"�r�G��#X��e�4�-CDI� ��B"ju��Nb�MUZLDZ�_P�\~�'$ȇ�N�z�Cy�٨�U��i��2�C/��Wu���p�m@vp���>��I���o�� �L��0�aB�������`}Ƽ���u�8�4!ā�hT�� �:��@`1_�3��O]�� +�) +7O�.B�a�������d�ˆ5:4�CN��,_��2kh2_� �-!��K��^��rd�ʑ��`*� �8��I���؀�Xe��C>�0-�\l +m�z��I�"JG���w��� +tᗣ|[��G��9��A6���ㅤ�䵡Z��`�-.i ��2�n�S֬�%P��[�*��_/�:`� �G�٧m�fЕF���i����\&�,U�{�� ��)w��δ��֩ﮖ5Ӟ��C���j�W4X~~] �V���t ����Cװ���gO�}���96�юU���7Z�&�^����Tݵ��)��O(� �� �{Lc�u�y�u�r��G����R��O� �Ew��֗��� �� +X�[1)�i��A���S��n�E�h4 +G�F���\/"���{�\ %�2n��?K�P��/�P��N�0�O?u{�S�~��-���6[j>m�_v#]����6�'�7�v��rܢj�a�q��ӆ�'O����q�eQ�L&!R[d��^=A�I't��}�0 ��fn�����Vt|�t������ZS���+ �Cjo�$2�+S� �62�i�%+mQQ15Gn5P��nU��O�����]3����J������P���˷6���C�#�VaRh���鸎�w�`��h޿�k�}�="�r>�<�x{��z�!8 ���B �+�a"����� +��A�6p}�E�CK�����p+�7|�MX�vδ�{�s�ⓑ���0�|�[���U� �$��2S����x,�<����0<8�3ɗ���]8���OOEj��'k�n���Ek��׾8����v�@}ܯ&�l��@��D�|�ҫ�m�FѪI]�m�*����)�Q7��2�GoO5�&���$�;�B��xh��ΐ��m��W�����}�� ٔaw0��&~n ��� ^��{l� �˴D���,Qs4��5�k�=�q��`����Y��������d��������2ܷMxj�Б��'g��m�������nKKW�#��?I��ԁ/��8���]���7� ����� n=r�o^��X�o���_q��*�(T��\��VQ#�5$QUUְ{�^f�3�`��:�`_��4�HÞ�����^��3������ !��0.�X�Y#���Zf����R��,��k�e���,� 3RA�1�����������r��25;�nd!F�{5��A!T �T���Y��� �l�7(�����l1�C�3˟p��0�7k0k�a'�=�RKN�Y\�Rm�!Ĩp�TB��2�+�Z�;�J�y>X�+�`�v��Z#a/ �J�k�>|D���?B׬-S�F���{�� ��Bc)�!���M�q&b���;����%e�udZ%f���kc���p�� �5x �j\��\����? ~ <D�Z�¿ +�0��X�g䊜� +8E� +�� ��'�c�SC�Ī`+��ܢ�����pM���D�pcA����[��79��I�/�����x��HYa� +�a���> +���i;�C+� �e�,�����M�ܺ�� ����e��B]dƶXz�u}��o2.�Ov�G��?16��%���`ŷ(��� ��S.0!��� ����Q2Ge���b{x�����P>+c��ޡrF��6�x�`ʊ��%�u?:�|\2Ax��e$]�����M?�ow��t ]��R=[»٧���0~x�~|;��r@z�X��t��$ہ���!�,~�u�]����)|��2�r���\I�1����`�H���xT�<��W���/i|�#�}� +D�e����� Dc�����{/[�]+�!p��@b�R�� +���}حy���m�N��I�%�L��WhTe� +_K�~�t+E��$��Ж̓\�-O��!�Z�e��>$�+� ZG� d��m�� g�mР�� ���� p��N����Dj��C�EV�P,1��G��7 Z�Vdi��w/�^��jè�p[Aض��n���U�뚀z��G3H=���hRG|(y�B �J��+�Xf<���ڻ;˫��t۾�K;G��j&�����χ2|A1��U%B����M�Ra��"��P1�>�j��Cw�Zm҅9ZVo�������ۈ���ϴ���b�[ �h�k)eF�Y���;���7�u�4x \ܹN�G�{*� R�[Ȃ��ܧ���Q���f��vXKw0��JzCպ�P���!���8�r�ϋ����,����O���P>�FE���M�7r����"*����5�x�O︉פ�U6�d�Lc���qY��X��2��sT�_>%Ӯ;�efrwF�����֌�9D�qH�@!�]Pةr��ª�2kf`dz��V�u��}y@�r02H�"�1��5%���IcǩƂ �e��o�:`� ���5��rk�s4�&eao+|&,u ��fH�0-�4��iN����]���N�e��串���G� ��YS�栫WNx-��#S�p&>���Y��Y�i'�?=�����pf��pTnND䈣G�4�#rdO�"Jw�� �3n�J/�>�'w����&<5z�lX�œ��N�m�����r7&��5�v}�t{��t9N������ى�m�����w����m5�[���N������X�oG��_1/���(��h'�c�EM!5$QUU�r7[���=cR��fv��l�EJl���~��~z[,����'p-s�D+'��*�D��ɥVV��[��I��K���WQRe�E.Sᴁ$��[��>����f-�H�S�pka�u�RA���bz݅R�h@+$im`��w��yI�s�DfW���LY�x2]^�B�,�J��0��tKpKia��,����L��Zh�bGH�`&LJ�'���-�Bc����(��ut�z�ѬӰ�ee+ꐌ|Fc)��o��-Y�N��3�^� (���Xk|H�p $zU�R���Ct��>��A��SAp(���@8��K���`�^�;��&�F�W����?��D�O*Gk��ߥ4��|�(r��y���5U� �� +�F:��I���2�I�.J۸��Q�b +�i~����=�2���i_.nn.Ƴ��&7p9��F��&�p1��$����[�|( � H�(�[x�>ZB�l��\�r��Rd��G�}R�YIK�� T +�\IǠ�$�[�w�H�HU谙�9K��H D�t�ʉl0���^��p�* bV��15hE:��m*�J��ad�e�����r�Ì0{ߔ�q"�y�Ku�$�<������ ��-��=Fs 2!�u ��#XS\H�) �h{�"q�ryǵ{���sL��*�GH5i��D������R2�>s�]-�.иMdX��P �ܠ+O��*�uH��� ć &(�1�٨s�)���J�dIa��&Zݣq5MGA'L�L���q��^�G>�\�;�9g�:� ?%:�s�3>���O�t6h;0<�Φ�k�(�>Q�'?t˼�'�StٓA�ʗ%Q*śhc�ZΏ8�j�Ȅ�k44<�<�+T�*�N�Y�F�����i��7|^�U$W� +��SA��5K��2E��cI�W�JC-#l�B�H�� (f'/q�A��.��m�����JW�S8Jq!��55������r� +�7kf��P�����d���fv;����8��6���a����~�����tn��U�W�h�ke5B��lc?�������ڨ�� �8Xs+�o��:�# ��Jw����Kȗ�i�2��U�d%��hͪ����Ƌ��U � +��Aƻ�|�1\�(��z$�@."A�ս�����֟$�1��'���i�XHOe�0 ������߅����ʬdQ��D�ˇ���eq\�-�\@}8�݆ヒ������#+�7U�'��;���E�ތ �����OOVpq<+�/4�����ǟy�����z��'^iqKC� +�&? ���v���_J�2��� uO� {���E��P���;-Ћ'��ﯱ(j{҂.�e�*ۚ&2S���z{�!����nJw���Fɞ���J�}T3!H{�gža戅�����V��ʻ��Ծk��J�PG=~KG6�5�]=��%��w�5��l�����j�=o� �X]o�}ׯ� +A!���1����@�r���(�B�Vl(rKr%����%w�+Ɏ{S�sf8sΙ!���嫼3|��/�FH�D+�B ��[��K�������Z] �:����?�):m �h��>�O"!e)�}����T/� ��.T� zӛ3(TJ�"���ڔ��(8�,#f�hM����ȇ���Ɨװ���–~��V�������#,�LS�[���ڬ}"�h(C�r��wFd+z��ؕ�3.ez��eอӰ�E(e��F>��\���+蹕w����[��(���TG�O(w $z�K�*�ޡ�j��o!�^0�����}3@���`�\�f8�n�� �Ɇ���O�������׃W��%�Z0�W! ���� .$��-3�� +�F8��>{�(�}�j�b��6 ��u/�0�v����xڇ_dzn���ww���z +�wpy;��Ʒ�)�����7��q<�� �"t�.B�(�{z�9�ZO6�D,EUV`F�� �%9���L�T)H�΋ʲ�Am�K��|�P��fZK��~�2b�tF�a6����O�c:�!YbV�kj؉��78l�g4N �o͂G�A/���q��{��Ŏ#��%+F!!�ul(+$V� �Q�π�cX���q;`�� ^��U�D�pÇVr4��$��<�dJ��:KA�6 ��"�t��|-�)�)-����r�ϼ]A ��4�P��*�30d�(� ;%��9�Y��@=�Qw>u:��^��e)�l�K��~�����TG�ܳ`0��s�%��/�*��c'�� ,tat���_/�ZKgor�4��A�� +%BZ +�4�>�'N}خD���3w}IP� �� +e`��1�{� ��8�!� +���`]X ��Ɠ�� +�x�ϖ������H�A���%��V,�,�J��3[���� �hp��1��n�UDc�rmv��~��!W-Ҧ��� , �x���D+�L��^�O��?� +�R��*��������r}�6�Ř��Á�|ʖ��A���,l �NX;?(+���G���3�P���Z�z���R|�Ho�ڮ �XB�8��� 6M;��lZP5*Y��x�&}.&tM�M�t�-ڣ����k��VH �q�R(��Xr_��҃-רD^Ht���]��"�_}������aǧ� s�w )���� ��AۇY +^��8�j.qW?8�>+��Q�JE�î�I|$�cW��� +f�c ܠ����6�e�������L�m��^����ƁO��y8��z'�|]��Ss��.�� ��'�;0�)`�����^�,�g�k�#tf\8!���Z��(�ڧ�{�2z��Iߣ���D;�����4��,��' βí2U[ ��%�s�҆eo>�ȟa��o����\X{��3ۮ�����DZF�@��SX�s�k+U�[���,�3.����X��� ��'A��Q|��n�9��mh|����!���j��Ԩ���ڞv~d�kg����L��k a����_�m��jx� 8~��f���#��t/����h�[��λ����Xms۸��_��7��HN���9��Vӌt�t�ɤ�"W��@ɺ����.�"�z��K9GC�>X�˳ ��&�����q ��F&�VNH%U n�����ZY��2�Nju%E���2�"��p�@�k;-`��2De1��"�a�'n! �T$ +/�7G�� h��� ̴�v9�>� b�8C�l`������wy ��~$����M�M���6�0�DIZZ$ �D�B�ca"�w�ӥ��ԁ^(4v*����2�)���X�iX�,��ʮsg��K[~�9�C7e� ����L,Ai�� +�!�ԁT�Y�H�B��wW������1�o�dU �#m�0u.=�v�EG��m�n������ux��U���Y%h-�-�#/A�i"C1N��r�8����N��MڶH��0UN+L��&��Qp1��0�w�ް ��F��<�_.no/�����p9�_�F�A���$���Wm@�hRC��$y��|*l�l��dS �D��g"F�� I�f&-�ւP$r&'�%��ފ*y�����i����y�)ݮȜ�Q9w���(���mW2$��ՙ�� +.X�m +�ܿBJ+4�`�p�2�]5tȃ����W U�p%�A@��%�3���:F�h��rp@x>��g��J�ILwz:�����q�逰��:\8��D,���S"�j�ƁA�%�eݩ6��A��%�w�krR� '�t����p�˟Fה��G�*�­/-2�ӂ�c�*�T�*�@��������n�n{��9��m��mG��G��^�@�$Z���y?�ƫc����[D�%�0p&�2I`����� �p�e&"��k���fѻ��=���g�u�~�I!��Uo�€0F,k���C�Ƀ�T�s/y�*)�}���k��L�l�ajW�y�^ReI�?5ͽ4��.�<� �Eή�����6,�2��P�+�S����#2�j�9J+OOQ�}�eֱ�/{B�5]�q��T� ��z\zyZ��\F�Jfр�T[+�ɲ4]YڕrF�C �"j:F�*���Z��3 \��a���_�R7�2�`�eTO�l��&� +9�ww�>���07�|C�U&�s~��})s����u�@g�+�͋<6�����D�)�͂C���UU��t%�W�k����\�br,���c�`d<��Wͭ�貐4�4�e= E��F�;XN��x~~A������m\s�_�}쪸<���B�pJ+��q<��j����Ӛ�� ��I�V�u�����'�q�B�(>̗B.���5|�ݸ���0��,�%��jmJ�����.TIγD(�c�ʏw�6C�i������� 8��u~V��9�R�|vE Q�1jT�� əP2�Af{���3�Y�����m������!�㻙p�����>����_|��A7hW�筜���C��Q����-5�шε7Cn����ٖl.�ʿ���>_oJ��eЄ���ظY`� $u��d�~�ܻ�5Wy�鲯!�F,��'50HsTt_�{X1�o$�\����P�tI�&bY�a5d�q�#Iɐγ���[+?ͩhe��Q.��*�����Xe�u.��$��-[0���� �������T�Р�7��/����8�&���ŦF*7�%Ն@*|[�^Gd؋��_�CR�ދ�5����φ`S禿[N�K0("��n8�6�WDq\�� ��佾���]~��&K]���)U����!� ;����5�[����/�������[���� �*e���=�i���|c�ֲ���5�'������ySg7�Qw���o;Д9A����'Em�*>����`~C�P��C� ��ˀf-[�P|��kqГ-����YQ�rRg��e���iii�^w�z�V��!����Ot��� ?��ףF��4�aw��e�^!۶W����:h5 +��=�mC�;�F�ƔD�ؠ���,�۳-��#\(aa9���i[�v���^�f~�;���o&ĺ �8�.�j{�˪�"�[�]��uf��f�6��w=Od����l��-|I|w��;.ofźs����j�y���V�n�8|�W �>�A"}L�4�\��6;)�HKG�ԒTT�ȿ/xD�Υ�]��3�2sĜ~i�f��p����^H-u _�Gvi�3���P�|�ԑE��s����iG9�a̴YEX��w�nL�s��O�7�:' �)��Eml��u덅�!JKT��.�DL?_�f��(�b|.]����|%:cQ ��2� +R�֜HZ*��C��i6V����4YW�&V���͐�뉇��`c�X�^ձGx �Bɟ����Aq�`�ѵ�@��ю�=��Cjd�n�:ct�n#�E��Ap)0��1����oNҴ�Dp‰�e:����.�����O��s�9K��R���i���Z�肂,@jtVz�ˣ�v� �e�5mHQ���`��K̖�c��-��u��sq������t��]/����b~5[��%7�ο�_���H��,�{cC�B��R��!�����k(��̠�.[QJ�D���![K�u:����l*�oj���c���2F1��):%ME�MIڋ2Myk�ߗ��|Ȕ�bδ�=�����RI��'�{�z��u<�}��0��k{L�r8���&_��' ����y��~ ��1�� �qL0H��y�'^Ӡ�^*< %sXr���S�2�O�#7'��439���4�L�Dx�#��$y/���wrs�_����g��a��M�E�d��Q��,}�bg`I�R�x�<�dBc��l�6 + ����Kݴ�55��l2����/���_����8a�o��i&Òom����p��'�-4x%ôU�(�,�N�H=P���" ��������E#���~�5���o}�e��C���)��ip�< R�mڵ�؃�Vg|C�r�/s�0I�DX��>�^���yL�������3ޝ��{���Z�������b�[TI~�G��r�u;���\]��{��HLy(܌�Rx+�� +�D����[_Yӽ��tm���[���OK�B�L��9?�.������������ �+�>Xr8�z�(J�n�L��+ �Q�)�v�3� +M��������� ��h��|��T�n�6��+|� w����κ�֦A�\jV��Q��e7ȿ�J�� ^H��y���oo��/�����cG��aϾE������R��@>��,����:¢@�cK>P�(������h�p'�oLƝ/� �!�xJhQD�ٕwC�;F�i�(e �&��כmu�ž]�7�8j0r�;E��iN�����!��J��&���?+�]���4t�/�m�R��ńc�9m<�0Q9a=5� +���D����c�Ag���ů}0��1z��m{X9􎍷=���c|���.)��@���`bB�t1��e9�����m9S,�U��u�����Ռy���Vj�{��{�����1)���`�Q9�o�:�&8��is��� &kt��Q�g�cYW�>Tۿ6�����_��ժ������j[m�56wX�?&������cG +z�5���Rs⧹��I�Г�=[8�����VI�h��I��8>p̦ + �?n����>�P�PmE\F����P3ͶdI�3zeM�◛J��wY3��=A����|B�Lp��I(�4{z�:ܚ�&��Np�� �}�z���L�3g�-�����"?�'X����>��d-�Dvbqݬ+��7�{�>�����@oؿp�]��ϴ���'ǧW|1C0T�Q��S��m���o1Nuc�'�U���QfBb���7�֚����8�6������AO�5�$�ht����Ey���w�D��T�n�8|�W �M W*��.�h]+l�"�E� +�:���x�$� �� Ғ�٫C�șs�9?���.J��s�UC���PZ� +�&�W�dm���T}K��&t)��Gōp���^R�Ğ��+I�R ǁ%턬 9�� aͽ'Q�q���K��$���lв9�bT�;6h���!�i�ȉ�f�˖+�U�G����je1��Ş DY*Z4PzϦ �x��J�җ.�;U��77�f��rlo��n�e�l�ɱ]#�|��_�ͻH�� �3�6P��T��i���e���H���h��zQ*�#|ґi�����D�Z储���P���?��T��v�M@\ߍJI�;�H'Ix?������Da�D��S 1:��h��=ί ���Q��c�Q��r�����W+M�Rh����.�o��u���;�A�oo�H6�����@��t��t'z�"m�9���# M�Y�\�N�Ř�M��N�>τ��V9��ȱr��K�{����'VC�7w���]�]_4Jb�k�_�H�֙^���L�2@�D�D����;�� �G������_���{���Xf�|�؆+�����\�g�{E� +�ZKl������E�� e%N�� P;�3��|����(��#̱Q5AjvB���"�W��luM{��|\��k��]�Jj��{��+Il����l���>�^�Fw\�t�mf� ���ڠ�f `T�9mP!JC��@F�ow�t��A�_(;�@�\W)�^�k��( +�K���4��*�)�`�ۣQe�{&c+����K�6#;$�:���NR�T�����d���2~���hr�;�� +�F�ڡ���: Ő�ik%X�I�}��tJ�s?<� �p��I�s�E��}�@8֦LF���t��f��/�#��d- ��)C�#D��J��&Ԣ� � +P��(��\x�Mp>�����}�"�h�̐f����l��������WW��>]g�]a�۾M��n�a��r��#�N�o �*2���x�@��Rq槑�w�iN�%�J�\v�$���L؎�L������j� ����6�&��e+�O��Wu@\ޜ��$�s�$N�p>n����W&�0O�q����=C��>zߐ ���n ƅGOƛ�%�X �ܰ���vy���"�*{�/+#'H�����߷�Z���� ������mX�ZX�;W���c�}L�-��0�̱2$Y0�g*u����1ƍ��V�@#�Son.gx&LiqOi�?�(vx���rA�~Ǽ��ϒr�a�hU�Β�m��V��e ����l�餛���,�Lbwq�(|��ƙ ��c���c��S� �Rw<�� ��P�^w�]��3��U]o�6}ׯ808 \�裻��\V�C���S@S���Hʊ���%�Y��C�xϹ��/�����(���p�[! \+DŽ�����ke���)ۚ��w }Z2'�Z N�R�ͬa�"dz�:f��UE`��,��B� +2Њ|%c��w�[\�*�F����}@�l�ZK'vP�3��u#S<�{uG1pߓ赯#X��97sC��ʹf�$]��,kS&���s:_,�św���EI���l���X�H��Z$�|C�B��'T9�h;4�y�NIB���V`�F�Y�4�Y�f|K�_W_r|���͖y�Ȱ��|�����j�au����#K��& �*2���x�@��Rq�OC �[�:ن���Tٲ�P�-�0( �ZX_Z � +HQ ��z�w��������GOՏX�� ��m�)I�Z�KRI�S��5��&�p�D���X?�蘅m׵p�b +BJQ2 �k��?��š�Y�禵>5�7R��W��qs��o6�(�_Q��H��6�s!%�\cn�9�`Pԝ���?�����i�a�+>p�L6�6�F�1��?��x�[&[���`��6��C�>A ��(l�(�oIx6�Z +�M�x��ke�i����0'��@qH�?��KV~ƸU�Jwj��xm;�x��@��7�)^��Ù=�{IUӺ�4��}�.�� ��\ ���gm�=�>�o 3��t�B�ȧ�����C̊մ��@������W3~>ƱO�n q[��|m�������8���>�G���ߝT�n�8|�W �>$F*}L�6>�F� l r��S���Ҷ�#�(�C��@Zj|8�-N�D@����������l>O0�Z�2ړh�|����hg�jL���WO�;/F��;Q���&���Q���2֦�%.���.��hhc�{�me�{cќ2�*�ܲ�. +�~�����D|)�������د8 *K ���胱ml$-Wd��U��h��=̠ٺZ��*�zjƝOe����#�3��a\��.P~��…�#h6��.�DtKGh��;~��' �P��!�"zd��F +|��}� ����@~R +��﮳l��bé�U6Q����jS�^�N_M�ݰs��g/�K쏠�kDѾa44�P��1X񢫫�v� �ez>��Eq� +05�- +�� �-�����|�a�������b��W��Xn7��.�n +l�Xl>����� +,�f ~�l a,$�(�g~�znur+9�BC��bT�m��m+.H�@�D#��h*����$l�t���T�H�i"��qtJ�Q�M�:���i +~]��0�6��}/�;�H'VF;o{�/.cĩ�K����>���a���:���M|K�%��%���Ao�0 ���9�Af=n�Z7M6c������*�'�u�a�}�b�)6 ��X�H>�Q�/ڦ���4�K�%k'����k������h�\R�$k�JXGЍ,I[��8i+ʆP�������J�$-���tE���l�cs�j�sl�!jC�#�l D!|�Zg��R����Q�^���=�Gl�@T������[6�P� ��T^e���Ⱥq�^���lc`��˱{<�u�=w��#�C3f�B�z���N\��p:9}��C�Cg�%:(�R��]���e�u�r����7~bA +x{| �y�@�\�6I���E(8fS'���&�/�b��<>�;��Z��IC6{��U�EP�� � +���N�z�i;��xL/MK����aF��@VLp�Y1�}����[�>��M�u�(���|�_g�l�X-�������3�t �sk�6���T�i���e��m��[YB ]w�&��D&�DKf'������ILe=���qK.[Q>�P�2��U .��$��פ�$����g�7F�&Ѹ�I�j�Z���ғ����D֤���I"��Έ��?>+��HW��Jؗ��{�#����Ta��8��_��&ak�~���i™+ ��[��L䱭[[�Ir�{���C���PJ �X�ͼ�f���Vm�,���ʚ!�r$�T%\����Z+�kNՉj��['��QÛ;��'�`�~����N�%Q12]�� c�;���`�ʶ t*g�أ�A��9#����9"�4� +gc c�w�C�ޠ�u��Ҟq�������E��- +m@y.��TC�B�&$⁆K2��X���Ȳrнbc+������m�d�9�x�Ӹ��Pʤꁌ �������o1wU͆���}@7t�:�����aH�����D@�=�߆ ����R���1��*���$��>��p�M��%&_��f�m޼�ߎ���fka��N�q��m-kFM�W0 z#�T�G��S��IS����@A��*C���i���n�ß��ܬ��W�C�ɰ��z�����.�~���G���>_���؀�Z���3���Oc�-�N�e! )P�*;*�>� �Ѳi���Z��Q�F�`*�?����o\�$n}���Z�qu��$�9]�J��>v��k�3FX&�����^A ynq���P�0擁�WұmIp`�:2.�{?�h�ٛ�=y����M�j�n'���%��v�Z +�5�j>_���� +���9f>������o�A�x����1z������Tao�6��_q0412���tX��6&����]�OM=Io�I���dA��@ZJ]l� �K������;�٬��3̱aE�F��u���/�2�E�> ����{�Z +�I6��y����%iO5�I��#T� �p���u-"W�js�^��`4E�q8wn���A�!ZGt$|TD�~�ۗ�5V _�?����C��c0��qu�qi���1��@G�pu�,�}t�vf��|�6�QJ����g�i�`�h�Qʅ�q3n�;9%��_�*t 4�ή�&�Q�M�Ӥ m������{������(�"�0W5A�vBi�K��@?䔵����r�u��P>{���e���T����v�Y2.\' aέ΅��`���� X�G���Ͷ�֭c�z��4D���@F�/��t:C��ϕ��(G�\W)���= +6y�5�.�lB!h�&��%7OF��w���T+O%���m��Z�x⶧�úo�_�XO�8>�h�?�;< �x�f���kvP�0���MS+�e@��^�> ��x� +�� �p�R@�\s�$]��"�)��br�Ng�l��8>0׺&ka�{� �X?A4M��XׄZt^� T���.�m�!ؕ�iC����"h�7ɐf{�8��l��t�iy�����j�X�� �+L���t�.�sL��9]\�A�Ud@���$�@��R�3OC ~Zz�lCRJ��lEI(��LpGCf����B���(��z�ܼM��y#�O��j�\��C?)I"Z�%�$ �� ޮ�߆0�(���O���g�zS��->�*��e�JX��4B\�5l(��WVI$ka�[�/�GG:ߍ}9�~F��g��!��B@S�[�'��7�.†�!��Vy���oӮk%Q�Z�ݝdm�i�;8 ۫C�0����n��������9��>D��Ta��8��_�ȧM�٥�㺹lB͕�i�~Zylϭ"��oZ��)v�R8�l��u����n|A���d�&��� m<:G?��b���[�Bˈ�}?#�$�䃈T`��0����E��}��Xpjl�����j�-��N_��Z�s���cK%g��U,�A�胂Q�8��[���y@�q�e�Ѵ�Dv? 5�, ��/����S����ǧ���r���v�X��w�>�m �6Xn�|{7�oȂ�[H ��j��´ :��$W,���;Qjs"�ђ=� �:]B�}*��p]r� +�R ���"��4LJ��Λ�t������� a�Y����7��\��b�t���������7ӗ��J�� +n8�{���^�!��'*�/�,�J8����ٓ.�ÿ�K�&?�sxfXY�4���B&s���Oǰq���;�q2�j����v�U�e�?>J�����7�q9?� +K�/�1��ۥ��Pro��{co���q&�N0}3�$�K�����T�n�8|�W ��U�1=������"��>��Z"BsyK*J������E�ř�ٙ�o}����%��4���3�E����e���G�n��)���*�E� �`4�@ "g��+�j>�A aͽkTƝ/��zא�%4 �,cm1�>����P��Űj�L�����f|cˆ���bg�{X��Ƥ��¸�17��B��&i��Ĵ]�$t�/�]�R��f�H<���'�')'��a\���$�Z��y�2�l�zv�:��� �#�@��U���w����[nvժ����ͻjWm75�k,7��}�yw2�#=zI"X`�D�9���CJ��S���hX��^���H�bx�� ���Xs41�*$����[r㕾OT�J�mF�����=�Y \�ż��#Qv�Z��q�I�9��gN";rB�O�Gp�\c�k�V�R��*� �����3ZEݥ���Cgҡ���c���x��c�{P�4io��M̱����X��O����>^_{aM!�_|�]S�B�C����䉤�H�9���[��Z�}S|�Tao�0��_q���U#A|�-�VD�VZ +h�&�yI ���e��#��(BB@�$R�;�{�.//�����<�+���N(�tW�\mMC��)�ӆI�k��u�T,�%���S�����-p&P���5!7�V�ׅ����� �.�a4y�a��u����0�#D�D-igc ' +���6[,Q�&� e8*0(W���b0��a��P�h�@��p +�@�Jp�uK��YU��4��U[/%_M���t�3؛~�r�zl�9>[/�y� ���ٸ:;{Э�C���OvP�0��4m�(�e@��ψ����켇A +Ly� �MN�s�E� ��Ppl�J&���l�\�˧��g�n�Z0}�S����%Ů!4b���(���S�:�h; ��M?�6���/����Y>��4��s|ʶo7���^_��m�̱��b�~�m��:�f�t}����s�r51�c/�0��(G�4��e��v$U�$��^T�����tĭ��Z � 4�U. ���ߴ�����N�/�j��֘& ���II�;S�N��}J��{��!�0O�)�~�MH1�h) �cu��Ǭ{W�!������$�l����s�;��1�q-�@��9L‘���ȁ'1��$]�i�����~�t�'�k��#ʒd��O7�\�wF�$��~�(���2|{+���{�NG����Pz�&�..~�:K���c���{`y896���p�"P?DQt�*��T]o�8|ׯ��T�1-��\'\a�ۢOE�%^(R�+i��^��A�� �H�����y׷}��� �X I�Z9&�P \K�o|��Ւ��Z��)g? -Y8Y�p�Wi�����T��ȑ����R��� a���#gy�>�W5hE� :m��Qy� ��1D1| +�D�~����BF|-�G5�Z�VX �\c� X]��I�צ������:线5�i����V�)� R����=Oa�ƭ����c1�D�ɯҗ8sm��������-�v��A�� +\w�L��=�H�/#��B�����307u +h��/�l��ńSm�l��}(��M�z�*}9a>*I���^�Q݂���U� �: #�P�"��d��6=mJQ�h{4�K� �eQ.����������*��U������bWl7%�k�/�O�y� ג��&��"T��?M9��}�=q���Ƴ����8%=�N��Z �jH� Me�0&��g�:P���ZF��atJ�1�tC*���4�߳_�0��O�wo��U�o�6�������p��ӥ��ؘ��"�E?4u��P�FRV����@J�dX� 0 P��ݻ�G���*�(# 1���rL(�r��@��D+�%-+'����TF��> -�?�>r +�bO�'e)�Ӂd\1^R�q 3���U����+�*#�ȣ�A�M[���i�2�冨$�l �D�~�\�'Sl� �L�G� +�BX4�<`� X� ��I�Ѧ �x�����+����Qdl!�Xy)�/ƶ�}Z���u'�Du׌>��^��� .]@�����]@�l�jKGvP�0��e%S<�;u�1�#�k?E� zs��I�s�u�4M�Pp�M������t�N_�����OJ��0�W- eX���J +�֒ Y�'@(4F8��G�~ N�tlZ_��gZ�� �)�����y:—��������Nj�|�by��rq7_͗��Ƌ���|q7 W�=VƋ��w���}�k�����V��FpH���\o��TdJa�h-�� E)\X*�/�y������3�Jk��nS���N礒$��.�����F&Q��1���aj٪g��a%92h��6x������O�����j����<�J�^\"�c�{�D\2k���zt����CD�@h���b�,5'b��O�.F��߆~�Ȁ���B�݁�Kg����S,L��ۏ�[�9O))�Ŗɚ�� ϟc5�2�@y�2�j���"�Β�_�k)86�⡎o߸V֙����a�c����o�j)qr����EI���� ']y��<���R�|���k����p� �҉ .����欰��� +�o0p�X�j��Γ�_��$�ڵ��(��7l�.��=!��!宯�Fs�j��>��>��U]o�F|篘 +b +�ѩ��� + $�T�)8W���;�>D���{qGҖl�IK@tܙ���9�����$��q%$�k�PB�p���3����j��j~˥�B�/BK֟P|����$8)K��ӆ��k�!\i�����4�:�WhE� jm�B��x� d�V����)�E��j��ͱ2� a;h���*a�js��6`E!Bj&!�V�:��Jf� ���Έ�rЭ"c+Ѥ�:Hɯ�blG<�uw��RT�͘� ��7�M���U4�ߎ��Et�������P�n�`�Gt��!G +|�I�& ,J�����aR@�\s�emۦ,�jSf����b6_���o�7泒d- �兡�;������$Hֆ �A� +�N�r�vX��1=6m(Qأ���F��|�ߦ�"���b����_�����z1ϱ��l���X/V��+L������$\Etۘ B��Q*�i�!lK?'�[�!�*=+ �ޑ�.i��†�Z0U@�Z��T6@�i 6 /.�oUﯵ�2".w��d�N���,�.�����&g�`�� +[�9��N=C� �ɑA�,v��TD���~�`~p��!p�o� ����Bݼt�t����E�pɬ�/��֑*^�t�}�#�cf�9�`P�h՛?��C��}��,Nbo������� z�>�����K�ȋ$���';&=]3U�>G$1&>2r�(�(��,~7~#��+����ke��ܝ��� +��}��YL�M*<'5Y���Q�ҵ���{�^jU�_ bm����!�=z��IlqzT�/G5�uT[z�Q� +�#���"�}�;J�.0�l�����hI�7�p���R���h"Dg]�>�'�'��Tao�6��_�` hb�Rя���smLhk�ۢ� +�:K\(�FRQ� �} -�.��� H�w���ݯo��K��<�k� ���(S�7�K.�8ִ��b�A�Jx��O��'�;I�* ��^I2�*x��NȆP������g�b}��Td���-Z��D�*{���Q[���w)PE��v�/W�+�rGU�o��0����-DU�Zh(�g��D�R-l�K�VՍ��kT�� �XOɸ#��3܏RNT�Ÿ�'�.H~����o"h6���_Et+0��;����2��vZ ##zT�#��$\�&BD)���3?u +h��.�l�TĄS�u6I����զX=���0�&�`��^Y�P �N+)JM�b���P�U^��"��d��6}+ڔ�r�=`{4[ȋ~[yq�������>/���]�*���r�y������v���K@��7o/@�7dAw� "�B��Ru�)����O�#��JB S��&�|K6NIG�U.��A� +Z��GS�}�-�I��ꄼ T�|�uD\ݎN�2�{��dY<����{�o&L0ϒi���ph��9v�$��ģ�}�u}�*琢S� ^�i@ +�R�a�z�t���y�O�����K�H-��/� +t��TO1�/��bu�o��%��A��p���?H�ӻ�{� +�'��X�Mn���z,~���{kp˪ϲ���R+�}odd��U�q��ҟ�s��=J�,’�ߡg#(nͦ�-������u8|xf�G����U���<$ɛ��ߝT]k�H}ׯ8�B�J������ڬٮ ��ҧ2]K���j�#J�ߗK���%��H��܏sϹ����.ɦ�S�UC���PZ� +�&�w�dm��]�뿔�JW�E�iu#)���$m���^tBք����f�K 8[��sx]�k +h6h�K0��� �cD�����M��(������ +�D|��G%z�j�ZY�l�p`Q�*� �>�ic!h�� -K�n��j�5[�.���|=c��Ǵ�q�~h�끌>����7�k��:�&����ۈn�-4;xK��A�a( �m�(�eD�=�H��C.�� b+���57N +���.����TĂS6U6��}�,W�|��M�z�|� Y C�xe�Dq �u���h���㠢�Fo�S���Ep:�G�����k�8��"�&���E��g�������Ǘ���b�߬r�.��m?l���6�n���k@���~����ɀn:�`��DOc A-ÜlGR�D#t�EE���L�GG� v`m!t�F��EQ�}�[�I�0 +�g홛��_J�2�W��,��.x���K� �Y2�~�����k%�@Ig!p<�^XX_�ʍ�b���A�4�H�Q���>>l �����,���q�)��k���@7�t���Í�.�HI�M�4$4�']r�7I�b�7>�0�}�/r� +^�"��*��Xp��3�熜7׬��,�����8x-c�o�$k댗����G쑇E������ėw�ջ�uu��g��Px��'����y��$y�.��Ta��F��_�0��G +�x �9�MM� ''!��z5���v������{ٕt�Ж +�l���y����۶j�l>O0džk���+6lJ��@�5�ִo=[��˚�[�i��� ��d�6���!�'�+!llg + �X�Kt� �5�V�X� ;o������w)�����a�Z��u����W�;�V��d�(8�V5؜�4��*������pYy�ސ���8*�fj� ������v#�3��0�D��� \�*�f����UD7��zt�������6m����=�H�/c{ �AE*���0(?)T޷WY��}�bé�2�(f���._?���0MM�A菎� +�ڶf��5�V}P0 + ���gS.�M&8��ihS��~�*j4[���3�[��|���ï��|^��,w��:����������r�7X��o����}E�k%�����OS�-�N�%�'֨�);UJ{KW�%i�i�)Ps�>���߸�5 ׭��B�q���q};:%�T�mI&���i ��f�d��,��}�u'<E�����ƒ��*�y��M�2C���CAHհ�_��C1Kt����/ �Γ)�ϒ� D^�c%�<9(��e +I��w�>�7=�[%�Ap�)�Q �ݏ?�Ux��XW?$}L$�;1��\���l�c������Wm���i1����QB�_]�:;<�QϾG��3�H\PnT&�"O��͆���._�B�C��}���Tao�0��_�T!�U#A|VJ+"P*-�'�:��,���YV��wd7ي��Rɹww��;�z��]����X�� �vBi�+��@?��hk�tN���Q��Ս�p����$iK%� �E'dM��� � k��RxN���.�a4y�a���Ym{g�!#D�D-igc ' +�M�.Wة&�Ke8*1(W���b0|��a��T��h���p+�֙*���+M�gU�f�ĶV] �J������SYg�7�H��8�3|&����9N\@�����e@�bmzK�Aa�PҴ]���=���_�$f륃T`v�anR +���Γd�X��c�U2QL>��U������O�O�!k���WL%�{��k�ۆЈ�+� +P+�tu��v2��LC�ZT���!�F�E�4���"O�3|I���O�,./Y��rl.��d��"�d96k,���!�ޝ����A7{���D�<��ԃw˨��H���h��zQ*sMv�#n���Z]�Q�r�T�Cq�#��脼�ƭ*�i��ztJ��ޙ�t���i �]���0�<����Hٳ�:�kK6 >,���ے�R�S�I���i�!�Ɩ E�7��~8���W���+����D���í�q��ǡ��ma�7ǒI8��4Q4��$]�)nz_t�E oX]�-!��扆I�{�o͇���\��F��Y�]�m�Į�24��4�:�;뜆��Lڝ��:+��;bҒJ�~���mHs��"�p�2佋�7��_�T�n�F|�W �>؂Kyt�ƪ"�D 0�y +N���t{�;�v��{q'�V hK� �������Oomg�b>�0ǚ5�6-BG���/��6��*86�F�i}w�f�XS:�c�x�皌�AR��UuG���k�M�"W�j}��4� �"Z��Ν8��A�9#T눎d�ρ�(��lw�r��o؟q�`��!t�1����8���XZi�� R#�U���k�O��.@C�wls`�T�N<� �'�G*��a��wr>R~���U�h6�ή�$�Q=�H@��%;(MlP��jV�N��s��8&�}T*Q�.à¤Ѕ`o�b�\��sqm1Q,ޗ�զZ��:5a>M��џ=;j���Vs�����LB�`��q`��D����R���M-��*@ T�h��PV3������������ͮ\U��c�ݼ+w�vSa��b�1"+7�n@:r�G�" q�8Qj.�i�!n˨��T�khe�^��VN�M,�#�(��2 49���� �h�xpgU�9� �� qw7�(T�%S�������0ü�&��ݖ�b4t`�,����4��J�������K��Zy��~/��@��=�eg���k��#�C��0:(&��T�S����ʩ���GʋIc_�۠B�r ����'1&rzgpn�oEz�~��ơ7u��ӧZ�����s��zf�@ʑ ��_�b[�ɉ�� �.U��Mja� 3�/�ޤ2_�/Y�����Ta��8��_�� �]�q[�Mӄ �K`���SQ䉭���I�z�e�{�b'Y�;��@�73o�ͼ}�TM��� �X(M�l�PF��"�O9c�XӺ�͆�oa�_�n���%��4���II2� +x��i#dE�y�;a nM!�|q��d���-j���ڶ�-��#Di�j2ޥ@Nݯ֛�l���_(w�Q�N� +�R�[��B� +���2;�uL$-�����foUYypgȺJ5)� T�Ő�;8�zƞ۞��|!����+\�*�F����MD�b����wP�0����J�=�c���;�m�D�ޝ�A��S@�}s�e]ץ"&��-��b�i9���������hr��i���=D�h%�V��Bc���Ag�W���DpަSц�{b�"�h4ͱ�G�0͗�_��?ן7�:����6�y�� f����f�^�X/0]} ȿ�����Y�}c �P��T��i�!���kH����”�( %ߑ�Ґ�� �u��V��QT.@�q c�!o��~�6�:"��z�d�h=�d�,�S��=�/&g�0�S�C _ �C�;��{8eJM� �N8�v[+����^5����`�K�n�[�m�6x�õV���v9.���[�,�Z8��{ t���͏�C�V�cfIxr0ԝ����>�`7�׍��~�U�E�v$������[kpǪ��x6�V+�]kd����d�m����X�{��K�_]=A�BF=��C}�N�)_��Tl[CG�NJIG�|c>&�I��]� �Uao�6��_�`p�Rя���KLX!��"��:K\(R#)+n��>��g��b �⽻{w��?}h�f_^�p�[! \+DŽ�����he��e�V���/BK�,�8������'�IY*�t���+B�׮c�p�[U,����ZU��V��ڠ�f����i��V�������2OnX ���{脫�*a�i�6`E!|h&!�Z�:$⁆Jf +ϙ�fkDY9�N���h" �T��!�w<�u[��T�X�Ř� �)�����U4�o�W]�-�vh-�x� +C(p]7R0��gw������ T���f`n�P9���뺈��#m�x�Jni�x�.z;`>+I��П�0T`�k)8[I�d��`hT�P�pB�S������R�!Ea_hz4�gH�1~�gI6��$�u�9�����<͓E��n���$O�i��-��G���� �*2���x�@��Rq��!���O�!.ւC2U��$�zC& HC�ַւ�R��QY=����_\7�?zW�h�Zˀ���J�c�:]����~�����p��x4L�\A�!F�,l���; SR��I�m ��a���Š�Y맦��0��R�Ǔ�q���[�x�%����ɑ*N����R�s�C̑��^�A�E���V�D�Y_T�:�G�^w���� GsV�'*p�a�%���J����u��ېk��F����]I��ni>@�I�.�`S�;==3==��o������ӧ��S򖧌��<�قTKF�?f���Sv��7���,'�6���XV��T9~|���%#g����#o�M�Њ����M������yAVy!z,���� � +��. +�V,�� !g�!��w�'�ߐ9O�}�Kю%�WKR-yIn�����I¡k����b��@Â-h��g�z[�Ų"�mƊr��B�a(go2����r��7r(֨�d��/�(a�/&�ȠZb��|����+�%Y^�M� t��fl]��Y�Z��f3l-G���� $���"�B����\(B�U�>�Nooo'���b�F8�����ӳ7��g���Y�ʒ��^��\o ]�S>��)#)����e����-xų�Z���E2S���A��+�;>#'g=��㳓��pr��w?���������9#�ޓ��N�;9?ywzF޽%ǧ��>9�nD��� �n]� �p�O�XԤpZ��T�ٌ����4[l肑E~� +� kV�x [�%$�+^!I��4��#߮������<�Sl�퍤��n�|���.�S|��A�Uk�B V�)j�H���=N�4KR$~F��e$_Wz��Xlp¢�'3A ��Z�i�ߖ��:e�aZ�y �rs��U����\S`9���铑��U��Q�$l�geUP�1�03��6�QVH0��򄽂��ě?�L�d�֟���l7���l�ˊ����t���x9��,]�pǯ +��>xݨ��eD��O�$ڭ��;���t���j�V|�3S�X���Q� ���" [�,��0�#!��Y�IXI�K�)�!�8���Ix�yăiOx�fU^l�s�7c�- +�����M���,X��t��I�_�R�ځ0`��[�f�������Ҭa�ch3 }{��U,KX2�5 +;S3���t"��`��E^�)P[�߯�&$@d-`�L�%s��@��r ����'�{�όV�%��E 8���X�5�-s��p ~deILp7���W���������J��F3���@m��n9<<L%�m�'�����e^T�ӱ~������E.}�L>f=3a,-Y�w�Z_�s�1�s��"�G����J�C��� -���:/��=��q8��d��MS��+�g3�&�)F��jW�� -��9���+�wCS��[.��>���ђ��L�bh�^O��`��ˆ���{)�z4� ^^I� f�K�ga�W�������#�M&0�����̪�@@H���v�*"m�����Y~�"0O +�c�qLɷ��{F>ߢ�P�錡����Y�����N5��G���KO���`�ō[�,�T��uQ���d��F ��h��5�.�B=�iꛐ��>�%3�X�2�Ӳ䋌%1������F(`��Vyqi�K�0C�p�m#��.���C���NSHG.�hGf(r{8_4q&ǎ�y�����q�&�1��n'���$3�C��z��ѩ�%��`\�iM���E_�1�jVV�Z�"k8<� '���-hcbu`I����ƥ`43�6�',�5�OJc��w���x�n��� Z���d�z��w"��z�W�UK�Y�I[g��5ҩr��ަb�P���X���g���En���'��i��1S?�~*��˒U��!��C��s�����z..����1f�i��=�G���wA����_=r@z�1�ҋ�R��@���_7q_����rB��i��U����L�e�ra�b���0��3��.k��H�"v�6R��r�߱�-TZ�?�0�g� +��(>�����bs�yS����|�izp��L�6��,�m��;kp�k2.ɔ��Б~��GУ~D�MS�'� �!7�6(��4�>�4�H�f&h8�\e�]�xPR�_�jQW�2��0���-�9$��� :�dz�Ɨ������������⯟/�?~���x�7�f:%��l�I �[= +K��� ��Z��q@zӿ.���io��Lɳ0����������`|@>� {5���MS��;���oX��0-y\~|��%q���MmPǗ��G�GH����ˋ=�\�����l����/T<�����x�h���k��(|x�3 +?�+Q�����A�D����^z����lS@d^��T��atI�O�������ֺU�]C���s'Z�+ ��e�_��n.}��?�_��qv> �}�4|ߥ��yFF�C � ё2V����f�>&w��a'm0�YG��9��ŲW�"�*���TN��I0���J�d^�+�nX�%B�1 +�+�uS2����:pn�(aTFq p�5���POu��]k ,��5��9��w�z^c�*�C�c�b�>�,_�dz�-�07Y�����/��̇�n9 Q�t@��i�"1N�v��~g�hSO@R��������6�&��K[S�[ +���`�5��`�Z�i4��[��@Yޙ�����7��6�^�;���ղO�>�<����vM���\3�[�)��|³�b4t�?��ؤ��I.5<>��SX��~���%�-��qG�3�uu�8�;��ٻf{��/u��k�� �1�/��D���/�l���ޠ��d��fh�g����g�� Ϭe�".L(��1���6i�KI�/x/������fOn�W�E��©&�e�#xaeY�U��s��J���x< d5���訙]��ˊc�Aq:����L��<�%<唫k�ZH�V�X�;�Ub�l�*��0(��fCSv��i�UQ]V�x2�c.Lj���0V���� +�F�� ��6଄��8�h����,���)� Iˡ �|Od���Q�x�q+�q0`.c��ٔ2VA� ��4^28�*���eeb|!�4���Ry��ll\^���/���C:J�ѭ��ȴ]��L�(%=�0=�v�gK��Hi x!~�, +�U���� �� ⊔�W�h��8���pZ �s�Ƶ E�V��ք���R�b�'M�h[, ��6�%f�2�%X���^FT�I�Ӱ�Dภpa����%'7�-$c^ �<�v���1�=j�4�V�(���Wgo�����E�h_����&�� h���:��{8�t�,�-k+�O��ꑟN�iN2�v��E0��&�����F������l6� ��A�m�d����Uj��3V���9z+�p��\ �M��Gը� _�j�Bv���O�hչ%s 9R�<��!#NLB����P�z�Dfc�o'��O��9�N�q�"6����yU�9 +>t�P� +��Y̡�/b��%��6 |{ �A�����F�آ8CaD�QZ0��l0��[�6Rw!� J'�jbw�.�,��0p��?�l+��r%v����v��:��a�����qSV"=�N�����������oY瘱���C���K-9Z���'Ξ[����qG��P�^NX��g6�Z�q|��6�kȥ�&t�ѯ�4�+R(�(�V-�����*n���h1�����.f�6+y`��Nr.�&1iD�>-��^c�8!��h]:4�Lj���K��e��h�rjI_�7Q��H�����+�Ԍ��� �8���#�SW�ҁ�n�iG"}�E�F�'��?y���d��L�[]��"B7��?$�kg�œ*�1�Cٺ������J�ui%�^�iG�\�V��t]����¥jY�`�.؜� -� +�)��M�[w$ ₷��q��`�(<���3���\��Xl�|X2*��s�iހ����&���nb��u�! ��8��=N�ypL���w�!re-B��$?�� >.�n�2%X<�?M���m����K�Zw�9TuR�$�u���ٽ,��i ���Y��<����SB�ȋJ����o� � 3*|�[ M�P 4p���vy�q�m_�Q@g����ˡ�Rm���iƯ7(���>� OK�\V�W3�.è��>Vq7b�R�W֗>Ԫ��p1�7�J^2�+0r���:_��ꃢs���3~���:�Ǔ���>���갆�#��f~ZOc�n>�R�����"`ķ��"��R���"r>���Ϫ��΢�c� 2�,M�,'��-ö�!��7�"^�)�xǫ�b�B.�&�C�Ёpb^��X{� K��u��'��>[�L�*Qv�Bh\^Ҥ�'*� ����RL'�1�L�qFC4"�9��&���x�I�fy���`�<�X�8ʥ��� ')�T 0`��m�P�U���� �� +�pa ���6)UiUi2r�P��a���r�ÐH�2��A�}�o�&=ڙ?X +x� ��J�����:�� +�:L3�1a�9�.&���b�A)�I�,#_����/��ȋV@Ug���� ���M �e=|�����q���d ���S�g��%�Ӫ�l0���y�e[��N��P�^)�a)�����1�4�7�*kv��WM��ç(IU�F�� +b�Q�3SVPՃg�t#���Y��^�t ���U��^xn��7���Co�O*� 2!k�Pt ��g-��������7�@��+G*��x�PE�ӆ�4��� � +�md� ��%J}�o0��y �+n�J&��� �Td§�bX�Z�g�`��p�����!��%�NpYM���٭v �^���-�)�U���H��@�q<���# +�%�d�Lg�)yW���w�;�vS�Ax�ToU+�B[�1�c��u���:�Î�U|�����(�BhO�{=�+8�Dl;"�@3#s�.�@�Om����3�k�� vG|0���Ns;.��8Ȥ��t��Op��jŊT���# �ׁ �DgU� +�E$��C�����dxr�4��;��61���ٲ�b3�{)R�⸎]V��U��ܽ߱r�V�M�f3��(oR���w˓jI�@o`��A�ie8 2�*����S����[t�K �`�N���&.�;ŵ�d������Y 0�ޑ�u�����ʊiY�c��ӊ�����IY���\n�Y�nV�=A�؊�� >�2iĆ��w��H��WE� �P(vx{�H=��� ���l��`������\5 �}�/(n� ��U�6 Ac��L#��?�]v�O&�ҞI�¬�L�O���ͻ^�0&�&�� �W�y��t�ϑ�刓�z�p�5-�۟h����3� ��M�c�O��$Α?��)��OWY?��",g�F�OB�����x��26gĺ�‼�w��`�Ϡ��,M�����#r|��l>X3;�7���"�߲* ���j�򆊄f�)5c���3[v�k��E�-�� DyP��*���M�=(H���}77xU�%lpO�6�e&�D �VX��Z�h]oU��<��]��tC���@�z^��n>���S��`��ؑH�y��H��*D�[fd�7�Hm;9 oS��������� �5Ys1��i��k���`M76 �*B� _E��ݙU�#�ܩ.�����k"��d��y��6Tg� B}u ,CR2���%SMN��-�k-� dn��]�i(C"��_�/����RQԋ VY��~�3Nu�y�4j��E��q֯�Lʊ�.����'-���9���t�2���QN��V��ж8Y�/S�'�2��^��� e��,�� [�p]�Bp/����wq�~��`r�%c<�� +@"��x�L���ܬ���^� ��=O���S��)���1�h+��&ޝ�1'' �aM�܏�udU�52+��~��m�DZH!�ܶ��u���T`��r,Sb��v��F�C��+RJ�cm� +4��qh@0E�6E�o�,3޹f®�$����V�Y_VKiJSE-�X&��Ž�G��j�«3�:�:*����p�\��_g`�f��D|�*���2c�wE�Xa㦶�V��U� �L��F��N#�P���־0��m�����gի�r�'o�b삹� k�_�O! Y�5�Ŏ�^C!����?X�e����� +����N�r��H�"��b��^]}�͊�~M�_ޫP�����wa��-�0ɖ���6K�BcL�R��_��#��^��Z s�ep�+��۶�?�Y%�n1�$�����+҉N���+�"T�g�Zt�4�ȡ��>>��$�H#�*t�����Ġe85��ɾ�N���������>�=���&Byyܫ����^ް������R�_ߨ����O���Į�$�g8< �k���G��� [����� ��j9!�ǵ��&���� +]F�u�j3/���4��m..���� +���J���ON�;�˳j�N�Ѵ�mTM܂UN���0�Be�ڵI[����U�-���Jfu9�fn�����3�W_ $����y��H'~��; q�aSh��E��Ck=�=L�,e���N�������t��S�����4=����l�v��:@z�_��(F>�&�Ӷ���%�������M�8й/N?�z�[��t���R�I)g���h+e�Z��f�V-,���X��_[-��ߠ4�S �4�c�Xᑰ6վ_�j�Rx mp�%J����u��1՚��:V��q��6f���C{�"o -F��� �,��T�c�eٱ'O��J�}%�[��yP5,Z��P�;c���l��F)��-) &7���LЄ3��,$��h�)��2��r=��,Uz)H �6l��Ax���!�>Fm0�� \p%�hp�����T7����^�3��_zY5By�/����P�@ B=�g�򵸒��6�[y/&��W���Ud����}��e�ށ��H1�Tl������Y��5 $��:\�G�:M%���#Ybz�u��Ǝ�^t�����m���.�ZǴ��p� ��ft���~�^햹n���`��]�J}��It�>����,�#�j��#�9��H�F��R���]d�|Y>U�R3w�3�~�E�$p��� �c\Nm�~Q)W��w��S=�p�q�>�=���"��D:�m' 8�DZ�`����{�v��O�}7�W��͠�[����" �χ�|� G��=��e�/E �]W�IzGĺ���:$-{�V��ŢA���ک_d�uv�y5�9�6���7��?3�&y�Xg]���x4H�+��G�_�R����7 ��1�/$����ܢa�w[�]�5D6qc!�6;�?����>����u��n���eS�6)]�Z�J�C{l̰���d��=a=���_�ꫯ�j����_��tlEH���BËu��%�L�Z�אYW�3N����i��J��r�#�Z�����Ǹ/͎�7��A~�$U���ql�������r4J4���P�J&�74剬qAy���7�f+������B�H��_����]�����v{~x��K“���t�'S5�]M��nL�ޔ���T�uV�zS�D�ڃ���γ�$�p�����8� D޻ܥ)��z�T�D�?���&�]�+�VR�*���!yCS��qC0��=�Y.����5� ZR�=ߤF[�|���[�OÚJ��1�^TE$lx�h +�_�.�K��0�� .z���������=��}|^3Q' �k��QY����ų�.��79[�N��uV �$ �<���&��t�N�]�x�G��v�l�_0^�]9R�����J�VrC +��،|�-���-mx&�%���֘� ��*cMFA��#�w~�`�f(��%���0*#�\�a��o�?^��\N�WR��Nm±��ZD߸���K�$�C��y��m����Ϙ%��GK������z�?�Ymo9��_�5�")�qw?��n�irgܞS�����3�GWY��4qҮ���z�؎�,���#�ɇ9��)������ �TI˸�r6G�o鹒F �E�Di!̘�N�7��4��UN~X�4G���]1�p�J�1˕�����J��%������~S�g�U��l�iC�L����tt~s.�~ƍ�� V��`sn`��W�+ ,�8m�p9Wz�B�Lg�d��{�����M΋`J�L.�a�7�� +�U\�y�уO� ��S��ma�{��i/�=He�4XY�K���%�jY�d괃w�=�/���Q€9W@��b�,i�@nmq��V���'J/����o������O�ۨ�h h���3��+ +�S6��(�.Q\�Js��G�&����*h��4��r�N`4�¯��h҃ϣ�߯~������p<]L��ί�F���xW�0!���z���� +MN( �"�Y O� ���'S`��<��d ���E�*�@��Rk�� _r�@eHu˷X%gK���PKS���8� H��Yi��e�~�-�Bx|�¡3I3��S�N,���)~T��$����s4�:�7�MP�&�6)( +��R�"��*mê)gKn-f������#�F�엁u2�s�Ɋ����2��%k3����&�[�4[�E ?�p9 g�hL��b�L�lnQ���L�H��0�i�nfT L����=�!�d������ț5�r����ƜӨdG�(���Qt�����:��{VhU���ѿ#ɖ�o��X%���X�!���Q��V���pd�`&Xr�������J�ECp��}�xg��%N�@�+�7M��R���:����9GW�u�(���WK~�e8g������[����> � ���.8;��R�wAg�4���Ȳx&�*�`�`��r'�{P�=�￶�Uh��1�'����h���� �����(����ѻ��@����2 Lkv����B+�)u���Rx��N��K���σ� K!z�"��Z� +���Nj���z4�[Mi�.)u�ʃ��2.�&ݚpn��Н�]��A˗75X촳������A~�9�������vxN�u�9:��,` qپ��%O��r�7 ��p�����k߶��p�p�G��kn��]��R�zX�1_�T�{ ���55���ˏ��u�9�ؠ-��2`T�'�h��{��0!`��P+��q�rliyʄ����3���-z`�J�J�� ;J��cZ�ν���:6�����.�o + k�kR��F[j ��gMJ-g��0/e�f��B��ej+B�ey�"�Z��$�Z�������Q"M�%g�&�Z��מ$���)>�c���������O�@Y��~ �Mt�\���M���� ~��^��A��q�j=o����ʅ��=���G{�Z�rn޼�:濼��� �_�mTc̮K���h�̺���|�Ѻ|��%^��J|sw[�Vl5�pwK�VL5�p�K����NJ�}�s�&c��Br��#A\iW{[��>1�b�m��2�3��Jな7h�����n#߬�Msh��x� �6`�c�?n�k��:�a�����zy�k��}}��nL�>�+�V`~죾�x +�^=^�Se-��C�7jm�x0��9$^�LJ��Ow�n&�=���h |0�����ҐTc���l���!�1�# t���#>�<�� ����+��}�v5�.h�=�^�{H���Q��p�71 ���� �{B+����ﰏ��Jn�j�xp�p�ƦûÛ�'� '���~24���ϸG�9Yx�t��p�,��Ԓ�I�t5�,��ݣ:7~�� j)�^±�u^��@�y���$��ש.1�ϯ�à큢�����U<1e1&-�����W���;n�٦�vj��;�_�w� �Yms۸��_��惜*Tz�:��vZMS�c���d2�\�8S ���;����E�d7�v�4�X6���> �}��yo��e^�;�!�R� ���K�e�c�KFC(� Cå�0cZ=���y�BcFZ�q��a&cS2��N"bVn0��;�BD�@ +$i�`)�s@�Ea���i�(Dk<�!Z�ӫ���b�Y��k'��ܤ`R����b��E'�,.b���T�0Q���W�'�Y +T:�y0�Pf�3�)�f���,�PQW��T�B�>x �Z�~��tb��lB(4��އ��B��3�Dh���j�����������ˀ���1��hT�e��ÁT�ȇ8z?9���._}��27"C�A� �0�� +X�g�������rW�p~5���'W�\����G���dz1�&Ex�+ +B*��Q�x�>Z�:�C�2&��%��Ce�#G��J���2��ƂJ�h+6�%g9 oIU�Ws)3+qvW!e4b�� +Ò��>��áUIӲPS��'����h���OR?�P�s?�e\�6�ND^����瀸mУ������x�`�[����ĠbF�!��BBf�����>/�JEHθ6�"̫ �/�?��Fm7��U#�3W�`H�}!+����Ioۇ)[���HN����f��M���"�w(@ƱFxϵ!rwK��KK4���Ms��'�F � �*f! �,�%0��M�.b�9y�+0��p%��rn��e<���@$BFT��^�ˎ`�h��<-4FCҪ0&�j:�Rہ@إ�C�\2y}��һ%[ (Ô��Y���L�%,��Ņ�+͓꫑��ւg�Rf0WB���o�*C�Dh%'�b�e�-�&U�������e^ځbǞ[�?W�\�;��5%OT̹N�}��`z��5_���g�y�D��,�E�C� ���ٸ�� ��8(����.� S@�Ƭrl���n��O�4B���?�xD�*�\_nq�7}� �7�{�Nv(�r��%��G�"3mŶ� ��]�����pm�J��w����z��.F��?G�� G�� �Éa���_�K�D{l�����������c�ښ%�}�T~�й[�V�7i�VsҒxh�e���F+_�����%����U��T{�lبs��w3���z��~d7�h̴�lc�^Նy�?v�ڂ�Q��*�)���p��W�kKÅ�s���d/�5O���K02�R��h�⎀b���iT�pmP�(�NrO�O%AB�GĚ=1OU���.<"W�������F:�tz����.�[)g +Y���R�ѣA�uZw���ώ/��9�u��9x*���?���f��.�� [ �:��E� +M�z!?}� ���-Oz`����(��sX� !��⠵7�*^�[x ǰd����N�X}��4)�1ME��l���*�9���w\���3��Ĺ�L۞�F��j[�Cl�kʅ6te%�]M~�njkϨ��F*��4;>������������1�#�jaS�~�&�6��t�Ϯx[z$��ʉ��. <"Ե��a�&�6G#g�Y~)��ȵ�cn�`��o ;��Pw�P�����6����{2���'�!�w��)���Γ7B���z�ksOU�)u���W� �b���@�Yo����܍�M����s&f{`R�ۋ��=:�ϣ��h��뛰P +��V�1s7���/Vri�t�{��X�����R�uy�[W���t���ʒ�qu�xh�X��+!����WLĕ{�2�2�a +\�d�1N�e��7�p�݋��"��MU��9����UW1��ޅGw�tqXʦ���_}�ޛu!�]��%~h�s]���e�E�x*b���n.�������Y��OH.y���Z��D5�p�u'��nF�wWUs7�o5�헶V�+�'g��͠�����ڇ�D�\؛����:�+6���'�.��}���<�޾�� +�[mo7��_1�JJe)�}98�7�[�z��VZ�P���ˊܒ\)j��~���$;�)j��Ι�3���t���=;�gp� +���0����+z+� ��i�o5�1��/4I��J���(!J �P��E�+���YJ��[1�k")\���D3��wv{ч��T��w K!��M3$�X�@��%�Z n)5䯮'�o�a��?f��1��^�^0k!?�LH q̐5I��K#n�tNd�jG"�H6_hkN�Z�t0AUn/�0��l���Ȝ*��1�� +U~9|=�0�:�m����^� p�!S4��SDS �C$�i���n�]�1��St!� +�Yq���:=�����y�B�G^�ѯ�oϯnϏ^��-�yB�I�̘�1L7@�4a�&�F�7�3k�4���V>�N�M�d��@p �C��[����Og�����r���� �~vssv5�<���x{}��rry}u �pv�����ջP�T��JTBH`hO��ˀ�⼤R�� !|��9��XQiNHJ�)t��cHؒiR +��t�g�MJ��Hʝ�����f��d4"�s�5��F�?�{m[�`Jd�D����aA��ᘣQ>�I�XM ^�ƭȑ����fR,���&R?�L�F��"to*يh��7��wר;�m�qM�D�8lI>R`�l��cV�4��(̊$̥*�����L.�/mb��|V]_Zw�������5 �����<��ެ���s�F��0p��ի�*�M�t] �*�mk��9[Q��դM�$˪�nqYl�Y�#�"���Y�{-��ovZ�����щ}9�W5;8�Se�*� f�,�,fKʕ-(DJ��*8�<��z!boc/M�YB$�]c�CC���O(�b.�rh +��:��Ɩ',�2f"I��5L&�qE�ב��I�fʿ�bh��7x��3�����"5"���z�>N���;(-~QZ� + �)��:��v TSy� $�9���c%�����F#�����u��zT4�zN�H��U�:TR�Q&34��2��'���<�(̘T"�$&� �,E�1�zK��Rk�������T�E`S3��-�1#����W�,QQ�J�2�`����ꀪ�l6��rC&S�� +J_,��6STBD8r���L��d z!E6_��n77�>>�S��{�w�����?�@��&������^�!?�5n�d\���L�oneB?�^���9!�]��t�8U��W]d�3Vu�'d}�pU��0��Ì�b�K9$L~�� ��T�|>nL�F�i[j����10����ʦG���!\j hŔV�p�b��K��!��˃]Y�%YL[��T��Z���f5T�����Rk&������Nz���s)���׆� l��1�H���Ԫ]1�x�$�bjLS� �L#x��S��e&W�d�I��6�2 k�$`A��{�cTt,�!A���O�U�g�'�k�f�PfXf�����%v�B�9�p�q��� +�2'�74ZQE��_�䬈��N��¸ ����^�(<��E���z/�Ӭ]p�]n�������!c�hm�k|�`����o�KY��c�j[��y�=�w+X�w{ ��v8��Xg��L���R8����E��"UL��SºpI|RO�k����/���W�Y�j.����s{_���w� �p��?�� UJo ��ݸ��a[��E �0���ٗ��� C�����{uҩ' �f��&r~t�7i5�7s�ODTm z|<��?�.�&��9�pC�q� ��L%%�_o�|;����y�6^��a���iE�z���x���[�^~{Q9��Y�W�純)��. +�s���&��!8�8�ш�Q(sX�j� =[�H�-Ў�d��屄�y�u�����T�%����b):� �HKĴ�.t;�*{�o%�1t�%�ߊ��0�1t�_L ��?���yoӸ��1���#��L�4g�+���0򅭽8��s�+6��J� +��&��X�G��9� 7� +� �Ƕ~��n �{[�,^��n�[(�-�5PB%�Tz�&}u:�z�0ߔ_� #��i��=*���| p��LЄ�qi����� �\�_hp�d� \����h�j�K�D�����T@V�%8��1��v��b2E洹Y�)����6'� ���� �^� �"���m 7���6N�=��M��_}��U�;����}��Y�� ����h��b&:T9��������l}��P�/*f��x�����c�~�=����残Ϻ��Ԝ +�>ad�� � K��L(�v6��SC� 2��*���Q���G}��2�uI��:�aAx��(mҢwwGG��wOur_�׹L��?o�e(!z*��_�T0O������b[2���x�}�j�9��j�:ݔG;Fa���-���j%p��ӑ$�u5��� j��h9�~�I�V4�`XZ��t��Ά�9��1�� H�&�qHh���J���ZJ�R��0A��b�d�0f~��I$%���r?Ԋ��z%Xl��|�C0��f�[ �!�IVh����D:# j�|�v��7ތH^� F2C�̗���:�p��g�KP��Ԏ�?�Cxc��GUI1���:���6�8{+��,�ɔJ���2R`�y���ǚ\���CQ���c ���X���|.{���y�A�Q�2p†����J�4L~�N�%��޶ +Q��U�{[� Ei�������Vc�l۹$ӫ����=w��"�RI*���m��s��CQ�Q�1�}��z��W��߶�S).�3��m3g<�� B8�隻�5�_H��ĕ@�#�]+���P��y��k�xb�,�k��z���D{J�—q��Eh�.���T�+�u.e��v�Ĕ�T:�ۑ;:i�s!�Up]I��H���eBy�d�^M��'G��Z�]�v��峼����)�{%kz+��b�)l7��:�s�[��F#�7�&��z!����IN[di�Z�q�|�oe� +0�w��;w�r�ީ��\�l�2ۀ��E�` �in��Q�����5��Ay`Q9�֣mM��׬����ZK�$˰uz�{r�۞�vOK�d����&$[s,|��H!-�i +�S<���G��� G�iB��,[��N�2g���7�lGI��C� G�cZz�'���X(�N�� ��*ᑙ �-gvWl� � vJ8�y�1kEYJX�,�2E� ���J��'��B��F���bz��Kn��{j�c�s�W�4������!����ܖ{]Y�W� e�Y �a.��"a���XY��z<�`�Y���R����>)��b]�IQ7aX�b�)��T��|i��N,�r�����.,�m��V�$+盀 mT��I��z�U����E �6l�wi?,@`����Kq���c�|�����j����nTɊ'P�j 8�t�=��u�W%�Q+蘕����t�2�Z���3A�de=űd�6L�ݗ����nb:̟pStq۞��%��EyŢ����6���N<6:/��x\����xGq�b����q��2˂�*:�4g� ����g�Z�c"�ÂI�*���}�FkZWPW;�oT�W���?�47L�6�=��J��Wu}��c���o���CQr�̫x��5K.-�G,���<[���0�kG8�5r��<���4��~��ք?�' ������r�oO�+X@��{{��9�Ct��q�����:|��.��5��>�k��@�N=p�j��.>�Q������~�U�8G�E��8� 1 ��CBr�x�z��I� F�ڣ�����(��k��F�Ɯ�h��.��J���t� �4���'����@B��Y�Bh�W/� �$�{��`"r��=��[4�������n}2����To����\�#�]G;D�4h�}h5.�Ե� �^vh�����K� WU_!�3纡0�]R0�r������r`�3nkĩ�#�-�]�8� ��Tx����-_4�-��,��6M�$u^�����z�+ׇ#%4��rQgˊ�R�p'Eg[�J��1�Ts���H�9����} �6n��,���n�6>���������:H�;mb�f�)���i�+䅒�!{D0Ԩ-?:A�ȳ0@��Oȭ���*�+l�=�_s��)�d��>ɢ��1���:x^�)\���&V��,�H��������*�Z����+�(��4�6�ahT fª�;@is魰���R�i��� ���R����wMo��;k���v�FG�qVy�e�Q��m�j?�<}�*q4�p��j�(:�E�mV6�92]�j��iF������͓�����0Qx��X+i�ci#[VC=��C��}�u�X|��%��js���&�c�aӲwek8ȴl �3jt�EJ�i3��&�=[?���s �������ܵU��i�oڷ;��:��F:�� Z�)_��.[i����M�v�b���ԅ� 7���+���7n�Rd����6g�����X}��ݜץKU���Ň�u ���u������)� +���?�^ �৤���?�X� {D�Ұ!� v�_��/-?�F�8������N�yrmҰ�Fi2hQ-h�4��5I�v$uH��Ո[�Hǵs��P�ף>>]���U�<�U� L C�4�4�vI�@��V$����;-��둁 �k_��iF��Ej��V"u({L%Һ�W��޳ +i(zWҹ{}�d�ᶻ��?~���3){Wm މ�l� �jp�����o�v�k�����^x���� +f�X}�V,���%�����5ud�S��ݰ{{�� �k +Uo��� G{ޟwD�1Bg�]x��9��;���t�`u(�KH�`}K�k4<��&˟6F������o/;PW� �N6�U�� ?0ͽ�Ϥ���K �����Oyy�I�|6h��5�;�A`����T\�� +y�����2���uGϒkPFm�[Xw?�˳� ��ڳ�m�i�b��b�^t_ �Gz���:����a���?�D�����LZ��|��.����cA�׌�|;�.��[��t��� +��e���C���e�/�aw��( J��3W�}�2]a� �� ���)g#��p��"e� ={��� ft��n��}ZS���b��"���>m0�� ^� z��&5�4ئ������-g�����1�A�.l��z�,�,ӲJ������-���WB�b5U���-��x*+}~ċ�'��.�T~zR�/���� +�@S��4^��/��� +��(�$q ����y~����.^:(�$y|�zCW����;��,���|�2�ݷ�q���i��1���p?G�{�k_u��e��iD�n꤈�e}��׳谵�vE��.����r��$4,y���F����v�5��[��;*�N%w�3� +��R�f>rg���M�*�f��ǎ?haOh�l��gNd������.ɞr{�����Ϙv81qR5�|K~��^I@h+��:�;+p��o9oӉi���\妌�1��%�(�� Y*��&�1��,� ��o�f��� �������^��W�n�F}�WL ÕY*��NT_P��X��<+rH.B���K�J�/f�w��$ P"qb���̜�C��:������<�{#xR�!�?{7Rh��0�3�E����όT��L�A����C��#��(a^�0���0�p/S�3å��hv߃T��@ +$k�`%�KA�eJ�c�X�W(�����t>���������١n"0װ��R�}N�Y \R�l"d�0d�'ȞL������tē������d�s��5�2͠TPg����&�/�B�D��,;=뽲�+�! �K&�O���3�Y� ]c�!s"��>` +Ƞz �!k� ��7�̀لR�������n2��|1�5�y'b���r�>,���$�[�1�Pm�,���↋�O�:'A�Me����]�����h���>��g}x?��1}7������d>����n����|<��`z����s<��r�|L�� +8U� +���-Y�t��1a�B�P�Q��HP����j`‡������d�–Oɛ�y��U6Ys)ck�f�1e8d��! +�����pz��к$�i�*˩a���F]��ibg� 0�V�h�c��G�q�C|�����:�r�<�Ej���Q���̸��9����nOT���t��X��T!S�B�F��bv�=g*L���lN3 ��W�e�6H����摒��;7�\�>����2tk�&�6� +��#�GyN���=� +WE��5�Q�N�+nh�hEd�� �M�[S�m�M� +t��K)�zZM�ơmz�͉�]p��˫����8V2o �am�h��E��Y��" (yM��;��ÉъvюL�&e��dF��V *Z��ű�К�pu4��hR�T'X��0���y��'�a��� >ρ�,N� ��1A�jk���K� ����C������˘{�³��ٔ�\tב��r�1O=�ĭ z��yt+����pu��� ��Aiwy]���� +�J..�c���k�8m;��Vn���n��ǯ�گ��"bs�Ż�l��.6���-���� +� +=���h޷����(Y�Y w��������z�޴��{�uK�׳�pzڞ�zk�9k�}>CW�6T���t����4�z���.G�vz5��:DS�ҳs�crd:}L.�y`�\]��Qi߀���Y�o% m>ڛ1Vd��;HE)v6�"z{�h�JХs��ibNf��~��i$�|"鶻(�"����ƅ۟YGkL�0��8�ƺ��W~\0o}�/�C�5�6�2�� +��a�R_�Ȭ����E�Y�A�O�"�ו�^B�B�%�P��P���g���G�Aa���d^���ݐڰ~d�6�٨�7諉�� ���Ox���u�xy=�2��\c���k��q�BE<"0t�(L�*0.�P�� ���RdC�t���O������= +�Zz��E~�(OП�V�I�y Ւ�>���paPi�LMHjū8q��{:Ejm��M����� ��k�����/�W]o7|ׯy� ��1i��@�`� �Pw���y�����y�$K�]T�!��������T�`t~>�9nXr��`Ū���=���jIW,�.�ʑY���B�'�IY*�t�7"�s�r�0�ݪB8� +����ZU��V䣵A�M��e봁�!JCT�r6�D~:[L.��b� �}��Up[t��c� DQ��ZH�ZiS">�P)L���u�1\V�Sdl�M,|*�D����Z���mLe/�X�!>��>�w�[��*��_O�>��Zl��Cki�zȩq`�\׍d����ޑ_#�^��A�T�W�� ���@�\�~4�.�p�M9J)�>M.����_�eoS̝�d- �Ӳ�� D�H��R��|C��X�3�X�Cm��۴+Z����� B�N�sL�'�c<�̇�2Y�9�[����v<]L����r6��,&�����_}�_���Į"zh�OB��({zJ�Zb�lC9�8��lEI(��L�DC�f�[k!T�5� *�C�rK.�hD~�Z�q��J�D�tIj4 +ϓ �{�^��A2�$y��&����P�>ϥ�uk](Z�aJ���Q�\�T�%y9����xO�·6N��B��?�-��:2�85��&UPq�a��u84��<[��N����oL���bC�3����r,��r�5Y����l+�S�B���m��v>�7�1@h����ҐpE]JP/��ܗ�?����&WiO��@[���Z״.!7��7!���'��!�ۙ(7(����8��s�:�ߠZ)q��H�����wk+���-;�n�{9��~�:0���f"d���������vp��!����Ov��긱��K�%&�C�N�]�ٓ6���� ��/Ѕ�����I�� �!7�W��|[�Vp��ݞ�w��o�T���k��a�s�i����6��H?[Ú��і�A�6"p��qK��'(oǐ��Eʸ���HK�cn[�C�(�hV�YJ5;�壟^��08�D�e����Uo�? _�-NI���D��Cct#�r����}8ױp}��~;[����auM��.��<�Z|4�s�����-���N��� 넣 ��צd����a�oen���(��5��*�f�jG�1V��(n���O��k�}ܴ�ȟ���1> _�������U�n�6|�W �>$��J�����&6j4��IpO-��m(�%)+���^����E �ٝݝՏMe���<�9f, �V^�bU�W��]k崤R�Br.��`��"�q��匔�^G�Ĉ�"�u�[a 3ݨ\x� +���� ���B+ +hmQk{�oyۄ��QZ���wc`M����z��e���8�Ѳ��+vh�}B�-D�sH-$X�ֱ��T +���6{�e�[E�Ul��&HY��b܁�O�5�����1�Y$_�/p�:��=9{ѵ�Ci��� ;�9#�� +���d����� 9����Do�� ���8 �t����4m�v,b�cm˴���ί����������+I��� [ʱ�C#9[I�� ��� � +�eϪ���xL/M�Kd�*@+�8����� ~�����_��.� �7�z�\�=��V�eD\��MIS�x]�J��{�o��ז0�y���������Caȣ��I����ؾ���*+M�������@,6|�qG6�J�2{j�b�-�F�1��4>�v�a����X.��\�O�I}�|�vX��L�}��v�8]����ƘoἿ����o9W�����w�������VԨ��r|ׅob�2��}���y��Zb^5�%�+��J�P4*�7��:ҝ}Y�@+&z�p��o% n¡��W:����%#��;�lk�~PY8Vnm,oԓ�ɏ1��� +[B��3�sQ�%�ъ�A�!.ܶ���ܒ ��g˅~oh�p�T�Z(6���a�̒�h����S�ǘ��=���k��i��w�rE9',�}�� .��Y�U��92!�0��O>&��oxȏ����"�o��}q���[�W�|�)��Xߏ9~�(E��d��d���.�� r�}Z��ۊ���n5����v C�Iny�����W�U��uY��/_��%�I����R�K�Ovc�3 +��n����_P��X�ڣ]� G=` �� ����ۓZd%�ܬ�FX�;��\xi4�'�4:G F#�6*c� V.R��D�E�P{7�#����bzs +��~.]��9l�/������/�2D�KR-H�2�bC��B؜\�L���(=��F�JY����.���ؚ&���u ���֑�?�^Cߗ|�Y|�l�3߮����8�I��a�Aj�LU+)tƷ�w���Q�YR�@�+`V��@��(�����x��lF��[����ߦ7����W?�^�+��B���i1��D]+���BPbC��4q�����^�bH�]��~�v!KJwp�h��g�9L�����t>�ߧ�_g����ӧ��bz;��'����.���9��`r�������P�-��ڒ�]�'�{hJ6Vb�\��\� ��E# +�¬�ru�h+�(���A�Jz����G����E��DźZ����:�d<�7j/���2��z+���$� �H4A��i`�4N�B!�G;�!��u2T*tAR� 1zL1�~%�K ��X��-�/C��03:R��y���ġE��u��z�r�X�bWs�Z�E��bhlm���;C�*��c�2�I����i�|iMS��`��Ӫ߾-�;`�+x%�Ж +;����&��o(����î ���Vj���.����G��W�<�JN ��WR���k���$(��ѩF�Ė]��`9���$�yv'Y��/�ɏ�����ʣy�&��$�5�W��(}SO�v�y���#<�*:K{@�!t$ ہ݅�3SM��P��"�y����A���hS<{pcp��RH>�š��|���1��a%Mh�۝���(��(r�^+���X���S̹�R�с��$��&��9�#����N8�VC%���0��QW�ۡ�w�7IL����f�����w�K�9s;��b�y�,����9 +nyNJ�w��+��[a���jI~����Ua�̲�V��z.��SGH��G��Z/[����������381U\n���������e���x\�SǢűC�)����ߕ��-y����i�����u됈� +�O�L�_���N���^/�Jl��<�F0]u[�t�S��De��I�/g���5Zm[w�t>tT�a#��0�mt�^v1���v���������ό�3�<��7h��$ؽs�i�vZ��8LN�2�{]��β����o> )�bN�6x*� �K�@��p�+�ؠ%K{u"��RγQG�;� 7�-ZR��i^�8S5 ��Ɵ,lݏ7 Z��2�1< )���A���b�}��|�:^��5;�@L�oIr{M˯���*"5-?a��A� ��&������p{j:���I����8�o�@��p�J�t@��ݏM�b�K +�=�Ā���Z�ʫ�K�),�Y.���{�������P� d�:� +�8 ��~ߑ��S�ݾ�0Šnliv&� �ݞ:� +�k�~�����I�W2��^���4��|~E��>��FO�=%8��n$y�[���_O�0���)�xj+� �t�j�X����Mn�+;�B7��Ov��h�V����w}��9~s�TM�L&&��$�Z9��U W�K~��Ւ>�j/YH]� +Ʌpڀ�#�9�B�+�IY*�t����+B�7��0׭*�c�0�f�1ZU��V�imPk�����G�]E��դ����(�_,W�� ��/��8*б��*��y�F��`��`�Ѧ�x�P)L�5��.+�)2��&V^J6����ñNc��^ʞ��2qK�z���F� +�A����$е�Bi���Ku�SN�+�n$ ��W�|F ��E���"H���o�p��_�r�y�$]��"4kS&���*��-�٫��h`n�$ka�sˆ +��M#9kI��� � +`�ΰcUz�&��˥ -��a�VaF� iv�w�,�q���/oV��^_O�t�ay����2]��E���Ž'?���C�� �1^�w��Q*��4������6��sH��V��R?� i��l�h-�* �fLe=���!%��|�>Z+�e �{�$�h�.I%I�}H����O&�0I�!��a�J8�#��O�o 5�EH=�c�Ka-�ֺp�!���-����<6��Hvoק����vF_��@����\k” �Ϲ�]��۵`�H��1@�zn�#Gޚ�|{�[ǘ��DO&amڵ��V���+� �h|Eߢ��4��Tю�6|�W �tg\� �Mќ{wF�6j9 ��4���I��Χ��bi��m�"�ۀ�3�ٝ�o�����"�+6�lTlٶ����;g�3��@!�����k?(Í�΃m$P�� ��k��D�(����v�xR��r�m�P�jY��1؆<�%A;����><�9œ�ZOt$C�D�~��Uw8�I���G N;ĎN���y��a9Z�=8L��S�|#���G�m�N�|�/��H�Ws3�L<F7LR.TOø��A$�.^�*v �Oo��7 }T#������4�l�ݱ7��N�I���i"q{�!T�w�,����/���_��өP������%�請�u�����Ռyo �O_��`?B��a���`�I6�� �'ϑm{#�0��rM/C�[��g�Ҏ�e����Ӳ��|�v?o���q��.׻��f������U�u�� +��'A�R��o@;�ދq�L�� ?�=�[�=��4X�(��%��| +IO��AV�l�G��TA���6��W��PM��9g��qrJY�!��lY��s +�}g�e� �2��_�F�T=�%���L�G������k�B�q1�1E���+���.z�d�pQ����,�$P> l)^�q�m%� �Ÿ��`b��;{�Ɨ+�e +f�U�C�p�Wci����{~v}�ZEB>����!�O��߁�v�3�ؒ �HM1=�_����ܻ�F瞧's}���aoX�0X�V�Rܦ�:_]���̲�?f�U�n�8}�W �<8�#e���n����."7E�(ZIDh�KRQ�6�� %��m�K�� ���̙����A|r� ,J�!� !��5���W�k��&��4Tp )#ZG8��"ט�4�$-���(�[Q�X &��1T���a55%�J-�#�B�2j]��B�\ �� *���Tȵ�Ei@��.��6�����ޭ�U��V�-C�G�m�o�3�ҁ�v7<>w�Y*���)J�C*V�Q�S�n��|D_Z#bikĥ"�>�X����ȷq\�uD\��PE�S�?L�nf�����c>q�Z��*�0������d��m]��(�ZQCy1�h�E�]� i>D�w��(�$0MB��$�d���?��yrw7�-�7 ���j>��.��Y�[�̾X�_������T6 ��ZF1�ғ��������Ҝ��/*R � �b��VTk��g��'*m���|�\H�>ZSmC-�`q��*%�IeD�ܐ"�ݖo��/��t�LZ�iQ�FS$M-3�Z2�p~$\MS��8ڟ �Rץ �W΅TB�2��0���GO��ѧ_���-�+�"��Μ�h׋�/���=QYh�}�wM�v�\}�a�Д�g�������sw��gEVt���8�+."%-��l��E�J%�7��gB���r% D�3�)��J�1XE��E�\%v�4•*e̜"���>�2F J��Vr��w͗�U��,Ո9JksD2?�-&痐pA�17^cXq��͸���!QXs� �2Q:�@��Ɣ��e�b�y�YP+��d�,\*���[�`��*���+0N�-j�R~�[�:�#���i�R�Tݠ��(P��j&c<�He�j+��%� }t���Z(%H��MŔᐕV�(-K�C� +���e;�C2�(fT��S�N�/�����&D�)�'H=�s����(9��U�ڮ��^�pd��z�����}��sS��%&nd8�s��`�-��s��m��aJ�E���kP`d{"5[���Vt �� 1���ї�n�O��L�"� �n1�Ю �� 37D�n¯�0�+��>��ж5Uڢ�Ѫ� �x�2ϧCo-�ش�V�w>w:��՝c�եC$�j����y�����U0�D����Հi툵�V +;��j���8�HĄXC��_ )�AYGQ���k��ׇm�h@��&�`�K� +�~>��V+��3��*�TYڪ��*h�y�p& X��U��E��~ �����X�By{|�e�ă<�J?:Ym�MsUZ��4�)6 +�Y��yR��r~�q�pv.��Ll�i�.�~�R��RFT���X�����Q�}��� i9�?<�B�h~�a���A�t_ d!ʔr ����Cv6��A��Ϧ��j3a�@FН�g�pX�n���zcW�]x����o,L����[��pV�_[-�IO�x�洧A�*�6S�#з���&�W$�4z��0��z����i�W�^���5�����F�q�Pb�C%�޾��ð� S4����1�rL���O��xO���� ��]4ﶉYqepG��u'r���t���@�����\�h4�� ���c�@�C�)���w�PB#�g[o��;�(�/Fv��u}�' P�6�3x��)�ъ�|m}���>>�����.�Px� H�������}�6�j�- �4��;��{�?4�S������m��U�-{�qӘo?C��|��3����yv���Vmo9�ί��PڏIII�D���S����Bfw����{�� �濟f���\ڻ[)a��̋��~�:��Z��gp+B`�RK�����hg�R��~�xi��@ ��5`�;�v�7 �JD#���g�"ܚT���и�6!�!Z0 m,,��#[9K���r� "��@�]`������R1>�.�a��1�X:Ȍ���� �PRh�@깱 N��#aC�40���(�`2���2iL���m����a���I�R6�.6��:*�e� 3�X=i^0z!V������;�}���!0�DI�F�U1�� 'fF]����o�����?�����ɲ�-8ᶱQ�,��np}3�<�Z�s`��TZ a��$Jb��Ȩ��(&�ԐY饎Z�v% 6۴޴2E� ��=:��`|o�ƃq > &��>L�����W���f ��p=�L��F�p5�L��÷-@�c�����"�I;�����-E�\������Q*"��,��X$h�9�CPr!=��t��rJ����U1Pc#�˂)��H��P{u:�T�����%Q̙�2�:�r��ަ�����< %�a��/X�+ �Z0!�N؃�rQl1|�+���\=l��'�$h� +��P_��'��.Q�t.���-���Ȃ�0�i�g��1_-�bdy��DR���B�dV$ � �-�m7��¤�S6A,���O�{��6�sh�:]�����<{�Ms�䎾t�B�x]4o��������l��1�7T�x��"��p�A]�л��R��$����L�-���z����6v:u�p��֓V���Q<���2�~���A���/�uM���P;�-{���p)(|�CA�|Fö�� +��ݲ҂��*,��99h ��������Q�\��^��b����#�\h�Y����V�5JF�X��cl��ڜ��P��o+�c��4�U��&k]��,+�/�M��,9-��={�'�w��ڃM>�-8�K�e�����a��׸��I������2���U���Z��9:�{<:t���[ :���Bgj�ח���Xmo�6��_q �9�;-Plh�4Y��� +���v�Pg�d�H���xm��p�$K���e������gY�������>\�� PҢ�B.���Op��Q ��j�ɘ9��� +%  3��~)��B��y�f�3�5���e��{���}�eH�$�VR�=���* �G\h���5#����^^M��!����~B!l 6 +��!R0 ScBFJ�n#�i�:�p�-�X�T!I�Xd#�+evQm�x���*X�� �u��!�%m8䇣Cس�s�)Ww��8�� ���� +�6�̂��4K��y���#�?K5���P@EM3@���[�=������H�Ÿ +q�rrv>��<V>odBƀ��s�)��0��ׇ�[̨\���* �Y��6��XJ�C�3_v�J.s��v�p�-���i�����'���8wmQ9ɴ�H�%p[���Q�Ũ}Wi���r�OP�h��� !�@V���Ul��Dif�_��i:h� aN��bh�n��4s�6��jel�d-�0N�R�1G���7-��p�m"�Ρ �%�t���`���L�����TU�'V]T5��G@�Uqo�JH ��B��k�`��z� +P�%�!x�7ž�@7�����!@�� oX[��;I��l�)E��Sf|L�ASQ!Bo�Ͻ��Š�J�-���v��Ek�����A����� �bN�|Kz���k�|N���t�B�4O��X�����ݛ +�%�Pr�Ʋ1Z>[$Q菮V�qR��9�&�m>&Qb���U���b�\�,c��a�8�C�� �I���z����_���ʤ���"A�� aZ��=���p=͟�D� Dn:�3�'�;�=�|��J��d�~CC����^�w�ie)��nYA�X�p�׫�gΠ��v~��,�bа8hZ4%~PY�1�}C?��>�՟�~���~ ��ރ����a�4�jY�͎�k::`��uֵU����$i�^��׆A�B�l ���h�0��F ���y��X���l���B�� ?�������H�P�k��kU�]�ϩ�sw3w?x��D�|8�r=ԁʓ�]��,�� G[�޲X~%�Xa@喏 �rAA͓�ƴ�kZ�Sj�l�f��[� O���N�M?��JS�u�,�'"�(��&��!��61�����Ǻ��6��[��m ~4;�p�<�X�Dzs�9)�͙�6h�d�I= ������#"�a>���չ���#�����4!�X} +��\���IEw��4de� ]3��x��Q�6��5�������l���0����>}�>?�G?,J>���������� �Z�-�� ���{% +��۩�q �R�ߙ���S�t��?����^��+^~�W������E☾= ���m�����k%�+��.����xum��m��Q������zώ{��U�o�H���)j�� +6���4�V��:���Tm챽����]� ��;ڵ7?�+E���f�ͼ��z]u��8�/ ��q�ES�Kr!��%�W2W���Rp#բ6\ +��dZ���w�9�a��Z6=��}1��HJ[��ç�����CWl ! M[���j.�Ȫ.9�C��61B�s�D�l���l� �X���1��(j�6d.�P�<��w���y|��y��c>������(�j V�%Oت$���t�r��↋|l�ڋ`�Mۢ���3���h0�1�x3�g��f����4���Η���k\,�og��bcq����E�1����)H��k+b�)��tGO>���O���gjR�ڍ)J^q�D�-􀛟��%��U?YK)K�8��E�12'aXE����oۿ�й�ӲQNSQ��@lT��n�m��Ciy�~�{Umwâ1uc��K+�΃��=|__<��\��y�_*絒5)�����q� o�:1�c'����F"%�(��8�A"C��}��[h���/���& +:���*�ސH����<6�ZIC���Qϗ��L)���p���3 � �e��8�8�ñ3=y�o�R{Nхs#I`IB��ݝ�>7���&��}��Ҹ�}�֑[���?� ̖٭�M�ٔ��j+ەF��O}deC����b캐���TL��l��L�j#�[Zc�*� +�����+~oхp��~z��x.���1�:ڬJ� kD�*s�Ɍ\c���Y>lګ[n��U�oaO�4a�����={x���鸏61�L��R�~��)I�ܾ³��?��k"�?��>�1�]*c' �j�Z�~�����J��7L)cMii��S�"oo#���'g�I�����IO��<�A��,��W�n�8}�W �<؁+w���i�4�[���mQ,���F�4��T���!EY��{�@����C���"/z����%���q�e6G���F ��Dc���9*[���X0c�8ox��`V9�`q��T���F�T�La0]^�� jP �4l��h�.�� �E`�Fܠ�&X":���jv~)�p�q�@�m6�*�?C�4�$�� �2Uz�!�ƌ�2�U��<�-�J�69/"���� �o8�� +���Sie]c�QJ�Y�6w�~���:�mA* ���u�� \B�6��L�]g���>�FԚ�̥*mf M?����x\UU�\����8�8~3;��//�<���;)�����Xo���1[ �*�k�#�Pin��F�6��6�B�f�\���%̖}x5]Ζ#�0[��x��ӫ��|5�X�� +��׳�l1_�����m6=�6G x[hJBi�TQLZ| +1[�>�c���Y�2�Lݠv�Q��pc�0���n� A;��)yY��3��k��p��75S�cVZ���,��V��o�C���Cv���&���a�-����Aӭ��� �L�! +�Qړ���4ŨM�� T ��?�fM����AB��l��~՗�^����9�,�K�d �6��c�mXJ�������`�%���88�p��Y�.� �gWJv�x&��GXNz�_���j�%����s���������m��) ��k��A�5L&5��wb�@yϩ��K���q��f��L�{Anr����1g���yefs8���{��7��o����{ +�����W�������0�FЧH��2�Vȸ�*����%*�����R�G���E��(�Z��gd��]�-��}���{ ��[h�|�e~�z/�z�U�n�8}�W�d��ǶN� ���^Dn�b�(hj$�I-IE���)ї8-Z�Ϝ3�3�7o벎������+i��B�%���JUQj�m̒�ym���b��<���$ e���&5�%!U�m�&\�Ff�1���'hdFJ�C+���]p-��UU�Vh�Ikb %����bzy�\T� ��(C+l [ +�V�[�J�e�p�Y!s�W>�T0��b���Z��j%iS�:���:$c:��*�Uӗ�Suߌ>�6��� m�A��:8y��+��T��-;�Sm!$�ZՕ`�{t_�&F |�I�� ̗�ﺁY�v?@im�*Iڶ��O8V�HB�ɇ���,�:{����"c���Fhʰ\��u%8[V���n�~P^B��� +Y���i۴��0{J�� &)���&�4��t�����'77��bz�b~�����t1��R̯1�}q�?���#��%i�}�]JC��R�����SK?'S�ਘ,V +uG�oFMz%��k�d�J����2zP[ؒ���[G���B��#.�z�$ k�*HZV$�7�E����H���ĮF{M%Q8�� ��b�9��ա���U�� +��֍��,��;7{���x����=�y�?*�V5i����,pd�ɘ˒�N��E�%�0�je���ޔQ�W�����!�}~����Ke�C�ڮ�����>|���n�3�YS�òr���� ����G����vs�v��`��a��(z{��Y�sڸ��_q��襐t�3�tC�2��tiwgg�����H��¶��w�,c�W����b��O�%���0��^��� �����0.�����w%���8›�p)4xӺY���{(4�`��i�� rbfL!te"|F�Pk�uH�� +�@� +�R��'F*�R���8Eat`�h��o���Lxd�}�S>�a�M&�fR��D*`��I5����TSk1* ���QO�sŃЀ� T:�q`H� ��1:��5�2q��v`4�*M.�j�@̈́��ȭ��-���AH��\:ࣇ�.���8�Lx��y����� �c�4`���"0C����Z�٬ɬ�M��V�b�]��t^�j�d��Y��r�m0�Cħ�ؠ�ĺ�[�%�1��IT�ORF����EJ��#���]���۶+�H +1-ec�Uɒ`T�4�i���$?��yU+!/0�NXn]bw�§ˈ��U1�rI����Q�9L�#�p���#�M�Fq�t���n�4�#)�=��g����'�d*`F"t�F�(�bJ��)D�ӵ��4໓��@�Y�� �ڨX\�Ko@�y�@|4Y�E����^�i�1Ŧ,`�����������f�������;�/�����n��䛏�D��`��a�g�πcDB5��։�,xE�@��^�#UnV,�؁i�A��C�nu�]����]�;\4ʒvs��~|;\"��S��Nع]@|�u�۾{�k��6j�S�AY�{ ^�� �4�4�b�6F3C�3�\1�7� ��<Ʋz�%v�P��Imɡ6��G�=�)���E �� )���p�Ee5W����b3�{p,�x�'Qd+Ќ/9����V*�;��>�%��:=��=t�H�����k�� G��%nve]YΑ�q,��eA� {#��j�ycK��,%g�ZPu2�ΐ����f6���Mm+mU�ƴe��G�·�o��^W>UȂXI��%��.\[�d�V�U-X�]{j� �KtD���������9��eծ�TK��P4pY-�&n4�w=|;�O��s�  ����P��[ͩ>����)e��R]�$��j�* �u�b�V7���+��NG�����I ��h�z��,��� ;gЕ +�̻�1�k;�3��<�f +�&~;$E>Lфҧj̅6L�Lz [�U\�=Ʀ�)'Ʋ'�R��q�-K�Z����� gq�u� ��“�b���}^�L�� ��\��3Y�3��7�EKT����Th%�Ar�Kڂ�rォ8}i���cOC�̖�gO/u׶��t��W�\�U�pڢ��z�>�k�dbhfn�����#��$�<:yY(����2>�� +���(@3��k��%�@-]�ԭ���������� �V�/'�B��?�zą��=���P_�έ�V3�œ��-p�-��0ҸE�D*d^NU�c�S�?��`�:+=�_�,S}my���i����kkʬ��G#��V(� g��VαBv�y�*=�K)���u�z��R9\�w�8�5������j�nv�ֿ=Yk� ���{).t��U�c� ���p���7�]'?\���\�$���Ee�_���0�uI{ن�<�� ���o� |���o��<�D���cj���Dž}��i_��HWƑ�0�1����r3P��\at�I�Gvj�<<�\<R��� ��-�bW˛S��J�o��6��v(�|��i��o�S��ϟ�ߞ]\dWg�'7U��v�ܸ��h@55� S��#�zTݔ'��72&G�z�`�>n?V���~������ ���۷�]� ſ4G� �˱��'٧Х�tO��w��|�J�o؞¹}��[��__!��am��>[/-2���C=����+Gv��`ɺ��:�߾*,�dv�u(����l Aa �D���-���u���g�"˵g�\no��[�IP6 B�.�����T� +����(}O}k@��Q�� �ú�k�m�׆�M9�v�Rb�.�w��p��T~x]��\{s7��_����-�Ԙ��d�P��HY�9�ˢ��rR.p$�\#���_u�������՝*S���~����w߯���W_�Wp!R��� )���-~��Q)���*��̘�Ќ"������g+/8ܨ�]3��Be2aV( ����#�d�5(�q�ҰTڱ�b�Y�!u��5�K.��pN䯮'���a&R�������.�.�����a�4�$Ț� �L�% �5�3��c��h1_XPkɵY��`�K���G8�� +6*�K)��oF?smp���0� ���O{GOh��m@* ��u�b�� $�j�J�1����y �扨)� -Ԭ< ���XX�:�������C�磰�ы���W7������2�ƀ��Ȅ� L7�V�T�l�rH��Gj"� k-���g�`e%[�2@I`���� \�������Mo.'�~=�7g�^�]M.�o��<����rry}u�pv�7��_�W?D��]p ��J�"���ɓ�5�V��̊�b&bH��gl�a�n�&oXq�k��R��L����ڂ�<]��=��~4Q*�Oo���F,�jΥe�ш7�_k۬�H���i���Ap���ϴ &��9 +�<>�c� `�խH8� i�����Q8 +`�B�'>�K g�q���S���p����ٌk.-������o �DR˙Җ�Q�H���N�B$� �.V ?�O�<לYg^h������,�A�ucGO<��n�% +�\O�v�������8��e���{Ac�����'u.iYh��l�BŨ�B�>�,�����,�\��8y�9{0���-��*U:����X��}�:�5��˂�{`�� �O�ҧ��]��,$[��&�cnLY����~L�%��?��3v���'��J�m�5dǴf��qF���{�3&�Ls�yc?��N͓�Xe�W�*sf[WYf;e����������Y�Zs �sGo }Z6��R4!��ꨔ<�N�;���+�?���VԠ�eϭ����q���aɍasޏJ�UlF,�;~�*+("S �>K&3�R ��"{���I��������w;R�sw�y�n�.� +#�)��sXs0�B-�Y�#��+� #�h_�& �Z��u� +�����_d/��i����!��_�m|I����0�[L28R�dg2q��iT�/?�"hN,����ߍBJ���;H���ܷ,��M�����\� &�Uʙ�s�)��4�}#�^��Br>�Lί۝�!�6�V\D�1�/ $ʭ4[���$�O�.�ˀ�r�1Yp�g�;����#�2�Uhs.�O@�0M���6�XO�#�i,���C��(?�ݡ˸�:\*Lq��B�m��C���C�� ��1�ad��tH�.��ǃ��&�|��P8~0�Ʉ�E�\`�BN3�{��1b*Ra7COjD��n-L�^�x>yw�z�����[-8;2�'�*�7���,�M�'��Z+�����W&\�&^�ε*MH��L7~\���i����%��ӥ�����(+�,�� JM�o�NY �CGmX0S�%���H�AfBA��TfpKn*�E4�y�89)�U�2U*��Ku�dÌ��7�Aϝo, ��R"��.B71[�01 �7��47[H۵��1��&��5�� ��£�x�Fp� +� �qx�m`�,��|���o� +21���AT!B����� OS��?�Ș���/�����_��&k��\z���ߔ{tQ&�zz��Ad�ǛD�����7TN�&؍�oUy�r�vs�g]��Ϻ �uY>�2|֥|֥||֥y|�����NX�� ��@VS�5S�������.ӹ��3>���fLHۯ�(kQ"DH���|�e���cU��� ��L��gx���[Y�*���2�<�2���!�+ۂೱ诣ڈ3�"�+|T���e>�ϙ��pW6K���#~�i�Ϙ������= ��`h�G� k��a<�v�tj�(n�y�&�^c��r�����4�V(��<�*�%�c���+ڹ�Z�ė���u�P��_�|˰�=Ɯ�|˰�ʘ|�i�܉�!|����%BBDž��P�V <�ߣ�$�E����]e�T�0ˤS��wTu�|�Sб?� �\������/JC�1��o���◓�r�_ Ue�B�@0|C�/���*�Pv��@�o�^���U� Vi-[�y��^�M�k&-n����+��b�ll���*,�9��5� k�;��6�'[�(P��S� �����g��n�,}�,���%]չ���r�V���r�� ��}�A���������~�1�xQ%�#*���"H��{1Dz��N.�"�M���n����"�vW*xn�9U �:��݅ o3Ț� @�w�\r�:�w����%�s��w�'�� r�DžūY��}�@�X��.A:��~��7U<���ۋr�JJ�ZH��)Mw�m�}%'δ.��p�򘰏~l�!��kK;\^�Ν�t �nz�����YmSZW|�b��յwĜ0e����X������ tDK�m�:��-9����Ix0��9�e���>�1�s�\*ͷn��wsJ~I�J0ka��k�B� �\?'-�B&�>۪� +�_�T&��O�݈�yr�];5�X�NsÆ�eo��7ѩ�𖥮�e��|�>7��R����C�7�ߕ���d�}�dy��S��4�'r��'��<|o�x� K��9vzF=���6�~�~9y����7�r�-K�d\�x�I�e�}����yƟ�o�C�����A�D��#�O-x7�|%~�((�x{-� �>1$���YF�:�mz��%H�b�T�үUS��`A=��Y8���c/�ͥ��'���u��ſ�w��B.�pC_�L�~�)�z�I��~��N���:w �S���{���;[E�5/g�-��Q��/����)��! �Q?ґޤ��nü�Cm�42 ��'�� �`��i���w*^�N^����*�6�S����16��B��iڢ4ư�|�άR��?�_�/��?}��2�l���e��3p(��ȿ�d����Z��VK,�}w!��}����ED�l_t��S��J���QZc�R��h�Y*~ �(B���b��L�� M����[���x����a��6@gs�G#xJ��l�Z�1<���� ���PB�7T3�F����5lG͟��1<�a��gv �����a<��������;��m[ q��u�����\�v ����n����҄�&�m�����Tq&���Ŏ�� "���9�f��Ƹ��2��@8�a�DvFՎO������@���*E�V9y�Pn�]����� ĩ��%�.�4��r8u�ŷ;�5�g�2(��X��mk�<9)�棻D��f,�h�QR�!GX� �1b6�,�6c .z��`�=�n!a�9����q��h�� +)g�t ��1�a�լ"���h�o��#�?��߃�d�9�������aXG!�40pW} �,��I�=ک���}��U��Nscʌu�w%����;hU=dH�/�����`�|.��茅y9`?+�x��7&7~IEL��V Եv�+��17�����ImI�%k�.X�j�g6�ܜ�P����U�����\�4�agE�zC4�D��V�A ; s�nYZ����R `�،3m0X�n�ܰ���;^�4�0m| �;-N���n?e�+*��i��T�;�� <��Z$�G%�!\�t�� +2��b=�ݶT"� _���ڑ�ݒ�Dž�t�A(j;��W���s�]�m�϶Hf���.�#��+���t{�O��e��,]]����m�[Iխ��Y��/���餦���D�m��s�[���i}��m0?�[^6��� �J�e�C$@Y�2�1; ��m�`P+8 ��O���*�{�\U�Ѿ�aq�9��ܗ���=���PO@&�����Jo�U����9��p���,K����U�� +MV�E� ����=Q��ߧK� ��.ۻL��C��X� +�މK6+�]�lV� ���,{��D��z͛iy7Z��|g{nP���cK�p�FN��!_l3<���~�woqƯ��+�Q�ZuUq���H�ګ�5zQ�����,Uš �Է�Q���Ϫ�5L��PCU��2�7 rEt��m8�E�0p���� �oS���e�q{s��R%b&��?�:��E�oxu=�2�Xew+M,�� �70O�m��3���B.V3ٕ�'�JOF�U�� Ȗ��á�Km[�!�[��'�n�&݌��}K� �c5•UU�5�v�zK��jy��}3�ux�����M�K��5�O���a�Ÿ�t��*�����ݣ�b4%�]��gkW��7��t����;?賚���H�>����鯻(�>[ +��l�5A���]�sA �c4��W�տ�ޞ �A3��'vb�/ +qN��4�;+|�� �:�P�Է �ك*�]��xK�LKQ��=r��;�����[�o7���b��>I�,�r8�;v�&�ո�"��! j5�ج�{$ײ��0Cr�W��܇G@by���o�C��7�2�?{���R��J���L`��{�R%F��F��Fc�BC cF{��~�&g`O�HE�D���] �p��d&�T �/&�ȒjP �l�a��[W�if���Q�Ј+L�L��������0�1ϟI��� ��.�.����`�4��L��"�̕^1#4Q�B���t��biA��f)��-�2� �G8,klT�E)I�1��Q����vɓ�����)�^� $�Bf���ajA&�UK�D<�K��1��'��d3, +�yyK������d<^��#� ��^�����^����>z>:s~Jb44�'�g0݀H�XFb#�bMdC���ZZ�,�4�'(��PZ`Q����`�_L�j��]L�&C�������nᗋ�o/�o�^O��-���~uu{us=��K���7������!��KԀ)y.q*I�8+�S�����ɤɹ� �" ���G�A��^IC�5 ��r%-;��� �B�|������t�T�3����2�̪&V,�c~��f��L�\̨L�O��B��(�j,�J�R�GS� Yx��j��"�#'���/"5�s�v�2 g��$7�M3�����/2���L&^�U�>�6��Ah-6}��i����0�bŜ�ѹJ�vG�J���Rh8��Q�k\x��O�P�7�I�g��S+��Á�blW�x��T�z4���� ���W��>L���T����eu�AM�AD��H8�c�^�>ȯ����<�� +�����=j���H��ߙ�*��+�e&FL���wZ$�>&m���\�~���=a�HZ�� �?�m�Y������%0dJj��w�O%u\�D�e�{ȏ�V) ����X&�e�s�XrEr��s�z����,�Иy��,��m7�5�Bϸ=���<h����v7�q�ދ�"���Άu�Gx䒦H˥Edb�`%�t(��ă\e�Bm�"ΐ��QD��.�Z� +# k�u��� +A}��B,U< �C�H4~\��=Y�c�p������~���ZY�� +J�{��'Vi��ˆ�f"v ��1�E�3�qĕ�np ����LJ}�*J�K��^��qL�dbP[�灔T��LqNU#-ņ�� +[�w?;�g�~)��� ћ +����Ρ�����L��JD�k�J<����MPdV��wΨ���o�əs0� ,�VY���A�;چB���l�((j�De1!�]*�T{(n��s��N��P���kW���Jrz:n�{iXm���+�k +JfŸbD��:�LkL���2����Re�~�GЪA��.�y��� �m�]I���VbΜ�q*7��`Z�(#��V+�֠��2u���[��&�N'c�*��7�8�f\ 6׫O�Z�%��W>c��ݍ�1#U���e�,��)b�1)���0ù�b�2 �EDl�Rإ��(�����*'�9�C�`fM����d�8�3�~����KZM}%��b+����I���0b���`�U�.Y���k�M&�=i����ܹ:ր�:� ۧB�U�a��ߋd������!�S�=%� +��??���$0�5�rY�=��5*�G5NZ�R^��M�l� +1��n89)���A�i,#�g �$���F��,���z���&�`+`���1�b"����L�>��Ɔ�p.�:���� ھK��Ih*J�w=�z�����Ww������7�1Z�����B�T3q�ֆ7VA��+%���]+�����m)�W�w���d����ߣ�����ke�����)���:���I������*9�i,%8�������~�1��"��[n�܋X�r|����[�k����D&��$Yѵ?D�]=Q��L�卙�7�~�EF[�Ϡmq4�E��C����Pi�(������|�z�M�f�.("�2^;�����a������O���l��M��a����K a�l�m��W�Q��o�f��1C�WG��R?6�p$��6 Ƚ[3��&�����/֡ܯ��x�jy��� ��9`�U�����Xa؊�m���޶�>�ږI�;P/���#�e�)���D��Q.�qG�]P�b��:7ki�%T�6=?!��I�ݓ,W%��&��x��� UIYO[�>�m�S"�$����*o�j���� �� ��ᩣ�_���x��Ԓp�(�C2���>/���>�~�u�b�m���zj��9h���T&�Щ�Z���%� BOZ=���rֆ�< ��؄�+'�<��j�;B�5҇��������_�����������R +����gp�.������wA��1r7�Sw/����џ ��O'�өo�L�ϢJ!� :�%<-z�С%�ܳ�/���4U�Ч��6�MQR��˖K�B�[��R,IV�m}֒vI�'�.3�_+g��gk�zɭΰ���� }�z����4۹tfa��>�6?����?Us���L�g3:�F 5cc��m,�]]��?z�ߣ�п��>�+9۾;%�������� �G-��ʼn��F� )B�0R���X7�\tbIު��=��D1Q�D���+a0�H�1�r�l(��Ɍ��E*�K|jTa�U{�hM�n'���W2�����8��=v����W��?�芇��Wgߠ_@�n��������R w(O�t>_� 1�| �i�b��|لz:�g��T��T�X�S�f�[�6�;�^�� �A+�5�E���% +KAyrBc���_d2Skjgt0�ђ*����t�����3�x-gv [9]`Bg�\�pɋ�Q�4�:��mW"*�����tCG�����ř���t� ��0�L���H���ݎ� +N�p�ya�e��]���j쬫Τ�7v}|F�R��{:�ɦ8��w<�~'҄�!9{��~�3�k�пt�����P�Ĝ�#[��������rIw�0�6L�xW,��T�����t��{_:j� 2t�C��,E�b�3N��h�_� �/!��j�rt$���\Ć\à�g�J�+�)�QK�EyiH��3��'>��͞�&? ��nG���z� <��H� �S�����㬢RM��� �I2�RRm���p�8��)xW�Jx�b�~TS%Ǥ�H��cJ���S͸`P �@*M��,��Wr���o��=�S;�,\�@qp��߈Y�br�8ع�,_#�L�2�6b+����'�o��\������m��`�*k�F�Gv��_��� +�������ūf]�-1�pe*��^��.�]�bn�=�SX�Eռݜ� +�����~}��b�����4:�}Fh 8����'����������wi���d���;cu�I�P�4�$B��ˣ�`P�iz�V���j������cBD�j�'�\D���9�>{��% %�d`G�Q��[wO2���%,Z����i���&y_���⡍���}�e{��|�2�L�ՍA�����(*]#/E�C��CUU��AB]����4+L���&Q�҇�Ï<���~���F� ��,��v���нJ�HH�z�Bo��^^Dq���T���i�G�X�!I�P\"k�-�u�,���ff�j�~��ƏB��:3NK]��" �.lRlTC� �k�'nǁ&Y��J��>9�T��x_��!��ozC8.Q/�`vl| +��K�MY�*T�n��?��S�/�����g+`7n�����?_B ��П��]5I��m.v���0�+Xw&[5v]�ى��9���@v��E89�*w��,RZcd�M �V�՟��C�:�ݤE��=���~}�����������X�����J��O{ߜ���X[o�6~��85���:��`�eҤI3 jtjbO�b��ұ�F&�$��//�dYvfg$QD��w.�?�˼��u� �y�Ka\�`��W|+��~T2U���Rp#��z�K���(4&`�%��Y�D�ʅY3�p/ �0å������HP�H�R�J*'[�yA�3�X�W(�L-��d6����,}µ����,�,���T��� +X�p�2�b!��*B� +S��5��F�ti@�*���`F�L�2�1b���,�)��3�*M&����YZ��_��/,��m@H�ƒ;�s��. ��<�LĖ�[��1��3�s�0k +�Eu0C���4&E��zȬ�C��(�}�ލ�w�o�o�'��֠�?W��|,�3�y���5E��� X+n�HD��a*�T亶A +`6Fݛ)��]��f:������ɧ�~��p3���0y�����h6���0����D��h�~���sN�%M9y� +���'�c�<����`)B*�P���Q����j`"�������Dڰ-d�u��Gb�Sj&ef)��o�I�3��k[����� �ܰ�o���6L)��Qo����/��i�������]Y��h*��aQ�;���8���A� ���k�ۥW�O}\���� +��Ur�U��{ɓ��l��68�^�`߱zi���Pة�c�[�N�\H��70f+ � �aKw��J��?c���h� IY��Y*����'�1�7���wvb�����ȱ���������� �� E�s�FR��q��\��'f�Ի��]m��z �Rލ��)�S4tp~M/��(�J 5R��3���r]��������x]�����e<���Џ��a���R�$]�W�b�n[������K���h� U����r�� �Ս���^�ίV�Rln�%��*��m7~]]�6vNݝP^jJ��9���ow�1��״�n�5-�� W�B)&��|�- �v:�5K�/CG]^I��HKK�����KgO��sY�a��R�N���j�߻ً�"O�����A������tz�a�����Hw�=�� 0�t�Nw��|7g�����S���%�0+ξԮӂ���=��*9(wWG����o�ޣs`y�G�x�[�����_9 �b{s����}Uϐg��7�>Z����\`�����mQ����j`N�=h�/޳H>h~%�Ծ�C�v�z��x��/�Yoo�6�Oq����˦ɚeɳ��!vW }����6��H*���?�#)ɖ�$E�%�����?��`�����+�!$Z9!�Tsp �7���� �N��N��$���~� *�)8��Hc=s+a�t�R�V�=_��P)� +�ZXj�w5rZ8m �A� ����1"��L�/.a&3�O��t��J�������fڀHSI[� ��i�dA���\���Lt�6r�p�W +�]�|0!U�WQ��m���.�*5��1��K*�8x ]�`�����w��K��+ ���D/�L +�0uЮ�c�g`���1� +�Y}G���p.7�V��`��̇Q��o�����񏃷����Z0�O! �0]���L&b�!dbEdGqH+#�T�>Q�u7UF�"J��@+���1\���������'��|����������r 7�pq3��zr}3�����$��^�~�J�@��RB�dQLk�e�h ~�9&r&Ȅ�b�0�h9����Z B��ɥtT�H�E�|�ErO��&ZgL��!D�p( +�稜���*��n��̒B���pL "�/ +��RK!�`��Sa,9x���\���+4����D�xFw�!k�L�D�yvp -$������?��-D>OC�R^�UlZ$���(_#8d")1���ȵ�ł��A �3�CuO�s��J2�a+cذ�� - �Л�p� �)}#O_|� s�"��b _ �y��Q���,&!�r�&r�y΀F V��3�����`�0�K��ß�Q�>��M�=Ƴ����wvO0�zl�ǚ� -�(�8�� G��.�)�L�� ~( G$44�iWZx}�!� %�v;�D^d�ͪ����,"�^ͣ`�V��R*���Td\ѐ3���q����/>L���{f��Lf�?���2��3}3ǯ��Ĵ�|%e����øZd��g軔��l�O�|�@�G~�V��Ni�o��D7Z���:�lȖ雋�ޯ{�2q�hs��R1`�6CK#�V- L0Hct �jH�2K -��f��-qo��ǒ?Ra�T�� ��� -|�O��(�V�����ro� ,�|��1����� 8��r�)��|%���0��~�l#MJc�4O!V'ь���Tَ4�M 4��Lc�t%�8|� �]�yև%|!h \���W'�a}Y��8����K����x���8��|��C�l Z��M�` '��s��5�F\�{ -u�ef�W���쬤�c�N�Vƅ�J���� "U,�p*�~�%�߆�W����s84������,�%��FS������x)t;��%�O=\�3{͎��ptt�W��G ��>{�s�.���*�J�C��)�}ʱ?}WɆ���������O������Zڏ[�����~�6�Jɾ=q �G��j�F�qZ������N�ҵ�5��n��汌�"��̹� ����2�҅�\�s�6'}�sqt5���l� F�Ę���ڀ�y&���来� DFi���7`%u[V����*B���ZB ��j�T&���Y(`�@�*-�jR^�h��5Ã�-��x;d�������1H�����!�Z��g�;@"�r���E^�P�q1YP�_�Հ��Z�s��ձ!������j������Stu�7�vX�!�'K�(�sL�A�;�I�M���qt��Ͻ�R�2��;g�И�꭫��K� �_Ϥ{�*���P��X]�k,K�ó"�B�U'�Ř�4K� 3�k�&]��<��"T�i�?�,f�������,� M��/7�Q��gG��|���+�����;�-���6����z��6m=�%�5 �S��3�E�Ns�"]R���Q�� W�O�� 8�q�=昩�i�o0���?؊������q'�a)Ә���*����}+��2�$�Xa�z%���'*˫G��Zq҈5�+#��nx�z�C�˫H>\D���H]��,��b�Rճ<�Q!�?�e�aR��' ��Y��0�E8�hGkg��1�^���a�B�~��)� -2����/��ث�6� -Ct"��I�����=���W��AeI~0DI�dy5<��6��R�S�07�~UO��t�6�6%�����PM?�[ը�=�n���C�@UΟ+篕C�(�P����9���S$X��*/q]&0ڙ� �] qw�+ �6x���;�<]ֽ����6lʝ����A���-{@o����������"�ԤRa�i>���~TA�x:u:�-J�ս�W����t���=��Bd3D� x% -0����s����Ձ�io�О���X���v �ϔqn�1�8)V��_�8��5�9}��Y��h�W�{,g�v��[�n��A�?�&Qn�'T%�[�N����C��Xp��}��(ӄ� �CNrh�#Io���E�Q�����7��8Ֆ���,O�]y�3:ۇ���|�*�Z������#F -�Ipu4�Rp�rk�TuA�L�Q'�NK=r���j�Щ章��.�.��x�� -�@�R���&7�5�l��;���6w^�*��4˝��b����j�auo�g/h�}��躥b�&���K}�g�� 0����۱f[R[�`�5��~��>�K`�ߐruĢ���g�P���3� /@3�F>��� -p�A�9��,��Zן�H��rV�V�#-B�$ �(v�&i�bER,&x�dj��H%c�Q_�W)���H�'���`�Z'-��P(�5C�V���0 �'�1� -��K�SWDf� G���iP��� }Գ� ���&�F�Ġv����6~j'Ͽm��k�'`W`qm[ $i�uc�m�:ϙ9�^�+U=���A����m�2���~�3˜91���{���suV�|-A��5{F�=I��-E�>��V�d�O�]%�3_n���ݥt5}g+�b����7+J!���t�.�p��vx�>��|�T���6���|�9Й��__���T��϶�1�fY�4�V,@���SR���I��0�[�穞Zb �/^�4�0�s�^�����5���'�\��q�hؗ�s�}-Sa?*eP�A�+Kգ"3(2�B��BF T� -mN%M��?� �J��=��O��Ơ!��F�i���ּ���Ҽ���O;��ڟvll Ow�Rf)J"�MVRBu�����I5��C<����+�]�/�q'�hT)1Z1Ȃ1���~��r\e�w��B�^Ť\��d�T~�Zk��Y�����K[�I4�7��U�\�� H�|�������5�� -�}.*�!s€W A��P��A���"�,D�K�=���^�3%����aÝ� ��&�}��zO���#$�Sv?�u��Σ|��*��d����J�)�:L�^��6��У]��6'�!�\ХV�ρS��vwP�a� dS�<ņ���m�"mi��vC^�ՙ�B�駛� -#�[-���W�)�[r�1O��W�$ ��� �2z����4��_�dK��zq; ���yf1����=���j{��$�K���� 6@��e V�o[`�vܠm�2�Uk�j�T<֧�JW�.N��k��ixc;jk��>Ȅċ@� {.�싛KPrM�~�|�U�3��1�ށ}�,�<����S��>�*g)M��"q�����D)���C)�í-�c�p)�'ŹV����-w\���� Oٱ6�3�;������s���,�b��a��0{)�1��tI����H�k�������$I��U�xn�fx���F�kی�3��]78n��M�^�2f:WV #�N��< ��+_��Ƌ:Ϳ&cλ2�G.�j�!'-ł'� �z�|��4`�mkuO�<� �Pz8�T؈�)��}��}s�о�y3.�d�EK:uϊ$qވjާ�=6��9���$M�J\�Q�G˨H�k:�1����]mL;�.��v-�OG��Xɯ�*�����^�\_}�{���t��X�n�F}�WL � E,�(�N�F�&va9 ��0V�چ�ew�����ޙ�P�.v��S���\Μ���r^%ggGpע��*i��B�`����3|,���`��Q��ߋ�K�3�� K�⟉����U%3f���N���?�%9i+ ��iZL+� -oX�9_piM`¹3s{?]� ]$�L�������0�R� ���2AG��� �)j�3�Qt�*�Z�s j%�6sQ�y���::c��x,ƺVU�uHF~GC�O����t�wϝ���A* ����S^Zt[��`2u�!�� ��s0��T)`.P��0K��`nm9H��j�g���yCL�cfo&W����Q���ߕИ��X�^�l��lEt�r@/V3.�i��f�6I�.b�MLs5:N`<9�w��x҃O��_n?�ç�����~|5��;���<����k�|&�_�7?��c��(�Xj -=�Q�5�} ��:���b&R O��9�jɵk���0TZ�Nf���T�Twb�]����LQ�ˀ�$a��a��$q�b|{����Lbg���Rd�f n�� ��� 2^�5V�� -V���oQj�Om���"�C�wv��Н2ٰ����b - ��>y�}�1��x�����cx#��]�j�O�Fs[i�-�����$&�~}�ufV]V����H�5�6�'}�l᱗�c��4܎�~�������tޚ����-jҨ�� &-פ�l"���t��}� �F�R�*��(����ڢ1#Z��n)�#�`� i������8�3!�JkZB�����:�> e,���Z��0�d!�TUN��Z7�vc׼W��Z��`;��֍Ǟ�?�ދ ��W�D�۞�q�yd �hp�gZ�5�W��Ez -�`u�x�����vF���O�:k�P������*�.V���e|� U� ���dB�-J�ֹ�P�n����O��:���w���6[�˃���t��K���cV���\�B �'_x�ٹ����ack:: -D�l� .YQ�⎍��]2jL[��/.��n�H��x�b�,GX�X�C��#�cf��r�VՏX��FQ�l�w�/�f �7�� -���nqj�޾X|�|��9���e{���ܚ-3a,6s� ����s���zDU�/�gW����?��t������p'I`4�X8�Y�ޖ5��R[�ak𡷔]r~q\s�K7��S�����q�枖��_S5��"5�6�����-�OGߙ�ƨp�c��''B>D�}a� E��6��G�I�K��cr�O;L�q3b lHe��B��/T�� %i����m���?o�J�Y���\#+�{�}n�"��=�Y�?٠k䛚��'����-�X� ,�#Ͼ�[�m>T�.;$����v^&��̷i/�k#1DJ��1���W�}Ls����n�sW�3����OG�� �T]o�8|��X}H�u�c[��1Nha�{E��Zb+�*IEq��߻K����a�$� g��|񪫻Yv}=�kX�@YPm*5}U��Ӎ5{]���&7:hl�Wr������O%��? -�:���M�rY���_���u�Z7 -pz��Ќ���#j��(�"�z��onaϪ_j?�X��C�5��`�g�3�����m�C��Q������NWu;r��﷕V�U�G�-�z����Yדs����始?� t1�^\=��`l��Ӊ�AQX. k�F�Q=uw܃5~�H�N0�v^-�:��Y� ð�(xa]���w�캸��E'�{Ӑ��֗^;�xw�X��kmp���*ǎ�j.h�Bp~L'ӒDn���m�xF���^/���Ç|����>,���m~[��n6�7�6߬�m��GA���o�@loE��&X�G�<�S� i���w��^+n�T=V��'��#�j/G�Yd��hu���y�o�iJ�b�? ��U�p?%$ːS_�ɲ�=�����G�c�,M�V2G:g�uS���@n�J �0V�Igʞ�1�T0��Pl��� ��)�CxLmI ��Xz�ĖT�F��w��f'�?��f��8����5��ɃJߓ�A�D*�%)�B�̸�nure�U�#o�?��(�U8=��b���0H��3�T -�?�׶o�IB�fK���ӑؒT�X��WD�j[>�4}�<�Ǯ;l'm�M\O��f·��$�O/C���w�P��F�_%�7��2���ٷ٫���Tao�0��_q���N�A|�(�������i�&׹$f�lgY����9qW���TE�}���{�wgm�N��� ��J��h/�V�_!��(�?��z��J{�����.�P��� %�VHzd��+��\xe4L��j��F�6c�{��u��Di��-2���l�O�PY�ϕ�@�|Eg����;(J��EM�i� D��b)l�:�i�V���k��R-ݷe)�*�qp����M7J9R=6c��%�^��)࢓q�d�6T7b�x�>�>Hl=�%bM[+�e��� �7#�ٱg �0��1���Py߾I���"^[&QbrA�]g�/�t���5:G���)K=��A��J�0Ԣg�Q!Ģ��q]ι����ԴH��������2�4;���,��p�n�l��p���\���y�K��YN��fMo+X�o��k��<���U��ZALw����m:� �+:-�� � 9�e���n��ຖ �֐l*�2�(i��01��c�2]�3#�@��P �9�d��T��+8�9�i��W���%�ΆL:1GFg`?�}Q�GZ+�i������,�A_���y�A���\c���M�y��BD� FW����yE�;�F*�=�k��of�e+�a/<�,��j�m�����G�no��ۀ2�s�v�k29{?� �Ua��8��_1Z�D�Z�G� -lEjO�r�O�M��!���l����q�nD��]���33~���ϗM�L��� ]�RWL�5Ai�MA�d��k��ov^��v��&�۫��#it��sN�ƔE�2��v:嘖�5� -��.�匰dG�H��j��:�k6�cER�c��?'J�c��f�zsM{����bt:��ў:���T�k9ZU���:�DDžr���lsp�(�΀h���*�r㏅�c��`۞ʈu/��#������"@�.����1�V26P���:�]�M\��J+�؝���}�����T���a��d˗� �y�$]��U<��H��(�N�����T�=���j�wR Pe� �꤃�Q�@�9(n�+��� �m�m���Ȧb�.)�� z�HW�}Zm�m>n����f�ޮ�S��Л���j�ڬ�Z�b�Y2߯�o��!��� բ(�#? �-}�|Ù�� �LѪ������P4�j��� sأ�!��K��y?%� �7)%�7n{�$��� 6I����^=d>�L�ߊDzJA���[�GW�&+�xe��B�8����P�� `���+�PM t���Y� -��\VF3c�e���$����}4�wcS�(��8��N�6���,�$bZ~DEUg���mݣ���Uq�)1k����:w���]��MҢ$<��<��混i����-8�+#ZY!�TK���?�� -baL����Z� �`�3�"���^�BdW:W��R+� �W]����V��:�Dg>c&繥/b�2CLPY��"������T&���x?*��6"i���=,(�CɩE R��+�3\�,���2��,�Baf"�R��2���1>p��z]�l��u F��@��Q� tȀ����v��y'bJ[� n�>�Z*� -K�X -8ﲻu��sD�yZ \+�u3��� �6= ��� Wp_g�A�����O/_Sѕϝ��B�\f��|"��1�ZcQ�ݠ��"#�ղ�ަ"A}LЪ����&܌��)��mx?���=�4�}������d2�F�S������b4݌�� ǟ������H�Q*|L3n�*��(�5>U50[�9�������X",�fN)f�4���0�|��my���k��������R>�� ���lKdh ���W�Ln��H5��1|�p�L�`�OH��!c�v�5չ��wg*͚!� -�N���y�����5Qޮ��6c� V4G��n����U�'T������HO�����;&5"��]:U�y�̮�b\�ԮzP#:����ߝ���Y�yF���ђ��{\��|e5=w�'��~�U�*F�Tؔ�A��?��3��V�Ӄ�p`� -$�����"�!�}r��t�x��y��*���ؚ���e��J(��8�|adA�?�lݒ�8B��,b��Vr�zfA��#��cD�~x5�]��f{_�̎XI��a�{��+���C�dD66T8��Z�8Y+9 ī�dB�&\���%�3�.,պ�Ӽ�R�9��qɇݗТ l�����B�!� �7��x�J�[$��g������I��'��c�pU��������M��,o���7o���b�R�ܹ(�u7:�V���˗�w��{<Ȕ�K#��D��? -���B����Kp@�(5�^���[�$��5;�a؁��Y�v�� ��"B�l9�M3��y{�T"I�y�=��m�r&|�M�BZ�S�ND9�||a�C|�'p$�v9��?%�w����/�X�+���R�c�t�D�Dݼ�6� _��wZ��ل��|.�e!#GrF��n;٭:"�i�_ 4 �nP:W*���o��f�8W�RxcO�|٥�i���D�Bs�R���ω���Y�*��UN�‰�����A����}ϫ�H�s&B:5�X�th l~�-����,pw���Q��G ;�,�Ma}|l����W���GkH�0Y�D��Z'�Jz���, f�[�3�H̺�]i���;ڮ�nlֲi���GCMׄQTݱ/�a)�4;�3'�\�%Nv(*k�LW�'ٵ�}���K#lKfǵ��?�:?�tX�o4��+s<[x�pا���͠+���07Mf�n;d��>o��m��ک��Q�C��Ɏ^>�~S���%��m����]�CE>X�v5�;7b��#'w��o�o��'���WmO�H��_1E�IP�+>�� mZ@�������� -m����o��&�N���Y�:����t�ݙy�gf׿�*���t��℃�g��Y���"�o� ��=W�)6gb���3� �&���d}{<���k�q�<�3��d��Y^f>Sq�Ao<;���>�u. �E^ċR�I�X(8Oy��0�\��^�'oO!@�d�Dz�C�XE�'����w����B�� �H52f �����r�X+���5aK��.��X -d< d-��eZ�f!b�� H�5��`2ۃ7��d6�O�����s�4��O��\\�ۋ��d>���g0�~!��&ӓp� C�BP�4&F��Г�@j1u��� �0�,,Y�!̯�нQp�ƒJ+���Hc�E%�t+7�%����䊺Jqm� U��Y�z�6���k��Хk[��W����� �l%I6W^DY6z�"/�N)Τ�!�}������Olc�!��z�K51��j� 5� -������#}��fR㎰P ��P�ߟW��y�'��i$�X�������s��VęEW�-D|����4v�������C4_��=�E�<�M ��A�t6�� �>e���\]a�8��z}��b�>�d�����K�S �&�WW���㘟f\3��~h4��Hj -��*əE!z�H���v+�K�Jar����G�M�5Գ)�EIճ�y��d,�5����Nj�qE����]�U3t�d }0e�G�=�¾���z�: -}��� e��R/w��ht7Ի�5jO5n,�v����%�?bn�kN]�6V��v�ϦFa�@9�����덺Զ3����å�R��� -��MK**���h��[ȑ@��x���՜w���8�B�z��g#�`�;I��z 7 -��GX���W�%8�[����YRV��:�7�^Cp`�L�lYO�VF֏v@�^��Zc5�B�n4E~�y�s���Q�qK� �c؛�Y�k��^�l�v42�_U�K��t]�jE�����tK|����yFZ��hy�E�U� �D��[sƗ�3�jE��*1���UӟM���}m_��D�ߥ�u���oU�'ھ�������'�H�z��J;��}V9���q�}���"� �r�e�D� ���|_ C���5@���\���l�{�o�e��T��n.|�ʊ|6�n�p��_m�ڒX����7��}�V)�+�ͬv)q��i���v^w��Xmo�6��_q ��R{��eI�Ig�u�8m� C@K��E&UJ����#�˶l�e�6ml�w�{��g�$n���ZpW"��)�2!� !�p�z�Y¯�T(���$��bZ�Jx\&܇T����y�g��t�4�+�I�� �����W�AIN�J�Ti���,�"�X�9�r�&]��F���vpq ZI�H�0�׈�J�C���� ښE $�05����!�>9�x�E8IA�%��Dĸ�-�2�ʍI��|[�u�2�J�k�1�CE��� H���=��)[�T)d /��x���h�4����v�{���5�`3�� -�ˀ�$M�&i������.3w�{���W��pt���e�ʈ' ��11/��h���hk��A(C�b�q�t������Dt��ac&F� F�?�������뷷�s��.Gp}�������]A��$ _G�p+�kr-�(�+|�m ��8%1�D /0S�?P��/˝c��Y�h5E�81k%���Ei�ԗB�w�S*RFd1�hJRP̸,�p���.�i�S�N�I@�No��>LHZ�W�e�҉V�"No,��JM;4�ȔK�>X`����D�@B7��1F8�Ѣ�a�w,�x�h�Yz -�V�,�"�1;y�A�4�� ?����R���%��]�H6�,� �� ��]���e������3�bz�r�Y �r1K��pz���N!Q��sHM�� r.w\H��[��:�_s���r� �h�)�U�Q��Z�cԔ4$� -��� �i��� 1��P�Y3� ��*Z�UYE�����B��*��Xml@ -\FT�80��&�� -�Wd����%?3`!}�/MR�Zm�f��)�Q�n�r9�� �Z��K0a72n�1�\&B���J���� -�}ŭ�&%6�A�ɛb�ٶM��>榽�*My���Ԥj������jK0�i���Kn��kD����A�]T���������:��1p�|���N6$ˈ�I9 ������=a&\V�|Q�M����I��V�%n��T���˹��s�[�~�x{FY.�B�:������7%L1<�f�j׃�Y�`o\ T,]@ ���`�lo��B��ᙣ�J+�� 7G� -󖷬�hS�X���j�66��[؝�j�z!؜�U���QD�&v��N�UN4���_�� ���� @{/�!� �-�x�%��e�%O���dԄ�� �6��Д�F�S��R�H~��)`/wN�"Z��lCsZӛ�[� ����7��{`�>�<�.����:B�&��[7Yϗ�2Y����3�c�7cM&��B����Մ�`�,���o{k�iM�kn��VB�*�L ��p����r��o��J����k�*7K��1����̙6ӧ�_n)�d�y��s!��j~��/B'_n�6��גswK/��i�l��X] 9�}0�)s���<�0�ʫV��b���ޙ�Ês���-�J� �ϵ&5�<)X��-]: W�uF9���gg�T�j�@��+�Cl\��ؔ�JbS� ��i�)����D�UwWQ����JJ�J[J�h5��73����A8�0��,2���J���a1u��܌�8�\f�,���j��_�7�a�� '���(]��^рV���@�MW��u���A�B�� E���r��aC�8?���#�t[��ZmnaCP"�%�%HE�'‰ arV��zgd�u�[��neM�V,%] dl<�%�;��R�T�fL� ��7��pH�4꿎�G>�;P�Ac��>��]"Vե*�ٽ�����k�/�f? ��l�l��߆a۶3� ϴ)�AbxN�&���r.U�֒[_i���DM�2�&��h���Q~�Ek�qUL9�C�ߦg��$}?�l�G�(�8�I��������� -����(Y���p�L��U�L�mQr͙���l -H�Q)�� � ���|o�<-}�l���Ȍ䩢B����5��T�rk-��i<*��PYN�I۰%��[���w������/h�E��Ӱ�߮_�A����\�R�~?��_���_����!����/�τ6��\󈟍&+�.��_�����҈��d����ŧJ���܉�y�$ E�J�����{�n�� l��K��Ƣ;쀧= -�}B'���Aa;�|�>��`|���`�h-���K������;�Vao�H�ίE�����>���4 *� -��^U�*�؃�����]��S��ͬ��R� ;;3o޼���*�z��E.`* �D+'��*�#��䝰Ȧ�T�5Yq� )��crb��2Ae1��Ϥ }�z�a��V�pR+L���'� -�[(�i��mD�A,Q9;�}��b9��� Ab�T�֏4��tGZh��� �i*9�(@*:(=v4� �r���vFf��(46��[r)�4��m���j��+�ꎌ�I�����/a@�鬳� _{�R�@i�Ň��+Gp XYR��{w��sƯ]�榁��^�؛��sի(j�f,<�6YJ�>�����>�U��[������P%bMX �p}��Ec�q�����遴�J?�@� ߣ�I �� �M�Y<�/����%|���N���M �[�^��ϖ�Ŝ~Ma2�ʞ���G�D��me�B*�QL�0�Z�>� -�� ���Zd��G㧢BSJ˭�2%y��yQYv}R[���D���y���B�H��3��Ȣț��z�~�? -�”��ԉ�&�I)n������ &�m7�0Q�QqLT��M��?j�z~�<�`�*ݛ[нz$a������hA�a�{$�!��� �1b7`u�l�Y"�Y�k�αh���ީ�.׼�~�B*w2pB�ϝ�괣��y��;rxQ��xe��D�ib�z�H���y�m�Yh�;I��EQ��>�^ #��\>wt�Pv��20�SElj�x��VI>x��]�� N'/Gm<� -ms[���wꋫ��>������l���[t�Q�jI��n�1-�V��%NODM;g羼c����\�^${�Y��(�`?�4ίB���~�R����o��8�jeѭ�Cz�܅'_7��J�w�/�D�j�B����h��P$��)�%� ��@EtQKt��'w� -�^.d ���H����/���[L�e������?"�LB|��=!�6c�ʱ�TϏڠ������6ڟy�����`$�����/�Vao�H�ίE�����}l��4!*�"L�MOZ�Ŭb����8���~3�^0����`��{o���|��y+8;k�\��C��aB -��Ys�?�O��3��B�7�B���cE]��˂�`���,¯P�L�4�+Uʘ�$t��U�'נ$�h�!S��j�,13�.#�Ds�qi�>@ȹM?���#X!W��E��@%�ψ*��`��X �f)�2K�5O��If���ڀ�$��Z�7')�'S���nTYKi���у�0I���:x��N�'��6:c��@Y�]v�� �EbY� -&#]��b ��:�ZRˀY)�V�c� E��ژ�]TU�g�p_�$��k��$�A�>�LyQ`��)��1,7�rd�%rMYE���@�ƊˤGх7A�M��y�(�y��l�N�!���4 �a��矧_��u8� '��(�� .����|<��+Nn)����K�P�!�$� -�(�~��-u���Gb%"�'��%uϵ����L��I�h�Lk��B���)��徣T4M��}� `��mΒ �����]��S-?��ed�Z�h 1_ ��f�_?�؉���� +����f.��ǧ�o � �#}��끝;f�*� �!�ԭ�.pDp���hH��I����00��-Ɠ�h6^�޾o��IK^�2����~q�%أo[�ߎ�iۜ�Mng�!6�r�g.#�5, �]���g�x�4�L�d�R^.�@�q9�E�9��DR��k|)��|�I���*�M�jI>�q� ����.y��I��� ��yI����me�=�en��Q9��3ͲZ��x�Gx�v䱂�J���by���%��'��M��>��7���R ��>8��28l�uԴ7���c�N@TO�w��Ɉ��oo��:eZ�#ú��U���.>������5�����46K���e�=8�giɻ���E����O�;�:Qoˇ\ic�V -�|��4�K�.���3a����v]E(�Κq�V�:����)�ڧ��I7�o�0���L68��m8W��Im������t��m�-Jǡ��z��{~j;u��m�h�Go��[�y����z�j}8o��Y�r�6}�W`�b;㚝>6m՗F�XJEŞ�B͉l�#�"�+�0�� �WL�F���s���z�����n �IM`k����c�-B#�W��o�tq���E=��^Ѭ�6}Ch�ZO��!��pG���A=�5D� ���bQP6 �I��J`{ U�������H�`��-F��@S���"(�Z [�`|�EG��\H�Qr�<~Y�.���4#Y7G��2�K����|��i����I� ��tY�� �9�� -�[5H�8߻a�6>��Ϥ��&���$�6�� -���P �=�j�w�5� ����Ț�k�W���=�.��l���o��7ܠ�zb�� �>����i�3�\Q(i����d���f] 4���`�����7�u��,1\Y�:"B��QX�!�����xf��z]B�b�;o*rL����j�b�{��[�X�� *�xk��o����mf�X&��7����9�֍J�W�K���g4�~Q;����������Q��?$�ރ� �?烞{��a�jw�����a��um�J{�� �vm�bH=��f� q�II����)��<.��(�#���ۥ��gp��w)��&�7�3�Y ��&�W���թM��;���^�.�s}!R�*x�Rk������!w�=�zl��u���G?���X�n#7}�Wp?���ȣ'��� "�X� �� Z��u��l�ځ�}�x�{K�f�����d�N������nҳѻwg��� #��r�Ś� #��Z&�Ld��&rM�j=���c&4[#��8�1|����b�^fbI ��D��}���L)JKE�R�c_d$N#�k�ؖ ���������Y��(���Ɂ{n6��k��Ꙭ@].9M�<�ZCPP�5UKt3��A����L� O�9�2��h�8 �d�])y���/�]�n�w����^�����ҐL�B;a/1K � �mӄS[i�]~���W"2B�+D���5(����^�F��~H��C�֣��� ;��]��A�H�ր�oW���@h -V�t�&t����+� -�J�@�r� -Ђ��zy�Fm�z���z��l2��'�>�������t>����Gr�0���'S�람����?&��a�^R�N��e��� �'����x �uF׌��)�)S[�1��\=��XRim���#����|6ͥL��ǝg�hD��k�;]�Fv)$���v��V%RL�LYN��B�㸥��"�FJ�H�-� 22C� El�$ks�e:�3M�A�����&&�*a��.c�w 7�h—^�%,FeB������K��/���ST��ԁ�(��ϭ��!��{���X�@��n��2����ξ��AR��q�f�!�i����P.� [�'#��*i*p�� �o��II`��r�R�=t)M �+�3r[�ӓ����&"K�Ai13ifp���oZx�5���\h��;~˘�T���,��Fȳ!'I�:\��2f�d�v���{DR������֙�1r.s�/�玷z1�����o���������ĕwl8���Kr��԰SC�ꅏ��]�+�*�N�ϲ��ʄ�K��]�ev�i��m�\Yv��=�kΠs�����U����#�S���J?X�1#��\Ω~m�Ѣ� _�� X)�v��P�\ -�hNV�����ȼ<)��o9p��(��A�Ć��X�ib�K�=>v (Y-G�., .X�E�dT�?' -���01�B ��r!�M�H��…)��vpp:�xO��ą��sg�,l.�γ���k)�)��=�t[�mJ��ؐKxk�Fg�7�f\�>|Uq~�4I�� �Q���Ȓ�[W�p�ʗ�S@��+b9�G)�f��M�X�Iy���]�~�Xk�},�Q�(�uW���mjJ��3`��`2?)�����v�i;iS���d�� 3�6|V�7/e��p����Go��pAzJQyu��ӫ� ^��}0�8��5��;��0t�N�ה��2.�R�ȍ0�&ol���d�}��f������8C���ކ�#f�0���\%���>tq� ���^A:�a0� ��{-^����;?/BW��w��8<��#u��g�� -./� ��*�%�C�mxt����n8��5��5�rkz��2n��#>��*�r�D$^��|i2ߨ4r�:&���bs,$+���U<�������%6�O�iQ�>�*�v��ɢܮ��xW�e1�W����å -4�,` K%�o�!�KB&c��~ �bȯ-]��9�,��3U��� ���� 2��;/���(v�U����c�r�̃+�3T��T���Iܙ�Ol=]}�_� -��1��H���jD����ay�����c�N������j�8h�-���0�� -����Ba�1���m`����)d�Z@9;q=��j��v~{�|����^���W�S�t�ΌjG/D�U��ﶲU)�����I��k1V#�7��t���\ ���qĽ�j�� �O���ή;��_kb��qѺ�f���")���e�ۈ�D�ѬW���y �������e�e���<�ꇆ�X�c�辝�퍰d@�!�\�'o�O��m���]-n��J��ɾ6kY��UsHY�Z���^��1�z�J�:|^O����m�uo��P`�����i?�t }�.يB�o�7�� �z�q�Nv(�0�}���_��Ax�T��z� �����}j6���?�A��p}�?�Y�o�8����'�](��>�M�l���9��MQ,-�6/���8�"��� I=-;�-��i�E�p�y��ާ��h���{�>�X�H%��D&KfV���DW*�*�ʅ6R%ג�jɢ�k=2��$#�h1gF�e�#������L��*O��Y�r���ȘJR���Uf���,7�"�_fB�Eb􀱉�~|;]ݰȋ�s�-��f{�f�=����ģy�d/�$fbɳ9��t����0�ID�W2����F[��X�u�r�JEkg���#T�o���l@�����D��[�(�r-J�L<9� �п���pHK>^�}8$�1��05<��_ l�#׏'���w�� �y���ܤ��<�щ�=l*`gd%�����L@Y���#� p_�$�"�� ���@�(Xpmo��[�LE���'�� `� ��9�m@.� -�A�د���x���`C����X��6] L��wpU�ؙ0oSR�e���@P�4�����cرJ @��_u@w� �Xߒo�}6X�55��� 1��Mc�����=<���%1J�=v����Gŧ�)�s����Oʉ|s�fH���A�yǖ�m��gk ��ɔg�v1MK�s��w�M8_���K�8+�dH\�/��^e����M��w����,3��TVk ���� -����<��*S�Җ�.�򌯅A�)���(^cԸP.���+����\� �� oVj>h���w�5�V�\�G����L�%�I����e�ȓ����� d�<2�N��� rN(a}:�{��c�V�����֎�O���1}O�9 J��� -Z����;�d�E��>7��O<�sDžUG�� -[q�r ��o�"�����"��ق�e�<��=7��#���*qI·3H�;��p]J<��d��I�t�;�y������!��IK�:Q��vV� -S7�^�<���6�r*�>V��u���ZR��m�����;�"M4 �R�1c�N��8�JZ�� (NZ��J�Dl��G.j*cs��؍(�M��V�¿Mv3�1];d����Dӑ���ʠX��"}�]�~|,��e��{�2k�� ��ع�UDW��o����?�̐�uY_OO B�H���:+� � �5�-+�%d�<,�v��I# ��=��W^l�u��Ă���i ���l%<�?q�d�:��W �Z�v9Sف�p�A+�'�������jz����z?B�>aw����.��`�����gӫa�^%O/ �`����э5�-�Xy���Ϡ���̍�N/ -'NhR�j�T@���_[*ar�cq�� ����������a����*������S� -�����7�n���j!��>XT0�S3� fG�xPc�B���,P[��eV��yV�\���xah��TɊ��+�`�D�Y��/���5A��ϝ`��|M;��m��x��� -x�8QR5v�l��Ѫ�)�Q"�Ŭ��m���A��f����* �A�ش+x{�� �Ej..���}��/h�\��v�� �;��¡G�sȂn^A o~��_��~��!����� �Q��`��q���m�b~"p�]T06r��{��%�|�Wm��*������Ѥ;o �>/�*Q�M��:����sX��5{A��`����m��U��橭m�zCզ����d�'OS8��o�W@{�>s(k�$g��ۮ��t�d�ⴳ�����������כ�p��W��@"9����I�^M� ݟ�$�ղU[��}&�Ah]��J�����TƣHh�g��R�'+C?�mR���>��i�t4���9tݐ�`�Dc����~ � �����=�gc/�1AI#���ā;�zz IvN���G�}��J�(�%�r���n�'������v ��e��Zj� -�>"`� ����u��!�K7�&��~.~����G �`�2�VU;���)^�����:��Pæ5���0�C'��mTbS� -����A�y����誰O�]yU��-L'����;�𩛪s[|�`;��j}� ?<��;V�p�lS�c��}dP���͂�\*��?/�73����]�W����/��G���5��u�kr���Ӆ�.¡ڄ�a�B� �i�Ru).�Z-��3���5�Ze~0�܇zw�����t�/�� �Y[o�~�����2T�8�v��udT8�XJ�" j�Z^-�p��� ��3Cr��6��DY��/� ��_���dt~~��ٍ� Tb�Ld1�L�\�$S��M$�[�c�+���#�eȍ�,�y� AJy'�d"dF�����5W+��Z��'�&U�ή�7}�B3��i��Z.s[��GZ��HL6dl.���.���ۑ?����Ҭ�Ffl��[�(�U��~ؐ!ȨE�u�n*�i� S�D�l-Sз@W�7ޘ� -�j�םʝ+�]0�#B��;���2���^���7|�eX��R:O�H � �m�X�$ n�]�l�� QKL!�� -S�*���ckcҋ�h��9�ا���� �����j��N����]���N��<ݰ��������� �O�F'�R�a��� X-.OY*����D9��ԣ��#���aj302���HCE�!k�7�%o �(�u�B��8�<�J�8T�Σш^�Fx~��!���T���F' -\��Ǣ�1]`�AsP�����e�`�A��L`?�l�B�� dJ�r*b��('� -jvCj7H�0�_eh� Y�?��< �w�BC/�2���D����]�]>�O�R���J�"�O$A���y��,�L �xx�&"����c�H����N�TAq,�]E=y��r�j�8�[ܟ#Af8�F�CXe�U�4 X��B���'�C�Ku��LbNM2¡��T ���q �a-�G�*�`އu6ljݩ7 �jf6�eb�i:�Z�S��-�b ]\���ngv�4���_,������_a�����nH]e��Q����E_�|=A;�]�s��ۘA�1���~�ȵ͘�eD�e#D�Ӵd[ʳ�k��g�ǯEPҸ;�I�8��I�9�Līz�,y���۵���Dl-��M��6u _e�;��HX�5��Yȩ���e��sg���8�Fl2D���VZm�0|��@�&Eh�S�MHE��'Jrf� -Đ��(�!��dܴLc�C(̊�#�D2DI�G+YxW +�.E����xՍ"Us* Zԃ��)@�U���X�,g�R�p � �!ͰU֤�%PT���',}�_'�`D �Gp}ڕlZ�\'�Qɰ�x��5[�I@{��=���s�rg\t[i��vW͚qGC�>��Z��)�_©Ke�M�"8��=��*v]�lI��Z��b�nUiǢ@m�+-��~/�ktj��ZQ�{�Ǖ��f��@��з^3�K��~,7->�g��g��f~��(�B#[�����Q射�[>)�j���A���Rf�v�y({4rG�J���\'"|��[���D�y�M��;�<�}K�r<�^���C��3�����"*I����f�����!.X���6�'�6�z�ӊrQ����bm��/�ɤ�����3r<���N�o�A�@��*t,�Q�,�)؀h�y�)������Xgt��Z�0eK%ej�5�V��K�ǖ *�� �B���p_�(_ds)����#e4���5���G#�*�ק�>Z�1-3e15:�Vۘ���!�K��f�e�$t���X �7U�[pB\wv�;����^��KP �%k~ÄW�4�d+.-�"�������Qk�JA==����&|�)�XP�w }X��_��'`('��޸/��5>. � ��U�"�.�y掎�.O/�W���)�y�}���%�=�pz�-��߽;�Zh9 =f�`q�~�ɖ��M��9$�\����*�}uC�˭�fd�B� �IJsfBƎ�w���G�<~�)�����;��@#�$����!�%�j�y�yù�|a�#���@�Ĩ�1�W�|;��6<ޔ -ҶF'�פ�n&`ǃ1��J����"H镊�?$����p@>n��.��z�"�c�B���#h���hK�DR�����2u�A�A\c��4�~�`��q�p# -��X��0>Z8&��1h![�y��軗w/`� ��X\�� l[5H[7� ��G��"�ڤ�չ�Y�zT��֤�UӇ�G1�)���jf cd� g�Օ�UYlzM���cW]�-E�V�m^�O0��^�"4.�x^',� �� %��]��/l0 �nQ�L�0G��Qt�a��{�.��L{s++f�<`����N�̪^g߽i }������k���u�@8hIP��@R؜ѷ�Y6y���}�@�͒�� �8��`�4��u��P���!���8�rKO���q{ (YڝK�r<�("O��q��ؕ�g��(e��V��]��� ̔^K�tɎ)l�բ��Q��d�#�RP���C2��s��D�������Q��x *�g؉𡽑Ī�2j��Y[سW��}q@f�`d���u�U��p��;�� �� � -�Z���{��f��Z0������"2�{�\B��FQk������Nt�$�t[}{�9"GXy��k�l�K��{Z��ms?�E��_�e5�G� \� �F��Z�!��f����ү��Ω4�J���3�����gɴ=�Gm״ -(l�k���Rګy�ڑ�tm)]*j _م�m�SE�綾d�dW�"հ�k0�����AQ�%� -�����������Z�PaWq���:�[���V0)3{+��X�G�R7`9����b���� M;p�G�����w��~;�W4�e%��:A]��.HdM��?w�o���[�l~&��X�4��^�q'��m�I~r4����� @GDZm9ӴG�eO�*Jw���7B���������~Մ�F� +�xpx�I��6 -{��+wcRi�� �D������8�#k���N|o�m6W�lžpmtE��������Xmo9��_�*�T!���-)����R� �rv���{Y{���~3c{ߒ��� ����gf��e������{�]�T�H+˥�*av)���iet*�-��R�7��:��g���Ss�s�ܘ�AMoe$�1����f<���^�5��Ѕ1�ƺ�Ӌ�?Eδ( �V:w��r^���id<ɅX è���~r9������4NXK��3Ұ�ί�T�8�h��L*��"GP0 �c =��&���2�V"7K����2���8��X7��Ԣ����C�s�;�������1I���)mYaD����Hd��VY*��H�GW�?y%z�ed�BazQ?ƸEi������h8\��Nt� C�÷�����98dޫT��V�r<�0@H�9���5V� -E/�9d\%}�6�2UI .B���6N5�N�x�a�O��i�}���|?cO��N'����]^���ɛ�l|9��.���J�=���3)S�6�1�TbFE\�S����d2Ʌ� <�<,�7"�>�D��Kk��౒�@ePt+��%� �ר�w�L�$^�x� �П�y2ң�?_��pH*bF9aj����X��X$rB��H-�֘Rj~h(�MS�O�����{ �����!�x �b�%�Fs+�m��a�P�X,�������Ȧ��5���g:M�0�]F|�b��{���^@1� -��X2q�>�r @����Ti��sN �r�����,�RD��`b�|�Ak$ q�F�k�/�/��W�4@�����V4-�A�-��n�§�/���PΉ�����H���� \j�����0lt_��'J0z��O���e~O�A��G� KW>.�R1�H琍L+��X Wf І�Q�1�X��IA�ti���Y�9���0,>/���$U ��R��s5�� ���b��Sl�[��&8����@P�NN&��%)\%=�_���j�R?h*]�[psZ����޸�� *9�,����M��#�[�IiA����� �݇���rx@�Ѳ^BH]����BE.���S�v�i�Э A�)[����� אN�c.�F�g��h���hz��/��PeL �BIJT�'��U�����Y��%m��ۙ|E1�O��ry؆Z��d��.=�X���\����6(�21�?�"��2�����sثK�_�H^D��[���~Ј�"M� _GP�t�,��k?��pK��1�ې�@ R���0ʓ����'�����>�����^y�oGG��vF� �wL*��ڼ{EID�\Pu��uH��e����G]g4��V5Z���Z�$�q@�" ��) 톡�5�^�m�8���JfEJM�<�;��"���%��TG��tسg��W����xS^�ͦ�qy��f��m��� -ߣ;�F�0L�Q��0��XV���Џ��@��ǏY�E�Qk@�O�V%�g� L ��� 4�g�� 뱗;��-:��.y9�eD �E5���td���'0���j �M`��h��܋DQ���%^j�����P���D[���~��eڝ���{��=����E6 I�a@�'ߟb�W���.�ǂ���6Md�`D>������)mwq�U��=�Q�Qg�D�|u3a�0qĂ���}��pQ^i�ݙ�Gb������Qw?�ў��C�mWM������ݞ~ %4jB��4{y��?�Xmo�H�ί�CUE" -w�ؔ4i^t�*� �UUU�b/f���z�!\��~3�]H�^��P���3�<3���W��JB<$㰫-�/I���3Dc�����>��*�*kִyo�"i/��m;��H�7���n� =��,�+hç��ӻ�mx� �kCw)V@ A ��%�rgq>#��� �q����{-] -(��vd�^�9�0��i���3���U���/78�7|e*��1J[�TU����� ��id���\]�e��w�]u:�T �y[#2Q|bz�� 8���T�%�ϗEmx�DžR��*�6)m -�����]ϛ� 0��N���Խ2W�:��f41F�����d��Kx�a;������!]hX��P"�B�rN���6����r��1���@���^~h��?ݲ_�jj������ϩ:��F%_�s{p����e{ �'Yq�� �� wAӏeKE�{�9�o8ۏ|��"WW�Fk6U�$��+��W����Xms�6��_���udW��ާ�U'��4��ZKM'��<I�)�%H)�����.���N��Lb��}�ؗg��e������Gp -7*���0*T����_����/�4������7�|'5����1� ��ʗ��SH4C\F��?#=K�"�p��� -Z�����2J�F�������XD�Xʥ �I������fh<�O��zh�Z% �Q�:��B��T��"�%B����xJ��u���|��^�26 �zc����c,�[���i��Ү3g��і�A H���z'��u��:�O��4 [F�������5�����P A�V@��b Ҧ�$�λ��z�lpG���b�-zv0�~�F;�_�@���3U1�x��U�����XS9P�h�:F���6i��0Ns&����6�1�.G�y��r�����_�_�������`ܿ��� W�q8�_7p9xO����� �e���Ŵ �T�G崔O�ʖ,N&���)��S1�0�+s�D2^*C�5h��c�N*C����*y��'����Z��j�eJ�+0���b���+W�۾4���l)T�2��[�6ݭ�<H� -JL7,�r��)%2�*۩��e �F�ܕ�3bl?����\Y����o����\�s���͓+ -זX��FEY �\�PEi �l���x�Ii�����]�����R [�����_������ٳ�����.��[9ÿ/��Q?�ߜ�o_S�[��\[3�.;�l�l���-�[ x���$���{M��-���JN ��=(g{װc��EX����^�} I5bq��,�!��`B?���1|+!eZ|��E�O�Se�@l� Y �t\�H*�*�γ��ܭ��搤���G�l�eR+�Qf�:YC���\&�l���{c �y��Z?�ݼ8�SB�r^�>W�>|��L��_|{f1�!�ْj��?��k�uD&���{�u���A�/���B6�m����q"� ��Ґ.͖��m�YE���f ����׻;��;n�5Y��WN'WM�^��kt��s����{K�]���ӷ�vP0|�X����@a���:�XA��η�ne Kڜ�)<�]����8�����<հׁ&� �\�}RT�.��&�8��҆�_��4p֗�Z�� -���Oڱ8���E%'u���b����4Ҳ]wi=M ^ޒ����& ��ir��V��Q�hpϰ{��2�m;(�W`��T���"�>�Τ����x2A:���,�۳��#�/7�r§ �v8�v���^�f~�;v�ů&Ī �8�.�*{;Ȫ�"�Y�-���*��o\� z�]�#�5���0��/�����Ξ˛YQwnєЁ/_��V�n�6}�W �}��*��M�q�58��d���D�D$R%�(�E��3#Rv.�nօg�̜3dξ��z��nT)!3� ��.�o%��ٕ�Δr�Di�%[i!+�s�VeR;��7���"ß�l|+����\xe4 ����c- m,T�v��Z7_�]D����ڻ @*%�_ܭ�WװA��ϕ�pH�U~�k����G�`(��R���!����9��zgU��`Z-�۪󭨔�&�q]��kݙ&�rPuh�0����7���G�ό�����8���9��G�H��K%t��P]�9~ A̚D����.� M[���$i�v"����"�%&���Ez}��#�^��9��ߍ����D��2�F��hIA� �,Z��Ř�.��P�}�"E,�p�M�FG����1M����W�ݯ��t��.V���pu���W�>��t���1Hl��ϵ�"�������O��%��j���ʰ<]4��P�'iy*ji+�HZ�$s�G�<���]mqJ.�ݏ*��ʘ��O�)I"���]IŸ� ��l��CIs���d��ޫR�]7�܋��ϭ�i쪥�pHP�vMUI�5y�摺���u�G�����Q�c� u�^x��!×���tL�O�T9Z�5�'OY�I�����)E9�L./�.I`�%^�n'��G�����4p�^{��3gIb�U��(ji㼩B�1���U� ����r8z�_2�a��l�VK����&^����*;�3�?���K���?�1���8tìk��kC�a�o,M�k]���f E���"�*�����u �c��^������g���+������In}��>�e�*���h�! �ہ#"�ߺY��{0�M�3>!�8맜F�S�������t��E�<$��륿k�t� ����=Pk�\�p���%nFB�=��>�E/��^Y��`�r���l�80� �e�-���)�� QU�{�S0�#D�[���}o���Xͧ,�/Э�����;������߳�����d�Kj���\��#p�� a�msÐet�_䂟��|���\+h��G���DZQ8������T�n�0}�+���VY���MkY�lh�]�Gcn�*��6�Y�ߵ��iR5�!����{ι���E[�Axv�lD����LH!K���WJUc�ˮAi�O[+�^3cV�t��Q,�*�Z��+U{�3��Q�,�ǝD���'jPZih��k�w��"�R#��f�"���6��ְ'�_3��@/lEw��^��S)Vµf5I�'�K� �����EYYP�Dm*�R��II73�ڒփ�F)3գK�A�����pBh1��8��� ;�T:����f�KĚ�Lr�ս� ��c����R@��׀Y�v����< ��_1Ox�tN�r6I��􄹕5Cn��&����Xq�ך�n�~P>Ģ��,�m���t4m�H���6�g��R��|��8]�]�}��fp�vQ����;��&�qo���(�w��qr�$˨>�ډ ��9��,O��qN�E.���DP�GV��m���4��x&�����e���s*�) -'�/�;�0`9E�q;<�o>��dQ�;����K\|~�U_o�6ק8�����n�h��km�r[������Y&5�����;R�-�ِ���������m�����&��� -!��0.�(�l��N --+LT��P��R���K� *�'�ֆKYŴ�T���Ph��H�*�YF�T�e -a*�3x���k���@ -��R�N������A�eV*D�ERD�~�X��&P �:GZn6t�kh��BA�X�s[�U����d*��3Y�/7d+P� ����RI������u/O��ڋ1����R~5� �� ��o/��.z�� ��F�);�ә��]]q&2��k��>�\[/�9* ��5`&8�1��q۶C��*�@1~G����Kb>� -�&��l85���jB��5a�XktF� �"�E9��:4Aߦ�h"Q�_ ٘��"Ia�^��I:K�i��m�a���2��f�K�[��g��bNߦ��?���g�� IF��V�!�VQ�{�0�n�>�3^��色a%B)P�a�Q����j�S{�qM�m�7����$�֦�c���r��S�Q��(�؝�)x�g�Մ�2����VNu� 91������S˕��Cg�����7�8r����>y?��.�Q���i?����Mőf�=5m�r!�{�>�����=��������n�<� ��N�cT�ձ�d�� �?g���,�W��`�=�@`뷈õ�33�c�4��00E�P)��?+k �R��wY39��$?���< H��V��'!!�d�{z"b'��'����a[G ���ER��(�9�������8���SY�����>|�.��B�(��g�͚��Fdη/_����U�7��]D����iR� \��>�⺈�>�Xި��[���Ѣ����o�d���/<�=e�0Ë�7��ti;�7 ��g���s���q8�E�Q���\�P�� 9D���/�ߝT]O�0}ϯ��&��L{� Ȁj�X+�� ��m�ؙ�����{ݤt�DB���sν��O�E�����P�h/�V:�@�o��hg -Ll^���g�3�]�(S���x�X�������f�M`I*!鑚�o�E��I�I:�Z�����Bi�Z�U���b�"��,� R�@?�LGg0'��ϔ[�H@����(��0'*�e��(M/� ��sa3�.M��*_x0�F����l%vbܚ�;��.M�Z�r݆ч"b��oa�60��~��t)������ ;`H�䒰�*��2�[w�3H�]Kbf\F� -���6������0���� x`lw�KJv�^��s� t���Z+Kϖ *R%Ō��� -�B� ���u�g��`�L/�u����M���Fi>$�(���h�qr=����*OG)L��l2>MG�1������i4>�Rdt>U�M�Rʼnb��O�N�B��J�=��"G��#�0'�R9.�#��G�|h*�П����S����� �S��c�)q,��s�q�wS��5�]eußP��C#��}X�o��hg��3���gd���~�݊�URhFJQ�Վ�Z���ʝ��A��7�8���z��}Er=��5�fO��"�1��>�Y��_��f˲�}A�����Ŋ�C� En�M�k�~h��N0)k�4j���բ���G���]�U=�N�y�e���EJ�TK��K{�Z1 M����?�{L�x�(vV�?8����ퟚ�{G���9�N���UQO�0~ϯ8U<��$�at@�h��H�ir�k�ؙ�:�ߝӔڄ��b�w���܏�������a*K�L+'����B��٩VV�����P�)� -�����Nj2�2Ce1�=vR��^�^�V��nT.�I2}�����@�MG�ȴq�PvAџ$�>�l��O�aI��K��@+݊b��V�[XR*�璏%HE �'�@��09��zmd�r�[�ƮdM�-��dړ�]��X�u��M);Uo��wJ�%���4��FG]�5(������+Lt�XU�R�̣7�m� �7�$:���^p}�V�ՇQԶm(<�P�"�K�.H�Yr~@�{̕*�ZR�W# i��A��*)q-E��� �!�U1f��M�ۦ'�z�T�n�&|���d�'I���:^|�_-�zry9�-���p:��ŋx>��)Lf7����ƀ$����"��dE1��Sρݲ铭1�K�Qy�hD�P�;4~:j4���ZK$s�G%�7�e��xLx���TOsUz����)Q$���(�����g�2!���~�+CV�$��5��w�j��gs �xhѰ)I���E���uۤT?m�]^�$�7dBAJ�-�Ʋ~T��ݹ`�>v{�<��`Q��������(:T�nl�� �������{@��v�J��$iB���7`DEB���U1�=��–R���H�`O��'&w����ߒt�Qp�e�Y���nRr ,�y�?~ХKĚ� ;bc>v䃻�=��W���g�9g! s�aD�g�^ڦ ����?�@O����ȓx ���S��U�n�F}�W � E,���IXGB�Ta2 �d������.E����3+R����Aj9�s�\���f���m���B��RIU�� �_��VVW���Q�l���+�V�~���N9 �}�9*�8��D�����AX�V>\G���'� -�[��9b1r�:���A��q�)@���'�,��Ú�!�яt�m�FZ��šB����ZT ]�;,�)�{�����Ɓ��� �˘J���c�!-q�붧r��c_(S�q�\�;������޻{P�Ak���Lp X�TR��{��N9�CD��� <��K3n��ƹf�]�M�<զ ��'R6I�o���YUh-��g+ i�ڃhU.V��W��7�� )�� {ۡ .�tm�H�/ H6�k4�R����q:��q���s_���(��y -�{�[&�,^&�kQ������� IF��1L��JV��~0p��u� �r-s���V��ޡ�Ҡ����ZYP{������� 7�����r�~�2�+��a�wJ -��U��a -��f�Մ2 ��H�~ġl�"� -dUɒ����q�y�͊�V�<7�ei� �ھ�FN�����s�E�CU��p� ����;��Ѥ P�]�ի?0wS�'�0�~%\��~��<4v�{��Z>�׫��Z����R��J ���ՠk����E�gӮ�/`ݪ�|��Js�����`N��7>�Q>W\�D�?øU[E�q����R����A޼c�>��0|ra/��Īi�l�=�>��l�����R����YQ������d%j�� �#������#fEc^�1��(�OfCf��x��h �(w�긶ޚ���a<���!8��w�ߝT]k�@|ׯXLb�X��IH����,�!O�,��k�;��d�-���=K��PJ ��igwfg��n�]��Q#X�!�� ��*���g6���gR���6ڸ�K���ZM��;�������"�G���a�� ��Y�,�@рV�hm���X��M��vI�}᥀ޞ��p�S;��0l�v"<�6E�K 勉�d~N�{̽*�Z�֏F����&V���R��7��h u\cF�~NmzoZO���Pۄ�h%'�%q2��8���O�!Z��e�X�a�Z��4^-����##�����ZF��6,��J�(�'��s�i�|�5fr+3���F�ޣ�Q���ek-��i<*��PY����ׄ_�R��9U�R�֥G��I CA�_� -C�o��{��!��a�o�*�cA��πE�[������[)�^{��:9ϕ��(eN�m����AV -��� ��<����w�������`jP8�R -�n�8��|��M����و -x���JTt��O�=�����2�`r��o�o�5F�^˼; ��n6� l�yOOt�Q�&sgCq��ci5���8���}��W������7��o�T�n�0}�W\�i�J�xĖu-DL��dL{t���Zb�YZ&��k7�E �x��8>��s�y�k��iSX��+阐BV�j��JZ��r�Q;�$��Y;'�� ��b N@���\�\� �Ju�dw��K�G4�$z�2�*s�jĶst�+� b���9@��g�"],aGT=���#�p5�ze�`G�XY -ߚ5 $�����)�J�����v�z���BS��K�W#{,<�%�� R�Tf�� ���_�]���vr�.�[v�tO���D����L����A�"j�',H�;��y�����q�����se�x�ߑ�Y�|E�G̽l�Zr�k' y�=�Ċ�-qmX�'@,zC��j��v ���N��I�����M��|7I��3xH�O����&Ɋt��z�uv��:��$٣G~N�� YF�p��AL�w˳<�|Z�9Y�\�'y��X�P�g4a%4�VX?ZK$K�G+\���ߴ�[rMv?�R�2J5q�<$%���B��|܂���BH%�h��H9����F���>��3$�V���GlKf�?���ޡ,ûfO/���{t�!��Tmk�0��_q�A���c۱6KfV�ݕ~*�}��ڒ'�q�����b�.�QƲ���r��uQ{�t��V�DH�0� .r0�NRhYb(v���6\�5�p���{�-���y�Bc -F:�f ="���)��lD�,��h5zER�EK�TG�oC �"�\!V(��"DW~����2�m�)�Gh�)h��J��bi��Ѭ.h�rD,Pa�Tj'��+�d+P��t^l�D���>�%�{�tR�;3f�� -Yɟ��0� 4꾎&�]�=i���Z�9Lt�XU���ġ;u/g�������9) ��6`�O -�0�> ��m}��R�A/1�&g���H��[Q���֯�+�x�V��m�k�Z�� �5�h9.�E� �1���S$�� ds�����Σ0��]���p7�����p����U��5��`�������jH��Q�T++��r�(��~�9�n�r�5&<� �y�r�\�P��QU\�h5�L�=*n\Si �K����~������,�r�uJ0��E��~ -ޟٿ��J^?�q��Ձg$�"s#ҍ5r.���5UF�6L�n��Vc0:M�e�M�c�<��8�K������U�����מ�KJF��禢� �t������X������P�H/��v�h���OL����I���v/����g��e���꿀�F �I�vk�{�͖����C �w�'n㑷��| ���1�,�a�'�}8���E�`r�����]|���Tak�0��_q�A���c۱�KfV�ݕ~*�}v�ڒ&�qӒ���b��,�+��޻{�Oj��p4 -`s^!dRX�%�5>gS)��0V�|�X�ؔ���)Ce��� �`^ -��8 �uh�X[��°m� �'R�a/1���.��{"�cnE��P�~6\S�W[`�XelE\+ֺ �Ay�VS�E9vhӛ�xL�M�)���j�3D ���DI���.N�.oS��nn�E�X��t����x���9D�{��/�ƀ�2*�OJ;Ĕ��b~䧞�sK7'�0��H�(V"�r���B]s�Fk�dN�����2���~K.�ݏ.U�Y���G\n:��!#��(�П�[����˄�2 ��Oך�� ˔|���mx������o��/Ύ��T��� -A*����Y�h&o�kh�E~~�/x ����>��T#��� ��R�Lr�3;���'�׬gB��QQP���?�.5�F �H�wg��fE�������@%m2{z�:��{�D"���o�wu����;�� �w��� `x�3�`\|~�TQo�0~�W���&�`�c;�ei��U�*tU�*x%6�Mi6����@�4i��H���������Y[�^�Xx�� or) る -L��?�Z6�+���R$F*,�O9���[��_����tذe9=Y��)���D�,��d3:�)Т���u�:*�!#�J!�P�$�.}�M��Jbm��蹩)�k�z��R��p�Y\����@�S���v�xU��@�k�R��JI6#}H<�%�{� R&�3���Y���pL4����c{�@��5;�s���]�p&r�Խ� �wC���sR@��0`f�@mL{}�������QbpI�����17�A�ɭ��C����eĵa���k�b�+r\TK���L��j�H��O�6�z4 ��|�(Y�m�~�ޤp^_�q��^�j_Di��鴁0��ȯQ|�$˨>�ʊ ��:��d�FvZ�>�s^�䉪cB%Q��hQ�����D����q�J[�����d��M5�U*e��ä��P�����=��R��W�hń�h㠮�-;�7���CY(��Gw�^� �]����R�d�0����+�a�4�Lx�3c�^���x��H��[]`U�[?ke��� DI����B4\A_(�Ģ��Χ���&��ʹ�"I�m��h��!�G�uG�����6�����b�D��7��n��$�n�m ��#�G��) YFG�Ҳb��QL��s�n��T�(U�$��y-r�����(�Tť��dJ�qP�7U�п��SrIv?q�n�c -������y?�p}�( -.?f<��|/�s�Q�n��H����0���:�����:��0��[�P�����7��E�h�hl�A�Lf����a�zi��V�ѰT����w�?d����H��o-�.ў�c����χ10��%��hI�-й�{�;�����>�K�_�?�T�n�@��#���5U�iՄ&v�aɐF9.�V�]����Q��� �T����f�o�7o�t�Vm��pQ#p%-R�l������Fոm�Pr����^3cV�s��Q,�*�Z��+U{�3��Q�,�ǝD���5(��44J���;K�PX��֬RD_>�f���D�� a腭�0�+�{*ŊB�֬!��DPc�t�r��(+ ���M%Z�9)�f"c��S[�zP�(e�z4c ?����a�N�-��=�a��Bg�X��Lt�X�ւI�ѣ����~,�r77`^ -���0���PY۞�a��+� ��.�IbxC�&����0��Fcȭ����q~�+�r�Z��M���X����ҡ������MI������Q -q��/Q�K���o�� ��.J�x��vW��:��mBO��{��'�K@��Z�S��b*��X��4qpi�dZ�b/8ɓe�J�R=���Ѣn�q�5D��x4��P�K۴%�d��+5�T�T���cRQ�K�a�ϧ-����B*ӶE�9�7zX}�>��O�DZXi){�ڿ"�\�59��74͜��du�z�3O(�,��ٱ?ml%�!EEc��&W��)m�ժ��@!Y- -�7L��|l�R�d�|���b�v���Jp������% XN1e���7^��dQ�����Kp�9� �Tak�0��_q�A���c۱�KfV�ݕ~*�}v�ڒ'�u����;�N3�,��HzO�ݻ�z[{�d����DH��B*� -�[��εjt���J��Ҡ�vk,dc�`�xJ��̘���2E�`V;��)=b��N��nU&�A��EZ!���J��#7���r��0�*��bDG��p����3>#]G:i�tF6�i�9Q�,�|�(A*Z��,���w�띑�ւ��f+k�/a+�r�쉇k��N���#�}1������8� ����CWbJ[h|et&�$��K)T�н����'����:?>�Il���|�뺙p�g��`ѿ��F��=�07�Ħ�j�l%����T�bCZK�q�.(���3TqUL� Mp�k��d���M��FA a<�/A�S� ����n��:��p�j �Ut&�*�K�;F~ ��) ���§ڰ R*����Ӡ���ϩ�1��Lɞ*ZQ ���M%��!��G%�k���y�1�K*�S��h]:��c�)�/�� T��և)x{f�jB���a򹷵�bP�B׌T�}���uNE�ǟcm�ߋ便�����B�YT�1��={����gs��4�v��0������`87&��Nh-�����<��@T�*3�Z��;��@,C��|�hۅ`�M;�:�$}�&|�zQ �_�x�a�|��%���F�d|��.���q2�N����##��'W@���—ʰb*�Q����qഴ}��r.S���Z��^��SR�)���Z"�Qȸ1�"�r�0]�����8��=������\������AVG����#�ч��ӫX|�O���@%ݚl��J�{XQ(�$�S� ����aG��0 3�u�52];ЕBcײ�| �MZ0�ܦ%�[]6Tz�1��1�W��pJ�4h��^{�\lAi��}t@�0�%`y�I�b�ݰ�rƯM��*��T@��f \[)��s�EVU5�H�4l)����,�yA�[�O*CkI��4��r � T�X�LT\A_(���2��J��m�&�i/Z ��� H6�k4G0��~M�!|�.>�?-����n<[Lo"����|v=]L�3z��x��=?Ng�C@��R�Ca�!��(&�~j1p�4u��r%c���R��ޠ�cR�ɥ��Z�P{������zčDŽ_\������֙���4����?E�����߯��5!� �v�ǐt� ��j� -aD��T����<��. �Ol�b�`IS+J��Ҳ4�Y�-�nc��V�0�3A����DX��#� -�[(�i�9�mD�A,Q9;H}��t6>9��g�L�֏4�-�FZh����Y&9�(@*:(=v4� �1�TW7F�K�Qh�RV�o�T�Qc��!-q��uGe�u'F.)S~;x{d�N��mo���.� (���x�+Lp XYR��{w��r��]=�"��T@/6�@�P)��s�a7M3�@�<�sRv���&���*�ZR�W- i<�Q�T� k!��/�oB�R\�}��� 6�t/Z�H�7 H6�k�&0Nz�i���>|�>O������b8����^��tr:���z�p�=��'�}@��R�ue�!��(f�0p�tu��r!S���Z��^��SR�)���Z�Q{��������� �8&��8T7_3� �q��:%�u�*��y�����oMH!�(��Pq��� �.Z�*aD��4����<��.3�O ��P0��5� "in ��zj��N���"��BP��a;T�*{ҧ��n#���u'�.�6\��'�n�.܏�\�2��זWKw���� tX�/��Y���G�� ���w;+Q�xA����� -�����}$��6 -VZf�Y��U=���E�R���Z�4�u���נ�,���#PuQ��O�V�����X��=�������Ph��wY�g�y��{��2��m!zu��i����]�+ھ��ҧ����l���B��0��ҏ���nx&7����<<ܪ�]���p����?�T�n�@}�W�P�Dp�ǤJB ��-H��(O�bf�����Ј��b'T�W$dy�g�3��� �^/�Ld��h�TRe�V�=ieu���I��� -���V�\���)A�i�1��Y&�,�ഏ1,EB�H/]- �DW� p<�&]�W4�2Z(��1rQQ2�wAd�@�� B��g�x:Ò�3>�v�#�t+�#-�ڬaI�D�JN-r�� -O��3aR��rcd�r�k�ƮdI�b�MZ2v�MKZ7�j��n���-b��o�.0��|�t�<�P�Ae�5:�w����̥P�G7�^r��&�^pAx)����@��R+���0��z <�6Y�J ?���h|B�[̍��Zr�[% y�؀(�U"�55W��7�� 9��>�m��ez5��H��/�m�ר3�`u��0�F}����71� ����x:�`~ ���jO�3z��pv��O��U�,�T�TAL%;��^?��[�:��� �SY%2�L?��SR�)���Z"�R{���� =��c�.��5�j�+�:���ǦS�PP�g��П�S��5�]R�0hG�@�!�ZX� ��Uo���L`N -���6`��@aL}m�����ʃ^bpI������1עD�ɭ� W��r �&V)[ג����Qn�E��q��-Z�C�ۦW�z�$}w��\�a Q<�/a�c������në�p�D�W0Y�/�$Z��� ���E~��c@���§ZYĔ[G1ۙ������O�Ɣ�xJ�Dް!���\>jT���Hf47n����i�1�/���[�KV"e��ݤ���Q�[�S����k�d���Hbh �� L �G�qh��,IY+��Ď)�w�L��������L����%�rql�ג�KKFm|��D$ �l���٣D���~F0Q� Z�;*����C���Ū��Cg��B��v"�ޒՖ��w��_O>Wh%�Q�[ ܳn�4�jD�j��ӅJ�jRs�w�C����PT�9>�=ءx��A�NK)�����E�.5�f�� O�9o�yg��o�Tao�0��_q���U�A|�[�Z�Riɘ�ir�kb���v��i����t$@L�����޻w�w�M��t�V�B�JZ&�����FU�n�P2QQ%�Y>r��st��Gi0�<0j�G���ca�Z�3��(]���%ѡ��Z龺���A�gVh��5s�ѧO�Y�X–(;|.L�#��%������T,υ+�*�jO�5L�N-W�N����:�ڔ��z����F2�O<�%�;�RT͘�J�$����c -p��p;99���@* ���쀾�D��� �\r���k��!��8�y)���a���@ims�]�͙'��f5��#{_IV��}�cX�^'t��� ��i����D>���ٴr ���>��}�^���P���Z<�vF��ӟB'� �:z���#rcK-v� �xQH��V�IRŊbv0O���'W�Tk%���k�#��m؍ -m�[�dF�Q*��q�+nc:�?�I�{.�nUjL2��I�"Aӟ���p�m��{��!��Q�[����u����ޢ ‡�f;x,iC�$י-�c�� �����ysj��<h}���x�2v��Fԓ� �~+B�:{�ro=�h H��!L- -O.@cs@Ѭ���c��'���X"�F���%�i7OT0�R%��!{]���}m5<��gQxV��|�u�epwG�"���?n� B��BH�������~l]�wtyd�"Y GO����7&�ap��z�����O�T]o�@|��XE<�U��ц4�#�)U����q��w��9n@���^�� �"����ή�_���ⳳ�`!+�\+/����E���L+�+\/�ʼ��T�;����5��ل���Z��uH25"�K�7�a�U���4[�=�����B�큉���Ӌ�DikT�M2Đ>]���6D��th��R�t�j�DDQH.-*��" �X -[��\����փnZ����XJ��ɸC�,i�릓2P�5c _(K~;y'��Qw::}е؃��/�C��.�M%���Sw�A�$z�.�R@o�a |���{s�m�ND <Ѷ�{��5u6�毉t��U:G���HK=^�Ab��5q�D�����R�U9f��`h�K�z�$}@m���4�$��i�dc�KV���+����L�U2�`y�ez���eJO �������Wc@j��'cY1��Q,��s�i�|rs��9�Se#J�R�І51hk��ZG$ �Z�0T���i�5�Kj�#��l�u��nR�X�����8����=��R�8�w�g[�-�7���օmט�#v���=���j��������iU1Ϣ�21�;��E�iG(l� �$z�s?�>���+�^�+B;��t��i>Xم���|�D%�]"��� -vZݻ8\M�&�`Ө<0yx�/%jrr,xBj�VE���_�GL�X�������kL�w :�`��Z�B���9�.>D?�T]k�0}����A���c��h� ��ݖ>پv�ڒ'�q����+�NR�FY �:��s���jYy�`����@H�0� .r0K����в�Ee���?�X߳�F=yIН�,�������t�b =B���)���E�,N�p�zER�EK�T[�ǵ��bX�KF�BD~��f� dD��S��8"�p��;\C#�3d��)��Y\�A�X�œ��jNd�V<_��@����|��N;2z�KKZײn��n��=��?�>�)]��^��׿p蒭AH��}t@�0�%beUp&�n��r��6��m��9) ��k�LW)��1չ�7M3b��H���$����y89#��N�5����<���*b������t�r @,E��|hѺk��2�M�(��� ds5�!��| �Y8��Y�uq�Cp{̣�$��-��Y4[��m -���"���7C@��R�K��bʭ���S��vK[']a�3��<��,G�� -�� -Uɵ-�&�)�Gɍk*m�G����d�� Վ U�׫�S|�Q��(|ߝwS����� )��uS�n��)3�L �# -�.h�I�]�y��i�uL"wVօ�UwC�E���n7@��4Ԭ�cUۈ�J����v�-���Z�{I�����[�ҠH���nx�� -8��oc��� -` �9�)�d��{ݓ(Ve���v��,#����:݃�Z XI��g�{VuLU������DK���N���s�v+�E�q��� �g����zvYH�oNv��5�@Ӻo�i�w�2�A����xϻ����U�n�F}�W � E,�(�ITGB�Ta2 �d������.E+���3+R���1�� ^�r�̙�/�M5��p Y �Z9!�T9� ~Mo����e�Vɾ��R�o�)��v��G�������}V���b�v�0 ]�����,^\ݢ�����R�##W���1"�� ����Ĉ>|�L��9� 8�g��@#݆l��F�-�)��2ɩERу�aG��0ל�jod�q���ndE�.%^t`�1p��j��-�WuK�>S .���OpI�4l��n�w)������st@�0�%`eUH�R��Vw�A�� z���K�p]�6�U� h�f"<��6yЕ|$f�x��@w>�T��[��ǫ=��P�bEX �p}��Ec�q����v"�陴"��7 ڄ��pC�Y�c�&�/?%�evw7��p��n�ч0 ��-`ݳ�a�a H�Q*|� AH%3�YOOVK�'[a*�2��T^�!�;4~@*4���ZK 3�G)��e׳�xL��{�{ˡ��J�.���]�� ��U�����g�%B - ���)�~��l��N$Ȣ�9�����s��:�}H�fE3+j���21OXmϖ�i_�/� -u�uk�P:Tٹ��b�4�IO�k�5(M��M�L�� S7�ή�� -�(�2�EK*���R���87��]x�M�R>���N5B�$�^����><�6�j�`�e�> �U�H@��U�a><����Sw��u�;xW>ƑF>��H��¨V[E{ttszmIV�.�8o޲}�e$>���oP���M�����hͧ/���+���rwgE-�޼6O%?H#��q���zL�����ÞNFO'r -��È�)�Ey$��:�F���h �1i9�(w��Y ޚ���a4���ap�{;��=koG���+ڄ�I��;�N��h;.� K����ˆRs�8���dm��~��w��n�"���j�TY��ÓWC���W)�� q��c�]n*�a�E�(��:]U�D��4%�ǯO�^�s@�ϲ���Yu�d��͋b���,î���V��5!� �t�3�4_�����*-ʫl ���PN^)dJ������9k�r2F�7�C~2y$�6�ɧ��Sj}�܉U^�M��"�8M�� �]��Y��Rk9:���7 $��� E�s�5�Tr������������$!|'y��S#��&����p�M~]-Ӳ����MV� _މd 8M�K�t�����2�����j1�֥"{�̔)a�� 0i �P��D���_O�NF����O�=�߾=<>=zy"^�/^�ptz���z%���-������Ha ����A��g:��Iဴ"W�\��l�Max��&Y�b�ߤm�uZ\g%.l H΀8���H�Ħ����f�����4ϗ���I'{{ ���=Y���#� ��ZH$�2�DQ{ߨ=Z����rI�Ӑݤ+��+=��Xlh��+S���}�D̀��2�-�K� i�2p�V��8�rs  T!�\�z� �I������U���uO�1K��MU$�c~�`�ɺ����B`_��n���g�m���D3Q��~���`�T���o��3�Zf��:)��_��W�r�?+d������F�ڿ���/�"�-��G��9��X�q>���?�_�U�������޼�89}{t�#���,��m�,��ě"���K��M���)>(�%~��iX������d��7K\���E2ǿL��?��,�lM3S�46� 0��WWI��NW3� �92���fc_QO�,�g�gjz��ϘwD<�v�NA��E�^o�൤(��8��,�A�>���T:'�ï�J�ڂ0p��[#������^�&���PIq�o�eNgcZ��35�1�)� �7f�"-�� )Pw���,���#�f�5�}��.2�� �o���J�q���8��`�_����a'�5]�ӫ~�5�����M>��U���������J��F35���ږ7�r ��D���1��wٮ�ʋ�v��a�����U.-X&�W=3a)t�����\wL�\������]q?�ߛ�$�(E�n`sDcNz}y�K��Ai��&�4!z�]�߰�r0���9A���3�H�\���>}����*��5��;/�&�(��% ��ӆ���{)"�h�Yy!��� �ea�]��0䳏�Z (Gd9#��!�6lg�<�_:Q!D�[��z�}��Ի-�s��_��Ez��P�`Tw�=�Z�RhH�`T��� ���ȇR�Yo���7@��!�?% 1�S&�)P��&!�����n%H8~}����O���Y%PGX�]W� X�f�GǧB�|�S���3D�r�_B��H��v�&�ʳ�#[����eZ�NTk�Mh�.��J��!��/��"��hE�:� -6�f���1���f�&�l�*h�� �5���QH*i -� -��` -�>���d��C�;��3��.����|�!U ���Y�u�ݠ���l���0\#޽r�+���L�D�Q�sP�@� i|H�Г�2��k���g@���K�S�?N��^� ��5 �N������Q���,��ހĻ��&$m��p��5a�(�K���e���{-�5�x \9�� ���?D�h5C?,PfoGG��،C�٬� �l���d!ʴ��+ �$��C�#9 Y&$Ǘw�*'/e��S�5Q�$Q�C��;��Ok�+��ؔ�Y;�f �JKO��o��xoV�N��������Q5��}�J��hm�\~.���-٘��S�@"�1���� �J�W�j6 W�7��l*�czqA��L�#�~vТ?�h�$x�i�_=����h{����?�Fc6�# ��'Ӕ �k�b��,�M�w����?�3���u�8���)X� ���uQ��V2 �t����g�lV�L�穦��8�k�,��Ţ����l���hh�����sΐRm��f`�]����o��/������YyN��ߌ��P_����.�jS����[[�S����1q�:�|�O.��'��ʴd+h���(�.�� �Î���mH��j6�-G�'v�D�VO��������T~�%� 8H��nc�W�!�/�v��a�g�ƽ0 3�3g�6��ӄ8F��ۉ ''ȣ��!�l �dt�x'na��.k��M�}e�T��ҷ -Y��c8���o���Ձ�y��g5.��y�q?���0?��1�5���♺��2hu�W/��뭣Z����)R\W=fe&m����H�ꘄ{��"�Ԃ�2n�Y �p� .rS��٬��^z^�O���T�m� ;�� �`CŧO��c|fX�;�d���ǘ �a6���>�M̽ Z�Eo���>|.��` u'M<����lP��7�#ϣ$Z<*$��� 08�5n=��uuga�D5 �?�W�t� G�}��������H�Q��^�Cg�����7����K�t�b>�n -��+�jiF�4|ԱU#ֻ��~��`�[UwklQ�˹����e�_��n.�(���ms��y0�_�_��/[����}h!�!:R� -�r���l��������� 8KT���>��Y`e�TL�ɦ#>�!]�J03���"��&-�8ƀr5�nF�9����3J�1\�Č��ߔ��g:zC�n�����C��q�����U�B��c���^�<_��Y�g��Uh�����ⳕ��r���j��_b�!�)��x��}m�)Hj�v�Vtr������$�џ��5e�Qv _��H��F�՝F�ȿ����ּ��zD�uh;�,�V���W�>��8��8��۵�]]�2��<#�Zb��`�C�$< -�L�c����ri��X�ɞx�*�j�h��=���cn��G2�(8F��۞(�K�c�5n?� �1'(_xrA��yIg�`� 7��v+d7Gc={�-�>c,��Of-��qaB� �ab��.%�|��2���[|`Q��� �ZiU��p��:k��^EXYt�&����1�5]����R5�kv~Y1�<�@d���>�L�R���:��k���9]Z-c=n�W����U�7�?QP��͆��j�,�.Z�4�v��w9E4m���I���>�"������J A����o7��%>�b��u���|E6�閲G���q+�q(`n�"���Ag2VA���4^18�* E��<+����b���6�+�)����Mk�U�x�+ h�ꐎR�r`t�=%2m��%���JE�����^e s��DJk � �bQ����"�ǜ� �)��V�@'u� �����F�k<�ƭg����-˗t��صq1܉b�L��j��Ul�:[qn�o����:���<��P�YS�AG��������CeE݆l�H�TJ��E�� -�3t;�J�6M)��E������Dn�$pv�Ǐ�%#7�-$b^ �:�XWv1�=�i­^�(���'/�����Y�h_����M4�A�������Q;��$�-k+�O��4�"��'�s`Ϝu+_�8�u�_Ú��s�˦�8;�N��l��QF�����b9�C�V�`�s̯0z��G���[gO��x.vwa��0V�](GNVMX��g6�Y�q|���;beJ����u��䍁�"Su�����A�X�30��l��8��L���,sb�:Y�χ �"�Fܽ0$�����>��xFR5Ӧ�U�m[dYL_Q� t0��?�M��h�a�@+}�� �7 �a�XC*E4���~�*��\�B�Ϲ^9�Tz�M�fXp '�Ɗi�t�9��8�t�S96�I� ��i����j�� ��m��#ʺ�/!���_�}�i�%}��9�S:#U���䮬33�[b@3���>)��RB^�*�b��QE���nx�&���/},U;D�a���d��%�#׋����5y}Hu�=��*����:��I&y~�~UeXC���C3?�R�8���^�:���t���v�WY��VM��G��@��pqݜr��y�.gA����c�yȠ���gbO|���YO�R� � �P�?t�g��V�k�8dp��c�S���w��]g+.U�6�[\^�$���ppd%O'�1Ä*;�X�F�8�}��9���oQ� �`jv�-�Ԏ�!�K��>�I�7� ��bB�t,���縲EJ�� -��7���6)UeU�2r�R�a���+�|�!��%.� ����dG;�K��}%�^ (/W��z�*R�u�%fS�Fs�]L����(���I��,#oћ����~�� ;EV�^���g��ɣ 71����W�K��S�� -G��, -b�Q�3SVX���5e�;63W�^/�;�j����;�q�s��i1��Q\RyM� Y�%���%��+?Yg�!G��6�@��+G*�Ϝܑ����0y�: D��Bn�+#�(�F�R?�&�m��Ƚb�RP_�K�Jtʉ�K�Tz�Z�3��e8dueu�!��5q���c5Q -x�������k���:8�cU�g������O��q��{�R� �m�3@�u�-2 o -p���]'8���@I�6P�,���;N�߷ꫮ��?�xXŗz =��"�������cg�����~rF�\�W���������0�����0�w��QA7I�D2�5�9t8Np���:-X���h���hP�9�j`V�.!!}�a��G 0Hp��hxL�����@S g���� �I�(P��_)w�6�e�|��6uim�9�'8��p����Vo4wj��W5�������'{<�����R3рE.g�|�y�B&We!GV3����N�|����f� Y��Y��0;Erۉ�ز4N7��X6O��D�k�~��S�A���\�ŝ��"YK C�����gqN���k۳H5F�5 =�&��י��s �Ն{����&M�r٘�!]q*���Ӧ����Օ�5��|xu���6 ��6�?g�&�mS�;���������yf �Z��?'�L'A�6� XȌ�o���n8e�XDmiB�/gZ / 2g˶���x)�$��/0���0�S"D�!��V�-K@5 <�l�v3qC��2T:c �(*ȯ�����~F �=:?óE��8�V����ۻ��g���G���抍[��`�ri\�-�E`�����S���%��u��a��`�+� ���i+}:#�v��SXbdw�|]f�x�~}��x@O��DT1\refã��E5�����9�Q=��kY�+X2�ǰ�Fd�1�L��kҼ�����S��?�*HK��`�9%����<ʛ$<�޹�f` ���Opf�����4ĸ��+�N����o�A.1.��;i��M]����Z�{Ԫ0�y_G[����뭜���;�M�.(�H���#���;s�ߧ�rs����7��L�������H�W�4P����QÐ��z���\ � �?�ȗ��YH��eA18x%o���1��l/ �)�ŋ��E_L��=�:�Y�!<�ӛ���~�˜h�0�&������H�q�)X�6)��G��^Sx�&)(nO��v.���ޯz����1��9���u:�����$3���e:�?HASc�v�9��C�R�W�G�F(���e����$3/���@��p����kf��dw�S� C�$�4�է�Ҝ=��f�� 5c���3[u� ��ş-�� �x$��U;�-��zP����}17��bJ���rm��LD��g�����}]ƪI��XJ70��C;��8��w��=#D�SbG�����9Yfe��-/2қ+Q���mRZ�_���i� [3(��M+��\��k����]�Q�Y��*z��ά -��Nu�t���]��f��γ~��2 _0���)R� )��e�5)S��8�^a���B�q�E��>CIU�ڕx�m�c�@Q�@e>h��N7�A5m����8��Tj��.u����?N6]�<�4:nT��:��i{�J�'��ejy�V&�� }^\�����>�E�5�ck� U�����.-���_�%G,��UQ���[��$�L�L�H�"8Q.��O$S��F�W��6�(|{��4O�x{��Ɯ�0�5i�O�ȪjdV�i�Ň��B -��]�i��L����Q�S�Sw4߸q�=�OJ+hS�}����(SpoS���bI�o��\��ؘ�h��>�a�����#M�TZ�Jً�F��j��k3�8lV1eSX�Źݒ������ - �9�̔Jwqp����ɹ2]�/���ѳu�x���h�sX���JT�4��t!��$_V�ļZ��j+7ˏ���ī�7���Յs%���M��L����:y���X�lUYs@/,��]�u��M�:�I1��^�� /�kH���v��Օ�ܔh��t�彊SQ`|��������;X�%������X�Q&�T���-�H|k/�H��������Z��+�(�$� f\F��/�W��N�B$��VlX�+�UN�*Bk�w֧�2�?�H:m�,àu85���ɾ�N����� ���݇��Ip ����T��}t/oW��{��ks)9�7j�-f�Ӄ�&vq$��� -��;���~�b��J�޿d]��Wȉ?�L��w�W�2 -��W�w�ݦQ�Z\��w�מ�B�?5�Q4t}t|ک_�?[t���\�j���s���_�Uk�f�B����U�-����du9�fn��������_$�����x��s�N���{H�ΏM��j�����w(3���v�"6�%����� JN}��,�����(���b �ޙ������0�0� �U�m.W/�;�KUW֟�e3mu�s+� -� ���d�� ָ�R�Y�%�l�.X s����ꢅ�Y�7�E;����L�P�)��ZZ�c�XՑ�0ծ_�jw�� mp�%ʬ���u��q՚|�:V�Q��6fǹ�?{M*o - -F��؍�*^n�sͱ����GW��3>�����a�~�Z7�^���m� u�ec����\_X��g™��VJ|�n�'���k{��w�v��5��Z)�x�1B����4w��J����o���ҜY�쿐�i����֬|�a�ޖs ��t�� -��D*�K�, -����e�#Yc4�P -�\��_�cE���2��\4xەhқ ��Js��Ӵgj�i��A��?�کOd�u>�y4�Y�6�����i�9=#�<�ǣI E�D��I��[��?�(>��~!1ħ,� ����n��!���S�ym�Rb3, #-�j�ͅ��nE��˕��I����J�#d̙�N� ���9Ta=���_ꛯ�i�|&_�x��؊Xo7�Qf% �O�dl g��\�2�f��Q�T�H -{y��CT� -%�_:�:)hvз�� 3���z��c��(8�2+��S�)�U���EV|H K��dU�������f_��Q:���_�U~��|��+P��i���/�R�$<98�Ȗ_��^z�N���&~�MI ��TDfUj7��L��=��X���'*U)Z�� -̔�f>�{搻4��]O��*�վ�h"^���Q�����w@ENE�F`&��7J�ނ@E�h A��F{�Y2��2�>�������aM�e�Ř�/� *�]��/~�=ax� �z����������:'z�O�hD!�H��OB���"g���~�Kp��9o��:c��/�l�$ �<���g�n��s�^fv�b��7�)�����M'��x�v�HY�z��[9���d8U���8.[ 1,`[���$�� ո� ��+cMNA��>:�w~�`�*$����������F�cQ����������.��2�r����-±��Z8��Be�e�����H<��6�m����RW�,I�����y����Ymo"G�ί� +�+r��]6&��J� �jEV34��azv�ǘ��߯�_���lK�I�dcf����z�e�۟�E��zՀWp-b�L4�H��_�{�(�~6ϗ<��L�*�ί"��S����S��H��e�e�L�2�Vt}��g N�2���졙��/��"�y�9�:#΍���x�� -f�)�O��z��J��+��� M��T��,����q�3>gٔ��d���|�A����H�1�2���(k����e�B �v`��w4D!���Z(@JMw�y��h/��!W���1�Fwѱe �DF�E�=}��� % � �,�I�~Z���j��0�pGf���+";]�F���oI̕B���"C�'k`)z� ��e�$��Xe�x2o���$�T��]��C���5�#���s4���`������{��W#����7����f����?�L�� ���2<�?��� -B�O>y�-.O*员��K�9�s�����Hy��R���)�c)�!�"�Zl�J.�{2�ji,el4.S�]��#�ټ�5�|!���C<4&�bJ��T�� |��1���!� ��D�hdu:� ��* ��7  JS��l"s]��� "�4pe�@-x{֑V����L� O0ǡg� �A`��M���OB��͖\�{D?�H9u>�2q�=�87�`3�GVO�-m�������L�S�t�h��S��J��b���ȫ �"ZP�[sF��$i�/.�|���6_��i&��z��;K0^0/ʹ�䨿Ū�ΕF����KnS[X�^�Ό�d����s�Z� ��S�����>�X&s#�*�w��0�r��%�I#�5��i�M��[�JTK� �!�Xk������ bf��> �Til�������#^�XWec�3Φ�' m2��2��I�6V�ٖ��U#�#�a�l�o:O�\�mu��I��ڠsMˡ�+�h�8^�%�~��p�d[�+]��i,4�%-�{V���5��M�w�$y������f ���Ww���`��@iK�&)5�Ғ�=0�0�4aOܦ9����̘K��-9�Z촳�9TgH��c�=d�b�u�m�D��Y���M����HE�3H��w�` -���y�t�5�p�Q��+k�:e�(�m��*��|K����f��]��z����|E��5b_!<��`��_(?�78�f��R۲3n - ט�n��\+�{=K��60��3�re�Z 7*ט-5��8��e�%p}� ����*;��4 ��dꔑ���#3e����R�x�T�e��C�d�yCʙȸγ���[j>�� -fy�����&�EC �ܫ4� q��i��ޑV����FZ*�r �ӫ6�i�m���׭Bނ� k9����7"]̓��[���fa�^z�ɕ雈��L��)�W�y�rS�m��q$�q�'̱��������M�"����bb�aM��ac����j��5�~S-��n��䋌���jM<��B|{�.[ ��ăb -��U+�� ŏE��ūԱ6�G����� ��j�W5 �Utغn���R���G�Ci��lWϮ2_��qZQ�K�9�H|Q�A��=����A)T�<�rqq{����Mpe���Q�&�5�^�޾և����ty�*wC��j�8���=Ŀ6��V,�;�n7�=��m�B>��\����y)$�� ���Z#�7!O^ -�49@�LYv0b,*��}G�v �4�elT�=$�R�3�� �n�Ʌ�I ̼���^�q30�6A��ˋs�XZwQ���:@ -�sVГ�d_�����Ɂ�A���J�P�U<1e>��|1m����PZ�[F55|�O���Ymo�6��_���t���c��u��!vVEQ�-s�I���x[����"˖���Zl���-��swϑ��/�y��?}�"O�O��0� .b挰?�S)�L�@%�� �aI���p)H�R�{ ��oxĄf11��2������*F.d.bj�:�����L)JKER9��~H�FBŘ5�#d̘U?�� O�� �F��k'��a פ���@�c��iJ���T,�*ƀ#�-O��B0��<{ e|��Nq0 �.e�C�D���_A��]�ҁ(��w�Gϭ�.���䚭�v�̀���"K9���ѕ6��w^��b���9�.#Ԡ4�#dnLv��Eѣ��TI?��Ȏ����� s-R�5��{�`<]��W���)-0�6Q���B�"颴EPM� -��"�^]�Q���`L��6y=�]�v8���zB������|L.�����l8^����ޡ�O��Y�0� L��La�)GDY\���V�ϓ�X�g<��D�ӄ�D�2e�#cj�5�V��1�ǂEk��.ypߠ*�W)S+���WJ�O��(w����Vh��Ӷ��J,1-sek�� -$p����: @��ˠ���!���⦲t(����B�� ��r��1&��(E��(��v��0E�8�`$��2[��܉����RAf&Ll��HX����P�h�Sw�ȯ�ۿ���O�7p�E���6}���`[�����kf�W�� +Wm�����py�=���!�сηx��G�s�I��^,�����yөQ�*h5��ʹA�,� Gl��A�^���#�$:�;>+�L��˂���!��i`rF���ǩ�8S�p&2B��$T�""M�~�� 1�[i�E13!���V� �d���X�LR�+���������I����؛�ۃt���)�}��`cᕐYa0,� �a:`Ñ.��f�M��u��J*i|6����?u�} �e䀇��9�n���k8 +N/��ׅ�a�4���*_�JdX<�C���D<�r�AA��M�2����R�/�)цuq��a@#�]J�j~z�����‚!�ύv� �*N'�{O6j����ߘ8���T��~�d���� +�fh��_�B94샀Nԙ�$�䖒�'h����L*�S�$�r(�8=����b���"6�EJ���=f7M� #�M��Y�M �'c;��}��u�FF�N��{e��ob�o�b��i{���J�z�4� � +�9���{��{!+���{���4��.&�h��:����U$� 8����J��g��p��1�)�A#������0K��ULL%���0 +���Ȋ�Z�۪ȲF踅�+N ? �Q��v�8_r7ȕί�׵gO)Z��ʭ��rF *�\i��x������]I�d�vs]�����m��+j��ODS��z�T��tAZ�B�ay|��)� ��<ڤ`�����h�]�I� � ����P�Z�[�D�Q���iHB�q������"�Kr��m�o�Y�#��t�D�u�/ h��H�[Zt�w�:�Gi�����k�;��s�aF-"�i<���[����]H#[L����O#B�̩4���::�߶9���`/�-��|[pй ����;@�K4slf`�#R��˅�z����&����m�En�[�60���.���r�>֝�[�� �HG{��m64(:*3{y�=>�xN����]X�ͣN_#�T��1�]��м��{��>aroA!qf���Ad�~�i�BgdLA�6�1�\�P�Y�)"确��K����i89���4��KRIa}��޼S��zjh��u��g�ݶ�Q���5�n�hF��h��m�����^��.t���c��+��(�7� :}���8����t~;��g;�V�j�=�⃶�V�j6��D24�kۜds��7l���e�n��Z6Ec����~i�E�}���v��)5E얉���Q��JGG����d�(��nh�F2�}e�P��6�H; C̝��p +o�?R�,���>��"��BZ�����5�7�h�`D��T9nf����t�mJ�;��0�&�𥂧��d�h)���l�4L��W�%����]�,Ϛ%|�y�Y�]�~:;�?�Xko�6��_qW�]����m�5������a����,�4��To��%);v�6]� ���}�sH�����{���{�.�Bȍ�Bj���+�3?7��W��F_7�G �F=`��e��aް�Y-� +afJ� +�pi]�����r�.Ђ�H�������E�<�XZ�j�F3Dv?��O�/���� ��J_�������P �($� +�.�]q"dhq)lAe�^[��<�V�u��Gs*ev��q�q +� �MK٪:6#�wh����k������g�Wl�k��C�p��.�ڃԐ�U���9[��#�_������R����@x��/�����xܶ�Hp�#c��T������tv�������V�X��� X�AԵ��X(%Z� � 5�Vz��Y���1m��R����A�����`2{ߟ�&� ~���z;�_ή�Ϧ��� ����j��d>�����Φ���O����Z���RƂ��b�����%��՘�R栄^6b��4�h�5ڕt4ZB��Jz�#ӽ�K^�"O�"���(�x}�2�ƛ%j/��1/%"|���!�$�9�X�Ը��?��} 6��y"��ZX�B�l��b�VLx'D��> �-��ػ�m6�F;�S���8����%ZΊn��g��]�B58pC�#�DKHݸ$�T�A8'����Y���Ͽ�u�:��K�.WM�UuyL*��,�R�eYS���K�Nj�P��xq +��t���X�fI�����I��ߴl�9�F��\X+���^w< P�y��`��EN҄Ej��e'�x���^��Cm���`����t�ŏ���[a�닟��gm�ǜ��h�{8 ;�@?L�'�����Jh���֢,g��@��6� �W�ݼ�- +O��ms�ֽ2o�7HP-E�S��:8$�P ���:d�A[ɼ��v��N�%/1q"��N- H������Q�mf��D���˒�Z��UwR;Y��.}�p��[�j��D�񄎱���֝8�~�E�������;�=�M���#&� w�!� �(��TR�� i���x�u�tR�i� +�Y�HB���5����:36+ ��/��֊:�]j��|QCN�I7����|kjǗ�F_�?Z�7!:J�sQC-V��{E��� -�;�H�-��L3��!����3sc�fT�@0�@%@f4bH4MeA���BP��:yP抉P�7���S�����<�^��@"h$���F�:��p��~z -�wQ�b3���)���]���>����X^�l�0�#��f٫eP� ��w�.�8O�GP=ݔ���M�Bs���)������.�HhG���e� �f�da��5� 0�ٹ(v�E��t�,�g/\�@gԕ�x�=0�����h���b�3��؞�i;��ʔ��4a�U[�-�S�� �#�զ��+>�����N�_��Ch�N�~ſ��`拶8�J|��,��{��Ú�����q��A��yM���T1zsy|�=�<��/KK[��{i$�=-Vl�����I�F -wȥ�J��<�Z3v��dQZ�{��샰�� -�.C�0�t=����k���O����KoCіC“[,���P#�al2U��[�?��(��j��.�_�ԅ�*]����PR�c��+Y�o3�� ����W� R�`�Z��}vF���i.Q_a����V�|n�{�OΘ��`XkX��z��5�����Ez�ފ�,��;99!"O�ƁP.����]��˲*�!/��1Yл�j����>"_�o��T ��7��(�]��R�+ �^�+�?ra��p��f6�O[��lխ�A� 9�B|�U������c�9t�@[�b��!b�ǗWg�W�g��u]b�0͟��2�R��z^�=�ޝ�f���p��i��Pq����z[�s�����r�-���s��p]� -����j�4���3w؍� !( -+��;A`��[{�=�oq��|����63i?x�3+��5� -O�+$�Y� ]h -��;�� -.��ϸ�����u�2��)�%fh7/�h�Ѩ���"jF��,�ox|/V�j�k?|Q� ��)��f�!_���>|�ojO��n��Z���SiO�M��ܽo�|���Z�s�6���q+)գ�}�qb9nb����7��N���@$�B�:�����ow� ��$;�1��H��b��]�9[/�G�/�� vF�I��0�K���?�wI��H\��,�<�q9�ID�E,$Oɂ�+5H��0�3�&D�|��3N��K�.� H�a�������VH��g�U"�2�fH>�_H!V"NՀ��D��fr���Av�? ��l�t cBŶ����@��f!�� cx�"Ap� � ���� ˔%[�P-�5�*�K+�҄-[��!Ɍ*��1z� �*�|�:0'���V�5�^�')˔ȩ3�{ �)� ���Q��f���7C$��2N��d^�x���?c�4]� ���v�I�A"C���g�����B�9�H(��oJ����5H�)��-� -9H��`�x����:Aq�r�YA��0�5j���ո�~8_�{�׫�O7&���������Ř�ܲw7��&W7�pw�ίÙ���~�cL���k�J��!ZT� -�de@o1��"�a�ŋ�/[$!i���\� -�V��3p�U��S)�Z��`�OH��I�D4���x�p�����|1�+�_�]~H$��T�I򩡍v��%�s�p��'�*�G�X�T��U�TX���dE�R)��3�D6�W<py�2��V����� ���'S!�<�`+� .e�l -D��b�[fã�+pe�i��a�����"'��i��޸�?��+�^/�����f�ڿo7\� �wC�klÎC|���L���\��b[�t�X���"܈ذT�]s�We��h_n�J�,hQ>~�D�" �N�|֥�ڂxcl���Sg|�X1�YP�+$ th�VY����P�¥�eM0��D�Lf�6d��O��j��$Z���7w�Z�d�P>�"�d ���%WDa�DQ���-�IqR�M����� -07D�c����tT~�=o�I�$�Z&2F ���J��=o�Ko0j�c3�1%\��1M'W�CGmG��S{y�8Z6d���A�� ο�������â��� ��L&[����B$�DEѡ�3�0BB���r�G�JY �H;20�NS4�MX��Th������%7M�e���3M֣F����j�T��买;2��DS�@]g�|.$�#��\'��ڗ �w�aUK t��Y�Pl(�K�)�R&�b����\���d!(w��f�@W"j73�o$4Ax��x�aD��[�'+� ���t�f/������R�>�͍���<�!d�za�w�}VYwKI���aY�+�*�qŏ�D�-(�YZdžRP�\@�;��4��8�V��k��B2�DU6�+Ar�v��W+���vMl|~ku�Db# ��r�W�Q6M�n -P�X��F�7T%^tK����Tf�7��3cZ�|��,k1�!���F�S6��E�x��,�`�*Y��|j��.ظ�[�K��8�f�:]̥�! kt���^����D�F�����B�� h�9�����خ'�/�S��ф��)�v�M�ƴ=��k���Q��Y`"�e�I����C%L_$�.�2K�6 -�7X��M���O[�:��3=��d*OS}����a�5W�֣��KG�)D�� ej�� 2&E�(��0U��� n)u; ��� t�6/�mRJ���i€8[MQ5WzN��.-A&��՜�kEvc>I�O���_���S��]���{w(:��3!^�Pw�x��ů#���o]�f -�E���= ,����K�NR@hPH�:���3`��$�2�����)�9x�?d�s�-!]`�c�n���(�H5/~a��"�X�@�ɦ���8zp�D�t`�� !)iL��-�! �hPL�#?��8�\4aLX�#|�>��`ŏUPaP����1���(��5�mWНA��LȎGrZD����pڄy:>�� -O�9zС@ǎ>��k��.7��N�SK�Ȑͯ�x��k��xT�@U_��T�+]\.2,�Gp֟�w�j��9��l�� A�� �T��L=���N 7��U����J�q_zd� -V�c�Iy^9)�GbK��]i�*����΍ij�}��]��W�;�¹O��b -���x�Ac� y��7��5jUc��Å� S���a]��� -��tHON&��������9� *5"�k -f�T�z��������ky:��}�VT��w��=͸g�7��$���'�Yݪ��̸&kQ��9�ؑ:C���w����"G��n�I�V�6��ƣv�8D�%�/O�\� &��)��B�ЀǸ�%86��j��7�� �v���[� -���ZR�u��W��쪇_'���|�Ц�n��t�����R��� +xb!T�*QΤ���e�S�R�`l���$��ڪv���Kٖ�~��d�����]�`o�on�j�� W�:����ty ٿ�,���<��{g��Ja����ǰ��dC��C�G�ި��&u�|��%'�t*�8��h?�Y��G�p�ҭ�� a��΢i�;jz�� O�X�*�-)�)��6{z�ga�S�}V(7򅨯^XJ����Ŋ"GLĦ%��Jǯ�l� y�v�d��a:yG�(���O�k�ys��a�����n�ӽ���M� -l�k� ������ K�F*S -�T���.�*��p����[�%�d�?�)�L��}�N�q|q����]����)m�~F��T&�<��P�:�-;�_q��"C@�\+����������䰃�龃 �+o����]9o�S������{�'V5�/`�\&a���O�(!�� e|�٪[�ŦQK�)l�\X��6��|�����t�\������at�e�����s������-5��uaT��� �*���g3U���n��!�e� -Ԁ��E���.� �خ�� �T���T�|��fl��2���SX���\��#W\���7I8��8h�c��6 DW��w9�m��2=}���P_e#�!�V���'�HD������U>V������\�\��{}�)F�s��'#V�?�L -α�X/x��.�Ի˭@�s2^:���㊰Б���[}�k2Y�}[��Z�S�/�lXLUm�9 ��!��f �?E�C�?�U)h�テ4� -s@gW�(�wgR�N�͙ÛC����ЅWm�wu�ړ�u� �&g��:^^����ظ�����BL�/gl��טlO���̦�{ ;�m�j�b�FG��ks�6����vF��~T�$n�\5ב3��N���P"$�L�:���K��ow���H��;m#��7v`��Z��ϟ���]�r6ͳ2J�$��r����M��<��2�36M#!�0!~J�<�_����7���h���8� ��V2�&(Q[��i@kQz+>Mf����U4�l����aŋe"P����<�IIF%������ �D�<�*�S�x}�,e0����`��|0�!����� %��ȫ�lj����|�/>-��Py8%�&8oĢI^�`a���e @���0�VU��2h -��G?6���R~���c�E2]�i�� 0 é�8�8'�R*YK���Y�a��X�Ұ�@��3�iђ��+�G�hA�1AR$U��)�-$I��d�#h*��ɻ�k'z�*r0�rs\�(fh��3�ȋR:��\�{��ڗ �P�����`vp��X$ͳy`�����%�`5�2��%{VnV�E�K�V�Z𾶦w��|Ui����5^����z|>>��U�"�`�z.P'�h|u�������h��;O����3ŕ��b����b �j�Co](s� �i4c���D��pQ�]�6yĀ�g����#E�z����hj�z,)i��xJnR�ѐ�@ Ýv���s^�-������d<�Pɞ-4Y��H98�CQŢ�����OѴD��B�5�_���zꂧ+�0�Q�=+����~� ��*�6(�*��i�u�ą;�m���0��M �⇬s� ���SP�ut�������r@��R �~%ڞ$�Ղ����> A��d^lZ�p����d��"x)]K&[i4� ��x��$�e%J76��[��k^UQeA�%�G��L�w�<�J��P�A�� /{��z?[F� 0�� ��M�Z�P�2ؓ�_9�}�C�t��b��"Oc-S�_…�������܊hs(=��%��#5a@��bFf��Ş��[�␯e� h. E���Z�/x���0����.����H�c6��z6�9� �C��{���uRl�Dl��N���V�>Lӊl��J�q�����Jv� �'﨏������w��`��{)"��d�ɲ/�;�j����~���g1�%�X [��Dj<��^�@m0�F�$&l���Ԥ&h��%��y��I�艊�[ �z�QEW���2�L��D�����K�+�k�Pf5-��,��M"��ɦ+���q�}���kt��W{iqD��>e�r6�����v�F&��������MC�-�� T�XT��� ��O�Կ�2 r�{�!�<=U�0�Z�b`��o� l�RTtd�F[.�RW���D�bUX�����c�M���{���ѱl,������������K�s���<�;R�,ey���{�-*U�E� ��A8']'哬��!}HVU -���ȩۤ� j�TUq ^Y@�I7Mw��v�mzP-�I�,�־ں�e����>;��m�X{�cw�Js�I�j̛V�N���u��|�e.h��zz��� ����5Q�:$�_�xro�c�m^N�s��d��OA‚4E3y��0""�N�H�@�H�]���$�Z#\�D�:ڈmڷ+��V�}����ZD�kt��� �~q -��� >�A1"��E�a��͠V�`;�Z�G~$}].�|�v����X -h B8�n����Og�!9D� �ڴ��.��CyY[�O����d" ��,P"��H|;2P���+��k���l�t����t���=0��gfS魣��rS{30T�\J2�z'�qP���|׎�'���8\�f` : � DB�w30|T�� ����9�^Ք|�5�w-C�7�H -�z34%�^ �����v����d�v�H� kN����d� �����X���7��� lfZ�F�~��^�M�V�M� �;ڹ���H��]��pǪ�`�(yA"�W��Ԃ�o�D�צ�`U9�#�ņ�T�� �w���8�x�R�誰�T�!Pf�ggf7}�e ��o��'&y�&�Ev#���q� `�?V��ɅI� ^VE�n�$���4�K:��˜��4��1^ئq�V(џ���@�H�^8��� �w��pMm��_�/���hG -zV��.�D0Ԫ�S� =����}��K[�� T)[��4fy'���c��mV����Ѭ�����8GxZKbv���Y�Q� ����&C&�m[�,ߺ ->��С�ZY�X�΄�� -s}Hcu$���t"!���\����tf�[�W���QRN;����q���s�Ժ��ͳ��2�_��-�^�['���uN�S���k�T�|P:��uA�xo�u��MW��3�l�K�/%T���VMVJ��K^��Ww�i&m��g6�-)?�z�?D��P�s�� ����Q|\����\�x���=�J6�U�K?m�㼤�� �n�(�j'����%�>�4r�%8�,]}�ۀ���}0����7�5�NA_f��2�E�b��� -��=)�n��vڀ�#�D����U��|��6�������ɣ|31§�t��0�Hw�����.�{�i~�m�� ��WI�s�x3� ��P�Ň,�o|(�B��d�ꪜ�1y;hg� lL����ܵ�W�硲@ �K3�Q�\q<�����*�jlR�Ne���Ӕ3>[��xp6<�GV-'��bK"�����zo�Kk�~� -��ߓ9��=.�XV�v~�)�O̍\����*_*;mٕ��bj]�ޗ�KG^�%��Zj��� ����w�������m���$�oY���3#x�� �ւ�*�'+��<�����N�n�3��b�s���f�Đ]�ړ�:��T��� Ր�vљ���E��+e�������Ѓ����7w8��٨�MkX�� ��X��㜫�ͷHַ��ʙ�sE�y�Fei���yOcǿ>C�� ��9~9 hf`��P�>��p���'����67&V��n�w��k�$��:���.�u��^ߍi���VSz��V%�(�?�D�V��'�?Gd��v��7�3h���������W�n�F}�WL ��Y*���N��F��2`) -�����ew�b�&�ޙ]ީk�(a[wng�� �ۋx_ �>���p/B�� ��0+���JFZ�|jX�3口81s -��� ��T' ���#�}0�j�b���T.M��{�� #d������+�'m���ʅ��"!�,P��yd�`ʹ5?y��_���&}_h���¬PFhH��K4�|_�k�����B��� {2�*� �4�J�D��fez�����-b��$�RA�%�s4D��] �������j��"i Ѽ����c��b`�8,�v����1�ˌ����rYfH�~V��φ�4M�<�*���1�����뼉B�5f��D(��b ,ƨ<��XC�Rm�,0�Taƣ�O�:'A�Le��zU��l�.GSO/���t<����쏇73x;z|Mf�)<<«���x6~��{Mޑ���m8� ]�"���r�§<bKV'sO,��� a�@n���s��J�1H�ƒJ�j [�%/1��T�Y3)C��r�1e8d���΂���pz��К$�i�(˩a>��F����۞ŊĬ�V>�9�<�^��abmS��9i�k��#�yT�X� Fb��s��8���9Q���\�s�� ��*d�V 6<����]e;L��>��i���6t$M$6��Q f~�sEɔ<@n�5<*�A,�`K�Ѝ-� �kn�Y1=�cº�؄*妊�a�6#��)J}E#"��MHX1�֨�ض��@�@�_`]�a5���i�6'�v.dh��.�B��_c�Iɼ-�L��jQ�t����vy���5U��"'z+�E32F�c�XhV2 V��f�#}h��e`�$� �8��G9� iZ�%�i ����C��~��'",�ȳ�6Y��Xtב��r��O=kč �pp�Э�������&��)��–,��n�^__�Q�駟�c����,�i߫����f"��S�n���/��z�1���V&�^ ����T�A��v����v�kRv�c���_����ω]ݜ��IU���2�{�ukU7ij�p������3�� �?���l��-,V���-��R>�e�I��ʝVW^��Ԡ�l�b*�t'J\݈�ł�r\Ǩ��AJd���ג�&�M\>��;� RY���J`���As�*AB�<,��lr�G�iG��3I��E9籿��N6����*ZcR�9�ę7�mF��v��=��?L=�t�L��*@j�J}i"�:����fm�ޝ�1_T�=��@���HqO����̴Uz�����z� +M��nHmX߳@��,�n z5�>���}��oi|��)^l�R�?�1�G�����q���xd���w�0���q�Æ)�px��/E��_0��Lʝ�`*5ݳa\IO\1O�ϲ������f·�/�笟*���7o���v�B�8�k;DD���Ű�Hjɫq��[:e+��ܶ���o-�_�ɋ���WMo�6��W �=$ �*z�m�q�]�@�$�ӂ��2k�TIʊw�����{�5R8�7o�ҿ~��� y�~��V�VNH%Un����+��.�Z�B� �C�)�(��>��� ���J��5�+��p�k� '������� h�� �ڴ����(ڌ r�X�rv0G�駳���V���3i�8�H��5�B��V�Jd��EA��E�p��\���Lu�32_;ЍBcײ��\��6��m�-պ�u(�Wu c��K�e�3��: �=���K������R��%`eUH�R��� �_B�䖁�^���p�k�I�4�Hx�#m�$��|&f��t��WZKl�SKC/w *B��%a-D��� �!�U>�hE�oӞ��J�/ ڄ���x���1�O�Cx�,���/�q|w7�.&7s����lz=YLfSz���� G�5�^�2� -�*�ERɌb��S��j }��r%S*O��r�E�=Q�)���Z��ݺ|T-�΢5]Ր~'L��0V��*ܴ3��O��k�&ܴ�HOk�MB W�t<�~� �Uao�F�ίE��D���^�;�5�H��T-��lcﺻ�8����v��w��"��yof����]=J��GtNW�dJ� Jm - -;&�#���ے/�*mq�J��`i��*� b%�Z�lFWjO�j����U�Kk=Hf�J�8T^B_h�]��~��Vk��񱛔$Q���M���{��g_B@&������`�1(�ŁB �������T(^����_e%�a|ynj�aV)���9� �[�f��T�m�0��p��&�l��V�_�Ō�z�~�O苈:��Ҷ�ޤJ,�����l�o_����k0oon"滗�+���郻 �ȦO��v ���q��J?�W�t�7�h)"�7����h$��?g�L@�l1��7&�;��>ҝ}Y���fCG��Ekma6����Fe�ag�p���lP+}�o�pd�E�ˮ���=؞17W�)^ -�1���d�e:��aԩU{?��pK)/�M.A+�M���� [�} ��.ñJ]7e\cJ��Dv���=�{(;g=�,J��� s��y��.��� <:���m� $�rhC��� ���� o��<���"�� ����(���k4����o�Ymo�6��_q( -4.\{��6���flH��]�O-�2Q��Hʮ7����HJ��F����Z��=w�� ����Uu6~�� �µ*%dF{������w��hgJ9�U��e���� -o,(��]�L�P�4��2������T"�?3��[a%\�Z��+��|2�~���$���V-j2Q� -+�Zj�F3)Y���|�� ,7���9�U~�g�����`��D�+2-J�� ��,�����T;����E/�JUhoN�̮'�����ѕ��1C���?�~�s<@B���G��,�;��C�d���LV�"�uU*�3���56㇨�,(} �0��1���?���Ջ�x�ݎ[����?0�7�7�t�y�K�F�ZY��b�BT�X �Rl)��(&��Z��.�$� �ij�� ���6�9z4��t�~�̦�!����}7����o'7��ܾ�W�7����� ~������}z�zC����ʒ�^���;|J�-1O���Z� ��E- - ��H��QI�V�R�d��X+Ϥr$zǷT%?c�?��XYscJ��y�2 d�t�x̯R!���c'���_���I{��Kaa�0������ʚ#0�`���F�QX ���,͖x���|�t��\^5�x�/��<}�����>��Ʌ��[�ǥA�Z��Փ����{���!䲒�G^t�1�s�%r��I�b�j{~:��Ɛ����c��"��~jc=�>=}�}�٦q�E���V��������ЏZ�!�u2Y��Dd nWAc��b�8�2�nb�ꓺ������DyP����y��'/�!u����0��{�����RZ�G[�� N���kCVP�hQ��Lh2i��pߴ�)��5u� -c6���E!� ���"��������Wќ�/�ͯ��d���;�pP`�i�i��S���5�2h�P~�G��r�4�,����Nޓ��G���+�1��Qܧ7���qT�W��L��!Hk[�����#>C�����&��^1ؑ�ǼC��f���x�c�)���p��0 �v-�����8�W�� ύH�S�u� �1s7�b�yn1I��|�¸�/w|銍�P�Mc��C<����\kX>l���U3�j�L��2r|���<|�xr��F������ݩ� -}�q!�����q:��w�����}{j>����i~׉!5���v��;���ܙg�A�i�q��ѵ?���ԧ�L��i� -7�Ȝh�'E������<���N��7^<�� -/�4����j��v��r��S� ����¶6��NC�}�6�T��ry�?jR{�l�!<<��M�>m��zw�ř`����,L�v�����5�2�:�:�2�?���޵�x����a�˜ ��iS�u�e�9�l��~os��� ���N���S��sRƝ�[�w�'14V��5A����wIl~) 9S��)�G��.�m�T�]�䭽���,������qA�D�SGZ�� �bxE!�0R����6����ʝ���� ��/�TMo�8��W ����=��m�I��k�ۢGZID$�%)�n���J�]-����|�������4f�����V�E(��B*�j� ~)n�r��L�޿�,����h+Q��R9��,P9,�됙Qп\W~a�{E�R+�����+Z� -9�*vڎ����ܢ+��-b�ʻ%@�ʯ7���*����tc�o(F:�}��J����Z��~�N�X [2�B���u�A -�k��~[���"7�m��A��3֓ xO����0�N��^��_��N@i��Su��Op XgZ)T�'v����TD�xt ��y����xo^&�0 K/���H1yKʮ���:�S-:Gj}�%�w�PbGX[1�à��`IqU/8�E���$Z�H��H6ft���W�w�g�>d�6��!}xH���>���n�w�6۬�� -��G��7[�-I2j���e�^V�3?E �iN�`!+Y=U��F��m� ����G�dI���r����%oH�G.5m�V�6d��ONIA���N������+��l1�{<�� �ť2H�x�ǕwlВG{q"�Tѣ�l��|4��������H=^�yb�L�0�NXغo@����2��DQ���{�~�n�6���gp�g' ���Hd�gѴ�ʓ��r#�4.~�n�>5��e�}�q�0����nyP�QEi�SL ����o��tA�o�r$��u����} ~�������Í��"\�gq.%VR�H�,gj�t���8�c�pJ:LFV��B#�;$f���*��)�n_��[э��'�����"|G�����Ә�^�G��X��'b����W�qr�詗�ǘ9�<�f���}�Tao�0��_q꧶* �#�makE���6���\Sk�lgYA�w��Rd%�;����\��:�����%B��RIU��#���J+�K�������ŭ(e.�6 �C�� �%>� �����-�޹V��nA�V0���� h�����6#� QvA�B�� A��W�4�Z���3>����V�=�Zm`G�D�K>Z�D�>T� ��9����b�@� -��˚�KYJ��خ�p,i=覗r��7c�T�%����1m`Ш�w49��J@i����O֎���.�P�G��g�����r�@x)�w��@8F��w�~�m�΅'<צ��Grv�,^�s�J�����HCo jb��-q-E��� �!�U1c�Bpڦg��$�t�&|�FQq2�wQ'3�������&Z��"���֫�8��+z[B��g�xu=$��(|� ��������i��i��dk��Nf$O�( -���H����[k�dN�� �E�0%�d���G+պ���>)a((��0�߇)����)�T2 ����&���O ����S�DZ�JAV�u�C?���- ���_.|r�r{��s����ig�-(���o -t��]a�8�v�-ǕRW��0�4��ƣ;䘣�����:��oJ��Яu����Q��� -tf<9 ��Apq��TQk�0~��8���.{\�ڬm�YIX����ž8���Jr]o��N����( 8��ݧ��NΚ]%�I�� -!�� ��*���G~���~m�:�ե�.oE% �����qJ s-sT p�C��Ӓ��A��VQ*A�h���@�h@+�lB����0r���DikT�N2D�X�Ӌ+�y�/����N��H �6�%(Q������D��0��u�Y��N��;��yk����ǒ�^���#�C1N���X��[Q'���x|�kу�Z�t��Gt�X�TR��g�^� ����pAx)���a g��s�y�$]�M�'<զL���*�Ȯ��s�*�����JC5�� b�� q�D��� �����m0�q�E I�q�M�ų �,�O�,�N�.]^ެ�n�Z���*�� -.���t�.�6���3����@*�ύa�^�(G~ -�-C�l���ʜ䩲%B����!i���rk-�,��t�T�S�������P�x���|�����$��U���0��ٿLH�I&? 3LL�$ -=O ��a�%<���2�y%��uk�����՗D/�+.|v� -{�GD�3�����o+t��.;�ZGs�hj���Vβe7ޝM%��r�����,�Q��1|��{�4���ػ"V���}�����(@�ɶ� -s2�t� �΍���m��<��įM�!C��U�o]�n�3<�F����c��V�n�8}�W �<$�cu���]��`�-�"r[�"�%Z&"�Z�������D���[��Hd���3g� ��m�*���<�s������9$JZ&�����%WJ��k�r�� -+�;$93���d�^$\��U�h\��Zڊi���)#C8Ƿg�_�%9Y+ k��Z,J�/��#�Ls��Қ>@̹s?��'W7,٧��v�v�g0�J�X�+���B���k� 5ϘN��0t��"[YP��ڬD���J|���ڱ��nT٤��uCF>�#J���+8�d6��٥�^� He�4|��S� �pغ����n�kc �/����s��Z�fɚ�V������>s��Jg�O1z��N� �m>ʜ�l�] -�/6� -D��b�YEt�r@�F�e�#k�E�[�-i"��{ic�F�8�I»q<�{�y2�c�q��ww��|r���f���|2��[O��埓�u8R���S�) D*�Q����c �4u2O�R$���J�q��#ג�Rp�Ƹ6`2Ey��u�2d���w��~ WMC͕ʝ��QJ1T�rgY�-��^���й$�U�ZS#�$�LQ.���?�1� �ݎw�G��Қ��WΨ� -ٲ���'� ��������5_�E��ٶw������~�@'u�w�����zۗ{��/���' -jV�MI�d�L�>�ɛ����9�i�Ç:������v��lsZ�7��OϚ�{"�'�9'E� �a�I��C�<B��%+s��'Z�C:!�.7��ސ��4Yz�d���Se�-؆�#�"�q������X��m$�I�7�u���L\��{���>��C6k)}o5`�͒�5����J0�VY���h�%z�V�i'8 X����A:ɹ|���m��H�r%�+_礥�_��AXc�B�p� �UHp��f��x�|��m[a��2��t�c�����o����IjQ��أ Ց�6�� ]���k��+���tk'��s���Wmo�6��_q0R�R�۾%uZ�I0c��n�b+Z�d����T���)Jrd;݊}(0�"����sw��g�*� ��;p W"���0��,���� -^�L˄�ʳ�B�DƳ��A´�$ ��4�H+8�Y����̚)W��BF��ϯ���\��8IK�Tκ�����i+�S�=�sn�Og���K��e��vr��Z���R}�U�0d�% 2��ZGHP񘩐� d�Q"^��+�9�[P(�+�v��Y�u#�2�F�%'�Q�?�@�P����Ϭt�6�I��v�w� ����y"XX�2�����T"��9`6�Q�0C���2&?����Y�R�C��";�_>F��̛,�Z#ZB!�� �� -�}Mؚ2he �^�"��'$�= �i�A�.b���9��0�w��x>�������ٛ��~=�.&�s�������d1�M�� -���$��dzq!CS�.Wz*Q6��} ��y�9D$ /� s��-W�4r�R��-��H�TK*M���|����x��+Y$!8[H4����ա߲D`��p�n��m7�-�O�9��M&/�G��#���X�9>�(�&�B��[�����9�VZǖgF�d|]qW.�@���7�܌q"�c"%S�Չ�"bm �+3��U��G�!�$�@�q�р-B��򢜅77��^Q5+i$��N -^!vL:�G�����Jɵ�+�_�)�Jc��v���~Mp���XP�C�0P~��� >��=��I?h��p��U -��#%h )쟼6�3�Ruϑjt��;���G��%�`��`"l^��>�b���" lon�d��lV:G��>��짎�8���t]p�[��M�C��#�^'�`%% Բ�g��4ۏՇz����H���;L�����S8,Y��`dkcW�]8�W �ׯ5c��"��[�Vp���J���^xɻ6� |W񴙲/w����� �-cj9�S�Պj���(��j�?����t��*k���� �,�[W{k�س͔k{�j��#�P[q���:��� ��}4�����Xyl�(�������-��o\�{-�0� b ����X��lǩ��C��n?�� ����m��B��Qz��ջ�\��|R���twn��__Z_�H��g;�o��o��;����t����_�j=�� �R�0��qB��~׈ �_&��ֶ��G��̅�L��} �R�� �j��/��;�Wmo�H��_1WU4�BR����PZ]t(9�BE{�����������f�^�Wg b{�}�y�}�:�f���q ��F�"��PZ��T��'�2ڙT�s�徟y���¹j��;I�d ްR'� ��υ�pcr R�zgp�|����m,̌-<[5�=�H � +�Lj�Z)�|�?�^]��%�X�B�+?E�`n�-LД�cE�E -J�B�V&�Ɣid��U�ԃ�ki�Te�oH� nB0�0�b� ����d]� ����uu �����q��3�m<�N.����d�1\ l��J舵��*��҈S�@p*`&�b �T:���;Wk<^��0�H�1�T̩��(F1�Xq�4I���iY�"��*�eܣ����x�tM�����0�O���;�a�z��p�����>�@���4����6Ab�Е��,%��*���W�b ��}r���DE��Nr�HH̝�<��3����1�c�<�ʑ�VnaJ.�ܷd���1)k\ޕHi��?A�����0Oo�c8d�1gr˘j�����#_ 6��yJ�Ô3_�j�ֹ`H(.���f�,1|���햩�u[��5Xm�@Py8�2��S~�N�ɴ�s��JOmѥ��$(�J��� U{l���g� �dyy� ��B �4] �q�b� ��T3�?O�DSaE��xX�8�� 8��'�걜�<��&K"��4e�˥�Ո6�#����yϭ��r3�?7��Ҙ��V`�@.ö]+�oy`-�����p�|]�V���t�����/�8�KF�i\ ק�k,��B��D|i 3L����6�P��Ŏ���7�H��`���sG���0BҨ0ť�8�:B�8�kg� ��bQ'�����L�˸�!!�4�s��%���*I����VÝQ�F�~jͼ*�e�{����m�;���NpRw"�+�� -=!̓0't�Uc��d�G<��?�cr���+C�m���1�Lr��ht�,Q���C���Ұ����:w}�sxq��B� ,{��r�X��+��&�R�D�=�K5�zq��Yy -�� <{�E�) }9� - hT�ˤ�H�h�?=]-Ί���Cu'S'�X��H\%P���P�p~�T�@m� �����s��F����]bNNw�q5��F8u8Xo�A���l�>�t�1�~�d�@~�חO����ϻ}��~*�j�4����k2�$������P,[V\�|��]�� h�q��ٲ^����`�0(�� ���ʕZ�L�E���w��RK%�?H�Q�Ksu�C������1�p�%Ow�,yq��d�!c��U� �/�^�ɓY�;̻vg�wV� G�Qw�d}�k(����"�>$I������3#_?�U����V4{�v G�v��j\S��H���<�L����/�XmO�H��_1�H\H�Ru��P(%jtU��^UU��^'+l�ϻ��(��fv���8\iK��;���<���e2OZ�����P�<k&b�@�9��S+�w���\�)K�-�%x!S�����Vx���d���\��c���H���2�`�5��b� ��T�.���G4D)?���6 -�R�8m�<7�[@,5d�/���x�1\ ,JB�b�hٕ>0�υ9��3�� �b�4i��\���`��y����2� \�����xr��A;�q��G���D�O���cS�5d9U��4F���x<둶rMP-�4"�^@ؘ�Q�d�I^�LF�|]�9�p�N޿?_��&p�N�ǯG��1~���3i�9��G���N��)RA�r��O.ꖢN*����ų��8��O�l$<��Rf X�c{DB��R������c���LSu!eh4���N v� ۝�s����vW��bJf��A�Q�D����`S�����ׁ�l�7T���$�}S\o� z�����)�pa 5Y��.A���A������r,O���(P��f��H6����xZ��)="@��f��]b�������w�i���� 8� ֌�{L��^̛;�^�1��3�l��҈�Z�]{3������ �l��&����[|~O,K��G���W�[��� �ݔ���A(Y�u��k ��"2uZ����?t�=��j�G�W�����s�йӿH�����7����bڸ^�/ /=o�?����׶/$�/o6�sۺm�^���Umo�6��_q08 \kݧ��Ӹi���"r[���I&,�IE����I�/u��DE�<��s�W��U�\�����q�Ef����RhY�{% �Z�.7R�k��Ғi=$w�OQh��H�=�YJ�D�e -a"�1�g�dr� -�@�-TRy�/����rc���!@������� ���g\{?�r�"���j 9�bY�mjV��r@��‚��RNe�Q�X��@�W��| K%�0�i��F6�=�]1�Y�?�32�N��w��yWlBh4�>�X�K����L�λc��A?wA�Ҷ��2�7f���XS���m��JUāb��*;Kn����A����_ WT��XM�R�$�%km]��E���XoD�ߦ]�D��o@ec�G�qӤo��4�������|�ގg��M�[����N����&0�}���Mgo�T2J���E�mE1��S�`���Iט�DO + -y���G���Z�1`"#yT�8Qi�z�-L��{mCu����tW��R☑� �;+��]�A��m�7��VbZ6�i*��H�jR���Q;����f�S�n7�S7f� hbx���>��� ��J���<.��ZI*�ـ����o�޼�ñ��o�1�P��'�� ���g�s�B��@{\o����U�E� �0����1�� ���J���˩j#`J��Y����at ��_����S8���8��{���s>�IZi����.<��J�v��}W��4ns߸u䖦�g��¡2���mW*�2s[mi�B��I��� �`�{�cׅ$�" (��<5S�� -g��� -��C��W����)����A��~�$��5�m�4��7"u����h��A�}�,��մ��x����ž�J�}%�^��s -����~����rVR�/_leJ{����^�o��5����!��=���V����P���t�e��� 3�YS�c�G��+�P�]�]��?���n�����je7B#\�2�Bo2��}E *G�V2�}Z.KK/R�D�3̭�����b��V�6���x;J`#��H�?��\�8�Z� sz��D�Pc"t�G��j��-�M�ڬeA�\��2$c���jݪ�*�Qu�ޑ#.���c��6�V���g��-��Bip��6��R��XV�R䑳���cP�*'j���J�jnaٚ��g��f� -��P�dJ�&dg�G�t�y��h �����0^nA�U$��k*6�A�(G�b� �<�� $h�iZH�Jon ؄�Qw2�� /&��|溜_��.����z2[L/�pu /�f����Ռ�]�d��-��^ 2 -����"(SɈb��Sȁ�R��ɕ���<)E����n< -ԙ4ƍ��c�G&�#�a�VmaJ�ܟ�U5X �Rg���b�h$�� �]$��[ -���m{���%S̨R;N�:A^C���|;DvJ���f��< <���⼍V������=�xk=S��c��!��`����w"��;T��M����^b�� �D$vn�D[R]pb�js��L��w����Hi6� �"��ho�� 1��* #N}x?.^��٫���(�H|��<&�;�i��Z�|�t8�# ?���V��%��qSSG-��J�a[ ��{�V�P�Ѳ&�H[*@���ڀ��1�X�#��[(�6��4���:��(��Ѡ��!��(�O��W2I�xg8�����ܮ��8Xh@�MՖ���P�pJ���?���s4=Z<<��[����Z�a<��߾1KR����~jĿ?����i+S�^#~�Q:���=���k���}���nn ox���,�j���|s��Ϸ�����}�k�r&�Y�N+T�*U����%�6ǯ_� .G���Uw�6V;���hG�hK����i�B{���wN:g��w��Umo�6��_q0�e���cۤq�3V�C�(�!���LD"5����ﻣD[�Ӣ%(���;�y[��(>9���e��j�TR��V�_z���&N��.��UN�'H a����LQY��i�7�DJ�D/]# µ�U&�����1�+� -�[(�i���}(ڈ r�X�rv� ����|ryKB�����h�[����hsK -%�LrjQ�T���@��`.L�Ŧ�Z���F��+YQ�9��\0� �R�k]w������' -�%�>��Ȁ�������.��vP[�F|H�r���U!�J�wW�&a��� n_ -�e� �co�X9W���iF�i�ǡ��1;M�N t��� -�������8^�AT�* �Z��;��@(C��|��6��ߦ-i"��7 ڄ��`��$��q2I��y2�c�q��77��|r���.g����d6��kO��矓��! QF��2\!��(f== ���O��T.eJ婼9B����ɨД�Z?Be$�R:/*ˮ{��)� ��8T7Ss� �qq�)%��?'��<��U�o��t�C�Ĭ���T��8S��l�u<�\��f�S�v+�jW�n� hbd�Zw���� �T��ۋ�T.*��j���~`�4�&^��i�jJ_��h~۩o�����5z9�RȢ6��A�z7�Ovַ5�Z���_���ʼ�;a��=F ��p��t�H�� �1b}� -{���9 ~ ����������u�^|N����։�: -w�y�VF7�uLO�����/5�z[�p&K"6C�˦?������O&}#�'Q�����ه��&��� �9��<�0��(��0%�9�F��K���iSx������\Ѱt�q��zA� �Z����[��ȃ�����q�QK�� -Z��vׂOJ���'����{�xv�G.)���-5G@pvvF3RP��_��U�� �փ�2�p|�Aa�r�w���=��W ��[���G}���0(�v 3\��p�e�a���C��������|;���?4�m1��S�Eoϣ��Y[s�H~�W��\d �3{u�&�zq���LMMQ�Ԉ^ �m������紺��%��%e[����s���?̧�J��� -����98a��D���r�;�a�>�����J�Wp|EMd"�[�� �.�P����?�p�Lr�q�2b�Z{Э~�€w(a�D��X� ?�̓��x��&��s-�7�]w`���+�� X5E�"��0AQ�u�f>�_̴!�(�ǤK�:�|)�7U..�����!�2�Zc�D�U��.�ظ��ڀр�P��m� jH@L'f���Zs���PA�T:�g�������`����wkh�/FH8�M�]�p�%���~�J�/Z��b�d��f(��u�u����Wh��y|E��b!����r(�g �A�Q:Њ�D��Aܑ ��6��Y��,�����������7h�������>�����a�3��{����􆽻>~�B�� q��׿iG�P�Kr-�(w3�dm�h1�͹#&�A�/f/|�RgŜ˙�"�,p1�S0eO�^aRb �a�e |�TM�ٶ�}���?��t��F^�n��ޏ�k?y‚96�ܯ2 ��v��n�`M6^G�{jR���f^�3e�9 -�Xx$\s�6�j�y�]�)F���O��p9�y%��&�'�P��I�ɡ6��G�=���'��|3��'�� &��j���ﱩ��d �{2,�x�Ǿ�+�;g��Mv�{J�;��!�k��u:�� �]��C��|��ew�Q9�|I��]Y��sbt�`��� �m��Pb��i�X��%�E�T�̪1$//'�i����n��JҘJ�Y,�A�!�JT�D����Rm�%Ľ��xb���VR?�1:0��d�J�������S&=�-D<���F�%�%�A<3�pyy ���7h�� �ZЯg�A�޿��j>�5���Z���S[�..��f8S�V�'�G�D���\FU�S�R�"�#�Y�c�eM3շ��������E�ƚ2�՛���)� c��R�1:�X������Z��:��[g�����C����x��[m�͎����������z��Lg=^���`ݮ�W��C��t��5��P͹I�ֿ�K�V[È����s�oO��/�Z�<�~�&�C�)�M��Q@�D�gi�����4ҍq�F,s��8�_f� � Fs�D{�����!�� -���3�\�CΖF����)�b9��:��Ǵ-m��2-_�����a+�Ư����Z?�@BsuV߻���3f�^�@5�DA�t�pBZO�Ey��zS�1)2�;K�Y�X~��AQ~��������p��p��ekh���戃7!w9���$�]rM�����ۖs�`{2���.�.k��@v�a�do_l��?�D۱�������w|�$h�n����ׯ -�+��v bn��(!Ȍ�E$f\,م�֕��O�h -�y�^{�����}�W ��-�����\���B� ���(}O}m@��Q�� �ö�[����[�g�E�9餷ݿ�~C/Ȼ��p�Ǔ�T� �\{s7��_���djLʱ��J�bő��u$��ĵ�\�$g=�� fDs}����x 0/Ҋs[Ww��9@w��?4@}��z�>����]F�`a��Yc�A ���@�MOΖ� \3�J3"����6N6�߲�������6`o�&��y�ޜ�~}~=���e7�ً���&W7��_�g������ P��.$�P�b�����E�I�Eͣ��, -�l�މ��a-�U$Ѵ���{����J����L�<u�GR:�&iӌ�w�S�c޿w���@��l]~H$��dZd�S��/0�IKXU�‚e)żYǤ5�u��E3�z?�E6�@�PX.>�&Ih�#v����T�#�S���Y4�� �6|�Ԟ�'�k��V0/'�L�d -d�dmܘ�IE߆�L��o�1{� �+��R�>D���#v����`0<1TnEN$���TF��ŝ�ٓ#C������Ɯ¨M�"�u�k@dA�r ��D��9��=;b�g<�7����� �e��}�YF�h��x�n��k�0 -��*�Ȅ�s�D� @u}7��*��y�P0���*��ۯ�J@_�m}7O�V�VW� �F�RaªXP���Dɳ� pA.���vg(Z���rZ �# � -�F1��H��O4n�&�������+�1���qB+2��O�b��y�N~�qy�|�~2�ei���������E�VE�J6Y;J$�g�?�㙮�K�g�胘 =Q`z.B��ÒR�����G����dS(%�Ԕ6�olzM{��J�`U3_��QUD)ꛎ��r���)8j��Z�[%�)��X��:蹊�U��U�C #�>&�ͥ �Z��&w`�,�����lp�6UK�X����Y5��1�h��A}zƞ��i ܯ�'�+����㭈�t�w?�d���<^q����_9��-O*���Qb�.��5=�ٜLM�m�hs�u�f�gm΀��<���>k�|������������/�����w�]A-�'k�f���Q_-��5�� t�P0����-� -q�:�R��r��au�S�Z^�Ԟ6Q�n{|'tm����qH�ϰJ�+!�۾��[Kgو嬥�����Մ���v$�WS���>���c�tff!8�ۚB��%v��խ0�1��ne)�� %R�� ����Ns:l��4:�����\�!��iv\ ������r��jv�u����������&j��&�����Q ��\pI���"4�:�c��I�L��F3��p�B�9�#4�����ќ `��4XqˊW�!��V�zs(ܗE�g���4 -����߃D� �oQ���[̃QU���a�_ձx&�U��- g�N�{��Z�IR5��Dy�d�amQbgؚ�ᙩl��N�{�;�&� S��4Es-d[�_[�N��[��r���D��P}ԽU�L��ګ5�7w -^��M ��X��*����� ���#�^A$kA(����/*����^�qX��� �v�����¸�2�}��>�:�2N�֞f��ۥ�#��M3~�޼���z��d��F�UYL ��T�� /�I�0��D�=0X�|�2<]���j6�*�\m�M�h��~��QԳ�e���p��Rq�� n�S�,67[ZFfx��'�}r1 ����e���)��.S)���B��z�4�����V�ա�!���HX�������w�-[��w<�#CIMW������K�ÐN�]��7����I#g�Fe�P��:�W�װ¥�,�%"�M}j����}�N�!B,��M0J�>~w� � �֮����27�l�@�#u<7d\���c�Ix[���̬H�K`EC��;��^�UB{y�u������Db�t�f�)���*�)��D��v��-s~ū�~��g> -G�B���$�T���mD/%�+��:njf�gVn��$ڡ̭��\x1�EU��LUK�wid��9��ah���� ��&�){����nLTȜ��(��5�$-��R^��?�i��g쨂D�(������)R,��u�5���ٯ���{�( ���*J%-��h���ýAQI�-�@H�I�^�uX�j!`�������ˀjپ {J��ә�4�5V(�T�cv����?M2 -���KLh��Qw�G �5�C�������]� ���<� -��S �X���6�.����[9�^ -�}E�'M���z፪Q3t��u���s�uvf�Ѐ�6=�+���[�{���������(���Ęʋ4N���cvKW��)�c�ϫ�4�U���Z`M�%�($:#G���“��|���݁VHa��@��'$�sj}|�o�S��R�ƍr -��j�m����ۊ���&fK�F�㘾�#�1����\�:�D嘌VQ̻nv;�y:��i$�W�SN�+ph�"lC���=i��� ��H���Z�Y�E� ͈}/$~���< �]���b�I�%ꆈs"$���tZ��nU�?��C�|[��&��p���8\A�:��69��� -n���&��`�򢵲=Fh%f�i�wJG��T�\��I-�`<��^����Z��a��� -�����dh[���&�9�K�[��<�᥾��{�-�r�h��ROť:*��M�}��Z�ߥ��RH�r0��^����^��O��6&��i�3sUiࠇ�����Zw��:7 �Q'���-����B�霠�2��s)N ��V�9����� ���)�e�KM��N���A�d�ӣF�x������dEĮ(��ގ�7�9�Û��;�P�u�V�"EӾ���c�n��xS�)�Iel���v���g]ԧR�6���k՝�������ӳ$m����t���ۣT�ֲ���&�ߪ�A�οt�oz�]�'-� 0ŗ_D4T�l%4��d��HG�$��ȱA�� �~Ҋ��h�/�`��b��jҾ��b{/����ۏ����{�����t7����]��.�����j�<�����,�3(m�� � � =�wO���t�@h��`�櫨��px��/���d�h�� �mC%��ւf-2nm��ڴ���h� ���[���/8�Y��4PR���]7)�9��4��-��m7����R�k�[�l�j��$P��k�^`��r�O�r��F�׸k��ǟ��p���/R��{ �- ��M�n�և֣�ȩﳛz��F�Q���p���@��%�{��iy�|#�ƙ --7���Ngܐ������p`o�w���X���YF�p�4M�D�fUo�[u����S� 1U��E�&�Dvb]���ARK���c��}�y�'�Jd�f�}6[|l�<�еʷ$���|K�h��:��l�  �fO��;�.�j-DUg�*��L{>��u\�ihy�:���wg��[�SG��_�� �!��R��MlS�˒�J�.j�I�u;����~�=3�^I��é -#vg�ݿ�y��h� =ځGp�z�0H��������, T���q8��R�� �8���r(9�$�)g�p��(�%KK8�`*7 �{6:��)cI���0�|cw�&���A C�� Q���L��j|���PX�?u���,�d�c\�0���өK��n�|�&�r.�)���*v��e c�p#�7&UF�V� [���*L�*��1��;"�BФ]�v�w̳}�� L U2��ΑQ��`~�"px��.�2�a���V�Yq��f��"I���p�\ <��Ъ8|����8@�휷��NGk�7uc��d"B�1AY=�$��8P�e��}��lݔ͊�����h�l�]��lt1�û��˫�cxw���������������㋫K���.������|��&CV�.��%I]������@�b��"�3�A��y*��ᭌ9)"��"�*r��� ���5�l�.��.�'�c�S4��m�a�����h� ڬ�d��(sAk�o�+H� -%f, ;wfN{f]K%��,}�VɄ�JU�k�l�n�|Z�v9ؙ���I��{�:��6�(��^�����᮰1fD!���k�3 ��20��Y -� �ʙH�DWZ�O�RK�`Àɣ u�+���%�i�a%[0k��MK��_N4�+��U#� �(�{0Y��2�zK�+1���`_w�-!��A�k㺐�-YaOe����>A���bV�|�%�z���_b���%�ŏF0�gX�K�Vzh��fr��@�?��2Kn���)�����$�E)�h��v6e�(ٲ�pt�7CE��\�, %���"I�:ض��k��d �X�{���S��{��-�''Ez�Ձy��,ypq�Y�w}���եS �Ay �cD�|\�02�������Z9� �2�3�r��%V�)� �����ɲԒ""B+���o�mh�W�w�rXɨ%���Ar�%G�/Ä��^�~ -����b�5�]��Eʔ�ap �\,(�O��Ι�zv�Әu��>��FCҶ��N3|�J�,�e��\H#�N|����?D`B=�l<^�����xd�6��x ��L��E��M7T��[�|���?8�e<������)J*e�;h��6X��"��yJ�Peo�\�;ף�..4-�>�]�N�ݒ� -T��L�ج1m�WE��kR3�ɔI٪� �莙�z���h��y�U(7��9��,��KB ��� -i`3]�ލ\���]��e��j^�;�:��-���6��o�#�E��w 42͈@.�;G���J+\��Z� ��Jo���y���^�+��:��x�� -/ Â��ǖ -����S!��&d���+��`�4�K AZ�:dc�ӎ�|����u�Ӗ�ލҕ����yQ�v�ש�Y��{���Y��Mp�FL]�=�J٤�kS�3�ŕ�f�}a��}�=i��jM�D��Їζ�S,h�����.�U�S�-�6�V�U�2=��[~4�B�?�������ጒ{�a6:NO�Y��n���� }P�̩�C9�l5vo��_ -��O+�ө/�T��E�B�t"Kx��q@�-�������n,.S% �T���U��z�v���`k|-5K.��!-}�.�>��]�kgڐgm��T�)����}�w����[U{�g*q�\4%ٙ���ͩ��p����cJ}�H��|z��o�vyBs0��ң�=:����ˇ�Н�_��n��r��vv�I���-� F��iH�B���Nx�e���N,�[v��[�+'�2P�aU��W���#��PKmE�Pu�H� 8T=Rr�a�օmkv�ъ��J�����n�'����Ӫٳa�v�+y+�r�`Uj�+�r�ߜ|�Z~պ���P�)U�6�Ї��I��3�x�GLȋ^���&���tT�\X<�P�M,�V3�Ue��Y�M��n��pņ�bCF1wS4ϥH() p ��� �؞� -"�T�ױ�$�.��~�=�����4Y�ZI�2��ݮ��d&f�>�-��'ӕ�R�� 0 E�S.����]���/�e�j�st$�X��y�B)��}+��YR�朋�R�$�g -�H�6�͞�.?���nK�oO�_ @� �se�~٪]v@Z��,�REQ��_lkG����]� ���v��eo��[ aX5 ݏ�ۯ�����1����Q�8���\PŴ�A�����t��O־=�R;.1�C ?���Ŵ M9�q\al ��&�n��<��O�ߣ��L�]��x��.p�x�u���gy2��漗�f}������%��#�����Ϝ���ȳ��{�綸;����G�8�W�0���G��V��[w�r�����S�|ڪ,m�|,f��aq�$`[����Vm��I� uyaP�GJ�-� -��sC��P����&�`/�]����A�s��Y&Q����O<����݋�a���>�$�����%!#2����q��jop��7l�� Oy���ϓ �E�6I$X�kjk]!u��2�K�0m -�Uu�����:˒7���nGL+<�*����{��t~{�J;��q��Go�� �>^�%��ϖ�kĶA( �b���q8$�ԁ���iNE4����t�z�9L�=�y<�̇��d����~�ߏ����f�p3���,&�)�����7���d�n]���SB�%My�<9-6Ni�}�>��9 9��+� W�RhST2@xlD�A�i�6�%o���ʦ�B�HS�}�H�<���,�<����a;�C͒ ��\iLy'.�?(S����YN�NѮ<�� E�l� #����ˀ_ӛ�i5�h����|�]T���Y�%y� ��0�����}[ՖL�7T���ЇSm�Լ������s��P �vq�'�dA_�6��|�F���B��K 9�S2��������>�}��~6~w6t"�)[p��<�6z����_�C�#S����x���8�����W� ��D8f�n���L8�I��1�>�1�;l`FE���d�yh��nEO0J� B#=�0s���[��Qs�fO�E'eܧ��pyҔ��u;�<,��C: -������S�^��E�U[� 6� ���@ 2�AG��`W��� -��%շl���~��|��F���'�V"��K۰�=�� %���� ��֨�L�I�0�6��ߊ(��툁�"��瑧G��1��PW�jf�e�����e9v�H���K�f��벖�#��5;~8������m�[�O�a2��Ӵ-YE�c��2n��L �4��`Ôb�>��8��6�ì=-sR~`~|�GU4�Kli��c�;��gߵ��A� ��C�k�ەU~@}Sb�9�wq]��U��es/y��¼ɦ�w��YW�K��s+o>��N%���p14dB�|S�L� -���c_�a#�֟X���҄�Q� -g�Vr����,�N����p�'V���~�L {��TϏ�(�N�}W�`t!Gh ��Lq*6��\���G��̺��Mm��z�@���E��3�Zbˋ"�2tI�����s8��R��X-:'�f�^|@ҥн�h�������%�P���v`9�\,� �$� Gp+��J1�+����ה][]������9�vT -���ˍ�������b����Ltឡ -� �H?�a����v��\��=�i|�[�:���=�w�.]��Xhf>zA����)G�X{U�ӥ;���f�Vs387����ġ�L��/��4'�fl]���r�:=��A����;�}-��b�z>����/�YmO�H�ί(!�$lHF�vnѭŠdv��[���Izqܹv����_Uu���N������z�����^����8��H�tj�JU:;� ���t��D��y6�DdYwЦ�T$�L�`5o�\�� ��.��p��4V�ڗÛ�4�SI����6��Q����Q15R�ej��PJ&?��^]�E�����>`�� ר ��<�I�8V�Z$�R|0gAh��Sab�2ҋ�Qә�L��fj��F���&�9-�ҹW���7F�@B��z hӡ{�9��s��T[�3YR�ɅEqQ��"Q"�x�׮��2���1y �zR]��n��Y�8����eO��=m���b�7��`x}�B�=��DfZ��2h�� -����eMĒ<Ȏ�@)�-�N��; APuSi� "�^]�f���!�����v؅Ϸ�_�>�������`t{=��{���r;���� \��������&CV�yaH �T�Ee\�� E��S������K���J��'i8%��UF��P��c�,UF[k��,���~$R>�FZ'��Ó��~_`�O1�Ŵ��W!^�]q�$)�2����A��+#y X�9FS������>#�%, �-�=r -��k�>ұ��_�>kkM��;r��R�l–�Ƌ��.r��@���G�C���ؠyr�e_ZYE��0���W�U�}��^+�G����m8b���/���.w�:�0Br���"_$Z�m̽G�q쉃��9��� hٟ���]��J�AD��c�H�D�'& Q�(f"������F�'��lZ$ E�W��4���r�ͳ)�xN�:���{�6��e�ś������H�p�w�Rlb8ҹEO��F�ٶ��)�Q��(��̧,��C�[x�/"\���fWMqr�|��E�ו�a�_�3�X�/xcf����`�B��I�  - -�0���X�f:�Y>����_�xB� cĪM��N�/��Y�����q��%�QI��`��h�������7n%ir��:��-�X;�����HS�~�M�E��>;9P'.��m�D܆P�_ �l�?�\��O/�8��w��K��Y�Al�EF%��.g�M�F���%�x�@�Oƶ*Q�����f �֝��w�X��߅�_%��M��i�*U��c�#0�S�hxx�d�w��uB�B�w���ka�#ʺ -� �Ubg�k�H��� ɷT o�v)MǑ|����*���b�u� ��p�(W0sZ�LžF�ܤ�� �$��%�N�$�����d�(2�hˍ�5w�\���p�:��ˌ{��b��"Pʨ�)�=�Gto;�7��a�d�v}]���N���� ���g�Mc��c�����%���f,��'N�b�B,G�I -�`͝N �����ϴ�݈��\ڙƐM�:@ �D3�Q�Z���k���`̷͔߬�\G�ojc��Ѩu�� h��(�["e�]���g<`_����Wo7;��}�ÄB�����1��)�r��\H�,���N#"�șj��6�����Ia�7�8�I��k�C�Hg�:�RVv`K��Ύ�4SYG`�����˼������Fﻤ�*�n�Wr�H����ƶ��=�,�pXu�ve׌��PсZ��J�ֻBu�1�&.B�o���Jԫvk�S��f�zc�5P/r[S�;C8��b��b�u��������Lөo���!����� ���� L��N��M24(:*��8�\T��N�ڠ��ݤQ�_��k���c�i�{߼7���T�d�+��0+V{DŽگ�S茌�Si��9��I(�,�."�P����(t~~�O����'t텒2�𨽝y���z�=��!�g(Du -��Yˉ�[����:���iq�Vp���G��<��kM<�#�.h*�KEsC��7�J�X��l/�+�Zo�|��}�_S�f�{���rc�N��huM��~m��V��� �)�[��禁wd�h��朖��[�?lԫ/��B&���?-ۘ��4m[�V6����9"�V&���A!T9��tլT�u��T�<���>Q��j�TIs�$�jq��S�lyՖIGx��N�A���Yib҅ -��Í`��}�*��"7���6�Q���h7z4��Si�1ڴ��)}L�2 w��{��y��-w�(��k�Z]�~�4���|y?��{_�a5Ϟ:�A������u��&� upm����Rw���ػ[��ƺ�e]46�����+�l�~�lu#{~��ś���~ږ����)�n��7��TY:�(��ЩlE�6� q��Q�+Ú��!Dڅbn �-&�}��HP<��c�; I/3�s>��� %�M���B����$�ԅ� ��KO���~���V��������?�.A��f��e�bN��./?_��Xmo�6��_q �{��4q�f f�p��mQ C@˔MT�4�����;����&]�m����{QN_f���?:j�\�XB�&F�D%s0 ��"Mt��̨4�)�N,��I�Q�L���IY�<!���Ȕ"�p��L�,t��W]��2�4�$��Lsk6W�����b�K����=����~t=^\B����Li+���,���P��'�P����1��X�#$�˹�gf�f�\��2��^� �M(��wF[��,ƺJ J#jF�Q��k�g��:p��,�+HR���v���� ���-�X�$di]e}�蔤SJҨy �!i��0&;��˲� v��������/��i/�.��ֈ�߅��� -D�^�b��Ƣ� r���E�#��< i�I�LS �wCo^@����| ���>�|N~�~7��77���r �7pq=�m8^�����>����oH� M��,� �SE��Y�O�b�˓�d�"bxɼs ��N�\�̗JSj5:9Cz,�aRi݊�W�+���r�4IӘ%^�9����?G��y��G����x�*�b:-r�T��Kl�"4��)��PQ -�D.��`L9�fBd�V�oAb[�tO�T�Rʤ��>�/a�kL8�$"}M;f! Xk�ʓWx��z�W|�Ną��.�u�����J*����x0q���^��TR��8�3�IL`*i�K -���&|GT"�J����(��d�)Rɠ��?�����vڟ�T 6՝�� ��٪�B �B�א�*� ���E��U�J.�w74 $�6�F��UNU,BjM��'���a����3��-��.[�[ؽ��J�#x[��s_��Ww;����;ndH������ػ�j�,��lI�A}b��+�m�h,M� C�A�Ek��\ -C�D��̾�����J�j$��Vʡ�SE�C�A.3��P.T�@iC},��%-�Մ�3�z-z0�?��o�cES,MbWh!�K�:x-Y,B�KT�Ռ�T=��c l��Ng#I������>��-���m��vئ�>�@�4Lgr�?r��1ʻ�ׁCk6�T�66Х,{���j��0�i_��p�`���5�]Y|���R������2��w�pc���)$�� �rfL�oEp<�WCG��%�9p�!l �I�{���BY�@n0��n -��C��F?� ��*�"P��}��E�J�Z��6TF����0=ʡ���T��C:�,��\`Vi�i#)��{䝳N�ĹA"��N���L�[���J��C�~E�yH��&�T��%]E5^$.��vQ{�);�&�Z�����$d󷷴���믫�ʕ����d�q.��6>W���*�xp{��u��T8x�n_u->�l�j~劻���V+�\̘�z{n�ENU�h�Zz�Qj� � �{/�_CK�f5eޞ���HC�����-������$��d��y��+s�.���Z7/��Š9A��I6���Ig�ֽ�B}�^!�Yq�d�^.��z5�Ӌ��b�:Ǔ��s";�Y.=E��h(�R��<��A*mA�(��� �3pj���c˭���)mK�+E��zj�b�SEi���F���r��{L3����q�� -(�D�;N���ٴ� ��?e`���}�ak�ͽ�f���:.-�����__��y�=�Z���ϖ���EQ�[�tn���!�|;L���'���� -%�;�O����c��7@��]o��މ+�3�|~*X����>���f#���/E9��'vC�{w_��~Ԝ�evWn�q$b-}�Z����/�X�oG��_1��, U?��8v�aːDQ��n9�>n��1u��wf�Ӷ"��$�������v6�U�''8�+�r�df��D���p��F2�2�׹���J�)3�L�2�G�L���<����9S�d����A�ӿ�>r2��-L�r���_�N"�Dq>�� �>�V|�zн��1ZK��Ў � 3A�a.��Q�cA�Y -"[��O����H�J$r�q�'b���J�*���}]�ܻ��F>� r���/PEb:�_�jg�{��I��K��">3h.6���e����:�Ư^�QҀYW@�Wɀ⦿cf�fs>�7�5�!U� .6?bd{��S4:�|�R�5F�\(��hl�VEl���lN���@+� -#�%u���iZ-����`ؘ��Q��������:|����4�/���Noн���-\\�>t��>]A���8���>ԁc�P�)r-Q��)�@h�y�3���н,�Y�!��\٪�q5�R����1ƂJ�o�J�a��H�������xw��l2D�pgI�i?�Bx~���Њ$�i�+��f%ߨ<�MyԆ�� ��VaV� -r�@����?LЍ�(��Y�#=�����hJ�֛H�R=�EN!Ql����登]�4�sw?B�n�l -�C�`�x>�S�|���@Uq�y��e�������=B;�0J�$n,�@�c@���"n�T���#4 Q��˾R�<�6��c��/|0쎜��W��>F��3��E ��R�R����p1���Wɩ�1�N�a���������������,#������w���iA���U>��ѽ��cR����J�B�0v+�c� �7���̈{�; ��O��(�-�,��+f����pC_��w� i$�l5ʲl~�Nb \"��t�tZBz�;a{�� ��,b����/�=���y�� �0�p�G��HGG��i߈��t�̻^�֠s���`����Ӡ���?��4ei��>����W ;q�׷��l�B��Ġ�$Ą&�� ֋ �'6yO���3U�N�|*x\�M�?�&�,%A�3B%���f��޷����q}Ia�VPx!�VH=��w s�>�������*!S�Z���k9��(�h��>�%qVqGZDz���kV��L��H��i��o+s˹,f?��9�*t���m��r%�Z>po|"g�F@�eqy�YdO�!�-��TC,�+)����׆,O����i0�}�@�X�� ���|8��S �{);��#�goe ކ��`�����nb���ߞ������k�*�d1��p��U���L�=$tB�dŧ2Kq�D�����,�fm�д�v$��,!���U�����A���O4�� �0�J��"��� ,��)ٶ�#.��v�2)]CK|�[�/ty��6����e���������-_D�}��i��,�ݔf&J�C�|��o�i� -�����߈�RÓ��^����t�R��.�1��t(�}��Gw��(�n�UG*�>�\�W�A=�����v��3���P�C�g��^%jaW~I�h#�=w�����+^]���3��v��k�N���%�7J���*��*���Uv?m��'�ƙBX�z�P�.2� -w6-�Q��6@�6߿o����z���� �pzn�J�{yr��;Xث�@��!��-f�������2�# Tn$-�9���W��1#�®��������XZq��=���Xmo�6��_q �k�ǴI�5f�p��]QE@K��E5���u�ﻣHE�-���mA�ֽ���~�:_�����0)�Pf��Ld ��wx%3-S~]��0#���hS���H�y�yFZ�a�B|��ؔLq�"��2��p:��� -d�I[*XJUyVb^� �,K�K�=�rn�O�g�wc�� ]�a�0 �J�� FS,��f)�,���;RTd)���W!�x��cT!�c�)+���QE���Y�'m�A�l�S�|��zS��l���SO����t<�������3�8��Nf�wS��������l|=�w#N>��o���>p,����$0RA�QO>B���y(bbzYR��C"ﹲ�ȹZ -M��d��X -cA�Iu#7ϒ7X�;2�5�2�o�R��!��;K��>�Dؿm�phMĴ,��T���"FS�!6R�LSZ�Rmy�/$�k�����J�<�5�+�ݵ ��>� .*:R�������z���Zl=�@^�2��W������K�������E� �bn�z�ƫ�d[t{/=o��[�"5>�3�(�q�� c>}vU*g�^�dO^�Y,��[hR��3&�Y!c/�)�V]��*'`#n�̙>B�H�%1G��qlwRK7"G�:��6�sdD��thT?{����3�� ��pS�LW�I�����3�l�`{�U������ϝ�n�q�B�:l��N��V�@�,��k�iO}��qR�^��6�?�U�8��x���q�,Bew{쳽����;����QmWҴ>f�������ѣ�Õd{kc��SoOR�Rd��7�=uZ�h�Ղ�wx�ۛ�Km/\�Ti#Ҕ�.��(���U�g��,lO��Njxqq1e|?�g�rN7ژ.�x��tQ��q���UG��+�{)��㬲�2�����"`�̪Q�=7�HЅ�R�:Dxm M�›���oPFp]/n�q ��]"�rtC�X�>���v#��D ���n&n%����o�/g�-ݨ��ӄ-�~$�� ��$�RO`�ȟ.����&�>7�� {�ғ:����w=x\��m��~��AH%��c��@ �Abp0�oo���;�>�.���T|?a���W-X���k��0j+�_�[(���Fhz�L�۟��=98�Rҗ7���fh?G9ϥ�s��a] v�����/�=ks�8���+�km%��d��Yŏ��2v�V&��K�( ���H IEqf�߯��&@J�$3;[�ڝ�"��7����|:����#���� g�,-�8�� +���/Ó,-�����N���@k��&��#VfԸ7����M6.�Q��y�HGQg)��ݜ���s��{g9�e�1���HDMr�g<-�c7��˫������ ���)�� ���;6P�h��Q����"�1�(!��l~�Ǔiɲe��b�a�>�rs��)`5,�z�-$)Ւm�3B��w�.4�N[��V�zϢ{�f%[�@g���K@�͓8J��[R���.�d������f,*�7���iY�����e'"�;Y>�W$��^ޜ�Ҫϻ4�E��e����=��Ր!��(A)`�́�餍� ��� ��@������z7��f����\ܴ�����w}��w}ݻ�_�ݰ�kvruyzѿ�����Y�����..Oی�`(�y�#�i��#K��-RNŜ�q<���"�p6�>��a��Y\�h @r�1�KR��z�)+�+��A)Cʲ�z���Ԕ����M���2���֤�U��9���#e�'9�J2w�k��p6��4�=�w��� P���C6��l�"��#*X~8��&�'��O �H�� ;/��p��)\�O��$��U9p�-�J.t�D�r����y�T�5?���}�J�w&��ޱ�Z��,z"�$@UH�$!�@�\ѕ�w� ����*4�K�7Z��$-H�E�p�9�#�^f�#u!�q�vI'���a%X ʄ��p�)�s9�#�)�)3����H�PCA�"���BmJ���^�L�R�&㉚�΀&��(ۢ�cs�_rR�M�ыa6���i�]��=�$��m�c٢�/J6��� ��`�W�l�u$���q�r9��&ۚ�hz˷��b�X��;f ���v���56-�{@�+���vY2�a�H�Yh|��z�,xpljV6��{PC��O�����x##���i�4\���A�1�@��ԌJ�jZJ 5�P$��{��� ��h6C����#���O���S�����3�3�x;ޢ����!>� '����6����#H�͸���9*��H\9LC�/5#���0��VŃ�RO4Lbo�!*w -& �n-� ��l��*���.�0� �� �a��8sK�Df�S:��9B�;�E{�j�G�fL�X�a�3������~��X����8�4��{��A�B{����@�G�&�9E/��sƘ�D�Ĵ [H' -�[/,�2��e<*�.����3��ب��sbM2b���E7�� PЏ � p��/J�G��h�ZA��G����$���`8\D� e�GsV��%Jmc�0O���:U$4 é�>���d�{oo{��W �gG��H�f�nУw����j��з����?%Q>�8�aMO�v��󡘐�y\��?�m�_^]����p�#����w�� ,`���'�$��Bj�w��8�xT�O���{o.~��==;�{��{ώ�A ���V��Z�o��g�zӿ�}�;�����u�>n�O�l�8�&�������vm ���: ~E΁B��hp��(`M�@+�^��6�BL"I�>�J1�πKHH�/N��o�/�vv*lg��cZ�D���b&&�6b���6��M8��M-2?��6�[q���)M�Dβ���OQ.�h\+����,�%R�������m),���UK7�fA+Wx�K����k��(���� �o�f�(��'>r� %p`���� ��L.�8�� -uop� Oa*ŵY�!�!�3�X�w�h�n�¯`��g����r�j�t3��h�K� %����I��q�Qb&o�?W0�bز�fr�;�Fy��T���r�Up�������ڿ� -�3����P��0�$�)�#e殝��$��"�x{K��/��n#GےMmC���[�%4A�lc��wL��fG�׎�`ؚY&Tikt�[�u�1�oo��Z"�w��\ޮ� -b�S� ����{�����#��|$��f��O0+�x�ŭ\�h�Z1�ԓ4�����4P���p�mI�[FU�l����G��y\C x�W��k3^N����0M� ��N��W�n�P�?���1�g���Z�0�ˬ�-��\="�Z+�U\���=�9.h�$�sr�y8�����b1 -��0Z����bȺ�V�B��`��D�K��,�IX -� -!,�f�|�չ�)5�{����8,0jL�9����yFk8�okԶDÄ�e�׼\�r�=\乱c�v�F�6���,��71JN�ޯ;�}�}���ɑ�Du�+D��ڐ���n x��>�㠌DC�b� -bLL�b�I�q���Gi&�d�4Dl>�/p�O��g��2U�u�A,(`��� ��5*��xL[�2�,�˄�E-m (*Q,���iF� ���-��� ����֡���{��P� �û��N��+e��f)j��^Ze_d���^�*��Q�&<�X���WP�d���"ץ��+/�+;_5�~��Q#�~����ooϮ޴khc�ԅME�V�0oW��e&B�:)|;��z_,ͳ���>A�R*��w����8�)Vr���ٌ6)Y��&��"�q*w�L8���'��F�؍��8�M�EMx�Ѫ Hz�f`r�����BMyz - pzu��� )�AE�M�4��� 㪍Sڂ��tcTg����w8 ��x���VH:�z�Cm�G^�����L�n��$;| s�"u��>Tm�6���c�X��ט�­��W%�����u��m�(!Hu3����7�5L�}\C�ǭ�H�e�US�>�՞p_� �����Ɛe��S��I, ��缡Lh�����m��3��U�����zYm�G����_�v�[�5W��l�� �_(���6����Ż��7r��Xhڄ�Bh��y�ɑ�T����=d^\�+�c3V��>��_�{{�,�t1FH_���V~��T��ed��:6�~�(�W�84�~�&.�͗!*D�f员T�;~���G%3<�H(�G[�t�%���˧��o���`"6A��u���bV�� ��&���=�(0�Jjt'T�r�T��d�dY���t;mYGm��D���z�\3�A^����b@��oDhXп���jsΧX�B�¢�0�ja��H����k�'�DUT � -�V3 ѩ���1�%���DC�͍֓��xr��0G ��@\���P痪4[�4�?��|�0���c�;�E�����?��O6���nW�U�ުbY f$9V����F8��h�w�T��E ?@Ae��J��عW譳uO� ־����108��$����� ��z� �����R�����;Q�⾱��oU�x����j�2�ޅ�t/�e�"p��i;��4H� -�KU��� �`�o�C��.���*��N<|��9z�Gk�G9��l���R]�"� -����Y�z�����&��յ�!�����%�z�z߬�܀!|0�>�6�hQi�|z�K���Z�W��N�E���"��t>^�[c+T��m�/�C�+��)R�C����4��=��7�, Y����Uv��|�T�(w�Cu ͹;�jC֖���J4dղU��l�W� �?]SP��/ � -V���c�a̲ZV���p~FKš��z˫Fv�pf�Z��� -tv}{U$��Wn� ���N���rH��H�ݩ�!'ͦ?��k��D�ֹ��#&����Y� [`0<��#��t�p�[G�T�3�_%ul[Bo�ٿA�-Y��>��R������s�5Z��I���x;���{�/�C�1ۆ���_B/*b~ܵ��r��U���. ��M9,-��ϬZ�:|6���� -�xO�0�褻��!� �ng�f�I��5h�z��0��䕳�� +�l�K1��kwS�#��e�TO�1��r�4F5�W��<5j=Rxi4���+{����T���Bj#�y�le���e��hr� �ñ>�P'\ �ӭѣ�Y��E�&�l�C�q���wW��z:Ȥۜ�yT���Ѽ�Ql-�g C������s�h<��O��X��ݕ$&o�t:�����u���0si<�� �VH����ERR�a)���)�� !�Ʒn�J鼈P�ሕdD�n����ݸk[��N��v�&�p%�EB7a ��`��֩�����1�M~�ϟ�y1m�9G��|y*.%�8&��xG|4� <仆�g;�Ӯz*A���@=��.�Ж#T�m}��W���Ε�� �IvW�Dž����7�ȅ8������͡�P�םnȌ}�j�fl���͖z����^n��!�ţ�0����Rs�}p� �����]=������^X6�8����~���=�*�'2�w�^`�۽��;��t��X� �8��|�#ڗ5�S�.;�B�Bl��� � c�b�l�Z0v#�'c�坤pnhu0!�f(F�_L'�c�GYi�l��lR��f�8��_s"��]��麆$�S�~i�`�����x��> #�y�{�=�FQ�D�p#dh ���V�; ����oSG4� [�R k�H�Y��{�49�c�?�� ^����l��J��=t|���c֍J�b�y����� rZ֎A_��<� �v��~BB�NB�"g�W�+���a�������Ȳ�0�����<z�����4�s`v6���Ձ3 �����JM��J�����[� ;l����Q��j8[Sǿ���B��~�zezh^Q̵T镗v�/!�*�b!&��-�Hk�,��ѵ�t�&��E��=P(��Y��Էv�t��H2��S}U��{Ի�k�W�� [U�3�v�e X�B׿��u���Q�d-�sX��Y����Y�WN#��n�Z�B��Gjc��򥪶v���n�qRw� �jY���<[L���w9ޭ'�� ��M -��:�vּ�*@w�x�|b� ƍ����h�%ll ���»�'J�}�!���E)�\H�-D)�O�T2��x�%}��_�E��������L�~�Ioj#�&��k-������!�c[�&%��&�ٌ]��}�7�s�a���~�x���=Z��.���;�C2\�*�VX�cF����O.�=�.>�:��J������2#;����缦T�x��>����+�7c�WU�O���4��]���(KcX @j���I e!�D>� �~����ڑ5դ=u_t�7��,� ���x���ۖ����j}ٌ���{,UxP���#���]%Q�k�W���r֬K��qC��SW�`8b���Q54:��Ο^yM��sL��m�E�w��_x6V�u�j��[�J��pnˉ����it��V�/΄�ұIN��)=�����xd1(�hX��1Y0�_F7���}�d�H��g��V�w�6����V0��!mIJ��x��R���I�V����|�J�kӪ~�>c=�3#A�Z�%n�� ����5���d��������cK��XS"�1��� -n?l��O�gճ��ʱk:,�b�E�Q! |�4g���" ���e5��Ğڊ�v[��ox:!������ls� �b�oI �k�%����T��鶮+�o�Z����Z����ho/z�p������]��n�p��/"�ѩUT�/''�.���>� -�AX����5���ڧfa�M��˙����N/��e��ȵ�dAhw -�\�p��X�Sqaג�)�ü*�:��$�>�pWqb����JŒ;y�p����=�ȷA!�M0�H�)�z�(�j��vG_qm�����i�����LRQ�Q� �E��$n �DىZN7�tc8ϗ�s �2t��b`BDf���%��c����h7v�fM�EV��{P���E�����'X�|_�ߍ5���R�[�s7lG�qJ��"���"�/��b?Tz���K�c��,�Kp���t�э#M�������˹� =�����=��3�:g��5+�� bG��ර��z�KѶaJ;'�<�B�lA���=�8���Z(�V!#~/��i�蘸Ǜ,$NVMn�q\�N�\U��� �i �����u���ͭjP�i��&*��� �DÝ�z�R�����G/���XmS�8��_��0%0)��Gh()%m��!i;��FqLJc�$��k��ow%�q^xk��zV��쳂�o&�I�~pP�h%��HeV$Y��`G�љʌJeO�Sy&��Ts�]$�̌�U hND�]5�S�%�T� �MT�f��x+5�LZi+�v�I?�� uA�Zʱ̬9�J��;����9 �U��p��4�#\��*}C4%���)$>�#�2z@QFj2�I<�����f�Lp���mg�3��Xg*��,E�Q��h�B���Tq�v�۝�cF�� 2e!7���.���c�I��,b��n����Q}��pyKh�Y;9�ק��`����!��f��=�Ņ,��`����9��@LЫ����R�PL�b�1�Y\#� $X.S���"����&�F;�.��;��mwk���p��_�WW�N�}ޅ�+8��k�ڗ�kA�󕐿�;�j 1e����h -=M(�r�ħ�����Ld� ����\�bu+5��D�qb��� =ƉeR������}C�B3)�2���3�^���.�z�_�Fx|���!�$��k�T�:�JR�honT���%���)h����2��`X3�����2�� #A/"����縖�A�%2�Op��4�� ���H�8�l�=��~g��{��*!���#�"� �֚P[���nV%m^ֳS#�T�JMCC�N�B�a��>$�a���;K���b��^r�p������K -7\!��U����K2 �����&="- �@X\m��,\�#7���W�m�$��y�~߹��+X���ʘ�)Ћ�Vo^{ -���CoN� - -��w~5F3r'�+�Xfa�[���6��l�I�T:���%��ua��@�Z̪� -��qr'�~A�?n���滅�����Z -����.y�Q���/�����< �I�,(���({ �sܥv�U�Q���yDS��d�P�0|����b��_��%�^��j ᛦ��pN�o��M\)������u�*�k��Q�a�g����"-�"�������@E�Zz�jlc���V��Ee!��%)}yr}�y�ž߉ �M���-v~,y�}){W������1h�n"��y��7r1��A�ֲ�J�Y���Zb^��Z@��S�����^GY>���9�O�n}��^@I���SQ!��OtC2P��i���)bJ&�Z�dU�����/��O���UF���XTu��������,9 -�O�y�[o�lϧ�[.~����-Ug��Wj�֜m'{��aGy_u� -O�nx��=n>���Qn�>�5f���GQ�j�\�V N��Ơ2G#(����kWH�Q�]�g���~_h40)b�� �֞е� e�,�U���βZ�k����Bxs�!,/�ψ��wj�Tf�w����5��n1�iz�~4n7I�������:��`CX/^<u}�L0��3�ar��r�|���w�v_j��t����ӳ�lb�S�:'�;��;o� �X�pC4�9�:�xJ��M�<����%�� ��~5Pt*�&�o ��'�<��b������I���}�xP���� -�+�s�Y�U���I�?�Z�s9���Z G�ُI�M섺N�[[��K tfXIc����n=�� ���l�T�ԭ~��!i^�ZΗ�'O� ���CG��HD3�s����8Rq�Gl��xAȔ� �|������ޒ��0�����$�0-����y �+�G��c �X�E�'-G`3���GZu�����Q��LQR��e�P���s�#�by Sd�&AK�D�?,� D(��� )�˵���xq��b��H��FY�~Y�u'N����m���?u�����Ѝ�^�[CkHϸ� �R��(�b -��i���2���cr0� -���4`���/�\���nw�Zu���Y׫���� Ϟ�О�Sr��Z�'B���k`K�* ,@�V�A�(�b%��ѬM�ʃ ��h^DT=?�ƌ�{C���ް?lï����O#��wy���gC���7���Q�b��Ρ7��(���mG��R�v)I �T�E�$�'/���I-y �"@��Y�ff� �&"�\.�"�*r��Xm@��tC7%�������R�����CJ���3�;�u�f���nۆCÒ ��DLu|��L���$'c��-m�,1�ѵ�T@�a���LB�H�4��s������ t� %w)c4�f�D>A�:\�q,T�#33�f�gR$a -�ee��#��#�)�Q�p�鴼�^�ҧn�H�4���2 ��-rb~�̋�� 6�b�5L��%��yr�5����|zꦜ@c��$o�]D�t�!��y` -�c���񗧧�K� �k��Ͻ<��<�y�� �y���cl`rŵ� -b�p*��B1��I�Xϟ�>�� ����˼�}ͥ�L��l^�����#ʡi݁~=2K�h���n���$�eӏ�e�2�A��;��꠸���,�D�E�\��r/�`��QX����D��t]�~W���Sy)��Ʊ�tn��ma������Yݶt.Q8�Ŧ�"� �.�L�]�0� -1�7�7�q�3X�/��ve���)p�>&��󄖧ƀ*R �8�����秿Ɛ���i�wrj��r��$טOɪ��G����?Z�dIy�:� [�'������߬��?�xF����˼8v��u~�#bn���5�PYr�OE��l�/�If��Xw��ی��� -=��L5[p -��J�d�ѓ�/C5��SQ)89Ō%g9�Lҕh���I{�L�7�U�V͑��hB�GC�����mhT,�0e���m��a����O����t���(m�f�G��)v(�����T.~Bt��Y.��IQ�) 8E��l�4�����,���ݴ��(����h��#'*%N�� -Ҝ�)�kit�Z�0� �L�mgRP� 1�|V�66�8�GH-�K�bS��o�v�3� -���p��隩������ - ����~y�%��G�e�h{v�Z��� ����Y�|�D����� �!i�Ql���y�f��A������j�A����eክ����̣ �hZ��(Z;p%�Z����L�$s��&1�%�@��"�"W����!︾?�}���,��f��a7�{�SU_={@_m-<�e��7[u:i��=�}��,�����*օ�#�o:i��k����)R���Q�I��E)n���Wnp�'���ֈ��*�D�7��j����Ԥ|�ˮ-=��,MH���S�����t�lrD+��a�t :�q�-�t�,�E�QH�Ti�Ln�������k�NϹ��K��y#;)|�3���z�G����3��9�o )1��kP�]�����W�1em"�X�W����{�����;�:���k����Y�3<w����-Է���خl��,ц��G�p�����m��t��h/=�=��u֧�X>��V�嵢(R�hNT�������D����**�iZf��I���=Ctu�1c;�f�<`.�0R�<�����P���*�/��_���_����;1�B�ZyPs����oL�gY�NNl`��᣿�Dm&�!�;����`�]�?��J�D`���\��(�]M����ڼ�u�>�^�+�vQ��� O�A&#n�х[M~E�)�ө�n��H�5��Ӝ2aUk�Ѯך� |����*�h>���9�՞Ѥ��J��ﯲ=�J� ����U����7o�d-{��+G?���|t�ޏƾ��/�e�����rׇwek�m�mOR6NO�E���/�d��9ݜ��k��C����z\L]Y�5�\p�mmq,�Q:��v�x�Y� �V�6�j3=����n��cL��/*����5j}(]o�"T�����ZD>����������%)��,[;hmW;l���t.�[��xh�/�\W����P[ Ly{� �n���ws�Twݼ[^�5��h��il{�;h�_�����p�����W��XmOG���)���I��U�Ӑ�j6�"UB���>q����aܔ�ޙ}��3iE�^U��ݙy晗����E�����`N��������A-8�?��Q(�������( -� ��]!�s���SP��8���?�h�VLp8��pʔ��{<:m�rQ�I:���1+�I��C`4� Η �g�c�t|uu<NFpq�/�����N�x��$?�:��24��cAN R���\>9 �-6N2�?�=t/�'l�a�q�k"�b�K -�D�SL���tRI����-�}K�l5Qi��w6Sz=��?�tg�^O/�B���m�C��RLF��9�s�ojX.��A�zե����I����Za&�^�+L�;C�1��jM� ���Xb��Q@c��1e<1 ��U�K S�����;?db��f�Z�t%X���$���ӵ[�Ę���le n������˳K�h����. ,1�<��<��Ԅ/؝�*�����"q�jL>�λ�L�4��z|z�S -��Z�C/�U��q�NW�ӦrNb�c�TE��2�u��G7���9M܇. #�_��얃L�`��B�qA��C�X� ��Q�K��`r1��VCsY����%um��)�� 4�ݖ'���^���U"B+�׌�۵���� �c1������P���:�j�~�:�R�Rhku&e�!��W�F�|p�w4�;�>�D�z���z����Wdgg稕.�3�MQ��}8��8�$������*)��}�:�K^cI�eV�z�ܰ��UncQR1��0o؞8��6�R�� ��q|؁�Sij ��SJvҜ��v\g������LT!����8������^*�l�:��I�T�`\e�Ibnn��D�� �)lӏ���t?��׵"��K���!)kl>|�L ��@k�!8�'�~-������~��xQ�5�^���%��J�?r�x�R+�d��E�N����'d�x�+��S�*�|�{�V+R�כ�+�vE���Ib��K�z�6(�hT#�z�Y��c�ѵe�o��rF&�x�J�1`�H^��|�6������[�J����� �� 6��r���kI$�^U�2%4�i.�¹�XH��p�y�xd�_�0dT=M �� ��^j� ���m//�������R�1���JW�L�w�\u7��O�ɵ�{mqv���0נᛘM��X�����&����j,:�([?��qF6O8��a��BrS "9��y�� v�o�c�0�lmp�%�`�urP:y��Z��,�a4���<�ps5��l\t����a��hۯ�K+UԹ���ӍT7�\�u3Q -�����?$L�9c��)�a�)h�4������b|��bJ��76վ�y9#�S�f}���`��.��m��Kh7�HI�,�H�2�v͆���8;?9���, �u�cΔ�4�Zh�Fs�S�'� �-_Ń��a�u�� R�|�҆��8jQB{W���m:�Sr�$6Yo��ͦT�7�|^U�����V*��H����)�1<��՛��M���ᡅ9���ߵko���{~�)� -R�F��nr�I�%��M��� - x+��z�h��~ϙ���Ah�.R��������Ux����x� �D�rCk~?�v~���o�E>���O<������Y����k��<$��� =s������+�ȝ%���|���AI4�����Y��E�E^�xA$� �ց�.�9#m��"!��ns�(xv��X�8������0|�%��&7�m�[��Ѩ'-o��W��[�a0���&��F&�֦s��V�c�~3� ��2��n���m��gh��J�!vO�z4�:��F�#�%cHW���N�����,�d8��a��v��=휲0�oƭ>��t:"B�����2 �` ��AC�R_: eS_s�ܖ��Z�A�+`h# |jM�w�������[rJ+ 3� �#��s,��v\G���jG`��:|�"�BJtc�`�~*y?��*�Bs�W��<��� ����E!@ C/�@�}�RUR�^�(�[%��a�)E��ؚ8�m���w�z�b�_����n,ZgE���E� ��%�>��������6����a�}�:ǜ�˱�F8^t��J���a y� � �kI�ȽD��5r�ȑD~f � -^vz�Q�Ԗ<ޣV�,G���׮����>�yܩg�㒧Dj��_cs��d3����:��:�(%�����M���=[�l��%�J�-��+X�x�"4}����"�LMK[�$c��R����je�8�`�m�X�u*�ϫ-���S�R�c��#�cb 3��[���X.�Y�$+ ��!��9���pS� �T+��#W -jUZ�/g\k��\��&��������ogc�Y�*b���U�t�Ё�7��_��;S���������1�JZQһ�Dq)� 8u�'�ҟ��S�i�� ��a͙�U!ERGM٢h�-IA�u�`�C*(������9r��P�Z+�*ۙa6��e �jHp�ɫ>�J������/�:F�X%���jq��e|7��T��GW2��I� -�S��ʠ��D}�B�*��/�#���q����%�jQ�6[c3�Ւlϊ�5����XP�I�%�8��I*)�H~;B�X���()��^ˆ?���ɂM5�U]પT�*��5�$7���z$�T׬�{��m�bQ}��1�ݰi�=��?�����@���8��VO/� ����NGTdL� f1n -ƒ3�a�#���a�?��\鳥>5�4�QC٪ Um� 7�w����{w8�{�Oc�G\��4(Y�2�|I�l�xCSJ�A�".��q��}����<���n�nIR�R�^a�}�'(T���psyrF���PvkCs:����;���<�x���k�z�k�fj<�b����l�TLX�C�3����-����� F�� �%�`�<m��r�xY���q%��(au ܕ�5�����b��q2K/&�F -�DOT�] -`'3��hP�e������gXޟP�N���8d��C3�xPU�N��&mye������r�q[�i�]� ������!*�g)��R��y.C����O� ��r�#����1p��+�[�4��?j${����p~-�������Dc�|�>c��֕���@Q9ԨGE�z��L��oJ��17���S7zF�F+!)���V��hT��E�=�T�TTV��ۋ���O8�Y���b��_��g�*gtV�St,���k>�m����V�Ƙ �"&��R�F�ro^BU�9�K,��7�����q���e�� F�M�i��� -��]�h�ªk �F��窒r6^VJ���8 ��;�Q��9����[B~���u�]F�g�b(Ri���w}:U�C� �������^a����с J���g��?��-b���0�.HB�5���t�!�w��  -�G�T�Yx��5�,A���"�-0������B�%g~������!ob�Gͳre�T�RxQ �w��x��^�V7�U��b�JFє"���i�=dK�S.��HAF�+�Wo�[�co�va� -s�}�w媬f���]��O�&������>�Uݟs/9��ǟ�*YT�8 �,���ٺ�6��a�e��g�_O� �Yms�H��_��J-"��l����q��: �$���-J�`+�j$��]�_w��@�\�|0�h����^�Mn����Ϗ�9t��Q7��&�|���_���/0e$��ѝ�Y -�}���T�ߞ�7Dm�!0u -R�Bދy��#�A��`�gA��!O�q.g�W�A��GX�r���!�n!���,�y�f>1hO_ -H�\Y&���>��Cv�g�G �0���fq4�.e���^z��n)� ��,���<�О�Ge��?���F*��`&Z��� ����h^� -��B?X -�ޣ J-ᢵAk�9j�)���x�/E�1�!��_b� a�gB~����c�!e"`Z��KA�U",�ׯ�Bq�+��Vd�?�TP �%1�h�/� 게3 -&�7G%1a�/"i��043fi"fgHP�I -�HZ��,�.m����t,��3|o��t�����ڱ/.=��{��9����s����±�"�1'�3�`}9�����jԷ� -ṕg[n �A�?�ك� Fs�A߾�=�� [J5k1 ���r����ٱ��w�R�mo@�Q� #����o:�i4vFC�2�g�ݾi_Y�6��r�zo

    wƮM�/{�Y�3y�p�D> B����=�z8`���sM| vE >\Z��np)&�L��E�^y'JE<���0�.���5�Z�vH�>خ���rl���J�%��|v�K��bׂ}f�Mʫ���]�����e��Όw�?��o��������sLr��Qޭ�����B��Q�u��տ����k �,����V�{tD$��kW'�9V�$��O���{�Y`���9 s���e k��5��}/�O�y��7�4]y��*$���� �f�Ƴ�_J,�T�� j�N<6ם%i��sѤ�Gn�AT�Wf��ޗ�gi�d��@f$b���wZ�eqB}�W~�^$�GPCJ�K��~��4f,V -S4�ӏ8D7O)H2{(� �z-mD;�xB����6\p�,I.T>{��*骓�4-Db�����>+V�����^ �4� y�hܔD�&�}�`��!��$���U8� �Ge�y����-�8�M�[yęL&]�2���ӏ#E"J�����>N\S�-d�LB�M8��W|���܃U��2Y|s -]����K��K�(��f�4�C�GOiTp;�N��ie*f3��I8ѵy"� ΢�K@��3�����Q-�G���H�����>bA����3���V��8)c�cf� k-�I[��+���tXl� .�u:%���E�P%��%f���&/i�O���"�T��LfZ+�ʼ�� J�g4���F�����Ӎ�wi,3cs{s��[}��|P��*�^|sE_+"�\�� @�����@�o�iZ�16G,�x1jXo�z�0Ev���N�Fֲ�8�%�������R�)��3� F4,��:LN1%��v`��6��r�}�ueG�)P��zڠM�1����8�����:5�� �S,>t�j:QU�0��D�5L5�~"8j�2 -�5�f�b��h=����D�������:�����T�ȳC< W�Jy2�m��b�f��A�\J�u�E��}�PŖ��-U; -vUs�����>ё���+�4����ȶ4�)G�8!����=�(�`��a|�rUA��j�6Xx�`�/A: (cԆ_�(�3�/gg���>j��C���q����]~���V-��Wv�G�`�Ҿ�ي��l1��B����ݟ��T��,;���8ϒ�oK���:�r�A�Xz%����F"'ju1�gMox�8�����y��� ��8�f�Å�� Ω�л���o� ��=|��O7�W�案����� -V5���9��\��4m~��Pd� �ptD��t�� �0V�O�Y�b`��I�R0�e �~�(�j x�3��i�2�:%�Sm N�6���eс�x�>08�]nP�W��R!@s����*:�����V��*�Z<a�̄����т� �)��3<9%!���n����ĵF�c�c�28UX>#����B��F�4����f��ե�WgqN�лB�pKAdx��:���D���ڽ�T� Fz�6����N���QJ�3�m-}*Mv��mŀf���;� ��%"�i� 8I��w\�m�z��b��eg\2����O m��SkY�i���F�M�Whg��������f�"&�[�7u���k�M��]�]��4(Y3c�y�� ��p�������g��S=�*u goTF��#��SoV>"�� ��'�+Ǎ�6��E݊�P2�m|A�J@�������X.����,k��(�7��BC׳����[� E]^�F���۰��,����,��F���il)��������y��= ��^z�#��l�v 뛮۷]�š0. �hqU�I��ek���ӵ8m��vs� �C�0�BB� i���i��S{��TK�m}�Z�+�6��f�Nw'�e��k��7r��5(ye�f���j��Q�U��̘:�����P���.[UM�l�?nU��q욧�y�T�݋/�+� �� 3]6�qZ�S=�����+M���<�s�H�����M����8w{��Ĺ� ��a��H.��r��`������ﯻgF�al�Rw������i����������؏��/V�s3 Yժ��o��|����Ϭx����w˃P��&|��Z?���n�.#h�.xp��#x�CCn;" ��e��3=�-g�DŽ� ,NO�� Vl�sQg�N8c~@��ː�}ۙ:���� 8[�`�!��"���gf���u�{ǻa���� ���b��)˷a�R� Oh��ּ��pHk��C��u���G0"�$a�NqT-�t�<8�� PM�Es��K��bJV۷�st���.>L�� y���� @�#� I{�!X��s���<���a����@hF��]st$��gܳa���/s?�L� ���$�#�€Ԉ��=x�F��L,��~�z_���IG"!���3b����ssh0�{0�괍6;��k�_���cv�ﶍ�5{mx�;g�q�j��-k��0�?��1���u.���fo�1Fu�鵺�v����i�?f��eg 3���d��f�svi [�y��v�_��yg�C��@����Ӛt�C�4� ���P�vg��6;�F�����d��lt��vs%!��"�3�m�u Inw�Fk����@��d��F���?�� Qs��$X�<2�=�y0�����G���RPJ;h��dh\"��sD5���Ɲ�dl���~�4?2��:-c�u�#R�ddԁȸY�q�Z�a��l2��W�76���`���j��Ϡ!� �m�u�G2����/��A����<��(�⚨�(�5N����qBX�3>v;�^���>"��5r�ag�s:���&P���d4�M��p�:��u�Y�����������rx:��.�����eaZ�� �eՄ���M��%����[��8�����@�"�'���Q�u�W:>8�,s6����W�4����ݑQ�ju;!�M���;����j�l�����F��ԓ(�pӿ� i������ [W��S��]vV!�g8;e'9 |4/�˙хEM�{΄�"W�96FѴ���LB\��M��^�̠����]vP� -�g�]��}��Y\F���(�ݙ;7-دV���YlS9 P�c�kpX6]z�KWW����������,�y����5 -�� �)� ���$�?p �Y̓f��_R�e�;߱�L ,�j-�m� ��ԣ�����~���j�]�<�[��}����3��?��"�3eU=��� -�K�v���t�������pb�u�b_���Oҗ��I͜�E���Va�6�:sf}/-�r�8ly��k��s�Q~_\��g�57'˷������>~PÇ����K���$��e/�bq"&����UEL @�"���8����5c�bûs����x�8���y���b��.��Ɛ� �2�`Ug�s��Clp1�;"� ���A�+,:�qiK�T��5������ث��z-���WE�����bG ��.�x��J �܄�ߴ��)F��դ���GE��<� S3#;}�X���w���[3�X��dc�Y�)�� �+qt�m" (��YU��ڸ�,�Ps�����CY�!g��:����� ��&�F~�(��>�V��2S~���u�����ll��ͧ�� 70��2����xL‘,�--"S�a�U6,3�����PV�&����Q���s�+�"�6��]�계Q�� �������1��ے�";dy!���^r�[Gq��Z`�Y"LQ��R�-���t��Un���(�7���6J<~��n�7���� �K�� �����Z\' �^���~ie��g��Y�`���E���+Os�v��/C��g�˱���4ýF���.�Fr����_�h��=#?g̡q���x�CA�K��osrI�]<ϧ{t�%��2Xm�a^���[ ��gde1]7�4�-_�d��)"� T^y���Of� -���EsXy$U��s�vY<��@n�Yf��5�v��<9┍+#-:Z�"�7Efk-�����i�x����e�؊�pK��A��N��05X��3��vʫV��\Y�]�<�5q���~pĕ檪1��� 2�ٛBlR����̪�?lF nTgJ$R\��S;9�xP��#H[ ҹ�ӵZ���p9�E��m�$��(k��b8gI��u[,Z4��#�bK&�f�v�晆,FR.6Ƀ��@����r�� &S+C�� ��}��{ ����!��O�9�TT}ϻ�wl:+�L���3n��dե?r{�l����bNX��v���`o�n��u�O���)��;q8c��~�[g�R��wS~�)�/[����x��� �.���g�B�}�!��J�5E�U~(L��c��T2I`�qV�Sj���ON�Չ ����_Qo4n0�� �|p5�zxg����N�`!����T�׶��L,x���Z�,���%�!CI�Q]��ϙ��e�3T���!΍�O� Ll��ed4��D?�F�����;BM4n��}���������FL ĄGv�ô`'r&��f`͜;@���`v�Y�2�u��jT�O�ꟼp�E.��g��B"x /�$�9���ψ�W�� e�g��7����8��~� ���㓘�s�L%4bF8��E���u�o�����m-� -P�C`.�����b�tR�V�ӭ�vI�rf7P�ճ���A$+��f��ҳ,R`dãQ��S�*��\¹�y�� ��pt��,�7y� }T�J��?�er��,}���0�3u�W��[����%V,W�_б�y(���J��)n�v4]�X����j�!�\��� -7Yi(�C~1vr���s�YU����m=�{48B��F�����OM2ي�c'T���牵���f�$�cߊ����8J -�z�x�� �;'\�o���~"���6��`K�j��*yeG���� j�CF��:�����AUÇg@�h�(Z$�Y -�N�;��d�����D�IB���>6YQ�ʪ�x�w)� -A�j �ȹF�WTO%�f�`��"����D� ��V�5��ְ{���'��{SVE����E�oOI�8J� ��r~ ��f|��D�AN���jc4��Xm��K~��xPZ�5<�u��.�k��`�;��oZ�W4^r�F���Oo*y!!��J{�<|��es�|�ZPW���d':� ���Z�bu��+�+f �/O��qF`�e2ٯ�� -�ڌUp&=� ��ʪB��Z�1������A�����\&x�� �B~��އH���TL�/Ep �WX�+���?�\ƔF��`�*dC^*|x�0��H���B� �-S�Dwꩻ���w��1}o��$��M��4A�&���DE��B|i��q��רӐ�ҷVyT��"�ANYe�f+e:�"j�e�4_�Yrڮ��PV�-4�_���B���i%-�N�dj�ޤ���Z=�A������·� 9����Mi%�'�hG����XY����ܟi�,_>�Pu+l�"+�J����B�d�ޟ@�JL���bG�6��-�M�j�������]99�:�T��� 2\���s�����j[4�$K;��f�q�J/���B���LVyVm-^T[��g�ȷ s -�T �����@Y>�Z�&e��^�U�*�eKu���WP�<�Ψc�yT&Q����O:_����M�?��M-t�]����L�������2gE G�TF��Z�����N����#z� ��/�۵=�+ -���B��J�qlK3�f��ڃJ�_�P=�ͳ7//"CܽU�6�vI��c�g�I�+��� Wa�,�5 -��0� h�W`��?�P�~�o��^�E���+��v����؟)�_��G4�z���q���iOa -.��M��lZ�mOY�/4��7}_Q��O��85D���������=��;��w ���I����.�S\2W��T݉�6hឥ�V��Qro�]���x�z�2�SO E��'9�̹�{�e�ܷ��\�n����|�P�n�2���r��լ�kBFW�O%d�\�EH)�&)KK��2���'=�� �VX�J����vU-�z�2M�3%m|��QYJ�X�LޕD��ߊ�T+Q?F�Ǹ6bL6��ʘ~�W7v`>)[c���X�qΡ�v.����o���ֹ�Oɬ�N`��M�G�C�t�=�JN*�/�<�Q ��/3jm��G/�E��}�������^��!&*�wE��]�� ߈V�����_���GPZ�m���},� �}J����W�9�_�떊d.O�{s??���~���?eT�$*�fh C+�R�g���wf3��<��zyS��� -�t$����7y?���<7�qkё��=�c5.G'����>����)�:��!ˎ.5�`>j����Ǔ�'���۹���$�֨���y�Pǒ�,e�r�(/��-G��JL������=���H�XgٓJK��Α�|�u}��)�Pcq� -�I�#��^�sb������L�'5��Xn������ \�ݢr��Rp�{�x+�P{/-���N�� ����АjOO�j|���\����n���öu����n�0 ��y -8�j���M�;�H���5RI#�AB��N�k;X5_b%��|��3�9eqOޡ&�d�&��{�"/�NJAU:G�a�:��>��K����!��2,�=�ȇ\o�wl(��L�v3��EL@p�F.|�F�.X-������^8h� <7Q#�S5����k=/�/�H�>] ��L�����v�o�-���<��o�M\Gĝ�U�v�� }�_o�0���}X��s�s���K�bBj�J��6��dY��k+0@T˹�w�)�/t�#I 0�2 9lp�,��f�(b�C^(��b��aō]�� /���5dc����'"�a%c�t�HvJU�ga�ȏ���Y��a��E�.�(��fWp ;�5�QS���"�J���{�36�U����uZ�gd_Jf��$M����d6iȓ�0iyU1��6����֓�vh=ѹ����� �kZQy��20[�<��l�?����w6�3�Gyd���J3���` -��{wK�4}^n�^����6�[SD����x4�]����gVu��f�ф�3Є��n � ���#m[UL-Reí�kQ$����fZ�P�����w�UX�OQ@(֢�����~Gcɀ�tKIg!y�� �2=+�f�<�a �G4s��}s�k#��:���V����a2���� ��� %�p`JZg������qM����!�9u*8�ݕ -- -���7� St܌��W�-��3nX��`4��%�r{8ľhʔ���� �fe�{��N���ԝ��88]�>�.�� -��ǵ��ך��7(���\�&{agR�T��� -�+ -�J�7w�_ -�X���Ub�� Jঞ����I��' -�qʨ{~� -��O�¶ �~�d1Ϙ���X�OOc�z�&D�k����/���� -W��m��e�h����(>��V��� -"lFt���TFU���Q|Ca�P&��wt<}��F�H�lv��KH3x=�Px�s��qp�g�Ds��Д�0�2�z�|†�8��I����cL,�V!ǝk;9�& �?�w�6�Ce��f~0���G�<��Z՛��|�ju����{2��&��k��S"�&�iE�J���ڸm -�I���=Sr�����_Vo�����Y\��m�4 ����`��RH�FU���a]���䁶$��ߗh��4D�N}��ܒ�-��3�ƾ�{r�ˠ���^�H_�^�,��*d��jR��i�l�rD�ϮU[c������V�n�0��+���a#�9�Ӣi�^� iO���("J�I� -�{I��L���"����f�>^ei6d�:#�g���j�%7�K�����E9��JΑ��P��4�n]L&�&���( J�����g�=�0��2��is�fSkN����3>U�Q�v��PlPP�g��� ��5��3Ψ݆�f��^C� j���@��F��D����)�'mrb[4ୱI���[ �E@�5�D��7b�a�)�U��H-�3�b�]$Ɖ���)4���F��m���l��� �%&��U|�����"`2�ƪ����w��ln}���6��r�+��,v��p� pa ��E/N}e�m1�W1n%R!�i���k�@t�r!╓�3��R6$�-FÄ���A�;�-Q�"��vԺ�9���nO��6��xߡ�SC�U�]�����0Dt�!��COϫ�kO�v�+�:�x��Z v8��ح����ԙr݆�$�.+{(ͮ?�k3ûǪ�C��:}>��c��(���0�XM�:�u-%|���쏙�v���"7�P�rh�%bK��,��0<�j� ��h#C ����0�F�k�]�0W�G;l���L�ߞ]�>� -��Q�W��@K�U����p�`��X�r�6}�W�3jI9���4/���݋;n�q҇���@$h!�ek�{�E RJ�I,vg��<�!�%{�̩JH@�����T޽N�� ៽=��D)8�D(*/5�D l��tN�VpW>̗�1:8(��~\ DJ��<�׉d �)�B&i��Up�-���{MAE)�@Q`\�J�1 Jy���0��+-�@�w���K ��v͓>n *�E��oH�1e��>|�͹JB25�u]f����c䟟�O��"3n�����u�Ax�z\h�D�C������ ���=��D� ����T�gGG�|Ab�+��ކ�Q�v�qW�t�$ Y`�iz�P��[���E�U|D?&! DH}��pkC�n�@JК~u�����[OҿS$����H ���u��NϠ��R!�.]�k��S�0�=C;�1('��'}���ԸM��Sz�X9���óD��*$���W�8(��o*^up�i�W��7]*�XݧV�m�\5KKY@��DQk��%��I�*��p�ֳ����&l��y�� �yI]�������V��E��'+�^���k����f��k�[��1��+v�"kusLh�({/��,I�.�mp9����1ÝB��n��[s[�v�q��Ɓp#��9�m�t��B�TIu*ygp��Rn�q��[�|:k���塐p�T�S|�[�Y����h`~E�Ϙջ���κ���X�ܹ�8��@�r�sɥ]]�N�z�k>f���Ǭ�i�ء~m�� [˚%pkk:�sl'!����iG�4`X��'� g�,*������HhhYҵ�������M`����O��o������z�J��J���4�]Y�/n!������o���j�o��L|�n����:��.�Z���f�x'�g|���L*Ǣr��btE�֔Z�c����ο��^�����^ ��q�d*GgU�� E=��Rb�����c��̴N�F#,OCN�hNxJ��#��D����9�V��a��'�9�E _�^&t�>�֎��v��aA�&�E��3Q��?Ă��y$2-���\�Um�- �5•�#�x�K)0�c��ahl4\����s�T��]����Pq7�T@b"�������=��ql�E�ũܑ|hk ����|F�ejJ*g� �=��G謎6�B���=F���e�#C�F�4��/D M�9P#��u���� e��*A11�H��g��b{X��yR^�!W �����e@� �1���(/���]%2+u"��WCn�����JX��X��n�hm�~���Xh�����j�_�Zmo�8��_��Z.\� ��]�Ul9ֱ���l��"ѱ6����m���Po�D�r�8m$q8��p8����j{t���yI���)��V1Q�.9}���W����J�Ч�lm��4�#� �n���K?`ou�&�wDB����}��Mu�(��]�>�}��"J<�D�.t(�r��v�D�A��z�ыW$��`�M�zKϱ�Ay�!%[n�8�.ن����C��c����:x��;���a��u���]��԰E$Xf����]�>� `��}<`Sf)?�=��nH����!���nHuֶ��a_��rv�Ѐ�����TW7pv����C�x�AH6vLC�^G���c�9Ex��c}�Ʒ7�U<���(�� -�(����-EGMB})� `�1%���� ���D�2~oȘenm��~}=����O-�8M� �$�ll]��F�yn�>�#mDή�Q#������/,r1��4�$�t_����-�|x����9�<ЦN������L�� �_�':��:�t��}:�,F���G����"�R��Қ�h��3��ɥf /�U=�'�uͤ�uk�� R%sհ��b��i�0�3S#��H7�U��F}�r��Y�ZļP'�Ơ�.2<��z6�q��H7����OC�#���9׆:>hj��j\3 �/p6�?@�d�^�砣R1P�:8HÅ�]"��Y��3�ҭ�����l�,oj�g}����df2�-L�B,�������|�0u��ҧ�f���Ϧ]0�X���{�l=�2��X3���1�P��Յ�aا,��T4� Z<%H{Z��d��O�sm:԰u���tS�2�2ti�D�� -��>4��HC[�2!!��{@,c!�|G&�L�4t`��%����[d�҃۵ܯГ�`Y�K,��t6՚��w��Ђ`@�����%���h�YipaG+u}S5^m4I�d>m���#�D�vVg2��0H� o�p�iI�I�6 0�@S�n!����wXZ}si�\��(*�s���;�<�ՇrT�;��ô2e~1���d0+^���H:�w_����w�!�H�d�'���T�Ώf%�������D��PR��J�]UpU��$�PܳA���;�]��{ �c%d�-[�G�T/��!_�d%�)��'����d"��G�>��b�۫`� |4��0��=+��6f�8eꌺ�#�U�#�po�K�΀}Th\_��E7 \A_^q$}�`e� �֮=���[K����p䭄y'�* UZ g�E�v���دp�@S�� X�,��� �+t k�CK��,l��P�E���� #�S���%�i�׀��3¶�31r�2��ʔ� +O{���k8���,�܃#�@[!�8�I�p�k+L���۴Nq���a�ZWY¾�Zyb���m�U`*�6X��o�1 -�f�J�h�ؘL��Fa�l`�%9B�Y� 5�7�nCC�)w�/�*H�BW�� 0��7�dB��NO�Q���}�N��}JY����^��s���=�8�Ie�Yz]P�����"���9�e��/���I��T��fַ͓a�!��;d���F{��[���!����w� -��RH�&N��=p�_�r��&�-������QNyb��H�?��aV�{{�6�-��wm˴)�x�H4�6�)�� =���TȾ�գ$t�2��f��*���=��C�~h�> ޚ#b�7�1��c�py����G��XS�)�>�� -�e6+�`&/�4���ώ�xM���ya���ДK6<��.I�����H�;���%�jN�YK�*6����,qWа%c��]�M�[0�c��y ��~��(�2JJ�J��f6/��Z)ѳ ��5 FC�'��]�4�?�x@Z� �1���u�u^������$9P���eG�����N�=���%}z�� -s�x����U�`��jx�Τ��0���h f��J���rL��v�1u1N�5�q�Sijp����,_��-�p��� �m�I���q��?��.�8�j@ʟ�1?��V"���Vd�_ -��g�b�|# ����̾"�r��Q~RԴI.��f���Χ���5y5�&\���gP��a�ؠlv\Uh��h�C!L���V+=�T����Y3�N��5�8R�][O[Z�����G�qx������M�s���N:�BUh��&��(��D4�Y��$S�����Cj�'�}���?+)���9)�NN�Kc��Sb���x�y��,J`���f�K��n7إTA�8�RH�ؽ=m`Z�~��ZcA�џ9HI��|`�p3���4Vp�F��*�y���Q��+-!S�2P�G��B�' �|O�N/Y$�Ov�)|72�(��r* ��AqF]�� J~/�I����G����q��R�� �vu��_nji� -�=JBf�6�u�?��c�c�C�=���=ӽ�q� �>9�0�o�!g�ܵ����KC����!�2��� � �o ���s��7:�]�x�!M�~ȭ�c��4�#/`@)�<���}�40.�+� L�ٙ1&�N�� ɚ}�U�0a��� -)��' uD����Y��G�����}9`����6��%���hC*�5�ِ�{��A��q����l�|��+n��/�f`��4hQP5����8�aq7d�?��s{(�5*�LM���֞�sF�<��pI ] -� ױ��$�Ir(��-1*����]:����]���y�z��|� ��_�yL��!���Ά.-� 0�+N�+c����W��5��oN��"��G�+Ol&�D�GKnϻQ���3<�}7�g8s������;=c���p&!�� i���!};��]�,J}�d�R8�&�8[ ���zF��q#��YԞ �����O��X������bQӧ�p.�Y��[���B�;8�O��� �t�o&nE�kCQ���V.��n�m��`�`��c����+�x��4�d]S��P��i�0 h�����a���� Nwٶ��v=���a?e�|b�Tw� -��G�Q<�n1�T��>��i��M`�K���-ͿV�VF�һ�˯E Ff��v�n�>t��ֳ+�\��>�[]�Ub���"�[�|����͇��|)~�AC��j�u���߈�"�H���n�U�l�D��z��TYo�3��d�嬐w����g���ł�t�(��l8��vA_�Q���c�ϰ�!80~���](�0��-��{JF�m���O�|��v��fÍ���0h�����ǨU�U�W!�{q[�핇]�Q���e5�v�1]���vGV���Ve�����ZW��˰����u=�:��YmO�H�ί�!�3ʼ�~�a��I���YہEBu�X��9��r;���j��_��|8�!͐���z���������������G����) ��s������~���_AOc����y�g���_���ǀ���E �3Hy��|���� ��4\y������CC���œu�� 6I���������wR�M�p��Ƅ�R;�n�<����! �C~�r��#N%�a|~!�eBi��//�p� �HR~�t���Oΐ,��u�@K2Rq��>�h�"aN�p�X�U?bᖧ�A���H6�mP ��!�| ���8A���� �%A��,�iȢ�e�� �#u<�$�-'b� ����"^b%i&�l��9%z��\�3�e���0!^�$1a� eD�d�?b6H0�fَ��g�R���^q�hYV�Ļ0]p��w�;��c_�3c�\4`j/o��ƒ ۚ� �b�O�cNV����.j�Ńk���?���`;`^.-�Ѐ�/<�p�`.��jf.�ǀ"���yiz(��㒚�P{��3����ĴL�FX��ނ,�ѤK������BZ����@.�Lwj��1��4�.W���B�,���D�]�HV�XFi���1�ȳ�OS�#����.��I�? �Hwn����Ȯ�� -�pf��~�>j���C�4]9�%���&�gz+πs۞�Ȼ�seN �,��[���x�������y�rM�a� �p���3���p�B�:j�D����e;7�K�[1�� ��/D+���8��h��՜��qn���bjЪM@צk�Dj9�K2fi�ZG�+r_lr+?��y,��9�+�ȗ�"%0"f�>��]M/������c�wvǩ�t��(a�x� -��T�۰��KW�1�;��k�1���;��Z��t$� ����s���V҆���H�+�6 S�czr}NJ'����7���a�]�� �NJ�؊� �L�I�#�j���=��x����>��M�}!s� -���+8$rf�R���v(oK����F�Ɲ�M�bN�x^jiM��j?���?���p��xG{��0��A�ejmJ� I�Fo���vQ5� �QS9ħ.V -����h� ^�UM]�A����$V>����E��K���ћ�dxȧCcTJw���sL�X 5$�T���<��g/y7jN�O����(/#f��w�U-r�&�so�!,Np��A�K�qӻ"�VX� ���ơ���F��F�H��,g��q*��o� y��-i�+�:�葵 �}�✚i%�rO���� �xn������h�M�8&��t�=��D8 �Ҕ=��>�榈��> �&�F��0�ܧxY'���:���0��./��d��f1D!�[��@ds}�.I��q ��o��s����6�ᠼ@m�Lɮ��Q�A Gq殓$�z��m[Z���VAc�y����Ԭ�_f�z*�?T}���+�g8T�R�>C��K���դ�¨�:�]��D���������{�n� ��i�Ԓ��,�[9�}�2�-˾���\Z��_��ڑ=\�嘩�J�"Ux����.�6�T� -�D�_ߺ7�\][���=��)A�98%ܳtR�Q��傜j�ڛ,��@xO�&�A��}5�:$Y�i���5�^Zk]d�R��s������bI��ex�I��"��bA��KM����m�P�t� ��SB����B���d����m�v�9<+��S�O)�O�� n�"��-�ߵ��}��ޝڜtE\zK�K���]�k=�t�!�JC݁K���}�V�\�O -5Z�Q��d�J�\�Q�i���h�G隥[���(a�0�TJbEju������0��в�4w��}*�6Q��8đT�b�`�N@dpb�p����:��V��g�w�5��?�95��vg�U��D9TW�����T�����֎)���}H_]�%i�8�\�Z�Rd|� -'�Tˇ])+�ӑ=�¤6���=�[�6W���k�['Ys/�&��]�S����3Ry�m���R���)̾�GM>Sz+)���sJ{w>�$��I��!H8��&q������w�������ʵ������T����\,�bRi� e�!�������1��`RQ���W,t&߆��|���w���ڨ���튥!%���ʷ&gp���B�f�AH�kBs,��mH}S�^,7n�� �Vt�����]+�!c�ܶ!����@��S'��X>��O�K]��}�_�G��UQo�6~��8yp5��֥�BK�M@�<��k`@!K�#T=RJ ��;R֬��З���xw�}��x�����0���������c����޾�����@t-aRe��2�����ׅ�h�IU��6����I׸nM�,J��r�6��!� h����Z�K��)�L��V���lAi�����(�e�Y�bfZ�A�}�4���VOe�/�c���D��R�e��\�Ei� ����#/�����I�@��4XO�!Y �mԓ5�Jժ)s�uh�.�� --�0q]��¬y��{���� f�ҳ�j��O��Xk��v/�Ɖ�b� �B �����*s��q.zPȰ�H�.����^ZbgV����wXJ���>{������(�u�Fi{��U#�� � -$��[4t��m��z����A��0��ݧm{�]�3�D�OŊ$�}��,�L�h����u�fs�8 hD�F"a�Tĸ��p�|��H��i�P�!N�-�!C�H��0�n�Xİ\ 8�H�LBڥÂ�P_��No>�$C���̾�O+"��U�����)��� 3�q|&Й:v��4� K<�Z(�N�`"fq8�9M�O��1w¥�z�D��UC3�ORά~�E�&I�,�.Q�*�L FN�8r5�Xq���V ����q�#7��p���Q@_ =1+�)�BDg!��ȧ�[���ҵV¸�a]��̩-�mr�^�칭6<0K�sv-���c��*O��Q��d���K���X<��� -���xƴ;n?w�ܟ��ODx?���‰X�h��ώ���M$-A8$�p��_m)� �$�����.��q��l��c��� �� kضu���ϟq�@�y6>��)/� � ;��~�i���u{�0W�v� ���.z��靃�"��9�Una<�wn�^����Z��kkG��a�c��o��^G��S�n�0 �����Mݽ[�J��S��ڪʂ["�9I�i�(e!�e������xxR� -$;�V�#|���H�5y��~�����i\����\�1��� 1<�6$�~�q.��Y�82�0�R ����w͆1��a��%8wW� ���npe�2�ag%/�`��E�!�M�(��o�~s�6ۍ�BipbbR����΀��O�V�΂� =*E���S�?KJ�O��X��s+M�N���b/[�F5����ֶF�%��>�>9KޗY��7�͋��לJ�ª�?��Ax#��f6w���f�U�.�eݻS����#�kdX�Z�R�[o�b��נ�]3_�(���ks�H�{~�d˷@�_ɥ���� Ǫ��!�l�I�d�*�DI���_w��4c��>ؠ���w�� ��_�_l�z���b�`~z�1������o�ovvcF�sv0u�+�{����g��� ������숅<��5�l�{�šw����g�?a��3�gQ�ǜޜy�ޱ� �EMv�ŗ,����,�x���EM�醜�y8��O�< �� |�/��p�3�7��Ɓ?�p^D�f<�K�b%�"��D�� @/���] Ѻg�5�������ċ�"yaR� -VO]o��- 5��$���v� -A,�u�3��$�!L������s�Q�R͖�Y�q��"���8V�0?ȇ��p�+����{��8p0�O`���-� �L� �M�H0GvB"Qp߀5��R3��|�vs=�����E'Αe3��|1�&�σa���1;��+ ���|Z��v��v̡͌^����u0r���Æ����������?CӶYȬ�A�|����9�i7��kwG����I�ﰮul9����4S1��ٱ9l�W���Z�WZ��rz��!,i��1t���k �`4�m�!��nw ���l�.3?�=��GF������E�&ktM�0ܱ�f�A��Om�#�m2{`�-�`�aG��+qs�m�wp0�:Ʊ� x�T�*�=��Hx�Q٣۱��c�O�~�$o���V۴�Y�o��F�لE�������`d[(?�e�s8 ��k�����RfwH��� ���"^��ɾ���ޣP��3P6��Ȑ�*�ӑ�e=�S��d��&����6dZC�FK,�ŀ�G�>) h%sn�j�uȌ�g ��d +1xk��G��S��8w�W�G�2�c��;���|,$w[-�|̣� -3�U�}�BD�#���� -C�/^P��9s.��y�-%��x��lo��р��z qh6wcz;����5�e9���֚lw���w�}��Y����d`�Fв�)������dm��L�͌P���� �.��H&��m��8�;aϣ"(F7�����v�My�`Nx���YkQ�ڦ�D4��|�1�o,2�9J!�>��!� ����؝���н�g0�$r_ZX 2ĘGQ��5U���U`!4B�X /��C|U��+���c��3s(��K�-��Y���_$���+��Ay�����eь�ݯ��D�gt�]�4%�}��{�l>8O�(�9טD���U�A�a���B���^\mN9���q �A�v͕�W�7���;%*�ϣG��j�*�����hy�O;��$�%�2̙��k>�@�N=#����`�N)AL8�ń�coW 0��������.� ��?����)T�P�BeY/���c<>Xsn~(�u�N�� �r>�Dd.�x�9� D�϶0I�o224Q�g�I�d̐�Cw�lJ��xCJ�i߫���E� -��CR�Ȩõ�""�^�����~��^�ݧ,O�ت�on.�OP�+/[4�a�}))}r��)j���@���bm���*Nj�Ї�%n+"��f��s�3(�M.-SLq���$���!f�x���Ѿ1��y -�bȏb��D��ǛNi�����g{��yv�WE8b��-�����j�w���}�Vc{�� -;c� �*�&C�IUq�N�B���w��A�)��f�-���6�[��P�������v696�.��Q�W�@t���K��� -�oe�O�;e{:�N��ܗ#��ؽ�E�9��I���$�"��0o�n�H6��ի<���}j�ch8��j��������ul- ��MK5!�Q&�~�د�����#�Hy>q��ך5͔��eB��O�-���y@ yL�y'�w�0֥ �j. �+���q|--ء�P��_��M^����1;�#ȗ~|^ײU{c�xvsI�����k�_��2������s>�� R�a�mlմ�A�Z so�^�H@�� -ݔ���FQ�HZ|�jT�8�"-�!��Lr��VkGE��DE�U@ 7�UZ~�f�r� -�#��$�C,1���fR:A�Ɣ������|*�zY�́� 4Er�+XT��^L� -;_a��j�Ty�\�K��#k��W�ޔ��4�3��0�b��:���1�� � �'�O`��+c��6����x�������]����*y͠@����)���ZO ���_�-���*�Ϯ)Q��D7��[m9�(r���[�w7|6��X~%0�T��A��U�b������%��,nh���S�}�����N�Li_�ƍ��K�\E��ۉ�rc��HY���,��>��0�:.9��/�=�j�%;|��&��xD ���8�l���~�(7�v�e�pNG�6^�N'dS�Qlg����!!�y�U�M| -�,g"�y��);� (ݍ�����"���/X4���E�ш����^�f�6\08�Ʃ�U�sz�gZ�ڥ֬n�DW^�uO|���r��{����N�i�3o�[Tx}oD ��kQ�a 7����~�ZJ�Iw�J� g�_Y�2�]N��� -��x�,���X&!s3����m'.��3e�CK�;*x��\�<�X����5&s�h��P��m��� �qې��q�@n���}�J��[J]��g������%!�g���ƭ�퉏��Ԛ�]����A�.q�G�����>�'�k<�ܯiz�4R��9���X��IZ� �g|�}u�r�%_*]�U�m���U, V L���B�+]��b���Ւ�kVI��nഅ}����.)P���֤�{,)���+Otg}@�Ď�\��� %�ә��D[�ω�UuӋφE��BKQ�)�el�~�"�xhtms�i��5�v-K�^�z٪Gۃ�\M�� �S>�k�U*����"���'?A)P���=\[�S�~��(\KV ��t�O~� Y/?M^{ޓ1V;L&]��LX!햲��^6���/W��~_��]��9����c�/ *��� Vu�_��&u4u�:��Aׄ�ş�Wv66w�$�?D����,XD�o���x� ����-U[>%�}`o�#(�x�]jQ�_�E�2ð��;���}��2�2Q �rA��$��_FgG#,=����� L�������~������QAN�0��{�Q�<��= qA�r�M6��k[k�!���i"�^�dYk��Ό�~��⑂ǖ�m��oRt�a�R��*<�=xF&�ȁ��^�P6}F�6BGrE�C�gmA&Ap���׆��f�SC^�[���8X���~���D������X��(��D􉺥��J�H�gO/���\+���yUc �l1��.^�v6��'� � ��˩�C…jbl$B<®D� o�ē���v�c�����B2��T�ӧ��-�ɶY��S��c�V�G��R�n�0��+���%��� �7!c �d��vh����&i6P�4�̼�Y��8�>Q�`B�-�������H.��=l���56�<%Xa#U1s5 ��x� -�QL7Qe0)�D�36�=S�X4F5,+��P0��ܐ@��4�W�jZ����F׭+�qF�� -b��ݖ� � �R�(&1���:�i��M���Ce{�,O�� '�'�y����uW���A��xƚ�&;;"2�5�\´K��&Un�\)�Sn���h�Yk�fp%�Nw�y��F0�$��V��>Q�=�� ni�'��wn+'6��O\f^c_�,�����3��(N�[ E>��9f�i��G�8E>�E��AއY�g�E�Q-�k;e��?���T"5*bw]�,����x?�Xmo����_qZee\�$��+m�i�m��:� 8٨�,l�1 -���Z�������o�� 3g���̙|^����۷��R7]���a��6iӇ����}8}�;Y"�ɣ�rIx�2.��C�@0���LH�=��x3W��"�d�E�e� IHK)(JH��l"��8J�lE�4�K���|Fi���eN�4���$`�� 2A �ͣ<!-��) -q�ς?Xq�>G�M�$�x�T��"?�ٸo��tZ8IC�Z�>� g�`�>�T�$ͣ�з��HR Pƪ��5 �y�\d��5Y *�*,C�%������a:Y�E���TA��yK!��<�E��&G%V!Tjr���`�$� 6���$�Nˍk�4�U����ƂI�RI���]�4�0C -���t�?�5U���r!&�K����S1Y�R���k�#Ϲ�� �$�\���=��cҤ�3�w��k���~�t=2�Fmߵ:C������7�ļaߓ���5=����A�&����[���ew�Þe_�aǧ�uc���}k��@�%ݘn��F��[���|i�6k��Z���[�a�p ���8�I�n��}ú1{�0�ɼ5m��k��o��T]/@;& 7:}s���,����������ؾN���Z|c�i�3ý/��zh���! �7���j�E���� ;�\pް���?�M�r��ʆg��V��Ω�x*�C�ԡ�7t���i�w����,�,�7]w8�-�n#(w�,6��S�wl�?����́Q������8�`�垃ipx<��W��1�+��m^��+��<�0؝��z�屜�6�΀C�J&l\�V(����uIF��b'��%]!kC/�x���&#���eL��Ov|�a9,�?3���'ٗm���U�����Ĩ��T�u��׏�>��R� ��g�"�^�B�RR|Ps�(Զ�7 ��a�|��ZĴ���bT��/T�'�e:�Ś��I:_�#�_ ��b�0:P\�&� �i�ц9=$A�̄�Z��#Í� -���,Xi�&A�\,� }�W��.*W��B�y����]|����3 aDߗ���?59�4Ƚ�I�/Ev ^�A �.��j�F#Z8 q�h�t�9�����ʪ�#>��}������V�6 ,����/�(E��R���v��L�`L�>��%U�N��u%є��Q2Z�U�����L�i�C�]��+�e�3%♾[�ك�o̿&b��i{����_�O�V��� z�|�-=&�s?�6�x��-�tT2�XuR�-�Zy�����џ�4��ʱ�ն�?c�U ��� �� C�A������xe���j���Q�툫� zʢ�:���t~��T;�F��5ȕ̗�&���H��Wn���L�(y����&+�r��s��{���Z-��?qF\��}�����<���r:Ek� -�S�0M˜�8T�B��:����Vٷ���D�V�)�<.ꆈ��v�o� �m�`+g����>pִ�O���=V���/J����F�E�;/��f�3���{:��4E�S�(�u�r�t�B���d���vl�yŪK$X6@���筘.w�����R��P��C�5K����� ��A_��d[�(o_�kQ�D/��L(P"�B����F�˶�(����Ntv��f�k\;9!K򧍌���T�p+2E8�찞Q߹�T�jsA�E sR=��:n�g\'��9�x��"��Nc���{K�0� �Y��g�����=�����M�� -1D���-,��/P�N�+��Lp݄d�ܿ�S�Yv�1��F6w��X��a���]�@�\��4Pe�� m(���>���o#�Ɓ���g�r&oa,l��+Jn�Y���JI 2�C�C��Z�~l�:$Y-����T�F��Z�~U�g���y-�1�0 Ew��d���L��Ť.���q��;%�/O�Oz�C�3(=�d��יf���/i��u��''�x��֑� ��� ����bq}�߮1�{��S��(Z�lɌ^�Ikw����Xmo�H��_1�"W4��[��a��+��4�U�u���Krѩ���Y ��is�]>԰;/�<3;��������ŋzA�t��E7�iA�^�z����W��!#K$ b?�&�"��>�?.���P�gm#�Ii��\fw2<�:o92��"�e� �IHe.)J(O�,�je%~�@�4[�:�G�-���M˂Vi-��g:��3Ik�����!���. -�P�������}��P�&a�z�RZ���E;�rJ� � !]��)|�e��"�㭆�$-�@�5�H�S �l��8 ;��5��h%�㟠��/ D�@�?�:�0 ʕL -Ezc�'�K -��V~!�ȏ�MT�v+�v�)]�I��d`� -K��v^�l�Y�Y���\H�$%��ؔ\3��J IM�$ʑ�ب��eq�jh�5e��e�u݈�/��J�B��V$�X����ޕᘄ�c#sD�kl�4��׎�{4����dLFX�x��< �� �Cux�gL�����K�1j�:�p��3Ǽd��9�rg���3�¶G�y�t>��鞑e����k�p�:�� -X�6�3W0lKL<�qfSOؓ>H�C@j@{���'*f�e;�l��P���jlbi��V��L� �^[^��� -�&�%.����]� ] ���r��2�r~e���WI��UκJ-�s2F����UI�Q�V��p\�ߜ��k?���H>VNl���Z�K��L��M����� �� Dct�$W��['���$�V�Rf4���VQӑT��Q�{pK ��{a��������%w <�%���)�j��ȿ%���O�P�N�/�}���Bq�=e��,�B�C� �5���x��OG�\�vD2?��^��ٞ况wԫ{g�d�Q��Y��� ����>,5�1�v���\�hY&�����+�Q��e'G�N�����>(����$)NO�V7��g[�G<�^�o�����'�q%�S�Jo���ՁX�H6Eu��ڎt5�e���!0�ŨU���KNo�`8^�?'L/���1�QO�!]�eKbO��4���TU��h����iMi=����C�S� �^��r���\�*\��y���B�5+ό�C�O�t*�6\iJ�(�?y+�|��u񨐫u���B\��,�ş��*���N~.G���m�hIZ+?]͎�a��NON�����D�"����|>�� ?��LJ=:��mg?H�����Y����Z8HWk|�|�^e<~���\X�|އ�� �64��k���6#����0�ӭu� :���f�f��as��>�9-�^��/_7�1L��NN�f+͟C��J�㵆8�ƍ�v}u��HT�,��l��y�6��{O!JH{l=x�O(��^*�I�Z#;K���Z����q�9���)�𷧩7oZ��O� ܴ����T��*.֖m�1C�L��Q���z|�t�$�� �+���m���^5 ���C�5��uF����[�#���X@�B.�j ��w(�-a��Z���&%�T}��Ta���(��e��n�wfc���Oƀ�/a�+w�K�!����yOC�w�fs���}��{;PJ.���Z���\{"UU"8�%�z��Q3������N�0 ��} -&��Fw��1$$���*���&�I&!�w'��ҵ�K����vo塌/Д\ �p�H���:�|�F�ȹ1���J��' ���+T;$$�E�c��6��ۯ(��`�u�\�dV�K�GnF�����pcI���������s�pK���� ���ċV=�C���R��S�J� ˄V^� 7��S���7 !F� ��m�a�1�C�}*�1�����T!��3B�H���i��]Ŝ�V���p@�{�8,o�s��L�;��6��a�d�O3�_��V��a]n��J�mX� �Oz#K��a�U����X��z�q�7m�'g�f����V]o�8}ﯸu�tD?v��sK�m,Q�bh'�J�ECp�I��j��;��if4/�C��s�9��|�sy�< <����zG�Z>5��}K���ޟ�}8~��r�ZҨ���i5}��?�u��B~�ܪ"����Z6�8��u!�,J�6�lՖ���.h�%�5i�jriGfe�5O4W�B;�X�����V--TQ��<3N��5���Y�m+ Z6�,�h�?XU����rU�Y����l?�p|�S��wsU`�J���f n೙z0S���j�\:[DBX����ꓨ��ȜWY��ͮ{���Aϯ�(V`�?����B嫅�[�!}P,>ž)5��ZٔY���c7�"����b��:[HCr�*k���kL��>�E�D3i��ɺ@�4�^ �JZ[��Q�4���!���#��ؕ�^���%֗�ZS���(�ޣ,sA"�Hnܘړ8��>�i4�$#/�Lc~9Nh>�����0��(M" �qV�y~1�Sb�&1�����$��D�� ΄C<����C��G ��'�L"gK���肮X��uG<���f��Ih�^ �K7N��nܡM�x FF�υ����'���ĮY���A�W=���w�#��(`���y�Q�my�d�Ąy�4�7en<}V��� �_)bC�{�^B�pǰ���yi̮��袃�H$Ϊ��I�����=��zUU��J����P�I�q˭�g?���bC?�W�/G^��9�j��-���)#^ϕ��[߽�� �_�ٌt�����N0q����_�4+ -T�p0�gO���8�w�^��ߏ��m��J�@��y�9����Т5]!PjhzD’�� a7�N������M鰇��͟o���n�E�90�֊2q8�Y�VU�W5���.�5-^sކ���ȭ����T?�{ǥ�䱜�߽�bsv�X`<ۦ%��0�},ry�n��[FY��w������&���X8��&�b��|�5���,/hIP�m��J�0���s!l �>�.Z� -Ŋ� ���L����d���;�t�!C�9s�����z w�S��I��W!���� .S��^�8#��41��|�9��������pʁu�4�n�-ig�i��.�2�r]�F�Ï�@�t �%؀���c���63Z�S�����;:���,-��*�����y�^�+,�yr�}�_�hv�C�7�ǧ!<~���dw�T�6��l|�ۋ0�fz8�j<�:[��_hu9��^����G�9���Y%~m��j�0 ��y -vH`����K=�6�= �&VR��S[��w��li��Ï>I���ݱK x��T��$�N��5����G1��D� -��"����0 z�-��E(� �p҂�(%��Ul�V��a�<�KjzS��&����>���r�'A����%�^����� \�zA�մpN|�UUU�Yq�)�!� �.i�I� -;iL�{6Owx�Ø��l]샛O�3η��]�5g��m��垕l�������%fd� ]�Mk�0����9�P����� �`w�z(���v�f�xX���Njq����y�<�}6g9���}�;�ɭ�R�KK"�����k�C��s ���C�^50�`m��f���w�.��֊+���yV�UU�U�}Ɠ��6�*��G��P>����K���؜�]�V������`�})�=F�ҷ��:?��CVRF/�f����[�PH<1!�|�5ώ�,/hIp� m��J�0E��� l �~�.Z� -Ŋ� ���L����d*��;���␇K�̽aru=�h����Je�����I_ �:�S��}G'�w: -��a@Ld4�E(��a�i�X���~2)k�m�����*ݳ��:-�O�3.O�T�%z؁����f����f� �:�Ή�x%�8,+Ӻ.��a��dv?��=hMz��kr���mR��mZ����߉���Q�aA� -�:�'t���&6��.�򇦨�Y�������� u�A -�@ E�s�\@�@�BQpg�Ch�8PS�o�����{�?�벆���T$O�BXi�Y��P�|Q~��3���jfK����3�,n�>�HAԀ�XH) q��8���<�B���/�(P��W(��,V��<��Ғ��ԼԢĒ���̜T]��|����Ԕ����x7OW�� ��p� �w�� �ihZsy�ެ��$��(N�x���vP�Fm�,��p7d">�a�gĭO��5��9H�'��-����F�GBMB \ No newline at end of file +�y �t�($�G̾-=�<Yv} F� +% ��EꕤS���lCO������0Ɛ���=�3�E�[,,8��HZ���}L6Z��� ��I��k�=��� .͌[�&ጸg��w���Q[��� *�wmƨة���,�̡lt��on� +�7���|#�TN:�M�8�Y��c�Չ�1���͍C?�=���`��5J|��<�5~�J����8��`Q n�n��+���{�0�L��7� ~-��Az&2�l�S<�S��nV;3 ���X\O� +���7����E�X�򎙹���C���F.���T,���3��t]+}^�޾n�f+=�p�a��%zb)q����o���W��8y��h򠻀��x [�>t�1���? +��~��w@�0uO|�r��<��-m��#$zӽ�u�`Q�_:{��#�KD�tF��{�~�����n�˄�% ��nT���e�r_�[��y����wKϠ������\J�#�';Q��?IGB����ν�g�>�MI��Q��c�%�w��M���ߣ(�Ѣ/��%_:��~!�緂 +��{� �=� +1ޅ�Y��'�!��������?霧��C��˥PӰzz��N{�X�oI��_QgEk���#>�x���Dg�:��f���t�v������S�ƉV��B�t=�~�h�������۷ x 7R!d���Z�� ��]���pX��r7d��A����x�_d��b�<�e)�ˆ�n) � U:N��������idn20'9�PA"�� �Q;�!z������Ry�\���9,����I K2�0%"�%� +��z�%if4X����+#��Zj4v&���]�$cl��:�Uѕ-��a�� �.����n����Y��s�� +49�,n�>eX:�2��J +�y���ZG�(�&4���60s��u���#��2E7y��epu};�~�S���r�Z ����&+e�d&& +A�%�χɇ_jX�.��m���9�d��5� |��.G0��ϗ��� ���ޏ��˻�����z�;��~��� o���7�����SP���Ұd@�yb���dc%Fɖ�ɩ�@ ]T�@(h���D�f.-ւ�9(9��C�2�o)G>�"{dQ1��D�s|\D�t��rT�v��v�VJ��G�%z� 0K���6Rꏜ����G�8%c��P;�$ZF��n]H~�e�>���1��B˘��QI�x�$�zo2Rd�A채wP� ++�y����r�J�2�'�°� ��D$^Τ�9NE�4 2�أX,�`�]�x�Zѣ2']�� 7%6���3�LZ%�^�Zd��W'"{, ����\;O�j�J�3�xd'5c��MYR� @X�`^)'K�ہ�����7QI�,LPѲ��yd�YJm a�F�֚eB*��TH��R��jM#�P2K[n���Z��N�ɩ�(�����>�pI���� +�6!��w,�������o��*Ѹpu�� � =W���aE&C�G>X��,�f�؂�D����e��_������q���ߑ�y��E"� +t������ +���;�j"�0Ά4���q�ʴQB�V�Y�HV���AW ���'���!r +�č� ė����c�ᖅ&�'�U���lۍ�]J���0Y���LXL���v��.5}�[)b�J��/���I�8��pԢo�$�u_��Z������9 !��G���cIz�<7�_S�Z�n\Zcc�@(K|�I�B6�����բ��G�>����s�>=��$��;p�#>ڙZ�WB@�}_�/*1+?���Q\j�Y��PIV|o����a�O�k ��]"�f}c�ڽ m��y*�Є��J{�\e�$�_a��.\?q�gB��Ck�Bq��;P����׃�1{�����ÁVX��+~��� �P�xW�u���}�K�s��J���F�+�q?t�����9�zQІ��]��2��L/�t>7����*�w��#�� ��(x����x)]ã�C�8�U� ����flg�������f˩�,����lN�dbob���\�N����eob�)���5Z�q���=�L����51��h˫ ��Ȟ����X]O#7}ϯ�K�HPȬ���%͂u*�-Z�V+�sg�Ʊ����n����x��dB`�� ���<Ǿ����$kD�� 8�+!�V� %T +n���VVK��]��+mf�Y��Y�i�~�����n��a�7g�J�*fNh�����рVHhm`�M�وq�YX��*g;CDo~p=��.!��ca �0nn",̵�B� �8�I*�� �h0e&�L��F�z��؉�:#JexUc å[�a���R֡m�����u�B�M�1[B�l�\$��d*�Y���;4^����Z L� �L8O*Kе�J�\d�O�T�Hk�w�)Q�r�ST��Q��Bؽm�x�MŬ΍�T�(��#ES�#5R\K���j�Cz��X�D���+������_H��5�C��'��G�u`���{��|5��@N���~�"�|m%�g���yt� BYGB��kv=rSB� ���I��(���\�2�#��6pc�㒱2{t���r��=8��8G�o��X����-$�0cآ 9�R͂�=1˴qL���� �8�V/En0���du��45t:,U���.��Ry�nN���/�W[�(8\w���QF�wh�Ӧ =�+G;a�{�����Ke��S�\W~^�1z�� ++#����n�JT��u�X �g�9���e��l���Bm�m�2�K���T����]Z�WJ�����RpHrUl4߾q��39w�"ߢ���O�*B|9��ֈ�H�K�Ƃ�9Gk� K���P섩�d 6aB� ��ºR�Gl��a�u7�r�lq��Ơr�v���W��/EV̋�H ;>����u ﰦ*O��ӗ"��"�[!eo�T�_7���B��|�R���t�;:j��T�ӳ�{X�//�_^���Z�W��:��Υ�ʘ).^S�).���N��:wo}s����[t�)�c9��� ���)n. �y/�Do�|jA$�I�i�/x4�Y'���Eį(��m��i$X�\�f�&o�� a��n�Q�lL��.��:�K[RM���k��9���*wZį�Z �5����?[+�lλx�bZ0�X�N.@�)�x��qm5��� ��3 ����㽰� ��)����u�#��-ݠ��R����^�m� �����l�� ׿��m�J��j/DMx#��~Y�}�V�>���@����J�-F�rk��� C�5ٿcr�%�N(�D�Yy�sȮp����3c� +;R�V�[тxz�F�oTu��#�- �\��v}�ܿ�Z^�Otm���i.����I����׻��"�C������=ks7���+::U$ٔ�$wWW�)G��Xu^�e����T�$q#�����0̋���&[�/�4�F���h@Ϟ/f�G�Ǐ�cx%R�� �Ȧ`f�/ɹ̴L����qʴ>z����L�1I��,�q���d��+�gcf��`�����٘+�Ƿ���TvF%F�� +R �Tq>��G7�������LDJ���1,���� K��`"��X��,�M��"���S��Ha"+%�3r�q�gbq0DRn^yd��5V2w�T;f���\i$�����of�Ҏ{�spBo�� +2i ׼��s�D��/R����v�s���#�0"�$�8ǍY������GRM������󋫛���W�g)��9��a��X�"!5H��Gb"� �J�M{���* +�d�GP�h�̀��v�n��f^��\���������!|8{���jxyq�������������_���_�����z�.̌+�� +�� +�m�8��8)�O�D$��l��)���犌a��\h���!saH�4�Z���ȏ ��!(oFR��Ə�NO�}�9�a�~�y3�\j]ZH Q���iT����sř!cG�j��B�̔v�X�=I�\�j�A���'�!�$\�k`���%��`��F�Ŝg�l��%��\���� +D6��=���lq"�6P���X�9vJ.��|�OĦZ�.}���L�=�dx/O�����[�{y9� ��������c*��*������Y'2��& AZ(����Z�xb�Ur���Gӣ��S�,��'2�5 ��*t%�d:ރn$t>o�_��`�Q��ƹiXuڌO���M�f?���K��G��O���?�6*m���Й� +�����c�ix���{�����a��B �W��,�Qk�aW��?����?��'p���f�u�`�hޝb�]�C��f�3�J.~׃�{�C���x-���HoE�|t�0�,�!�4�iؽgiΫ�VE��қh��[� ����Q�3����@`��Wn'�(���`���`$.�N1���ؼv#^�@O� �������@qg��H��i�q�jL������� �D:�s����1X������� 8����� �V`W.�����۸�.�n��B�a^dv�blf1���hA-b!�y�wm$�i�DFHQ4C�j �o��\�[/ W�HՃ34�3Z*���(E�y g���2بd�� �lPf�1��u��<;�/U$4��K�Ө>�D?o�û���g��0��NU�x))K(f?�s�P�R�)�Y����؆j�R���:|��g8���h�خ�B��n�ͯ�����|T �!rˣ�� ����C}��TL3+1��|��Ը�I�b��ٛ˟�n_^�:{�f8����k�(�l����ߛ�۷g/o�]��z��)�t�'p!|�p� F��}���Ż`���� ��q �@�j�Q��� �\T�s�O�uv�HS �=giڌˇ˗�׷�.�r��Z�� �A�g1��v9�!���s����?�����W��P�L�cF�4���{�ܺ�Yq6���g>>�f^(ix���n �Y Ug�4#QGM���s��vzm��&���ڙ_ +(O33fl��83�F�m ��[�\���!�O!ha�T�p�2x�H*i6�`Y�Y���� �G���=F;��x���(ah���)6��%���Q�@�5s� +�r͆����+�U����S����C�KW�E�{+G_ 63��K�i�g4� *��^]c�Dv�R1�6�"�"�I�YU��%�Uyb�;9�sl�������ِ(� }xJ��N��#��@U�S[�L-c)� 稾x{��ه=B~�S6b���'Jd�`�QN��Ń�͒�`��}�s@�>u��r�/�7�b���֥!F��!�s4����i��Ѓ���>�8�;���`��ک����0o@�����(H�s3�c�2aM�HHf,��(N����>�Z-�w#њj�bc���v% �� ����si�)F �b�� ��!�,4�Jv^�uO��a.�M?����z_sM�l;e�dkg��P>� �-6c-��a&��Kֲ@N��e���47umnwI_׺O�\M�&v�{�/_מa�б����&W.�Nr�J�/ ?��ƦɖYy��+���a��͊7-��dn�.I�l� b��0�*I�`���;����NN춅ݟ��,��\�6�C*)�K�V��l�1ߧ��O+��| k9�,���+'�]!m�Ʉ6V���ȄZ@-��=_���d&�v!�|v�'ہ^Q��r_������~��^��i_����y���8�(}�����׉�r�,�����~���J���T�)�0�e��Q�\�V��"ם��*7�����Fr�w���BJ,Ƿ���^\�鵐��5[�KR��Oׄ�F�X�M_w���|\N&��h�o�w��S��SP���� KH��[_e����>鋷"[|M�(9L)4�_)����Q��# Շ*�vʔ�X:{@���g�6��[F B��S�&_����9���ql�����b�Z�"M�)��ԬԄ��_p�1��F�[��`����/��i��v!޳@0���z��R9�ӽ���뭦(��>:���ڇWJ�i�dF��^��.�"2�%qT:�E�� 2�5*����dsͦ�Ӝ��T�w��Ϸ �� a���n}jj ���|e�VډWV��62M�Ai�1�0ž)���[�5ݲE>�ʼn8Nf����B�9/�߭u��M�i���3 ���9$�Y��{����6F�m;�q��{�:l}]no� ����._��m5�[��s�_oS�'�0�5�/0g�FVk�u���-�Pj��c��en �U���Q پZ��Ρ_�nw� ��V�}��=5G���z�&�{����odԅ�i���х���(^iqCEC�y��?{�� ���D��x�-���Pq�+>d%ͳ��6lX�����'e;��6�D���&M��E����R����R�>H��\œe+�E�S#�g��:�l�j�¾�p�rXVü;�*j���K%�+���^�^<�Sx +�ax6.����`��&��0���W${��>��=O�B�D1Za��l3vj���Ң*�OM�Q��5�lX8�l ������Z�m���I��R���Q�E�� �"s{���U�\���2ɰᆺ�m�cJ���`�C!����u���eDm+���j�%?���+c� a�I��m&�' +E�C����u �Q���Q����`�p�(�ٸM8�������S ږ��_.�ʅص� +� �R�D�ַ+�Ǝ�-�����ؼ�v�]Ƶ �: +z�c}�hMi�c�I�m�me���'��QL3�x$���˔����0���| +���J���q�3��?���Z���I4޵�ց�gwMYD-3ie�'p����B��Ȏ���)��\�f�[_A�X��V4ַIXujn�>Ígd���)"9�>aK��^smi�7�_���6� &j��UA��sIZ�f妋2� �Ϻ�{���X�<� +�� ��3L�~�P9�RM؆�n�b���Q��+/g徱бk�xӲo������?z��O�6Y���(_儦����'ׂ����U� �X\���MM+��;�Z��*��Mdj�> �F s����`w��.��9�1l�~x� IF���o�����QՆ�Ԫ�_7Q�z��(. �a�@��T��Þ�Խ��j���~���f[)� k[��3����|z�0d�fy>��-�>ꂑ�w��Z��yp��B%߂�U��S/��x) �S�zN�B́��m��:����zUL�.8v��lb�z_l*� +,��h����AE�� 9?����*���Y��v�q�άV��:|1_���A!�1�Tҹ~K�C���o�w���$��h�E€0׻b�菤1r^�Db�)�m���~"3�=��nN{BОF�F�~�uGe���\�漶غ���[fT��ָ�&�q��w�x{{'�#�� �G����o����(��}�; ���a��Ȋ/uckd�v'`�඾��gl��/�-ؓ(�;wB/F��>��κ`�͸�4�`f�b�Ԙ6)��<$ ��ڠ�.B��BA+�a޽"�Yb�ў�Dɹ�T;?]�pp�%�L����(�貫GQ�,�pwik-w�Q�5��Ĥ^q���N��2�t%E*�>�z{"�'��q���x/Yq(�mD� +���m��9f���=>X��t8��1��%�!c�~t#?�ZKŰ�[��)>o� 9vS=p�r�&�����t���;6���q۬t��t���݂[� �P܊���C5� %�M]�d���n�L*׸"�����?�p!�G���ŕ��W(��LZmq��t���"cxmkq��G�v �o�-v,5�w3� �B`3 +]p�Su��=�E�~��Ze�E�%���L˖�۠@��${�w��_j:V�ycR�x ^�i�n���t_�G�Ok��r��5>��F nq�a����@�{� F�t �)���L���� + Ux7��ko]y�=b�M�x#�V��: oP�`�W0���{��J�W�AҾ�L󿭭�㝣ҹ�_4��Ѷ�=� ���!��R,��۫��x�_sI�4��Z�Y\(X�u ?�:UE��~H�kG5�R��uE|�����pG}ꅂ�'Ч�LT'�zW��0�kU�����R�����ɓO^?x7��&��S�i�~:�~�F��(;c�GڨHHOK>�B��|8x�d;�� Z��`�뢫�%����i{<���-I�-����X���l����9�Ku�Z�zUh� �R��ͻ�v7��2t���,�g�r��+_����\/?�ⷫ�v�'����2��`��[;Tn�Q,1��h�S.l��ZD�5LS9b��Ē������x ~�<1���)�1m{: ��xT��aPL\������n�I$�ڐ�K�>|g�ki��#�'���+���¨�d�������9K#;��j�墰��K��l��?����zW�K�@���:f +}���⹡ ��%�����40�+��tkb�]�d��ٴj��Y����g)/b?p�o�"7؎C#��xt{tZ���uʼY�k��O��e�na�ǯ?�U�{9�?��$������{��� ��S<���z����b~��=�I�CS �q���ѪW�s�6��๟�,�ж�^;崊V�=��-9��=]�E�Y�͙JE�hu�/�5�@����Uz�'���ac���;�{0G^$1m �/�Nc�V��+[�b]]C����? ���LX�j��pW�⦽n����Z l�/M4Q����d6:/\� �' �����: %��t��v���9���iج{�,�j%������:kil�DVj��̀�ӎ��A�ZB d�פp����uij�&���u>oٵ�ԇ�>Dv�h]X"{2�I*%&��~���ض7O���K���W�5�Q= +�� qp��Z��v�o��Ξ��x飪�s7��W"��f���~q'�\TN�*��:�6m�~��d9��=:�G�e���o=Q�L����!�m +�#��n��3�����f�]��v˨������U���\�M펴�?���_]���@�����p��N�p����Wt8��.Č��lˣp���"�j�#�&�M{_p��t� ۬�QԅΖ�%��Ϧ��G_=?}��XmoG�ί�ZV�-i>��1q�AuqdH�����n���ؽ��ӄ�^;p/~Kz�e��3;/�33�y��ZGG58��") るL���F�Rh�ል3<�,�(cZ7k`A ��JSȯ���nR ��o�O,z�� ��Bci�.���i�q&"���-�h|�F�*̆r�z �!4����ǭ�l6k2�pS��Bl]�ϻ�a���櫀�$2��Sp�1����<��26� +�BYp3� I��:�`�Le҂�\WH��h�3��p�u��a��G.?��K��3��C���������9�e:�����?x��&Ex�+ +B*��Q�W�| ��:�#>�dL$Ky��J"G5�J���2>�ƒJt#�����E7d*�I��"�n=SZ-V��0,i�� �Ǘ�>Z�D1- e9ժ�_!���B�``l�#�2 +:��o��#׮ � +P]�@����ы��(���� �����i���3�,㉰٤��\��Z��k R��J���� +֝ա���,��f��-���L�F׻�� ��\���V$��Cr + �� �:���Uכۭ�T��\R���[Z5xg��qa`��j�C_Q/ 큰���1���Qhc�� rR�������E���5�Tf����ٞ����F��SW��>�T���λ�Q�j� 1Z%�j�X[fa�[���2��lө HHe���q��\8~v�0�ؼ��>��;������+i0��_Z:�mܭ�g+�~T2�8f�ܖ�z^? �I1:�.�Sn�� �JM�d��Qe������T��b���JB��LI�����&v��^K[!��DS6��p�x/��� �T��m(4�p+y\�]1�x�BD��^_/�R/�k��Ac�yb��"3��s����﻽Χ�8�8BҳO������FS� ���:��X�h�Q�]�?�� ��У�X�~��78��b(@�sYw��̲b�,1Oe��Pb�̪���HcW֮����·��(�b�v+*ۯ�݄ �>�DH��"�dڀ���'P�m�}�K��I���У�Hz|�v˪.��l^BG�75��KNK!���W�z�2�����Y���Qu˘�Jͬ5g۵=���Gy_u���Uc��O����D��O��X�棨���\��g���q�P����i,[�q�݊f����^h��0a����/φ��Y��{�n���7c���dKX��>#���IF��7���k2�I�^�h�n�����K{�$���^�x�t�L���3�aro�r�|��w�q_jw�t���ڦ�����[�&'�;��7��m���hs�s��)��w����Ta�QR�l��������cPx��V�m�Ǐw�{Z�����U�#9�Ɵ�Z�\r��ʢV{{Z��Zmo�H��_������~l���4i�+�"v�XE0�hi.�wf���?p^$K�;׽�.N@�$rH>$����y:��xD�[�(�\�Bq�E�#j�`��hAD�+ �f1�=�p� ?���|��]|ç�������t�prq�f8^���� ��%� ����IQ��)!p�(�+�d o�8�9F|�#ȘHr� $����9���&b����T�H�t Q�z΢kbbI��R���2����a�``_�@����в$�2W֧;!�GF���B2 +���� ��� +H7��ڄP�j`�$p��bd��e#&�Ejk2�\�9*�Q�%�+�L���$7�2-=�]a���I��i������[B��o����^F2�#�i0��� \��W{�ɡ��z^�t_�� `�b��<3�2ϊn���x>=�Ka?Eg\��*� aI��"'[� ���/O�(��������y�dz�dz*�̲y*�@�@) �ƺ��SU��D�a�Xϟ��=�:9=�^��94��dJ�IZ��u��TȢ:�a�nݥ�_]��a�*�g�Nxk��+U ��}���]q鎏,�~�E��l;�^����F�P�:d"��:�����z��.��)؛H�:��lហ�w5���s����dqi��dM����ss�,̴A����->����3����^������:t��UAf�cسPU�ӟ���Kr��-H�9=;��~�Po��m��8Oh(���r5��\�d��Q"U�)��d�u�d���'�=t"IHeג�_���a��ǎ���Ț�� ��s% FtN^�����h��}o��)OY�uc���Sv�w����2�%*&L]����Mח:r 34����kb � �ل +�iK�fw�T�� +0g��Р��"�Q + �e0A*b1�4�"���Z̚�*�� �R��5�Sv]X��i��e9�ڲ��nQx�S`�lT�Q�J�.�mIʪد��O2�4����")�-;:g���Q0?=*����jV��Bǒ~:��ɦ��Da�������S�X��D>�b*ѝ.�t�%�j>}���R%he<��#�c*Ya�Τ����[̅6�+�iЭqk�H�u���с����{<���{�߰��=vk���_<ֳ?��c��$r?ݭ��%�\���K� +�өFx˵����嚼�(�V*paPMY�m,˨���t͂c3Êsӗ����պs=m`�F��E����)�� {���0�2���)�ʤ�h$u> N� �l�a +RPjC�#w`ߐ��3Dm0����>;8��9~�Y���/����~j%�v�'��\_qaVV�����'��j�d���`�p9:��������Z#���ĞǗ@��v������_u�����- DL��-0h 9�^P�-�R{[�{�2;r���ޑ?�M�3j,|��#ј� 78�X�;�����j�c�J黋��h]Y�*a�!DTrzϮg�fwo�ɂ�����j��n7ݷ0�w�F^m�$�v�}&� FkO5d���u3����85?{7�EOR�Y�����Z�o�ڷo7��SS=�lP�o�bF+٣Lڜj:��E鶑m7��lP~���7ȇ�uBk�I�pm ����R�!y���ҩT�%��h�(��jf+�#O�\<\h�"!_Ni��M��b��ϴ �4�����û4ö��[�jPH�f4=��?̈́�]k�C���MZ��G�����K{׸,fnW�K�U��!]ZJ7y@��;<�.<��M��(W +�ɖ�1s�@�{��ȴ5��Z�2��XL�Ts(��;nhf �׸r������" |����ޝ���R�_�G���!��Q��k,����Q����s{t�qن �z*o�;u�ډ�� R��P�3�͟�:H)��D��G1%S������6�� qytlQ�j S�� +za|�͟��\WWy����(\p��C�*l��x%���\������k�-�!����kF�B���m$�)��7�po����b���Z�""/�gK��;��~�P��>M����b+��O�A)#O�TX�c��-A_�5w Ւ|�J�H�+ʫӪ:aS ��V��&� ���~3��z��� �� ����#�}��'3n����OL<�E�_ޔ�EK�Ul��|z6��mG�`ٖ�r����[�kջ�57rm\�M��������}˶�t�k}m���}}�_��+�]�����k�c��Z{�}뢠���Zp v�v �Y^ grӡ�ǧ��#ݛ��0B~�<�J ������WG;��Xo����)��{�by��h�D�8���چ�48�@@펴�W�ɵ����Ő���vZ��.���o���r���,�F�#x���oGG���G S] +.� +� z��76�\ ��M��@U +�U���V{� +��tZ�Mc�}�>�ӣZ"�^�B���R�Ƥmq���[�������R���z��Rz�=��Ĵ�c��gke����p�p�A��(�R+��������@z�2��@,�7eDjR-���ɰ��x���"w`]CϢQM��� +Q�jt���|J�9]KY +��"F�;�!���0 KD[UTW���D��T�l�J/z���B{J�a�.��m5>k� �`f\ ���T������#���Q�~��P��y��Ǵ����UytTmo�W�5��0��Ə��F׺RT��0�U��[�3��VR\[�9nO�>0�h��f,��ǚ���c�)w�_N5;o�$�?��q�6�8�p�է��T����zx��X\����.Á���Fu��ÂܤFeR?�^ &�IS�/n�^�~�z3��Ew�q���t�q�k��Jua��tS�n�������K.�So�"�Ĕ���3Z�fZ����ӓ�)���Bt����U�'�k��G!O ������Bx �eP;�=�Z���t�|�.�����.����tn� fȌ�4�Œ�Qk���'e��5���W��0@����l(Q�� ��8jQ�z����cz%�.�l�-7QR�͡T�7���:��l���NEt ��д��6��FP���χ���h���ѿ�Z_s�8�Oѓ�0d6W�pɒL�Z�9�L6usE)v��'��P[��W�mp�!�\/1H����V;��#Y&�>}���ϓ�����f؂�ϟ��9�|�7�C�� {B�$�N�/���n�W�ڊc0�J�u?��0�R ��*�A*(�S���2"�0�b%��B��0y�`�#:�!�m�IB�bE����i��%Q��s������ET�IC�Bu���$�y!T�#�U*T�2K��^*,Ÿ�!�34��b*��)3fюT�aL� +E��4���RH��!���\׈�� +�2F/ �θ��(`E +Jb�u�q��.)RVq����=��P �a�o�e.x�Ņ,Y�5<��du�$���B�̤$D(�3F0|�YD�z! �"�d���3H��'tx�,Ф,i�:>��0��<&�����~��ֆ�;y��n����|�����s�����pb���'&y���`�9�l���n2r��[�g����������ߴ�zk���;'���L4���!��^��ֵ3r��u�c�q�z`����?Y�F�L���۠U8~d9w�� ��.�_�q��5�j �WVW^�0r�둝�?����~�5�>���=�Q���w���}7Yރ����!}��S{8�֝uc���1Ўu���SϾӂ�C �O��� �� 7�;0��m�ӷ�K��1�Է�0����'�;t�R?_O}G�Oc9�����$p�q n�{���Aߚ�������[��4�6�qE�o������R� gis�����N׃�����0�oF΍=��z��@��o�Lhy���8�{�ܩV�8m���c)��Ƶ� �|u���f��;y��C�����Ef|IH�D���J�9���$UK.L�w�|�V�#(��N ���4]�e_3y�>�*��!X��(�b�_>h�0&R��$q~ ���L�i� �Յ9 뚕��9]\���ǘ�0OYh��f!gR�4T͑�/���o� �6 36~-�k��?���u�2r�U.m�0��BĚM9�����~�Dʚ��4V*�)��8F �l�eO����St��DNU�����?�_��K�}����l�9��^P��0et�����p +�n�]K~q1��P��H�Z��,� ��p��U*Xa ��/[ +[.�h To�vŨ�Q�q��> �h3�cT/�[������\%($�j��2�1�'թ�U����9�q�@�C_!S�9��W�3�6h��I,�'B�dHh��I�-m���ݓw�zLie�V5��,B�?�`���\ C�U�V�ǎ(Sw<�cbL�Loo�c�B�g-�a��Q|>��a��1Ai�Y�V��G�2�Uk���q��*��iO��)��o�1���r�z予�0˖n�p�}c�`�qg.r}4�us.��ˊV:�0$Tg= �Q�K����rQ�,m�۹�~Hˮ +99h��h�-v-����~_>�٢.( T�BA�9J��A�X�s����0�f�r��YvH�D����,�O��� ���}2qr�������B��N૮~��Z������}>�Uz�D}�XWR���F��X���Ь5 �%Yo�M���a��-��'�f�Bǝ!أ�LFIG��`A����IQO�Z5�����l]�����J���n��S���v��֮Y�Nͺ�g��M�D�� �z=h���������pɳ�jO֢��+΅�sq��g�5S���L�"��",""�΂q��"BU+}q�/Z�$-����Pw �f������=�J����� �t�(��K.g�K�������,k�i�g���1�w��d$�+���t=�����︹�9K%.3���1��Z�`�w�&������F�D������{��N��U��30Qɒ=�ԓ�H���TJ��'�#��!��o��{���˒�KX �� H�O �*�"�j�uW_L39�J��$Y!^�x�E��� +������֕�e�(�<5[Э5��G[��P�;�n��m�O�Ώ��l��\���J�;W�O��T�&�8�D��ZγzA�HGbJ�XO,��g;��r� ��oY/�9n�\Z� ���5R�N�����=6:W��r�����hD�K����ԕ�;��@Q9tP�����G����o�i1j6x�Lʸ�S��ӭ� 2������U� +$]�e��Nݤ����O/rff�pسO���"Ѓ��ya�jFu;'oC����I`-����dB�L��H4�z�54 +� �;jN�K.1�g��fC*��΢Q���M�7u;O��$=P����*��3�k���*R��xQ+E�=�#p�<��x'_*��$��]B~���u�c��3����)z��� �I��0�ÿ�����}3�;��B zf� \b��/��"f��śIBY�vQ+]r�9��� �n +5�}X���H؞! Wj���4ڽ�D2�d�Ŋ3?C�*_k$4�F�ѪV�_k6��m<���f_�rw���8�?��fe;=E3��� �č��\�MP�"�(�r&0{��, ��8m�|����r����rw�zp69ח-���_��]>�Ø�Ү�˿��bQ�"��J Y���a��*� ;s+׸�l���o��������(5�p����J�&1 �٠ ::�c�L��ݜ/-�j��=9��E�3r�Aݝ�2k�*=*����$�Y�|��;�� +�Ie8�6�-ީ]o�ہ�p݋� +��/��\e"(Ss}hߥ��I�aA6�7���)[mss���#��@l�lu���8�>Qa.{�"�"�N����n�+���z�ٜ~�����UT?V��^��E6 �9e�|a���Q`�PS�! ��l:]�<#|�����M#�����Ё_d�������C��r�=8�,X�~�/�Y�r�F}�WtT�t��<��I�B-EpЎ*N�F@S�Ġf�l������"z��A���3������]>�O�^�<��0���s V܆ׯ^���ׯ^�l�!�S�ݡ� +~e2��7���ߒ���`�HT(�1鞀�1�JK~Sh.2`Y�B�(Q�͓�1�3!�\�AH�W"�33�&�9��ȥ�� &��L��#�D����B,����2B �o*^��M��դb� , +�A�f<3��F�ӫ�R��<�N���\Aʕ&������*�*N_��`óu��lr)�"ƿ�T�&".�ic��eə� �%,�F�Y�V0�3�k���8FndiM�H����kU/��T5�{��@J@ �,R!�L.�Bh��LZA���c3)�E���&���L�S�A.9E�����@SjM��� �.�O�����>�Cg�k/x�k��p¥7:~�xo�nz~�vnpj�g<{| �o� �|p�&#��'���q�:A��`4���OCcM/��{��B�SRsv�wW�?��ǡ�wGnxmv�p�1�x��`���Cw0�>!M���  �n0��3�;���GgBpi�F;5�_W���\�?r����0t}g�f�Ow�C{ԁ`� \����\MF�m4�|2d��g�C��о�?8X 5�CNL}犈{L�A��Ё�74���;p�sy�1�4p:0�C�C�'�w��9}�O��GX�8t|: ]o܆K����a`Oghl퍍����ׄK�0����K'�t|pǦ��l2G�� \_��z~��,��#��38��#�On�Mh�n@k�r�O�5xSR�8m8�ǵp�ׂ{��K���&$� p���. �.+�י�>g��EJ+��",1�Y��B�t;�Dy��jGHt��@�>�1f�0�C����˳�Scr#�s ��4�urB"�tS��O*O�Q�a�ZsX���8-�Rϴ��'�ZU5�����.1Xg�o�2��^�1KP���P�d`+%b�4�G`R�'S� Xw��^�,<�g�,-�MO���˳5�u���L��kOϖ�s)4�tֿ��;��2�ӹ�J?A�%�t ��KDP"KsFm�lI���?B�n�#H{���Z�Y�Y��6ʜIK��}cn i_h��1(w/ORF�B�7�ݷ�YBAN?g��I�|�J�ENިnJ"��ԇ��L�-���?� !LW��*�<�eo�0�V����ʼn�h�;v� �(�vK���@Z�>2�� � d\�)Ө�9��R�j�U�n�2Z�ަ�� ��} �X��4���F�Y�� +�3�*|�LN�Ld&��4�ks$�+���s̒������FxQ(��8ߗ\]��'�s)c̫{I�Ri�q@)��65��j�gu�LiYĚ.��s��}�r&٢:4�n�%v��-�ƾ�Iy �"+ D�5+� ކ�V�1W?��ϫ����wJHmm.o��|�_ƾ:J�T������XA]�hq�A�� �>Թe�T� ���U�X{���J��-�� �=62p/|�9jJ����S���P��4xAĄ��UA����YF+�l�@�M���� ���~�JXD`�-V�-:��=J��"$8cE��[3�sQ���_v'eU:�LUR�Z���+��h�����]��g�U˾��V� �oϔ��>鍂�Zj?�k�V� ��i�v{��ޙѲc�b�1*%� � )ɮ+ ���j��ܮ��ԣ�k��u� +NF0(]YM?bb:�3���G�z���v�l8���#,[�}��`��a撉rYA��������M��X?pqʘr��?BV�)����N0D�D2|�K�:m�p��i` �O;{e߼q�{��dɾ�y�y�,o �s;K��A��R�;��oQ�0�ү�z��3��0$�p �+B^�&q�.�#��ܓ����H;���߼��� 3�8Ng3Å�S9͵LN)��>EQd��l���G�L�c-V߈,åSA�����{+��V��t��?ȊFvÌ����=��r ��������jn�N�RPdHYB��%3���t2��nn��� x�Pm �r�0� d�х�z�=28 7���ˋ������EV ����"�O�f���UiV"�y�b�Z�?�:�:ku*���Cm��c���r����Q�Ll�=��h��/�uG�$\!�#�h7�rA=���{�Ģ�r\�nH>�)�,�R(i�Nx�ڽ���W@��mӋ�~:�zN�*�� �]���8dw���V �Я��KL� +�YF�\#Esߥ�:F��w̩XE��tg\�n��bQ��ké{!{��ȴ��v�yC�g��F��� [g�j1tM�1!�e���Cy�o̵�$�m�Ŧ��߯����Dު�H�n����τD��z:��)�ޖ��_Ƃ��0қ����s�&�;�+���.�$Q)SsTV�g� �-ڸUn�:m�x�����L�m��]j��7��U׳(�pGNQԵ� ��h��QD�fW%o�h����[e��E���p�����n�;��N����7�������Fv�� 4��ޖZ�C�{Rw�B�Y�wz�c�Z��������yv�:�D���f��b)�U;�/Z˝�l��6p̷��b{&3�E��e3�k�ƛ����ye{ +� ���,�;J~3w���:��u%7M���Uتju˶��V����1K|^=Z��� !�6 }! +3l��ycM��=x}�d����<�s�6�����f�NRG������*Kt�9Y�QR�\���$d�������o��)Ң#�S�db�~�],���2��;���=��"���r����ͫW��y���IN�($��J�����t����{� C0�H������� �Ғ]�5`�(0J��O͛ Ɖ����+Ն�� ��_�5�D��'��0��Q�bZ�")�Y@�K�A/),D��/�<`8N�A+��1]�A��H��E@a�V$Մq�\�klJ$Ņf>m[hz��Li��G̃US~H؊��{�a��(V�0.�fna �BV�.(RZ偐���DR���`ŤT�k�B���� }C$M�%f�"꣝A$Z�D���Д�q2;q�0�>�<�)L��Gw� ��3�N�'�=��� N�Á�M�7@<�y��|6���7w��L��z����<�������� �S��z���L�������}h��|f�9���=ug�f�%�) �c8u��Io4��Cw��`=vg#�x<����7s��a�CH��7O@�?칧�`����|tF3����R�a���E�G ���б�F�a�zN��e�݁3���m�N���8?;��a��l8{(ȩ��3���! z������J��=� #���h:sg��け���>�}g����|�aЛ���>����l��>�O]��rG3��擙;��d����x��ͧ���z<2<�N����0�*���ę�8�#���z(���s��|ϱ��7�1 #����������'w괌iy������gϑ}���Ա�̹mT �1�]$�v6&1�N��|��0��Ob�'3㧈�W��ꭵ �{��K!�t������j5F��v �)d>��Pt4���܃������lI��ʳ�h���1�#���?t�qM�h�2�QPI%��K?y�L���̙��~|�IvM4��ђ�q�����FetPټ�aT�xM%.����4iL[S�vM$_ y�{� ma;��˱����5���t~n�#׾n�0�q-�o^���y��aA��k%է���/��}���r�w�T�%��� ��Y*���\�&����Kʯ[y�G���_��GBh�%��������ٶ�����wս��RM��M$���׍_[�O���.ڛ���M"�� �K���4hm�V��5 �a�͝�� +�����h-���BϹ"���_�����v������63����vg��}� �`����ﵤF��X�e��3Y�����f �d �廸���է"���n(�,�G��K�{Q��P��y�s�h�_�'�_B�?t~ͤ���;_|jT /i���]�cL�����64�B��6\�5܊����h��-%�:���Eh�O��-#r�_4�#{q��.L�5m�V�����/��)UvZ�>:�/j���( +$� C���ĦH��BE. ��U� �m�o�uF����+������Z������Tnï�/��n�I�0�/�1��{���:ڄ������ԣ�Cb�u,�<�> ��&D*�H)��V�`$��j�ū���%� I�Uy��ҷ]�u��!$GGc�闈��U�H?f��E���.4�h�$�4Rۀ�( p��ܯ��r�7�|����w�B����$+�3���,YHi�*'��k�t��C�V���1�m��O�d���8W���W<+Xgv���AN��lWϼ�v%nq�#pz�&�y7=yi2]�8v��n����NP��AQ�9 ��+ +�>_�"Ra�t� �!����9�<�X��]��o�jc���'`��i *��\E��T ��dId�;��rW���,z�x���ޔ��@CL�����=$ )E��?e�k�^(G?��؊�]�c��)+�]�ۇ"!#*�1�,��Y�G"��٘����7b� tk�.\E1��N�:�cY�셗B2�\�OG�r��(��Z?��-�) �����0I믋��.[AUn�)5(���r�ō�I;"��4^�m +�~*^2�뀖S7�lnl�1un� �R�;�����%6Ϝk��n��~�����^qX��T���ˆ-r�bPm�+P��͒ij��JYM��i.�^��<��u��)qwpɔ�U�Z��=���5�G�1��.:4�e�! �B�i�� ~�x�i��m +��Y����qo8-�Y��6�?���4]E!���%���x@x; !���u�iH�k1LF�`ˤ�"7��3�ޘ3SϪk+���MV��_o �BR��� Q���6 Ī���2���8l�+�+y�R��D8��\�U6 �/on���E�70���5 Y`r�>��H<4�/K�l�+y����~5�9�% +Q�Æy-����,J� �qt�q��� ���+�gr�4���Y<���6��O�Ư�SʼnX����bZC�2,�����o�bۤ7;ف���@�Q� ,���QLA��ᔅP�x����} ��>�Y"0��mq�Y�?� �&��l���j�1�v�K샹� U� m;� +?�eqC��\%���^↳S-���W�~���&i��≏p +�E��߽{猏�L�n|��=���Wx$v��� ��?"��Z`�F(*�S���_�����#�{��Wd�����$��}�MR�hM%G>��fzD��~��0sѡ8< �ʇǭ�@;�,�'u�m��-6��U�4oX�D +�:�� +�R`Y�O= rr��$��ſB� ū��ƾ�Cb� ��Ja�Q�&-����8&6����P�ฤ�J�6b�d���� �FHB�$hf0}I�Hɮ)4�8�I��#  Ü��Q?�[�Ĕ�^�k�!5�YR>"�S�&��›���[��1�i[��I�f:#"���G� [2r1!��{,����gZH�kNOz��m,v����x����oZyQDmsL2,:5��������o&�8�yW�ks�c{�a��i_��r���]���,@5�6�^�ۜ.�����*kƸ����B��(W*��A{�����F��Cl������7`��Uq�QG�x�/#��c2���}��0h���'"��\��PL ��A�ݶ�h�Ƥ��J!"=��t��[ ң�Li*���TH|ZF%4��Z ��U;;�%���3@���` Lمu_��X�eƅ �!]��Na�U�Ld�#�?[%���qE�b�]3}k ����aZ*TsX���� ��O`�o�'��tZ�9��v�TUV��P��'�4� ���9s���\��� H���T� Eݮ.�����Hx�v�r`f��ʁE���i�F��P�q��d���x�4��oC +;׆�7�B횲ĕK%3�&�*[?R� +w�qLE��4�H�P{{���N��V,� j�߹��b%BR�jr�E|q ɝ��\�DƛEBa&�׍6����*��a1�+R' +9��}�m���IZw �����e��Cy]"��䶒� ~Q[�TJ��NE��F��@��I�f�K�;�y�0��T�?JҲ�,�&Az�q'r|�U[ɩ��^���- o�z?|���&�wXLB�Ŵ�t ����x���sxO�ⶦ�����G������SF��!s�d�Vr�_\1�~�7�, �"K +�w��ëo�x��Y���wv���s5A<)�}b^�%'�����|%WR�� L��<�=֞s��v3j{�(+>S�j������Nl�9QQU�'��e;��6u��/�7&��vRF��3>�o���!��� �[!�]�[�Oo_�j�N +"A��A�"h>��*XD�͢$��Xx����IY�-ok��կ�p���z�x��^��r�Ch�̗��2 �$m��~�D�~��u�Y��:��QZж����_N,�%U����W��?�c�@{��� ���"�&��w�Ӌy)|������@$�r��N���f� ��{��_P��\nY�����(�aq( C؛��GY�ۯ�������(Ki��V|v�|"�&o����)GkY�n�Iwj�oν��6d��Ћ�e��U��9^g�;�+���]�U�gF +�W��9�&�9�5���{���ۊx���<� ε$&�(ź�f2����J���w� opp?��p��T�w���B����wK��h4(�}\,��DnQ�ٗx��}����T�j�0}�W܁�vH�di7��6(k��T��Sd�{զ+��!űGi3�^l��s�9:��+S�D�5��m�7�����F5���$(� �o��q7���l��� �< �}�l_�F�����Y0B� `� � ��Y��_�����=*Y@�t��ѰZ�&���,�!�Z�����m( )ג�/�B&�����4#�X�M�l�?�BH+�p�C[dg5<6��{0QV ���g� +����������'��n��7F� +�O�9�yq׳=��B~t2*����$���D��V��X�M��r9���/��Ǔ��|�_������-4&�kۼ�Ɨ.ț�/���L�n����,ϑ;���#�t������"zRa)��9r��,��>�ka �����=r�:���N�w��d��8�q��$���%b!�V��ĴC��ᴒ���^�q�-F��΁�J��� ���t��o|�J� �. ��o��M����N�0��~�9�P"P�� +qGm�H�bb)q�������ii��i坝�vn�C���c �X��d��yҦj� [c�V#ɖ��U>���͊��Q�aC�g�� ���Ao��A܆�1�o���nΏ���9*�a`�k�,ޓ�����6>�$�����5�\`��`���Ww�nV��v>;]>0 �� +k?���a��zz��cx������&X�8F N�֚�5�5_}��n�0��y�=TJ@4�K[J�8A�^�,�,ĪcGk��x��&I�=Yqf���M��e^F�hK.>r\#�fQ9� �fQ$�^���S!�~)�[�ΐ�@�����v�I�{�L��p���`kL�< gI��� ϲZt��b�u$��r��ꦰ�UR㖐]�Xr����k���6�՘{t��wZm����p�h`LmU�%-y�&�:������>6zx�6�:yI�@������#t�[�Fa� '�߾�u�<�#{6S�M)�gƄ)J����„1��X����c�8�L�W��o��,��|��Ī�jv�l�П�6��uK���tm�1uH� H��b2L��IMZ�P��Ůu1u�ҳOt^�e�5�`�8�or{+S�� +br�l�aˤRU ~�%B.1g� B��>x�ʧ1W��^5���c��o�&%�l����y�J����ڢ��I��N�H8|&C�0*�g@�`�XK�WB-!S�&�Xg*�,�B-��������t�q/�?Z�`4����7- ��|�ՙЃ���dr����K�� �)�׮&�jE�ʇ��޴�8� �&�FU���'��Fc�;6�#߷ѹ��������cCZ���ߙ���U�P�=�TWB��҈OUd`Q=�$�GP�f�y�Z��3�Ҏ�jL�������/�V]o�0}�W�I<$����nZ����j�'@�qn�5cG�ӵ���'��'P��(���s���|�&�H�=�P�_ > ����H.I���Dk�&9Gj��G�qS��j2i�a_���yq�^5�bO� ��&� g�\�;Ԗ��2�2���yi��a�"BA^4�FK�/4�R�(1=Q�l��8�0)��J��ʨ +k��S��4� �E��I��-� �Ǵ eM���fy?Մ�rr�R뇔�����q�8v�)4���FT�M�W��l�Cs�����&<���;�4�d��51G?g6n��TvT��� ���׳��p*3q8!,�㹚r۔�,��+G(�[W��0��aթ`/O;�I_��`�ڟ� %��A�ĮT���u+%��Zuve�C����]d5 �Q/}"�=1ֳ��°�U�c���Ė���aXJ�[�S�0;�'7�h��B��d�?ۖ[0|6(" ��S�W�с���������G��Xmo7 ��_�v��.u� X�$u�.i� i��!) ����Ȓ&�m���^}o�Zo�/�O$E>|H�z����=N�% �����y�����@Ȉ�p*RhT�1B]H� �F�M�2U��p?���Ӓ( J�U��0�.]�Ћ���EQ�(Q��㽺�F��RX��x�hӘ�� +�q(�6*��N����9� +�i��Mzs���ԳU���N���qpB������kWH"2��U[�1s%��] ��!J�_S����r3��L��!<�V?. LE�#��P�^*���\3�����߸U�gGG�|I�2�_���n�Lp���a�W��U0��*�N��+$�z�{�b��|a("�-,��~c)�m�F�X��`�<��k��&��q�)Tk4��w�^(��μ�^�wQ�}hd�ڶR�l�P2b3'���{�����C�9�z� m���$o���?c����PH�y� ���N,dJS��len�xr.=[��()t���́8��6{��̢<��F��Ʒ=3���D*��Wɞ~��~UP7��QM�8��׷qm͡l0�e����(�l+��z��[rm� ���q�z "�pނ�k�M����Sv���jKk����Vr�\�{0H ���d�$�̓Zz}p_~��ԍnU"�b�1�������E��IG��fV&)�Kt\ �������q� i��9ll��46�WbÆH+ ��| _C�f�m$ޢ��ff��ZY{1'؃�u&��n�&V��<�d Fn�W�z������t���1=�X�d�)�k� �d9� JF#�1c_���ݡ'۱�SJEfs��ܠ�j �Q���r*�4��Re 7�3:��v�q���`3�_8� ����Zݹ��i��4���5��aY0s�j�٬�S��F�u�hQ� +0��I��ׇ_.��<~��⏳�d4�xr�<8{ +u�LWGH��IHϤN��ho~��h�og"���+9�mHLre]8��m� }��i�ϰ(���btI��6lkc�se����|u Oộ9�����0x6QÓ��U���#��R� `r�Q�Z��`����y4ʹp4��1aC�C��5��������mA�1�S³<���8h�)���&���J��9�3,�zU/9���*�k�8g�D�����K�UuGTF��]�kM�a�4��|��, ��:�Ba�#��5� �<��%��� +��_ �-��ɪCˆ�Aǡ8}w�3f�M e��}oq���R�]�{:��V۶�!*������k}%��F�!n�b���Q�`�m�P&Jxd��$1�N?ĵ;䕲f�6%L����ܜ7x��y~�/��U�M@�����{J��U�(���ވ7:ZQr%^@[���?���7�# 7ūbw�-��0�����Zmo�8��_1-r�\�N��.p���b+�P��Jr�A���y�EI' v����_-����, g������>�F'gϟ��s��葑�����/���������~�Lp���M�}�N�փ�h s��?>���>႑۽ 4���H�՛["��v|Dl�2�?� �Q�l��$������1zO|��"b�aC��>��<�D��j��7 .�a�@7)(��v{.�a�H�آ[z/?�� +� ��Ėp�OQp�WP��{";��hHX�K�&b��{�]}��w8��)C�g�[�`�f<�5qjtA���KL�XI���*��3O�Ǽ(�)�z�[,�A�>eK���Q�!6���cF�Fw�E8݈�p�,u3aO�D�H�cҽ���8/h��M�½�mLV��ٜ38�wn��Z]��ǹ sk13l�� ��ҵ��k�<�0��j�,g�/���me�� ��ja3��m[_����\N뙹�8�󵫬i��0/Mט�k�bhF�`�.�Ұ�s}�����t��� �]J�� :�t�5��nKN������3ә.t�Ҙ��\��㳱t���E��`�Eu%�s�~�0bq�k���1u�f��ԜKW_��YSS>����n_+ -[�1~]K��0�/���Z�@��I��m�R�.$+g}�v �hY3eyǰ?�S�y �Q�[;�f������m]���V>��S�O�2��a��kZ�!̭+�a�T_;�L��Z*�ݹa�ג�4���\� wn�`.U(��ӥ9�6�n�Ҳ��l��,,�� 󣱜�%]��1T�e���1c�W�5Xk�����cďw�����gS����KX�c&�c]�����+�C��o��e�� (��{�[��r뷣|ȣZ���>��!W�Ν,�1޳e"�ap����}Ma�''r� �aJ� �K�ɿ��{$0��kO�����.�c�����aOPF0� �У6l��{�� $���ά�;h����IB/�+�X���8�F�&��{����aKV{Iΰ��m��o%�F*��,1�#L`�^,��E��S@B|�0�x�5|�h��y��0]���zt�C��� z@lw�D�3ԉR�=r�k��(h�uɐ�K@@0���|$�e;�d�{�� ��Jz��"��+�~i-�6���[��0� +x�����:&0h0G|�w���5��9�� H��wH����r%��$���~���Q��M@�=~�}�tWZHR��j6��S��͍GC.���.�����G6R���l�Żr�*x�Dm#�����n,&� �����`��k���1���g{��nIxV�M?ڕ�X���V&-ZTNyP_�󢵵��-"�S���vWV°س�QV_e3V� h��{2��uu@�(���62���G�T�W�6� �d��!AL ��Bb;�^ju��. .r{���)��h(M��\ؽ���w3���0Qg4��o`0tB8;���wX�r��૒ ��4�.����!����@V�5�{�N�=S+�-르��{ #� �{��L���O�Y��i���-pf*n6l��˳ �[�#Ys,�8�K7�$l�#OS¾�b��Ôca�%�I�ۂ=ςS¾�S1�ػ���Z��>$#U.T�p���l��,��Dƹ] �����ċ��m�n�0pzƿb����r�Z�a�xkeC�Z+ǔ�m�V�uڍ�q]�ӎ��zZ׃�/�����jP�;r��sWr � � �;̈W8�g�@#�V�N�BW�[�`.؍L���05plH~��Ty�T�1����΀���3�¯�s_s:��,�L�Z��D�6����5��W?�ޯ�Z���5O +nY^X\ `�N��G0���/�ʾ���S��>v��&Y~�%�ޜ��:���q�8��)��f�����;�4�98#I�$�-�:�gh���CE�3m��;-��A<�ن�Pg(�����GI�e��ͱ�B/QF�TZ�H�-� +�EO���n�9J��!B;-(�� ���CA� �c�CYA�΁�TP-������Gp� �E�� �{RV�Ȋ��@��\�*Bi�r�@A�J�I���;���X����;''��\� ��X/���M���{0�6M�a!���J��x�i6K������E��e!%z������h���mnx�7 ��1����j�}��:�:���� o��N?J�#g�������H=�������`ؘ�-����� �Q@|�ݩ{x#-k��/� �F���7y�.�2�}'�ո�4�8դ�z޽Q����a����}�F=�3�ů������� -��1?{�Wb��P�Oj�Y�pʅ�7���΀_:�tٷI|����(�k;$��suӗ��v��u���׺� 5�ݪ���-֕��-ʦWt��و�:�º�P|�j�&ײZvAۂ?���ӵ���U� ���1�yɻ�����G� ��U�(�*���Os�C��'���p� vi�W��@`"A�%����i�܌g�H~6窎g�q� �(J +"Zq�,R�,�^-��3��@v�E ���v{�[pR�wv>�%,Ā��B�GPِVsմ����D�]J�b�������O�[�V�q��Z�j���8��|��V�`�@�Z���SVy�c����]]�]�V&*��栐�IBB��p� t'�%�������� %߁җ=7����=�(�R��F�za���[e +z$/�J�(u~h�N�ɵv�\~�SOkV@t��/IH��g�����!H;���#�!��R��{�"�]F{E�7}H�2R*��y��@7����}����O'�6�a�m�qկ�q�Q�AE���6���_���;��E9f�r6�21ȓ�4a +�a���)3�И{viQ�t��;�i�/�+��;�n�jp��D} b���� D���4�����ۿi�n��n����6�bc�Ľ�5�u�Y�����:-J�� ���U:B�1U���|����"{�ʴR��z1� �h�PCx��(�~�JSa7�޵V������ɏ��V]o�6}��� +,N��u�Ú��,�1Y�D*���"]�DeQ#�A��>��%����`���s�=����?�Y�9>8�����S����~ޅW''�:y�;x�B�Y����6S�_7�G���^Y��֠P����8�Bh����YAV���-*Gw�ZT����Ts݃[af ��� sY���3 г��B�Qͅ1X@��(�3� ��T�����eU��]�͛/��ANR�,� m@��D�`�kyc5JU҈{K43J���i��'� +��2sTGϰU[��M�d����"�^ �/�X'z�UűT � �3�Jd�~���n5�n1B�rmL���{�J><�+�K,�tCd���5Z#`$`UH��z�Vr. �R&��@%n�����"ZN�m��kl�k̭ϠVºOY{UK�i��)���� �I|I@|H��Ǔ�^ 9 �0 / +��#��~����ǀ�=�x��h��8!�A��CJ��ċ8%�4��4��E�)wj�B:����ޒْ �F$�^Ľ> )����#[q'���K8���K,�8M�1#`[ (�C��Hp4�(rI"l��֎!N��Z�>��z��,�EhB|n;{8�i@"�=`c�S{Bޓ�8����0N�������S/��y������c�� Y���B���8�)'pǁS������B3'\�H�{=�|���٩=琉Z�,�8I�t�iua_�K��樓N�8r=�!���ŵb�Q��jH��$@#�[�<+� �y;2N�� o5 ���|b���2�u�J(�1tY�ʛ@�����RF��-;��h�� .�%� v���+��`�?\�߬��:˿f_�.+oad)����f&�[n?�F9��~"��� �ȱҎQ�./;pp��=��r>C��>6��{�cS����Ť2�RfZ���;(�.�*��� �n�9(�{!�Uf�oEXe�'B8�c����o�W�ms����Q+q��.>ڜn>+��tKn�7Xn{�m�� S*���cץ�a��r����s.+m�"7���^QB݃�u�F�����������]�{gk��m�m8���&�~7�Z�����ũgp��T*�����L����;x1%>�����/���Tt��kJ.��n�kS�ey�����F�}����k=�gz[����~�F��ȇe֧���oQ-�G4�@;��N;�a����v�l�6�����˗� [U�� ^�l��̔�� +o��u�-�ڒ����W�p��T��2��� ��3���z?�|�K9ʪ����!u�����h���������9�X��l��&�g�lYJ뒛^�T`���6g,i|���ȶncnv��mGƒ/�y ����`Uh��p���p�2�4��o�]��s���Y[o�8~ϯ8S�p/�>m3�V��XXE�Hr2�tl�E I%�m���DG�d�}X����|�;Wʿ��X'޾=��0f����+��������O������ߐK���{��}�J��2��8 +���? bJ�������@�J�@s�� �_�iN�,߈!��+�M��%�̍ +��:�F0�kw�L`t �́q0� ��Y ���8a�?�q�ǡ;Z�A�;7z��ǟ��߂��������\?v�p1����,�q����"r&�ׁ�m�gN�*\� �!�̜x����+���Q�㸹3!¸a,�Υ�^:��Q���q#g�S+t#�ǭ��ط,��:h�ȩ>6�y�C ��ɵ��W�uJQ���L!Z�g��Me|)H�P��]J�1���I)׌�r;�D���Վ��;��KF̅f4�&�U_+�Nt�)H��1B�w�6|?9QB�2k�1��?E ���^q�$+St��oS�I�DN$�g��$#B��h9�C��Ym^o���0f���?�~.8} �4�{�^"�_�Q[��� ܵ]yWq4<�u��M`Y扞�J����y8U-��E��/�� ���r�%�a�GQZ�{��fP�����,Mh�3�������X �C|��jG�Pzl�Bn ��Ϋ��S��``���w�hywA��%Ey�R�?�9���>�vw���w8��M;;��V����'D`�{G�a�z�%X�tty99��0�mV ��w1���I�4_�b�J]�� �[��[w-:�~�7�� +�h���q��4�� �e���ӥ�!���2j�4C��mI3��Q!�U���D�I�$pe.��K���� +�W5�b��{��N8'O}d������6 ��X�#��pl��Y���`�n:���0{R�]41ː��n��RH���X��YS��t��QF�o�����K���VW��pP]��z&VԷ5��1m������epZI��h[ӱ�Zo�jh8�8\8��*j�U6�OE楪���-��݅@YK�}���KV_��`ԓVF���x�n'�Tߝ��MF�o����Иfdeݩ�$��č��>���|�n�<��wtT��go5fZwF�N�8n���xhR�j��>�f�~"rMi��cf�5"�45 {�`ˋ��- �v����F9���0+�3&���oͅ$�1��GH%'�}B���@��W�Z�#tC��_H�� +Q���+F�{������S �M�V�g7���S˨��]�T����wE��+����0� :}��!�[� �5OThr�Y�=�wp��Q'P�MTCu�P���K �%����J��{���vK�z����ժ�R��k#�'�� E���WG�1�~��>�gzk�F}uo +���u��Ƣjbֶѵ;��=�R�{�m�}����� _S�-�[�������S֛K��9�_���_!e��������Z}'�=��iw�R��B���_X߹�ʁH���>�Ԓ{CUnDbL7��Sv\U��w�� ��wGڣ9�8�o=j�^� ڞ�?�]NU�y�zjr�$+����2����6�S�,��8�7�R����?��X�3������#�y`��LҴ:�<��'ܥn��><��<���U�o�6~�_q(��jdO[�.�D�d�#���D�D%�#�A��} m�n�}�^�w�}�ݑ|��n�]�};����ݳ՛��qu�WW��������L�}V�;x/m�nW���V�7j���r�>��b��Z;o����t �z�@w�Lo+WV����ƶ.�'��`l���Ckj�֕ I��V�N�V{�j�Y�kU��J~�`m��<�n��j�\tj���� �����R�����`�����re�֠Tg��T�G�[�������]��U�]�H�*{�ltw���fgM�W��"�\kS���|}�]}i,�UZ�ղq���E�DNS,������� +�^uXg���@|�e����V*4R ހ�jc� +=���5^�^&�VV?��ִ{E�Y�'i�6��۩*���gC{u�Fs�$1#8���0 F�I�3�,A�0�t�d�n&`F� 3�� ��`dR +�8�AO�*��?.�(2_�g��C� �'@�4/3R�%0)ET� +�ɜ���ɞ��3�)�1Kg�hBr"�1ꔈ"D�R� ��9biQ��B��i��g@ +((�{\�3����(;M7N0�Mr�W,!# �"dv�$Å@y|�S�#�/rĖ1Cʂ��Y�B�C���s��:�Hi��<���� D���YT�cvOR�o �< +Wr�@�J����)�&�'%'A��E +�+���f��c)*9΢ִ�9��lp�� <̰�a��Wq9�`$�����L�$ ���.Rviz ���b�����@ː~,Z��~x��I,-�)���{���srh:^�����ɸ���ܨp�P�Mcd�e�����s/���V� ��G/�6�R���&<�|?���[f'+b��R���6|��S�H� 7����Z�vV?J����^+���� ��:|Q+[X�|�V'�G�~�� +�}W�7�ӧ�t�۾�����Q�,\r�> ��7����~�1��Y�������va��h)^��k��{�>�|o����_����U�Ƈ` �Ͽa�2z��S�n�@�����6�nkk�������L�Q6�e3�k���A¥�v��{oO/:с�=���+�O"^͝�� c8�H��@6� �R{�(��x\�a �d0���-��Ť:k��#��5� Ȍ�a4��)� +� cC�� ���u�B_�ڇS N��@5LPF��#�Ў'�n��b�f���h����Ɔj�������:�O�a(#KS�h��p2�F:p<��4u �ڙf���ND�*� � ��F�TYʱ6Ԭs>�f�p��Ɗai��P1�xb�uSdq��������.h#�~TG���p(�tCd�0Ԕ� 7:��f�} 9˾���:��a̱������z6*�9�P7P����:�4e�L����*� ����Pϐp�Q��c�Ҭ���]pɛ��Q�� u� nb�(�������h�y�ߏ'���C\��R c2�4}ԆS���Q5��LLu�e��8�֩�������NU�T5@qS��SP�eh}K�� �t�����a�}PG}[uD�I3�6W-C3F�����>A���ML5�*�s�O-h'� >jH|�UB7M-V��I�4�~�2ޯl�ƾ&���ux�=���u�(_n�y���U۠G���{�u��8E������eog�ۘ����$�~N��/;;�eoƧcx�} N�\�!;#s�'��5� E��o^7;p���͛�p��zA�R�f� _x�G��qﯹ����GpgәHCq��r�NO&�����e��h�jD)�@�Z +W$�`B��䬊0�&c����hg�x6c�q;���Z�/?w=�b��OUո�Vԕ��㟜hGܹ˕G�'o4?$t�R���uo�@cI�E0;�W��R�����k\����+8��>���`�o^o�*��!^�j(���zY�۳�}�T��}��nn�O���O�V�G 7�E�T�J�~R������ ���ܐ���._j��+��>1��S}��6�1ЇC��]��dž�W��!���#�…+R�n�t v�r"�A�Ze����$Qjl��Պ���s�،h>#>sC��Hn�� �)�RT��٭4�G2���qk��O�z�/��%�)�\E�CrK� +4���h+k��k&�� fdE��7��HɌ8�M�(3����\�k���t�> �� [�9��tMD�O���we��']�f�����l�vo��]z��ۤid�s:�-J�R�g:���M�%��9� �����+��?%���ak/��*%�}h���?�x/�%6��[�^��{ #���-���=�7a��Ka�'��#9��9n-",�^_���p�i�����t��U�a����D6Nyn��؞7]3B���[��F��p!s����~䙉m�O�D��mA:�h�;rSЖ���d&�����UX$qտ|y$�{�K8x��$���|n�tGs�c�����͠D�Xr�B2-�f���^����%�Ilg��)��R,���،�x��;7t�j�����J`3���~����:�����өfE9�aŠ�\Qb�ȝ����e�Zz�E�Z�(^�#�M[��qp)g�Q��5��:V �ҍ�H���}:��,u40kI(�(̈� I5>��h� ^w�'���7�<��\⫩ov�uDs���SR��%좹����-%,�n� ���¶��j6�Q�缪��s=#s{�u� +4��"I��;d����V���y��-|�Q�M(��O��naf�q\;� 9����'�+�|��������O�Չ��-Ԋd�˦.O�b��+|昦>����!�_k�;E �*�Uٵb���# �y�"�Ͷ���D��矓�$?l��ۗQ�L]z��$�K ����fi�'�l���HL��q���P��[Y�N�]�_�nJkb�v�u�s��IV#����j� 4*�^L�� a�ҍ,ɥ P�'�Hz����X���cI}��� ۯl��zXI�i���WD=E����::>���.;F��j���W$D$��B��o-����������Hq�������������ٓNOٓ�҈����9�?g$�{�G�,F��.K�����a��D�ߒ.��=`凩]�s}~�v���u���Rw� ���� �PmK1���?EкI��{t�HeD<D ����@�g�NO+�n޼G�� \�l�RZ��6D$���]JZƫ?G�(W[���T��C=��l<U���� a�5�T�/�~A�K���� i��E�@�#��LK�2E��zF%���{�x +��"��r�sB��,8j�הS!�ּ�̳����!�>Ӹ�G ����ރ��9�h��5��G +�Ң��alr2k͊�[n�b�v��w�Pݞx�mK����ǧٿbȱ�ы�ם�Z��~R�땬O�JN��yD��& �ME�o7�q�[�(I�[J&a����3ME��OŎS�����LE��8�Ēs�+]Uq��4|Ƣ�]�[�NT�{�D%C�x-�D��&#��4�#��zY��˞ںq�h����~�{�'�+ ���#"�'*��_��k�/�s��Ҳ�"�-� μ��a�������G�v׷�SB��6׹l+�������N����2X���5�'��m�~�n�+ۦH��w�J�v-��եPZU?}��� M~Z���Z�W�Ǔ6&*�1L�!��T�Uɮ��애`Q�/�M��b�_w� �Q1N1���)(�%HI +$�%�Ʒ��plk�N@����%R��V�3;3^<�>�@{�D����Y�W%G��3k�ϗ:�0�� Gt.��Ђ?���h�s�[XO��8�ܻ��'h,b���xI%�Q0�Jc�)|�dł$�r{�-�W���Kܮ��AL��u]!�,n��̸�Y���Ku�t鿍�T�C5�;w�P ��2z�.E�Ύ'�B.kM�rDOTg�e"�=65�ǁ��O�f�Me�EW���?u{}�nn~�/�R�n�0��+� Ĕ�Əc6m�қk CG$R�]� ��{@ٖ��Q^4�.ggfu}S�U�Ti�R�������� ���=�t���Nq~��J�b��Z8����0��/ +��{�l;�E�+�/� �O�f��HP��M$ٗ� �;b��U���%��{�޸i]�ca5�j��z�1�H���;�F�\�\)j� {É9���G�{� hTQ)��$W��,�=��N�K�φo}Yz�[���vB�ڱkH@J �)�h�Fg5��P]0�)��m6�٨�lr�����*�ez��whv�U��=��D;.2���x��%p��f����v =�*���0t����0NĴJ�Ay�-��e+����-��-\7�p���j�-H�8��������+O⸆`1l�&�(�C\�'�XQo�8~ϯ�9�)���" \�I+[LL�#�D9٠-ZDŽeR ���"��@ٲe[��N/���Ǚo>����K>��>~�P���|����B+9��O��}t���7�D�d\NQ[p-��z>N�B� J �'L��c�I��0V�Qa���e +�A�*t�囑�\�a��̴�Y� (]����L�b,��.�9꙰Sȵz)�`'܂� �U��g!!Q2n�)'�Оm���5�LT�0+��� Y��zrC�RY�`{�`'�@&�uXu'd��a*L�q1C��^�gB���<˵J�����2�T%� �-R�2��4(;A 3nQ ��ur�Ė���BP��V�:'�T)�z�,�X`*m�N��F�D��U�2Uڠ�W��LY�u�@�Zt�!�膃��^�b�}�D ���n�� �0b��c@��� |��{  "��ЛA��(��t�C��m� ��0�>��1�!�kI�WpC�n� b�C�4�/W��q�V� +#�`�E1��^T� �� d\�>eݾGo� 4� rK�X�����0��^�v����Ų�=�4"��E���R���o�.u7�/r3�{��*�0r�2�! b����n�k �E�[.q�aDn\�Udž�x�C��#�-�v���Di���^ۍ�������w��:>+<�$������!��;rK"�zCF���0(�{$���#�LO�z$�h�*��L����v�uAFq-p�u�^��K�h���(#�+�E�9;�p�λ�p��(�9ddq[�|�L9�+��[�X��2F�� +�� ��eF�w�ל'S����+��OWc���˧_�d_���f���|�5 JSz�a>���1|�++W��x� Q�B���J+�(�1_hL��s�p)^�/�$7�;��R���r-��E�7՜��D�r�e�j����LE�7�aØ���Bc<����t�@�����a��)�\i�)�C�'�f�������'pq �Qgg��~>i�ڝ���ewr�{���-��+� ��Q&2)C����"���t�r�^�{o���Z����.�Z3[�8��|���A�]K�F��ڟ�_q�&�����e6��ڷ�|g1��;!i-Mڋ?LqnZ#�z8<�ƃ�$��3H|��T>�L��~,�7�g���3�]&�B�q�`� +8�����{w䂩T��4���'�g�AoB˳��n��Ru���̧�nt�.���������i����jG��rm;�x�Z���+Fh�-F�#�;�������a��������Y����|�B�(���)���P5�v�pwv��J�^�����YkY�� ��#���7��.���#ڇt1T���I^X�,ʺX���b4Ź+ +�ž�W?�u�ld􍈫��&[�w[ �mQ��z�fs����is�� ?Vy2i�7�zݨ�X��+�fԢ�V��fw<�|x�IM�^�j��̀k"�78'�n�0 o�Wͺ����n+��q'���jE�!� 5�����Y5�BKx��~k���f��>���-��7۵hKD�� ��.S�:�..��QѲ>k��k>6� v6zps�[Z���q�6��66/� ��K������;W����-dȍ��L�e�SX��:9�Y6���}���m����.�D�Ǎ��x� ��Ow�������_=�� +�@D���-,��/P�NL�@8�Ir���=B�]���f�1�?�!�/�h�Pcs�8�M�9�)Q��p� �[!�DMy .҅?�(�;{G]�>��:iʫl +M�^$Ɍ��Wɭ�1̣����;Hf���.�׳f��(7�`ID'�$�b8rc��m��7���<"q a�f�S���En�P��S���z �Eb� �� M�I8n���^� ��37H� �irg�^�$0��\��QB� ߍ���"��1�G���� �|#A�����C��5'|�N|Ҹ ����&&��Ӕz$H\ �L�y �����Fw6�02D��_ $���so�k��C�;&I�EDn ��ʘ��8��"!p��e>&�7:%�9�al�[�d ���c�?��+����y�����آAB�h1Oh�`ޒo$�����g�s2#atg�2l*�p;#ɌD@;� q��#N":M��aI%�`! �>�&�������1�Ҋhldh��ֽ�pa·I[Ĥy��ئ���7j�7¶$�8�m��W/�����3�V,��д�[kQ��uV땐���w�|�N�wh��:��k�3,�E4�=��|8 쌩X���0F���@ß��Q��#��5�y�+Pf$f �Kl��R������K-���fZ�ڝ��JD����am��߬`JAlC���Q����2W/�{�h�0����s��v�qT���Պ��k0l��[�/d�Qo�l1�\�NO���#5âB�3]���`Y�����i&J�e�i�Iɞ��rb�9>������Ea�3��>;�[����>6���˾=��� ?��N�Z����u�x���ĊI��6��=��d�h��`8L�� +��+�}q�KNo�`L�9V� 3��'�nn�ؓ8������F3_1y#rt��AX�F;u��0�^�4W5ss1W�RQfb��[yg�;��0ʨ��:V�����^l|�[]���U���%�Z�����5��X�K�=S�q��p�����^~v5w���F�������I�� T� �4���(M���'GC8���Ξ ����ٚ���V8�i�ÜKs�8izE}��#8�!��0M=��y�q=x��{�m&���ΜY +�,[u0��z�m~gL��Ҁ8��5���[��(68gz崚?�(�*���BL�(�������D�޲�39�F�M�g���3����a�'��nI�5*��|�_:��F�8:�&g>������P�ޜ�����1�Y��w�󶊻HB?tۘ/.�.�Ms L�}g���-�ch��-�Dx;��vk�LfD��̄��u�)��O�_�u�)���0G�Z���9�Q�� ����ͫzA�$��[|��n)}�J���?�Li����zC�B�Ȼ�M�9������t�h�\my������ʬ�f +�:&���}�â':@꜒h�@�_2=�����'���O1�v +"�U&[we@��aa����j �����v����0��?��O�������C��nl��X?�,�O�� q�ߚê߬���X/�7�V�N�8}�+�F�ZF- �i�a�4q���tm��J(M\jMG�[����ӆ�����%�}}|ι7v��Uͫ#8���y�|]=�8��f'�8?��q~�v���q���ʑϞ���{�Ƴy�L��!����'1Ǐ�� +�_�u;�^,ܭׯ��sYgFM%���xJ�k���2�_�r�:+�E���x�u�&�%G�'Ήc�5��L�A��Ø/�Fߎ��i�������N�r��5�||WI�N5��):8�����N�A�O�_���߬�S+��Ig8}��6]������������u��j�0 ��y +zH`�Е�K3��4= �&VR��3Y���>��R��ϒ>��n�%�)<�^�V��V�C�:��d,)���X�4�JKd�7L�@��[r�JHCc,�-h� �6���b|d���Ԛ �i����]:�� +!���rL%��P�9���ى��3��r@�E�bV_�� b�ZTժ�v�8��y�!9�t0� �$>���L$'��I�޲k9���0E��EU,�-"�Z��n�?��%/6��y����f[l��2���������]�Mk�0 ���:b�X���Y���5I�1���Ր:��0��^�|�= =Hz��hN�8ӡ|��� �|W�KK �qЭW�����1��m�8/�j� +����u�����+�/�M k�]�D�ʴ�ˢnq��t�B��pކ�>��W_�)��^V�~e�rV~ �J��I��z��|�V��=,foE.w�O>�r��="�M^�<�?�XLxX�9  +�ΰ� nӲ���E�W���u��J�@ ����X��}��hݭP(VlA$ �lw`���T�w�Z�?�2_��!����}�Z�]�q]-�i�:��L��Li�?�UG�E��L�&� ��A|G�X i�Kp ڃ���D���b�D�^����-"Ƌ֪���(1-LM +���]�1���� �±�T�Y}�G2"nʬ�ˢn�Ȓ��MR���"���H�9��{O�C0} �i\w����xFċ�y�! +���p�+�L� B///�����M���#"&��;�u��j�0 ��y +vH`�Е�K=�.4= �&VR��3[��w^�� �?}�>���c�,�X;�m[��Jz��:O���?��}��- �b�92>�:�� {v-Y�IA� �(�1�Ҝ��`�d�T�Ձɧ @�ۚ���Mmd|��cK�5 �����,G�H�p5��1��{��Nh "��uUm�ꀈ��m��W�@<�߆�{6�{�赧�xZWbS�q�5w�n����k.�C�CıX�E)v��j��r%aU�]��J�0��y�9M@���EJ l�Adݬ�mL҃Ⱦ��J���c�����o �!���'t�C��%�k��;z��=���u�2�F�<��I'��@op7����ٵtN~��Aļ��,���lׅW{��d�w��;�5h� �鶛 "�}V!b�>_ˮ�� ͹h���������ؙ�DD��S�8�dfb)A�ge���E%�(K�/u��J�0E��� l �~��h�V(+�� 2�f� ��&SAd�]bٺ +;O��p��f܏�z ��S���UҫP �y�׆�� +�A^4#��sd��tiAN���%��^�+P�c �9�'۱v1n���w��^tF���WX��`>R�-�ɘ��=��`;��0ΉJz/?Ӆ�AĢ΅�+�"�²���d�����yy�N�<�O�S���E������{H�h0d�T�iЁɧ��M�.�]�R��m�< b�9�Ҫ�u�A +�@ ���"�~@P�z[�!�t(Iٍ}��x�y`��}��0���VK�157�t����iT�G%���b�ځ �^ŤQâ����<@X#!��q�剈'�h�>��/�(P��W(��,V�,VH�SH,-�OO�K-J,IMQH��IU�UH�W��/QHM�,኏�w��q�� ���犏�p� �w�� ��q �д���{v)�[��)��Iy� &�5޵yv]�)�Ӗ����Pa��C���������o�3n3�O*�GBMB \ No newline at end of file diff --git a/tools/phpdox b/tools/phpdox deleted file mode 100755 index a0c849199c3..00000000000 --- a/tools/phpdox +++ /dev/null @@ -1,4239 +0,0 @@ -#!/usr/bin/env php - '/vendor/nikic/php-parser/lib/PhpParser/Builder.php', - 'phpparser\\builder\\class_' => '/vendor/nikic/php-parser/lib/PhpParser/Builder/Class_.php', - 'phpparser\\builder\\declaration' => '/vendor/nikic/php-parser/lib/PhpParser/Builder/Declaration.php', - 'phpparser\\builder\\function_' => '/vendor/nikic/php-parser/lib/PhpParser/Builder/Function_.php', - 'phpparser\\builder\\functionlike' => '/vendor/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php', - 'phpparser\\builder\\interface_' => '/vendor/nikic/php-parser/lib/PhpParser/Builder/Interface_.php', - 'phpparser\\builder\\method' => '/vendor/nikic/php-parser/lib/PhpParser/Builder/Method.php', - 'phpparser\\builder\\namespace_' => '/vendor/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php', - 'phpparser\\builder\\param' => '/vendor/nikic/php-parser/lib/PhpParser/Builder/Param.php', - 'phpparser\\builder\\property' => '/vendor/nikic/php-parser/lib/PhpParser/Builder/Property.php', - 'phpparser\\builder\\trait_' => '/vendor/nikic/php-parser/lib/PhpParser/Builder/Trait_.php', - 'phpparser\\builder\\traituse' => '/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUse.php', - 'phpparser\\builder\\traituseadaptation' => '/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUseAdaptation.php', - 'phpparser\\builder\\use_' => '/vendor/nikic/php-parser/lib/PhpParser/Builder/Use_.php', - 'phpparser\\builderfactory' => '/vendor/nikic/php-parser/lib/PhpParser/BuilderFactory.php', - 'phpparser\\builderhelpers' => '/vendor/nikic/php-parser/lib/PhpParser/BuilderHelpers.php', - 'phpparser\\comment' => '/vendor/nikic/php-parser/lib/PhpParser/Comment.php', - 'phpparser\\comment\\doc' => '/vendor/nikic/php-parser/lib/PhpParser/Comment/Doc.php', - 'phpparser\\constexprevaluationexception' => '/vendor/nikic/php-parser/lib/PhpParser/ConstExprEvaluationException.php', - 'phpparser\\constexprevaluator' => '/vendor/nikic/php-parser/lib/PhpParser/ConstExprEvaluator.php', - 'phpparser\\error' => '/vendor/nikic/php-parser/lib/PhpParser/Error.php', - 'phpparser\\errorhandler' => '/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler.php', - 'phpparser\\errorhandler\\collecting' => '/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler/Collecting.php', - 'phpparser\\errorhandler\\throwing' => '/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler/Throwing.php', - 'phpparser\\internal\\diffelem' => '/vendor/nikic/php-parser/lib/PhpParser/Internal/DiffElem.php', - 'phpparser\\internal\\differ' => '/vendor/nikic/php-parser/lib/PhpParser/Internal/Differ.php', - 'phpparser\\internal\\printablenewanonclassnode' => '/vendor/nikic/php-parser/lib/PhpParser/Internal/PrintableNewAnonClassNode.php', - 'phpparser\\internal\\tokenstream' => '/vendor/nikic/php-parser/lib/PhpParser/Internal/TokenStream.php', - 'phpparser\\jsondecoder' => '/vendor/nikic/php-parser/lib/PhpParser/JsonDecoder.php', - 'phpparser\\lexer' => '/vendor/nikic/php-parser/lib/PhpParser/Lexer.php', - 'phpparser\\lexer\\emulative' => '/vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php', - 'phpparser\\namecontext' => '/vendor/nikic/php-parser/lib/PhpParser/NameContext.php', - 'phpparser\\node' => '/vendor/nikic/php-parser/lib/PhpParser/Node.php', - 'phpparser\\node\\arg' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Arg.php', - 'phpparser\\node\\const_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Const_.php', - 'phpparser\\node\\expr' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr.php', - 'phpparser\\node\\expr\\array_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Array_.php', - 'phpparser\\node\\expr\\arraydimfetch' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayDimFetch.php', - 'phpparser\\node\\expr\\arrayitem' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php', - 'phpparser\\node\\expr\\assign' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php', - 'phpparser\\node\\expr\\assignop' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php', - 'phpparser\\node\\expr\\assignop\\bitwiseand' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php', - 'phpparser\\node\\expr\\assignop\\bitwiseor' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php', - 'phpparser\\node\\expr\\assignop\\bitwisexor' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php', - 'phpparser\\node\\expr\\assignop\\coalesce' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Coalesce.php', - 'phpparser\\node\\expr\\assignop\\concat' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Concat.php', - 'phpparser\\node\\expr\\assignop\\div' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Div.php', - 'phpparser\\node\\expr\\assignop\\minus' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Minus.php', - 'phpparser\\node\\expr\\assignop\\mod' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mod.php', - 'phpparser\\node\\expr\\assignop\\mul' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mul.php', - 'phpparser\\node\\expr\\assignop\\plus' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Plus.php', - 'phpparser\\node\\expr\\assignop\\pow' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Pow.php', - 'phpparser\\node\\expr\\assignop\\shiftleft' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php', - 'phpparser\\node\\expr\\assignop\\shiftright' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php', - 'phpparser\\node\\expr\\assignref' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignRef.php', - 'phpparser\\node\\expr\\binaryop' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php', - 'phpparser\\node\\expr\\binaryop\\bitwiseand' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php', - 'phpparser\\node\\expr\\binaryop\\bitwiseor' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php', - 'phpparser\\node\\expr\\binaryop\\bitwisexor' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php', - 'phpparser\\node\\expr\\binaryop\\booleanand' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php', - 'phpparser\\node\\expr\\binaryop\\booleanor' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php', - 'phpparser\\node\\expr\\binaryop\\coalesce' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php', - 'phpparser\\node\\expr\\binaryop\\concat' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Concat.php', - 'phpparser\\node\\expr\\binaryop\\div' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Div.php', - 'phpparser\\node\\expr\\binaryop\\equal' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Equal.php', - 'phpparser\\node\\expr\\binaryop\\greater' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Greater.php', - 'phpparser\\node\\expr\\binaryop\\greaterorequal' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php', - 'phpparser\\node\\expr\\binaryop\\identical' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Identical.php', - 'phpparser\\node\\expr\\binaryop\\logicaland' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php', - 'phpparser\\node\\expr\\binaryop\\logicalor' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php', - 'phpparser\\node\\expr\\binaryop\\logicalxor' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php', - 'phpparser\\node\\expr\\binaryop\\minus' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Minus.php', - 'phpparser\\node\\expr\\binaryop\\mod' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mod.php', - 'phpparser\\node\\expr\\binaryop\\mul' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mul.php', - 'phpparser\\node\\expr\\binaryop\\notequal' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php', - 'phpparser\\node\\expr\\binaryop\\notidentical' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php', - 'phpparser\\node\\expr\\binaryop\\plus' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Plus.php', - 'phpparser\\node\\expr\\binaryop\\pow' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Pow.php', - 'phpparser\\node\\expr\\binaryop\\shiftleft' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php', - 'phpparser\\node\\expr\\binaryop\\shiftright' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php', - 'phpparser\\node\\expr\\binaryop\\smaller' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php', - 'phpparser\\node\\expr\\binaryop\\smallerorequal' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php', - 'phpparser\\node\\expr\\binaryop\\spaceship' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php', - 'phpparser\\node\\expr\\bitwisenot' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php', - 'phpparser\\node\\expr\\booleannot' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php', - 'phpparser\\node\\expr\\cast' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php', - 'phpparser\\node\\expr\\cast\\array_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php', - 'phpparser\\node\\expr\\cast\\bool_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Bool_.php', - 'phpparser\\node\\expr\\cast\\double' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Double.php', - 'phpparser\\node\\expr\\cast\\int_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Int_.php', - 'phpparser\\node\\expr\\cast\\object_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Object_.php', - 'phpparser\\node\\expr\\cast\\string_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/String_.php', - 'phpparser\\node\\expr\\cast\\unset_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Unset_.php', - 'phpparser\\node\\expr\\classconstfetch' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClassConstFetch.php', - 'phpparser\\node\\expr\\clone_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.php', - 'phpparser\\node\\expr\\closure' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php', - 'phpparser\\node\\expr\\closureuse' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php', - 'phpparser\\node\\expr\\constfetch' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ConstFetch.php', - 'phpparser\\node\\expr\\empty_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.php', - 'phpparser\\node\\expr\\error' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Error.php', - 'phpparser\\node\\expr\\errorsuppress' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php', - 'phpparser\\node\\expr\\eval_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.php', - 'phpparser\\node\\expr\\exit_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php', - 'phpparser\\node\\expr\\funccall' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php', - 'phpparser\\node\\expr\\include_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php', - 'phpparser\\node\\expr\\instanceof_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php', - 'phpparser\\node\\expr\\isset_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php', - 'phpparser\\node\\expr\\list_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/List_.php', - 'phpparser\\node\\expr\\methodcall' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php', - 'phpparser\\node\\expr\\new_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php', - 'phpparser\\node\\expr\\postdec' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.php', - 'phpparser\\node\\expr\\postinc' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.php', - 'phpparser\\node\\expr\\predec' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.php', - 'phpparser\\node\\expr\\preinc' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.php', - 'phpparser\\node\\expr\\print_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.php', - 'phpparser\\node\\expr\\propertyfetch' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php', - 'phpparser\\node\\expr\\shellexec' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php', - 'phpparser\\node\\expr\\staticcall' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php', - 'phpparser\\node\\expr\\staticpropertyfetch' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php', - 'phpparser\\node\\expr\\ternary' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.php', - 'phpparser\\node\\expr\\unaryminus' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php', - 'phpparser\\node\\expr\\unaryplus' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php', - 'phpparser\\node\\expr\\variable' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php', - 'phpparser\\node\\expr\\yield_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php', - 'phpparser\\node\\expr\\yieldfrom' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php', - 'phpparser\\node\\functionlike' => '/vendor/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php', - 'phpparser\\node\\identifier' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Identifier.php', - 'phpparser\\node\\name' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Name.php', - 'phpparser\\node\\name\\fullyqualified' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php', - 'phpparser\\node\\name\\relative' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Name/Relative.php', - 'phpparser\\node\\nullabletype' => '/vendor/nikic/php-parser/lib/PhpParser/Node/NullableType.php', - 'phpparser\\node\\param' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Param.php', - 'phpparser\\node\\scalar' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar.php', - 'phpparser\\node\\scalar\\dnumber' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/DNumber.php', - 'phpparser\\node\\scalar\\encapsed' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/Encapsed.php', - 'phpparser\\node\\scalar\\encapsedstringpart' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/EncapsedStringPart.php', - 'phpparser\\node\\scalar\\lnumber' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php', - 'phpparser\\node\\scalar\\magicconst' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst.php', - 'phpparser\\node\\scalar\\magicconst\\class_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Class_.php', - 'phpparser\\node\\scalar\\magicconst\\dir' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Dir.php', - 'phpparser\\node\\scalar\\magicconst\\file' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/File.php', - 'phpparser\\node\\scalar\\magicconst\\function_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Function_.php', - 'phpparser\\node\\scalar\\magicconst\\line' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Line.php', - 'phpparser\\node\\scalar\\magicconst\\method' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Method.php', - 'phpparser\\node\\scalar\\magicconst\\namespace_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php', - 'phpparser\\node\\scalar\\magicconst\\trait_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Trait_.php', - 'phpparser\\node\\scalar\\string_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/String_.php', - 'phpparser\\node\\stmt' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt.php', - 'phpparser\\node\\stmt\\break_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Break_.php', - 'phpparser\\node\\stmt\\case_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php', - 'phpparser\\node\\stmt\\catch_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.php', - 'phpparser\\node\\stmt\\class_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php', - 'phpparser\\node\\stmt\\classconst' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php', - 'phpparser\\node\\stmt\\classlike' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php', - 'phpparser\\node\\stmt\\classmethod' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php', - 'phpparser\\node\\stmt\\const_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.php', - 'phpparser\\node\\stmt\\continue_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php', - 'phpparser\\node\\stmt\\declare_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Declare_.php', - 'phpparser\\node\\stmt\\declaredeclare' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php', - 'phpparser\\node\\stmt\\do_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php', - 'phpparser\\node\\stmt\\echo_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php', - 'phpparser\\node\\stmt\\else_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php', - 'phpparser\\node\\stmt\\elseif_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php', - 'phpparser\\node\\stmt\\expression' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Expression.php', - 'phpparser\\node\\stmt\\finally_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Finally_.php', - 'phpparser\\node\\stmt\\for_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php', - 'phpparser\\node\\stmt\\foreach_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.php', - 'phpparser\\node\\stmt\\function_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php', - 'phpparser\\node\\stmt\\global_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php', - 'phpparser\\node\\stmt\\goto_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php', - 'phpparser\\node\\stmt\\groupuse' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.php', - 'phpparser\\node\\stmt\\haltcompiler' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php', - 'phpparser\\node\\stmt\\if_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php', - 'phpparser\\node\\stmt\\inlinehtml' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php', - 'phpparser\\node\\stmt\\interface_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php', - 'phpparser\\node\\stmt\\label' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php', - 'phpparser\\node\\stmt\\namespace_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php', - 'phpparser\\node\\stmt\\nop' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php', - 'phpparser\\node\\stmt\\property' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Property.php', - 'phpparser\\node\\stmt\\propertyproperty' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php', - 'phpparser\\node\\stmt\\return_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Return_.php', - 'phpparser\\node\\stmt\\static_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Static_.php', - 'phpparser\\node\\stmt\\staticvar' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php', - 'phpparser\\node\\stmt\\switch_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php', - 'phpparser\\node\\stmt\\throw_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Throw_.php', - 'phpparser\\node\\stmt\\trait_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.php', - 'phpparser\\node\\stmt\\traituse' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.php', - 'phpparser\\node\\stmt\\traituseadaptation' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php', - 'phpparser\\node\\stmt\\traituseadaptation\\alias' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php', - 'phpparser\\node\\stmt\\traituseadaptation\\precedence' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php', - 'phpparser\\node\\stmt\\trycatch' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.php', - 'phpparser\\node\\stmt\\unset_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php', - 'phpparser\\node\\stmt\\use_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Use_.php', - 'phpparser\\node\\stmt\\useuse' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php', - 'phpparser\\node\\stmt\\while_' => '/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php', - 'phpparser\\node\\varlikeidentifier' => '/vendor/nikic/php-parser/lib/PhpParser/Node/VarLikeIdentifier.php', - 'phpparser\\nodeabstract' => '/vendor/nikic/php-parser/lib/PhpParser/NodeAbstract.php', - 'phpparser\\nodedumper' => '/vendor/nikic/php-parser/lib/PhpParser/NodeDumper.php', - 'phpparser\\nodefinder' => '/vendor/nikic/php-parser/lib/PhpParser/NodeFinder.php', - 'phpparser\\nodetraverser' => '/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php', - 'phpparser\\nodetraverserinterface' => '/vendor/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php', - 'phpparser\\nodevisitor' => '/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor.php', - 'phpparser\\nodevisitor\\cloningvisitor' => '/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/CloningVisitor.php', - 'phpparser\\nodevisitor\\findingvisitor' => '/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FindingVisitor.php', - 'phpparser\\nodevisitor\\firstfindingvisitor' => '/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FirstFindingVisitor.php', - 'phpparser\\nodevisitor\\nameresolver' => '/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php', - 'phpparser\\nodevisitorabstract' => '/vendor/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php', - 'phpparser\\parser' => '/vendor/nikic/php-parser/lib/PhpParser/Parser.php', - 'phpparser\\parser\\multiple' => '/vendor/nikic/php-parser/lib/PhpParser/Parser/Multiple.php', - 'phpparser\\parser\\php5' => '/vendor/nikic/php-parser/lib/PhpParser/Parser/Php5.php', - 'phpparser\\parser\\php7' => '/vendor/nikic/php-parser/lib/PhpParser/Parser/Php7.php', - 'phpparser\\parser\\tokens' => '/vendor/nikic/php-parser/lib/PhpParser/Parser/Tokens.php', - 'phpparser\\parserabstract' => '/vendor/nikic/php-parser/lib/PhpParser/ParserAbstract.php', - 'phpparser\\parserfactory' => '/vendor/nikic/php-parser/lib/PhpParser/ParserFactory.php', - 'phpparser\\prettyprinter\\standard' => '/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinter/Standard.php', - 'phpparser\\prettyprinterabstract' => '/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php', - 'sebastianbergmann\\timer\\exception' => '/vendor/phpunit/php-timer/src/Exception.php', - 'sebastianbergmann\\timer\\runtimeexception' => '/vendor/phpunit/php-timer/src/RuntimeException.php', - 'sebastianbergmann\\timer\\timer' => '/vendor/phpunit/php-timer/src/Timer.php', - 'theseer\\directoryscanner\\directoryscanner' => '/vendor/theseer/directoryscanner/src/directoryscanner.php', - 'theseer\\directoryscanner\\exception' => '/vendor/theseer/directoryscanner/src/directoryscanner.php', - 'theseer\\directoryscanner\\filesonlyfilteriterator' => '/vendor/theseer/directoryscanner/src/filesonlyfilter.php', - 'theseer\\directoryscanner\\includeexcludefilteriterator' => '/vendor/theseer/directoryscanner/src/includeexcludefilter.php', - 'theseer\\directoryscanner\\phpfilteriterator' => '/vendor/theseer/directoryscanner/src/phpfilter.php', - 'theseer\\fdom\\css\\dollarequalrule' => '/vendor/theseer/fdomdocument/src/css/DollarEqualRule.php', - 'theseer\\fdom\\css\\notrule' => '/vendor/theseer/fdomdocument/src/css/NotRule.php', - 'theseer\\fdom\\css\\nthchildrule' => '/vendor/theseer/fdomdocument/src/css/NthChildRule.php', - 'theseer\\fdom\\css\\regexrule' => '/vendor/theseer/fdomdocument/src/css/RegexRule.php', - 'theseer\\fdom\\css\\ruleinterface' => '/vendor/theseer/fdomdocument/src/css/RuleInterface.php', - 'theseer\\fdom\\css\\translator' => '/vendor/theseer/fdomdocument/src/css/Translator.php', - 'theseer\\fdom\\fdomdocument' => '/vendor/theseer/fdomdocument/src/fDOMDocument.php', - 'theseer\\fdom\\fdomdocumentfragment' => '/vendor/theseer/fdomdocument/src/fDOMDocumentFragment.php', - 'theseer\\fdom\\fdomelement' => '/vendor/theseer/fdomdocument/src/fDOMElement.php', - 'theseer\\fdom\\fdomexception' => '/vendor/theseer/fdomdocument/src/fDOMException.php', - 'theseer\\fdom\\fdomnode' => '/vendor/theseer/fdomdocument/src/fDOMNode.php', - 'theseer\\fdom\\fdomxpath' => '/vendor/theseer/fdomdocument/src/fDOMXPath.php', - 'theseer\\fdom\\xpathquery' => '/vendor/theseer/fdomdocument/src/XPathQuery.php', - 'theseer\\fdom\\xpathqueryexception' => '/vendor/theseer/fdomdocument/src/XPathQueryException.php', - 'theseer\\fxsl\\fxslcallback' => '/vendor/theseer/fxsl/src/fxslcallback.php', - 'theseer\\fxsl\\fxsltprocessor' => '/vendor/theseer/fxsl/src/fxsltprocessor.php', - 'theseer\\fxsl\\fxsltprocessorexception' => '/vendor/theseer/fxsl/src/fxsltprocessor.php', - 'theseer\\phpdox\\application' => '/phpdox/Application.php', - 'theseer\\phpdox\\applicationexception' => '/phpdox/ApplicationException.php', - 'theseer\\phpdox\\backendbootstrapapi' => '/phpdox/bootstrap/BackendBootstrapApi.php', - 'theseer\\phpdox\\bootstrap' => '/phpdox/bootstrap/Bootstrap.php', - 'theseer\\phpdox\\bootstrapapi' => '/phpdox/bootstrap/BootstrapApi.php', - 'theseer\\phpdox\\bootstrapexception' => '/phpdox/bootstrap/BootstrapException.php', - 'theseer\\phpdox\\buildconfig' => '/phpdox/config/BuildConfig.php', - 'theseer\\phpdox\\cli' => '/phpdox/CLI.php', - 'theseer\\phpdox\\clioptions' => '/phpdox/CLIOptions.php', - 'theseer\\phpdox\\clioptionsexception' => '/phpdox/CLIOptionsException.php', - 'theseer\\phpdox\\collector\\abstractunitobject' => '/phpdox/collector/project/AbstractUnitObject.php', - 'theseer\\phpdox\\collector\\abstractvariableobject' => '/phpdox/collector/project/AbstractVariableObject.php', - 'theseer\\phpdox\\collector\\backend\\backendinterface' => '/phpdox/collector/backend/BackendInterface.php', - 'theseer\\phpdox\\collector\\backend\\customlexer' => '/phpdox/collector/backend/parser/CustomLexer.php', - 'theseer\\phpdox\\collector\\backend\\factory' => '/phpdox/collector/backend/Factory.php', - 'theseer\\phpdox\\collector\\backend\\factoryexception' => '/phpdox/collector/backend/Factory.php', - 'theseer\\phpdox\\collector\\backend\\parseerrorexception' => '/phpdox/collector/backend/ParseErrorException.php', - 'theseer\\phpdox\\collector\\backend\\parseresult' => '/phpdox/collector/backend/ParseResult.php', - 'theseer\\phpdox\\collector\\backend\\parseresultexception' => '/phpdox/collector/backend/ParseResultException.php', - 'theseer\\phpdox\\collector\\backend\\phpparser' => '/phpdox/collector/backend/parser/PHPParser.php', - 'theseer\\phpdox\\collector\\backend\\publiconlyvisitor' => '/phpdox/collector/backend/parser/PublicOnlyVisitor.php', - 'theseer\\phpdox\\collector\\backend\\sourcefileexception' => '/phpdox/collector/backend/SourceFileException.php', - 'theseer\\phpdox\\collector\\backend\\unitcollectingvisitor' => '/phpdox/collector/backend/parser/UnitCollectingVisitor.php', - 'theseer\\phpdox\\collector\\classobject' => '/phpdox/collector/project/ClassObject.php', - 'theseer\\phpdox\\collector\\collector' => '/phpdox/collector/Collector.php', - 'theseer\\phpdox\\collector\\collectorexception' => '/phpdox/collector/CollectorException.php', - 'theseer\\phpdox\\collector\\constantobject' => '/phpdox/collector/project/ConstantObject.php', - 'theseer\\phpdox\\collector\\dependency' => '/phpdox/collector/project/Dependency.php', - 'theseer\\phpdox\\collector\\dependencyexception' => '/phpdox/collector/project/DependencyException.php', - 'theseer\\phpdox\\collector\\indexcollection' => '/phpdox/collector/project/IndexCollection.php', - 'theseer\\phpdox\\collector\\inheritanceresolver' => '/phpdox/collector/InheritanceResolver.php', - 'theseer\\phpdox\\collector\\inlinecomment' => '/phpdox/collector/project/InlineComment.php', - 'theseer\\phpdox\\collector\\interfaceobject' => '/phpdox/collector/project/InterfaceObject.php', - 'theseer\\phpdox\\collector\\memberobject' => '/phpdox/collector/project/MemberObject.php', - 'theseer\\phpdox\\collector\\methodobject' => '/phpdox/collector/project/MethodObject.php', - 'theseer\\phpdox\\collector\\methodobjectexception' => '/phpdox/collector/project/MethodObjectException.php', - 'theseer\\phpdox\\collector\\parameterobject' => '/phpdox/collector/project/ParameterObject.php', - 'theseer\\phpdox\\collector\\project' => '/phpdox/collector/project/Project.php', - 'theseer\\phpdox\\collector\\projectexception' => '/phpdox/collector/project/ProjectException.php', - 'theseer\\phpdox\\collector\\returntypeobject' => '/phpdox/collector/project/ReturnTypeObject.php', - 'theseer\\phpdox\\collector\\sourcecollection' => '/phpdox/collector/project/SourceCollection.php', - 'theseer\\phpdox\\collector\\sourcecollectionexception' => '/phpdox/collector/project/SourceCollectionException.php', - 'theseer\\phpdox\\collector\\sourcefile' => '/phpdox/collector/project/SourceFile.php', - 'theseer\\phpdox\\collector\\sourcefileiterator' => '/phpdox/collector/SourceFileIterator.php', - 'theseer\\phpdox\\collector\\tokenizer' => '/phpdox/collector/project/Tokenizer.php', - 'theseer\\phpdox\\collector\\traitobject' => '/phpdox/collector/project/TraitObject.php', - 'theseer\\phpdox\\collector\\traituseexception' => '/phpdox/collector/project/TraitUseException.php', - 'theseer\\phpdox\\collector\\traituseobject' => '/phpdox/collector/project/TraitUseObject.php', - 'theseer\\phpdox\\collector\\unitobjectexception' => '/phpdox/collector/project/UnitObjectException.php', - 'theseer\\phpdox\\collectorconfig' => '/phpdox/config/CollectorConfig.php', - 'theseer\\phpdox\\configexception' => '/phpdox/config/ConfigException.php', - 'theseer\\phpdox\\configloader' => '/phpdox/config/ConfigLoader.php', - 'theseer\\phpdox\\configloaderexception' => '/phpdox/config/ConfigLoaderException.php', - 'theseer\\phpdox\\configskeleton' => '/phpdox/config/ConfigSkeleton.php', - 'theseer\\phpdox\\directorycleaner' => '/phpdox/shared/DirectoryCleaner.php', - 'theseer\\phpdox\\directorycleanerexception' => '/phpdox/shared/DirectoryCleanerException.php', - 'theseer\\phpdox\\docblock\\descriptionparser' => '/phpdox/docblock/parser/DescriptionParser.php', - 'theseer\\phpdox\\docblock\\docblock' => '/phpdox/docblock/DocBlock.php', - 'theseer\\phpdox\\docblock\\docblockexception' => '/phpdox/docblock/DocBlockException.php', - 'theseer\\phpdox\\docblock\\factory' => '/phpdox/docblock/Factory.php', - 'theseer\\phpdox\\docblock\\factoryexception' => '/phpdox/docblock/FactoryException.php', - 'theseer\\phpdox\\docblock\\genericelement' => '/phpdox/docblock/elements/GenericElement.php', - 'theseer\\phpdox\\docblock\\genericelementexception' => '/phpdox/docblock/elements/GenericElementException.php', - 'theseer\\phpdox\\docblock\\genericparser' => '/phpdox/docblock/parser/GenericParser.php', - 'theseer\\phpdox\\docblock\\inheritdocattribute' => '/phpdox/docblock/elements/InheritdocAttribute.php', - 'theseer\\phpdox\\docblock\\inheritdocparser' => '/phpdox/docblock/parser/InheritdocParser.php', - 'theseer\\phpdox\\docblock\\inlineprocessor' => '/phpdox/docblock/InlineProcessor.php', - 'theseer\\phpdox\\docblock\\internalparser' => '/phpdox/docblock/parser/InternalParser.php', - 'theseer\\phpdox\\docblock\\invalidelement' => '/phpdox/docblock/elements/InvalidElement.php', - 'theseer\\phpdox\\docblock\\invalidparser' => '/phpdox/docblock/parser/InvalidParser.php', - 'theseer\\phpdox\\docblock\\licenseparser' => '/phpdox/docblock/parser/LicenseParser.php', - 'theseer\\phpdox\\docblock\\linkparser' => '/phpdox/docblock/parser/LinkParser.php', - 'theseer\\phpdox\\docblock\\paramparser' => '/phpdox/docblock/parser/ParamParser.php', - 'theseer\\phpdox\\docblock\\parser' => '/phpdox/docblock/Parser.php', - 'theseer\\phpdox\\docblock\\varelement' => '/phpdox/docblock/elements/VarElement.php', - 'theseer\\phpdox\\docblock\\varparser' => '/phpdox/docblock/parser/VarParser.php', - 'theseer\\phpdox\\enginebootstrapapi' => '/phpdox/bootstrap/EngineBootstrapApi.php', - 'theseer\\phpdox\\enrichconfig' => '/phpdox/config/EnrichConfig.php', - 'theseer\\phpdox\\enricherbootstrapapi' => '/phpdox/bootstrap/EnricherBootstrapApi.php', - 'theseer\\phpdox\\environment' => '/phpdox/shared/Environment.php', - 'theseer\\phpdox\\environmentexception' => '/phpdox/shared/EnvironmentException.php', - 'theseer\\phpdox\\errorexception' => '/phpdox/shared/ErrorException.php', - 'theseer\\phpdox\\errorhandler' => '/phpdox/shared/ErrorHandler.php', - 'theseer\\phpdox\\factory' => '/phpdox/shared/Factory.php', - 'theseer\\phpdox\\factoryexception' => '/phpdox/shared/FactoryException.php', - 'theseer\\phpdox\\factoryinterface' => '/phpdox/shared/FactoryInterface.php', - 'theseer\\phpdox\\fileinfo' => '/phpdox/shared/FileInfo.php', - 'theseer\\phpdox\\fileinfocollection' => '/phpdox/shared/FileInfoCollection.php', - 'theseer\\phpdox\\fileinfocollectionexception' => '/phpdox/shared/FileInfoCollectionException.php', - 'theseer\\phpdox\\fileinfoexception' => '/phpdox/shared/FileInfoException.php', - 'theseer\\phpdox\\generator\\abstractclassevent' => '/phpdox/generator/events/AbstractClassEvent.php', - 'theseer\\phpdox\\generator\\abstractcollection' => '/phpdox/generator/project/collections/AbstractCollection.php', - 'theseer\\phpdox\\generator\\abstractentry' => '/phpdox/generator/project/entries/AbstractEntry.php', - 'theseer\\phpdox\\generator\\abstractevent' => '/phpdox/generator/AbstractEvent.php', - 'theseer\\phpdox\\generator\\abstractunitobject' => '/phpdox/generator/project/objects/AbstractUnitObject.php', - 'theseer\\phpdox\\generator\\classcollection' => '/phpdox/generator/project/collections/ClassCollection.php', - 'theseer\\phpdox\\generator\\classconstantevent' => '/phpdox/generator/events/ClassConstantEvent.php', - 'theseer\\phpdox\\generator\\classendevent' => '/phpdox/generator/events/ClassEndEvent.php', - 'theseer\\phpdox\\generator\\classentry' => '/phpdox/generator/project/entries/ClassEntry.php', - 'theseer\\phpdox\\generator\\classmemberevent' => '/phpdox/generator/events/ClassMemberEvent.php', - 'theseer\\phpdox\\generator\\classmethodevent' => '/phpdox/generator/events/ClassMethodEvent.php', - 'theseer\\phpdox\\generator\\classobject' => '/phpdox/generator/project/objects/ClassObject.php', - 'theseer\\phpdox\\generator\\classstartevent' => '/phpdox/generator/events/ClassStartEvent.php', - 'theseer\\phpdox\\generator\\constantcollection' => '/phpdox/generator/project/collections/ConstantCollection.php', - 'theseer\\phpdox\\generator\\constantevent' => '/phpdox/generator/events/ConstantEvent.php', - 'theseer\\phpdox\\generator\\constantobject' => '/phpdox/generator/project/objects/ConstantObject.php', - 'theseer\\phpdox\\generator\\engine\\abstractengine' => '/phpdox/generator/engine/AbstractEngine.php', - 'theseer\\phpdox\\generator\\engine\\engineexception' => '/phpdox/generator/engine/AbstractEngine.php', - 'theseer\\phpdox\\generator\\engine\\engineinterface' => '/phpdox/generator/engine/EngineInterface.php', - 'theseer\\phpdox\\generator\\engine\\eventhandlerregistry' => '/phpdox/generator/engine/EventHandlerRegistry.php', - 'theseer\\phpdox\\generator\\engine\\eventhandlerregistryexception' => '/phpdox/generator/engine/EventHandlerRegistry.php', - 'theseer\\phpdox\\generator\\engine\\factory' => '/phpdox/generator/engine/Factory.php', - 'theseer\\phpdox\\generator\\engine\\factoryexception' => '/phpdox/generator/engine/Factory.php', - 'theseer\\phpdox\\generator\\engine\\html' => '/phpdox/generator/engine/html/Html.php', - 'theseer\\phpdox\\generator\\engine\\htmlconfig' => '/phpdox/generator/engine/html/HtmlConfig.php', - 'theseer\\phpdox\\generator\\engine\\xml' => '/phpdox/generator/engine/xml/Xml.php', - 'theseer\\phpdox\\generator\\engine\\xmlengineexception' => '/phpdox/generator/engine/xml/Xml.php', - 'theseer\\phpdox\\generator\\enricher\\abstractenricher' => '/phpdox/generator/enricher/AbstractEnricher.php', - 'theseer\\phpdox\\generator\\enricher\\build' => '/phpdox/generator/enricher/build/Build.php', - 'theseer\\phpdox\\generator\\enricher\\checkstyle' => '/phpdox/generator/enricher/checkstyle/CheckStyle.php', - 'theseer\\phpdox\\generator\\enricher\\checkstyleconfig' => '/phpdox/generator/enricher/checkstyle/CheckStyleConfig.php', - 'theseer\\phpdox\\generator\\enricher\\classenricherinterface' => '/phpdox/generator/enricher/ClassEnricherInterface.php', - 'theseer\\phpdox\\generator\\enricher\\endenricherinterface' => '/phpdox/generator/enricher/EndEnricherInterface.php', - 'theseer\\phpdox\\generator\\enricher\\enricherexception' => '/phpdox/generator/enricher/EnricherException.php', - 'theseer\\phpdox\\generator\\enricher\\enricherinterface' => '/phpdox/generator/enricher/EnricherInterface.php', - 'theseer\\phpdox\\generator\\enricher\\factory' => '/phpdox/generator/enricher/Factory.php', - 'theseer\\phpdox\\generator\\enricher\\factoryexception' => '/phpdox/generator/enricher/Factory.php', - 'theseer\\phpdox\\generator\\enricher\\fullenricherinterface' => '/phpdox/generator/enricher/FullEnricherInterface.php', - 'theseer\\phpdox\\generator\\enricher\\git' => '/phpdox/generator/enricher/git/Git.php', - 'theseer\\phpdox\\generator\\enricher\\gitconfig' => '/phpdox/generator/enricher/git/GitConfig.php', - 'theseer\\phpdox\\generator\\enricher\\gitenricherexception' => '/phpdox/generator/enricher/git/GitEnricherException.php', - 'theseer\\phpdox\\generator\\enricher\\interfaceenricherinterface' => '/phpdox/generator/enricher/InterfaceEnricherInterface.php', - 'theseer\\phpdox\\generator\\enricher\\phpcs' => '/phpdox/generator/enricher/phpcs/PHPCs.php', - 'theseer\\phpdox\\generator\\enricher\\phpcsconfig' => '/phpdox/generator/enricher/phpcs/PHPCsConfig.php', - 'theseer\\phpdox\\generator\\enricher\\phploc' => '/phpdox/generator/enricher/phploc/PHPLoc.php', - 'theseer\\phpdox\\generator\\enricher\\phplocconfig' => '/phpdox/generator/enricher/phploc/PHPLocConfig.php', - 'theseer\\phpdox\\generator\\enricher\\phpmessdetector' => '/phpdox/generator/enricher/pmd/PHPMessDetector.php', - 'theseer\\phpdox\\generator\\enricher\\phpmessdetectorconfig' => '/phpdox/generator/enricher/pmd/PHPMessDetectorConfig.php', - 'theseer\\phpdox\\generator\\enricher\\phpunit' => '/phpdox/generator/enricher/phpunit/PHPUnit.php', - 'theseer\\phpdox\\generator\\enricher\\phpunitconfig' => '/phpdox/generator/enricher/phpunit/PHPUnitConfig.php', - 'theseer\\phpdox\\generator\\enricher\\phpunitenricherexception' => '/phpdox/generator/enricher/phpunit/PHPUnit.php', - 'theseer\\phpdox\\generator\\enricher\\startenricherinterface' => '/phpdox/generator/enricher/StartEnricherInterface.php', - 'theseer\\phpdox\\generator\\enricher\\tokenfileenricherinterface' => '/phpdox/generator/enricher/TokenFileEnricherInterface.php', - 'theseer\\phpdox\\generator\\enricher\\traitenricherinterface' => '/phpdox/generator/enricher/TraitEnricherInterface.php', - 'theseer\\phpdox\\generator\\generator' => '/phpdox/generator/Generator.php', - 'theseer\\phpdox\\generator\\generatorexception' => '/phpdox/generator/Generator.php', - 'theseer\\phpdox\\generator\\index' => '/phpdox/generator/project/Index.php', - 'theseer\\phpdox\\generator\\inlinecommentcollection' => '/phpdox/generator/project/collections/InlineCommentCollection.php', - 'theseer\\phpdox\\generator\\inlinecommentobject' => '/phpdox/generator/project/objects/InlineCommentObject.php', - 'theseer\\phpdox\\generator\\interfacecollection' => '/phpdox/generator/project/collections/InterfaceCollection.php', - 'theseer\\phpdox\\generator\\interfaceconstantevent' => '/phpdox/generator/events/InterfaceConstantEvent.php', - 'theseer\\phpdox\\generator\\interfaceendevent' => '/phpdox/generator/events/InterfaceEndEvent.php', - 'theseer\\phpdox\\generator\\interfaceentry' => '/phpdox/generator/project/entries/InterfaceEntry.php', - 'theseer\\phpdox\\generator\\interfacemethodevent' => '/phpdox/generator/events/InterfaceMethodEvent.php', - 'theseer\\phpdox\\generator\\interfaceobject' => '/phpdox/generator/project/objects/InterfaceObject.php', - 'theseer\\phpdox\\generator\\interfacestartevent' => '/phpdox/generator/events/InterfaceStartEvent.php', - 'theseer\\phpdox\\generator\\membercollection' => '/phpdox/generator/project/collections/MemberCollection.php', - 'theseer\\phpdox\\generator\\memberevent' => '/phpdox/generator/events/MemberEvent.php', - 'theseer\\phpdox\\generator\\memberobject' => '/phpdox/generator/project/objects/MemberObject.php', - 'theseer\\phpdox\\generator\\methodcollection' => '/phpdox/generator/project/collections/MethodCollection.php', - 'theseer\\phpdox\\generator\\methodevent' => '/phpdox/generator/events/MethodEvent.php', - 'theseer\\phpdox\\generator\\methodobject' => '/phpdox/generator/project/objects/MethodObject.php', - 'theseer\\phpdox\\generator\\namespaceclassesendevent' => '/phpdox/generator/events/NamespaceClassesEndEvent.php', - 'theseer\\phpdox\\generator\\namespaceclassesstartevent' => '/phpdox/generator/events/NamespaceClassesStartEvent.php', - 'theseer\\phpdox\\generator\\namespacecollection' => '/phpdox/generator/project/collections/NamespaceCollection.php', - 'theseer\\phpdox\\generator\\namespaceendevent' => '/phpdox/generator/events/NamespaceEndEvent.php', - 'theseer\\phpdox\\generator\\namespaceentry' => '/phpdox/generator/project/entries/NamespaceEntry.php', - 'theseer\\phpdox\\generator\\namespaceinterfacesendevent' => '/phpdox/generator/events/NamespaceInterfacesEndEvent.php', - 'theseer\\phpdox\\generator\\namespaceinterfacesstartevent' => '/phpdox/generator/events/NamespaceInterfacesStartEvent.php', - 'theseer\\phpdox\\generator\\namespacestartevent' => '/phpdox/generator/events/NamespaceStartEvent.php', - 'theseer\\phpdox\\generator\\namespacetraitsendevent' => '/phpdox/generator/events/NamespaceTraitsEndEvent.php', - 'theseer\\phpdox\\generator\\namespacetraitsstartevent' => '/phpdox/generator/events/NamespaceTraitsStartEvent.php', - 'theseer\\phpdox\\generator\\phpdoxclassesendevent' => '/phpdox/generator/events/PHPDoxClassesEndEvent.php', - 'theseer\\phpdox\\generator\\phpdoxclassesstartevent' => '/phpdox/generator/events/PHPDoxClassesStartEvent.php', - 'theseer\\phpdox\\generator\\phpdoxendevent' => '/phpdox/generator/events/PHPDoxEndEvent.php', - 'theseer\\phpdox\\generator\\phpdoxinterfacesendevent' => '/phpdox/generator/events/PHPDoxInterfacesEndEvent.php', - 'theseer\\phpdox\\generator\\phpdoxinterfacesstartevent' => '/phpdox/generator/events/PHPDoxInterfacesStartEvent.php', - 'theseer\\phpdox\\generator\\phpdoxnamespacesendevent' => '/phpdox/generator/events/PHPDoxNamespacesEndEvent.php', - 'theseer\\phpdox\\generator\\phpdoxnamespacesstartevent' => '/phpdox/generator/events/PHPDoxNamespacesStartEvent.php', - 'theseer\\phpdox\\generator\\phpdoxstartevent' => '/phpdox/generator/events/PHPDoxStartEvent.php', - 'theseer\\phpdox\\generator\\phpdoxtraitsendevent' => '/phpdox/generator/events/PHPDoxTraitsEndEvent.php', - 'theseer\\phpdox\\generator\\phpdoxtraitsstartevent' => '/phpdox/generator/events/PHPDoxTraitsStartEvent.php', - 'theseer\\phpdox\\generator\\project' => '/phpdox/generator/project/Project.php', - 'theseer\\phpdox\\generator\\projectexception' => '/phpdox/generator/project/Project.php', - 'theseer\\phpdox\\generator\\sourcetree' => '/phpdox/generator/project/SourceTree.php', - 'theseer\\phpdox\\generator\\tokenevent' => '/phpdox/generator/events/TokenEvent.php', - 'theseer\\phpdox\\generator\\tokenfile' => '/phpdox/generator/project/TokenFile.php', - 'theseer\\phpdox\\generator\\tokenfileendevent' => '/phpdox/generator/events/TokenFileEndEvent.php', - 'theseer\\phpdox\\generator\\tokenfileexception' => '/phpdox/generator/project/TokenFile.php', - 'theseer\\phpdox\\generator\\tokenfileiterator' => '/phpdox/generator/project/TokenFileIterator.php', - 'theseer\\phpdox\\generator\\tokenfilestartevent' => '/phpdox/generator/events/TokenFileStartEvent.php', - 'theseer\\phpdox\\generator\\tokenlineendevent' => '/phpdox/generator/events/TokenLineEndEvent.php', - 'theseer\\phpdox\\generator\\tokenlinestartevent' => '/phpdox/generator/events/TokenLineStartEvent.php', - 'theseer\\phpdox\\generator\\traitcollection' => '/phpdox/generator/project/collections/TraitCollection.php', - 'theseer\\phpdox\\generator\\traitconstantevent' => '/phpdox/generator/events/TraitConstantEvent.php', - 'theseer\\phpdox\\generator\\traitendevent' => '/phpdox/generator/events/TraitEndEvent.php', - 'theseer\\phpdox\\generator\\traitentry' => '/phpdox/generator/project/entries/TraitEntry.php', - 'theseer\\phpdox\\generator\\traitmemberevent' => '/phpdox/generator/events/TraitMemberEvent.php', - 'theseer\\phpdox\\generator\\traitmethodevent' => '/phpdox/generator/events/TraitMethodEvent.php', - 'theseer\\phpdox\\generator\\traitobject' => '/phpdox/generator/project/objects/TraitObject.php', - 'theseer\\phpdox\\generator\\traitstartevent' => '/phpdox/generator/events/TraitStartEvent.php', - 'theseer\\phpdox\\generatorconfig' => '/phpdox/config/GeneratorConfig.php', - 'theseer\\phpdox\\generatorconfigexception' => '/phpdox/config/GeneratorConfigException.php', - 'theseer\\phpdox\\globalconfig' => '/phpdox/config/GlobalConfig.php', - 'theseer\\phpdox\\hasfileinfoexception' => '/phpdox/shared/HasFileInfoException.php', - 'theseer\\phpdox\\inheritanceconfig' => '/phpdox/config/InheritanceConfig.php', - 'theseer\\phpdox\\parserbootstrapapi' => '/phpdox/bootstrap/ParserBootstrapApi.php', - 'theseer\\phpdox\\progresslogger' => '/phpdox/logger/ProgressLogger.php', - 'theseer\\phpdox\\progressloggerexception' => '/phpdox/logger/ProgressLoggerException.php', - 'theseer\\phpdox\\projectconfig' => '/phpdox/config/ProjectConfig.php', - 'theseer\\phpdox\\shellprogresslogger' => '/phpdox/logger/ShellProgressLogger.php', - 'theseer\\phpdox\\silentprogresslogger' => '/phpdox/logger/SilentProgressLogger.php', - 'theseer\\phpdox\\version' => '/phpdox/shared/Version.php' - ); - } - - $class = strtolower($class); - - if (isset($classes[$class])) { - require 'phar://phpdox.phar/' . $classes[$class]; - } - } -); - -Phar::mapPhar('phpdox.phar'); -define('PHPDOX_PHAR', 'phpdox.phar'); - -require 'phar://phpdox.phar/phpdox/runtimecheck.php'; - -$factory = new TheSeer\phpDox\Factory( - new \TheSeer\phpDox\FileInfo('phar://' . PHPDOX_PHAR), - new \TheSeer\phpDox\Version('0.12.0') -); -$factory->getCLI()->run( - new TheSeer\phpDox\CLIOptions($_SERVER['argv']) -); - -exit(0); - -__HALT_COMPILER(); ?> -�\� phpdox.pharphpdox/Application.php.ψ\��`8�phpdox/ApplicationException.php-ψ\��� �phpdox/CLI.phpψ\�����phpdox/CLIOptions.phpψ\��Wㆶphpdox/CLIOptionsException.phpmψ\a��!�(phpdox/bootstrap/BackendBootstrapApi.php�ψ\�0�.�phpdox/bootstrap/Bootstrap.phpψ\1��P!�!phpdox/bootstrap/BootstrapApi.php�ψ\�(8ն'phpdox/bootstrap/BootstrapException.php�ψ\y��G�'phpdox/bootstrap/EngineBootstrapApi.phpψ\�[���)phpdox/bootstrap/EnricherBootstrapApi.phpψ\ ��nF�'phpdox/bootstrap/ParserBootstrapApi.php}ψ\c՟�phpdox/collector/Collector.php�ψ\��Q��'phpdox/collector/CollectorException.php�ψ\�T��&�(phpdox/collector/InheritanceResolver.php�#ψ\0���'phpdox/collector/SourceFileIterator.php�ψ\E�����-phpdox/collector/backend/BackendInterface.phpψ\��8[7�$phpdox/collector/backend/Factory.php*ψ\[W�0�0phpdox/collector/backend/ParseErrorException.php�ψ\�YJ�w�(phpdox/collector/backend/ParseResult.php�ψ\����ɶ1phpdox/collector/backend/ParseResultException.php�ψ\��لh�0phpdox/collector/backend/SourceFileException.php�ψ\�,B��/phpdox/collector/backend/parser/CustomLexer.phplψ\���f��-phpdox/collector/backend/parser/PHPParser.php�ψ\��JOU�5phpdox/collector/backend/parser/PublicOnlyVisitor.php@ψ\1�&�9phpdox/collector/backend/parser/UnitCollectingVisitor.php\Bψ\� YMiw�/phpdox/collector/project/AbstractUnitObject.phpdCψ\& /y�L�3phpdox/collector/project/AbstractVariableObject.phpl ψ\���r1�(phpdox/collector/project/ClassObject.php�ψ\�����+phpdox/collector/project/ConstantObject.phpYψ\���䡶'phpdox/collector/project/Dependency.phpg -ψ\A3��G�0phpdox/collector/project/DependencyException.php�ψ\��[���,phpdox/collector/project/IndexCollection.php� ψ\����ݶ*phpdox/collector/project/InlineComment.phpA -ψ\n���s�,phpdox/collector/project/InterfaceObject.php ψ\,R�/��)phpdox/collector/project/MemberObject.php ψ\�D5 �)phpdox/collector/project/MethodObject.php�ψ\RrJ��2phpdox/collector/project/MethodObjectException.php�ψ\�Q��϶,phpdox/collector/project/ParameterObject.phpψ\����g�$phpdox/collector/project/Project.php�(ψ\ Fe�}�-phpdox/collector/project/ProjectException.phpψ\���+��-phpdox/collector/project/ReturnTypeObject.php$ψ\��Uo�-phpdox/collector/project/SourceCollection.php�ψ\1ɂ���6phpdox/collector/project/SourceCollectionException.php�ψ\�V���'phpdox/collector/project/SourceFile.php+ ψ\/.wY�&phpdox/collector/project/Tokenizer.phpψ\�}L�(phpdox/collector/project/TraitObject.php�ψ\(�- ��.phpdox/collector/project/TraitUseException.php�ψ\|L?�Ŷ+phpdox/collector/project/TraitUseObject.php� ψ\�[.?�0phpdox/collector/project/UnitObjectException.phpuψ\�.Ƭ�phpdox/config/BuildConfig.phpψ\�K��ɶ!phpdox/config/CollectorConfig.php�ψ\>䚋�!phpdox/config/ConfigException.phpaψ\�����phpdox/config/ConfigLoader.php� ψ\r�W�m�'phpdox/config/ConfigLoaderException.php%ψ\���7�� phpdox/config/ConfigSkeleton.phpψ\�.�M��phpdox/config/EnrichConfig.php�ψ\txGPZ�!phpdox/config/GeneratorConfig.phpAψ\q�P�_�*phpdox/config/GeneratorConfigException.php�ψ\~�����phpdox/config/GlobalConfig.php�ψ\~h�q��#phpdox/config/InheritanceConfig.php�ψ\� S��phpdox/config/ProjectConfig.php�ψ\VȁP~�phpdox/config/skeleton.xmlYψ\��걶phpdox/docblock/DocBlock.php�ψ\��3��%phpdox/docblock/DocBlockException.php�ψ\|O��phpdox/docblock/Factory.php�ψ\ �/Ӷ$phpdox/docblock/FactoryException.php�ψ\�����#phpdox/docblock/InlineProcessor.php�ψ\,qxP�phpdox/docblock/Parser.php -ψ\Y{zն+phpdox/docblock/elements/GenericElement.phpψ\s�f��4phpdox/docblock/elements/GenericElementException.php�ψ\��� -�0phpdox/docblock/elements/InheritdocAttribute.php@ψ\�:�3�+phpdox/docblock/elements/InvalidElement.php\ψ\?4 ��'phpdox/docblock/elements/VarElement.php;ψ\���(�,phpdox/docblock/parser/DescriptionParser.phpψ\+���Ѷ(phpdox/docblock/parser/GenericParser.php�ψ\�ߛ�2�+phpdox/docblock/parser/InheritdocParser.php�ψ\�ΙS�)phpdox/docblock/parser/InternalParser.phpψ\�S�o=�(phpdox/docblock/parser/InvalidParser.phpψ\���(phpdox/docblock/parser/LicenseParser.phpψ\�|���%phpdox/docblock/parser/LinkParser.php�ψ\;ҍ޶&phpdox/docblock/parser/ParamParser.phpψ\�����$phpdox/docblock/parser/VarParser.php�ψ\SW����"phpdox/generator/AbstractEvent.phpψ\�tn�P�phpdox/generator/Generator.php{&ψ\������*phpdox/generator/engine/AbstractEngine.php� ψ\�,;G�+phpdox/generator/engine/EngineInterface.php�ψ\�2��0phpdox/generator/engine/EventHandlerRegistry.php ψ\��H�#phpdox/generator/engine/Factory.php�ψ\�bS7D�%phpdox/generator/engine/html/Html.php�&ψ\�}����+phpdox/generator/engine/html/HtmlConfig.php(ψ\�����#phpdox/generator/engine/xml/Xml.php -ψ\�f4��.phpdox/generator/enricher/AbstractEnricher.php�ψ\��A���4phpdox/generator/enricher/ClassEnricherInterface.php�ψ\�Ū ��2phpdox/generator/enricher/EndEnricherInterface.php�ψ\�}�|�/phpdox/generator/enricher/EnricherException.php�ψ\}�Qz �/phpdox/generator/enricher/EnricherInterface.php�ψ\}����%phpdox/generator/enricher/Factory.php�ψ\�@>f0�3phpdox/generator/enricher/FullEnricherInterface.phpψ\���8phpdox/generator/enricher/InterfaceEnricherInterface.phpψ\�����4phpdox/generator/enricher/StartEnricherInterface.php�ψ\���w+�8phpdox/generator/enricher/TokenFileEnricherInterface.phpψ\�|(�$�4phpdox/generator/enricher/TraitEnricherInterface.php�ψ\����)phpdox/generator/enricher/build/Build.phpGψ\C��r�3phpdox/generator/enricher/checkstyle/CheckStyle.php�ψ\� �˶9phpdox/generator/enricher/checkstyle/CheckStyleConfig.php�ψ\����ٶ%phpdox/generator/enricher/git/Git.php$.ψ\� s�U��+phpdox/generator/enricher/git/GitConfig.php^ψ\� ɻ��6phpdox/generator/enricher/git/GitEnricherException.phpψ\��A*�)phpdox/generator/enricher/phpcs/PHPCs.phpxψ\_��>I�/phpdox/generator/enricher/phpcs/PHPCsConfig.php�ψ\�H��s�+phpdox/generator/enricher/phploc/PHPLoc.php(ψ\�Ns�d�1phpdox/generator/enricher/phploc/PHPLocConfig.phpCψ\�y�D��-phpdox/generator/enricher/phpunit/PHPUnit.php�$ψ\( �(�f�3phpdox/generator/enricher/phpunit/PHPUnitConfig.php�ψ\ ���{�1phpdox/generator/enricher/pmd/PHPMessDetector.php� ψ\��/�7phpdox/generator/enricher/pmd/PHPMessDetectorConfig.phpIψ\���ZH�.phpdox/generator/events/AbstractClassEvent.php]ψ\��]�.phpdox/generator/events/ClassConstantEvent.php�ψ\�w�)phpdox/generator/events/ClassEndEvent.phptψ\�vZ��,phpdox/generator/events/ClassMemberEvent.php�ψ\�2�l��,phpdox/generator/events/ClassMethodEvent.php�ψ\�t��?�+phpdox/generator/events/ClassStartEvent.phpxψ\��a�d�)phpdox/generator/events/ConstantEvent.phpGψ\���9�2phpdox/generator/events/InterfaceConstantEvent.php�ψ\��'���-phpdox/generator/events/InterfaceEndEvent.php�ψ\��WO�0phpdox/generator/events/InterfaceMethodEvent.php�ψ\�;��/phpdox/generator/events/InterfaceStartEvent.php�ψ\��Ws�'phpdox/generator/events/MemberEvent.php7ψ\�Uf�#�'phpdox/generator/events/MethodEvent.php7ψ\�}v59�4phpdox/generator/events/NamespaceClassesEndEvent.php6ψ\�RD��6phpdox/generator/events/NamespaceClassesStartEvent.php:ψ\��C隶-phpdox/generator/events/NamespaceEndEvent.php�ψ\��|EǶ7phpdox/generator/events/NamespaceInterfacesEndEvent.phpQψ\�p�L�9phpdox/generator/events/NamespaceInterfacesStartEvent.phpUψ\�M㩴�/phpdox/generator/events/NamespaceStartEvent.php�ψ\���sJ�3phpdox/generator/events/NamespaceTraitsEndEvent.php/ψ\��L��5phpdox/generator/events/NamespaceTraitsStartEvent.php3ψ\�hn�1phpdox/generator/events/PHPDoxClassesEndEvent.php�ψ\�P��'�3phpdox/generator/events/PHPDoxClassesStartEvent.php�ψ\��?9T�*phpdox/generator/events/PHPDoxEndEvent.php�ψ\���O��4phpdox/generator/events/PHPDoxInterfacesEndEvent.php�ψ\����Ķ6phpdox/generator/events/PHPDoxInterfacesStartEvent.php�ψ\��Ä�4phpdox/generator/events/PHPDoxNamespacesEndEvent.php�ψ\��<ٶ6phpdox/generator/events/PHPDoxNamespacesStartEvent.php�ψ\��#�Զ,phpdox/generator/events/PHPDoxStartEvent.php�ψ\����0phpdox/generator/events/PHPDoxTraitsEndEvent.php�ψ\��^z�2phpdox/generator/events/PHPDoxTraitsStartEvent.php�ψ\��o�=�&phpdox/generator/events/TokenEvent.php�ψ\��bK��-phpdox/generator/events/TokenFileEndEvent.php�ψ\����/phpdox/generator/events/TokenFileStartEvent.php�ψ\�)�H�-phpdox/generator/events/TokenLineEndEvent.php�ψ\� ]亶/phpdox/generator/events/TokenLineStartEvent.php�ψ\���?8�.phpdox/generator/events/TraitConstantEvent.php�ψ\�p3�R�)phpdox/generator/events/TraitEndEvent.phptψ\�_��j�,phpdox/generator/events/TraitMemberEvent.php�ψ\��Y�S�,phpdox/generator/events/TraitMethodEvent.php�ψ\��V8�+phpdox/generator/events/TraitStartEvent.phpxψ\�6y��"phpdox/generator/project/Index.php�ψ\X���$phpdox/generator/project/Project.phpb -ψ\��R̶'phpdox/generator/project/SourceTree.phpψ\���׶&phpdox/generator/project/TokenFile.php�ψ\^i$�.phpdox/generator/project/TokenFileIterator.php�ψ\��w�A�;phpdox/generator/project/collections/AbstractCollection.phptψ\7�gr�8phpdox/generator/project/collections/ClassCollection.php�ψ\� �8w�;phpdox/generator/project/collections/ConstantCollection.php�ψ\���4�@phpdox/generator/project/collections/InlineCommentCollection.php�ψ\�d�X�<phpdox/generator/project/collections/InterfaceCollection.php�ψ\�UM��9phpdox/generator/project/collections/MemberCollection.php�ψ\������9phpdox/generator/project/collections/MethodCollection.php�ψ\�����<phpdox/generator/project/collections/NamespaceCollection.php�ψ\�nуO�8phpdox/generator/project/collections/TraitCollection.php�ψ\��wp��2phpdox/generator/project/entries/AbstractEntry.php1ψ\t)�Ģ�/phpdox/generator/project/entries/ClassEntry.php�ψ\������3phpdox/generator/project/entries/InterfaceEntry.php�ψ\�]� �3phpdox/generator/project/entries/NamespaceEntry.phpAψ\� z��/phpdox/generator/project/entries/TraitEntry.php�ψ\�� +�7phpdox/generator/project/objects/AbstractUnitObject.phpiψ\���) �0phpdox/generator/project/objects/ClassObject.php�ψ\�v� ��3phpdox/generator/project/objects/ConstantObject.php�ψ\�+�:!�8phpdox/generator/project/objects/InlineCommentObject.phpXψ\��G轶4phpdox/generator/project/objects/InterfaceObject.php{ψ\m�O^I�1phpdox/generator/project/objects/MemberObject.php�ψ\ , ��1phpdox/generator/project/objects/MethodObject.phpψ\-�OF�0phpdox/generator/project/objects/TraitObject.phppψ\h�gѶ phpdox/logger/ProgressLogger.php�ψ\�H�7<�)phpdox/logger/ProgressLoggerException.php�ψ\}/1�%�%phpdox/logger/ShellProgressLogger.php�ψ\�*�׶&phpdox/logger/SilentProgressLogger.php�ψ\�=֙L�phpdox/runtimecheck.php�ψ\j�Tw�"phpdox/shared/DirectoryCleaner.php�ψ\�J o�+phpdox/shared/DirectoryCleanerException.php�ψ\����ֶphpdox/shared/Environment.phpqψ\�'P!�&phpdox/shared/EnvironmentException.php�ψ\��b�� phpdox/shared/ErrorException.phpψ\_uQ0�phpdox/shared/ErrorHandler.php�ψ\�q�/��phpdox/shared/Factory.php -ψ\�� �y�"phpdox/shared/FactoryException.php�ψ\��FB��"phpdox/shared/FactoryInterface.php�ψ\vֵ��phpdox/shared/FileInfo.php, ψ\��X�ض$phpdox/shared/FileInfoCollection.php�ψ\~�MX�-phpdox/shared/FileInfoCollectionException.phpuψ\j>_k��#phpdox/shared/FileInfoException.php�ψ\�דא�&phpdox/shared/HasFileInfoException.php�ψ\��ț�phpdox/shared/Version.php�ψ\k�#:��bootstrap/backends.phpzψ\��y���bootstrap/engines.php�ψ\1�E�bootstrap/enrichers.php^ ψ\�T���templates/html/class.xsli ψ\#Q��:�templates/html/components.xsl�Nψ\� -쮡K�templates/html/directory.xsl�ψ\�%zնtemplates/html/functions.xsl�ψ\�y/ �templates/html/index.xslKDψ\]��templates/html/interface.xsl�ψ\�� �templates/html/method.xsla9ψ\� ��pZ�templates/html/namespaces.xsl ψ\\ !B� templates/html/reports/index.xslψ\���W�templates/html/source.xsl�ψ\�o�{6�3templates/html/static/css/SourceSansPro-Regular.ttf�zψ\/��"��$templates/html/static/css/source.css� -ψ\ -+�'��#templates/html/static/css/style.cssNψ\�[`��templates/html/synopsis.xsl�!ψ\r��j��templates/html/units.xsl ψ\��XN�templates/html2/css/style.css�ψ\�E򖍶templates/html2/index.htmll4ψ\=/P�ƶ(dependencies/php/classes/APCIterator.xml -ψ\�kGA��)dependencies/php/classes/APCUIterator.xml� ψ\�8��X�+dependencies/php/classes/AppendIterator.xml�ψ\ ��9�/dependencies/php/classes/ArgumentCountError.xml ψ\�I\��,dependencies/php/classes/ArithmeticError.xml� -ψ\�>y|��*dependencies/php/classes/ArrayIterator.xmlψ\�,�̀�(dependencies/php/classes/ArrayObject.xml�ψ\)�B���+dependencies/php/classes/AssertionError.xml� -ψ\�<$P��5dependencies/php/classes/BadFunctionCallException.xml5 ψ\����ʶ3dependencies/php/classes/BadMethodCallException.xmlE ψ\�үS��%dependencies/php/classes/CURLFile.xml� -ψ\5z?��,dependencies/php/classes/CachingIterator.xml�ψ\,:���"dependencies/php/classes/Cairo.xml?ψ\��Dǎ�+dependencies/php/classes/CairoAntialias.xml^ψ\�� ڛ�)dependencies/php/classes/CairoContent.xml�ψ\��'/��)dependencies/php/classes/CairoContext.xml<�ψ\��n��+dependencies/php/classes/CairoException.xml ψ\��x�k�(dependencies/php/classes/CairoExtend.xmlAψ\�_��Ķ*dependencies/php/classes/CairoFillRule.xmlpψ\�{Nr��(dependencies/php/classes/CairoFilter.xml ψ\�E0���*dependencies/php/classes/CairoFontFace.xml-ψ\���(�-dependencies/php/classes/CairoFontOptions.xml�ψ\��LSI�+dependencies/php/classes/CairoFontSlant.xml�ψ\����ζ*dependencies/php/classes/CairoFontType.xmlAψ\���t�,dependencies/php/classes/CairoFontWeight.xmloψ\�ܕf]�(dependencies/php/classes/CairoFormat.xml�ψ\XbOJ��1dependencies/php/classes/CairoGradientPattern.xml[ψ\�_L#8�-dependencies/php/classes/CairoHintMetrics.xml�ψ\��v@�+dependencies/php/classes/CairoHintStyle.xml�ψ\�R1���.dependencies/php/classes/CairoImageSurface.xml�ψ\�-����)dependencies/php/classes/CairoLineCap.xml�ψ\�0���*dependencies/php/classes/CairoLineJoin.xml�ψ\�g1��0dependencies/php/classes/CairoLinearGradient.xml� ψ\��|�v�(dependencies/php/classes/CairoMatrix.xml~ψ\K�8�Z�*dependencies/php/classes/CairoOperator.xml�ψ\�㒶&dependencies/php/classes/CairoPath.xml�ψ\pS'�)dependencies/php/classes/CairoPattern.xml�ψ\ou�Ѷ-dependencies/php/classes/CairoPatternType.xmlOψ\���6��,dependencies/php/classes/CairoPdfSurface.xmlXψ\D�ڜݶ)dependencies/php/classes/CairoPsLevel.xmlmψ\�mg�+dependencies/php/classes/CairoPsSurface.xml�ψ\���l��0dependencies/php/classes/CairoRadialGradient.xml� ψ\�y���,dependencies/php/classes/CairoScaledFont.xmlK ψ\�z��.dependencies/php/classes/CairoSolidPattern.xmlIψ\��6��(dependencies/php/classes/CairoStatus.xml2 ψ\�vtzU�/dependencies/php/classes/CairoSubpixelOrder.xml�ψ\�LA� �)dependencies/php/classes/CairoSurface.xml�ψ\^�ܶ0dependencies/php/classes/CairoSurfacePattern.xmlF ψ\��M�-dependencies/php/classes/CairoSurfaceType.xml�ψ\3T5�2�,dependencies/php/classes/CairoSvgSurface.xmlZψ\���젶,dependencies/php/classes/CairoSvgVersion.xml{ψ\��$˶-dependencies/php/classes/CairoToyFontFace.xml�ψ\��r��3dependencies/php/classes/CallbackFilterIterator.xml� -ψ\��Q�?�$dependencies/php/classes/Closure.xml ψ\!7�>��%dependencies/php/classes/Collator.xml�ψ\����+dependencies/php/classes/CommonMark_CQL.xmlbψ\M�N7�;dependencies/php/classes/CommonMark_Interfaces_IVisitor.xmlqψ\W ��x�,dependencies/php/classes/CommonMark_Node.xmlψ\i�訶7dependencies/php/classes/CommonMark_Node_BlockQuote.xml�ψ\��Bض7dependencies/php/classes/CommonMark_Node_BulletList.xml�ψ\GB:�ȶ1dependencies/php/classes/CommonMark_Node_Code.xmlKψ\#c�쨶6dependencies/php/classes/CommonMark_Node_CodeBlock.xml~ψ\?�>Ͷ8dependencies/php/classes/CommonMark_Node_CustomBlock.xmlψ\Xq�9dependencies/php/classes/CommonMark_Node_CustomInline.xmlψ\m�o�5dependencies/php/classes/CommonMark_Node_Document.xml�ψ\�9:�C�6dependencies/php/classes/CommonMark_Node_HTMLBlock.xmlUψ\'�_H�7dependencies/php/classes/CommonMark_Node_HTMLInline.xmlWψ\)8�T�4dependencies/php/classes/CommonMark_Node_Heading.xmlnψ\( ���2dependencies/php/classes/CommonMark_Node_Image.xml�ψ\@41���1dependencies/php/classes/CommonMark_Node_Item.xml�ψ\����6dependencies/php/classes/CommonMark_Node_LineBreak.xml�ψ\��`S:�1dependencies/php/classes/CommonMark_Node_Link.xml�ψ\@Vm&t�8dependencies/php/classes/CommonMark_Node_OrderedList.xmlψ\V5�G��6dependencies/php/classes/CommonMark_Node_Paragraph.xml�ψ\��: 3�6dependencies/php/classes/CommonMark_Node_SoftBreak.xml�ψ\��r#�1dependencies/php/classes/CommonMark_Node_Text.xmlmψ\0y��Ͷ:dependencies/php/classes/CommonMark_Node_Text_Emphasis.xml�ψ\�ʱ}�8dependencies/php/classes/CommonMark_Node_Text_Strong.xml�ψ\���9�:dependencies/php/classes/CommonMark_Node_ThematicBreak.xml�ψ\�v^y�.dependencies/php/classes/CommonMark_Parser.xml�ψ\A�����)dependencies/php/classes/CompileError.xml� -ψ\����˶:dependencies/php/classes/Componere_Abstract_Definition.xmlsψ\i3��1dependencies/php/classes/Componere_Definition.xml�ψ\'��kζ-dependencies/php/classes/Componere_Method.xmldψ\b�diq�,dependencies/php/classes/Componere_Patch.xml� ψ\�r��,dependencies/php/classes/Componere_Value.xml�ψ\w��]��!dependencies/php/classes/Cond.xml�ψ\S�o_�$dependencies/php/classes/Counter.xmld ψ\ -{���$dependencies/php/classes/DOMAttr.xmli.ψ\�L�&\�,dependencies/php/classes/DOMCdataSection.xml85ψ\�_�+�-dependencies/php/classes/DOMCharacterData.xml91ψ\�p��E�'dependencies/php/classes/DOMComment.xmlN1ψ\�+��m�(dependencies/php/classes/DOMDocument.xml'lψ\2 �6 ��0dependencies/php/classes/DOMDocumentFragment.xml�(ψ\�h;��,dependencies/php/classes/DOMDocumentType.xml�-ψ\�K?��'dependencies/php/classes/DOMElement.xml�Eψ\��Ɣv�&dependencies/php/classes/DOMEntity.xml�.ψ\�Ϟ��/dependencies/php/classes/DOMEntityReference.xml�(ψ\����)dependencies/php/classes/DOMException.xml� ψ\�9'0ж.dependencies/php/classes/DOMImplementation.xml�ψ\� �nͶ,dependencies/php/classes/DOMNamedNodeMap.xml�ψ\���P�$dependencies/php/classes/DOMNode.xml(*ψ\��$pe�(dependencies/php/classes/DOMNodeList.xml?ψ\�E���(dependencies/php/classes/DOMNotation.xml�(ψ\��M�(�5dependencies/php/classes/DOMProcessingInstruction.xml�*ψ\:l�(�$dependencies/php/classes/DOMText.xmlv6ψ\8�)��%dependencies/php/classes/DOMXPath.xml -ψ\3�iBƶ)dependencies/php/classes/DateInterval.xml8 ψ\�C~ma�'dependencies/php/classes/DatePeriod.xml�ψ\�tD/�%dependencies/php/classes/DateTime.xml� ψ\&��9�.dependencies/php/classes/DateTimeImmutable.xmls!ψ\I��P8�)dependencies/php/classes/DateTimeZone.xmlxψ\�h� ��&dependencies/php/classes/Directory.xml:ψ\�*Hɝ�.dependencies/php/classes/DirectoryIterator.xml�ψ\h�Qb��0dependencies/php/classes/DivisionByZeroError.xml ψ\�&f`}�,dependencies/php/classes/DomainException.xml# ψ\� �P��%dependencies/php/classes/Ds_Deque.xmlO&ψ\�#h�#dependencies/php/classes/Ds_Map.xml�0ψ\8/Ρ��$dependencies/php/classes/Ds_Pair.xmlψ\� -�2B�-dependencies/php/classes/Ds_PriorityQueue.xml ψ\d���e�%dependencies/php/classes/Ds_Queue.xml� -ψ\wM\yL�#dependencies/php/classes/Ds_Set.xml�"ψ\�0�LѶ%dependencies/php/classes/Ds_Stack.xml� ψ\U���&dependencies/php/classes/Ds_Vector.xml[&ψ\�{�⍶*dependencies/php/classes/EmptyIterator.xml�ψ\0���"dependencies/php/classes/Error.xml� ψ\Y�����+dependencies/php/classes/ErrorException.xml�ψ\Qx ��dependencies/php/classes/Ev.xml�$ψ\�8D�#�$dependencies/php/classes/EvCheck.xmlψ\oE����$dependencies/php/classes/EvChild.xml�ψ\���1ն$dependencies/php/classes/EvEmbed.xml�ψ\�`�ғ�#dependencies/php/classes/EvFork.xmlψ\p�f���#dependencies/php/classes/EvIdle.xmlψ\[P�� -�!dependencies/php/classes/EvIo.xmlψ\w�ٽ��#dependencies/php/classes/EvLoop.xml@8ψ\E�/� �'dependencies/php/classes/EvPeriodic.xml�ψ\��["R�&dependencies/php/classes/EvPrepare.xml -ψ\g�0�%dependencies/php/classes/EvSignal.xml�ψ\����j�#dependencies/php/classes/EvStat.xml�ψ\G��j�$dependencies/php/classes/EvTimer.xml ψ\��a[/�&dependencies/php/classes/EvWatcher.xml�ψ\��*�[�"dependencies/php/classes/Event.xml�ψ\�� �A�&dependencies/php/classes/EventBase.xmlqψ\H�ˮ��(dependencies/php/classes/EventBuffer.xml�%ψ\�/���-dependencies/php/classes/EventBufferEvent.xmlP:ψ\-�#Զ(dependencies/php/classes/EventConfig.xml�ψ\6y�YV�)dependencies/php/classes/EventDnsBase.xml�ψ\�����&dependencies/php/classes/EventHttp.xml�ψ\ �z��0dependencies/php/classes/EventHttpConnection.xml@ψ\�K��ö-dependencies/php/classes/EventHttpRequest.xml!ψ\U6��Ѷ*dependencies/php/classes/EventListener.xml�ψ\�b���,dependencies/php/classes/EventSslContext.xml� ψ\��bn��&dependencies/php/classes/EventUtil.xml�ψ\W�n��&dependencies/php/classes/Exception.xml� ψ\V��6M�+dependencies/php/classes/FANNConnection.xml ψ\�.)�/dependencies/php/classes/FilesystemIterator.xml'#ψ\��`y��+dependencies/php/classes/FilterIterator.xmlQψ\���� dependencies/php/classes/GMP.xml�ψ\�����*dependencies/php/classes/GearmanClient.xml�Dψ\6\��϶-dependencies/php/classes/GearmanException.xml ψ\�:�k��'dependencies/php/classes/GearmanJob.xmlyψ\h����(dependencies/php/classes/GearmanTask.xmlψ\?FN �*dependencies/php/classes/GearmanWorker.xml�ψ\:�>�n�*dependencies/php/classes/Gender_Gender.xmlF$ψ\]M��&�&dependencies/php/classes/Generator.xml� -ψ\?q�N^�)dependencies/php/classes/GlobIterator.xml�ψ\w�]5�$dependencies/php/classes/Gmagick.xml��ψ\n��A��(dependencies/php/classes/GmagickDraw.xml\1ψ\#R�A��)dependencies/php/classes/GmagickPixel.xml�ψ\�в��6dependencies/php/classes/HRTime_PerformanceCounter.xml�ψ\UW�X�-dependencies/php/classes/HRTime_StopWatch.xmlt -ψ\���[z�(dependencies/php/classes/HRTime_Unit.xmlRψ\�w��?�+dependencies/php/classes/HaruAnnotation.xml�ψ\oY#�U�,dependencies/php/classes/HaruDestination.xml� ψ\�@���$dependencies/php/classes/HaruDoc.xmlI6ψ\�sĞ�(dependencies/php/classes/HaruEncoder.xml�ψ\U��*dependencies/php/classes/HaruException.xml�ψ\PF�<�%dependencies/php/classes/HaruFont.xmlu ψ\" SA��&dependencies/php/classes/HaruImage.xml�ψ\�=غ��(dependencies/php/classes/HaruOutline.xmlψ\��'D�%dependencies/php/classes/HaruPage.xml�tψ\�Z+�#�(dependencies/php/classes/HashContext.xml~ψ\��"�5�$dependencies/php/classes/Imagick.xml��ψ\�&�L�ö(dependencies/php/classes/ImagickDraw.xmlz�ψ\� �z��*dependencies/php/classes/ImagickKernel.xml9 ψ\��G��)dependencies/php/classes/ImagickPixel.xml�ψ\�W>ܶ1dependencies/php/classes/ImagickPixelIterator.xmlIψ\5��� �-dependencies/php/classes/InfiniteIterator.xmlu ψ\����.dependencies/php/classes/IntlBreakIterator.xmlt$ψ\u~���)dependencies/php/classes/IntlCalendar.xml�ψ\+ -\�%dependencies/php/classes/IntlChar.xmlI�ψ\j!�!f��7dependencies/php/classes/IntlCodePointBreakIterator.xml� ψ\:�Ķ.dependencies/php/classes/IntlDateFormatter.xml� ψ\z�P}Ӷ*dependencies/php/classes/IntlException.xml ψ\�"dhb�2dependencies/php/classes/IntlGregorianCalendar.xml��ψ\,�­h�)dependencies/php/classes/IntlIterator.xmlψ\X�� -�.dependencies/php/classes/IntlPartsIterator.xml�ψ\�� ��7dependencies/php/classes/IntlRuleBasedBreakIterator.xml9%ψ\� -H��)dependencies/php/classes/IntlTimeZone.xml"ψ\>��ٶ5dependencies/php/classes/InvalidArgumentException.xml5 ψ\�5�Q�-dependencies/php/classes/IteratorIterator.xml{ψ\�$��?�*dependencies/php/classes/JsonException.xml ψ\��R�?�!dependencies/php/classes/Judy.xmlψ\?���ɶ?dependencies/php/classes/KTaglib_ID3v2_AttachedPictureFrame.xml?ψ\4��*��0dependencies/php/classes/KTaglib_ID3v2_Frame.xml�ψ\uRӋ@�.dependencies/php/classes/KTaglib_ID3v2_Tag.xmlJ ψ\��R�̶9dependencies/php/classes/KTaglib_MPEG_Audioproperties.xml& ψ\���3Y�.dependencies/php/classes/KTaglib_MPEG_File.xmlWψ\k ]_�(dependencies/php/classes/KTaglib_Tag.xml�ψ\`��S��#dependencies/php/classes/Lapack.xml� ψ\���*�,dependencies/php/classes/LengthException.xml# ψ\�`��R�*dependencies/php/classes/LimitIterator.xml -ψ\ -�vDN�#dependencies/php/classes/Locale.xml�ψ\Y��>�+dependencies/php/classes/LogicException.xml ψ\�F�`K� dependencies/php/classes/Lua.xml[ ψ\1�ý:�'dependencies/php/classes/LuaClosure.xml�ψ\ -?�%dependencies/php/classes/Memcache.xmlz)ψ\�WI�m�&dependencies/php/classes/Memcached.xml2^ψ\� Yi��/dependencies/php/classes/MemcachedException.xml- ψ\��Z���-dependencies/php/classes/MessageFormatter.xml|ψ\G(��̶"dependencies/php/classes/Mongo.xml�ψ\���s��)dependencies/php/classes/MongoBinData.xmlfψ\��m�(dependencies/php/classes/MongoClient.xml�!ψ\ -���&dependencies/php/classes/MongoCode.xmlψ\J5���,dependencies/php/classes/MongoCollection.xml/<ψ\,��1��/dependencies/php/classes/MongoCommandCursor.xml0ψ\����5dependencies/php/classes/MongoConnectionException.xml�ψ\����(dependencies/php/classes/MongoCursor.xml3ψ\ >��]�1dependencies/php/classes/MongoCursorException.xml�ψ\��\3˶8dependencies/php/classes/MongoCursorTimeoutException.xml�ψ\�3��ȶ$dependencies/php/classes/MongoDB.xml�2ψ\������'dependencies/php/classes/MongoDBRef.xmlbψ\���X��0dependencies/php/classes/MongoDB_BSON_Binary.xmlF ψ\P�8��3dependencies/php/classes/MongoDB_BSON_DBPointer.xml�ψ\��o -��4dependencies/php/classes/MongoDB_BSON_Decimal128.xmlHψ\��*� �/dependencies/php/classes/MongoDB_BSON_Int64.xml�ψ\���v�4dependencies/php/classes/MongoDB_BSON_Javascript.xmlhψ\�c���0dependencies/php/classes/MongoDB_BSON_MaxKey.xml�ψ\������0dependencies/php/classes/MongoDB_BSON_MinKey.xml�ψ\�QY��2dependencies/php/classes/MongoDB_BSON_ObjectId.xml2ψ\���73�/dependencies/php/classes/MongoDB_BSON_Regex.xml3ψ\���>,�0dependencies/php/classes/MongoDB_BSON_Symbol.xml�ψ\���G��3dependencies/php/classes/MongoDB_BSON_Timestamp.xml�ψ\���T�5dependencies/php/classes/MongoDB_BSON_UTCDateTime.xml�ψ\�-���3dependencies/php/classes/MongoDB_BSON_Undefined.xml�ψ\�Ne>ƶ5dependencies/php/classes/MongoDB_Driver_BulkWrite.xml$ψ\�P1A"�3dependencies/php/classes/MongoDB_Driver_Command.xml ψ\�wm?�2dependencies/php/classes/MongoDB_Driver_Cursor.xml�ψ\����4dependencies/php/classes/MongoDB_Driver_CursorId.xmlmψ\1-�QնMdependencies/php/classes/MongoDB_Driver_Exception_AuthenticationException.xml�ψ\�����Hdependencies/php/classes/MongoDB_Driver_Exception_BulkWriteException.xml�ψ\ �Ӏ�Fdependencies/php/classes/MongoDB_Driver_Exception_CommandException.xml�ψ\~���Idependencies/php/classes/MongoDB_Driver_Exception_ConnectionException.xml�ψ\���Ee�Pdependencies/php/classes/MongoDB_Driver_Exception_ConnectionTimeoutException.xml�ψ\��;�Odependencies/php/classes/MongoDB_Driver_Exception_ExecutionTimeoutException.xml�ψ\�L�A�Ndependencies/php/classes/MongoDB_Driver_Exception_InvalidArgumentException.xml� ψ\�$0)S�Ddependencies/php/classes/MongoDB_Driver_Exception_LogicException.xml} ψ\�(��ԶFdependencies/php/classes/MongoDB_Driver_Exception_RuntimeException.xml&ψ\�A����Ldependencies/php/classes/MongoDB_Driver_Exception_SSLConnectionException.xml�ψ\��Edependencies/php/classes/MongoDB_Driver_Exception_ServerException.xml�ψ\�N|o��Ndependencies/php/classes/MongoDB_Driver_Exception_UnexpectedValueException.xml� ψ\�d�ʶDdependencies/php/classes/MongoDB_Driver_Exception_WriteException.xmlψ\���u�3dependencies/php/classes/MongoDB_Driver_Manager.xml�ψ\A�C;�Idependencies/php/classes/MongoDB_Driver_Monitoring_CommandFailedEvent.xmlpψ\�i2�{�Jdependencies/php/classes/MongoDB_Driver_Monitoring_CommandStartedEvent.xmlwψ\r��|��Ldependencies/php/classes/MongoDB_Driver_Monitoring_CommandSucceededEvent.xmlvψ\�L�9�1dependencies/php/classes/MongoDB_Driver_Query.xmlLψ\41l>#�7dependencies/php/classes/MongoDB_Driver_ReadConcern.xml:ψ\��Kŕ�:dependencies/php/classes/MongoDB_Driver_ReadPreference.xml^ -ψ\Zjܽ�2dependencies/php/classes/MongoDB_Driver_Server.xmlzψ\�˅�3dependencies/php/classes/MongoDB_Driver_Session.xmlR ψ\��芈�8dependencies/php/classes/MongoDB_Driver_WriteConcern.xmlx ψ\���y�=dependencies/php/classes/MongoDB_Driver_WriteConcernError.xml�ψ\9��`0�6dependencies/php/classes/MongoDB_Driver_WriteError.xmluψ\Y��6��7dependencies/php/classes/MongoDB_Driver_WriteResult.xml� -ψ\�"qH�&dependencies/php/classes/MongoDate.xml"ψ\���<8�-dependencies/php/classes/MongoDeleteBatch.xml�ψ\m`i�7dependencies/php/classes/MongoDuplicateKeyException.xml8ψ\o� -�i�+dependencies/php/classes/MongoException.xml�ψ\��=�?�;dependencies/php/classes/MongoExecutionTimeoutException.xml`ψ\3��굶(dependencies/php/classes/MongoGridFS.xml�ψ\B��}ƶ.dependencies/php/classes/MongoGridFSCursor.xmlψ\�WeO@�1dependencies/php/classes/MongoGridFSException.xml�ψ\���:�,dependencies/php/classes/MongoGridFSFile.xmlx ψ\|`�{S�$dependencies/php/classes/MongoId.xml� -ψ\�6��ڶ-dependencies/php/classes/MongoInsertBatch.xml�ψ\o):&��'dependencies/php/classes/MongoInt32.xml�ψ\e���'dependencies/php/classes/MongoInt64.xml�ψ\e��4}�%dependencies/php/classes/MongoLog.xml�ψ\���B �(dependencies/php/classes/MongoMaxKey.xml�ψ\r�m�S�(dependencies/php/classes/MongoMinKey.xml�ψ\q��ñ�&dependencies/php/classes/MongoPool.xml�ψ\A���˶3dependencies/php/classes/MongoProtocolException.xmlPψ\0�c�)�'dependencies/php/classes/MongoRegex.xmlψ\dߕ�3�1dependencies/php/classes/MongoResultException.xml�ψ\���0��+dependencies/php/classes/MongoTimestamp.xml:ψ\l?�Ѷ-dependencies/php/classes/MongoUpdateBatch.xml�ψ\mTĪ2�,dependencies/php/classes/MongoWriteBatch.xml�ψ\�f��7dependencies/php/classes/MongoWriteConcernException.xmlψ\��[�^�-dependencies/php/classes/MultipleIterator.xml�ψ\�~� J�"dependencies/php/classes/Mutex.xml�ψ\oZ2t�0dependencies/php/classes/MysqlndUhConnection.xmlAWψ\q���O�7dependencies/php/classes/MysqlndUhPreparedStatement.xml�ψ\u�����-dependencies/php/classes/NoRewindIterator.xml3 ψ\� -W��'dependencies/php/classes/Normalizer.xml�ψ\����c�,dependencies/php/classes/NumberFormatter.xml+ψ\�׀���"dependencies/php/classes/OAuth.xml$ψ\?\<϶+dependencies/php/classes/OAuthException.xml� ψ\��!�ȶ*dependencies/php/classes/OAuthProvider.xml|ψ\����+dependencies/php/classes/OCI-Collection.xmlv ψ\���K(�$dependencies/php/classes/OCI-Lob.xml?ψ\�SՂ��1dependencies/php/classes/OutOfBoundsException.xml1 ψ\�XX&z�0dependencies/php/classes/OutOfRangeException.xml+ ψ\�� t�.dependencies/php/classes/OverflowException.xml+ ψ\��׷j� dependencies/php/classes/PDO.xmlk$ψ\q 8U+��)dependencies/php/classes/PDOException.xml ψ\}��4�)dependencies/php/classes/PDOStatement.xml)ψ\�B�饶+dependencies/php/classes/ParentIterator.xml�ψ\�hA��,dependencies/php/classes/Parle_ErrorInfo.xmlJψ\<1��M�(dependencies/php/classes/Parle_Lexer.xml+ψ\��Äf�1dependencies/php/classes/Parle_LexerException.xml% ψ\�u��޶)dependencies/php/classes/Parle_Parser.xml�ψ\�@!�2dependencies/php/classes/Parle_ParserException.xml' ψ\��h$�)dependencies/php/classes/Parle_RLexer.xml�ψ\9,��*dependencies/php/classes/Parle_RParser.xml�ψ\�QHs�(dependencies/php/classes/Parle_Stack.xml-ψ\��z�(dependencies/php/classes/Parle_Token.xmlaψ\({\l��'dependencies/php/classes/ParseError.xml� -ψ\�@��H�!dependencies/php/classes/Phar.xmlbnψ\Q8�ʶ%dependencies/php/classes/PharData.xml^qψ\{ D���*dependencies/php/classes/PharException.xml ψ\����~�)dependencies/php/classes/PharFileInfo.xmlψ\��K���!dependencies/php/classes/Pool.xmlr ψ\j�vm�-dependencies/php/classes/QuickHashIntHash.xml\ψ\F��\��,dependencies/php/classes/QuickHashIntSet.xml>ψ\�_���3dependencies/php/classes/QuickHashIntStringHash.xmlOψ\j����3dependencies/php/classes/QuickHashStringIntHash.xml]ψ\�{(¶'dependencies/php/classes/RRDCreator.xml�ψ\�#���%dependencies/php/classes/RRDGraph.xml-ψ\�[2U6�'dependencies/php/classes/RRDUpdater.xmlXψ\U���+dependencies/php/classes/RangeException.xml% ψ\�-��'dependencies/php/classes/RarArchive.xml ψ\�OV\ƶ%dependencies/php/classes/RarEntry.xmlN$ψ\��5�4�)dependencies/php/classes/RarException.xml� -ψ\FR 2e�3dependencies/php/classes/RecursiveArrayIterator.xmlPψ\� -"�_�5dependencies/php/classes/RecursiveCachingIterator.xml2ψ\�����<dependencies/php/classes/RecursiveCallbackFilterIterator.xmlψ\���ܺ�7dependencies/php/classes/RecursiveDirectoryIterator.xml�ψ\����4dependencies/php/classes/RecursiveFilterIterator.xml ψ\�D�]p�6dependencies/php/classes/RecursiveIteratorIterator.xmlψ\ M�X��3dependencies/php/classes/RecursiveRegexIterator.xml�ψ\=B���2dependencies/php/classes/RecursiveTreeIterator.xml�'ψ\��0f�'dependencies/php/classes/Reflection.xmlMψ\?G�F�,dependencies/php/classes/ReflectionClass.xml/;ψ\]��7�4dependencies/php/classes/ReflectionClassConstant.xml�ψ\�X�>߶0dependencies/php/classes/ReflectionException.xml! ψ\� Tۋ�0dependencies/php/classes/ReflectionExtension.xml�ψ\v��(�/dependencies/php/classes/ReflectionFunction.xmluψ\?+Ӷ7dependencies/php/classes/ReflectionFunctionAbstract.xml�ψ\H��]:�0dependencies/php/classes/ReflectionGenerator.xml"ψ\����T�-dependencies/php/classes/ReflectionMethod.xmlY.ψ\�� a@�0dependencies/php/classes/ReflectionNamedType.xml ψ\*��5��-dependencies/php/classes/ReflectionObject.xml"7ψ\8<�̶0dependencies/php/classes/ReflectionParameter.xmlfψ\e6��s�/dependencies/php/classes/ReflectionProperty.xml�ψ\JQ"��+dependencies/php/classes/ReflectionType.xml'ψ\ʶ-ɶ4dependencies/php/classes/ReflectionZendExtension.xml -ψ\ �t�*dependencies/php/classes/RegexIterator.xml"ψ\|2z�j�+dependencies/php/classes/ResourceBundle.xml� -ψ\P���-dependencies/php/classes/RuntimeException.xml ψ\�F��!dependencies/php/classes/SNMP.xml�ψ\��b6�*dependencies/php/classes/SNMPException.xml ψ\��D��$dependencies/php/classes/SQLite3.xml7ψ\��v -��*dependencies/php/classes/SQLite3Result.xml4ψ\��zd�(dependencies/php/classes/SQLite3Stmt.xmlI ψ\L(.�Ӷ dependencies/php/classes/SVM.xml�ψ\�~o��%dependencies/php/classes/SVMModel.xml� ψ\@�k>q�&dependencies/php/classes/SWFAction.xml�ψ\���54�&dependencies/php/classes/SWFBitmap.xml�ψ\Id��&dependencies/php/classes/SWFButton.xmlJ ψ\*� bֶ+dependencies/php/classes/SWFDisplayItem.xmlR!ψ\"�w"0�$dependencies/php/classes/SWFFill.xml�ψ\I_����$dependencies/php/classes/SWFFont.xml�ψ\*%g=?�(dependencies/php/classes/SWFFontChar.xml�ψ\ c�-�(dependencies/php/classes/SWFGradient.xml�ψ\���Զ%dependencies/php/classes/SWFMorph.xml:ψ\%;����%dependencies/php/classes/SWFMovie.xmlψ\�~[�{�,dependencies/php/classes/SWFPrebuiltClip.xml�ψ\9�%dependencies/php/classes/SWFShape.xml�!ψ\����%dependencies/php/classes/SWFSound.xmlψ\B:�T�-dependencies/php/classes/SWFSoundInstance.xmlKψ\6�U��&dependencies/php/classes/SWFSprite.xmli ψ\� �;�$dependencies/php/classes/SWFText.xmleψ\�@��)dependencies/php/classes/SWFTextField.xmlψ\& o�M�+dependencies/php/classes/SWFVideoStream.xml�ψ\\\K�@�$dependencies/php/classes/SeasLog.xml9,ψ\�#�uݶ+dependencies/php/classes/SessionHandler.xmlBψ\��N��-dependencies/php/classes/SimpleXMLElement.xml�ψ\,�j �.dependencies/php/classes/SimpleXMLIterator.xml�ψ\������'dependencies/php/classes/SoapClient.xml�ψ\$�����&dependencies/php/classes/SoapFault.xml�ψ\WG����'dependencies/php/classes/SoapHeader.xml�ψ\>W���&dependencies/php/classes/SoapParam.xml}ψ\l��E�'dependencies/php/classes/SoapServer.xml�ψ\>C�:�$dependencies/php/classes/SoapVar.xml/ψ\7�'��'dependencies/php/classes/SolrClient.xml8$ψ\� ����0dependencies/php/classes/SolrClientException.xml�ψ\1B�)��1dependencies/php/classes/SolrCollapseFunction.xml�ψ\�u��,dependencies/php/classes/SolrDisMaxQuery.xml?�ψ\u Ho|�)dependencies/php/classes/SolrDocument.xml�(ψ\��hR �.dependencies/php/classes/SolrDocumentField.xml�ψ\_����*dependencies/php/classes/SolrException.xmlψ\'���0dependencies/php/classes/SolrGenericResponse.xmlψ\?w>�9dependencies/php/classes/SolrIllegalArgumentException.xmlψ\7���:dependencies/php/classes/SolrIllegalOperationException.xmlψ\8RAC��.dependencies/php/classes/SolrInputDocument.xml�ψ\��P� �Cdependencies/php/classes/SolrMissingMandatoryParameterException.xml ψ\��q��1dependencies/php/classes/SolrModifiableParams.xml�ψ\�I��'dependencies/php/classes/SolrObject.xml�ψ\��z��'dependencies/php/classes/SolrParams.xml�ψ\@� �-dependencies/php/classes/SolrPingResponse.xml~ ψ\�k,$z�&dependencies/php/classes/SolrQuery.xml��ψ\(= �2�.dependencies/php/classes/SolrQueryResponse.xml�ψ\�q �)dependencies/php/classes/SolrResponse.xml�ψ\?(\�x�0dependencies/php/classes/SolrServerException.xml�ψ\1C]�/dependencies/php/classes/SolrUpdateResponse.xml�ψ\�sP�&dependencies/php/classes/SolrUtils.xmlψ\�Uyڠ�)dependencies/php/classes/SphinxClient.xml5ψ\�����$dependencies/php/classes/SplBool.xml5ψ\P�b�U�0dependencies/php/classes/SplDoublyLinkedList.xml�ψ\c�h��$dependencies/php/classes/SplEnum.xmlψ\��=s��(dependencies/php/classes/SplFileInfo.xml6ψ\�����*dependencies/php/classes/SplFileObject.xmlAψ\���@Ѷ*dependencies/php/classes/SplFixedArray.xml(ψ\���W��%dependencies/php/classes/SplFloat.xml�ψ\6���!�$dependencies/php/classes/SplHeap.xmlψ\��� -��#dependencies/php/classes/SplInt.xml�ψ\9!��'dependencies/php/classes/SplMaxHeap.xml� ψ\�j�e��'dependencies/php/classes/SplMinHeap.xml� ψ\����r�-dependencies/php/classes/SplObjectStorage.xmlrψ\����!�-dependencies/php/classes/SplPriorityQueue.xml�ψ\����%dependencies/php/classes/SplQueue.xmlAψ\��O6M�%dependencies/php/classes/SplStack.xml�ψ\�͡�ڶ&dependencies/php/classes/SplString.xml�ψ\9.�;�.dependencies/php/classes/SplTempFileObject.xml�"ψ\q�Q��$dependencies/php/classes/SplType.xml�ψ\?�Z�߶)dependencies/php/classes/Spoofchecker.xml� -ψ\Kb�*��"dependencies/php/classes/Stomp.xml!ψ\ �����+dependencies/php/classes/StompException.xml�ψ\g4@Ҷ'dependencies/php/classes/StompFrame.xmllψ\c����)dependencies/php/classes/Swoole_Async.xml� ψ\��*dependencies/php/classes/Swoole_Atomic.xml2ψ\~I�?4�*dependencies/php/classes/Swoole_Buffer.xml� ψ\`WZ�O�+dependencies/php/classes/Swoole_Channel.xml�ψ\�09?��*dependencies/php/classes/Swoole_Client.xml�ψ\��cu��7dependencies/php/classes/Swoole_Connection_Iterator.xml; ψ\�4 ۶-dependencies/php/classes/Swoole_Coroutine.xml6ψ\�P��}�)dependencies/php/classes/Swoole_Event.xml ψ\�����-dependencies/php/classes/Swoole_Exception.xml<ψ\1��=�/dependencies/php/classes/Swoole_Http_Client.xml�ψ\7�����0dependencies/php/classes/Swoole_Http_Request.xmlgψ\��9�1dependencies/php/classes/Swoole_Http_Response.xml4ψ\n��i��/dependencies/php/classes/Swoole_Http_Server.xml/6ψ\��s��(dependencies/php/classes/Swoole_Lock.xmlsψ\t��e�(dependencies/php/classes/Swoole_Mmap.xml�ψ\J��7�)dependencies/php/classes/Swoole_MySQL.xmlψ\�hZ~�3dependencies/php/classes/Swoole_MySQL_Exception.xmlHψ\9��Lo�+dependencies/php/classes/Swoole_Process.xmlSψ\� � a�0dependencies/php/classes/Swoole_Redis_Server.xml;ψ\r��Cv�-dependencies/php/classes/Swoole_Serialize.xml�ψ\3e����*dependencies/php/classes/Swoole_Server.xml�9ψ\����f�)dependencies/php/classes/Swoole_Table.xml�ψ\ �lm��)dependencies/php/classes/Swoole_Timer.xml�ψ\��~_-�3dependencies/php/classes/Swoole_WebSocket_Frame.xml�ψ\|��� -�4dependencies/php/classes/Swoole_WebSocket_Server.xml� ψ\)$+a�&dependencies/php/classes/SyncEvent.xml`ψ\�F}Gs�&dependencies/php/classes/SyncMutex.xmlψ\c�m��-dependencies/php/classes/SyncReaderWriter.xmlNψ\�;�}��*dependencies/php/classes/SyncSemaphore.xml�ψ\|Y�۞�-dependencies/php/classes/SyncSharedMemory.xml(ψ\��j��#dependencies/php/classes/Thread.xml2ψ\�}ҵ\�%dependencies/php/classes/Threaded.xmlZψ\vh�!ʶ(dependencies/php/classes/TokyoTyrant.xml4ψ\0��Uܶ0dependencies/php/classes/TokyoTyrantIterator.xmlF%ψ\A��;F�-dependencies/php/classes/TokyoTyrantQuery.xmlψ\�A�件-dependencies/php/classes/TokyoTyrantTable.xml�/ψ\��G�5�+dependencies/php/classes/Transliterator.xml� ψ\���␶&dependencies/php/classes/TypeError.xml� -ψ\��9���'dependencies/php/classes/UConverter.xml/+ψ\���$�$dependencies/php/classes/UI_Area.xml�ψ\sp S�'dependencies/php/classes/UI_Control.xmlJ ψ\���,dependencies/php/classes/UI_Controls_Box.xmlRψ\�����/dependencies/php/classes/UI_Controls_Button.xml� ψ\��"��.dependencies/php/classes/UI_Controls_Check.xml�ψ\=����4dependencies/php/classes/UI_Controls_ColorButton.xml� ψ\��)x�.dependencies/php/classes/UI_Controls_Combo.xml� ψ\��P��6dependencies/php/classes/UI_Controls_EditableCombo.xml� ψ\�^����.dependencies/php/classes/UI_Controls_Entry.xmlψ\Qr��F�-dependencies/php/classes/UI_Controls_Form.xml�ψ\8��ﶶ-dependencies/php/classes/UI_Controls_Grid.xml�ψ\{o��$�.dependencies/php/classes/UI_Controls_Group.xmlψ\S� -q�.dependencies/php/classes/UI_Controls_Label.xml� ψ\��Ex�7dependencies/php/classes/UI_Controls_MultilineEntry.xml�ψ\`����/dependencies/php/classes/UI_Controls_Picker.xmlP ψ\��~t5�1dependencies/php/classes/UI_Controls_Progress.xml� -ψ\�/�_Y�.dependencies/php/classes/UI_Controls_Radio.xml� ψ\�oV#�2dependencies/php/classes/UI_Controls_Separator.xml� -ψ\��=]b�/dependencies/php/classes/UI_Controls_Slider.xml ψ\�Q�1�-dependencies/php/classes/UI_Controls_Spin.xml ψ\�k3���,dependencies/php/classes/UI_Controls_Tab.xml�ψ\XH��*dependencies/php/classes/UI_Draw_Brush.xml6ψ\Sm��/�3dependencies/php/classes/UI_Draw_Brush_Gradient.xml� ψ\���;��9dependencies/php/classes/UI_Draw_Brush_LinearGradient.xmlM -ψ\�ޟ�h�9dependencies/php/classes/UI_Draw_Brush_RadialGradient.xml� -ψ\���F�*dependencies/php/classes/UI_Draw_Color.xmlm -ψ\�7�̑�-dependencies/php/classes/UI_Draw_Line_Cap.xml�ψ\���/�.dependencies/php/classes/UI_Draw_Line_Join.xml�ψ\�WZ���+dependencies/php/classes/UI_Draw_Matrix.xmlm ψ\�z��)dependencies/php/classes/UI_Draw_Path.xml�ψ\$N��y�(dependencies/php/classes/UI_Draw_Pen.xml�ψ\�e*1��+dependencies/php/classes/UI_Draw_Stroke.xml� -ψ\��zڔ�.dependencies/php/classes/UI_Draw_Text_Font.xml+ψ\eabdö9dependencies/php/classes/UI_Draw_Text_Font_Descriptor.xml\ψ\�|�PͶ5dependencies/php/classes/UI_Draw_Text_Font_Italic.xml�ψ\�e��̶6dependencies/php/classes/UI_Draw_Text_Font_Stretch.xml�ψ\mLҶ5dependencies/php/classes/UI_Draw_Text_Font_Weight.xmlZψ\��\$��0dependencies/php/classes/UI_Draw_Text_Layout.xmlLψ\��lx%�Bdependencies/php/classes/UI_Exception_InvalidArgumentException.xmlG ψ\�E�d�:dependencies/php/classes/UI_Exception_RuntimeException.xml7 ψ\�A�ڶ(dependencies/php/classes/UI_Executor.xml�ψ\r�ؙ�#dependencies/php/classes/UI_Key.xml�ψ\R��$dependencies/php/classes/UI_Menu.xml@ ψ\���v��(dependencies/php/classes/UI_MenuItem.xml ψ\Yg�#��%dependencies/php/classes/UI_Point.xml -ψ\�b�tԶ$dependencies/php/classes/UI_Size.xml' -ψ\�/�?Ӷ&dependencies/php/classes/UI_Window.xml ψ\)�It��/dependencies/php/classes/UnderflowException.xml- ψ\�oi��5dependencies/php/classes/UnexpectedValueException.xml9 ψ\�����!dependencies/php/classes/V8Js.xml� -ψ\_�; ޶*dependencies/php/classes/V8JsException.xmlIψ\� �S�)dependencies/php/classes/VarnishAdmin.xml�ψ\����~�'dependencies/php/classes/VarnishLog.xml�ψ\d�@d�(dependencies/php/classes/VarnishStat.xml�ψ\At8�i�%dependencies/php/classes/Volatile.xmlcψ\>��(��0dependencies/php/classes/Vtiful_Kernel_Excel.xml�ψ\j>��_�1dependencies/php/classes/Vtiful_Kernel_Format.xml� ψ\)�H��$dependencies/php/classes/WeakMap.xml� ψ\e`3��$dependencies/php/classes/WeakRef.xml}ψ\�r���#dependencies/php/classes/Worker.xml.ψ\"3���)dependencies/php/classes/XMLDiff_Base.xml�ψ\���[��(dependencies/php/classes/XMLDiff_DOM.xml.ψ\�æ -J�)dependencies/php/classes/XMLDiff_File.xml�ψ\�$�:�+dependencies/php/classes/XMLDiff_Memory.xml�ψ\�]��s�&dependencies/php/classes/XMLReader.xml5ψ\�;�QͶ*dependencies/php/classes/XSLTProcessor.xml�ψ\�M}�:�#dependencies/php/classes/Yaconf.xml1ψ\:=�'��0dependencies/php/classes/Yaf_Action_Abstract.xml�ψ\���,dependencies/php/classes/Yaf_Application.xml�ψ\(i A��3dependencies/php/classes/Yaf_Bootstrap_Abstract.xml�ψ\zϻ0��0dependencies/php/classes/Yaf_Config_Abstract.xml�ψ\��я��+dependencies/php/classes/Yaf_Config_Ini.xml�ψ\��0�.dependencies/php/classes/Yaf_Config_Simple.xml ψ\g���4dependencies/php/classes/Yaf_Controller_Abstract.xml�ψ\�dIņ�+dependencies/php/classes/Yaf_Dispatcher.xml�'ψ\f'��3�*dependencies/php/classes/Yaf_Exception.xml� ψ\�/Q�¶9dependencies/php/classes/Yaf_Exception_DispatchFailed.xml~ψ\ -m'��5dependencies/php/classes/Yaf_Exception_LoadFailed.xmlvψ\�=��<dependencies/php/classes/Yaf_Exception_LoadFailed_Action.xml�ψ\ }d���@dependencies/php/classes/Yaf_Exception_LoadFailed_Controller.xml�ψ\����<dependencies/php/classes/Yaf_Exception_LoadFailed_Module.xml�ψ\ �ާ|�:dependencies/php/classes/Yaf_Exception_LoadFailed_View.xml�ψ\ %�ݶ'dependencies/php/classes/Yaf_Loader.xml�ψ\)�Y�M�7dependencies/php/classes/Yaf_Exception_RouterFailed.xmlzψ\��\�7dependencies/php/classes/Yaf_Exception_StartupError.xmlzψ\�k,}�4dependencies/php/classes/Yaf_Exception_TypeError.xmltψ\ L g�0dependencies/php/classes/Yaf_Plugin_Abstract.xml� ψ\�tȜ��)dependencies/php/classes/Yaf_Registry.xmlfψ\�����1dependencies/php/classes/Yaf_Request_Abstract.xmli(ψ\Z��T�-dependencies/php/classes/Yaf_Request_Http.xmll(ψ\��ñѶ/dependencies/php/classes/Yaf_Request_Simple.xml&ψ\'�Cd�2dependencies/php/classes/Yaf_Response_Abstract.xmluψ\����*dependencies/php/classes/Yaf_Route_Map.xml�ψ\����5�,dependencies/php/classes/Yaf_Route_Regex.xml� ψ\��t���.dependencies/php/classes/Yaf_Route_Rewrite.xmlV -ψ\��� ^�-dependencies/php/classes/Yaf_Route_Simple.xml�ψ\����G�-dependencies/php/classes/Yaf_Route_Static.xml�ψ\z �9��/dependencies/php/classes/Yaf_Route_Supervar.xml�ψ\��D�P�(dependencies/php/classes/Yaf_Session.xml�ψ\\C"4k�,dependencies/php/classes/Yaf_View_Simple.xml�ψ\�����'dependencies/php/classes/Yar_Client.xml�ψ\�^�<�1dependencies/php/classes/Yar_Client_Exception.xml�ψ\m��[�2dependencies/php/classes/Yar_Concurrent_Client.xml�ψ\���~\�'dependencies/php/classes/Yar_Server.xml=ψ\�o;j�1dependencies/php/classes/Yar_Server_Exception.xml2 ψ\��,� dependencies/php/classes/ZMQ.xml�ψ\�� ��'dependencies/php/classes/ZMQContext.xml�ψ\��h�&dependencies/php/classes/ZMQDevice.xml� ψ\��g -�$dependencies/php/classes/ZMQPoll.xml�ψ\�"�� -�&dependencies/php/classes/ZMQSocket.xml�ψ\�h5�'dependencies/php/classes/ZipArchive.xml�@ψ\����ֶ&dependencies/php/classes/Zookeeper.xml�7ψ\+�R�E�=dependencies/php/classes/ZookeeperAuthenticationException.xmlM ψ\�<p5�,dependencies/php/classes/ZookeeperConfig.xml]ψ\�s����9dependencies/php/classes/ZookeeperConnectionException.xmlE ψ\��]�p�/dependencies/php/classes/ZookeeperException.xml ψ\�J ڶ:dependencies/php/classes/ZookeeperMarshallingException.xmlG ψ\���S�5dependencies/php/classes/ZookeeperNoNodeException.xml= ψ\���u,�?dependencies/php/classes/ZookeeperOperationTimeoutException.xmlQ ψ\�=� ��6dependencies/php/classes/ZookeeperSessionException.xml? ψ\��|�J�!dependencies/php/classes/chdb.xml�ψ\=(AǶ"dependencies/php/classes/finfo.xml�ψ\�K�KG�,dependencies/php/classes/lapackexception.xml ψ\�y���(dependencies/php/classes/libXMLError.xml@ψ\�C�4>�5dependencies/php/classes/mysql_xdevapi_Collection.xmlψ\S��ٛ�8dependencies/php/classes/mysql_xdevapi_CollectionAdd.xml�ψ\�F�e:�9dependencies/php/classes/mysql_xdevapi_CollectionFind.xml� ψ\�q��]�;dependencies/php/classes/mysql_xdevapi_CollectionModify.xml�ψ\���ﴶ;dependencies/php/classes/mysql_xdevapi_CollectionRemove.xml�ψ\rSVo��7dependencies/php/classes/mysql_xdevapi_ColumnResult.xml4 -ψ\R����4dependencies/php/classes/mysql_xdevapi_DocResult.xmlψ\-����1dependencies/php/classes/mysql_xdevapi_Driver.xml�ψ\��Д��4dependencies/php/classes/mysql_xdevapi_Exception.xml�ψ\��A�:dependencies/php/classes/mysql_xdevapi_ExecutionStatus.xml�ψ\0�Y��5dependencies/php/classes/mysql_xdevapi_Expression.xmlnψ\)��~]�8dependencies/php/classes/mysql_xdevapi_FieldMetadata.xml� ψ\Zh��9�1dependencies/php/classes/mysql_xdevapi_Result.xml&ψ\8̶A�4dependencies/php/classes/mysql_xdevapi_RowResult.xmluψ\?� �d�1dependencies/php/classes/mysql_xdevapi_Schema.xmld ψ\�]��X�2dependencies/php/classes/mysql_xdevapi_Session.xml�ψ\dependencies/php/interfaces/MongoDB_BSON_ObjectIdInterface.xml�ψ\,�x�߶8dependencies/php/interfaces/MongoDB_BSON_Persistable.xml�ψ\C�,���;dependencies/php/interfaces/MongoDB_BSON_RegexInterface.xml�ψ\+G�hg�9dependencies/php/interfaces/MongoDB_BSON_Serializable.xmlaψ\N�L��?dependencies/php/interfaces/MongoDB_BSON_TimestampInterface.xml�ψ\0U�텶1dependencies/php/interfaces/MongoDB_BSON_Type.xml�ψ\~U�ل�Adependencies/php/interfaces/MongoDB_BSON_UTCDateTimeInterface.xml�ψ\"0���;dependencies/php/interfaces/MongoDB_BSON_Unserializable.xml�ψ\r�Dj�Bdependencies/php/interfaces/MongoDB_Driver_Exception_Exception.xml�ψ\���"�Kdependencies/php/interfaces/MongoDB_Driver_Monitoring_CommandSubscriber.xml�ψ\vK�jz�Ddependencies/php/interfaces/MongoDB_Driver_Monitoring_Subscriber.xml�ψ\���M�-dependencies/php/interfaces/OuterIterator.xml�ψ\>�zN{�1dependencies/php/interfaces/RecursiveIterator.xml�ψ\V"hݶ)dependencies/php/interfaces/Reflector.xml-ψ\����0dependencies/php/interfaces/SeekableIterator.xml�ψ\Q 9��,dependencies/php/interfaces/Serializable.xml+ψ\D;��r�7dependencies/php/interfaces/SessionHandlerInterface.xml� ψ\S2>E�2dependencies/php/interfaces/SessionIdInterface.xml|ψ\���S�Fdependencies/php/interfaces/SessionUpdateTimestampHandlerInterface.xmldψ\+*@b�+dependencies/php/interfaces/SplObserver.xml�ψ\ ���R�*dependencies/php/interfaces/SplSubject.xml�ψ\,�a'[�)dependencies/php/interfaces/Throwable.xml�ψ\��rj��+dependencies/php/interfaces/Traversable.xml�ψ\qen�+�3dependencies/php/interfaces/Yaf_Route_Interface.xml�ψ\���1��*dependencies/php/interfaces/Yaf_Router.xml ψ\�Iv��2dependencies/php/interfaces/Yaf_View_Interface.xmlPψ\�v)p�8dependencies/php/interfaces/mysql_xdevapi_BaseResult.xmlEψ\�0L}߶Cdependencies/php/interfaces/mysql_xdevapi_CrudOperationBindable.xmlψ\!eV�|�Ddependencies/php/interfaces/mysql_xdevapi_CrudOperationLimitable.xmlψ\�@�Ddependencies/php/interfaces/mysql_xdevapi_CrudOperationSkippable.xmlψ\R֞��Cdependencies/php/interfaces/mysql_xdevapi_CrudOperationSortable.xml�ψ\O�D9�<dependencies/php/interfaces/mysql_xdevapi_DatabaseObject.xmlψ\�i�8dependencies/php/interfaces/mysql_xdevapi_Executable.xml�ψ\�~�&r�:dependencies/php/interfaces/mysql_xdevapi_SchemaObject.xml�ψ\�\���,dependencies/php/interfaces/pht_Runnable.xmlxψ\��vendor/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.phpHψ\e��;�;vendor/nikic/php-parser/lib/PhpParser/Builder/Function_.php�ψ\�3`�m�<vendor/nikic/php-parser/lib/PhpParser/Builder/Interface_.php�ψ\���!�8vendor/nikic/php-parser/lib/PhpParser/Builder/Method.php� ψ\�Db��<vendor/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php�ψ\��b�!�7vendor/nikic/php-parser/lib/PhpParser/Builder/Param.php� ψ\��yg3�:vendor/nikic/php-parser/lib/PhpParser/Builder/Property.php� ψ\��I��:vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUse.php%ψ\ -�jB�Dvendor/nikic/php-parser/lib/PhpParser/Builder/TraitUseAdaptation.phpXψ\���z�8vendor/nikic/php-parser/lib/PhpParser/Builder/Trait_.php�ψ\D�T@B�6vendor/nikic/php-parser/lib/PhpParser/Builder/Use_.php�ψ\�ˣ��8vendor/nikic/php-parser/lib/PhpParser/BuilderFactory.php�%ψ\�s,�8vendor/nikic/php-parser/lib/PhpParser/BuilderHelpers.php�"ψ\��8%�1vendor/nikic/php-parser/lib/PhpParser/Comment.php�ψ\�6v�5vendor/nikic/php-parser/lib/PhpParser/Comment/Doc.phpgψ\X���ѶFvendor/nikic/php-parser/lib/PhpParser/ConstExprEvaluationException.phpVψ\N�o 5�<vendor/nikic/php-parser/lib/PhpParser/ConstExprEvaluator.php�#ψ\�>~(�/vendor/nikic/php-parser/lib/PhpParser/Error.php�ψ\�#����6vendor/nikic/php-parser/lib/PhpParser/ErrorHandler.php&ψ\���E�Avendor/nikic/php-parser/lib/PhpParser/ErrorHandler/Collecting.phppψ\g�z|��?vendor/nikic/php-parser/lib/PhpParser/ErrorHandler/Throwing.phpjψ\��gH[�;vendor/nikic/php-parser/lib/PhpParser/Internal/DiffElem.php,ψ\ �j�F�9vendor/nikic/php-parser/lib/PhpParser/Internal/Differ.php:ψ\i�_��Lvendor/nikic/php-parser/lib/PhpParser/Internal/PrintableNewAnonClassNode.php~ψ\*cd��>vendor/nikic/php-parser/lib/PhpParser/Internal/TokenStream.phpqψ\�rM��5vendor/nikic/php-parser/lib/PhpParser/JsonDecoder.php� ψ\v�{�[�/vendor/nikic/php-parser/lib/PhpParser/Lexer.php�9ψ\2���9vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php*"ψ\ ��� �5vendor/nikic/php-parser/lib/PhpParser/NameContext.php�&ψ\v ��l�.vendor/nikic/php-parser/lib/PhpParser/Node.php�ψ\DH -��2vendor/nikic/php-parser/lib/PhpParser/Node/Arg.php -ψ\���R��5vendor/nikic/php-parser/lib/PhpParser/Node/Const_.php�ψ\��^�K�3vendor/nikic/php-parser/lib/PhpParser/Node/Expr.php�ψ\f����Avendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayDimFetch.php9ψ\p�����=vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php�ψ\�>2��:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Array_.phpψ\y@.�|�:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.phpψ\X�\?�<vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php�ψ\Sy�a�Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php�ψ\�4�)̶Fvendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php�ψ\��W!�Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php�ψ\�F�%�Evendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Coalesce.php�ψ\�wy�Cvendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Concat.php�ψ\�@-t��@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Div.php�ψ\�K�ֶBvendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Minus.php�ψ\����a�@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mod.php�ψ\��M��@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mul.php�ψ\�FH�b�Avendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Plus.php�ψ\��k��@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Pow.php�ψ\��eжFvendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php�ψ\���8�Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php�ψ\�Z��=vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignRef.php4ψ\p��̚�<vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php[ψ\�ψ\��.Jo�Fvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php<ψ\��!�G�Evendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php:ψ\�oR�նCvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Concat.php5ψ\�Ҕڧ�@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Div.php/ψ\�}bz�Bvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Equal.php4ψ\�}�uz�Dvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Greater.php7ψ\���x�Kvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.phpFψ\�a�)�Fvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Identical.php=ψ\���{�Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php?ψ\���{ζFvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php<ψ\�0�t޶Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php?ψ\�c��Bvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Minus.php3ψ\�[�J�@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mod.php/ψ\��� �@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mul.php/ψ\�����Evendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php:ψ\����ӶIvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.phpCψ\�����Avendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Plus.php1ψ\�Ӏ�/�@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Pow.php0ψ\��jH��Fvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php<ψ\������Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php>ψ\��� �Dvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php7ψ\�� ޯ�Kvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.phpFψ\��0��Fvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php=ψ\����>vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php�ψ\6�A�>vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php�ψ\7�]n��8vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php,ψ\�P鵶?vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php�ψ\�g,Gd�>vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Bool_.php�ψ\� ໶?vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Double.php}ψ\���pn�=vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Int_.php�ψ\�uh� �@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Object_.php�ψ\����@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/String_.php�ψ\�����?vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Unset_.php�ψ\��2���Cvendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClassConstFetch.php�ψ\��}$�:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.phpwψ\1�Y��;vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php. ψ\��Ǘ��>vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.phprψ\{��VĶ>vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ConstFetch.php�ψ\9Ò0�:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.phpzψ\3��Ht�9vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Error.php�ψ\~…���Avendor/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php�ψ\7�?��9vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.phpwψ\2PX��9vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php�ψ\j�1e��<vendor/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.phpVψ\kSﶰ�<vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php�ψ\�$��e�?vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.phpEψ\lF芻�:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php{ψ\9-ʼ�9vendor/nikic/php-parser/lib/PhpParser/Node/Expr/List_.php�ψ\V�EnĶ>vendor/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.phpmψ\��i ��8vendor/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php�ψ\�SU���;vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.phpzψ\9 _�{�;vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.phpzψ\;G -u׶:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.phpsψ\5p��G�:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.phpwψ\:8>Es�:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.phpzψ\2��D��Avendor/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php�ψ\��Ù��=vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php�ψ\A���Z�>vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.phphψ\���9�Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.phpψ\����;vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.php�ψ\�����>vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php�ψ\7���=vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php�ψ\7EB&��<vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php�ψ\6/�E�=vendor/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php�ψ\>?�|��:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.phpHψ\d�r��;vendor/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php�ψ\ -m���9vendor/nikic/php-parser/lib/PhpParser/Node/Identifier.php�ψ\,��>�3vendor/nikic/php-parser/lib/PhpParser/Node/Name.php<ψ\�#�]�Bvendor/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php�ψ\O�aX϶<vendor/nikic/php-parser/lib/PhpParser/Node/Name/Relative.php�ψ\J)FCs�;vendor/nikic/php-parser/lib/PhpParser/Node/NullableType.php�ψ\^y�W��4vendor/nikic/php-parser/lib/PhpParser/Node/Param.php�ψ\:��5vendor/nikic/php-parser/lib/PhpParser/Node/Scalar.phpbψ\]�f���=vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/DNumber.php ψ\��Et�>vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/Encapsed.php�ψ\G����Hvendor/nikic/php-parser/lib/PhpParser/Node/Scalar/EncapsedStringPart.php�ψ\K�qs�=vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php�ψ\�<1@�@vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst.phpNψ\{N?�Gvendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Class_.phpAψ\�T�z�Dvendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Dir.php:ψ\�!qp�Evendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/File.php=ψ\�i ��Jvendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Function_.phpJψ\�(�de�Evendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Line.php=ψ\�� �g�Gvendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Method.phpCψ\��~O�Kvendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Namespace_.phpMψ\�u���Gvendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Trait_.phpAψ\���d��=vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/String_.phpψ\�U����3vendor/nikic/php-parser/lib/PhpParser/Node/Stmt.php�ψ\fn��:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Break_.php�ψ\G�J���9vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.phpXψ\t'uc3�:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.phpBψ\�$Cꭶ>vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.phpψ\c��a�=vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.phpψ\���1��?vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php�ψ\cK- V�:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php� ψ\�+�-�:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.php�ψ\<�X���=vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php�ψ\IL2�R�Bvendor/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php�ψ\��Շڶ<vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Declare_.phprψ\jC�7vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php.ψ\c�QӶ9vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php�ψ\<sܶ;vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php5ψ\iu�9vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php�ψ\;��ö>vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Expression.php�ψ\NFw�9�<vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Finally_.php�ψ\=4�2#�8vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php*ψ\���C��<vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.phpZψ\?�!;�=vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php�ψ\�o�O=�;vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php�ψ\L�3 �9vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php�ψ\_V����<vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.php�ψ\���ֶ@vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php�ψ\O��쉶7vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php&ψ\���玶>vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php�ψ\8�mٶ>vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php4ψ\���9vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php�ψ\PX���>vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php�ψ\�E1���7vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php-ψ\��u��<vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Property.phpbψ\�2_�}�Dvendor/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php�ψ\�;A�q�;vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Return_.php�ψ\<���g�=vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php�ψ\}���;vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Static_.php�ψ\G_4ú�;vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php!ψ\a�/�:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.phpQψ\�v�Y�<vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.phpψ\�+9���:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php�ψ\BV$���:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.phpFψ\D\�.�8vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Use_.phpYψ\b�M���:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php1ψ\f�w���@vendor/nikic/php-parser/lib/PhpParser/Node/VarLikeIdentifier.php�ψ\(j�F׶6vendor/nikic/php-parser/lib/PhpParser/NodeAbstract.php?ψ\?���޶4vendor/nikic/php-parser/lib/PhpParser/NodeDumper.php\ψ\�N��4vendor/nikic/php-parser/lib/PhpParser/NodeFinder.php� ψ\:�8MH�7vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php`)ψ\�<-Av�@vendor/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.phpuψ\�}�Ŷ5vendor/nikic/php-parser/lib/PhpParser/NodeVisitor.php�ψ\�ZM�ͶDvendor/nikic/php-parser/lib/PhpParser/NodeVisitor/CloningVisitor.php�ψ\�z�a�Dvendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FindingVisitor.phpbψ\�h�ł�Ivendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FirstFindingVisitor.php�ψ\�z�JB�Bvendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php( ψ\e�Զ=vendor/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php�ψ\����X�0vendor/nikic/php-parser/lib/PhpParser/Parser.phptψ\4T�ﺶ9vendor/nikic/php-parser/lib/PhpParser/Parser/Multiple.phptψ\iO��5vendor/nikic/php-parser/lib/PhpParser/Parser/Php5.php��ψ\�C�Ұ;�5vendor/nikic/php-parser/lib/PhpParser/Parser/Php7.php�Cψ\N@C��7vendor/nikic/php-parser/lib/PhpParser/Parser/Tokens.php�ψ\[���3�8vendor/nikic/php-parser/lib/PhpParser/ParserAbstract.phpn�ψ\-"�Gݬ�7vendor/nikic/php-parser/lib/PhpParser/ParserFactory.phpNψ\#�up��@vendor/nikic/php-parser/lib/PhpParser/PrettyPrinter/Standard.phpN�ψ\F#�6��?vendor/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.phpv�ψ\R+\-kֶ*vendor/phpunit/php-timer/src/Exception.phpCψ\�/u��1vendor/phpunit/php-timer/src/RuntimeException.php{ψ\��%��&vendor/phpunit/php-timer/src/Timer.php� ψ\�����8vendor/theseer/directoryscanner/src/directoryscanner.php�"ψ\A �P��7vendor/theseer/directoryscanner/src/filesonlyfilter.php� -ψ\�A�f��<vendor/theseer/directoryscanner/src/includeexcludefilter.php^ψ\T�B�l�1vendor/theseer/directoryscanner/src/phpfilter.php� ψ\��FA�.vendor/theseer/fdomdocument/src/XPathQuery.php�ψ\4��߶7vendor/theseer/fdomdocument/src/XPathQueryException.php�ψ\��*��7vendor/theseer/fdomdocument/src/css/DollarEqualRule.phpψ\Sp���/vendor/theseer/fdomdocument/src/css/NotRule.php7ψ\���ꙶ4vendor/theseer/fdomdocument/src/css/NthChildRule.php�ψ\ [4���1vendor/theseer/fdomdocument/src/css/RegexRule.php�ψ\����s�5vendor/theseer/fdomdocument/src/css/RuleInterface.php�ψ\��,��2vendor/theseer/fdomdocument/src/css/Translator.php�ψ\F]hX�0vendor/theseer/fdomdocument/src/fDOMDocument.php"[ψ\$7h&�8vendor/theseer/fdomdocument/src/fDOMDocumentFragment.phpIψ\t]�~�/vendor/theseer/fdomdocument/src/fDOMElement.php�8ψ\R ��o�1vendor/theseer/fdomdocument/src/fDOMException.php�ψ\s�d�O�,vendor/theseer/fdomdocument/src/fDOMNode.php�ψ\�VGQ�-vendor/theseer/fdomdocument/src/fDOMXPath.php�ψ\��!X̶(vendor/theseer/fxsl/src/fxslcallback.phpN#ψ\� -b�|�*vendor/theseer/fxsl/src/fxsltprocessor.php5ψ\� &H)��Y[o�6~ϯ��� ;�^��mn��Cдۋ���h��Lj$�$[��wH�%Q��À�%��s��9<���bU���9$�J�TݪǂHt�~�1�&��)A�V�1� �p��W����9�s�*.�WlEU���#�<�q���0"���d ���ˇ��r��^��C�41Zc��iQ�4�z��Rª���j��ϩ`����$z�����~����Hy�(�r�v���:�i�8K��1s�QX�D��$ �D�@.��|�ْ\eqxhh���� �U��^H �~�N�&ey���{�ĤW�5y�C4<2���0C(������K&#��+J�G0هOtM���$�)\�QV�6�S<���dh-T�*�KA�Ц"��� �.�b����qf "5�ko�s�+�PNf��R86I�h�m���XQ#�i�>�fW�t2����Y�ά�inJ�l�P+��yܗ��N����h ���n6e�ف�FP�K����^��x�&�0�d���'�Ĕ�m�==i���\By`j� ��� -���I邒l8vz��V1��i���P��:U�(�� ���A[����a���x���MS�>�bi^f��wp�ǽ�d��o ���o��ur��d1�8������B�K�WȮ�cQ�;����:�t%I��p՝H��:���������M���V�JS=0AC=�%�J��Nfh�U����s�ސ����-=�b��׊��2 -S� �X�ty7�|�n�F���U��D*D��@ㆷ ��"�� d`��t|�}?���K������y�K�Oe}��������9��� C^ ����kT�3�#ܑ6���g��-��8�����$ސ���S2^M4���=$P�lk ݊�Pd&r����7GsH�C��9�y���q�!�[��s�'�8��� �3�^�.�>�4�DɨrIbMmN�HA��a)%>e�Z=dd�&�w�E3�jea���(;m�|a%� 8�s*o��1����� ��u�C۝@���ȏ��Oѯۂ��0i� ��z�������|�W�ԋ�9�0���� 8�Qj�9}~�ɨS����廃Q��"R����j�A ��ٜ �6�<-�0�چtioR���{SM��_�7՗%���o�]�NJ�w%lI���h�� 75$��f�]=c���uFr(+�X�MB���b�����M3����ZZ*�����jÚlEu�/3��e��R7 A��<Ә�Y� -�3:4쇈�# �� -N�>A����x����1~�,����oX���ٮፆ��C�� -��-��S����� ���N����4�\���_��JXK��pVv jw��NOG�[����q�Ͼ����Z͎���öe�,Fp)H�r�kǂ���f�-�U1��h��"�S�ߎ-:�PK��0���L��.�2���92~��^�ú�/C�V��y�c/�]��jP��_���c #@O�l�4Ł�rٽ�U��ز\ݘٱ/�Ƙ�Y�EC��7.׶w w��e�����ϱ� ����z13���4�Ј�$(��̐�f&�Ctu����O0�~V3���X~�]��4�`"i�ÿf�Z���f�ݭ\��t��9�on#=�oK��m�s^�͹� -���fk�6���߳J]Ux�p��za��(Rz�6�!d�G����}�= �@ ��~EF����`�N�M�3 �a���V��n�,f -�y�\�&@E��H#I�a:�G �Lƙa{%  � �D񨁭�3c4#�Z�69�y�> P��+��w�4���j@ϒ���m]UFܺH�|||���z� �;�\;&EӿH�h(*� ��+�wN�q�f>dJ�E�/Zd�e޵Y�o�6����jx�\�ɞ_�%]�G[,i�:�0�@@K��V&�Jb���w$%Y�NW@[���<�.��I�)D4L���Ғ��V/S��~��q��*%!��9S*'�p*��2�z�^]�_g!M5 P�Rpr���|�l��B�������D-?"�w�쁆gR -i��%�w���h��$���uT'��lV���Un ���~m��k��H@PL -��\���0�Х+�5"��R�?��[l���P ��h i��&9��K��b��8㡵����Af�*6Z{�ޠ�S���`���35<�xj6Irq��Q��Y�� +-1�[��$�� ː -b�1y�JR����ixAf<@�+� -��}�GM} ����**j u�*���l2IQ -Gk��q()�- �Tg���I<��P�� | ��w gF9����_i�t<��j�]��✦4rbw�Z]��L���i����L�Y��v)"���X&>�������4���%��4/�ٺ�waךr�Y��K1>��I1"�^��4�8o��Zз/����3��^��.IS?��i�e�h_Rv#�Bc�%��f�gN��|��cq"�����6+F!��*wp{{����-B���L ��) �R�}<�{����/s��w�=cX�J�u�%� ��l�KeA[u[)�m�����bQ��� ���"�`�?s�7�-�Bu��^�ޠ�ܜA��g�7�2�ݎq�R}HW -���]��;�Z�6Ҋ��o�`MZ��6�⹇^�( �PK�k)�`y1� �xR���98:.��b��r���5�ڔV-��"�W�.�VX�e���9�3zJu^�Z������_O�������-bP�W��\�+�,��j�gk���}�����l3��VHa!剐�&� ��σTύk�&;�)���"ś��;O:���ӌ%�8[,� t#P��#��l6C��Y5��֝n{5����iL�7�g�?�7R.0�4,E&���5T�"� ̤ď���@�x����׌C��7�m�:���x��{�b�T�m4�n�6Ԇ�C@f�%Ʈm}�P��6}�pe��?������6/d���~!xs<���U�㠼� k���f��>C�/�A�ME��b'l�"�L����e�is��9�h�+�2�}�Ѽ���?vH�5p4�D��'�7,¶��򕩶]��>*�`�fs ��d&(sa��qK�*c����"_�5���[E�B�O�ץ�Rl�=ћܝo�;*Y�t۰.�a���=q�m6�ҳ<�fL:���&`Z�;0��զ:z��l�ѥu9y1�ơ�[���J�y�$�EvV�����Mޘ!�� -�|��1n� 7s)��)�� -�}���L��G"�)��0�a�(.3���r�W3ew�E�1ښ��5Tf$lۼ�6-�b�Sά� �C�0� -+�|�V�ڽ`za3�J٭ >�F5'Nf���-���Y8�BN5�9V+�8f�ɱ��,;��6���WW3��)�/z�� �a@o���<�Y)�ռ�v*q[�AF��zx��DT��g��A?��`��7J8�A��v �����r�LnN��L�7���DLI�H�a�\Ѧ�;����L�v -_�4V�Ss{�����o� -�r��j>���E2�� �%���q��Xmo�6��_q-�Z�l��&�e)6`[�y��0h���ʔ@R��!�}w|�d����$�x����r�c�U��`�GJ�<� �\q�� [sU���_�r.Q��r{3��Rp�ۯ��K����d�a���� ��<��T<�O�e�ǐ�"&X,b�Ѳ�uĤd�c�i]�,W��t&h�% ���^T�Xr.�.�亖noo�> �Vl�?�-)�0SO��3��:N/a�Ҏ��x��\$ -o�Y� -< �)� � �DR�\���*��_�.k]��h�5�㟂�E�F}克��!.E�1]�M���؄0�6�{�P�xN�e\�ל���rzʸ�6s� ��S�$)��@=���F7�~zw��ِ�n8?j�M�s,��oXQ�lv�ZE*��e�z�99��h��4;V��!a�~Zkg��P�f�[�Y 1Zk ��z��+�ud�f?��g��A��L`8����,O��(X�8oqg5�ʔI�>�ny��ϖ�B� ����6�M{���8d�ۉi=� ��^�}"��9�0 ���$ -��-ʄf��$-�I���L��4U ߪ��4���g����8?>e45i��`)���y�c�����h8 -�.��ׇؠ�p?���r��%�ct/�}7���^ �Y�Ǹl��;��J��������[�o�V�.Ŷ���!��� -�H�e�\���Ȼ˘ �w}���D��SH�Kf1v� -����AW�i� [�3�#�,��}���z� ���j�)��?Z�\H�!+�B+�ϹVPrϬ�-:7�y�"���1#� X�"_wq]��&+�PZ�u��� l��Q<��f),Zʙ���k�� � ��� -�w���B�h��-ʍa�-ឲT��i)�VY�[ -� t֭�H������ݚ�����ˣ�J��Q~ ��S����Xi�v\ʹE� �[��AT��z߸7+f[�uؖ�v� ����פ���>T��`P�|Ds|]�'g�P>������GwJ{�R�Mi>X8<CC%G��LΘ/���h�C"�IS�Jz�5L�~5}�oE}�,��谟^��Py�s��I=��(%��n�u;��6ݡ�$U�e���.�#ڢ/�� i � MN�ީ\(|fy�����@��}X+V���H������� �P�4S;+�/U:L��3��Y�ը��Q���z�Z������2}o;�-�����V�n�@}�W�P�$U��^hB!!T�(�6�ı��Z�� -����R{��N$��xv.g�\����M )&�8UZ找�O%*8����g��J� ���wDyC����hT�Xx�A&Zț%K7��y}�������R$�B$wu� ���%3q?�,�X�w���D��N�N��0:>:�,���+˜g���k!Ab�+���V.c5�,��p0�BɤB�ș��c�+�B(�yJZz�[��c�79��Uo�$+-�э��� ��i�s�Y�[ߌs��9�K*�c�XT���9�gZ��(�/Z��k(���C~��rj���˂�;�d~���b�ߤxn-Q>�<��!sX������7�O�'ȉ>z��/�{���#|[�E�ß�Q�U6�\�%r��p�le�5�c��.��T�ѱ�-e��4�xMƋ�=������bpȕ�+�?C@D#� ���k���4fqW>��F�R�'���`:� =3�}ԙ]����8���������*���v����i_�h�`]�����M��4���j=���) �S��J��N\��J���\}Y�ƅ����5ϘV�z}�.��(�I���Z���hR��v9kfqf�h�9Ȱ}�BN�o��Ϩ���:3��k��ٽ��P;N�t���B�8����Ɍ:S��=X��4^���V*C|�mv]�b6����e�����c��*�ƿ��TAwxX�����)�_+�Ԯ +*]c�﬜�r�|�Ę��LkfĔƔ�������uR��޺�h���w~��vrذ�5pd�?��ٺ������}�?@�unU�ww�m����Eͱ -1��~�bK-�OD}��@b2p����=���n�����e��U��j�v��@yǛ�@�=���b�995�X2�rP���M�'�=EϾd5��Ċ�� ��6Ї��Q;O�0��+nȐJ��\ZD�C�0F��si,%�e_���c'NPJU�X������7�2P����ԑ��r� :���b�o�.^*|F��'��5c�;�GTh9i�ݫ�T�=p�opO��nDwZ�7���H�d��X�� ! ��VN*=ܾ�R@�*AR+�s���k�=}97����*�J��6<��h|ҏ�ПB���|٘T�Ůۇ�Ӥ��u�[myQ y#�;�";N�"�V "����pE��a�.�x>��� �|a9?�*��R�K��Z����u9����:�/��OO�0 ���>��Iہ��������4ҖD� Th����ZU�K�g����[[(Q��̓S� -j-z���b�hqDo�Dx����l�3��$i�T�Q�d\~�9��΃��nAx�(q��g}g 1���V�W\֩wAi�`�H���Nn^JB�hI�h( -i4�5��ξ� �8-TJ��m0��=�Gg��}��QG{�#j�r����Y�-?3:歶�,{�h9�-!����R��O�e��ФD�����C�*�� �c�sƔ}�8?�{�+��ۓy����;%ߍ�AK�0���s衅���u�"x�X(c:e�m��b���&5��u��%�%��������I�h)sl���C6p���9���mG�D���\'I�b�բh��(�P����I��7�a�� ZG�К�X4F�W~+��)*�Yj�M'7͜5��{+4��1T��ʇ������e����R�Iwu�k�E��,ƈ�l����pOv���S] ���,+�o`�e�&�z����-ږ�+�I��rP�đ �����s�&|����XS�9�)mB;$ߥW]o�*���`S���U��f�9:��&mZ����*�q�� p���`lc�٢�$�}���ⅼ���6� �F�@!y���5D�x�.��h&�fK� �|`?���� ��/���򇊛׻k�(��XN�}��?�Z����$�T� [#.�G����FV�F����|�%�.�9�p"���h�������A4�aeP� P�B�^$�c����~���O��)�]�0� ���$��g�+�8��g��s���ӥ���a��'{�f8t��MI(f�Z��>0V���>��F�]��C�Vw��5���0M��kP�����QE�b �5����ʒ�r��~e��m#���m%ί:v�f5��gd������VC�<�ޱ�j,�/98m$��h�}M#o)�G8ݗ�rH����Bh�I��W"�Y�1Q�0 ��2e>�k#�c*zq�x���(��-Ռ@�~���uj�˰V��Ho���t�f����-q���g~n]f%N�8J N�?n;'(!�X��(� -* -�����~��W>�/�ac;.�� �X?j�Ȋ�d9Y��<|�.8�pe��G�D&1N��o^4�=65���n�ȖSO�`3o� U�����S昵Tžy��� -�9(�zܓ0C=��'��x�Į���`#��=;��p�����ԇM����㾲[ Ȫ�;�MtS�n(T�L�j�o��m��L{�D�# �� ��jk9�L;fmm:?�p�ƶ��:�R��^_7�j�XH��*Œs]�a���CĠ��-��O���}8��q�����Q $Ё��hn8��1����?����������1�m�=�H�"�muA������"���ۅI���`8�n�m�Fr�����N1�BM*Kdhq*����<-�Y8 zg �W��Q��a��1zS�Zi05�RQe61�V�DJb�T%�E�V����m�1ᄇ �j��3���#�d6��E^^�w -C�/���=���o]α -1�>_���֧X�v���{��ٰ�D�w�F�)�y0�e� u������hA�i�r��J`б�P?�� ~%)��h�ܨ��۴��(�0T�����m�ML����OGc���"K�J{�Y��VU��q/��Z�o�6~�_�-,NӾ�k������S�D�\lQ ��F��}GR�(��d� ��z��x�����H�߳e��0'�����J�2"�k�r<�xMD�c�.�� !|��v���V$��O��(��g��3o�$��Jw>�<]N%Nc�s��J}�l���ł��N���9����-M|�O��+�Y �����yT"�9@���$�b�� b���e4q|�s�b�IeTh�ׯ� 7��Dnx:�m������DF�i����g��hi�,�s��q�W*�6uk"�9�D*�鈀M���OJD�N5g��x�l *���p6����DzD� (�s�;�O�X��J�&��Za�DZym��}�#�.У�@�"�(sU�LT`����h���iX�$��R����Tg(�G1����Ҁ�!���qʋ����b�}���r?h��|����%��r�h��m8�RA+p�<}�sl-"Y��11��p$�‹ ��0~$)�h�����O�o���/~�@�4 -��1:��"� ��1�c��,(��i��=�H�U�l6J0�x�G%����$�Usّ�-#u�a(�_=e�b����[�vo�+WK3L��V�,gN�(�I���.C�r�����P����@��� -ɪ3"إ�ܘ�� -fS��������lS���e�;�J��4쵗�a�]U59K�z`)i=Q��LrF}��Y�@�\$j�<��c[�� �̾����Ԑ҉�x8ȷ�]H?.,�6�ֽ�#<�A&D�K��i/' *$�1�+o��q{Xk�1W_����l�9���=E�m��o�6@�a*��V��2́�� P*o;!�uŠ�> ��Ƀ��g�CH�!g�z�+�qi;����4Ђя:�y�s$�ᐵ:�7pm�ߏ�~�����} ٯ��������m� -���l�H�2د(9��8u��4���C4�:���y -�8&���� ��@F�3�s*�.�N��I��Uh�L �ݒBJP�0z&F�ts�Q���}���cn�>���{��2'�{>�rħ�D�^u@Yގ�Q�ߚk_t���� K4�:F=G� -Z�Ϸ���s%�d6�+rE���*:`4x��}oIGňJ�w6i�����Y�V�a�ը܅��!��zڄ�����!��'�Z�(1eNJ/�>4k��k%���w�tM��ב��#�O��g�0�k���m�����?�}���fX=��t������� -�ק6ٽcֶkx�},�R=^��C����K��E#=��d�������TMo�0 ��W�P v��ݡ�����À`�1@��t,D� INR ���'��!���H�=Rz�Rd$�%3XgwK�R��)|'�r���3|F4 -x��ţ���f2����MH��RM^�m-<��p�� �@y!1G�,,:��*��2�p#;�9�[ßę׉P������`�����ظ�:M���CZ*�V�\r�(��.8ؕ4��d[��8l��u�2adz6����:Չh?=p<��Gxȁ�����KcH� ��u�W�AW -w=pR�x֥�����e~8�pt\����^O���9�����L�.��nÇ��g����"���I�i*p��i0]{�PȜ+��F0R�✩��Uܒ�|�����A^��ɿ�8�ոk���Uw�_@��o#�iR��.�it.����.=_[r�֎�fS�Rz[ʄ, ^ԉN ��9:ө�]+�c�|cA��4���:_X�2)�+E�b/ȴ�Z�g��E�a'H�5�Y��Xz2L%�� -�#�=J�6�Ѫo�����O��ؓ?��E�jU��}h���N��{ ��ߧ�؏ά�ҿ{9k��\���}�}�O �@���)��A%��YA�"; -��#.�����$�uj.ox�~o�k]k(� j0��p�n��ha�(%�6h5e�3D�w�^=��S&�RvGY��x�'�)o��.ɥCS��#}��'�n�8b�hjh�R� ��I�v<'�~l��d�+ b1����N��%Z���^�z�R�"o�RMk�@��W H"��k�/Z{(�1 �fl�q7�N�"��nbl�FJ�N˛������}�����o�R�s��h����Qb�&�-�WD��a��ѓ�2��)zr�*�0�DT�/�0RCX��i �{,��>2`E�C��t� ������=a -_^QA����Ӎ�����=3���!���<��QY��g�>���� ��SH�>��S��)B Y��)�].�I����IaM�3��bD��ř�$�g����*.ޏ#� S��Z7�>�"�+�Ý�m�=!�C��Q�'�����l�mU��֍+t��'x>!�y�ιA?����pբ�J5���]�X�Jr������j�[��Y3m%�b{���"�^���?D�$�%�"X�7M��x�zQ#�]o���� f2����"���!�zr��sU��6�Ԭ �O�J�Y��%�)�J���URi7T|`wh����A�6z�w���$�˗3ؗ~�E�� -�@D���-���"hlE4停��/���"�{VN9�{���!S��`�VF���2�v�Y7.� ���7�� -�����6)����7�U�*]BQ\�s����6J",V7J�_����G&��Fg餺Z�~����/e�� -1D�|ŖZj{�p� �g�u�x���dOr��n��p�a��lvq�`�&^dI��,s� [X-���9"1�F����.��1IH�E��7�R��3 aJ��/�Ql��Ej$��YOUq�8K@�g�M�)믟�J����5 -��|o��Ro�SMo�@��W�����C��"��H����K��b��e�ڝ�DM�{�65���=���{��͌�(�2L���sdeJ+z*������;t�H��vɄ�9,'F)L���H���ax�0/ʹ���x��l� ���E��J ���� �L�#��� 8���6��Oc�����S:�nn�W�. -�\R��oS� ���4���it��›�>��v�L �.�ï��~�d -�S�FC�3���w�� �U�@{����HXwr��n�g����tT�N�����K��(:l���R�o����ڜr�S�4-��d5�/�q��f�d<_̦�E�xl��y~��⯟�f���^���L������5V�R �������h�[��ɡE�V�� �/�!^T�N#��f��z<���٫j��R)��ivgU/H�W��4lHM�ՂJ��E�u�� �V�)��c-/�o�V�n�0}�+� -�N��4 ��ҵRա��2�m�j��vh�Ŀ�q�a�����s�9����|��(#��2�z�w9(4A�q�� ��P@Okx� �p%���1�Z������q�W(@�u>'R���vMʠ��n�x��p�1� �� ��0Q�,�ʥj�3k&��7��� ��J���܁d����V�Q���sI�W���������%-����2� ����:1⾻��N��]7 ���^�bE��:-�Tp3��jwV�D�b���T^}���p�nL��h�8fu�n� �27hd�VB0�l�|�l�}�^K�ff]�����)�%�X���U�x�����Q���`:PA�]��ҕU�qx �p���ܫW����� �p -���Ej��F��"�09�$���=#��BIZ ,�RІA�)>q�N���j$A�#�w�� ��F�o�A��H�c�^�'I���D�g4�j��gקKnj�%��^�f�;۲t�x8� ��#J[>�]��2���V�r(-d�_�+���_U��l�W�>��;�5:<��Q�ڥ�킱#y/cϻ�(�Q �-�F����ai��O+�e�����;H�݆���V��c���T�� ��NFj�z�����9q,'|ᔤ��LeF=ܑ���cnzl�d���tmgj� -�?Zs���������3����;�_�A�vD�{�.Q`�� �Q�j�0��+�P�(�g��^�z2E�`QGҚ&4���l'-!�t+���}����*T����Z�;����4Fn08��5.}ɂg�-s�4����I�4U&D���&�m�ٙ\�� � /K�u����f���$�9������4���%�>����Kc6��'L��#�ހ6���1?�O�{ú5��5���GV\����א$}��I�Ю�S���=�`�?J�[��1_�j[�C�[~Ia2���yu�ð�$��zĸ -��?&s�����ݻ�Nt��kS�H�;�b�bWv�\��k� �-�B�M _����1�"K.=����yh޲��e�sQؒ�===�=��^�o�(�q�xT�e�W��Wh�^�v�h��uct~�?a\�Ḹ ��,�q]��(����`g��0�ݬgQY��E�Ó�uy�{�.������M�G�����(���ȫ�-���0��,͛j�,�'� -�6Yv�ge�2ʼn��D� �S;>����}�Z`�9Ȼ� g�uStA}"��_�G�S��QT!����9�R��č����z�������?�7��L�t��N�,��Q�Z�|��o����q�Ɋ�KȔ{�[��1�#�8abRn��j2O� c��v�<��~�g��ۨDa��9�^��mTc���2�(G0����o��,Z�p9��W�W��2dLQ����#���v�)�3��>n" y�I��VE3���f�j������ӓ�W��7�N�fz��u���8��A��������!�EM��IO��ϯ�OS&:���DA�&'nV���*�9^6q=�����D����;|v뛴�;�]�؉������ �b������M���Ŀ�4�B=�<�i����$�ǸБ�J��W�I�hDo���%�c\,��� 7�B��ʐ��wѓ)�O%��,�[�LQ����>?Q ������EN̶�`� _M�D0C�$�#� N�pݺ^T��� -���b_��5����-tʹYV6\�z�S��lĢ�X���.��>�"$�'S�� �$���:ٌ;��㪂��,0�S���ib�z����y -�e ^�CZ����y���iQ*��e�YY�qY�o�'L@'��g�a?�����f�����Q.���F�S����|�����O"�����t¸��oN02��o�`��L���� ��q܁H�,�1���?��ɺ�g~�Z͎�Lm� ��a��O5Iޑ�l�$5�[��&IF%c�_L"'y�'��CJ`Рu�rՇ`�V흱;�kQߦy�I��\�VW���Hcg���^�4/��X �E����H�aFUKjC�e�!�� ��;'#����ؗ��e���a��{�S1zt��9��,#�`�Q^vD�J�-���$�C%E1_�lIk�E��jjZ<Ӓ��� 8�5G�o�?��}������0���h]G`Sfy������u Ң��6=>*Y�+����� �h��b�M�<�h��&�u�a��O�!2�d/_�=eG��v�i�UV���)9w|\��ڊc� � �QRͿ� �d3`��B���L4)_ �E���:G�tɲ���5�y�͈�-fɬĤ���a��80�QR,�~���]�5$L;FikK�7e�c��IY%��@�v��"A>����"�wk��ET0#l��_*���D�+�g�J;�>�`��/��ޔ��LE!k�Qm��H{F��D�yM�ٌS1̱��d���= bQ��/�S�5"|Xy%Ӟ��縌2��o��'�0͐�2�A���ƀ��`�� �c����!���(��#0�_�T��Zh1��Q�)��*r�bKE�Xd��i,���V4�Z<�М���(���T�t�V�"������OA��5�L2|��,# b|�9�WeF�x�[��BCS�g).�z�.��A������Q*g��#���S�ĵml�<��7���X �E�B���B���5Xv�I#����%� � ����2+�~��X��E��w�� -涓K�\˩6��V�>�&BQ�y���2D}ՂJ�n>[�m4$9z��ݫ玲z"�U��i�&�p�����j 5��҃�-��{!������!cP��������u�VZ��2��u�i��ZZ�=O�&x�� �Bjo�J��D]}mH��/5wȧ���U���Dh�~9;,�`��&��|/��g�T'o�����fJJՅ=2��#�K9��t�˨��eָ�$ `h���:��}]�]zL�ni� 9�]����kG���k�@R(qUd�8�N*�]D� 7�B��hd�CnS --� ns3�N+���\^WZ(� �Gq��_�n�"#00~�:�^W)������i�w�@k!�c�� -ái�k��G�����J�&H�Y�.�� 21E�GJ�����w7\���g�&�Z�� o��X�! 4�4��z2���3����r-eg �Ar���-��r�-����z5�����?C+'I,�聝<�x���`�˓�AlM#o��E����D���W`�x1@:��v�]��xOH�����7�����E��׮�T=M1�5�<� \Ñ�ui����7p��3�]�a�{E@��l �"eh�TE�^C�J������5$��� '߱� -�ZmeR��k\�s��nm% E�)��M�՞���q��!˗8�6NH�K]d�WH�[j� ���Ϋ��9���/޽ ,���8N&�m������B�^���,��ܰۀ��1��NR��z�8���ĶN)_< !A�v^n�}U�A��|�J�muڸ^o�Ļ֎Vm�����3�i}?�.9����<��d %l�`tI��ݹ冕��T�rV �1#*������}� 2D�1O�4����cicVg)�4��z�� d����|��SekAj϶�]f�eQ"�;;D�cB���T�������Y��;�Y Bu�\l8��U_+ds5���]��@�w�t-��^;p��iX���ڤ_�pp[T�$��������[YO�J~�WT���AH�Fd d�h"r�t%@���i߸]=^(�ߧ���6 ��tۧN}g�S��?�+�ìLk8mں���� -6`���ߨ�%lVi���BX_������%�ZT�����z~����9DY��U�������[�7�G�����SQ���a���f-�r4 ��~�����_&���g��k����ï�!�M������@���99��Hm���޾]�o0�ݾ�`���Y�!X��o�}V�&�����F�%��:�X7i �$G�H[�`S�7�}}��:]r�}&� ,���U),&s�ͧ�yWem�*��Uf�e픲� -�������3HX16�Ō��|�9�N�E�l� ���]��|�E�~� -���G�\��T���I�"���6�������h�qR������!�qߺ�ɻ7o1���٧��%� �����t�iœ �l`9�١~���=���bKs'89�j<6����t��U~�(�|ju6�#�C��"`�+��ƈ7m��bM��-z� 6l�?j�+G��-1X�F��l�Ӛt���tQFN8�0��7 GxV]�0�@Z����� :2�̤���aӕ-����6b�����k]�Y�Zӆ��)���K�G�}�n��ۖq���tgXSg�ש���+ ��V�ܒ֍`_���X������$����P���͂3 &���#lX�3t��6�@=M$�DF�c!f��T�C>1Ѧu�ޅ3�N�~r�R�����s\�7�,�ٶ����%]�4א�dC�'�t l��XÉ���34(%*���9ɏ�c�nW&;�x l>��RL���� �, �7d!������� 3��c8{+���)�IN� ��ʕ��B_�����D<-��G��9��Y��9 tJ�4���'0�p�n^���r���������82HMT2Z' ��%�M�]��N�lA �v��ʙ4yl�qyU\w�k��f��!4� ->��d l�%sд,Rb�ajvOg�o��%?{:L��zR��a� 9$�a��Ee��?%���{�c�7�L��s�l�N6��N�!����^R�IO�y��{ w$�O�'��Y0�Q�g�E����'�SԌfT�ZV�*���e�e'�͜��[����1l(�iU�^�h�c� �,ţ]��)&;����':�z���ՉfQ�s�ǽ��W^89\�h�&��S<<��ΖT�׺��Ҙ}��ٹ��m�_�Zc� ��a0Oq2��e�h~Dw� w>o�fB��-�V �hɔg ͉��r��� 1;߿)��(��n�%��lH�����F�` +��#�V{-� -��u{�����c�������O��Z���";e`�� ɳAnH�J�X��q�%]����]��IW9 A�Vj�)����0�&�X�xk�c��C��wYky������5�(��O1��i���M�=��J�LkH��֍�M�l�T��� ��η0l�u�T#Ds����HF����3p5\����� �2#��\)��J�θ=r�6T -�����0Fk�܆�wFۋQ^/����b���6��ћe�ِM3el��Mqc ��u���B�0�� �=�; �C����yD����Åf�Z��%�C�H� T�ۃi�sH��b��{L���1�M@fg3��F�U�Pp�Dq��Ǟ3�:&QxY������WאCm�}#3̎N����v^�xɇ��' -\nZͻr�ǽr�y�zr��ʭ��ڶ*�3b*. �{���J:SE������P?��$�U X丆�αzIu�C��o����� �"�f�R��o��B0 ~O���3c��9D�G���'E�Uvfsy'���!X���Z�W���NTM�M�m:ۊ��$SzN<��P��W����cؘ�d5��d?��a�3�*)��g{u���|C�'r��MT�e#�r#6m�>��%Tw�{�~d9���/&to�� )+����.���-l�@��1��u�ǚ�V��UQ� ���l�RG8�gXՄ_S��'X��c�P�H��8@\S �����\Y�Lp�֞�j���{#ź���K7�f�,q/�LŬ���b�!6%y�ߓ;�;C�1N��Igu��ѽB���E�9�r��t/�?l"�O^����ke��p?x‚�sy�۝O������׵��X���R��!�g̐d_�UT�$3�!5����|O*f {߀�s@Z��/�G��*����u�G�B��c0~�I��7�8j�G#�e���ЀՅ��ǜI���_�J+��Bq'�"�a���ц� �]�럖�{�ǯX{��^�� -�\G�=�̹�f�F5��ek��3æ�%��u?7-�{��!ygZ�;�?�c�CG��xt��5�(0{��!A���6M��LŽ�` �����^f��(0{���s|^�@�5�Q�3&��j� c�t��>EqZ"��{P5bo�A` -�‘8��WPOV�� ��QL>c7��'��MN��n@��h�{�i���PZ�v <� ���>�덲�\�_*ƝB�J�mH�l^�[� �$IЭu%��8��}ո`�0�9-�m�.' �&���(�p��5V��9���W�'jAf�^�R����M^�es;d��My��]�����|���j���MX�-���'�t��a���_}�����9(�k�����'u� -��2�k\SS@Lzx�2i�*Y���bX�����J '��5�2���?-M1 -A��S���*"g���������;��'����$$���P1S���RM�d7{VR��z��i���2ЙH:/x�Z�&J�����O��]�٨d�}�1ٵ���^��*l�(�B���7�����o����n�0 ��}�*�Jp�ulLvH� ��U3JR%cB����BAh��4���q�<�TE�2ej�[4R�+��=$�H�k�U*�� -�0sJ��|��jӏ"gO��x�o%�Aa�l��<�bXj�:NH�J������*�.�I��"F�2� 2 �RlXt�R -�;%Pj� �s��[���������7�U:b�w�eg���9�R�G�;I����m� �6�:�Z��8�F�ط;yd-��%��^����!U��;]V��B|��#�ƿO�3oaB�bo�{ >�3B�-D����� ?��8hxh�|�ژ�7gZ,�� -��&:o�&�7H�X�y�(�tެ���~Ȝ���̼��I�l��ʂ�!�.���=�ɥ��ГuO�b��iU�ʂo�z��>��VQo�0~�Wx�U� -�֭lRV��}Z*d�K�f��v(h���$��Tڪ�R���}�������2# Ĝ*�Q,6s��@�1y\�]��h �v �T����DW�s��T�^���t�ŽLe��@�Gv��2 �xK�����̽�3r����Q�F��3���9a�� a���?1�n`Vl>���C7rA5L�z���?qb��,� ��"(��C��"6L -2��R��yl��zr��ՠ��OЦ J'�:1K��g~;���v)���)[]?�� -2�U���q�ހx��cs�I�eN�2��o�A��ÇLP7U��vM"7�viL6 -�͊���P��;�+uWOǵB�#���Vl� `�3�6a�ĺ�p6���2�6��xQ�r��Z:\ƔW�T)��g2� �z��fa [5H����7���׿sPۙlW���I}/ �G��~\���Sݿ��*=.lPj ��A��v���oi����,�|$k���&����L��n�ցx��#B��\$������|4�7�|���f���66�js�_A�n��}.iⷮ�9�B_N '�M N�En����^�v��i��R����z�ж�X�5�l�83[BEb����Ƥ_P��{/ T��m�rƆC�E� -Vr WK�Q��]4`�.�#3.ך�}nZ�ɮ�#T��捎�:N�%���(ju]�g ;�V� ^�U&�qN>ؘ8� M���c���X�_Ǥ��FQf�%��"_O��1����Y2�,��d:���@Jsn�U�N�j��XS�✲?�܈�����ju̒���[��wx�9�\��H�w��e�? �0��|�uԵ��Upq�n���.��"~w���7>~��f�a�j%��lʤ��ղq쟤�#A�� Q�k����KJ�&�q��U��L�q:�H٢0�h�S���AU�)"��\9�Y�(�y�V�1'~�Ìv�_�֍��/�V�n7}�WЁ�庲��v�8��@b����Z,���u$���p�IM����r��̙ �׷�:%���YA3g%w�n���\�7��D�Dd)�<�Žv - �Y^�wƞO&y�lNj�O��0f�Y�X�*D�A8��� % o��r�qnl$���v�գ���S��dL�Q�6 Id� w4�)l�)!��I*)6|�,�e�˭ne`o�Y]cS�[�q���� ��� --� ���K؈� y���ʠ��E�p�~���e���Q�� z10� �R{�[�����rlO?����̭��r��Rۆ�졓���1�W�C�¬XqV MK����!���5�$�Ƹr������\�-]f0m��i0����W?e����DWx�2�2�����Ni��~���6h��zۚ�-��SxwZ�N��-f�Ym����#�W0#C�j�^5�û����= -�Vh�G�>�V�0�;�����k�>���?�x�h�`<�Z�,M���0o��(?Ha� &�;-��8�b���Śh�_Q���ߡ]ix�(���[�q��H�# ��/g�l �Ԅ�/�V���k��ЌhHg�Y�h8S^4 ��`� z�\���c� �n vy� -؃㳑�7���� -=y$��]@��J�#��Y <��+�?�S33����K�q8+��_����ne�U3�W<� A�M�G .ƀ�<��A.�g@N�hY�zS��o�6� �؅��X�Pg4J���R �0�^���zlE:�pVxw��*�uv:����L��V�o�6�_��BEeN��Ѯ�q h���a@� -u��Q�@R��������,+�������������k��$&b �X͙]ڧ ���`:�q&��?R����mt��f���F��o��/�H;��,���oc�W)���9)y\y� 8#LIcɟ߿��aT~jm> �m&.1�Dm/%��h棱�揱�t���ȍ�����Ϫ��r%�rY����sa��ǪȂ:4�<�rsq�Z��J��jG� 5�� ��C���i��їZ�"=Ky� �V�ّJg���].8������v�9��ި���d��Z���pS.�[�JȵMOڎ�\e��4�� yB��5�r���O{��Y�^&5���א�RWː��^j�9�?�t$��� �_��[ĭ�_t_)d���F�ͅˠ2ake�PĤ*�nT�aQUR}�T�hD���2�-K�? Czyx/at�|5޹��<]U��Z�O-�&��GœN8-w�N�R�p^N�c:~|z~?Fh�J��c�_V|�A0�������:���� ٪8��g6ܖ5�=,���j�=x�����b�>�O��>٬0$ |�����H=�����|�&��]c%��I�8~-���W�v'�)A%�J����)c�&����8ϱ#n�t�'-�V���F�n&ds'����5 V�Iy �<9A�I��눶���7�-�PX�X��b�����������dG@ [��_��ui����o�l/���;�ܠ!Z� Ȼw5�Sb�@�H�jp�E�&��G�/�_ʱw���n��m�OO1���)F�f�^AP�.�(���������v�@ ��Y\�c�Ӵ}�~o�뛰 -�Q�2�H��W�L0�+9�\��J�0[�b��������ZT�� �x:%�8�X�t�|�� �� ��[*zv�ڧ� �B��'ԐG��QL-̯O��{]��FAU;E�;(�����5:���{هOot k�T rZ���!=6L�Q���Ƹ�:�"�D�{j��#�嶪�初��Y��ޫ�.��p1�c�<�6�����ݡ�O����g~���aʼn(��g�YL>:�� �7c� ����]� �SMo�0 ��WpE9��b�tK����zi�B�際" ����'ۉ� ‹����Q�t�**-=��=)~�W�>���Ub� -�� -�g��~v���Z�b믒�G�Uwڪ?}aqH� W9���� �e4E��<Ζ��'�kܕ�&�r2iO����^� �VÈ�K&�+^���sM -��(&k w��Ǐg��T�؛qEa:W���c�-G#�1����4���F3��E��ayK�#��ռ�hM�r�į�$?��t�u,�JH?,��(����,`��Q&F���P5"EC��f��]�,;�!����*m�k�*���`AR՝_��-�.��=�C$�ҦP���p(9͢� (\�,J�(ֿ �u�Sl�R���$��س���3�@�$ �} -b����7��o��2̥)��O���r��(�ǁ7C��B\h�")����v���[D�SX���9��A�������ݠa,����R0t#bjv!�ψ�K�kf����ua�i�\����󄥱!Ux#�>�qE�~GN6NȨY0�����4W��BJ� ��%�f�@��*��c���oN�3�������V0��%s�-dz��G���c��ֲN��� ��¶$���$���gn�/��B�o� ��;rD�.�L���Î�Ó�?�i�7�^I�c�S������kܬ��!��د*������&��r�NW[������j:��_h8eD��x�5Sk�7e�\��>{� N�8��g� -�CCcy�Z��P������U�����}N�mEY'ݹ�~��ްn�j �bʠ ƛ�`W� -��������啫� 5�w���cA5��ţz��� ��hAF�ԌG�چ�;�o G������+�B%���Q�Oh�x�y|�bw�����+��`��[]{ 9_�oH�{dCƴ��)�=��lͱ�=:�,�i��.���m�ĺ���Q0� �E�� -1�|ŖZZ�b�b�XHn��Vb�����n��r`�庌�8���Z��� -�V��w.�;�x�G��ڷ`��~�c[��s�V�l��8ܚ�M�b�a2������Q�<�(L�����G Q$��~��}�5�AK1���s(d ��Ֆ��OI�o�H� �Y�E�ߝb}����x�aW�B-Br�� -� �2Tz����dwF-.��{�|R�8L��!%xc�ҵҋc}��R�0 r[i�5��p�O��?�Te�)��$�*�0��#4�X�X��{�� �j!}��m�i�Ud/�܏�����i{G<�� -���&۹Tau�5�b.��Z�oܸ�_�>Hl��S�v/� @�9�}�^Ð%ʫF+mIʱq���ᐔ�%���CQ=�w��p��7�����zK -���)��ŭx�RNN�_'{M��|��\��%�l �������i.Zv�����qy��g�8o�nCq���SOs�U۸�����~j���9'���� �c��u���%��O#�\�<¿[V=d����M}^I-^���||�e۱�j3�&#,�j�ŧ����8T�hR���M|u Ϥ��o4�A����T"��n���s[�mƲ�1�WYݕ������]�ڒ�ۼm $�\���ݒ w��v��ĺ������G�xI�I�����t�(����S� ���� � ��c���.��B�g1��|��`9��IdE!��!��"��mk����_�)�j����᠖�(�WU�T-�=gٞ�M��{�JK�h�S�ؼ>�����,N�bq[��}���R��ú���(ڤkSפ`���*€����S��VjF�v�%��„V������������Q��bp�HQ����x3����� �>��2{���ToID(rMJ8�n\�O p/�{��7�Qen�Ӫ'�5���n�tB�A�~\�+�U"�O���?�i���fxs�Vj� ��p�x�æ'�� "F+�ބ>n[&���+F��L��b�� ��x��I�_�����)M�kDztp�h�{ vݴ"�I�<�?ڇ�O���Ki��wO�Y��{r y�uWV�[�� I}��봫K� Z^�d��)Y��u[�4Y����ʊ��ͳ��f�eO��v�*6�R���8Lb��P �J;���R�Pl`����M�/C�Bb�گ����jk���J{%��J��܄$�WBJ�~�����y�����)-R]x�W�tN��;O��Gp-�����^�Lm������I��9"��o�*o�C�!�$���� -��se��(��V@{#�4���|�E� r�#�?�}�!�XZ�2��>>_���Ŕ%��P�md�:�v��7� �Ir���������]'�"ab�+�)�� �dl{�8I4Yc�f�?���k�pK?,��Y�k, ��֒�l!�0���ˊ&�/͸`�"�h���od9��9��F!G?Q^k��xc�����a{�ɶvu���VT�}������'�m�_¨�m�__Ao����mI��=R���!Zj^���q]�۵l��ϝ�vB?=[v�dlŖQN��}] z��m��|| �,LS$8�t��1���Z�_��D�D�Ҭ?�0��}˂V�@��N���9xI��9�� �����y�1��_�r��V���\�9��"�1���:�j�ϔ��^�a�K�����ṕ[��:�>�NvJ�W��}<ԧ�DTo�uA �jU���Cel��ԲW@9�(��R�O ��L�|�g��6��-DQ�����6Y6XĊx�M� -q�� 3P�"�NT ��)r�8��|0��OE7C3AF� A��0ZL�hcꋅ�*�{Ꮧ&E�vu��q� :��IR���Af����/�Fn�F�uƃ�� �볭 e�����0oV��gMl��e������m�=K1�>�bJ�T�[E�<�C��Y���Hn&$������N�����C�2�4\U-��~gT���M�x���у� '� ��$m�IJ�*�sκk��"���G�(Lh -����ǑU�?R�䅫�+G=�>����h�n�Jw2�`��� ���:E�v Boߘ�]S�R��M1-�"��m;���M�?k1 �w - �\�]s�CI2d(�6t ->Y�\��r�R�ݫi Z��'�=�!�# 6SS${�N>���Ek؞�$���^��A �8V1B��5�����=Ok�D,*kx)�BR3�5{׿�hbW��^��f��}��� ���p���#C�ad�+Js�3�qq�L>��e���'��Cf2�r�`�۲Pf~�k�ѻ��;����W[o�6~ϯ`����$n^���ִ��M�����ȤFR����}<��$��������x��s>��K�(%I���4Q�� ��Y|q���'�d��� -�|����$�����R���˯_��%O�-a�"��)'�je�3���l͵i��蚗"!�K����gztd���������S�]��A�R$�� �vBZ���\� e8יY�^o&iw�1�,?rqɷ���.� Z��j��&��2������;����s�2*O�viG���ʝ�`�u��C�'sA6T*"�jtD]���&�h�)U�O��m~j_�2����d�". -��*uW�ш�B<����\�m����{B�)����Ӵ���_�T��=���Y���i+S�qn���iY�m��Eo�B����J�Z/{7�v���W<%��?�����Ȟ,������� � '� Q����/{�`�+H9Mр~iE��ĵ�LԯJw��R�DB3���;, ����~�_��sV$� �����E�����y��_ҳ�冾�u�讲�+?v���?|߂rg�P���mEچ�0]A��CG2�0ې�OSV*?Z�����[Yp�܊�d�Z��j�ُ#��ԾulF�(�u7��0@�7�&���G?R��@�=�|P�=FC��4�L�Gӂ�Q�i��~D=x�RO ������'�Wh�K�"�P��m�v��tw~n���� {��H7�6;T�&ѣ�d�L�:�t*�>����C ���,%�����B�~njʌ��!�޴����Rx�\O�$C� ��n�ɓ���#,+|:�x�IjBY�>B=��X+����!���B��[�\��&��Ltua�h6C��#����w(8W�i�V ��Ս�c�����<ú��<����k+M�;�K�+�<JS[o�MA�l�� -�V�Y�ij>��t� kF#ͯZ��@s&S3��0s�\]�&n�V�α�$���ތl�/ޯ:��Jr�����2i�伟>�$@�Ai�]���������Z�y�&oW=�X?v�Ő+�ݏ�`lf���O$գ'�l7v���=�[FlG��5�&�#��c����{`n�aY�\���q�8���eй�� �s�D���S3@XNgF�%V�4q�W[i������s��J��ͨ���j>�L� �d�h"J!9�##z�:�yCs�e��m�d�Kq�i��c�{Qнs6ȼ.|�i�n!��"Fof�\xu���X\�.�\��'[ca���s�60{��E���AT4.�zg���}W���^7��h ��{��J�v�J-���� lO�`�p'_e�R�*B�ÿ�-����?E�� -1E�|ŔZZ�bᣴYˀ�� �3!3�����‚�:8��1��8�2�=읡t�ݶsixA�����(~���Ib��ι�V�^ja�~�ti���RP��q4/�g��X����ĮRSX�;�u?�V�n�6}�W��I��C6gq��Ng v�� -T��PW�6YH�q��w)K2%�]^��(�x�=�𒗿LW) ��T�-�����K -�\���E'�k�)e@�+Xc���yS\\t:�<|�w��Ϙ�l ��h�<���k�ۻ��Ho�3��&�a�e���'�H=c��EaJR���V!�$���!��gg�����?��w_ȼ̹w�w�=�#�'R�/���-�VJ�C�ݮ�s���yʕ�Y(�A�dž -�=N���"�P����R�I�8��v$������Y´����4EƔ� 1���w���$Y����)n9��9=W���vZ��rbW�"��ɪ�S�xF2z�����Y�S�JWn<��ClgX���\������{b�*���!�Bq���aV^��֞a�*YF��{�V�:�q.:u%%����`�ԟ ��P��(3���ӥ�+Af ¨b+b{!���K��D^zy�-Ug[�2k�D���� ��'�R���b8�����߅v+\�<@�s�*\�64Q:�<&lEe -#��"3T��lٙڼT��/��>�q��[ˮ%n���w_���`0v�7-޿��9^�z�s3�WCU{ΦB��]k�A6m�%e .bר]׳�Y�ؿ| ����"�K�����N)��使%O|��f[?i��9��F��zm�U���Nl���ZrDJ}P�=�p����;�bW.�����|]�{i@�#ű�'Qy0�X����H��������GQ"A�+9�B -r*�z9Z �^� #!��*��6�{�klJt]w��������K�)�.� ���!� ����D�?bp���–��a~���H�ͺ�谊@}Rx4>f -K6�~�O�Z��/�)hÄ^Q Uo�O8ꏏ�?���H��|�o��h��^@�/Vc�-�<����f�\�oIaE�k���}�Ռ�f]���&P4a���u�WO/CLU���2��qz�Ob N{�(wN��>�ο�Wms�6��_�dhm��>&!wxZ&6�xi�J���#�$���+�6H� �TB�}���v����2 �.�z����9~�aZ�zy�+�Į�h ��)Q�D/�v���<:J�������O'�� 1��$�� �!ߏY��S� N������޶!ʷ��7F���T���Щ�����il�!��u�O�vc�!pFaӭ�$GF\��W���h�I&$K�5к�h����q��N���oF��i��*�ږ34U���p�ul L6S�q�Ut�����Gm�l%إ�v��XN��I��#�W�M9�m���0IL����-I�5Iڷ�C^ސ�v�'Ne�X4ߔ���ɋ?K�cs8�:��m ny౜���e؆{QJ�t$'�#�X&1�L!�/�b�U��}s04z�as�6{��� 5[)�kYƀG�-GL�^0�S��VG���!q�m�j 2S���E� -��>�J�ȃr��K='`Z��c�G��V҇���czTpDz��sW/�_�nJW/��q�5JҎB��uU��s%�q�0��*B�@ ��pA,������83� �e�i�����$�)���� -�D�m�q|�l��32���Yq���t���L��#j��j�*�� �T)�p�v�O�� ؞-GH&t�%� e7�8Jh}ҭ*w�t��@���<�D�U�1̗���V�`s��_�K?�@?�&� ,��0���Lir�]�)� �u�� -Z-�8��F��ϷrtmJ���]j�7 �Y,Ҧ�z�i�v$Ӫ�����T�4F�i�āO��9Ԛ��Ԕ�4� �1����7~i�5NY�2%�+�4���n�B�(�= vk2V�o ��In�],ثW^CU�0 ���J䧠Lz�yN__˩�y�in���q9 �q�q8�,C�����n��'�ͪ�2E.����N��}����؂);���@,��Ӧ��^ -�E�v�7����1��-��2]�D䇧�N�פh���nI� ٗO <��ed� 6��Y��8��4��B�(d�W���#b;�g��l^hd�#�#�_�Թ�ѓ�4fr�̳��D���5�F��$�Y�E7k5�����ݣ���z6��6����s�_�~�Q�t�D�+��`��ʪ �e]cF���q�b\�ץ�F�[}�#{��@1p�����$�����E�CL�����d#�G��� �r��N�C��y��U��o�;�9�m�AO1���+F�d�b���ϒ�rbJ;+5k��S1�wga��0���}oz{�l0�kPD -V���0�9Μ���(�Pn�1,Y��˙�k���8�X#�AY��?x -�%t&�Ú=���;]�d��O|By��ĂZ��-oҺ���4Y�@����� �V[O�0~�0�( �t{ � �hy(�&�Se���ڙ�@����c'!Υ��������ss�|M��$bX_iI#=�/)Q�}.z��JqD�CB���n�z�M0F"-�E�������~l��2�"\�kp�z�����?���C��#��W��M��K�N��p�b�-�sN�P��o6<=���)�z�9��a�^�'� �Gz]@����G� -��s\f��7,��� U���ܟ9�t{%�TH��$љ䎟�WQ�%^!S �D}S���F,E�l|k��I��%/� �1ߛͼ��ypQ�2aV�b)��<���p ��C��9m�J���e���E�X���װ%�~9jPVb��z�"�;�7-�Z�=E������Q�����Я0�*ق�� -}?��C���f~i�f1�J��j�8�f+�/$]R��)��?y�>�EL�H�G�� ��3�+��������W�-��.�3�Y��v�=i�Y���%��+A���CȯI:�,D9rv�)`^=1�͞t߮#��<�se��Br�7e���rZ�mϪ�bg�PU�k�w����V���\/|/��‚�ϫ�3�>>Qǿ�j~����� �O{����G>��Y�9��:�{���F�5�i5y[�'��Z���f`\v �����Xv����8)޼�D���G -I �ΟK-i-��ࢵlo���Pk���fsC��͕hu"X{m��fe�S8Q� �p^����" '�l��L�˷�FHY�M�u�?O1 �=��#�����v�H��NBi�I����EW!�;>���գ�{Ͼ(}�!��3����@hA��~�@���X��>rJʵq��"��Q_��\�E#g¨ȝP���tdS�΢��>��Y�{�ik�Ǟy�[���y�J€����7���NЙ��7�C蟠=O����2r{�,Q�g�p4w׸/� �SK��0��W R��UiŹ�e[� $`����J�Ic6kG��P��N܄f�G����13���"+ A��#KF -ZѶ@ Sxӟ*~B[��[�KD� �g�.���g���� qx<0���Q� �kf�<��#�p��n]�G �z�����MF� �0�s�� Z4�0y�\ -Tl��|��O�#���k-̝̓wZ�r�2�0�P&�TٺܠB�6�!���mpk���)AR+X��V�_'(�P�T��!4z�-�+c��Q&����<���VZ�/�i%���C{��e��bZ����NW�|����i��:��E�G�E'x�� -gU��D�/���l���F�NnN��\kG��1a����6� iؿ6� դ�L �%��ptZ�WĔ� ��M�K���Z�F��S��&e����^U,C-l�x���3�Q�̫��v��u��q8yݝ�i4�0�����y*� �e�U���:�����Wr��ev�3�U�n�0}�WxR$`J+�5]ZuM&�!K�N�CWE��/��l�%���6��@[�{�9>�6���4#10A5��j���n30dD>E=I�`2ʀ�H�@�B�Xm.z�����l�ok�?cKcȍ�U�FɄ/���+���(ĤϬ���������/g$�%�\I2�3%Qk�lxWT��w��h�Q�쮾M�9�,kq�{�F ���鞟��Z��i ��l�e�����V �Z#��p���2���bj��B, -DU!���G�dT�A�7��Xi�+��O�Wc��؆ѐ|�ne������.�{�k�;�w���T��ұ��#�'���%��Ns��vNn���L��T����Q�R|�ꨀQhB�� "�ш� !h[*M���e�d"�aJ�ʴ�*�,J^P�Ր����\���I`S��v��w0J��U���JVO���7t��0��䠷3�ֳd9�{���%��lp"SOk:��.D��}�.�Z�mi��=}J�G ����0���QSd՗8E�#�/�qg��c��Di�,�o$���sR�FM��4��~xt��#�QU�������{u��N1E{ŔP��!��(M@ڔ���o���X�ٰ����L1���9��.�3y�hgE%8}�cF�)]�7&�w�lh�G ȶ��7��L)4� ��C����+�/��>}����9NEi�6?�j[��i'����I� N׬ �����XA���A<��qr�����1����Q��1��䟒��-o�eN�WmO�0��_q�:%E�վ��M�eB�{�֩2΅FJ��v�h�i^������U�����)I4J25v�@�)�� ����dt��1���!}cۢ��� -r�Sê�d1}���/�Tf����H�NJ��1�^B9������.��#�I;y*N`V_2ض��',_�_��~��u�? 1 ��~��:�o:�A�IA��@j��6%�p"~w{�jƗ�K^2��&�Gw��=QNzO(0�A�0�^Q�u���\gÒ�˜���S8��z�u�4Pl��?��@�t;^�GQ*�ݢ�v�_��e���8�_D�A䅍>x�X�A��>�E��z_4��_j���Ia���RQk�0~��8J rH0�㺤���`]t���X̖4�䭔����5%� ��}�}w�x��Vz��VTғ�kx�_gFv�T��!�=���u��𚬷�ñ�*vh����YS����H��s�b1ܰ��^z��[�lj;&��v^��f5W�o�ş�VPG�H3]Y*k���H�IN�|�J1�F��&���ȗ -�ˬM�^� M�&T)z��Q��>�� &"� W�Ij��D���؎����謲\�5�ӹ�Q� [m�ǀ���F�U���m��/[+��_D6��3�Pzt-#�U����"^-a��y���/�|\Y��z��q��?�yQ(۝��� ��tkCc� ��4��V�d:��]��J��W�7^X����Eb��Cp�ϸ��� {L�_�t���SMO�@��Ẃ�BT�Y@P���'�,C[-���h ���R�"�Ρݝ}�͛��^gq ԩ" S�yƟ:��e�*0j�.Sa��^�0�WA����r���?�WhX�;�vm��cKPĐ �M��;;�*�������m�II�g0LS�p�i�� ��&�������~;��N �:c#�Ʒ�,����]!��"��T�;��Q�V���\��1�$Ŗ��ӣ���؆en4'��l���s�5���Yէ��*�)��q���{�m��L�e�WvW�7��E���jeB���1P'�=�G��j���L� 5� ��3���#��Sy)u��<�y�6���l�� 9��˿�eϿr{�]�M� �UM��0��+�R��J[V=�Ceo�P0q����*��:�S�Y@j��̼���c��D ��CX*A�Z��$������=�$$��"���v����I�yq;��ͼc�S����}"T\|�lKw�O�'������ - -����|A��]�4�H�w�0�%��ZH���c��O�Q�Q�sbM@��cJ�6eDQ��jE8�b�D��_�~1?���˞��������9^�D;�����Ƨ�v�J��bD*���'=:�gS��"�nA���;T[i�hX`b�j"�S5�\@H"�Ϝ���+��v�`<�`;��l�0� -�p��G\�|2��U�;��(�5�s�/uV ^�a��3} 0,��:�Q���������9�6��XY�e[ӫ�U�Jk�Vi�Z�o���:��)�*eT��H�� ]/W9��YZ�O���4����V�I�7�uP 7gy�q��-��*T�X����A6�"�'��_�,vׄϤP�2Z� -������ˬ:W�Yij��R���6��f��5�L��:BEq���Ӧ����״~ -�Y�}��H��!���^_�~�g�b2��ȳ2z���`�����^6�&�6>� -־��!�}|�]�� -1�<ŖZZ�"��]�� 1���n�n "��g��,�y�F_0S+)��^J+Z�;�� �>��N@�L�NZ��Ԩ���M�V����- ���ҿ;���6�@AX�65��;H��[w�㾭W[o�8~�W� ��E�]8��=����U��@���ۡ���}gl'8��"��g�o.6?}I� Y� ���R�0P3�O�$C�}ވ�Ʉ���fO�� (\��y����������w̓t�br�)%�%�s]�x��k��3�t��t�Ŗ -�̄ yl����pK#������u(X����$Y� �򂇯a��x��]�X��кnT���rU�E�D�CI;|�S�$����(�%H�>�UHY�,�%��4��a�IB���� �� -�� -�& �))e����rX�՚����W��E���]�UIu:�€,�8��g� �˷���A��X��[ �݂�ڶ�� ���=��ª�D _�Q����|�i�wy{�Uu�GT����V���J���&�K����a�s��"1{)��{w\J�4 -Y�R�u�:gg���M}���Ag���O-,���?��&��,�ģ;��@�`S�Z��d(�`K��A����ȱ�X:��F?R&��1�|?��ж �b@��P_*���T���� 7ޒF�A‡C( -H�N�J�7?�Uh�`�ҥ�G3����XD՝� ���ڠ/���g��7�~�0h��c -�P Q��r%�],|��)����^�](� -���)����\Q!�ޥ' -%fx<�G<$�����t�Ű�l��1W~���HI8�`o�W!Q�3i� !P1h�=���1�~����-�o�]��#�<�}~�c�X�_�4@+P�3���g��l�$���"h��� -Os *e�2y�L��<��dл��Z�Pj�FѼ�}����R`V[@mm���u!�s�tmF=G�.iu{�ǐ��uejx��1~ZxG�d�$OE����r0(Ep�� �?�a&kpfzGC]'�&�>̞o�n����s��5$=f��^������g'�� j��W����Zn|�&s��w� �Y��!k�1f��lsm���JQW�~��;^����h�3͆��u��e����4�h �pH̋���^*�!y�aL4< �Ysc�U�틫�(�/���{5�PJ�L�:����v0�xu@�LS����>�<���4J�4�[C�#]��~�חt0��33i��<5���E���c뙂��!��殬qC_�v@kNq~|4�'�d�W�T�T+O���0��3Q!�'�Bx[��hq�r/�L�^@&�`��`I�f��9 ���Y7��^�~�K�}��j�om� -�L� �� j��S�c����o���w�8����vyu�{ L����y���<_Q��:��(WH%�R�u˝� <�h�cQ'H�ȲXa���F0ҿӭ�/ -��U�{�/}S_k�0��P�`���6MFi6և���֖���XC�\��f���N�;I�=s�߿�|���V������5� x�`��<3b ���~�W��y�?6���cz|հ�tL�޳S�S(��{k*�a3F�8� Jv.1�7����ZIV#QY��k:&�A"��Zƺ��G^����A�Lк� �:�Z��r�����������}�L���y2�}�%� ��Ζ���*lWЀ)��v�\ʮ��� �D;�W�-��E����_ޭ������v0��J�D����7���T�'���:^J|��r�H֮���S2�<�l� �o����*����OW8]cdI�_YB֌���G���js����>�k�E��؟-k��~�����b����>���Ɇ -�d~��}�4k�u�?���k�0���W�A�vH){M�l��>��2��Ap�c[�"y�� %��$��v��*�Y�;�����wY�� �$JKJ�\�2T0���ǣ�,"_S�!��`*�מ���d<}zp_w Wȵyl$��g)~"ѷ��4�̸ �/ ��:�� ����sW�7�ti��u������'z{��#ex�c�_��N��֚(�SN�0��'ab1 .a���E2Z5]:C�c�� U-�&+M��%����sN���9��%��аT�v:���q�S�.'e����֒R�.�����Tv�K����� ��.�����/�:��e�����m�Y] �Z������.䯎�}�0�M�48p���ڜ�E�1�7FqI�?�b~��0�$�t�eB9M�AIr�U�n扳݃yG����$۞���0 -�i1�~��|�>�d�yTu*�FAq��m f�sYt�� -����:nM5O���oo#�����$NF�*���R�����ꛧ�g��H�}���",��j�АFk��.�Ѕ1�Zţѣ��̊U��@W�������W�� 9��k��?A~R���/�_�����,���ͣ�_9���?�X[o�4~�_a"���3�$T��ˮ�X���ѓx&��l������/��$�r�V;I||���|����Q+���>[�1� -]J���:�[}����^m>Z������g�A��i�ڪ}��Y��N��Nւ�؛J�r����@�.�i�j�uI"�d�6�I��l��$З�����"����Z �q����m����+����}X�a�t���<s��-��*����Qڎ�Lw���;��7�Z����uYJ�A���Ygx�-�ķּ������k5���H� j/�z�k^�-k�:0#~�$<�t B|=�턨Y-�a�Q8� -���Ly�>?߰MbC��fy�]�;��+y4c�ݰ��+��� ��& -G�ⱀ���VW�⏂5]�d �FJKjj%����%v�7P-�1՝)���[nE)�KnM��'m�6Y�v�.�r$��( �7�����K���W�Wg�;^��)�A ȵ9$�mkYp���#s���������'vT�%w<�ק����\���K�C���I�m�%Tk�qR�+��3�o"�!��F��1�������}�����W��H�ΊPT�Q��և�k��f4!O�t]�'�"�ᤴm�9p��� ;� �q��vp;XS���؍������*��e�cJ����u�3c�ɜ�#������^�b���}wU]��I1|�_�>���j�H��iXj�$�z�)Z)/�^@���%��fDL��H���� )��yQ�}���v ��������R9aP���e1b:W�C)�l�L�54d�a��MT��\�ġdrՂ��l/0�^|O}�jZt�x��}M���+���A�M�O�1F��ӣ�;�?�A����{�wq��q Gţʊ����*]���I��D�C���%���SyA#���dے�'�*v��X��l�r�TD�zG��1�C���H�'#�H݊]��.(�JdA��em���dn�*��)g�9<�;e% �%�T �6�h��cC�d;�pWT�W P4p�e F�K�.R���"��i���a�{�4Y�v'̓Dx�}�G~D�, 4Y>�oa����Z�B�J�U!�d��s�~��8x���G�Q��<�V 1�����R�\r�x��P�WXj���%��L��(O�eG���`��!��e���>#H�i� ��k�Ӳ��9���#���Y��I�{3�l��W��>�#^���Rv��Gl�b�_�vu���E����W����~p�EZR������C�9dshcX��mh�W�H{��� ȸS̻���D6g'��i�7���J�1��,�Mlm��B��z��-��=AQ�LM?����/�"���~5W��T��4I��6�onUZY����-J�)�k���RP�ԒZ�$��B����Jt���'�|�`��_#����"Ƴ_h?K�,� w�孤_b�0m6�\���m��y> �V�<��᭨�Z4�� ~�:3���ɖ�A�*��hk-{Ξ￉�L :(NL;��Cho��(��6_�p#|E"L�]s�\�Hܝ��Þb������;a9�K�����1�,���j��a�_v ���҉��m�꼶���>.���S���<�����o8�xF5�m��1K��դ(����Y����D[M�kH�ٗ� -, - �A)�{�d`j��N��(��q5���k`�.*f���U��=U?�5��.J�n#t8�1��!���U� -Y�E{F��)��.�����E�1 -1��bK���"�iy����� �l��AD��i�)���\ &_i%Z#�M_���][����x$��t!��#772���c��SC*95��_� t�rO9���z�%�>6�5��W[o�6~������^�[�4��u�ڮ/q�ґ�E&��ct��;�H�.��d�a��|���?��os�@�Q �%���>�Ȃ�4��8݁�i ��>�*\��Յ��d"���F��OӋ���;�z�8�ʗ4�B�\�LA��R���#�+�lO5�qN��������1��K"�xE��Di�b�Z-�ܕE�f5�8��~sr@����%�5|墖�R�+�]� ]�.Ğ�`� �] ��wq|����YƄ�i�P�4���D�YfY5i7 �&gpg�������Q��"Z0�]�p&��d�[ߢ��8��%�I%{���y�Ȭ���*MWL�:=G���kJ3�~��l �_w]���N)-2}�lxm2�#<���w\��a�8I�'"�$�f��s�h�8�?�.�^�����迖7fv���UV��^����wO�I�a�0�e�� -�@D���-��6� Q�6v97 9r���&��� ��S��6�O�F�i���٦D -kX�+��J�<4=�r[�Zƶ�F��r�����h��݈�,�F�)����$�.1 ����c���(�����˃��e�^� �V�r�0}�+T�����!��%�d�I���S�"��%�$�����0�I��G�����8���M) ���T" �W�1��E�v��pDe� E�9��Tx���^���!'�;�l�]�0`t 8�Rr�� O,��D�)2�<���?g�(.w}� aZ�=�[Z޹�뎾.W�֓ջ��}�[=��}�@2 �f #*� �>� d%DY�Y�u�6� -M���u�T%e -D-�\�~ 5��ͷ� �J�L_�#[^ճS39�"�>�,Cх*'T%�m�XH��m�}[coG����@y1x�GX������ӤƷQ�>�u*��`�3dyA�|J-�����6r���r�:%��%�bE���F�Ue�քg?����'v�Ye��-� ]FG�g�z��jUIf�:�1(t�S�e7�pa-�P㑱׋/(E��vvVW�zx�O��[�tqS6�0�iq�q։ך�Q���k9�䏱H�^�Qs����o^2+ 9pQ�c�W�yэڒ�)l{|U���F,R���j�/�ܔ8&��C��K��[�(�}/z\�a����ȓ�q�Ytl���]|������ͭq=~.!�1�p* �/� L��=9:�Ӌb{:lF�e�_G�����r���P�*]�J*6͞Z��@U:���t���@�U��P��C6����·SW Zt %U�-0 ��t���r<-E�� �Z�-����7�6k4���evoUR�}C�Y)ԏn]:a sP�V)����� w�ϐ�r�l1ԗF�J��7P -�t�y�֯�.��E����%ߏ�{�=��I�� �,���ڨ����d\�^����k�3�8ܯ��*B�+�=���]*�׳N���[�4W��dJ� �����H�A��˕��.�4��#��t�[�i�`� f)`>gR ޜ���P�"���*���j#6x���=�{ { ��P�要� � ����F�~�D���� -��*���<<�fW�81�D`��L��v�h�x %|��I�FL�„�?�s첳�6T���as�T����m�g�/��~�e�YJ�)[A��.��sZd�������Q�6oo�LN|f�� l��m����$"���-���dV �8��Łg�G����2.��)_�c�.<���Y|�����u%�j��7w�Dȼ���� -����"����᧓���9 _�Io ʖV/d�ў�=�8�Ǹ�ma8}9���01_�ט>�nRIc�h͍�Ռ�"�u��)҂xv��ߠ���:�OI J`ܡ��g��m3f4�K��yO9��)�%fB�P!����%���I�J-g����k���nu7�M�}�O�F�+9�d��E��*��R<��i7R���ݙ}�?�z1�� ��������Ŋ~8*}���2�aq����g�c��gK���&v���8���b)H����Dd�]�ٮ������f�s��£LbgW�["�(�p���K}����̦Lp��������S�JI�ʇ߰����?��� -Ư%r;�xp�5�N�S��g�?f9}䊀s��eu�[w���^��ؙ�}[�����c�v��_�TMo�0 ��W�EP�@�l�-�3�K7��l@{k�@��ژ,�ܦ(��G�N�8����d�||�_�i1 -� 2�L&��=h!����@�m��m�7�fA3�^̴��Z�; (�Z�� -)�Jb����hF;c&\8m��~�Oqx���+w��W��껻�^(W2��J�L+X.�V�L)�Q���?��M�~ ]��Ѵ�@�m��+>�o�m�~�y�.%��RS�G.K '𨳸�=K�- -�˜;��`�עRdž�b�r��O��G��Z`ᓳ��*�"��9��ǧo�M&u�\�Y}7l9����c��3D�7�`�� m x�/��9xu���崍}eE��WK�qZ�'4�ږ+:i9��3j%r�f��#�p���ݱQ����~7��X�}��4�+���.I��@�2�6�!�Ӂ�-~��L�l�5s2�}]=��e�ۡp�=s(�����T���oX�:WL��u.������"�Z#H��vH�c�eH4�����ڮJ���D=?x �_�(� ���1��[@�b��y���ϰ]ڏ�T)%��@����a��E���팦$�_J���-�ZM��a�����U�=�d��є,�,љ�j?�N1ត��1��� �e�� -�0D���=�Q�Ui�z,H݌4�fC�B���Ɠ�sޛYmbɂ}�0˚�E��LkZ�+�9v :�8�-B-c[ o��2��9�Eo<mFFT'�0*���~������z����ڋ=�ָ���/��1ID��\V�e�U�1O1 ��� -�.7t`.A!`��IU긺�\%�t����J<���{��OCK�M&]8;�%*p7�J3RI �}�^�.N}��G�\)%p)�ʎm��}e���-�LAv��i���@*սw��] `JG������s�z��S{�O�ђ�<͗k�d��޺qs�F��G,�&%�����/���or�Ε�V�+��k��,xTG�u��K�0���W�P��_����A}� ��WL��\�C��{��m��C��|�����U -�Z4{j���8����xQ�wB"�*\"69�����m��"�^̗Ъx�X�!����� w���r�Z+ ek$)k@���q>(���u�aI��BI���+4�@��ק�lPo�8����|��z�q �� ��72�@��A#��W�i���`�Ƒ0ƒ��1R��4����#XZV� -�þp�3Ax��$���-�OqI}�&uʺ=�Uy�m���$�j ���G��p��R��ϯs�/؟87���� ���7�����SM��0��W�����1��CT�t���A�1Hױ-��f��w�|,,TŇd���f�����k�.���:Sp�q�,yO� g�d%X�8�{X� U� �? ��gQ�dk�Of�(A:����+H@���SDp�*'\I�ȯ�owKLE���d:�Kq� ������p� x��T��oT���3�=���v`ڐ���r�X�6��(SJ�>4`Ue8�@X�Z�y%�+�$�.T�]g�������V�c��a�7�Rmmi<-钤��3țGPO�;p�֘Ub���a[�_EN�=he����Wk:?<�U �:�Ȝ�J��?�����&�y���%�g��� 3G��\���";:iU���uh�u��5��UD�$�A�{�o�ҳ���s�mr^�k.MCN<9�c����ƥ ڳ u �0�_�4w�R�as�>ɯH��j$����w�j6���Va�ވ� �'),��0^�WZ��X#�m�+�[T]���c�q -�f{�r�п�f�/m��=��s?K���,�`�����~�U�N�@}�W�P$;R�+4�"JՇ�HE}��h�'[6��^���ul��M��C�2�3��_n�Er� ����̭ -�0�σ��bK��� ��hr�ӯɝ�R�^����塚���h��F;�3��;mVd�s��`�'+�oB��ӂ��fYs!��C�נ'�qx�h��R���x�,,޼zR�E��F�)jX1c�*,�r8GS-��6\NiK�@���U��D��Ҡ��p�J�i ϧRpȽ�Nh�׊�=w��������A�]x�n!��ue@�mi�1 �a;�i ��a�׊����KV .�Y�l?{-P�� :����'��:���� ��:��0G��T����O1������ V -Z -��=���5 � (⻻�z�9�q���D��� ��c=�-"ь&��x{A��A�{@�X��qx��/��pJ�� �ixg%AW�����{��s�:�:{V<�����+bo�+s]C���W��SO���2������M�G?Z���<�U�AK1�{~��{�Z��� -z,H�}�M�0�@�7ڥ�;��4&�a�f�Y�Y��)!�3=.;�r��1��C�x9l���hϝR�L� ���� &\��􊀺u�~+�I�xgi(�������9�J���D�)�^���4ќ�K��.�֦8��dq�]X<�]���������:��G��ڽǐ��~�WuU?U��j1�{�b �P��Z(B��B�'�L�Y76M�dR\��ި���8��̬�Ґ�����fag�S�Da9�T�o� ��@�D��`O�M4�>��N��s������7�L t -6� �K�鯂�T�w����@��G2�"3�������~I��~����b���v"3w�;{���[�3�}�v� G��޽�$�õ�guVU�AK1���sX����j)�"U�cA���nj����H���.�q���yOiL��z�Ѳdg�S��G�_v*�8i ���}6��D���ꔪ23�8��xә� gA虶�K�鷢�T�w�����y5GXiu�z�Ɣa@^��o�h���FFǫ�)����8\/,�n^�O[���5��Nz�Q��qRr�ï�E]�U�ak�0���W"XYE����"7˔�XGIӫ�fIH.� ����ޗ�������4*�Y�Y����A#����b_� ����6�D���R�mEv�Bm3fZ����Pa0����2���C�'����9��Z��n��m���VW���FХF����BV'�'��^r���0K�3rcq]8#ŝ�G�ϛa'�����U �%�g��Y���O��eQL���{P����8��+���>���{��=;�7&=���.��[������R]K�0}ﯸʠVgݛ:�����!�:J�ݹ�؄$U����nݨ��TOC.9��sN��H�)' -=m�&2�Z����<���"tGx��BKh��-�t��8��5t�"��Q>LN0A۲��8`!Ә3 -�4�����\��H�G�"���p��^\�Q�ݧfFLo�)ヂ�>�� �]f���>�J�w���o�� ���h���n�L� �>�����svڍ..���N���S?1CG��T���f#��MsP����j9^�*9��F���=�j�[s����� J右��]�<�hڨ�b2ww�z�ouT� H� -�x��Bȭ��� ������E޵���q!Ʃ�U�>�˩|�TL��l��-G����V��(Fb^��k_��/��Wq�-.�L\V��' Vd������V�)4�J�����9o��ak�0���W�Dheu_��"�m�Y�JIӫF�&$�P��}Q[�s��x��}�^r� WR��(��Q����$j@��wr�Z��p��" ��6 ������Z�;QQ��`�j�`�V��~;`K g�"����h����Rdͤ�2T�r�PM���6M�b�=L -��q��r�3cW���W�� )� �u�GmI�9�I��XKΌ���#�/n; .E|��! -�Ɠx�]�<����è���j���u���O2�?�ʙ �)�sV��ԶWh -�eN�{g��eNM �@�ϯ�CB:�QѭK�X�)l]v�(���(H�2��}-7�rx%]+OQ`o4_��(� -qV=(8� ��D�þyG��7>H�0�]�B�m)1J��I��NA� �~�-k���Zͦ�x'�eE�����g\�0_�b8��H������s�a�L�I�����Z[o�6}ϯ�C�ȅ�b{l�lE�^�v � �-ѶY2H9q0俏7�N��dz�%}��Ï��,��m9_���(#-.���}Z"N�/���.Y���9�D�P�����3��m�OV$~�漞�5�_��) w����� �� -�hVR�O��t sz�C ����J����wKG�u���vk��՟�j��W���/p3È�o�l�X�l€ - -�=�x�������M�z�?��|�-�U2b ��~Hg�-L�qD��5a��K�g��v�-8t���h1�eǖ��b��y=��ә�)� �DXp��l�׳0!�E���Ig���aϥ��~�K�Lb+��Dz��;�V����]��W����tU�m����.ojz���]I]Ռ�9�!��F�.�q��Kr|&�:Nu:�Hf�`f��S��q�H�S�L��9Z��ٴ8�E!$5st�=�0z ��0'n� ����,<��5� -��2O$���S��۠�3��2 -�9G�jy_�K�����Dw[ϚF/��63������7�a���$��� Li� @t�J�j�NHB�CrQ2���n`p�^ՙTe����- m�j%M��f���_�F�vI#��� �o\(�=��/t���_�R}� ��Fc�e��9��e����a����?��P�0�Ԝ�&��FY�#|�� J��窙��G]=Y�����D����\�X-m˝�@�J�gy�XV�EE���LUA����!Q��"�6�|NgL[H(�.Vd�\Tn��t�@�ʆ�(�����;���s���.� �P�2Fb���]0c���L����(��f0}�-�@��.��i*^��i 6��nM�'�1���>*��c�Q=��`��a�W�Y�+1;�k�]�\]* Am�I��q�i�Q�q/^-�/ - �4�|be�C*qv��6hȎpM�Dԡ��Cb-��a����~�j�fϴ7켁{y�n��7���x�1���������6x�@'f/?�C�|��/ܾ�ό�UA�y�+n�����r�p�x�u2�U5�����7��݊ |�v�L��x�7�m"ҭtUg�ɖ G��-�e�-��q��?˺=0eჸ���=��7���c��t�9��{�-5UX��˽�t��v��(�הt:�����+2df�ȱ��\H}�s!����Pm-6��Έv1Yщ"�H;����d������LiȚ-p�t�e�އD�6�F��.&�:Q�k �Z�&>W�����㌩?�*�=)i�4=86�$��"�d��=��n(�͠�V)b��Hߐ���E�ls�š�ϝ�q6a}KA2$s�\�ʞ���m�m� -�ghЊ��LT�@�T� Y���_� �k��O�];��~L�dw]��-tX���'Y}=�����r%���g�bt���h z&z�|G� {�#�,��&H���!ܷB�ΛB�`g��` -; �H �yb -A��=x��{�gwv�w �J��v��]�-��m�G=��zTz+A�١9�p=n�b�z�(B�@����Qo&�;A^��u�Me��Ӷ���H��J��١��}�B/\ Oo���v(�*\>C���z]K��_n� Z��.�ї�x�+G������G�R��_��Ӆ�y_QҊ���O���������VKo�8��W��QɁ z7n�u��6F�{`���&*�Z��l��;|�zXI���l����߼ͷ9��%TB���L/�c�ܐ���,�)��2 [��*��!�H���n� �`rvV�Jj=��۾f�)dz������}=,�`���M�e�%0t��!�;�>���Z`t�9P� &�y�?]���y&E��<� �&���>��AL�E�4ـn��4�������y��e�'�HˤCD�.2�&��5r���$�Lr��h�y����� ϊC�'/nnȫ�Kgs��p*A�d�4R��x�9�U��� ��0�Yu�| � �% {9���5� ױ`�V�v �x��4w��F��gh �m����Stu��H/���h�p��2��ԲhӸ��(��ȆG�Z�Ci�e[ۥ|�%�2%q���?Ѕ��W���L�xǨ0���}W�0��$&'%v����Ѐ�����t ��/@�ԝ]�g�Pa��~��d��=F�Y��2� � ��Jz3���%R?|~-*K'���DZ��o��\�C�� ���^;�7Z�&L Q𼻧�t�� ,0f�PEz�vB6��p����K����G_\�3.��O���(8�yLM�Ic�W3GM%Z4Mc�HF�<�e( �ҹ�� Vf��m��|�3>���'8}|d�����>1���;�3�[�b�:"Y�qՌ� � �ky��JP[����x�ߏ��GBn�WWWc/�I3eN��~毨2Dm��G���m^�"�����=��_+����h��mV8, ��W�4������QY�!fXܺ�97��Ď�,l�K�c��tD�q��?[���۪��V�:���C� ��e��u�ܮ��-� -�2�V��pʶ<����@��.�ڞ��&!FS|�ClDaPХe��~��ف��'۞��U�>��.i��Z����Bش�L>����_����X*"Tw�0_�jQdq����p�Vv䩀)MQ.�#}�9�T�PUM���>�2�b�eW@_٥3U�����R����n�jl�]��v���4���L~������l<�L#�A��oq�B�$����7����0>��q�@� ,�>�6�"�| -"���Ǵ9Ɵ���4E�j�D��d;a>=���ó+:��S+8ۭR�z�"E9��f=�m�Iw���q-�o�;� �9�ϭ<���B]�Awz9h7� �/�� _���!�,W6�9G �χq���� ���L~�A?�z�#�iVH8�.���!�A_%T�����ܹ�x{ &� (s@忕až*���3�i�,]��oԇxBd���L&��:�9���Áy>��}�GIR�$>�0 -�a��ӂ�-���M�Xfhv����գ�T6өy�l�P%�raTV���zc��k���v�AH� �垤;�-�ޝ�^K ��{�����Z�ݴm����[�e#��UA�؄�֎?M��?�T�n�0 ��+��@e �k�u@�v(0��T�+ӎ�L2$yIP��GɊ';^��&�|�������� -���ȌՂ�G�k��'���,�iK��s�?uA_Զ��ui�.nd#$�Iҙ���N��k%k��s�ے��NZ�5� -I1B^��je�[� E����?P�$�=�8�=����� -%���^�#b��pA��-�U`u'�+a..��~ �>?�y�7�B/�iϐ����Cū� ڞ��0�e�F�i E�u�{|��0b�^6h����`��IRw��Rr�U�E�铿�Mܗ�vЇ���`��>�#j`Fج�Y��]i�����n�['����p�K�A�ʒ� َ��g�����|�j#���wd�A�H���#�)��ީ�-`c_��[ʲ��8��wU�w�@�5=�Y�����o�SV���6vwǫ�7fXMړYI-�l�Ȣ�}�����qn-��@���K��4ca4s��� �/�Z�o�6���� -H.{��,�&] �i�x؆����ʒFQ��!���O�/YyuAa�$�w�'�����ZWh��U)��U��7X�u߷�;U����* U�R5Wy��US��E��iY�O6)����-�Uv�^��9���^�uV�u,L���kL�� �$�1dG��c4���� Rk�I�Y�i;��h���� �'K&'j�C)�������^[ٔ;u�Zi�@ө�k� ��eS�x�^s�;��|���<��O�e�Iw���䖅�x��M�-�������R�$�e���,sFc!�EWM�/y:��B�1x��C�AY��!x��TD�at�h��W"�r��G�p�Ϗf�C����� -�(`~�����kC�ˊ�^+�����U ���fni��ƣ��F2�r��X�-2�%��LG�@���t�)ӡk��t���[l�ĸ�F�E6�� e*U�01j0 - �2r�*zș��ڠ�!"f����9�L#?k��>�����q� /}{ހ@��=�g}'Rw}�$�.6��]�D z1�!p�\����A_Å)� - F-�M�9���>-�[�z���F����)@�J&<@�;c뮵K}^ͮ�왊��gLЌ����|�n*�. !E�*�f^�]��V!]�^��w����]�MP4��N#4�-��,�KV��7�&Y���9��4�+�[7���"?�lĹK} -݆ٞB��P���&�9:��%¦t����a��̞]��xz�n��>\���4�;Q�"�5Q�TP���,]�3��!��;� I�˥@b�X{�!1�v�*�aK)2���G�s�ĺ&ľ������~� -�(%ϥ'Q�Ğ�eX;UB��B��}������vC7E���pٺ$�� �y ;F �3X�m���I�9k�N��B�O���fO��y����Fϱ��yS��K��08�߬���{3!� %�+%^�N��9jt�@9��*����f"���D�Ux��RZ�h�H9�|��[B�'cVb���d�Hl����qnA�+�k���N��3��d�6}oHY�u��%��P�x�b�����d3�SDL�e+���V�o(%�UC!-�$���ٷ �n�@?v�[��ڽLRp6VD��x��i�7Wǟ?�D ��|Y���c��e�Q=��:3 -t;j!v&>��B%&�ǗЌݥ�C����Rt�?Z�����_���0|��b��ۍQ�+g'1~7T�/=uZ�;%�l?�ԢxI�;��-13�Q�qO�C�/������|�jʉ����Ln�T���,��W�0�"N�5�2 -+�9.K�Ή"ǹ�t�<����ng�)濵O��y� ���V)܅x�DkH)����ÿ��̔��9�p�C��ť�S�cr�w[L��W�w|���0wq��ӗ��&��Q@��Q�dp�/Y�����W#[����G��w�6�ˋ�ḁ6=����v/�ZFq">�Q�ʚA -\XjHX^���*[x� -��ÿ Z�H}}De��gd^�k���_ g�ҨV�����ܪ��#iz��A�$+��oܺ��ʍs�(� ��K�`�����C�E�*�m��i˩zE���$����S[k�0~��@h S٫N�E7�66�Sa���͈i���2��;���a�1=�-_z;(��� 7X4R�'. -���n®�� l��I�&"�0�GϠ�p�M4ҩ���<"���q�s�Ȕ�AǖE�'U\��x��pS%K�(s�R� � -��Ҁ �E���4bH�SH6�I��d�i����ɿ�a�l��v�W{�2n@��dZ=深�d���w�t�֊�y����,^4�H����&\U��h�@g�F~���GW�f?�����C�+(���\!(�!�z#���� ��_cOt�6wF\����ТN,r�� ���0������=?OR���y,�ߍ�~������` � ��Xq�zL;�؀���9�N���/|p�`ms���VQo�0~�W�R�Pm��1u���6uZ��$�:X Nf;4U��� !���./������;�çd�@�����6�W�RhR����0B����O������6���vK� �"��H�0��$�w�Ӿo�Voe��Wp;�t�k+�F�tc�{{���0���_P���p��w)��:�h�� ��5TRLr�P��jA9��d`+�%�Br~e�..afEq�1����0�X�m&S�:��\V��)gw��I^��5 �&~'8��M<���U�{����J�O � �Y3�8&�N��p{y�$f M !�ک=5��>̈��Z�\�Dz������`>%���5�����n��t35��몪��l��2�y�&�X�ýM�>x�V�ܘ�݌:m�0����lFm��m�`o/�D��9e�,~L �/�(�����u��t�����v��w -U Ц��r5�",��A�n�� ��+ؗٯ��[wcY��;K�����$�wy��[;���n�ˆ���3�n�/��v)?��_�ں�8l,�������S�n�0���R$ J�z%�4QS�e۪J+5U�ɂ���TU�}mLi�C}0~�ޛ7��J� -2�%���zSh` -��$�B�Gx�q��׶`)w�_(P3�z}#lU�z��p���m�ݔX� {mU�����0N�:x�.Uoʂ�����}��œH�I��� +��݅@J���se�p� �i�nG��Sә��s|�V��E̍�]�+\W��ڳ 6S��)��(�:���nH' ��;jv�{&�t�}7� ��g� E�1 �!Ew�kl�hIZ�j��—�{/0��� ��s���;%�%0V�\�^��!���ښ�"�q�O�І?���6��N+��13#B?�FD��*a(j��24���D���ҩ����y�=�� -�0E�|����*.����1 1�6��^^A��t�;��lv9d��ON�������B[Z��a�@�΃.= � -���G0�i{�jHgLd� ��O�6T���= 3{��i��k�i״�y���|�T]o�0|�Wl#��ӥQ_�6�tM�HU_�<�(r`+Wپ/E�߻C8�Kt~A�����߫�� ��Ȍ�"�Ov[��o�%�����x�pW�_D���$�P��V��Z�D�Ҍ�5_�J梈��)�lo�E�S!�!5ƀ��к�L�'L�NJk�Z�߹���+nB�\��w">����B��/ej�����m{�IJ�5%�{�f�R"��� -m)��U��>�H%D\��}X�{Җza�윤�c��P�m5�Ʋ�G��.�������dǠZ�.�A��/��Nr�����r��Ҭ��66���V�;�������/�"�D�0�ɨw�0�ӺeK�� qݎ�z�b�t���k8��� He���G����7����ֲ�{�{��Z���[9�yA8�l`Nw54βh:����Gedb_#��.������t,�\�fت���Z{?@]R)4ey��uCN������F ��(3���k?�4ca0w��D�u�1 �0����� -.�U�J�v ȑ�!V� N��3�?܉Ҧ�R�hqk��Y�ş �0)����< � ���ڑ��� n���i��J/>� }�= �@ ������bQ�B r^��BM�\*�{��`������6��TµV����Io�ִ�/ � ��t��RDö�b��"��!Kc��G���r����Q߰B�1���I�Ơ�2���n(���m���i�1a�Ma�w�0����<}�1 �0����78�ѹ��U�۱ 1��`} ɫT��n,�E�-o�����w��^dQ�5r��G� ���b}E�ڀ�%���X�Z\� NT��+5�?��]�{ۣ���%!��� ->�'�8K�D�V�����[C��F�c���մ����O�}�� -�@D���-,bi�� �I��2!qs�m$"���� -n���f���C��� �*��Y��v�Y��1X�:����p���X�.8�:Hn��p�X��Z��ք{VH����;��9cRp�WyJ�K��#;�n ˾�h�y�����W]o�6}�������^�9]���� ��-Q6Q�Hʵ;�>,ю��ay� �s������l���� �+-Y�z�Q�F���u���������R9�Xl�)��h!�Pk*�{�\�v����7a�R���� ��:'���1[y j� Qj����Ct���k*c�d���3,�l�"�Q��%�;x���7��P�����Є��r�D�n5�B�K( uY�ҬH�B�ۨ���!�����!�ȳ^��aʸ;{�����}�K��HD�$;���>3�6DStA��x}�F�� �&=�.�~�ʵ]Η Q��P�*Z,B�!�y�q�K�Eh�}����k�7U8p��6��7�S�!7+��v(�p߮ނύ��L�2� -��p�8Ű0BMR&v�WD`8�Î�Cd�_5"�T璣��7�Q�� -�R������5�,�fke2��g) ZH����LxD�&3D�EZ%���5#~^BAw��� `����l���ɱ������(�ΔP� -왖� ��=(ǝ�F��5o-t�ԣ��Z�jMn\j�GX��P n"��F� -�| -��f�Yc�~ 6���V����z*"�k���H�����W��Wof��ш�ŨtWMW����P%�i�-�zd��ha?A���uS�\�Vl����j��W�TW[��p�U�D�pU�{�ul���zL�������E��{�� �W�N#9}�WԎ���F�f�Y$Ha�Y�nw�َ;k�C���U�����خ��S�|��Ym ~ĵ`�j�ۅ�o��/��w�S|-̆��Wb&����y��Jhnc=�P(����KLy/<��q籟���G���x�t狍�����@�>!x�ۿ���O?�o!pqcf�k{�m�oݾRV���n�{ͥ����d�V��gf����Br�#A�7�S�: ��8��ſ�3��\��)�>��2.���W�o������?1䃱O� ��2��~s����}}A�@qW� ���P.���T�TKS�&ʧX�b� �ķ�tؙS�+����ە4����*�6.D1.3�,�tx��:^^�k�]1�K�^����)&�&@���Z�D+T�����O�@�8�)�/�Z��2h�7%ʳ��{K���HE r���z��]�Y#Y>f9UմZp�0�[�0&���(��z}-����T�D�E@��$�jLL�9!\_rȈ/�HKG�d�%� -���&�w�T�!%ei��"�6�a��V�� Rm:�A�:)�;�I��X�}�����:��k[_;���k��JF������_!��$�������/Z2D:��y��KK�)A ��J�����K�[�ᱩ����zk�n�`�U_���9^W����b��Tw|m�),Vv�Au����ٝ �,N�/�J����y���Xƴ��>8 �^�wٵzP5$;ܜ�k��eU����z�ݭ#��j>�V����Y���os²;ilEyG�J�Ϡ�3����'X�2����(�v�O���"����RXu�hY�L���`-�M�"9�� �j�b ������'�*����7z_����4��w9��ͺB3��{$�ǩ��p��s_���f>L�&���2�w\*���6��Lp cZ)��dC�޽���/������P�;o�Y�ќ�-Q��F���T;�Z��d�M�vï��N�\C���`8�3.�ޕ�����q3���ha��J�2D�1��A�� �Ç���A�vbh6��n��N��1}�)%�z����W:E��c@4`4(D�5߻�M�Id%��xaƖ 3���c�?12St��]��˕u�[i��$�O�&W�i/@��^L x���s?Ǻ̠��Y��o��V �2� !��H�4r���|���������cݶ����HU���/��}SK��0��W�ڤmĕ��. v �J��Lµ�=^Z���'�6IS�(��3�{_�%K@Hn �h -�<�`ق���w`K.������ ����gP`8j��S���̃��s]�z�V=�$�@ἓ���j�Q�k�m��#$� �0��h���Q)w?��#� �Y��w2$2��'���&��7�'��↿x�l�������&Z!챁�n+ �R�Z�͆�4m'0��ii�n�%���|�0/�t�яEU2�����V_��8l/��3?��󨣺��¬��皾��D]Cӥ�KZ+-��?]z�_�-�%��s,.�g,����I,ufO'�Hم`I;��^'0]��~DZ�C�BO��}���<4� �sǞ����b8�5�T��/���=��sxP�-��/��]� -zsSs�[�Gs�u��`�m֘�M���5��r�B�:�j�i��?�kSۺ�{�c���(m�i��4��ii��ܹw�(�B<8��,�3���+ɶl�!p���8���}iw���YJB�T0?�" -䥼OYF��v�݋��Y�Ҁ��;eL��3�a Tr1%5c�݋yV��~��C��̃|���==�Y{�E�(�Yv*�����WK$S�zi�__��( �_�4�3A#i�~�nȗHv'Yf�p§�,�I�y����<���rg���4��Q@�d�|�vvr -��fR���ͻy�܄�n#ar�*�1��>Bn����d�|���L8����F-�*YM8pzxC��Nb��4�X͡���)���%�rA�&�Œ��b� 3�j՜ V(�Df��x���͜��Oν�^�xt�����@��J�.������x2���t5�k�y8�}��' _FB�w ^�@�M:����gt����I #���KeA"�_ -��t��>�re�y.����(A�}�ε���Ld��4OS.$�5�WL���(����Q�u �R 8��� ;�����F&���\$�O!Q�7��-DȔ�)���OV�Z7< -mA�|� �i��oI������f`��@���x���T?�Bb{|���U�?�%�?>�18�m����^���:Q¯�UcC���"���K��1���g���Py��O��d��#��n��#���G���D��#�l��m���<����0����<��UXQ�% ����%�l� -Y�h��l����b��#��`@�[z��K�<��+�����s�+[0є�0D���nپ��XB�B�?�Eq�ז \� |�KBU,�Y�;%A��.h��^ �%[� �ƌ�n+�C�`�V+�����h%�j 3���a�jA�%I=�!S^֖����S?c�t8T�#X>��L� -Ѵ�z #M�i�)��%Hz h����$����T�~E�����j� d? �����4�,�{�@�x0H� �m��%l�j�Em�3�PQ�@M��*��S[�B05��c��B!���օ -l랋��.Ɵt@�s;PZ+��x;�ũj � �@� K�T�c ҁ�K�]6܉m�?�V ��_�&_E\Gފ�%��!�ү���aT�Y�sE��AJ�����s��`D�WѡA����5����UwVS�t���8��o�8Y��,�@Y�������TH�ƌ*��F��\&4��1�_�E;��(d,�,Za8���lB�A���;���()k�H�1�_��(���0�½�����<\���n_% ��o��������2\ֱ25OC�F5v�����2��>�����rC�fq���� x��.LJ��k�����S��4�u���K��e�`~;?�aPN���/��0�T1 -%� �B�y��/+g�2bN�c��Gh�~��~mO� /�-�hHG�W���F^G�U��;N��T{-��mX�|���{5a6 ��k�"�����p���ΐ$&f1ؑ+R(a�ʛ�G?�/�s'�6 �#u����˳P��������fݹ@���?9_ɶ�́QE��L@�nBn�N%�+�s$_��� �,��d]Q�x���e�$��i���.`��Ֆb�G���Z%H�4�o�+,�*��'Ae?�@�MIu��b�ST�8_ ��h�N��j�e0˓k_C�IY�*���U�[p|�"�K�j���*?^*=�9c-�TC��2�4�3T��zQ#Ms9�q��^LV����CM�llE���G�����,##3L��Տ�� p<�$O�;��l+�=AV�CI�1y yu��%�,�XfASf]h�R �R�k�^^h�&33V���cb��M��7*�se����5s�f�p�I�Ͱ�^�u�u��rog�����������v�f�{�l[�^���}�����}�eC���P$�㧘&�L�l#d��5O�y{2x8XL�p{g������������y���ڞ�ۄ�Be(�{�:έ{n����ٌ!TL|Zs�22�N3"a��'�����X����c�l ϙ�>��d�k$=,�a��Z������zf\��Jj�r&�-I�-q�����D4H�). -p��RI��� �Z�O��Ey�`_.���10?��ԏA,l����BUPV;�|q������ TSq�'4c�p�i2��9u���]�ڠ6���9Ly��ny���<\Y�V�Z�� �� ���ʐ�&gc���R��z��[���U�j�p��]����HU�ؑ9���^�^n�ھv78�X�{ۚ =Us���Y�誤_S�B0]r�PV�� -tb�V�)OJ�N`پ~��4R��?MB�Qi F����,��ڑ�[w74�v]�� ��ء��*q��`���U�~vd��$��J~�0Q4T?��'�&c�?��z�L��5����6꾠%KX��W�@]�t�%��N���0ia�Z'���Mu�:��µ��+�l�6�%qX�sK�v1�:x��T'i�p|+`0�Eմ��������}�X����jʨ�ؕԿ;:U��і��%Bgk��e�xo9:����h���rծ$]ם�'����w�헸��ˣDM��B�O�{}�&�:��/@�����g𭳕�q��Ou�3�ƁW)�Fv�s�t���)���o: k�0������짻��O��V���x��`s~�L�Q4�i�u]�y1�t�����{�?��cr����_���Z!��O��%�� -Z�u�y������9��=�ytK����I�²�1�J�3g�k�Կn�.{:?Js^�3)R���(�.!2���8�,Pf�g��S9�J�����d�a-�_�B%���W� BDŽ��vu��� `��H����!��Z@Y��-&�vlpW(�φ��ڨ >��7ݎD��2�-�w�ey��Uƨ��}��ʌ`S�\���K �������Z�����vEO� `P�����K�X����"�ʭ�N��Q�� ČH� �� �+f�I�^ ����d �#�1V�C(f��/�vn�Tw����Q3xx����O�0���WxR� -(��6V�&1�Ĥ�T�����v�\P���}��Ӧ)/��$���~��|�(�MA�@T��'�(�bCv�gP�\���=�S�H��נ�p�f|�(*3��z�KG�?��U3P8xeuY��~�T��K|�*��'�UŮ%�vؿ����z͛�ؗGn�F����wi�#G`�ډҶ%�'Z!�х�uRH��Z �Z�Ʉ���Z`��ǫ}藡�8��]�������a�2�txNg<.�l�����I��Q|����4�ڨ�J��~��U7{[Y�ɠa^J�W�T�߂L��zՉ��5�ŭ�(ivF>a��`�LY��F���`B���e�EK �W$AI�T(iD����n����uG@U�%K�.|.������Z��e#�<�Dtᛘ�xR��r�ăC��o��|#g-�������'']�ɉwV3�R����S�#w�(~@���s6��o�M$�щ7_�����);fa�Nv���������M\�m���0 E�|�G�� �>���*�4���J�Fj��wR!���>��쏡�� :b����LLڬ3����=.@lR �cS�!j�)\J���R���*+�]1��F�똖���4��֐񎅊&����,��L 1�u���d1��~��?8)]9���?3���[}u�[k�@���+�A�����E�7�V���Ʌ��awJ�wV-���C3s�9{���2/!A!c�MK���S�^���T|@[�a�c�h�,�q;E�&&m�cŪM��*{�K����jQP�y�����F�H� ��gD'�������B���loˈ��9Q� ��Av�A��]�X#|���M��p��F��j��9}�*a�����H+%�� -2�%_���KBe7C���(��ȹb���o(� ar�qI���B%�j֓�F�mhL۰��Xⵞ^���ג3��U%e�f����op(��)�����෷΀��mh��!wiH�ƾ"l��3�8C��P:��XV���o�Odg�-�= �0E���7�Ep��P�&)Ա !��b���'���� ��r���Kda��X����]��mV� -z'm@7��}q���ZbP�V�h���� 71<�'a��8�W'����(*K9 -������^N�ǵDK�*)��i�U����T�n�@}�+���6 -��BC/���H����ڬ�x%�k�Pſw�6�\�~ص�3s��|���9�(3a1td��%mstp �QK�5�\H���&f���� cS�^)�Q�U�W�d��<&Fk�4���n$椌>֟����������s�\��vX13pC�c_�� !i��u��O�A�]+n5�M|iZ�ߠ�-o���ga��{��wnճ �Nl֜E)*�2%!)����r)��< -Ia�ߍщZAG�wT��_�R�z�̈���,�-z��̬�� ��0�nx���l}Ͻ -�!�6�U#�E*���Fj�΂7�����v蠿8̳Qq� �c�T�׵UYĭ��Qo,�Ĭú�ҥ -��.�Qu���a#��a�~o�-�VM�����Du/�^�v�K�M�dVd��@�\紅��T��#%�7bi��wZUD�f�vnM����QtP%Ƣ�)�uI̓���Bkb~�>�Uپ�D t|�M�T ���`f޳���<2k�ʠ�9��M��8<�z�Yo,9_�:���sC�)Q> ��>��M_# ����E�2;Ol)2_ܛV��%�ό�#I�>�#�Cy �a���s��ݞ���-�%n�#��=�C�Z�_`�O��2| �)jJ�������<%�M��~pu�ˀ��g\��Zc���v�D9Г�ϗ�\y�k���=8�~��i�֙����v�\X���)l��3B}�Q��:'V�ϑ�g�.l�� }SKo�0 ��Wp@Q;�c�e�V4�6�[�=nE�m���ѥ��G9nc;�x�`�߃���cUV�E.���:#�[�� --d�n4�ۡ�G�U�OD�"�B�W�Q�aN�Ս"T�fEޞ������s#q���:�>˵V�(���X �/�;͏��7�t<�WçGf�mri�VF<2�pQ<�I��0����5��o���{ŝ� -�k�����j ��F����p�et���������-�ǝ�yQ������`��v���yB7F�f�Q^fcu6waFph�KV�W@�� =7`I� -�l�F�E�V�y� _�|����;�qW�����o������P)"�%���̉� ��yl��x*e��0X��7m����= -��d�ƈœ6^���*b;$����|&�D%���:Pc�q���g�ĭI�N���Wܠ�~��]��Bn�}�"�7k�Y����sB��%��Z���]��-� ��>���A饫�ktqe�E�G�߾y׃�OX�˥(O��v}p���/ۻJ��+h]������R���rJW����S�x(��2��� ��FX�{"�uE�>������e�Y9о ��x��Y@74.Et�و�_4��1���\6eXڕ��nG��ڽ�]���2�����֞�q~��|�|�\~��:��$�:�NG��O?���4�O>^^\}݌�������Q������"�_����;���'�T�+�܎��"�uh�h��ӣ��ՌČ� -��0��0E��V6k�uc�&� ��73�� � ���{�9vv�Z���$�n��(~���j�����3�+�������t̐�� ɛ;Hr�{�m|�ܭ�}0�y�&Q9��]��YnHk�b��qռ��Z��2_�_�3�۝�: �Lj�{j[�kȸg���.5Kx�<��h�����; ]�`��w9� ���H��o�\����<ٖ֔C�������W�r���wh�~�rN��F�f���>��o��A��xO5�N�ߧk|�Ȫ:S՜��"W�����m�yo/�W�����H����q� -���KUʽ�Ռ����tą�mj�GU 3�&�-"J��?��=�mV��O�PfN��v���ϑz��i�*����IW+�����p�����UV[D7z� iǜ���5��=O� �|�QSU��$�٬*�d��n0����K�0��E^�(���9�#j,u�B�p֣��9��p] �Y`�]{���� ���u� ���X����i -}�ބ8oe��4 -u'CF�^~�0�gi �$` ���--�[�Y��C�51���&M��֊`E�A���~�(�x#����W��������}�A�]��z�ut��kI�O�����e�u�%���~E����������L�'��/�T�o�0~�_q���R�צd�F��am��O��O�f�NM��w���A����>l�>�ikdY��-��L/�.�x�MG"z�"����Ղ��.��@i�ׂP)��hd��/�W?�>���N�f��3��,6���yB�d�(`�m� �>�������� c�����uͯ���H#�%�&�"��0)4nu ��*� b#��R�rIu��0���X�om��[���\g:��dV �FP�L� ]�`�U���%Hn�1�y�S׻��� ���\�G&�D���p��&3�L��d��d2+�?n<�kN:Ю���ex��3�Nl�x '�9Z�o�'�4*>i:�+��uJZdz�l���r��N�����8�ZT���Y����?��N�>���cc飫�P���|�(JQ�N�Ev�? -�V�~|� ����o��XtC�������⥼��_�Bm����i?�F��r/�br������R���e5�\��>|\��;a=�؏��V�n7}�WL��}�ׁ��|�,àv��]R%������R�ڛ7|Ѕ�˙�~�m�ZC*����32q�i-,|�_㣑ⅰk����B�9*�������N���B��0G�Qi�r�����8�IY历��s���6k'�j��@�.s�Eez�p���#��KIw��.��Ӝ[{�q������0Go��j�tM�QB��ϛKa�p�Q['Tj� -�J5�b�f�c�j(��y��}�w��Fn�p�h��%�k�n��9݈�N�E.�J��6<<�"�-�:�z���x�ց[I{x�{��@�y��F�vb��K�.���p�݊�q�|���WHlπ8�� $F��(��w���E��Q��鯃u����F˴vP%z� � -�~JP�� ����M� �����<So��U���q���Ó�m��[]�DkX�c3-��� -Dp�9�m��M�6Z� iZ %_ܞ��x����ϭ�weW��-�m��S������7�t���˜�<����a��7�2��x����ݚ��i�-��\�"�U���w���L�*�a�*��A=O��f��mz -���[�'����7��(�Xh;�d4�H+�F�d,��S -�Ģ�H�d��{CY�4Ć�����z̫z"v���,��ŝ(��a�%o���]�j$��3�^��3$�Q��q��� ��%���Rc�,"�[2��� "���=hj�|���ty3U�\{��n2�Sesܺҩ��jN�~��y�(R�J�����x 'n��pj��x��n.��w�D�, ������{`DF|kP�Z ����_5�����������pR�K>� �R8�W ����j���_q��|/"C53��kp 1 -��p\*(��I|�R ;u4N�TV��N�jd�2�I�� -�\9ߙ�z�?�����P�u3�P|65o�Xp+0�C���!G�x���s#Ӓ�;� #�/�Er��*@��T�>l|&�\оT��m����4��S�%�&�~��� -��F&aU�l��Q�'A�;�c��-�"�f3|N]ݢ�:y/�+��/)t��9�Ꭳ�8<�-��c �W�fq(��o|��m���;n�2��E�-����͠�fa���3\ � -<"��[�${Th9i1�3OrA�5��zL7]��Q`C��wk���0V֜&=�u}��J)�\)AR+HS��g+A�0�x*��.����Ե�߾ ���TY�e�'9<��s���G]�Z�m�Ak1���s\�-���l�=أ�����I�̊R���9f&�{o�u� -��2I�;)�1��'3C�S���"�U���$d+�gƨ:%X��2PK�~@�� U:t� ��VFY���l6{�`ې�ҵRn��k�NTw�.�}�-&W����H��[V/��w#�}z|�,3�s����t;�l]��1J�4� !D�b5����Ͼ�g�S�y����m�MKA ���� -�<�CD�7=�ca��Fw�f�L�T������2�I��K��P�Uj�߉2,�n6w�('���ވdk��x�>�x�2w��9���]���m���T�z��8�J�^ &%h@i��. ���"C]cd���U�_�OB=�fgj��v!߬NS,����x�� -d���^x���jP;r�^/v�kִ(�v��:�_m�1O�0����"5����0P� 0V�畸jl�|��P�;�ӠD�K���޻{x�z�E6ZJ������2��E�J��|����;n^a�J/�,�C�������`�p�:и��Q<��A (O�HHŮ�M��j1�RYjgc�NK�#>��Pަ�M�8j~1H�8���b̹H�`ï\n�V��έ��\ߐd\��ұ�Цvc��p�N�㊯�f�r�'� �S�m�AK1���sX�T��Z=X��XX�쫛�NB2[*��n���.4�@f��f�|㩆��2J�F*�񈴠��\��#zm@_ >��J��;�^�Z\�+��1���~�4�~9��ppiX�U����Tde"�b��YC���X�TU�q��);��z #T���s�5�d^�e6r.�4X�������n���s�t=�7$�C�i�hcHp�b�q�N�ӊ��&�r�%�����m��J1�{�b�n -�k��M�XX����H���l�H��l\Ņ�!� ������.R �[A�Tk���Vt�XoH�2���m<���b5�Ҙ�N��w�Vt}�W�I��DMv-��/C����UЬD3Q�}�wL�gu�S]s�9۳Vm>�:�#5�L;���~�X������������?Ѧ����8e��|�Kּ(�i8��;�om��n�@��{�-(L�"��Q� J -(���1������@�w�l�E�rv��l�@��dQ�;-�iN��ܰ=#�@� -[@���U_�k0�j-�1�LM�&F��9�e]�`%\|��1����PR�ZM�PI��oʓw�ݰS_3E��i6�7�i�:��&Z���O闗�.p��:�W�@��u7m�AO�0 ���>TZ'1$�� �p�Te�G3 �r�i���UKh�rK���=�v�6-��v�����I%�-F�������Z����;"�T��g$d+�ƨ:Fx!A�h�C�(��i�$�A�#�_ �i�� >i�����;�t����r'9wN�z[o� �|��s�J;=���d>23D#��Bg����<�/��D�V��hF����0�������g\�M2�:�$���m�MKA ���+r(t{P�\?,�Ez,,��oݑ�2�R��w�j�-6�I����.w�Z��TE%��+�� ]���'J������i�|C�&�;gt)�� -Y���������B���}���oGVY��+h�����Y�@�����T�!��}�j�xi>t�����h���гU�)�����w�Q�����Oe��>���w��ٮx�7L�v��Q�s?m�1O�0�w��"5�(k t!`��R�:��U{��KU���9�&������w��y]�T�mlD�$z'�|�HtG7�a�E��}Tx�Ba�x#Z qf��S�gĕ��@�P>��B� �L4|�6���~g��N����,7�Ѫa'>0� ��'��\� e��v���z��d�ruD��t�;!tЮ+�ʧ�}O�= ��.����A�q�4����a1�FGy�k���_p�7�I��)F�`~m�Mk1���s\z�*��z�Ga�f_�M�dV����R瘙�y3����D5��2�,읔rL��L��� v���}7X�R�=V`+�g�(�3}�ut)�e�G�A�Lo�������J��V@#Uun���;Z�������Š|��C�N���j$��/}O��t#��Y?�>�*�r�g��q���y�/��=߸7Msw��Ux2m�= �@ ��~E�:88���8���B�����^�\Z*���U̘�}B�3�{:�\4# ��H*W�@�G���k������E��Wp`-%���YLj#�h�"/k8!4w4�V��MQ,϶���@�^�*�XC�����45����H�ʛ�x���;�Y=�mL�A��#?���� ҲɷĐ��/�f��m�= �0�=���vpp� Aq����6�IH��H��釢��w�>�M�tT@]�G�k�?Mi����W'h_b�C ,������l�X�ǤTL� �6��� � SZ�+]�)(���&4�����*�hE��(��P�)kb�R�t�6?#�ci�55�R���>r3���'p�&ߒW���o�/��Kk�0���{ā6�sꖒ��ri����Z�JBZ����^9~�i�����0��� -G9T)=��^+���!PJ��R���m �~^�q� -/���q;�t�����6��Ä#�䁞�Q@*n�'A�9��A�,Eڨ��w�j_jE�Q���,S�Dl�8��lY��u̻f���o…���T,�WC?9�#S�����p�D2T��ʛ+��@}��~q�-�<��x���[�iO�����~�{߅�Ok1���s\�<�m)*���Ga��i7�M�dV�7��vWl�[���=�<���A�����gҊ >:���l2F~�wR!l*\#�6,,�a��I���a�{xoG�~͒x�GÀFSzx� �R�$ �om�Ak1�{~��C�jKA�{��}�t&����Y[����y�K�x�u� -noER�NK=G$Z�l27lH�:�G� �X��� �dnLN�D���5W�#X 'W�^wY�Nv� ����F]C�n�n�}6����t�s�qZ�*Tν��Wm�Hk����n���#��U_Ю��m���CL��)��w{r�>�Ɲ4��4�ss��OK�@���s(4-x�-�ŋ a����8��NJE�ݝX�I��9-;o~�xsy�%��0�Q�Y)�=`�+��-22o��/>#�J��n���l��"�t:Fxꤏ$�}�%��-����p������#��nka�Ҝ2G��k4��Y�4d�y������X���;_�x��g���A[�\<�����aL+l5��6_Q�l��vFi�~���2?A��{�t����B��4����ӎ��>��OK1���s(t Z�\� *��K=�4;ukffKE��M����Z�2o~�xsy��F,d����� -.f ��r���%"����߭�!i�P*N3�s'}r���^�&yآ����n�q�6���٭��͓�:�d]�]7���uF�wP�ƻHn�y��o���g��� �Dj��׽0F0r�C�G���I�?n��O�n'���o��r�'hx#/1�F���$�1�4���9�+O;�^}m�Ak1�{~��C =[[ -��z�Ga���݀��ɬ(��ެ�֥���^����&j�[���i`�퐐iA�����<�>Z�]��kܯ� PoQ�Εt��~]�W[� F�����)�������7�d�(���l�g'l! -�5G)���J�bz����j�&ֆ|��w�Ũ�9ޮ�� �5��N�<�4�Ќ�ӓ{��7�������/��AK�@���+�Ph -Z�\��-޼�c!l7�YHg��I�H���l�6Ru��o��x{��+%�Z3fA�)��c��f Ez��k���� �7�`��$d-�J��ൗ�Y[ +*W{$<R�i��H�~)���� L�����vp��mm �7d�:��0�"�1�u�Ϯ�1���7��ɵ��T6�>$Q���!~h��H��x=�Jj!��d��il�fh��/��I���k��^�M�<��ǟ��ԣ���OK1���s(t Z�\Wo^�caI��X�03[*��n��ڕ�s�������}��hjM���5R�g@�n+��r�aS�������/萴xZ)���u�nH[�h��=:<���a��_ -��{-3i�"m����fW[o�3b���0�Ejc$k}}]c��#��(��5�L*��w�(-�S��S �$R�/{G�z��= �!7��3���L�%v��׶���x���-9��|��7m�Ok1���s\zֶ+�$B{���v�$$��E���#��9 �x��y˧XE�0�N(2'g���Lt?[(����6�� -o@ڋ�%��Wx$�!-�wδ��DXu=����� -��L��k����H*&w� ���$�~�jg��]�T�&xq7��� -u�A�Xg#�� W.�=��<������-�����/,����1�A)�\{����Jz�hS цv>�����<�m��j�@ ��~ -q ���PҐ�J =�f=���E+��w��lhS���0���{�M��5�2 ;+�|E$���٢��) zk�x�������`#�E��h�Qa9�H[1,��z��=�ol�~����&�dS�8������y+.x�*��;+�`�'�l�e�Pi\�yȊ������-�|}�Ɛ��?�%���5�/X㟯��5�Tc�C?ϸy򚞩����OK1���)�P�T�\� Xԋ�caI���IHfˊ��;�킫��q^���dn�C�@�TD�8Z�$���ټp�)( Z�Xq#�G�n���8/ -q�D�� g{8&� g=l�4�ݯ��B�{Š�u�FM���k�;��q��wTU�;�5�˗�=".h囨�k��rM����{){ ����,��Y9���܅�?C"��n�u�����w���aF��c_�N�PS9��한b:��7m�Ak1���s\z�Z�J�Kڣ��dt�I��ʖ�鶴�R瘗��y��]m�,���0��GC�s���wD^�a��3���cԝ3�^V*�� oՐ��{$l�gx�h�ur~�6��8� �ŧ���l��`ې� ��ER@㤸��c]�Y�"�~��H�ݬ�X/���p;r���)����0���qE�C�#�ڿ�Yo�z]�qG��؃�m�AK�@���s(4=X�\�H�"�z,����,��evR"���֦���}ߛ7w�XE*ak�ȓ��R�wD�{���2o������ -+��jx -��l$�,�ԝ-_�*�yoՐVbX�w�Bh�L���c��?�Dv;#��;9yP�M�,mo�OEa�W@c%?�,B]�O?#L��nFR�t�ЋZm�}�_�����s*C���!���r(�C�w=�5�X�[�v���;����~m�Ak1���s\zֶ�V�S�(,1�v�I����wV��R瘗��y3JU��� ������'3C�9Y���p��7jx��� ���gƨ���K>�^�_��$�������7��r�YA]�X�G�����gCNB$(KI�����O�a��f$U��^�b�����v�J_���f�&�����r��c�{ 8����N{�TO7>c�m�Ok�0 ���:�ع[K��v��X��-��6�R2F����KC�������q��%��f,��� &3�r���5"m����$+�fƨ�V�+>�^�%y�a�V0� ˭FXwz�3���� �.Q�ȃ�lk�� N| P.4N�K�s�k<�W�� ��H*��y/j�AX�i?��/�]S ��p �(�.��xh�a��z�2�yO̹;�� ޛ��OK�@���s(4<�?�z�Bz,���i�n������;�&`���������ۦn���F���R>D����2���1��Q�Q�}�6��`#��Y��i��R���B'�U�������i5��F@3�+tʘ4����nw��k뭸�,m��k����; Ψ-[��z,��T3�]<��_�fc�9�(����NO�����! i�O����i�`����� -� �����)�\V��"���G�!�m�AkA ���+r\zֶ�V�S�(,�L��f�LV���ݸcK�1��%�-�R���k-c����Z~fx����Ɯ�C�6�A�^c�{CB�ya��s���Z�-� yE~u@�^�|��­��=�IV&2x4l�}|v�$D��v���9��Kl[,��9�/3�&��"h�?�E<�>�Rޮ���c��������Z�-�T���\���"O� m�KKA���+���`�s| *z�@r ,���Xg��ް"���>��.�뮺�Ou� -���" {'�|%d����������k�>��)v����cԝ3m_�*��z�;�,�'!t�Pez8*޺i�mH'�?Y-dp)jض��;zo����Aͭ�b�?Ʀ��M���g!��Ww��1��x�|�2>^�'1��0�1EA5# ߴ�K���W�n=�ֹ/j� =�-���@D������1���@Ib�c��]��1������̛�6t�Z���Q�wz�w@� ��Ұ}"�@u� -�& �~l�`�U/�1i#��>��J�F�]��:]ҏ�D�p���;�=� K����ߛ� L����哙�-�1�0����� �3c"���Hbj9���M�0���7^��n�u����i����!ІVY�X?�6����u�v��`x-�JE;��|�z�ܔ/�F7�v׸��,�GQ��V` �6�Y�;�?������3%2?�}���nR��IM� m��k�@���+� ���Z�a��^�(�u�l��0; ����M�1�s���{���**ʡKň��ђ�oOOt�,"�~�+�A�+�7A���OX�Nj( -j�i�����J�{+����^���t?��(�v�����(�&�������(�q� -�����F:�t�B��3���� m��N�0��y�=Dj*ז� np��J��,Ĩ����*B}wS�F�o^{��ٛ����n�r�΢�w�H�t=_Vb�8c�V=�1�: �~���j�uYUI#��8�#[7�v�T&�)D=RP�&������s�Y�\�̳�;T�m��jA ��t��X�L������m�Ok1�{>��C �� -���A�’��vSl&oE�{�a�.8�ޏ�<��.R+|�*U�:F�s�Dϴ\������e�]'[���:���E-�����h��a �x?�� ��DoMv-���P���h!4�P�D �����z�p�S]s��3��o�a���H 3C���K���7ux�޷����3*��O�)�yi'N��g��=k^��4�e~î����N�@ ��{ -�� Ua�"�Ё�����('5���T���%P�*�p�}��ۖ�oۺ��V3&^�)dע��L�t���a]�"o� w�� Y��R���|�0<�3�A�X�t�aI%v� F��U ¤tM�0��������(���Q0��y�&���1���Y���`�����>wM2�3J`a��k�;��9��% �V��Ը@���q{�뮾֚NS���/�% ru��= N�\���;��'Z���q�h�}��ګw�VmO�0��_�NI4�k; -S?�E�I�(B��RO���V����y��ľ{��=����$]�$�D|��Q���9&G�d��5�4�@.Wp 0��g� #r2d�2/g߾��L�l '[f�>g ��Rh�^Z)�]�_@�<����}�$���>���wn*��T��� 7�d� ��*I��� z�"�^ g<��C��m�(Yf�"���P�5���_(U�= �L�z�D3�WLNs���R��C�8*4gx&�,#���S7�;� +���b�Lf�o��\̟��N�y���M<����On�K�`��/%��]���yjؙ/���ע�Y�V��W��p�;��{Td�0���͸��+.�?5��p/���Lɇ����Q��ϥ;�G��n��l�S/�4���:����k'qf��A�����$�)Y[g:C)j�:�ݱ��P��9.���rJ�W{סwP��1�� �l:5�y������+�v)#�ݢYӛ��f'��`^A�9G�K[/]•�7#^��d�)b��JUZ�eL��۝,?�X�W����{8MD�۝Ps s�H�x�X wL�,���w�i��b:Ca�O�8`�{� ���D܉���Z{h;��Y���� ����ڍ� �YTS�������z;� -<�}&����h��}OMK�@��W̡� ���V��xP��P�v�,f?����w�Z�8��}o�}�ܹ��e[ -Ϥ$ox���-\g��T��$ª�%"��Ai��#��-͓$�3�+_�OOie�h8�Q�{X�@W�J������:�uMXW��@G����l��'(��J�.���l�51t�,.-O7ٯ�q&�(?]D4����t���q^��"�m��K� m�]��ng=83ȹ'�f����5�PQd��;���^�$�@ ~���eЏ��i��I�]\L��sT��Zdc���pH~}T�n�0��+�� Q���W;u7'E�F-jr`0��&*�9r���Z,)J;K���据>��7�,)hK�9Z����H�ڜ ��׈&v +}���B�I��hT��8]��-VZT����_u��p��ZF�����@�]��uq������{�i�.'�� �t����yY>s#���i٧�K��q���J�� -�[��#�Ě�U����M��ޔ��OҒea���~�-���ܷa�Ho�u��좼h���4A� �\L�n�C��i�G�:Ne���s�7�t�+�ܬ����ۘ��Ζ���C�^�*a�o�8�#FUKZ�vI�5=%M��q�����Ћ�~y���Y+�����G��B��.Eػ*�39=��,0�3!�z�� RaT��<+�N!���W���P]�i ��Wu -ݽx)�NFEK7�u���-3�֙�Ka�;����M�,9 ��%�La@rO  -�U�� {V!��P%����%��:�J����_�R�O�0~�_qH��J�+H�ؤ��E��s��m�G[4���8)qr�ﻻ���W��ТP�C惓",ãE��� �t�m#n����X�]� 5�&7/��_����k�2%n���<��a��Y������[�l2�O��ŦqP/~��0-~�>���?���& ��|oH�Κ��i��x���U�"H�a�F�*QvXÐ�犓�����9D�e� Oog�9���A�CZ�Kd/�U��>�|��&� R�ҥ ���X:��&��%8�������N�+�3M+���.��i8Ӫ�_~]s���YI�|��e������w�*y3����ۡ5�������r>*��]�����������(���rU~�i�lG��@�{ҽ�(�í��hg���s<��TMo�@��+�U@�r ��"U[�i��G$���xŲk펁���?�CR����̛yof��}�吠��aדS�ft����� ;F���B"���'��r���N?�A'Ⱥa�S�sz�����q���,� ,$g�>֟�5JRրZ啷��W�p�w���~��>|X S��n��<Ն�|�N�!���ʙ���3q����f�:6/�ZIH S�9�Ik��BR�]�C�^]v8'�)6nL�۪-8�?��{����͂�#8�Ѡw9w�_�5Bj�F�Ȃ�-V��>;�Z�%dD�Es�"���)���Ī�9 -���G<8u{��*yʰQ���X^�H�3@�����,�cb�$Ȁ-~�hW�\�-&�� f� )����h -���P�pxP!��m7���S�X}&ʥ��4�M��Z%G�S��gn���b�a�X�9wHx�r� -�$�w0x�b%Q��T��˄-݃��[�G�/ ��,+}��❍5�e0�_��ō�8���b=�w���������^^��rk1͝%����;�Ҥ��p���}� -s���^�^����M�� -�@��{�-,����_$����9/� Ľco��ݽ ��bf�a��P��Z+ȢJ���}@���a�@ ց�5N�� ��WyC�zY�����m ��g�K�U��5�Z���PR�m����N�Y>�����n �&���&Z7q��C��?� -Y�~����/]�= �@D��[X$����H;-��rM$�{T���"┳��x�@\kYTi���iB�|d؞�u�m� �,�u�C�z����ղ�m��L�*��4/�ں���PJ�ʶqt��ݺN�Y>����)!��+턉q��d=��؟��۳��<����a�U�1O�0�w��:$Cצ� U�0VB�� ���u��"����t�7޽�;��) �z�`UV�N��;!�#-�ư��u����� -�/�-b5JcL�s��h�8���!���L�(�����uW?�J����c�y�&������C�ݹ��-�������\�:��������{Tuy�_���]�= �@D��[X$��������h��2�@�;�6h��' -�Sξ7��Y��p�dQ�vz�. ҄�Ȱ� �@� -;@�$,��X�!V���Iv��a����M��g�M�e��1m[�?�������ѩ�w�Z�f��b��ÿ"�V��?&�iU��� ]�w��D��?^��<�U�= �@D��[X$����H+��R��e4����۠"�w/$�N9;���W��T�Ԛ����E^�4Ig���k:�8|���=�[X��3�"������5�T��[Z�Q����ފb|�ו�kk�ִ̰���Av���]Ҳ%���"IY����z��H��@G}�G}U�= �@D��[X$����Q�R -r��& F$�� Z蔳��l�+O%L�I�����hN�4SV��6�S�#�y6�˷�`-�3�"� �+׮i`�v�� lhUD�6�sz)��m�Ԇ.����e��$�~e����zCZ�d��[$#��0^\!�c�J$i|`�zի7]�= �@D��[X$��������������ޱ���{��8��{;��2PW[AU*��"Mh�� � b��+�d���o�k0Ī��1Ɏ�6t��N+τV�E��1m[�?�������ѩ�W��f��b���?#�F��?&�iY��� ]�v6�@��?�����M���0��<� ���ʯ 6�PH �T.��*Z!ޝ�2���gy��u� -���"�x��#-hR� �R�t�q䜁m��{0�j��1�N�Nb�nB���L�\%Z_�u���PVl��wtkyp]+֢�[;V�ݯ� -���#�}/����B���߿�ۼ����n�0 ��} -OBj�ծccC�4v�n�PH �T�*qh�ݗ�v-4��v�߱����9$(2n�Y2RВ9Zx��h(�E�s���'���c����B�I�A� ���~kQlQ��zx��)����a-��߉"s����ݮߡ �;n�����s#w�:J'��g�Doݥf��_�2)`](AR+X.�VN��Z�+� -)�C���a�v�J� kAM(���A��4���¨6�?J�yRw�uiΪ�9�e=�}�Э�O��ȍyU�p��¨�Gir �NZ��:�6��E�V�~^c�U���:��s{���#o��HKh���c�{v�އ��D�c���'g_!�ֈ�xy��=�H�z n̤9XO����Ak1���s\=xֶH�xӃ= -%�L�)��0�P���;Q)�t!L�����1� <��26Y88y�c� �0�- �s���"�����~��l%��u� O�|&�#�A�|�U�H���2��J7o���H���ѐfv}��(� &҇|�Pѫ�|]���`3��Z�ZN�&��6��a�IQ��w���4���~�.C�����HW�O��|��MK1���s(t{��~P�"^��ǂd��ndw&l��w���b�4L��#7��O��+h�Jp����L�t�X�#r�����M�q�yC�FYS�9�#+d[�5�� ;�L���Z��뗡:�tCp�-�4D�w�S j��iZ�i�}�Ww}%~֦֕]Q4���$s���6����ӥR�>wp��|� ��O����h}]�'�9�`���OK�0���s��ʑpP�ϴ�*Һ��i�N*��v"�;���4��}�aZh��ݤ��*��Fkˮ(��TnYKN���H�����R�����n�����d|�/�w��o�+#x������S�N�0��+����RUqm�P��8@��R�:��ȱ���!�c�)M"�'����8�UQ� ����3��g�\��tIZ��(�T�#�������݂MQ�AY�M��}xL�%Hty�n ->e�,9N/��|Eĭ^�v�!WoT�fw�腽���"��L�6��BpFr+r%�|Δt<�a�� =iM��)�t�.�&]���8,5U&iG[MZ-��-��ƪ�@&I�$�����@���_-��$v�d��CG��m�� n�?�w^.�Ǔ��?�5S=�6��Ľ^M�+���/���$4�j�ǃ��*:8D��:�#t��**�E�:؍���ڣc��k7��m�8wp��c�hp�O6[Î�{X����.������ʂ��k����6��R����{��&�P��~e�1O�0�w���� X[(B�Ą�[%�8��(���EM���qH���t�{����Sn35�cXQ^?��!�H���D�C��� -�K���4�ژB��v�o�7�FEl������cЫ�k�(u<} �kH���W�5Xl��yݦ��l��$�)�t��w�Yn��Ro���g���\ܤq���U�j*������AK1���s(tw��ժX�=(� -K6����$L&�"��&�B� �s����o�wy�[{4�f,��5R˧�WpQ��#� �K�ψ�K����= !kq�R*�����>�}�G$I���$������T˪:Tp�k��qԗC�l{-3r{L�A�Mg "����M^��H1��x�qa���6,�YNW��<8�~CyJ1S�D�)j�N�n%E�D�b�����_u�K��>�om�1��@���SXĀ��w* �8K!��HV��evV���mb�K�+f�=�7���W.hj͘ak���>a6�+� � ±�/D>�B�O[$d-��J��_�þ�$Iq��;�-��5�{8��<$M�ې���ޟv۳�kA��`�v^<���5����8JD#ـ�����FR�0Y�vz�G���=X��5ِ�(�i�<�/-�� -�P ��j��]��ղ!����/΀�5�Os{��%'o�j�GK�����MƜ�>x�կh^m�?�З~��Mk�0 ���:��JٹK�A�N���X��..�l9����9t9��R,� -=/���M�@���CKF -��Ҡ��5S�F�p��Q�;�9��T_Q��͚1gۧ���]�5*�m��X�h��7�8�3���r��Q_��1�儰P�@�5�WR��)AR+�2��_� -'�a& �XP)�r��~��5���MuN �5� ���Wf��'� ��0 Zie.+I���D�$�b�u��;��y_M��X��yD޼����Mk�0 ���:����s�n�����c!8��x$v��2���|��0��’��H��}S6P����В��2:6h!��h(^�m�@x/� ��}A���Ϩ�p�f����n�O֨ȧ=�Z�"����(����C -70)��7F��J�}��ppJ�� -�Lh�p�� g��F��TJ��ta������ܦ���䌚�.>�^�gˍ�H~�# Y7x]K��G�In����$� ��"H��eNuX�e༮&�y����������{���UVA�hN$ ����~���k�.�K�)x ���_�qM�|�;�4.6Z ��^����>�q�bW��$���N��Y���{�<=��g&���&*-�h ��O����OK�@���A�` x�"B����?^%6�f1��݉m���i��^�˒ٗ��~���uQ#�e�*�4+���jҸÕ�w��K2ʅ��sG���b>�'����}|:0+�7J0y�G[�L�8������������Y=����,�����L���~��Dk�R�vUc�rk\A5�������Bピ�D�w*����DL�)�Fp�v �x�JJ�J�F�������X8vWč�}�k��K���e��-��L�j�߃k��f+��?�D�JH���.���F(sk�QT -eʤ�p�"��f�J��irA�P�1/��,Ī(w����&�Q��w2�%����[$��'�N�n���'��|��Mo�0���S ��D=��q��F��Uሄ����8�h�_�vݤR}�șw�g���*�`���1[RҮlS!�=�$�H��a����N01�Q9 3L����q�B#��ܩ�u�$lk-�2*2��*0�[�J�Շ��E�� -�@���-���ba��b��A��cw �{���a�w�������`��>�^`t�ӱ ).�����נ�[Bm̨{��6#&�}c���#�F��Օ�5 �dNxU��!�x�Y�� ���U]o�0}�W�U��@��a+���ҧU��N��T��o�Zb{�C�V��}�0~ҮI! �"!.���s�k�}PsED ���ydC����sr��M�(���g`¥�ǽ�C&b��)K�{�M�8��R&�pŭc��YH�J�{�윛��v�Sx���������q l�[|�棤 XG�N�z �lV�/{E��/��f����;�%ͺ���?�����#).�y\��� ��i�"&S�����t�3�H�w_�rcp�8|�Y��͉��� ��k=����?�&����I��50蔰��a�� C��� ci��p3콨7ީ��m�Mh�c�t�긔�Y�A%x�|ot��'�_�,���jy?���}����_�߫?�_�]�=8G(:�ڹ�D� &ZK=�#P�0�ާ�� �n$b2���d��&��*ID�ەF=rM�� ��U��` -�+��&RZ*Ж�z�.�� A$3a�����[j��5��Y�S� ���㍌�q �U��l3X�E�NZ�.�&�5���n��3���%x-4�L�&{�W���}T� �(]ϯ���Qh!S��x�YT&�����9}��Yf.B� ��Y�`+�ai��]n;���\;3��C[f� �z� �Y�%�P�������e�/e���0 Dw�Ga ���L �J(���:QlP��I��x��nw�Cƞ� -�� -G��;��7k&�"�e�3Q�ZᘪhU���d"1_#e�$H�Hz�n�>�M�y9bL��~���'Vey̗m�����Th�� ?u�]o�0���+za"&�Ů�. ��ā)l�c'�B���e�|LZ�� ���r>�{��v o ۧ!y�ɵ�f �ߍƨH X��w�Df�i���� �%'� ����IB�8��V�x��J��C�T�e-V��5�4�X��[}x���. �็�� /\�wk�D�0�=�G�d��^�ܰ�n��5�:��,����ϡ�vK�ǴX��F��6�t o�D�-㸚�v�(���D��,%^�F��x9U��t�6# E�1��-:ݲ�a�v�L�g��Q=��c�gx{,�:� �\*UΨ EU�;� �����v��l_�r��NW�Ay������ЕE ����|�@gtF��X�o�H����%���4�S�M�M�)�D@�� -M�1X13��E�����0�$XJ03�w��޾�D4L���T" �Lm3*�^�ޜ0��2#!��N(2|�?ޜ� ��0��+¢� -���y������aM|�B&���W�3ɚ( -ݵ�Dyfyu�&!�+*\��,� �Z��wbv,=�N?]�H����ھ����ڤG� / - �rAg�GH3M9[���7 T�U -����K,�Z��-�;c:O�B���JE|���� (�p�n��H8Q~ok�D�SY;͸P �����$,�I�|/Jd���=�����8���:f���\����y��G�}/��VX�9Ao��U>��}�C0�M��������tt9���~ߌn>�/��������_���p<���/���~/�7)������+"0�wv���_�5)>SFIa�E�.��^�L�u4X��̱Lu~(A���X�Z���)���$�H��A �)�8��!��jX`sC��9����6NR -�?�!�l�x�u��&�µ��c�V�_-�H�x�`a�÷'�����������F7P�ꗤh���2�E>������4��\�{��”�j�oyl�"��*M�G�#/���ڀ]��:��He=�sp��$�1Ԋء�E����L�U�/����?�$ګ%p0*�� ����Gd� W�!q���0G�=��ptD��c �q�dk��u\�7"Q�&�OX}�,`��L�����&�,�+���iD��r4r�rG�/��8z�N�d���q� Mh�E@�-_yk -1*� -�W�>HYJ��e�l�A":Lϩ��e�r&��+M�FcKDIR�|�# �N{�1�ɚ�l@R��튃Z����|@�g�� ;�dt{�_=�w��|���}^�ZPL9�}=δ_�2��,��4n��^^��bhD��ky��:������k�g�ߔ׫V}P%)MO@SL� -G��������#(vB�8�������Q���������� -��Aκ����t{4����5ȍ1)@K��1a!�>Y��ZL�9H�����T�8ž�:���&Y � 6r��{6�����8l�[�3*�33SS� 5S4SŊ�bث��!�k6�Q�tZ=��u͚�������mmy�۪<"�WDjF,���V>im|�C4Eç�t4&5�h�޿d2@{Qe���������Hb�[���ӓ�M�����}�� /">Yc/���5ۋ͆�pN `��fo���L�m�����7��������u7��_��?��aQ��֕�����������-;��|.����[���[���dΟ!5��h�!��1�9���5�^i�%�6�w-�]'|%�3��x�i�p>>���e`��tf+���1E~�3$K���f`��f����K�ŸX�8�^��mlS�%�oA�X��a�� h�������� c1t�@u�w���?S�M���5��Ϳ�`�]G+C�� -J�VC�����v��|z:��Xmo�6��_�"i�}��vi��.���. F:�\hR e�ƚ�^R�h��B9���=��<����*�2H ���㴘�D�.�) (Z��Q -їL�Ln�n4l�a�sH ���Q�5�+�kFH)u� -�M� -8.M�#۠�{���_^�_Az��Ԙ.1��x ���hF�?�KB�����H��nQx�� �ó��7:�~�"�b�`zrX��oQ�Ɋ�AR'u���Q?t[-v@�h�b*JBՖ�ցd�t��T�D��XR�ZI�y$8��ҵh>O�4�7i�KJ�=� p(6�F�mH�͘���뻉2"���zi�n1gt-c��s�9=ct��w eUh��ü��5����� ���K��Qj�>�|~3y�ϣ��txq1LK�Ph��nMN�:g��5D+z�9��md��[.+ 2�-9Q�X��"��`!��Y7���))�O�%6���o�����Y�U5��Hnҥ-�B\��u�[�t;��#c��8(W4���Wk��-���U��$�MWy.��*'��� [�4jۮ�iZ��u�8ZW���z��R���l2xQv��Վ�����Q���w��~����Q.�������>�,�ժ�Ѡq�gX�K����!֊߾��R ���u�"�OV'e�F{�M�L�oڑz|��-�[��w��5d�xchš�t*��^���g*s��(`=)�����ϓ���W_>M흪o��k@�1ob�E[l��,�TY�|j�JW֯U��}�[�.�6���*s� O�OW�+��O�pCցZ��O@3]F�I99ѩ�V�f[��2!8�,�ac��L�s�c#��}�#�S��j�����*W��3�ߵ�1��'94E�r� ��d}�@YCy���2�V�Fy�ҏ��O3�ӡ�*���9��pT�}�U�J�F%s��>�vMi1d���X�w.M_&��^D���X�!��`�Q6�G�р�b�At��괍g���X ��q*3�+xh{2�`zpQ!^φc1؝�ku��(��#�E�;*]ԐP��.Y5���^�Bj����^x[7StZ��� �nV�0��!��D�݉BtW v�Т�^lݍ�b#}�;_���7:�l؄�����t>�?m�1 �0����uT�*�������' �$$Wh���ɥ�<8���폱�ԁ��ʒ�C�Lڬ+��9Z�{܀ԪЄ�2F���lYB�O#� �0 |����>�t������.z�����3��@�E�zq�9@��2V�Lc��Ю2_�=�� -�@D���-,bi�F���\'� �-{0���q�y̛�QG�x����%��/�B�m� 񅢑A�g��up��6�$V�E�lK�/>�jt�O�i��=e�'���Q]�f����7��VM��0��WxWEqK�µe� UBhEwOEn2�X�vd;�"������I��+>D.i�7�7�'}��,J�A*�j��M� �&���@�%���@n ��ޫ�40�� ��\X[��!��ş�����$�dj��$I���^�'��_ �]l�%؂��7�`o�-���1��hTnj�;[h��c�a�B��j�^�� 0���a�����DQ��L:�i���� nG���!��]�)Q��K� ���d --�ʢ��XrER&� -�a���f�.�^����.D&��\1�3ց72��>�Ū;�״��c˾ʾ�^(%ZBũ��e6ei�� -��3=��XO��>s�3N�;�{;p��T�b �2����7rq}M�q�� �t����<�R9t�M�^i2Ul�:]Z�B��{�k�wp�x!�-k�\��2|�ϖ`q�3���d�J��8�� �^���m��}S�����ߞ��k�!��Vn�NVB`Q+ųVI��{�J�'8_��X-da�7�?+;�a����&�&ٿ��ݚ4_1 ��6)���_$J�sh�H�t��o�<����N�0E���A�Ԥ� -R)Rװk"�:�µ-��C�����!}$b6�2������� -2��h �Ռ�/�Sh��8d�F���D�8����A0 ��c��q��\N$�H-���Fmi[)�+�@2�������,�% ����� _x]];������*���"�2b�#��� {]��O|_�T���B^�-ɲ�x4�r��a-YVa���%3wcO0O�W�-.��K��r㲟N5���tKQ���>���n��ê9��X� 3mؤ��.zE���J>��ry5R؟����[�(�m�Ծ4�B h9�dH��n�Xק���a��w^�.��>�q&�k�Y�MR�V�n����7Ld��O�ã:�%_rh7����/��/�(PHIM�I,J�(.)�L.�/�,H-V�U0Դ��K�M-.HLNU�H NM-�jpɯ����).Vp��I��K�w���IM.���s�HN-1R+JR�R�bB�\�\e�� -1D�|ŖZj{�� -׈���d���r�ʉ�����)��fV�"B�#fjc -v��@iM�y��P�t�qƮ -�25�UG��)��[�Mb�0a2pT�~��Q�ܯ9 -�ը��)��߫������d `C�Բqo�}�?O�0�w�<� �-��ĀT�J�{�4�R��Q�w�Ȁ�b������C�4���T�d�R�G"�;�Y�LpG��ୣ-Qީ�1+c�^� -��6��|Oϡ��)��h - ��W�4�'�(�B �V]�ղ�=B[��u�1hJA��aw�+��fL�2��XX�����㛽�D��f���_�M���V:���g��̥����w��Ԣ��$%�9nœ��T]o�0}�W�JHN_���h��j��Z�nҤH�q.`-8��@�����4P�n�1��8��c>��R��0Yg�tܭ �0��x��b���~�cD�P�E�0h4(�Z��ƪ\ï��k��7���R�����U�¨�pM� -�T�3��=�̢�dJ´��y$��\Sz)]�i����tse;'� �|A⏟ު��q�) -�t�ћ�3t�z��{� ���E�qM7(���Uk�0�.�,l��Fm6�VN�L�� ��>����c������ ��&[��ˠ��X5�;�F1����S����� �^Fl|��nt�/��/������pz �S "�\r��3��3��q�N��&����Ʋ���� uO�����FAi��^�|{��s� ��� � Q���D�_\�q� ����3S��?>BB~)r%T�S爥ʊI�|�²� PR�Yg*���`�����Y�����"Wi�D��� ���r,��{޾�%�)i��s�/~h�<ˠ���uy���к���(����7�6�����0$�l�4ۻ  B�A��$��t��FU��^�V��:����҂�pk8:��Q7oCs���m�v^�x�2V��o͌��õ��o{j��UMo�6��W �m�-z�.��$�& K*I%k��bamѠ�A���G�F�t[���̼y�8}�u���\_^N��{q���@�� ��Ï���gb�3o��qCO�j�w��Uc>�h��P��ə޸'�\�ܛ�i�~p��~hmGu�о7�v�۽[�pr�v�{�{�}D���Hօ�hk���]� �3�3n��ih��S�`1<�~ p6��v��]���>m��ˁ��[O��Hjmx��� 5�z���>y�Q����D#���i@�s��kްB���n��]�d=����6{0��ѡ�Ʈ�[� A�# �q/���`\[o�� �O -9-17m��>]�5�؛�쫹?���D�� ��H�Ē���p����(��D;�= �"���� G�c��;��}���w���Ս���'��P����e�֥,nD�S�W0rJ�r%�|�iQd)��X��4�Rĕ.p��)D� �6���)%W� -IbYfxH Y�W�<ɪT���,4eb)48UU�8�|�]��Z?���qI���ㅹ�ؾ�{2}�i�������î|b�R6na�������QƸ�G���d���a��S�(�v�8�wl��'����gg0􌋡���ٮ�Hrg�����qw�q2�k�7�UMo�6��W �m�-��]��$�& K�H%1� -�6��hPr�A���!e!F�m�@�Lqf޼y�>�����n���� -"��z��ᢼ��>���=>>������e�>K|�[��ו�uѤ��Gw`U�쓪�qߙrU鮷z}�iA�:���l���Z�Ҿ��ئ �Y�;0���C���F��SZ{e�����5O��E��=>�Եy��J�V��u>�Q��G^�'n��H�4z���%�u�rm��iT�5�.U0���jt8���� +�Z�R7�^� �z�������"�Z+S��^�o�\ :Xhd���u�z��|�I!�%�J�X���F9bo:�5���H|�2��4���5Vb@����Ҙ^� �UH�6h�̦�n��6���t}���u�u����u'������x 9\/���4�p�F -Q�X�l:0˒��H�n*r"Íw�c�;y�F�%��EN9�,6_$ �0ANR�(��QR�,��^�L@��L��Ȃ��F0d��<��+ Y���g�0���LI`Ar��"!�CZ�"�\�1�QB؜��H����>#I�͊���r`H�, :�Âc��H��^W�$���F�-�#ŊH��b,"s�k�~h����k�x#�u�!EEN�x6qP��`��Y{�9��YD�'H2�+8 0� ��# -��f\�gN?��RA�X���(�*�L F�^�,�5�XY�t�N <�(�㱧~;ሓ����8�Ĭ��8)R:Mؔ�u��=0N/}k�;6$ ��p��CCn����&@�{��ξ%Pvl��E4;�?ތ��,�ȭr� -?.���w�o������'w�3�;"�����]���<��ǐ �h�9��*;ŕ���1�v���w�Ժ{�BczjrO�~��7g~q�|k�矲���V]�m���s� ���C�w�F>�$�t�fu�s���r���[��k�vU�D�캋���j5U����j5$\�1��`��Z�g��x��>����e�Ҝ�q�v���e����VMo�6��W �%�=uid�� Ȓ+J��E�h��$��lP��w��!ݶ ���P��7� �#}�}s�9�<;;�3��٨�}'�)��������+���0���A������������h���E[0�J�(� �'S*+e;��-m[+A�`�֔��ܩ�0ϰҦ�<���q����FWj�ʂ<�,���4��:Y���GUᢻ/:���S��I�k(u[)��.���o;^�n�jO��zom�z��lq�ɴ�T�;UJ�GtQj$�a�z� -��u�i.�� f�e��V[d�A�`���嶑m犾��K<����F�}9wp.z d(1��ŒO[4����V���x����i�g���H�D�l+4J����NB_&ī�$�#���W��U��ݰ۷��Ȓ� cu���j�F�v�$�r"g�~���4��! a�@#� �/R>�f0M����8��8K�(���� ���.��x���zf��Sc?�d 3�S|�G<���e�,��cL���O3䑟��x�bn�B������:��f,V�.���ƒ�)�}<�؍b*�O�X� zbV�g6 1�D|�‑5!�[.ةk�� ��}�[3�$�r뗃v�����p"�;�����]��ȃ����q�)ʇb-�Z��%���n��f�e{����e��!����\�8[���DQ����#7`����^ -)Ͳg ��{/��Z׏�����b�o���.��b�y~e��4�5��qwr|�Uuu���8ci��ъ��w:>�����J5�Z�����9� kO��,w4�ˉl�)p,.��L˥Kp|��)�UG�&<���� -��?���h��]��{a��������vϵ$�/Oߛ���B.�d���]-ZJKB��#4Χ��B�z������IkC����I���&:��!�Nu���1r\�_�%~�����NI���ޥ���Nj��^���P��� �H)" ���7�Z�o�6�޿�U<�-&3��a5l�@�a:�C�a@[ 2E[D$R )?��ߑzZ�G���Pҽ~�;ޝ��V�c�w1U���TL�w7���6���ԋ�N�o6��槑�+|���[������ �Z -�x�P�� -p2o#���qn+F�3J�4��+Iz��.�eƉp�ɜU��D4 wƙ�� V�8 � -~��Q��"�+�]\�(+#Ѡ>��|�8��A��r>�iB��SI�lKaK �.g!�%UY�$�(��(���o��ʘ�0dI*�F0�y�8�T#x�!�}i1�� ���誩�$��j��v������Q���ih��@���fՖNLL�80�)o<��I�~%"W� l�V��#��ʁq -�/����J>��"X�p�P�e(�J,C�ֈA$�JC�w�镼��"�dѩ��W1�:�e�QQ��O-Etg�g\Kf�ڛMT�fE$��Ų�wc�JB���|"�����Ϥ7�b�LCB-bA�� ��d�1�m6]�yӣ&͵��J̑3���|q\��\Oڣƞ,K���D���m�x]�n�q��H�nO���_�u��C�׽���Z����?H��3n�_������P�۾Ce�T���5�28!�/e�����@�h��~-���������\2�榪�)M��� `�.ț�+VB�}+��a��C$$*.��#����G�B�f�l[wIv��u��V�g���ۢE(N -�{p�G���p�E\5rD�*��I���*�����.E=��& *O����Ņ`J1OŴ�'A1��]\ %�P�N�ՑS��0bJ i��]�#�E j�R�iH��:�� �.��f{)���=l������C��7�rhy����[� �����3=�� Ķ=5eq�������ls��`(�h����aP����(�kF7@� -L]�J����1��~��}lIw����f�d�֎�\��9d��5U]l�̓6���h�� ?���K7��)��3uN�ڪ&8���Q��w��$۰.{s�H"�쏴�5�z�!�k.:'��~�8��7���ax� `߼��� �;�i�7�١�U������x߄���:�{Z��fu5��1�ل�p؂^K��ql�o���QN����v�w�����J [��܏�c��ܱ�P�8�9E��� %�w_!���Z��=�{auX�L��3n�b�璚\[�+���i.�VĹ^h�E(-�������狉�_ �m�9�ڱ=���o���2�ȸn���)�ɞJ�hְ��7_v�)�PN��d��T�~��:��j��l\��Ӽ\�\���~q���:�a�EPz:�{� �n��c��f�}����ŠD�L�?(��^!����������Q� 1�������3����1���;��8��#�����m��&�3 17�'0$�눣�p`}�(�-�?i��b � ����o��W0{��\�n�8��O��pҋ�v�?�Y��!{�؏b���Cd��x�DE9 z�!� �InH��$)9q����ݍ���p83�͐��6�.2q�,$D�-�e�|�r�b�n�(�.�d> -�H/0����޼�2��/߼y�����+��l�x~!)�& z�W� ��s�A�I���ik�S�x�hQ�hg2b��c?C|o)��MG��| O�����&��֋r2a��I!/fXh2�hr�8��Z��w�� ���9���>�Ȁ��`E�d |�����\�'�68ΰ��67��� �}�m�K�/=&���CE�G�N.��~��dY��3�d��?�a����B_lb �v<3"�T0����$ �ӏ'e.�4Z��v������]#A2闍t��h����2�AJ%%��f��nX+7D�*!�im0�nhKNJ�at,�� -�z\K���aT�{�ޚ 5����u/��T�r�l����C4�aB��ˌR�}�#�Ms���1ÐT�%9����G�Mk�*���6��]��S?����Rn)ҳV9���z��(^�XIo�ru�O�v�W�]�B�� -����(���&��Ê�)�D�Q�=��r����qGx�Y\X@K�/�C�fI=����;N7�p(v��q�V2:�z�!��C�4�K�� ^J/�W�v:��ȑ�FH!ʹ޹b#����M���k�J�ݠ -ͭMF�NH2�ag���_�S�� ��*eu�u�̺�(.�w[N���U������h�Σ���:6�h>o���pNS��e���Q I8~���2���َ�)TwPZ���(��r��mIp`3:%�1���a�Ljl���4)w��#XQI<��X��.}����Av �]��i����ĿV�f|U~�h�����gp�oj�{�$�nWFo~�x��w?�,���%�A�WB�"�T�"�6T�Ϲ�a��xBfXܲM�K ����6=��Ԣ�g�XO�gY���W�S<� -<�Mt9������lhi��i�G^%K@�M�y�4N�e?T�wS� -�!c�'.܄$)��� ��%LB 6Wg��˔�㵇h��o(�;D��L�Դԣb�o4<�j���S@P�Ö|�ċ�&k��>��������XE^r �� �$�P��Ɩz~�u6H3�Xdq��J�����T����%��| ^�$ǖN/Y���0��dK8wG�~�k�H��kP��,��IU�y@8�j��`#�U��}kڈ��E �N�g�F��AZg�M�'����]���n˻Z��|f�c��Hq��nh ‹W$�nԓ�d��&c��z�1�8����̔���r�l���K� R L�h�N1�]fԂ�i�q��tg���L��|c�z|�%N��#��L�M^d�!���䓀��}�G -lus�ſ�ExX� �,q� o�;X��cÖ�)K���Ȏ���#玵��A�@;M(��vlU���%ӰR�?+�F�pĬqw��0����1� ��������rF{�����Ҍ�h�}>N9݂@K)�zQ<�6;wʘ�hhc��}�j�T��Z�1D������~����L6䈭q'W����k�Ҭ���]�|Q߾4���^�N �G�(�t�P��~D���9�Lp�l'�H� t�j�f��+{�Ӹ���3 r��6�0���1����{\�U)5*L�Uz��ͽ�j=�E�>"S�\uN�p`;,������Ԑ ����4��  `�z�@_i�ؕ����O����J;\f�N�5i�-4[�:�<��!B�����Q��!��=�KI� ��G�}*�{���)�����>e����2��ؤ��e�nh�E�oI����ԭjQG��W��O}zj�T����/��nS�;�o���8UzAEwz6>�r�,��r>hAsԅ�c�x����ҋ]�a]��7DT��h�M�]w�Y���=��.����\�y��7Zs�v4��P���m� L; -�q�OC�a�4xߧ3��=hO�mQ�����"7~�������������ڪ���޷��vԙ���Oj>Ҝ��x�[���[�!M �u^�U89O �q���]��X׵uS��` ��e4p�냈��w��.v|򮴡b� J��;��_qBo�q@ �k� i|%���86㡾��h��rӭ�queU�V˝U�oJ�p��MAZ^�S���}q4�^Zo�d� -�pP�Tѝ�8��~{��r����YԓԞ0�o��Y�ޜ߅:��%�GU v��U�ea.��ʤ�o�,�����uý4�'vq���0Դ�t��r�=z����+-���X[o�6~ϯ�ؠI��LP`X\��Ӟ��!0��-Q7�H*����P7�2)��0lBbH�~�j/�F̍� f2�,zd�p%��fv��6�́d�3k�9!OOO���3�7������~��T &�;MW�zf"�V�<�>}\�F1����4���q'�xp�s>(����vc?�k� -T���A��S��{흏S�{`�-�t� rB������ ���y6�+�T)����A�E�t6[�/B�r�]�7�օ�Ȼ�-,=�"���������rwt������׏GΞ�#�?�����m��S��_5s'i/i����l�>�z���~��t�, �9�_hi�1��U��C��?Q�lh��P�ǃ�����s�������zX{������U�h���cċ�9-��Jn�l�*(��Щk6����3�7ݿ ����)?��)�����y�>�j9�˸��U~"b���/�����X�n�0��+XŨl ��4]��A�k�\�C -JYD)J)�i��P�.S^��ș��f#��T�TOd ��r�R1p�^�!ӄ �C��+��|2�x�w^����������7�GN������/�\�ܫ�/_��ݸ��䔋�y�*� �V�E�⨅h������0 ��ψr����mdӔa��V�$�8$���SFU? .怍?#�F����P� ��2�nLe댪6*����+���lFq�A�0=��0�K���j��(p��'�L���`!J��)JR -"���C�4N�P(�����C��L�����C�*C��$U(&*����DY�GΒH�y@�l�|��������2!=����#ǟ�ADf>^��,_Ud��$�A��p8r\�+QjXQ�k�� ��؏�^ɂ�1�?0B�fOx��jx�2a�}��F�ˢ�$���~�O�T�N}��sI�'���!aH ~p]�a��P��_��Fg��g_[dm�'�o4��u ]���UHĜn2k�.1��le�H�&�A �<#�ޏR��S$�.a�sBm �{�D�ܻ�S5�A�r��X�u�����'�A��8��fj”r�?t�*�5w��NR�f��^�q��E�f� �e��u�Ռ�!���� ��Y���zʵ*О�����$*��B�*�V%a=���$iI(@�9��*  :!�̉ha5���۹e�c��$ �o�Nh��$4T=��pg�� CVo)iR/�g�hsujD�����i^�.�΍� ���?��/�Y^<�tSm+���J~�<�\���\�St��%�Eԧ��j���Jp6�������_����=8 -1��Q� ([��Ў��)OY�^>cK��0i��bU�S��[6;Up0�Ҹ�ӬM� �eS(�j�D�U*HKVF휙١xЂ�S2ok@���*�n���@B�㦹�d��1x�֥�@epd%���nђp@�b�V�1Xѧ� �%�� ��&Uv�S�uW���–܆3��Vh����z��� -�4�υ��1=�������r�V/T���N"�7�F�&6x���./�����ӧgZß.�u<�cΰ �nX �\H�WDo��:��{s�`��h�T2���G.ҽ��bb[����.�k����� �[c�n4����[(+��+I�.Ø�c=�U�cb���Nc ���~�8 /4���.���}�9�<ڦO -��������.k{G�Ԣ�v� j����k�����-�~!��j�ç�V�6P�0�~�����V%j1X�Rg�žK���:uH� -���b ��(��a���}U���~��"��{�l�����_n��6��� 1{��?�xO��Xmo�6 ��_�s �n��w� - v��}h7�E�c��dHr^������ql7I�a��ɇERt�+#FƮ���hÕ�����ʄ4#����(����p�q��<��������=h*M�t\����T^�6J�<^U��d��y�VC 62��h%]jI!�Er�S��T K!�N���c�����9D.2Be0n֓}���u�m�. 5�Rd���9)�\BԾ�� ]:� i�\C�W�[�8�i0Q�j0���#C�� � �� �^xc��<˕�$E%tO�DL3ĵ�D��*�/ș�T��;���*l^X��MU< 0H�2F�I��L�\�'�o?��$V̮s��X�&�����9e�йNmcA5�3D� &�˪��+C��?��l(��\P $���(L�p��~A�G k �6�B���37d+�%�i�SM���� ���W���.)�.ƕ���!0S���QI ^!� �q�2ʥ�X�-tz-�4ƀ�"�������W4��}�K���}.�Vq᥃��d�6�d�( TI3���S��8:R�I����ϥO�f�^G��3��cy�i�;ף;���n`>���% -S�����H���~�2އ'��U� ��AG��n{�۵�^;*�7�����>L�7W��3��ԭ��{��\��/l���������|���vy��� -}D��M9�њ��`�������L�ߘ�]xX�,_38=����9�En�����v��n4�r7��,�{�f�T"Y�#��R�ܿ3�4���o���;ä=��V����<����P*'�#��H���L2[3�Qi_�<���'L-�RX�-����3���fJ���v�{�����0l�:�7�1A ��3�j����۴+9��c�Ο�f����Wç����9����] �qD��G�6K[D�MY������\-�@�h��������k}�w�Oֽ\�y�D�� ����dq���ò�T:H76��Q!��P������5��^H���w�ϵ�ބ�Yh(O��7��]l��JÚ���{u},��9bh�8���N�}�����q���w��~-��G ��%�R�8��$Q��%"�2��%�G���(� �s�q!xm����=���F�A��K� ��4�$Z��~v^<�]�~{�M��f������.�'��f����۸������� �vs��qlc��p�Ͷ(� -Y�-ueR���E�>�=a��CJ��HJv��� $�-��p>8�|����c�q�"J����A�mL� �,���d溻�n��~JӍ{���[��v�R��5M�� �������oc�, � -~�&a���`�Է@�M`���cF�L�2?�[O@g$���>�x� -� - -�q)l/��$Y77A���nL�>r�`�l"�]�!�c"�a�c�ńO���=� -�t~�x�b���� !J�k�dD���|!q̅Fۄ���Q�F�&��=�K�B�\��Z�襑��1"�/aJ1�͹��o>�/����-�! � ��(#�Dp0j�T䇤��c���Zu��Fir�*�K!� -����K�]VB�E�O2�� <�AHG�3a�� "���׻?M�ࠀ�B� ;0�� �[�Y�7��{y�E�%��1�z�A��R��\��=A�� �{q��oŠF2^s*F �QkJEz�V�� -v.���Yq�|Y�7���o��&mSѩyk�RZ6^�����E��:h�.��s��3�.WßE���>]��������c�ws׃��t�J� ����6�KX>���͢�����^�Svz5��u��� ( Rd2N9���oV��0�� � P,�,�'��M����K Us�>K���\���U��־M�ʥw��\����xdQ� -w�/�����Mf��U�yyF��\���/�����:��&{~��9�!�"��b�l�� ��߿��s�n�M[�� �;�1���Ǹ�rHc"����0����%o!H����a�@�1*�����[�,��2k�QT �f��ݧ��y�ؽM�P�c��\<��*9���Dr�Aک��3I����a�y�ۺ���K��^ުVn����$ .0��jgl�R7l��Fh�?��B�i���\x�0,Pqd?�U��]�y� '�|9�i������V��e �����v*�����s 1)�f�i���_@}d�A�j�R�y��E���=?]�1!���4Q�2"���+M��V����/a����,b0tI"�V-7��y���:? L�C%d���,K�0��y5F߁ƭ�^���O"�UZ9O�Whs� �j�aepit@��Ƞ�t#p����|���<���yi�JczZ�LjE�(��A� ��m;X9$�:�7�i�F��C3K�W����gg//n~�Ny��M5��ⶼ\�������>\ji[����Ő��˘LNO"}�C�9�iC�h6{7Q�ڗUXܳ]�|�z�jp�>��K�!�苈a�� ԓ�3���Y�I�Cꅊ�b}�^���VU���c�AϷ>��Ф�_� O3 N���&���c7��v@���V��б��G«���.v�%�2��|C8o�dM��Ş@&�����Z �u��c���{5���.[�XUQyǧ&�o�H��v��# e�<�=�{]�q�)B�Po4�ԙ���XZ�\����� ؀V�js��s,������<�~�s@_�Yy�4�)s��8)�8�{\4p`P���T"� -DSyE���w��/���h���{�TF��M@~���b�%������N=��WmX�Q��9B�V>;/���ӌ��#1R����8KV�m �f'm�3̶�`��L�O]�=����l�\�l1;�F�j��~,�0�;n����/��>�u_��I��d� -�]*hIe����>�ޖtBF�тv��z�����,Fף<���C����D���o:�7��ғK��L����ﱟq��.݁���� a���^(ew����� -9��p��� �i#��H����I�ȵ��>�I� )��'$ۮpzU����)�B7�ׯG�Ӌ������N��)��w��ݰ�J��#8q1zo���=����Z�L�*b��{4g�GJ�D�vؓ���"+���H�蓘�<��VӨ�Lb�i�ϵ�/�?T�S._��W]��6}ϯjhv��f�/�41�B�P��-�a�h�XT��u���r'N&��i��b���{��H�>z��x2�Kd{h�vv��w�+c��B6�D�WB����kv����A���O�c#�ߺ��3v� ��c��I�Vuq�tgY�u�K (|�&P�l�Z�$�'�m���Jtk5. ʑ7r"8� '�@�a����u�1��G��R@�t��$�N[ㅀ#� 퐁� -,fu[}*i�4�P�- k���:����߲�e� � x>��І��]��$����ҙ~I�8�%еX��*��N�8Ӷ�� ?�� �r��� ����7�Nᩆ̟�à.���g��T�ׂ.���2��P �D M�������D,�Gg��}�)j ֥�w1�t� Q�q-�͛��ͶS�V��A�������x���6Ş�umN}Sy�i���4�+{.�D5z*����.@9sM ��W`����%㑌&��I���~����xm����T��u\�/I\����rxлk,�O�b/M ����,rr���#e۪�F��2&�+�Z4���Zx�Xf�lduF�� �yb��'�\�EO���N tܖ��O���Q݂���a ~�1�����k�0�ib�@���$����!{����yu�Ey�|7'��#XU:�o�F?K��v.1�#�r�߲���˗�eO�G���i}^>��/��_��SMo�0 �ϯ�r'i��s��޶�z�$"�K����_�Ѳ�Z�H a���g'͜]�qq�d����⁳ٻ�k*i���j)�׫��q����x�/~˧Q�����{���y@�v@u2���HC2qP�Q���`�4��� �6h�xU�S�( Hi6�Y&�CP6 -���j�Ka�-���=�\���I�>������E���q�����U��3�J����M������!K��cib� ���r4�Z��l P˅��S �l��L�� ӄ�Ѵ��qf���/�9�����o��ӯ�'g&j\Ty����L��^�*Z -�hPYr -�y�zh��v�9m�ȘSE�]bCk�\�Q偶ʬ"����,�w��% ��C�&i֕��yo� ��o����WMo�6��W�ڠN���l/�W���-zH ���F�TIʱ7��P�d�&备�l@$g�͛/*��b�Ͷ������f: Ȧ,���E�S�)}~~�>�<�jEonoo���/�/ńΤ*� r�4 -�›ܔ�Gh^��^W�U^�r3`�VɈT��j�tN{��^T'9��Jׂ�i -4�� [��xX � -@�L��Y��_����'�[�\����ς+.����'>CͦO���-�6�B(�a�JA�7�)d9tI$E�B�@���82J�����&HA�I��X��E�-���J*CrGb$� ����{�a���VL��V�"X2 ��S��L����T�!\�hs�g )+�"؂H &��"@����%��B���#�ʪ`H�L�/�=s� 5)�X-�\|@i�K��#��1��w�!!���J�Ȭ5�fE !�u.R�y� �w��‹h��Ty��Q�=f߇b��g0%h�����KK�+�����+!� ��R�79U�x�ju��80b3:��k���"�MT 7 ��n��g��Li0��d�/'Z#�R~�����aݒ����� &�� 7�k�1mJ��� -�#KgU/�KT].�9v"������W^��[m�^��=�s�;�� bu5��Q�#��>�f�ɵ�N~�И�_�6���C��<�ex N��ԃ=�д �gQ�)�t���σ���-ѷ� x�4�—m��8�u0����H�LJ�Q0��5e�k�t�����C���C��ىO�i���Ҥ`��Rn�[r]��.��\��v4;R6ӗ�~���x�Ysx�(�@!��΢;njT���6��D� �����a�����Go����-��udo�G�k���N{O%Y��B`I>���3�qvl�Dx�o�톄��A�싧�}�x{ -�: -œ�^��y=�ݗ'��Vu���N(� �B>�����=�i/�3���X�Z��K�k!4* �&?��g2 b�[i��g#����9���'P/�������cq�;�. �f�z��~-�n���ⵆ��x��\��[��,��1�ތc�ە�����D�&�<ଯ#Pgi�ؾ��r����d�b9wv�1ό=4���s0���6�{�HSjbUUl�1��M�y�=��+j8D̾D�c��Y��� ������x*ۻ�v������7u�ġ��=�R��m?›\�o�~��ʁ*[r]�ۏhD�]��Ҧ-�}��@h��c��?��xE.�Vu�f���4��Ҍ4 -#�(�,9�Ar���9�L�Y’3K\0a� &��`2�ք10g��O�7�KUrF�w���y�D+�#�u��`h��o]���ު]���}���/B\Rw���⢞�� Y����v_��+���9x�.06FT�� �'�����h�����ex�kv�--�_�Y�C��!���E����=�=��wO_F�~����]��#?�ϱl�]�8 ��Qx�R<��o�}�\Y��,�l������?��k<�(�!�䀃���]�����?Y@�씄U�f���ʌ֟H���jr�o��FDz�#͉͑���|ID�o���v�{�R�oN�_����3 -�=����H���LXC"��J2]�;DY���d���$DQJJ��j��h���Bb9t�v�&��56�|7fb�P��пqƶ(2I �s�b��{&Y���Ze-�d ��=����m>"N��-���\����ڄk. ���S�BEE�YĦP�A7��BRI�sed�n$UJ9 ��*ZL�W��a�3�!���M���12���a� 8������x�:RD�IޙA_#���$������������j=I�BYJ��ƃ�0�㈛��ĴI�>� �/w5������{�U�k`;h��t��r��C���@}�au>�_�����[������#���׆) tq*0� -���\'���[\09X;1������ `z6��O���qMG������ؙ�T i��"1ڦ�Ci#A�f����C֤�_�-��kl+�'$��Ǿ���籏���/���ߤA�#>N��"#`��h:~���3;E��~�5���Ƙ͊�ۮ@E��m�7�=u`��r�����w���6nO��҉gak'b,�����n��;l�x(nR�����oI��zRџ0�͏�GRGK���c�� %�lBx�D���>��$!����#�H��o!2I��q�h�vZ5��� i^�ucH|�}���)�����Y}�F��{�tt�z�e/�:���/��2��{���8��J�A�=��3�M]Ĥ����P�N�iø�>y!xX���k]8�;�9�/F��( -��/�o�; �N='�#�v�3�I��� Y�L�d[��R�� -�S�<�G�<����ѳE?�|���6+���˩����R��n�_!���A�u(7"�z�v�طʋc�����,�^�����ƾR�Q�ud6� �f#�� Ǜ����LZ� e��L2�S��]b�w�M���t����Y��*�"�u��r���$)w�6�$L��s�x&�t�{,?�K��mH����g��>�{ѷ����PyY�ۦ<�����q\��Ʈ�vV�G��~*賑&+������W\���GUƶ��<�b2Q��߇�~(��:8��� {t�� ���ܶ�����q��'� �U�� �m��H�%� ��� �Ð������k�C�*�����HC��j���7QG���{�8ףq�o ��c������{q݈�S��� �%�6� ��v �G�z8���:}l+�z���x�7�Ný�q= ��p��3׍�\v�W; �G#P}��^\?ޕ�π�%�߿�p������t����)���4�W�����_xam m���W�+�$�_�$��N� ߑN�)9 -���.�/H< -�Ǟ���Á�$V�HF��mi~��%����7�n����� �]�r�(��q��j 1�%c?�^���7��=�^�.�0i7�=�m&1Z! �f�tf3O}�QwA�{��()R2��RY��w�ql 燥���O���"�/�����߀|��tIY����q��e�X���D�W��yh��H:/�!�(�"���H���~�a%��b������F�7����A����]��������s�[H@�[I�?�p�����ʑ���C��׈~�����/�0|��[\�>�ų_���O@S�_H?[)�s�L�:ޏ2��KJ���?����¿�������N������\ ���k_F�^F�^����sQ��h��Q�X �� ����XL6� -�0�*:.�� YE}_�>�DA�X�� ��l��{ �@`�jy� (��+��7������+f�a��j��*pmj�s�٩ӯ1��PLУ�/Y��5�g�qm�_"]3||��K�Tڍ� ����M�s܂tn!ՠ��u�M@~7�w��8*���;p�����)S]�,a3)K�����fy�j�j� i�A�I��>g�b�$��@��ίעΞA ?�o�F���e��x_���|�e - i��Q�e�[^������MĦ v,)��s`Wf������2}�o�>+���+��=�a���=,��G� -���~�4`�M�VRp�]ءא�YH������1�f>�{�#��e#lKn⎖e�*˓���_4���t?�',A_��� �ō��$Wo3��>�G_R����0]�N�Z��vE:fYv�_~;@c����1.G7l�����[,������� ��.�/���V7������墭�}��蟞��~:�1��M��[I;Ƣn�Q��S��XS 4O�c�Fs6�������+���؟x��@����J $�~�-��oq������yo1|��p�z�7�� ��h��r\WӁ���=��]���8�>` p'�0�ȯNz*��A�w�Op'�mK�"��4��� ��;�N�g- !O���s���\���#��F ��m�s���Ѓ �Kj9��=�C��k!�&[I&=��B�_�w��C���䢾C��������$�y�G����z� ��u����Z���d�+��$��?���)���{vP�vK$�c��P�W��x�4҉�֩�w1�u�i��:�f������o7��[�%�o������m#�ӂ@�V��k~ͫy����ƾ� ���� ��q�̱3�N;m�dn�������lj��n��[?��Ύ������O_�t�OW�����8#��3��-�n����G0������>�{K��-�[��$�N�e �����E����ϟ��1�=��}�٧���v3��g�|���N�숏�<�q��=w|��1����G�t��}x����\��G��/(�> +����٦��.0��y�Bs��n�� L��.3�f:�t��i��K�~s���\i�1��u�s������k�m�c��62ϼ��ȼX�K�GY��,R+K���J�Te)z�c�#������@� �`��P�T���>Y9Q9I9E����C���� �L�Kg�>�����b}�~�~�~�nQ�P��_�ߨߪ�iZ�fѲ�l-W�iy�Z���V�>��OhEꓚ]}Js��jnu��Q_�Jԗ�R��L��&�\}U�P_�*��T���XH}G ��j��{Z��Y�W����F�C�I�Hk�'�b:�t��xө��M�L��#sh���d- {�Jr�%��r8m"�c�}ơ�`|.&���:��O� 0V��.'�+���ߎ��zy�O�'`�w"y��N��x~#=��7����cz�Bo'_ӻ�7��-�W�B٪|�|�|�|�|��[9^�Y���9ʹ�y�ze����_�7�w��w��ûL����U�E�&(�s��jx~�*�)׫f5QM�o������TnPnTnQnUnSnW�P�����E�]u(w�W��r?�� ʣ��Cj�Z�6�����'�'�A>�lT�S�W^P�T�R~V~Q"ʘ��&��j<�b5�}�v� ^e�� ϲ_yIyYy��&�U�5�uu�:]Rg���9�\uXySy ��;ʻ�{�f�}u��@]�.Q������ -�C�#�c��S�3�se���ڡ������/����S^T�W�S�{�:CTg�&u���1j�Jy��a��|��Պ�J�r'�㛔�1F9\=�p%����(�\�V���0F9�G�Ǩ� �y�"�E]���}�S��������������ݨ������$�d�T�Z�`.0�ozL� #���G� ?���'��M5�3L��n5=dz��Ak�0�h�dz����i�3�gMMϡ�~����e�+�Q�צ7Lo���~2��b��|��*["���i8 =���[@�x�x4�C�W@{�bӍ,!��4�[���p-���\��\�_��M�0\+�u�V h��Юĕ��M�݁+������2i0�D��ab�i���=\;4���rh�ĵ XBX�7A/Ρ]��2�El/���� -Ӈ���X4�*3�4�q�,�����q=F@���;�]��q��q�Z� �\#��4�0݆�u��p�^�t�&x��F��w��ƞ�Y����-��o0���&�1��vӧ��!�����4���N� ׻L,��L�zp/~3��O����?L��z�����v>��L� -���=$`b�X@;�G���1yT@c�~ `^{=�ӗg$�����\����K�xA���E��{>�KT�?�& ��l��&���-��m &W����<6K�tߗ�8�| ���|(@Yy?>~>�`3[h�|�ٸ���P&ϯ� W6.�h��]��(k7��L�?P�V~~�o���ޒ|� \� �����z��(+{D�>$���r��'��zj��"���Η@�G0����1��lW� �X�XYf�Y�.�8I������r �3+X�@WJ�*e����8�{�X�Aw�`����W��uRFl��yY~V�{��#V�{ ��#V_��~��+����'����ĥ���J��av������X�"��z�|�V8X��Q���#0>'�p���(��d�-��c$��t��c����Q���� ��p=_�B/ �߫����-ǝ�ŨO������/ 0���10�+��1*���&�{�W%X�&@���(�+�7��)?��%�|��|�����{�{�W`��!�'���O%X�#v���� f�`��Z���6���' fS ��%X�C����]"C��d �| -��0�J)��oJ������e�E;U� -P�56��d�]�Ca��D�UX��_��!���%�䫠l�~B� -������r���}T�2RP� -k����� -�?�=l�r�(�r��F�L��2^!��[�R�ի�7 -N��*L�(w �`��{L�[�W��d��� ~��A��6�<(`�O��F+��>��d9��n��L�[ar����W�}6��(o �Y�#`fu������{f� �f3|�}3��LL. ��l>01}�D�t�� -��˕ f�*0��P3� U0ÇP53�5A� _A5 ���f3�~5Q����& ��ǫ�f袚"`F_�� -���lVZ��;�[6��f ����� -�Ɍ~�m�V����۪_��{��l�\ee��c�~�f�a*ڡ -�����Z3�'������I�0��Q��h�.f�6Q�=W��$���I�wO�@�QO�@�QOP�盛K�?S�.�u�M�g �6��+��U�P���.�Xem�� -{��"ڼz����{�k�*� -{���P�P__�L���`mZ}x�aي k��  -�}�@��WN��e��U� �BA��!�l����� &��$���K�'P?�؍/��,ʡA5�[��Z�XT�n5�n-W�U���9i�b�OE_�U� �F�Ѡ��Lv\cs!�~`�4ԇ�L���y��"]m����ûJ�]k ����%��v��/���(��,1Yk�(cu&{� � -�b�b��80��.P�s.n1�����ؼ �G�e0��csI� ڵ����$Ю�[T���mlN�����;%���]�%`O�{�ګv�D��7aP��kH�9���z�����b�}\�����zEcmT�閶I�J{]@�-�ސ�-��`���%���-��Fk�H@o�O��j�J0=�"��vk_H`l�mP?��K 6��� -��}/]�~`z��G�i�0��Lh�v�?�w�`��C&�G�ll�}�� v�>,`�8˾@�d��ۡ�&��Rt�~�� }��j��a�s�I� �Y����i;��ۑ.@��#C�������&�ƨ�As��8�Q,@��8J��� ��ll� ��GX����v�Q'��l�z�~�� ��`�FY9��x��,@`�-ll�h h�66st -�yG��9�ڿ�G�������'��`�~�6�`�1ǠA�wL`c3ǰ��t����c��+t,`� -�6W�X*@�gg��yC�r6�p�`󈎕:�X%@�n�PV��cW��b6���W����O��r�/@Y]� @�>' -���Im�q�E�8E���9N��q�s���<6�q�/���P�,Džl>�q�;K�q���p\"��c���\��j6��F���9�`���؜��z6��A���9n Lwn`sy��ӣ[ؼ��V�t�66��]�0��C���9�.@���)���w �ww �y@�=���lN�q�a��6?�_������+tl LG`sd��ӗ��|�����O6w������xT�ͣ9 ��� �95�~��I6��xJ��t<-�����=q}V�ͻ96 -��� ��'����� l��[�p�$��5/ �� �+l��1*��*��|��U�n�xM��}8^`k�7�<��M���xK�͉8�`k�w����]�V�xO��w86 �u ��l���[�p|(��A �� ��lN��[�p|*��G� ��~���T ����B��k8� -�5Ǘ�����z��k�d��[p|+@�̿`k��(������d��[?p�G����M��8~�LV? �5�/���W�x���������QN'삓�_��m҉����:�ޜl^�,\3����D��d�N���8��Ʀ�eN��N66��r��u��U0Nw�wt�q�mÉv��OqB�A1&wVl�>��c�r,��s�T C'�F'�s®9a�T��N�'w�7v�^8a�� [��?��F8�[8����ɉ���|u�w�b���T@A�w.`�x�J6�w��q�1xu¦8�8���ىv���;Ѯ������}��q��p·WPON��N����.����d��ԉ1���{0~u�Ws‡R�s8�G9٘ �O'ڿ��! '�<'��;�{9��p��q�NT�� ���A�V��o'� ��}�z�2y�m;�X:�D�tB� -��y���:p�+���z�����~��|@@�mw�;�\ xr2Y�����G؜��*d���� -�|T@e�|L����� ���� ~��I���S�L\�`s�gT�.N�W�sb^� ��|A�9_P`�/ �q��e���+*�7�Q'�'��p�v:a3�\�`sηT��N�E'�끬��;N6��:p�/�� ����*�� ;�Mc�N�3'��+pnP�w8�PYۂ sB&?�-��b������r�F8٘:��^@e:����6����:�m*|>��*|V�/*k7�F�Ẉ9~�oV'��*�vm�P�f\f3tڕ%�ƽ.���jg�j`c`W����]r��5ڊ�@@����8����h7.����{�r�q��#���.�����f�'W�� �t�^��.��\uf� v���d���*��~]ml<�_�B{Ҡ{�^ڃk@@C[t -���k��v�8_��E��0�7uAG]����B�p�����&� � ڟ �|b�U��B�l�kL��\� ��ù�|Q��u�� 6�u�� 6�u�����wt�Şc��l���� &�{���v��)`����L�Sw��9�ml��v ���#�!=�O��wW ����9&�s7�7ۢ �a�����n� n�GLh��3Lh�n�:bB;�(&�=�I�����0��y,&�=O�� ��c0���|l��@����q���=�&�mϠ� ��g�� ����_�_� ���6h����Lh�%��6���Z�]���VK�%L�h�%��6w��Y� m��9�������K^0�W|+��{ �!#����X�����_�9�*�[U����B�b�k��P����g����ߑ��h촢Ș��B� ؛���ZJ�E?%2z���ܡuj]Z�֣�j}Z�6� ��@��F���I"8H&)$���t�A2��d�l�C�$��H�'��7M���8ಹ��x����#~RJ�H��� -RI�H�T�"aRKꢱք�(�d)��\M^!�%�����ͦvZ�#�[i �|^JW�]�t ݍ�O��b��kM׳hm�z��z��A�_{Мf΅��$w�>3}`�lz�t��s��צo��M_�V�V) h:=H;V;���i��v�'�/�3�3�#��z�D�Ƣ�yL��;A;���Y�w�/M��n�N�Ng�s�u���)ک���di +��}$��fq=,��:`N7[�6)\d.0��0*36��9������ةH��Aԃ�rE�B+\��={��RB��_j���.��x�w�k�kݴ�ֹ�]{��]Z ��ƚu�����Ȣ=��E�:�?�,^܌t4����Y�)�%S؋���㡄���TߜEs�uBo�]��� �nW�]��Yt�c���ŋ�I��c�̓<���)��"��Ewu�E�['���:aݺ�u(��{yl���NI�$X�jI�z�~�����n�|.�E�I3F��S��J ���=TQ��=*����>��!��h��y��l��2�2�ZQ�~�{��wQD;�D{1�������Q�v�Q;�uԎ:�@u@>��"d� ���g7�����|v��n�� >{�g���=�|�������Ⱥ��E�{Q�^����!���|J� @ƃЍA�;��n B?�����A�� �s�=�MG~ӑ�t�3�MG~ӑ�t�7mt�VIf �������Э(�L<;��ij3��2�g����³Cxvz8��L�P�Y��,�l�2 2��͆�f����}6x� �砜s���12� ����y.x������F� �n�Q7è�a�;�r��6�h#�����A�#�{y� �y�{򞇼�!�yxޟ�̓��An� ����|��|��|��|��|��|��|�9i�G� ��"��/����b<��/��K �%���m ʶ�[�-E�.E���n�B_�B_��~�B�K�/K�/��3� ���r�c9��X�wW��xw�]�wW��xw�[��Vཕxo%�[��V⽕�g%�Y ~V��]Pw���vA�킺�u� �n��.����s5�Z��V#���k5�Z��V#���k5�Z��vE^�"�]�׮�k7���ݡ'�#����HcO���{A�{#�}��~�g?�?�a���ˁx� �{t���B�p!t�"��Ő��x���%��R�q��+x�+h�l.ǻW��+�ە��J����+���(��й+�ޕ��UH�*��*��*��7��l���N��Fy�OW#�����H�j�u5Һ�^���FZW#�k��5H��q �������]���C���~_~��s�����ux�:/#�����(��[��� -�� -�y����^A�^O� �W��+(�(�_��w���my��|����"�M��߄�7!�M��߄�7!�M��V�}�U��*�����_E��"�W�����U��*� ����_C��!�א�k��5��� ����_C��#�ב����u��:�����_G��#�׷��U�������@�o �7���� ��������D�o�7���H�M<�&�}i���o��[�����B~o!��>�[b�����m��6�o����>�~�������l��w��;��.����{h���}��>����S�C�Ö�[���v�ر����f~��>�}��?����h�BN"���H�C��!���(߇�A�!����GH�#�>�]�6���O`{>�\>��!����ϑ��H�s��x݊�����||�翂�� �e�|~�w��;ߢ^����E} �} ���ܾ��C�߁������߿���q���G����xچ4��� y��t�,B}� ��3��ϐ��H��� x���/d�_����o��w��;�����w<�x�{���A^c� �/��B�z�Z'� ��6�#Լ%4q�Iy@)����&4Ϧ<����Mh:�K��x -x��� �*�^igv�m83�o���, �BW+��� 8�P[1Z^-�<��������^p�� ->#��� ��@s��6B�xו l � ^=��`B��<_\���V��[n�A}��C9}x׏<��ӏ�1����7��1,-G�*�ɭ1x6���g�m0��� C�a�Y ^j?"������h=����Gӥ�o�� �&�o��f�oF�`�F1f����݊���ށk���Hh�w��nȤ2�E���_?�rm|B�� d:��?�7t�s�eҟ�2�@�̸�Й�W�Q�~�Bz�P����"t6� Y�F9�0��s�P�9( �t.�� >��a\������t����z����,B�A֋��t1h���\�V�ץ��R� u� |/C�.����ΖA����2�� �,G>��|~�u��^��C�+�s+���� -��z��Dz+��J�b%x\�4wA�wA=�2�zZ��V#�Րﮐ�n�k�����l �\�2���wdz�����{w�iw�|���k��Z�7��e�������=*��@n���gD9BZ�<��F}�A=| -^�3�!��!���V�@9���#��x�(�> -|�z=m�XЏ�s�"��P7�����}�r����D�q"x>uz"�?�N��S@?�Sq=�]q�T�y*�< y����"�s�+�E�\�.�?����������yx�<��?Ul{Z�Y�[�X�����CV�!��(�z�y=�� �<�v>����}��� �7w� �.B;��p��"��E��E��Ex�"�s޹�\�w.F�C/�>^��q1l�%���q ��|\>.��~)藂~)藂~)���� ��P'��N.C�\�:� ur�s�s�� ��+dp9�}9�}9��r��r�u9��|]��_W��+���� -�u���� -��+�N�@Y�@Y�DY�DY�DY�\&�]�g�y^ �^��1ƣ���5H���Z�y-d~-��Z�z-ҹyb�G�C=]��C�0���#����H����y���� x�F��&��&�c7���e�|ނ���V�{+�߆�n��������sd�w<�w�͝H�N�'ҿr�e��s'dy'���� e�i܍r܃��A>�"�{!����?��?��~�|?�u?�?��o�����m@�<�gD�����è߇Q��L���@�����3�B9���C�y ��8�=�t�Ľ'�ϓ(�Sx�i�����_��� ��������1��b ƶ�g�dz�����9��9�����y��x�x y���(�+� �3�(�E>�P�M�{��x�nB��o��o��� �U������*d��} ��:�{|�|�^�}��O_Gݾހ���߀�קo��7P�7 �7��(3�}��7r[�>?}u�&t�M��M��&�ߛ��M��-��d��?O߁>�g��"�wQ��A����f�g3l����}��>��}�{ϼ����(�� �v -��~�}�2�e�e��|��?�L>DY>�\>B�|�2��C6��'(�'�� �� ��)��S<��� �|��Ho ��YmA�_ �/ ���}+����K��+��d�5�� ����-��r��|�w���C���ߐ���p�G��H�?�nOې7�p��� �� �~�;�@����_�߯��W���H�7����<��{�o��t;�n;��޷C��+% -� -M^$��Q�}��Oń�H�I�$\�;���G��b���%��J��G��yQ,���D�:�= �f��(9H;�䀞3 -l#�iY�up -�w�{+V<��4ˀ�끍D��/�V���y��D���S>��dz������N�B�^���\x.�{v��\q�l��t�����(.���n������<���(^��X�^1�V K ��]rpQ|�~�⇼�x߿�(������K�] -K? �2�_�wʐfY/�@�e�~�R�����!J�(k� -���9�@����R��+�f%�U�^�<+!�J�U����n�A��@ �[,OVCV�H�r��LjP����B�I��aȨe����[-ޯC^C(uwȧ~� x��4 ����!�F��x �zk�3M(C�� �l�3��e�Ci�,��\ �k�޴  5�V�kEڭ��V�պϭ�W+�ۆ��P�6�kGyۑN;�hG���K���;!�N����»ݨ�n������{� J���{}([?�������{�i�}:�������fBGg�ކ �Y(�,�1 �3��F]�F>�(s��\<3\���o��v��R���\�H�"Vr ��зF/X� ��ҾX#�о�A�~l�C$RKV�C$E��k��4~7S��������_�5a��R�R��!�A�6��O-��tsd=��j��z�����#�?�.:N�p�>ͧ�]������ek$D����)h��z��B�EK�˜N1���V -IHg�tFx�����bdY�Y1̒V]�m���ߛN��p���5��|ڙ���2\�~�����w��wۭaߦ�}#K���ظ�񦲳_�b�M�H Y�2~����Lq�I��X�׃��&䌚�/�_v�b� ��5�d�:�5']��P}]���Z�?�<iq�[�w/k8n�yӇ��4Ų�)�|m�5�3��Y�Ճ�y���PS��m�mM��ԎmS -�+��4Eː�XN�� ��Ʉ岐s��R�jەp(�f��^O:Xv���[:���{ �W�ګ�u����;�Yo���V����S�?g�q��5��"OI���]�}~ome���s-� I��&�r�4�t�r��u�]�U���>���o����+g=��wݱ��O`y��g -ډE^���Dk8�f����$9q[�ְՋ�У"�����FY����wlk��u��=���r��~� 9}�y7V�>Ի?V�?���8�|�x�I�L>,ꈬ���FH���9!���U)Af@H��,�r�i؜�U��%#*��x���O�=@��ً�?uT��� -�vv�N�f5چ�ͨ'$���,߱H�N��T��чeI�SI���Н��O}���}Y�,+�9�Am�vڐ�4����kV��� �lY��q��T-՚z̚Y��V{L�1��jF����y�?]�ٳb���"��y�G�h�T��c��K��~R!U�0?D�d�qo WQ�6:� ���|7�i�����]KG�畧���Fؖ{n��>d�B�픦�p���c\��z�t�j��UJ�"Й�k���M_�޾vFYٌ��ᢢ�@���V/>~��cV;k�񋫧�vmo[�[R�����O�)�c:���|��2���d�}�-��MW�i>�;���v{;��z���+��9���kg��#�����.����\f�dF�/��k�%��/x���Vj&c�IЛ�d�~�C/+a�J�I����T�p��:��v�81�T9�V�^������mA�m^}�;=3#��\�jǚA��@��\������]ؐTQ��u���}� 2� ydٔ2�`��)�ՂG3x%X�̌Xd�L�,*#o����5T�K��m�9�7����������S�gW􇇖Y"�Pk�k�������������p��(`|L��f޳e���P�qsZ��*�㋚c�J�F�Bz?&VGN�PY��̾��Cޕ�A��Զ�V��s6̨l��[�V�Г���,/ -[��é���%�-��������҆��`Aeq����s�g'��:����a���E��ԅ%��QYq�)̆��j�i���x�:7<�O��=�L���~?�.DۯB���4M, �2K�A[�Ge�g�����cў�Ž⑼�F��1�7��cA��g����ȱ-s����9-������a���� -�H�6a(`l榘T &\}���{ffkYE�=���]ղ[s�n-t5�h -+@xJ���v>_+#Xwd��!/�36:��<���f�Dn |{U1R1�מ��/)'IK�KY:sij^�����7��vM�H�-���T���Q_�g]��v/� �,�4���u��O�#�g�R���$���h����eY��RE��հM����U �DA2hƣ�,[�f��ҭ� ���Ui��ZzA�J:B�n����嶛#�F�;/�p����:Ͼ�@���������`�.�9�SO/���hOƼ�OG������?�/�e������}Ѽ5��6���� Th���d\���c�����ZIG���Fn��CJ^�����w�1���F�Y,�,�4��g��̈́�2���q�bC�6�<<�ş!|����ᗓ�2ᗋ{�6���{Lf;eC)��U��9��q�O��=���\�#0���nY<�Ҝ\o��m$l �vϙo�Ux����ܲ}�F�l��u�]�'�:��@1�S�Q�ߕ���|�h9�X�� �43��dOZp ?D/œ�ޯ�IQ���¥q�N����X�D�/z������ -;�sa�pZ��R��Ê{VYp�&��<��.��;k<-��T�=�l|b�^����ս޾š����`A�X�M��*��N���q�d�wS}�M�|ܝU=������-X�vwc�-����4ي�sR=]+[�Vv{<����P_ �[Sh�i����$��A}�@Dz}$2� � -��>R�t��{ef����$��a�ű�ޞ�J�d~��(�R*�(��ǘ�E����}��3�v�6������:�M{ؗ۟z�Q-+��#ool**�s{"E�š����b�����6�3w'j��xEG��AQ�\�t6�|���0fn�[w�Tw�����j�w��� -TM�)�JdC��~oK��5��u4}��ޮ��+����y�i�I��Z-�9�>��o���?���h)��X������[\�X=ȉ�Q�ZŨ��t9��k�1����M-����f+�d��]K�kO�� �ܑ�b�'�^H嘋~�6�����M��$lf�G�p�>i��޳����w}��(g&�����x/ -�Y��]��[ΠT+V/\���2Jδ���Wn �G*�>��e���i���+���}�|Ov���)��߲��VT�U�峤e�V̟��\�d-�g����f�K�ަ��P��twfAތ�,�j��Ѧ���Ϊ4-�����2Ϝ�a�u�;��S -sl��6O[U!�;�Q^X��R_X�Ȍ�a),��� ������0�y���E$ހ��.���,�Y�:"5gF�K� Z!-%�,\:f������5�����Zy�����J"W�v��t��r4$c¼���J�����l���'>�g.�s���%R�Lw����<������<� �Ζqsɓi!��0JQ��r�d��1e8pي�,Iyi�yí9����NbB���T��?��H�Ƹ���� -��h��N��N��P�M6����s�W�^���s�*#J�����^'��F�_�9��L��# ��N������!{����)�۫�QS�G&�� -��2a^�i'i�E�eƷ -9?#�D�@�n�L��p���-���5-��(�U�N51>�������6o�ϹɗV�!R�w�`�7o�l��&f�6ծ�z*��s��9�e��v�P�?��UU���[ ���ʞT����U��.�f&&��N;��e��� Y�3�s��Gm���+��S�O��1�̋�F&�{�2��YI��֚����c>�D����:��~��s��L�;�dw ͑��L� �Ӵ��3���XY�N��W�� ��2�]��L.�$.c ��7U:)�Sf33F�|���k9��h��H9,��i�gF��6�V_��#򂙥Ռ�T��&�G���ʝ��|��w�_�bK�RrS�ͽ��7Ń^�`1͉���q���w��0��c�y�“��2��ם٤%t������ZbfҌ93�,�Zbz����v�O�H5�������|�ۋ_4��[R����䘎�By�c�J��L�y�������M&����i�&q̶� S��.Xܕ��Ƥ۲�����kiy�ݑ�{��������� -���v �� ��"�W⧒��E�D*���4E�̙�=�W���KQjfbNR�.#������C r��y �UѸ)!�KIh�,�_D���]4m����L/�`�<�9q*i�0����C��y(���rR��9Fhb=��"rf�ꮊ��o6����нq�~�O��q�J�^G���8�t�������C:�5J ��^C3��R~�^��:��)������#��������r� �M��>��c�ҋя��o��'�����'��?]�?h̟��U(��z��J�ʨ�Ɉ� ���"�UU�\���ч�3鼅�E�9*&�-y�}>�z���]���p�On�;�����܂����;���oeQ�-ۖQ���^Q��s$m����۝6[ze�E鑡=(W"�L���Vr�8�2V.� ��L��2����i:�,�@���r�5=������ּP2�הDc�"�:��z�n��"w�E���W[�RW��w�GFg����G%�yYԽQ�3E�TjI�Nw���CM�������%u�0�B��|�N���:�뎑^zD�k8���ϙz_b�{���:�G���K��:�6����q�}tzs}H����0����5t&/�x~�^��:��)������#���������� �L�k��^0.��:�t��JBc�({+G\L���q�����(z��EJ����(%��P�Ed�,�vlS�.��/ῲX�f�J�f������蚱 M b(�x�W��갟���{ tV�;y����U}.Oٌ�c��+�c5�`�X2#�R�F��V�/;�zW<��^���=���wu�,�z�+��-�+�;�� ɂ9JKo�QK�a��Y��y��_<Ŀ��z|�\1eQ_~^�l�FJ�{�b��C �X�Kw��oA�h�qa�̚�GVjv5�d �ݒKC������5ܖ��X`���L�P�AK����̖r��nЇtzk�N���t�+.�d�~%��Fz ��tn��E_Ͽ�l��0�2�Y���9�9�_�G��u�/�>��[u���3����2)}X��t�tzHO'�| �p�q)���Qiؙ̚���$^�.��=3�����y��Ch�|�~b�t��|�$M�=�˜��֒�fKŌ��3-]�9��vԼ� -����0ԟ�Ի�:B�@qQr�e�ꕻG���v��K���=5E��<>���*���_y����*�8)}�}͛��q�������^z���k�;����Z�f�} )|�M�`��3���0蓣yY���GӦ -����O��g�_�c����J{�=)�G���8�4�^G���q�<���O���8�:�1���No�����8�Y:��8�F��W� �~�����,&^���n^���=tz={���W����:҄Yr��������~:j����m ���l8���z�qj|EicZ�-)<�����Z�������ȖD]={��߽��̒ݘ���U��c��H�|?���<ٞCG �'�!�T'��RR���y�oH(�N�pd�"���N��:�y���8�:�>�>M��t���Q]�{x�y2�3�����8z�N���+uz����w�N�H�}U{�7�ޯ�}q�J�^G���(��}���^C���+��C��ǥ3M�� -����|J9��C:�5�^��CQ:����S(���_ [l����U���h{�dqT�'쒑C � pG���822*���k�ir�FD��r_)��j䊭c�q�=[n8�yD�8��"�U�Q��wL����Ǜ�ro��������g{�� �w��f{%�|�ˌ�c����F�_��e��g�ȼ��(��˩�V_;ʻ��΁�z�� �_���c��Y��S��������Щq{�E��0GZ���8rSw�AtP:б[_ �t4a����2�='��s���ȩ ��;��f>�b{������_X�w����޷)|_��8���/xDέ��x/I�>Nr螌����,<0l��ыs*�sRl�9Ņ����✊�Ҍ���յ5#m��`W�9qF�)�}��黵d[#����u���/[yʰO����-�����;�%���R��r��Q-�����KFN��e&��J�F��4� �n����}g,�qz���6%�8��k���=F��N/1��U6�t��w��p�|�^GW�W�ѿ�� q��tz�N�}5:���������:C:?��~��gp9���K���#�����G����rϗک�wL\z1�qq�9:��8�G�����z������2D��?� i!GFu��4�9q?� eңa�^��<%�N��-!!=�Y�ji� �;+��+�b ��I�&ŏM�[����t�=��K���])PY����dM��;�g���@N��0��^6q���Ŏ���,%�j�]Ll���ίy�`'<�ˣ���[�*FY�S���X��I -��!�t�hܮ ����\M�� ���������v٢V��v�R�b#3�U�/����ϵ�=��D��Q:hw{V���g��*��yj�i�_Z��6:\3Cm�E�5y�9��d�<7l������q���\��k��|�y6KS�������_�]W�/��ь9#L_��������\At:��^��,�C�/s�*rN�Vd�����LNɛ73`�,��Y�ܣqSl�J�A�y��C�ĝؖ3��Ί_ez�G����"n˙��p��?�z6}����O����_n?k)OX��z�B���2/ܴ��ݗ_�����z��{� �a�K� ڼ]ʪ��UTa |'��N�G3��|���)s�N���.���F�W:��@�S�_��Hzu7M����g@��6�\��l/����*n���H/����c�Q�E��yѺ�K���5[w����L��yM�c�x<߆�}��ĸ�M�7�F���&����x�6o]�W��O6�]s���`�I-�^`��v�� �<΂y�//O��tC����A��W�-���y^g.�'���#�ץK֥���No��ߩ��q��uzY�]�^G�_����0۟g��y݋���uq�l��~}�]���,.��0������%�o�ܼ:�(#����Bn�_�?���}�a����w"�y�P��χ�F��+GШS��3���?2�=��C��?F� ��Mɲ&��N��(H.�N�f}���m��#�=���e���6�Ǚ�&������f��;Mʏ�������s�z�>�����8��:�&��U���ѿ��-q��uzY}�N� ��V����c�O]�̾c�Ɔ�or������!�ʈ �W��5P�G�aT�K�w���Lq{�xW��Ov=���W����*}�%%�g���s��>{�ˈ}D�ɨ��`��A��\V��^"�/�^��@����%�i�IzX�f�X�'�������-����m���(�ca��uS,}�cx�����+�}-zH׸�� {�'��s|��l%r�� �X  O�Օ[s�ޢ��d��|��M�����?��G}T5ϸ��J����q!;���M�h2N�2�c�Vʶ��7�}?�z�5�\��I�b8;<��u���_��#F]A{r�=��3��z�4ң�լ]�e�G�{5�_jl_���/)z�X��@ ;3�s� ;3�՗�^���s��x�<y����o]c�HD�Da��de�^+˲��p��/�&�N�6CҵCɽ<��}�A��e��+�2a�>uz ������������e�(�~�>Ƀ�<���@g�-c���o�́O؂,��*b;x��\�ڨ9��kzG��ɱp��1ϰ����:��歫�lѷ��r�����)�}�u1:�;J�N\� ۫j��*t��|�N�L�8�N�I�1:�k�d_cx��)e�O)�u�-�o��]����uz��|8�y�ձ��@/��5���PF����Fi'�>ο9��Ƥt�����죍�M:�G�S��������8�uzS�+�ޢ��l/�N������o�3��K�7��o(�y��1�aqr���#��^�~��]>~!� |���W�� �~O�K�o;)@?TEN 3�=bޤ�k�d/���vn���-�5��19����V�ٚ -�B�>�!�f�����XP��c��^��e�j.͘�ZSR�og{�}��P���Oۛ%��:_�zg�ߚ��?�����_�������z�/�*=UT--��������'��/�Mu��.���C�a2չ*b_Y��K�Ѻ]Z��Q��ϓh�¸�5�;��i|��B���[�ŭs*W-����W#���������jZQ�t�#T��Sr��~ko O�7L��6u��9�l:r����܉�뭤*��R;3�OO���Z -�L�.'︨g[���8�F�Pe�"�ܙ����[};�nM� ��S��\G��jr��;�q�n�T�O2�N�ѝF:��o@ߤ���D�3��N��X���-�O_��0�g\���誯8�$1n�(7��_�p��Y��*f�x��m��bOIQV*w�*�ə����\�Rҹ�9�?<�R��٨Ov�� [�E{�>�RȚ��eT��k����W>RȮ��m�x�E>��}�>�R�oG}nC:�~$������J铼c�?� }��2�Y���us~�$���|8��hA/����[t������X: }X��t���5z:��kȇ<���U^�XD�c�8�)���P��F�}���ټ��%�e�Q6��l9!�dX���1m������yP0�/s��u� ��@�^������-û���E�"N� ��ؚs��.��(�U��TWT�|5C�"����@[i��WWQV���/(�/u՗�d������i��<ȷO�O�ϓz2�>�>O���8z yS�Y��e� �c��ڠ��d=9ðo�Mֲ}���y k߅�P�@u�;6p�2*���j�؝���cw�إ^(�a��i�c;D�nz���ɝ6�7(6�Ƕ���N�/����Ϯ�;��n�������\��� ̜��1�y�������I����w��p�|�^&��]�^���:��@�����4>��No�+�]�G��������x~�f������%��U�ױ���!�)Ƒ>�Q��䳀.�C��56Fʐ��)��]f����L��?�Y�c8�Kf���}��WW�JT=3³F5�<�ʀ�"��?���K��G0���� J�r9�X߾�`�Xm�h�:%��1U�x��M�_�I5�9��q�C_���0e1�S��9vޫ���Lkhz��`n�Y�.�i�~��E��e}E.׊���s+]�>_��~(H=��]�#�-��\��-1�Y����F��6~ޚ��e�J��cx�K�W�\��.}�ܫC=�W��k"�W+���P!;����@��۱��}U������b�.=�����!���s�����n5�3�g�������F��W�qym�nwCYn����� �h(��Z\�6��7����CM�-����t�lg�>D�c=L�o��toZR㮆jwD��6����b���̨!e�rl���f�w��.�C�le�� �O}w��H�%�M��8�_X�RTrpwJA��-�d.���=M[f�o$P�j�7y{�l�����)8�����6����yE��rO�����*�ܝ� -�����s�P�ܚ #�Fș�A�*_vU�#ΐ�)C@a���km��g�Mc�i���ف���$.�EQ:��cZ���f�=9+13��������ݣ�: -F�ݞ�2� ���?�Y���(or9�1N����&7\��� �4Ŏ}�FƂ}�L�t���2I�eb ��; b��>���R��i�HrL}����s:-������R�v�?�����ʎN0L�#�������c� �6�G�"���а�^����ET{݌`��&8�󮦪���i0ՑSo�.����V;����^�ں��K}�����˳.�8ӝ��D9��.OJ���_;�N����Zf�:݊�g�J�#��F�u��1�I��뮱����O^������1v�g����x�h�jN�wU���h3�KZ�v�H�jV���4������� ��IӖز�l��n��=w�%PZVe�58�Y��t���2�����)��q�_xviNY��&���B��S�C%NA�k�OC}54���nu7�w��շ�ז�� -��JO{Ua���wd9�R� 6kUY)J�����[~�+����k�lGY��ޗ�����x{��ɚ -�UdM������� +a���[Z���X�Q�b��%� -z� -������-�h�m* 4��M�(W�Ύ����U˖��+���>��K��+�O 6��e���l9/��=S;~�ǂaxU��,�G��Q���+ՑO���ɻT�S��cc�|�a6�GO��4U�3C�<�:H���)D -��lh���P�A�g\C���4�F��e�Αcw��Z���(����RhѴ��E�桲@+:�^�I�[�����\���]XМg�kYV��t,mjZ��u�u�A_m -"YK�P���]�����?�Jʇ��}�jr�N��3�T�,�R��W|��Jt �t ����X��r�p*�?����2�L���&�c������U��7Q�r�XV����~)%7z�5�Ҽ��%] CM����~wKy���ª�}�Q�0�ݵ��eE����*ʯ�\�[f��-oxY�������p���w0�"/�r��G��_&P=��f3�,��##.b�|��wq�2+i&�9*(�7�M��[  ߘf�Hl������,99u�z���{"��M�)7U#� *|ެ��&� ���+��v($����$���TV���Y2��!X��/��%���I�Z�uT�Z8�j.`�&��9~�j̸CE�W��G ��M���=��Ƣ�삌*[��wv��*� ���PPӻh~uU����*u񬆽�.ozkJJ��4��Қ�u�j����%P���dAE���i��&�Σֿt7��?9y=I�j r"PLnXH�8m~�_t�d\���%x��b5��;�������/� -�x������`�C�w��(W%���MNސLҁd�>&CHpި����2�-��If$⢂�� ⢃ ����ר�G�d�gEY*�Ѳ��XY|���ӉW�>��e��Ryu��6�N2�:i'��������zQ����zFJFP���[)�c'fJf���2��M��Zk�}��k�jx�+���#o�M�ٶTU�C>�ع;2�6��4���Qq�"�����Y�[�nfg'�j�:��3N�&2o`8 �[����_�]�q��a�����}b��Ӫ�ut�\;�_��%g>I�Z5o�GFz語����#3D�=� ��l7���M�H�����-b'Bm6~>!C~>��UDi�'غ��B:��&y��I!k��\����Qd?�Jr:*�U.KGsi����6�ÛXZ\P�@ -2��@�ݝ�(�U6�5�zͻ.s�l��B������]����O�Mc-I.a$�>)C?�YL���2�~��[l;�1ʸ��)Lq�^��˙>Џ�n�*�*H�BD#s-�Ҝ���쌬L'���y*S�~���T��6ֿ4��e��g�^R��h�֕���&�?nq���0l��X-)�F�϶��#��_�B���=E,�"�NH M�퍭dž�A_�~��H+���Z�ɢ\��M��aO���#w[��r���j_fYA]a�k���[܏E���>R=���i��NZnQFm���o��'&VR%�\��I�����􊺹��Xq"����.O֏�V&�ҭ����-��l��X�ʾGp�����������W���*���eE<3UJ.O׼T�JV�)����ˡ�p��@4���z� �#�����^��)>ݣG̠����~Ou�<�e �2��I�,Fc�` �&�f��I��8I�F�>ۘ#��}��4��8/�Xn寚wz �e"��/ݱ�W�J6��܁������-� ˜��u�n��h �T�if������GW�)�seߜp�/���8�\�+�d �l����3嘔����D[�ת�i�7�Z�K���2?�$I��7�x'|CO.����E��S��N �տE"�r��\��Չ�j�1�e��3a�����g�D9�����SGd���>�勝%�0�K��zM�\�c^z�>�`��� �Q{��ކ��in}3�]���/~i���)���%>w�3�����v3��q���^E�(��9�`�����[�隚0k�tG~��}kk�U/�?)�ٹ-���)���bs���ʟ�Ks�݋F�;�Qm����r�uW�c �9(������Õ�k�$�M���1;!�^��TW�ʎ�U].Wת6#D�5gppN���s�.��y�� `��4��d���y�^���XȌ� f���ky8���C��3�����^�:h��=�}�{uN�73ӛ�����-�nV���n-�㜖�;�s]�`����U6Q/a�(��l��26k2�D -#����qm�n��V�R��)�n��j���w��9�;0�d�%]M��Ү�u"H���b��l=�� -�鎪Bz�ʙ��U���� ��, � /�,��V�W]D�QJAu_���j�4ZT���)��Z�ڲ�ӣ����\�Һ��K�b�j;�2 ��2�$���a���D�6�3�i� ߔc�)���Ik�L�bΞ|ց ���d��H��`��-ἠ.&�2y�r�B�����R�����4z�b���D�?��|}�3Q�V⣧�\�DO�l�ܳ0p � uԁ�@�A�v���w|�>*��x�s�֙� N��kb����˦R�U��=�ʔ)l�:E�vf��}���+�o��>�6���$�>��Zrt��>�,�ex�Zw�%?'�[�I��N�3k��W�-��|^�1Jy1M���"Wħ�-���!*cjx? lУ�iX÷�{k��*O^z�ҙ mm 3� OCiso��=�Gey���_��ו���0 $]D�W*���|%�(�R'{ҌL��treΏ -�H -�&1h�A��쐥�W͜Y�֖�m(m�M���F -:�����o�M�ߨ�}��Ϙ�O!-S�ڌU�c��9~v��g�8�*��u�&��߯k 畤ْ3Ӽ�����]NQem�ږ��hz_��p��i���:�7�'�1*2�x�a[t�#]� �'�_�7F�u��q�0�,P�}�{�B�^n�����5�x�����g2^ج����c+0N��82c_5͒���b��;q]����j�y�|&k�>�A�}�Ɖ���p�� -M�I���C���h�A�!�~�C���%��A9z�9����C�Eq��+U�XEw\���*e���2�W������$C���w'���N��e��4��# �O�g �����������Ց�f���y/�ˏ���&�)[S�e����;ػ)F��l�!^0Uz$N��8��=j\� ��ћ�T�T��OhF�H�=#ϙ����P�����d�J\����p4 ���� �tԖe(=u����UEEU����ñ� � �60���g���*ݍ�%R�3�.�y)�=��;��ٸk8��r�c3�|�;��@'V?���~1�����`'A�Ⱦ%S���'� 3cў~�ƅ3jΨ��b�~�ڙUU��lOUA����d�V,={�}>|Ϫ��V���7��^��f�;x�EIa�����K���� e)%'Mi)'(A�tn��E'��H���y�Өp�Ą��r�����K�R��-lEH i�R�Ӱ�O��ʢǾ0��=cf^0������7�Vf�*,�]��^YN�kO蜹(9�=-\�>4c[�^1�Y��±2��2��-XR�t�=r��mj���gt&#��}�����^�}/�Y������^����P֨�XdR(c��8D6�'�a�x�+"�U�ݺ<�I�=��q��ߙpt�?H.>R�2��Q�v�ZT/�N� ���Wc� � sSr�2F(f�b!�!!B������m�B�Q�Xmz��"{p�N��O& -s��M%[���d�7���_�B';S\�(U���Ee?�۴���d��%�ޔ61Nෑ�E��߾�E}/a����3�w���.��=l�{%��d��K�O��~}9;�^�K��9���s�%�V�}���73$=`ȷ��/顸|�tzk��{���(},���k������ -.Wgl�B��ɿ�N;�s15���J�gəyc+���Fϊ3��k�r~���"o^ ���^�(/�̉֌��.�$�elg��e �2�sߗ�ﺡ���I�"V��ÄD�+[�ˑ�b��(��UI���ؒ]8�--�����\y��-�M3�^�퀦�g���k+蕧���FnQ�Gv;#�w-a'�GϽe{}&�7�c� {� &�{�t~������7�t�N�������������8��:�!���No��a^�(=��]<��N����$��$�I�r9�������%�G��ͫӏ2���/�6�/v��|�b����st� qt�N?).�w��+���0!������g�x%���"R�N�͇�2� �=?h<'Bx�~�u �M��z���)�x��!Jx���x�}u�.�K�ܸo�2=x���q�D]Q�aM� j���9��i��Wb�v�l'����+��8 �wuze�~�^Gߪ�G��_���8��:�,��I��=z��q��0'x��0����<����9� -;��d�Q����ωT���9����Ήt�Pr?'�h�A�c�:�t�噚}car�Ng����̥��τ����L���9��� 1@�����H��Ή4�A����k�NT �=:��R�d�8�l�hH��.����1��� ���w�_˾ C�J���R������7�K1�[} �y}LBl�<�k�X`ʣ�_��n��%ne}��� ��a~wO/%�r!g�&K1���d�(������7�$���UoG��%��i}!e��kC���W����y9ķ�wu�߅z9C��$>����T��/Z�>����*�|�U��ʓ_�b���8a�:.�a��n�GD?��>8��i��`���G٧��#�D��eJ�����N�u�*@���?<��(��k��7�zu�^��l-�Ȳ'�� -ݵyM3��\��},�;]����-�s�C���0{��[�N�a�G�z�u�;)B��|�D �7r)�G���k��F��g��o�|�w�D�� Q���^B7�D�`2Q�o�_�=������??q�^��фo c��L? ��$Y�lu�T����M�'�{�� �B�U��{�W���o�!o+���w�����ǾR���Ⱦ���"Dz�Y�H�0jJ��ͨ���5����Qq6[��g��Y��`��� -)A"4{��e��+C��pah�b������״ψ��3�I��:�}�@_ء��Df,W}=�[��u��˖�%]˚[���T>��V�7d���2f���f'��bQ�̾$�C �u�J��E�r�,E��l��Ǧ*�x��d���7+!�?�������#�Υ�?%~�5W�\��E�����emN�'�'�r$�NMl�d���rF�d -c����ڊ�b���)��jP��|X7�E6��cGg�,��Zy�5ඖN[�ٹvz��]n=�ˢ�b+*��j0T�S\SԫY�GB}�����o�j�m����k���|=K�j��7涱:��U��U��\�����\'Ǿ���NN'1�A��4�N����8�Tߟ�m�B�|fp|��9��#}�o�}��of�+ D:�E�b�<�Q�IC,"x�H�$q�*��(�0������;�<�3��-�3M���b�d�?j���T���Z�J��K�-��2��gU�s��Y�E9�Y��)�܊Z�qV^Uq���\��Z -B>[{��cik���澃���}�;� ���T�w��+kp.- �S�U�_�d3{lHI��a'-�@fM��Sc���L��.��?_f��� v��m`wR�Xm 뫧MB���bg4� fGI�VS��ob�:��Ќpqw��q���T-�) -���ʛ��[����dd��4uP�z��U� --h�Z˻*�G�Z򚡚�u��@����d׹�g�:]�꼢@Q����n[T���Ct��>͉��ݙpL��'���S�3��9"h�]��* �/�B�V������KFr�+�)ZpZ�H����ҤݛYX��i=��Ӫ��%{Ҵ��5��x�������m��|gKl�d+�Z6���6SD�ȹm��9�^W��}�Q�41��/�n�R>?����Ԕ�f�[��ӱ���4������9���ݟ;��� �:��k+�5��f��k�~G��ۖ������ڪ��*j緹{Os�%˪���\���b`oG]U�5��O��6��;X��~J�ա�B�� -�a»$SD/xd@ �Ăɶ���l�y��X���rF��%���*f{��Oe[mKzQvnq�}���b�冷kJ��[�>��k.�?M�Ne��*���S��[=6H��x�}�5N7'�D�.�L��� -ѩ�$����g+Sz�TC��od��e6JV����]���_�)�9jf����uw��� �U�B�yo�'�`C��8'Œ�f6;j�6u�n^�О�n.o��̖��'N�S�����kVf�U�����{��m�����2g� ���۷��qՊH�\Gz��#@ovnɊ2���&gNa]GR��*�����'��Q[�ϊ����\9ʏJ�+��_��V~�9�r�r��}l�H��ѹf``M�c��E]{ ��U$�8�����V�មd}'�%̚��n�����yR�RR�R[�鉻���ojO0�z�6�+e{4Jb�p'��X|�$�0B��M*�7����N���؜g�� �r����Rav�A����Y}�]e\�ac��9�@iV�-%�B�T��M��Θ�O,�̪�m*L�m��s��3�C����AWN���в���Yim5ųgOs[��r~�!K��������ɋe\Vѣ��.���D���ǝ$�ќw���L9u�{z�%���ڰ�4-1��`��K6�&&����@�9��݆:/%+��Մ���##�,v���T�ަh��Zu�sš�S�� tV����3���V'fB��˪3].�J���jII)� �%(}��j �>�^��[]]fk��΄r�d3��� Vfً����_�?������h&�˜�j�����dD�}�G�>��t_u�s$��̕�U�$�$;+�KzN>j��տ��Ԭݵ�Î�䱮��f��>x�q���������(Wqn�Kߕ��+0�B��hA��5��1a��'�弗���t9��Ul�4����'痻������zGe�ϓ�rzm����*<9���9�99�gK��h��]��觧�̅=����$-kZ]���m���^P��R����k�W;� ����|�36״�9�w]@&ۭb<ӄ�T�m�z[�VV�kU��`W��@��kE[۪.��kU[ۊ.O���׮�? j&c��!��,{%��N�yG�ҳ/Y�eF� �PT�>����ysx[���5�n7���ϛ��:n�J�d�O��EQj�����S�[�,+]��ڄ�o�ڑ�[�r�@VA��#���: ��ϐk.��ٓ���>�‚&�I�̃w�1I~���{�0if�:��<�b�ޞ�A��.�r\a�8'�Y�)�.�z:亮u�hw�r;���w756���~h��:�,?:����ke�h��i�P���� :�FS�[�����%�s�ir�?Pv7 U�xr,�g��ک�������F���}M��]��U�e�=a�⥁�j~VQ��$����N<�)�l�C��(�"��IE1���j,���_�@�S�RS�ˠ��=�g�o� rD�%���Y�W�{P�7���[�a�*E�1���:�ˣ���!z���� sT�{�ß[�g�ͱ�:�g��ܥb~�������6�]iO[������Wv���W5���'�U-g��;���Q����*�����vO�zkl�����vO�*�櫓.�u�FNbe���c��G�8!Pc�Cg�,�y ��\)�w��!/��;>@����3}�Cl��ڐn��fջl�� _�����\�=�J�fk�_#�F��%"�߆z:�T�1�1̛ϋ�=�[Nw�&���yU���|9�W�7 W�m4�z�>���D9ɧhu��"%a��P:k��܅ZRF����#z�Ԟ$T0�2P�:}�Ұ�9u���p�+�m���׫7��a�֡~�xJ��ƺ�����=2�\� �G" ����2�!L���G'P�`mf�Ƶ�VWnJ]Y�+/]ӖhZz��RV��m�N�q�Җ� di�=��֥��Nkr;�Z�}3���UG������RG��568������G��||=�^������$��#��r��n^��{;�v��y���E垞Z��uVEMw~�Znw��sr}�"GYA��wEn�+-�]?��yi��c���˯�*-o��'%���@k��:єW1�*#ͱ9�ď2�+:A��Dܑ�D�^��c��!��ܕ -��q�-*��˺��ܮ@^����;�lh��}ȣ�m^mnSiS�yM�3�u%��-��pj~��n����:+��j�{��@��~��K�V�9��Fۋ�~[�����4����r|���薽���Pz�7�ݸ#F�n=$�\�[�� f�f_�b���j��Ķ��>����kݲ%�$��4w�b�e��9l�J�5�( �p��~�bw�Vd'��^ر��%K�� -, �ޠ=5�s����a_iOu����y�"��`�����dY�&N�����G�>��p��®kDsK/!4�.�u���Tv���i /�U��m�U���������0��%�U���{o����3N��.�6{]U�ʏkw�t,ij_�SB���mG�+E��ʑUD�Ɠ��OY�绋i�����9}7A'���6 �M���|�#�������ʋ���_!�tk#��c�|����d:���}N?X�-F��zXҹ���.�Y�����T����2_��IOgo���oP�n�2�>Q����3 ��z����x�N��ʍ�'���)�9���,�~��M�Iz�OJ���~��|<���G%�ZN?�ӷ��Z����Ǩ������� ��Y?��l�o~ z�N:�g���0.���.���K��:'�]&[�a���e"ߎ��FI�j�|���/���II�� �m|���4�#�ȿq����d�RH_u�|���I�8��BIǿj�t#���C=2:��+��I�Gӧ^^���卣���f(��m,��r>�Ǘ�^"���K/��q��K����=y��Ǘw]��Э\���_P��m=�볠$}��3�l]p�8���A�G���_�:b]�|�X_D��/W&��(�{$��|@o������I�_7ȇ�퉼-q��见�'�"�H}6�w�y�uP��*Ϸ�� |��#������z)`_���E��N��S�r=U��tz@�Y;}��_Y��8�U^��z���ĺl�������Z��Xy���Ѹv:_�7�k��$��q�t���/�.Ws�9�z;�~�����$e=��V�Oq���*�?����������%���bп{ �����?�Q�NC��c�������{��S��'��o5�C>V~~�QQ^ܟ��;>����F��q�O��O����\.I��:���u{�����,@M��x��q���`�u���G��V�.�`�����E�g�/1�o].���rk5{�KF��������;W�)��t�|����\T�\>��OL_~2�e_C���E�[ڞ����q��{�IL.����fh/1������>br���������:���Q��N���x>M�;��{���Ϟ�~������G���s���3�Y̮�W�����eq�M:�o��Y���뤟/�?��#��GНS�=q��8��)�gNA?k -����wY��'��c�s>w��>9�G��wΞ�~������G���s����+�&����q㯺cD�g�s9z��}˟O��N9>ꅜS t�|�#<��!��%��q��ix��,I� �� �3$�{v�/���3}],}���_�+%��)�A/��W@/�rt���'�7]��>݊�7J���lƳ�Q:��~RS ϧ�t�tO=���S�Ϝ�~��3$���p���b�?�s�����Ǐ��0�N���?�������`i7&�{���Ϟ�~������G���s�����ؤ��q�wuze�|�^�~l�a�)�{��}:}�)�=�� -.N��?\�� -��i:�9�G����3���5��8�_��3}������{+�>7��K�_[+��p����=�/h0���1������;��'��������g�������q����'�����B�~�o�1���d?d2�=���z?��t�+'����r�>� 留������q������?�O��'��w�?��9r]�}������è�u\�K}_����/�qv��`b�c���`b��r�wb�g���3���Yr����� �������u���� �?���g�dͤ�7���ؗ]Ů�ľ�`�߾�� ׵�o��_b�?[(( ����?�;K�t�U�;�>�� ��=��IJ��M���V��\�2)E��Ҧ�L�d��6}�;zW�!R�6~��C��-S�� E�h���}��タE�,���[M����4� :JͶRy�����7W?"j�1���9%�S,��5Y�S �!�2���ҕ݅��l��t��Uҽ8|�}FٌŶ�&O�}C��$+9+3Ü�mi��⥥�e���p�m!2���̿P5U �q�͌Kn���^[��<�i�g��/��wZ�^���mA��*�;��v���1���+by �z�K|�RE��/�8'91���>��(�;e����^x����{���U��؛�}Gu4:�}}�V�Vw�ߟ�#������� }O�k���Zb���.��l�,霦8�� -ASB.�q� x� ���^5���&�!�A�s�fx��?,��Nܹ����򢫑��esuM���͝���e����N���� -_)b���\�o�(Y�\��L��U]��fζ� ���Ly��q��w�&�Q����`��{�[d+���6�5��d�s���E�1Ve�.��1�`ؚ�W �xE����JW*�r�!����NG�������C��y��:m�L@;X ғ8�l��/hGsڙqϝ�ig��A;����i')!z���y"m�N[�t�G�M��Fi�@޷ry�7 mo�v�2�ެ������6�9]���T��PXb�yΖq2�(/��. $uf9K�K�$G��H *��� �����L��-��������,��)�Л[��:�#e��Mء9I���7�|��9�o(k��7U�&�7I�X~��]��1��e7�x�o+}���\���$]"5+c|'2d������/��լ]������m`ܞ}�7۳����U -)�Uv��[@3�ס�^� k�q�� �S��v<�"}� �ù�z�u�7��nK�j�ֱ�p�ś��3-ɑ�k�KM�HKO�3�%M����ͥ5$���>�)��/��3�n��M�Вy{;50��-��_��� -RGN�ro��=2a�N� �g���N��9�"23_�hv�FwІ�Wp�!RA�IP?�3��ةJ�~��p���c� '{�+�hO~Z�j�ؗ������fJO�N��{���^4+�S��M�NL7�&�����=��֕�W�JW\^��JP�Y��\X]r�F�����0YUg)JB����ȵ�!����~��W��乨����1�1B�����B+#�Nq$��ю�ߎ�0�Q���X��Jl�'�2)�e����_�\��ceH��o5S���\��t5z=��������S�&5!��rg/�}��K��.ONaF� ���I_ؑ���ͫ&#�Z�qYz��ڣ����d��ɶ�t,H�fe�$����S� -�|/2ѿ�*H2�BLY��'�.pe�Qe��c��Y��k/te��}�+&���O;�tʐ�L�Q$�k�͵M g��e�U]��7>���ᑑV>����,�����oi6���i]��W`l��@7 ߴ�1͓��,����<��!R�{Y�D�ҩ?j˻`�;xP(X�����_6z�™��f9*�{�V 58B�M3J:�V��*�-�jgGkgScm����[n+���ζh)#����yvC=�=��)��I�1L�3��d�l�(�R�ă�Ź2�����Y]2lV�����>rG�ޡ��U�� k׆N��3y8?� 4�|>�PMg��hN��b�� M�\���/�?���t���?E_��B�&��oVMj}������s#cJ��g�f,�d������W"�ۦLρ��W��c<��X -imx��t;��=�@�rW��������=��r{Y� �_Fnv�h����1�D�xFo��^��?���a^S���jqMm�Ȳ�Wm�v�s�#1C�b�������v�'E�� �NR�#eE?b�o;��i?�M���閌�4�͚�S�MG�˴[ҝ��T-aȔR�߼v�쨑��/����:6��+�Y7c��@��˹�0K�[?�D>_ȟ���qg���Wn[�1�Ä3��r'� o���Ly��8$T����WegαO���3r��Ĝt�?=���G������a�=�$����{nǙ��s�q:M�yfgwgfsλ���$-aTx�� -��(�(,� �(�O\�a���`zl�w� =ݳ�����BOwW�{n�:u�TթSe�P�d������r<���a8GP.��!���z��VTũ���z�$�,�~�e�!��o#��:'� ���9�=�Nt���޺u7��[��U7�t�8�&Vl��}��+V��T> �R�l�]v��%� ��>r�l�|m^��P�ۍy�jiZ�?�Oq)�e�B��4u>��jnkg�gji� ��Ȏa� =o��5�9�Ee�����f��8�e� ]�f���]�y���-Һ��7m�wECe� �d��t����Z�j� �N$�$�G���YT� �r��-������r8yI�[;}r��i���u��Y� >S ��*?=%#<���$���D]e�oк���J|��C곕S�1c �?.����|c�¸؄�5xд�4S W��LIZ^֠Ԕ>�̍�������=kJ�vϬX�r ;>\��95�-N欃�Ý�hL$XF|��B�Ӭ� �c�Y>D��37�JV�@�4����7q+��0�r�ʵ�3P&�ٔ�Q�z�*O��K o�Y)�m�"��a�v�� �Bl�ՠ�y� e�� Q>m:]N'Q�Z��4e�#A:�}E��L7A��;�i�����2�`���L����]]�;G�����T!�蒑=�}��7�\y���+��fІX �3ia���E-�z)S��3�2���%x�uU+�.�B���������C1ɍ337���[;��D"#�;�Y�-;���wUj����7�R=p�x�4���H3ැ?P���EWp��Q���Klf���:��$ .㒀�d��: W��z��7�o�ߵ��'��_2=���֭]�N�b���B_o�W��\��V(�t?��S<��������Qد���4<�V:��Z�GZ -�>j��3c�mn��O #������9]�I�07����W�4���ϭm��=w�O%�HdjO><> �U� ����2��eY�m*k�8q�_�N�sjam�����9��K���1�lF�:��_1�~�i2;f��{?U�����t ��e2r(;�A� Aٱf��$�<-�y�9OЏ��fj�Û�u�#u5e�����!NFw#�U�1���\Xlj��ޡui��p<� �beZ�P��)��eT��5�(��F�k�5Elu�x�Ӕ�*y/�M4�a��#i�r�Dz��S�.$�D 2������E�O�^�{���0�S���� -�Q,��5�K���q� �)x�N6| ��vWqE�gzr��j�S��2U~���4���_� ��7��>�Wdl�JvA[4�����-��_C�Ϝ�Y�>��Lu�v�g�#��NѶ�`�^ �Qm��_B��]�o‹|�m7����,og$�΍���� ��]mg4a��7@̌��cJ��8���DWsb[�q<��kwv�d����ST\�n�(�k�Kg`�U\Z��T��}��kg��)4g|]�ʳ�3�gV������b���D���1�]dĹaI=܁3XsH!K����P�� V��Up'&�����Ӹ{z�Y���Y���˦�/Y�L�<�|��Uiv�9g�uΩ�]q�EW� 5qv��'���4��dZꩅ�Њ[�@��R��ك�*��je����1�t�f����Ȍ�wl���Pr�t� -YV -���v���jEt�_�8�-�p��W���)�HJ3|�O$�0ʏŀ_�\�3��7q�� �ސ�2�s�7 �6���n ٱ�Ec|KQ��~p���e���<�Ў,�qhg>X�t���8�3�&��hT#�Զ@�W�u�f�|U'�̪%yX��mA���z��C5.%L���B540'9r� �4���S/ޤX��끦-�]qF����/���|���o(�q�#B�E�K"@R�B�.�zD��tZ4��6�j�~ -��^�O�1e��{�Q�X#�3!�|��2�S -���9��i |_Gq -�O��esh?�f9|_��Ċ�H���Ʈ��e�_�TRC���틘�����%rV�M�+�"р ���#��|�k��ƪ����W>)7�\�$%�PW���L��KY/�|l��'���{,?�H3deﯴ0��� -�c=�ˢ����'�T9���huf���_����p�E�ر%��-��HL��C�d��{.���G�9^7X ��pU_kV� -�g���<ӊ�_n�A�yp�q"��� 융2���� ������o�j`=��$ -�g��uW�.� �}g���=�q�����`c�gf@n9��x�o�����ܸB�Y���D݀�N�2 -�q�`j�QG��Di]_(9uVO�:W���L�\��D9��S9(����/=�:��M�!����A!�V~���չ���0�g����� 8rMe�k��!���dJ�\�,�a*���J�� �V�>�f�֭�����U����?Cmy����Ph8�h��̟�L�N魆x��=����%��������&k�����c����2V�/�٨Y>� �D�Զ����GB|)��١����M�����5��TmR�Z�� B�yКw���� �גp��{+_�� �����s!��D�n`@5ZY�D��S 9�ޮ���Ʌ;k��kw���W�P��W3��{|r��EKk�h�燄����X�­k0&��`�gUE*Gh<�BC.�|��Jb��O���'�_8V�7I�����"����u(��&�/�:���'w�����ا�9�ր��`��24�QVh����̡�-�W[!���3�<�"A���?+��&ub�u��X@�d5�\�(�Y�C) Hl��A�y{R�g3���d�^N;�76����D�DN�_��:���Z��3Y�!y'~=+�׋3o��{yx;��|��zbhՇ}(��o��2~&'���Jn�c��cy��%��B�_cMTY ��� '�D!l"E�-&�if���IJs��N��Ik�y:V�y��U��hޅR�:3�`O`24���Mݼ*�$�ɕ���fY�1�r"�t��7����ĚpK��s�ȡZ`���!��!�-�쭗����x����*�sGM��"�k��2��3 ἐ:���c(1m"l -9���p����BK��=������jW�+���Q{��{��WnH�BqZ; \���PG�*Bq��4���� -�B�>? *�-=���5E)���\NY�J��CV7Gt �����4R�?�����k�A�U�AzD9�7�C��ێ?V����߫l(6�U;=~��Y�����:<@�K�{�~p�7ji�MIl�wm1��28,�40��%�Lm7myW��)Ǜ�2�b{Q�ܛ�B��{n�x�}�������pe�dߕ@����?�������w����� cz��Ť���k�gbz�_��GB�����9P�hoMŻ\��L1؛u���oiD��&��g�G;b�P�l*�\Zo�/Ю�6������e?�9���ϴ_��Dw$�����@ӋF)�QT/�ϋj�M/�I��P�#K�G���E6d,�����Ǭ��\�5Z1^�w��[�n�� �~ ��q�~��4�Cy�υ��m�q=h��0v^�X�^]��I��7B����:dۻ�^��^ŒX^�s��|o�4�l�"C�I���ܮ5C�h�8Ih�E!��HS�����5/�<=���X -5k��L�w6�������J%�{l��Օ���]��m~���L�xڿ^/~W�N��k�X��{0i������OV�M�ޛ�y���T��YK2/W�b��v�%5�Qd� -�WѨo��)�J�J*�d�\i�G�V��W|��<��7�!�38�v-�)�#� �7:��f���Y��* � ^l��%.0�iS��I�diܰq��HX��i}y#�+�+x�g��������Um"�2�:����pR���U������8��Aõ=9?�U�����F�k�?x�k_���R}� ����~>WQS߲>KK��D��oD�л�_��B���ܟσ���I��t>����s�a􇪟[�K]�\�8�����}���O@��z|�j>�^��(�=��|��,� �OK��M�9T���\�`EM�$io�\�� H��f���U��_�2~|Mj?V|� �(Q��Ԥ���^dvd�(l� �����9���_B���8���*C5�$2PFr� �����>!�� �k̹f����!W��.c-eG�?eq�C�����6&ḳ��|o�#�6�-�� ӘH�e�f���u�@ߺ6T6B���k� �א��;�!-]m��hl&�*j8��1������q�ͮ/Y�UA���<�==��4T�!Sn@L ub����3X�X�]�f����j�"ұ¤�%�ap����&�U�a�t׀9h�~J"���u�~w)+���bg���ކ���̵����==}���˹�—���O�����*��&�,��@N�<���?p�+����"L ��yJ�����&�^e*�uam]|ց�s��*&���T�������:k�� :�Ơ\�������k�q,��A�p����D��y�����E�(`�"��2� %�C�ttw�������Cm�X{X��3���>H?Iq[�sP�2 Y�M��kj9�b2=���b� �bf -�7�P>�:^atjr������[z���C���a����bf��o��Ⱦ�J���`��M�R˻�����b�Ϯ蘜��[,Oϖ��M=^�c�:�y���ͅ��5�ej�p,k�V�Zs�u|��U��Z��]��xkP�gz�,y�u4�X���-ɸ�>�P�;0Y��}�� -��Z��\��M]����ȯ���h��s�G/������ǚbZ��z>��=�%G;G�� -a�b(e>�:��Vo\ ��pS�ו��}r����'��h�������OG���?��a�{� �Q�y -�#8�ș��?�58ɜ����� -�oM؈��r�倝���,_��D�$�3�_�U@� n� �, �����.�H7�B��#O�8��i�A�I�������/|A�̭^�,N~_)�Ȝ�l�]Y4B��� ن � [|�ko>�kB��J��v��.�'����i�/{�\��N�->� �G����f������=bn�vN7��g��@���[���l��݂�x �ɉ��Nc���g� �ǰ���c�r)?�~~�9 ��׌�����1X��J�o��Y$�Z�#��7��v#|��N��N��^�>��+����#�!,GTP�< ���|��i�� ��D�sy�*�Y�?���cZw BTF����h�XB M�j�r���U��`�|agw�C�։���".s�]26�$9^G��/��,=�NG- bg+jl -��Z�eAW��F�_c^��]u�٫n|DZ�"ӊ������E������gX9hg8����<�=�'q��a ���Ǐ�8e������'�!N�`Ӣq�FA��㔫��n�e��lj8 -'�(����#W��@� ~��B�E�\� -q�TѪ�\j�8�WM}ij�,��|�/vJ���M�&i��)k,�hL*m��lW��mp4 �4:(��˃��]�}~={#����S�F��� -����4@3���i��sK�M8�͂|ZM�žYN�Q� O3�F�pAEgH���d~��wO���d�c�W^!{�y ׇ^�K ��*d����zm4�ׄ��p�@8�l��IK� i��Q"��Ya_�����7aA6���vX�N�� �mB�ߠ�������r�5n\�N�@�S/�N��;��� ­����|uT�G[����#��� �K��R� ��OVE��;�x�%����޺>��OH�lS<�On_H���~��y����6�D�{lvB.���v?���'�xd,� ��鬎�p�bo{˭ 8�6��Eܞ����;�~�5��m8�q��u�=W.h�}��}K2�o�GM�O~[�A[��5C(��vܖ6?Vu6$�p�/$��\wS�‰$.�K|��ׯ��Y�5�<��(.�B� �� �Ip�A)½p�,�+D �p�N֗�� �Y#���7�'5� ��Hw��΁׹ ����HC��p��c���s�7J��jْ��Ȩ�Uщ~d���T �!��㕍����uWo�*}�����\���q3G��e�a��> #Ԩ�B�w����H0�}� ̿@]W��6NY>^y��*���*F'�[�+&_,�V�U��W8}z>�jӀʌȋ�{YZ7����9��g��WG�R-R-���ݙ�aʜ_�v�"?�,����/�H�,�cg�ҊX"/; G����`�F��A{ܔq�Z�s���o�� ��RD{W�6��q�O�Y�2�3�Oz>��^ -�Ɖ:�O#I? -Ce�"��i���R��i�����W�CZt��i��^��n���$*��*|̂�\�����#������K9�IN�i2� @p4b�8tW�Ҁ��i�p��;/y� /�e��"L�2/0���,��s���z9�0.FW���j"yIܮA� ��#� N"8/��4���f����>�����,m27u���a�N�cN�L �9�_�m���g��!̷�/oa��Q��d�@f� B�G�錱�3�v�a������8�칠�Dmc�s�� }��O�&����$��6*� b_a&��B����ȠՐ>K�i�����p�����ʉ8�d ���f�� � ��k�D�=Qv�"N��1�� ���c���������Q\l�8#�*����ng�����$����c~�1�؇��*�c_�}j�������~y�����W�~����W��`�}���5��ާt#�S�!��;oþ&�?a_�v/���W5;��H�6��K�6�c��p�p�����KeZ���k[ �q"�|7`�큳V�sa��kU�q��t���8�6Z"y��-f�/�� -A���L�5�s*wyY����?6���f�D�Be���rE�^�l�� �l�`2���?hKv��XPg�<�/G �A����{-�b���^�\�`�MSW�\ -�s -s�(���{�i�(���f� �� -�������))e��+�'I\�Rg��ty7�"p'~�ܤ `+������*(�DYנT*t���Չ6�T*���B��S���‚ ;�m�[�=�!�H&��էT���[Y��w_��� �ޒc�>����v=��2 �X.��Xg8�3�b��pg�\nmɷ��[Zni!{(��4>����1�]�y�9�E�(aE�@?��l��C2�N��*f?�� ,�)�v�ö�lډ��C�J�CV%qt� �� -D��HI��*.pm�"�25Vt����b������VtxWe��ck���0&�� ;tjṣw�Bɂ�ە]d���ʑ��J�.�$胣0��-eb���Hls[�y�W9�r�c�����x|!�+Q�49m�m%gWؑ��o7 ��� O7AbhZ��i�0rz��$��vv�3X���e��-�����L��z_��L��C��w�y�w�y��ӷcxx{��ݳ}���b�o)��E�(| -��V�\� ��QR�&��f� - WG���Ko<���\9�[TZ���>ȠL� �*�K���<���| .S2�I*�|�l�&!|� :�):zp2��tf�k{�� mj���͍�6)���dq׊Ժ-{:X�����|��,}�"�h���Cy 0�2�E᣻���j��ts YQ8yP������BLw-ik7e���C }G>:�J�ִŇ��ނ�%l�M�46���N�����G�M�2���H�Ii7J�X������98ލ�Q�ǣa}y -�ǟ�8��&�#��TԿL2ݢ�.��}Et�Ȅv��XX���t�W���� +U����-�#���Q��ݒD�l(f�⍩�~�10�c@��V>*�Eg G}���) ��u�'�F�KZ]��y�ۗq%M��:K�-y��KT�GW��p��?n48A~�K*�57Q(,+� }��g!y� -�p׺>}��>Vg��W�s8�u��7�̲tpU�0�H�n_g� ��"3�܅��5�ֵwzF��Ux��%��^�����w�d(�u[z���3��ޞ�Hj��U��7�X���/j��� ����Հ�J���E�I-Q<(�&2���mm#q�t��W�ɏ^yͭ���t{�-����{���=�� !��Mc�Be���↿G��cA�Z��/*EnU�u������6��LY���#���5��@ə���36�ͣ1E=wix��T�5VT�9�=�p�jQ�3�aO1�SIm#�Pg�jK��l騷I��DZ����v��}�W�k�<���I�j�Pґ��!oE!���4j��F ���?{D������*4�����?�Q��9�ˉ|�'O�c�]�o�c�]�ccݶ`�_�|w�{�s���y����v�V(����k-��@�F�#����}>2���W���Wxv��ṍל��ˆ_��t��_��]�!8�㕯�Ư���/cq�Q����m�q,���g ]���x��ƍ��o�������S���ތ^������_$��ul��O��l���D���b��m���E3������dN�q��g��JbN���9a�5��9�J̉xW�}�A�p�˾�P֎�Y���=� �.3���h��NA����� �1� -�c��2�Tek�����}�ޒ%4�A? q 3� l�s���6!ug�)�& F'hy -�-���t� ���A"�?�,����_ W�jY�L��ŶeQ�z|ݭ�^���U���F��[���1sC:=y��I�u��w�)eD-؅��۸A�@fs���\_k�y`�+�%<��pv��u:SN�g&�f��8-̶{�}kM��c��|�S�o��iG���p|����VOb���W�I�����\�J)� �b��F��u��u�*����?�CY�9�,�a�,q�[��,K����Ivd�h����dewr(k ��A��"q�,:��qA�`��8�k�'|��B.]�$��\���ǿ�A�}�� �=�/���RK��#q=�_Cy��MO���x<��l�R�}����~��3��5/K����Z�������}�O���;�gldW���������_7�Y��c�Q��kԯ -��>/�Q�{�d�ls��,�+��+/ � -,�F}�F��+4��_�z~�m �Og��m۹������:���t��}ik��;��p}XH�r��).�}����~C����g[: �$�Ļ#�Sp�r���$�^a�� �����d�ZXY@X�ľ/]��x��s���K���ξ���m�n�Yy�� p�[�~�~ ċ��[��9��2 ?��f�-��T�mwC����#u�^����{�3�����E�Y��^�QUl��ˑ�!L�^�'C�]��S��>S�����v����n�cS��y��%1�E�s;�>��+�[Ư���͂����^*@�i�߫~��.��������{�1�D~�O����J��� -9����Ѡ���5 �zez����+�*�}��NZ�鞰=�5I����{�v�f۳���Vy��ŗ:ն� vz�9K�^����_c�?�����#��~�@i�3mj�z�SNֵ,>-���W�R,��0e_�R���kWvi��:wj�{WG���������� �X���5P�@���6p-l؆NhC��P�ڀ�5"�Z0&�@���6oh��YCO��O!Շr�`���k�{ں��ߔ�8���B�� -���l��cx�V���goG��Y�Gp�$�WH"L�4�3l������+W}�'j{�y���mq� : �*u[ʆ��"���mjkbc��G6� +Wmd��a��sa�s��ƞ�8׾��qH�qTО��� �d�Eܳ;�:w�o�e}��wEyr�5��~��o�8}=�SL�Ҏh�ǑfVL�J�LK[@�<�/;߉ΜU��G��I`��Z;������U�4�7��4DȢb�|#��K�dZ�(\��-Y���Wy�0�{���H�l����Y>���>��ZG��p�� �k�6�d�D >{���� >�ׂ��ڰ�(柣���6p�y���>�­�?���K�~��W*X�a���s���d�jw��v�݅���<�>&jKN�B��&�JK8$���n��_� ���n��3��U�g�gC����0��!|!>���^��s���̓��,�0���`��b8k _�~����~��� ��NBiW�������'x�y��u�����D����?ᯗ����,�5��� \���#�ۺ ��}p>�>,� -�ys;��v�e~5�_��x���;��P�BgX���?��A6&����{~ ��u��b��?nkc��ؿ.v�-�u��-v��Z�u��u �ݲ9`���zX��8��]�;H{�_?د���}@e��p,�,��'D��\WeD�F�� b?n���R�ZP����^�M:)�9�p$q��ü�r���,/8y�\&�T����쁚� �:�_*�����'�U�:�I8i ��e���n~m߾�6[w�����X{����#Ϛ�_ؓ��?9ퟅ� �U���=<ܝookk`����򇰽>�������2���J�����k�y���N�^y����w�U���!�� vNp��`[H݆���;��[$�3�� ��3���6�� �a{-��9��-��y�.���>/�$�}�>t�΁?�� �PWo�9p8�ʊ�Hh��)�}�Пu�K$� -}�A��ޑr�r������.q��i��aV�&�:����5ϥg�|�����Ϧs� C� �!�y���OdS�:���@?%_�g�}�]\G�%w�[chPM4��G��c���b��;�R7[l>�vC�J�o!�e ;�#Ʈٝ��W�]PP�[:�sD��f�W���r��p,x��<�H7�^���.��}r�q�����$��y ߊȵѶ-�� ��]�-��p�kc4m�{�5��Z������^���U�bT�zB���r��_cP-P�*�"�'d�_/F����w�?��&~��Y��b�z�c�� �E��V��Z�3B�f��|&e�I�Gǂ�`�x�0n+Ń*~������qFÌ�Y�bD��$C���ڨn�B�F���+��x��HGP}���MGr���h׈��v¿��K�=-��h4��e��!��.qE�%_<��e�j�5���^�^���>�^� �})�3�r�ݮxW:�k�F��L������t�]�l�Y��[�&����8�A{S{Z��5���J�"��KЁ�g�+m�V����9�-��t[��2��5HP�v��)���)�Y��|���%��� -�׻�b��B@��B=���zrXz1�_ ��px={>���/f*��Ӣ�/D�C����!� Ǖ��S���I�C�>�j���8-{UF4R�K��&E�k�~_#d�f�]_.{���Rt m��~��é��;��nK4�v&�<�D,e���C��L���q�>�Ti�hǃ�J� ��k�`��Q"�<���k��~B֡y��"sj)�v���ݣ�Z�� � B��-���$Cڝ��]+��hz�t2�SS��h=]���&ۭ�YQ�D��5� +l"�����ϥRt���&��ANQ��*$c�����J�6������js2Ia�&�?0�ux!] ]���T.��f��/����+��� � T���/J�[�g*�st�:m�ޒ��9&ar�o�_d���3$솉�^nF��d`��$�ZNrQ�Q-�$T��_��\��D������V -����;wM���ܾ������6���oqz��x�5`���}%o��vv7eV]�d�•��� '�\�*�/9�����Xl������7:�c��p��g�M�Kl��Շ�SE��Hg=J�C�n� =��� -?������J��|1�w�{���R�b�� ��v�ܾ�O{��P<�E}����>��Zd��U��� * ��O�Su$���$9$x�\E�8�A'b, ����D5��4ټ�@�"�k -|T�H�;�D5#�!�>�Q��o)b�DJ�IoڨK�1{Boq�Wa4�-����%� t$�p� ;▮o��z�|fxx�_C�s����ޅWt:��fȎ�+(��@�Ij2�Lmܡm�3��C x����&�:����=ȓ �e�J#`1��$�o�8���hY!ˌI`?TsJ� (��:Ry�=O=���ހ��� �u11���#L"�<�����r��Z�J-��Pff�.;�F� �02\�!�37G�,̡�Fd��/�2t\m%�X���@Ԕr�.���f��c6��d�XB�g�Υ �,V�1��k\��ZH��6KHg2鍚&KS4ə��ޢ�,͑��kר ����(��ZEP&�T5�I�DZ���j<}�8����"%��e-��n�fj��sa���)F�h��jo��(�&��gl�c_��`w%,m�Y���֩��ͨ�=��N��WA60N؛u��.����w��q�!�0���i�P�\�:��8�\m�w�1�0�%f!�����ǧ�F��t:�q/�(-��ɴ�%8V��'{\�f�m�'��Z<�[�Ho!��� �΁9��F�\��90Wk�99t:f ����+�Q:�@U�Mt L�_ ��!9 :U�{���/�T8���� V�C�#�X������C����=�f����5��w�ڑZ��7=�7��Ȕ���n�;��EoK��j�����dW]8޿m��"�Cљ���}�\gqI�s�x������M�l�\����Y ޖNg6N�>?�K�$���*�lH����nm��8O�et.p�$R�3����z�kI�� �}=�/�KNOSaj`���RU "-b��;���{�uS���B�?��A�)`��u��`�ifU�Q�)`������]]��c#�ˁA�%<��9�v�#���������������z|��+��g]ᩞ($�C�%kv$� -�f��������f�oۯ��]�=�tE�/��B�x��8������h����w�`|�G�`��z, -:B�9r YGuP�{L��F�Nz��裄���W��)�_8u ����e��+�[��w[���賭h���R��m7��k��������Ù��2���;�><=�޴�XJ�`��V�g3'���KI-iP���e�!�e*>o��UmUK�֯<�����ײ�>>��I$�+���S���F"Q��C܈L\��2��C]������h�41A�z��C{�n.�Ru��:�+�~:1Rp�R�>O�Tz��P{w��}KƆֲr�;w��M%h��A"�*�%�8��P���X=��|N��h��X��і���bE;^\�� l(�,�g�y�����D:li� -Uph{_ώ�PԞ7��ڸ�l��%&������J����"��@��t��������ޣ��֬����/���+��mY��=�Ӹ����#��ĩ��'�ߘT�U�[�ߤ�2Y�Mk����S������ ��s��<�0'��\qZ����UU��X F�|�EV>��� ��S8�>!��?9�(F�kr��]>+Ǜ��z%�#�o�0� onm���M����kW��ڃ������1�Y�F�Y�7��V_��������X,_�&��'j�����"�� dģ����mf>襗N��7��ǃ�>[R�m%$�"9��� �@}�ޛ���V_=zs��,� }�g���.���YC�K�:֞i�* -t�<}*�*!��;�G�'�ƒI�V� -#n&�؍&�"(Q@�I^]������_��L�) �t����xɄ�/���f������Wz�h;l -��b,��z��&��=_��(h� ������q1@�U�������M��v����&XV�JSۢ�RR ��������}<�П{�Թ�x2���h��N�{��]_�Ԭ"hטୡ� �wt!�Y �� ���J�T�\2�z�h~�@dC��ϖ�5񷈜Eu��P>��Q< �9f�䎭=� "P� {ה�k����5��+F�K��ӳ}(��ӳs8<�o��={6n�Ge�IeC��F*�cZ� w�y��CX-L/){<��鍫���V�rm�3�� -��|��mw�H�&&)�M��gh"e�C��A���6>��@��B"ꠌ�\.i$�� SJ�q�Y��x���UK�2z�J�N��oAT�|���=��І6��m����_&�V(���X.Y]���5*�8u����kD���b^��`/��zY�~.1Vt���]s��U��@zk��� ͫss@�����ġ��<�wN�g�s��LZ��Tl���8dGة��"��`3`A�"�A��]��J�����f�G���L�l�m�+a���h�30@;K����ɀ�n�(�v_��-�La[�� <~����K���Q G1��:�)�Se#g=ĵ)�� �vxrh����p~ 9�D�M��V�V�4J�d"���3��f��ɉ�gF�K9)�a��4���V��t�۩ΠV�L�L�;��J�_hоqh��Y)� Vđ�± ���t���V �[cQ��~J�7S�R��B++�\˰7����ܚ� ["(�� ��]]�ń�Q-�~*�l�y-�"�* !�Brݑ�R��4��GG�MR�ZY^rΖeJ�\�P)� ������@-|����A[<�_����ǎ_�h -��������D��(˱ �G�r��Oj��do2Q�@#dg2y�4w޼n���,m24 ������Mf���4���B]�b �.����2c�j�/ch-�;�<)/�m7.%�Ю��j��=��������W��U�����_S��W��H�?b]�ϭK�������I�����Q$j�@T�3���R�*?����\���) ��7ذ�)^����T�_���*�wyx� -~����w��x�{<�Gv5�1�~�W��o���������*���_Po��U���-B�/I�%U� �K��C<��*x'?V�����ψ����I���m�ML��Q3k1����JK�p5�ljH�"��P�F�~� �߅z����G��P%���V��ެ�~-;�����So1�թ�)��r2a {� 7ވ�u��bQ��t��ԝ�5�C�_r��a�_�N�%��=��1c�"���yb{��� %�shxﮁ�+Y!�X�/��4�D�����J̉�_���闿���W�O����c3���������4�H�!���I�w��8�C��u ��ka�P���z\c* ~�ВI�=6�W�MR�6ڿ �Pm4�\��r�K��$J�X>?�����O'F �T��zs;xo�ѣW�`�B�N,��*�S�Lj,Jj�ίB�)�������v�s#_�?��+@�� �=Lǧ)Ø��W�_��U�xx �_����z��y�W�'���w���p�??� ��[�Z���x��g��'���/��p�U�Y?�� U�yx� -�=���?��[88���q?�F��H���������t���<<*j�X��^D��狮���~�O,� ��*<9�E�8|�� �� ��mXT����A�$�W��*';D�T���(��~���R9 r�Pn6��xV��v�/�It>�֭W,�4��>=��2Z, -�IV��������}��QA��������*�S<�����o��O�?����������"��1_|����ke/�6���4�=1���#���1J ^�4�&X������w� -���r��Pi��eQa�J-�e�Ԣ�x�Z��[H��ox,��c�y�oxn=C�V��{<�?&u�ؕ�OcV�KM��L�ؓD�\X� 鸆��l"�;�t��@���)D�O�HF_ \.�S3���ɿ��b���_�r��.[���5hކo�b�|���p��@z4�����[0����)�� �w���)���X�=KuS?� ����)y�8��8�qo�z�<��^��}˸g��I���~�yx�9�eLb>�����&K}��2F��Sq�#��T�E[V���֙ M -ʉD�)L�U�`�Lx^?K�n�+��>�i�� �=7��B�7�N�u9_ρ�މ�1G�� ����|���5���0���w����s?��y�>W��7\z.�?��(>��s@J�m ���#p����^��+�/�h"C�c�'�0z=�&� (�nE�-O�x _� �w\�p��4�C��O��V��37��]剫�������}���Ma��@��G?���p��<�;�����?�v�E�I�� �{�����*�wyx���q�~�_�o��W]/�#1��#��Ex�H����*x� -�W���ãU�<�P���ǫ����U�Wy��W摏���@��\�/���zϋT�?� -n��T�[y�EU�p��px�Z��b=����`�ps���ꭽ��+�?�jN ݫ�>��_�F��y��:@���u1� ��ED� g�F$F���-"3�l�i vڱ�9=�i ��}k�k�S�|՝c�l��J}��q9;��]���X �'ή���pK��$�����@ـ��g�O�$Y �^:C��s�� - �|o���A�TI����:� 1z� �_6���'�P�%޲n����`?8F���^H�������4ʰ�0�|�5���c�t��B�$�꡴��]ґ�V�{����T�m�f@kW��ak��9�w�z ��.�}�隹-�{��o��R��<�"�P8�z�p��L��92�$/3��� i�9S���䔦����r�^^����LV-��8S GI �z_��ȶnn�d��~}��Z@͓��e��xY���+x��"��X�~���.�?��[����N׆_`���6�x]B �]�n -�k7j���UU�� �d����5*���W�_��<܍�qx� -�M��B�4�P8j�)�·���>$x��J�_.} �1�7��C���ASМn�H��PO�7��{x��Ш'[��4����U�)���C����C�5��eʞ��]��:Й���.�{Ff�m�ű��sf�,�NU��b1��z��(O��&&*�#鉢k���X����>�=$D�)��i�4�"˗)�I^���i�W����_a�:!O��1������Įѹ������~J-�o�qc�sB1�C8����kh�����|jh�S�_!��~��W�� -�x� -�W���ãU�<�P���ǫ����*�)�*���;�P}G|��<�S �Xy�9U�<��*�R~x�����<�?��9�|@�y�����x�1��Q��ż��H,�)A*��B���نB�f�Q!vs�E[� ���x�%�G|g$��4��i&���lŕ� w(�c-�fC���o �/e���3a���KEy��FG��B�������p�6x��[��H�es�"W̭�W��6V�@\��|�B���{��o��R��<\��<��ƞ쯢��=��J|�Bx� -�W�O���ϓ�B�����b�.�)ܟ������VIk�RI���}:ԇ��+8�~�����w䓗�1���|`Q���A���3�kV>���J��Iq�8�翰na���ʕc�+W����C#;z9�w�Hh`ߖ-{�lٲ����!���}����d�8�/c�h~���H�+���б>�3[>�j�ڢ�j���큊IU�d'z� "�!��-L��~��� Oך�S��ͅZ�H.6ѽm�)i��9p^�ٿW����~H�W���Yqx���?U��-�K�Q]�����[f��)��d��'q_�=���BM��|�$Z�:V��m�=~��rv�gQ�L���*���S���޷S}���'����e�GCc����c�g��nbQV�f��ĥ��:��/��$4f-�������qR��ۡn - -ሮ��f�N EZ� -� - mT�Xͪ��U�s�ߏ�����{ִN�עk�@�?"��L:��?&N�H����8�Ɍ]���"�ݒ�#���ߘ�=��ZpL�1;M<�cH�Ƶ҄s��Z�:D��!_^ R# %~7�,W��~��e�}K������t���/����[ҁы/߹Y�{6�GJ��<���j��#��*�8�N:93Ļ���s~I����]�0I� z��bJ�� 8t(mmyMO@���~>�e�v�B�1SW�_��zB(AEx`C)�S�k$��]����e8:�+�q�����0�$������)��/F˝��������ո"d��C�v��e���k�O��h�9B����ST���V�;��x�j�J}1)�brA���� ������QD�F����>|V������F��u�����)C�Y�W�P�lВ��(F!6��1�p�W��Q,}qAxh��j�f��'��y�5W������۪�eM2u�)�����quGL���F�ѧ/߳溉������,�v�w�{E[D6��Ç+���������+��s�ilr⣙O�!XBfO�L���3���| -J>$�ϝ[��x6?:6З�lO��޵-3���l��<� -FZ��\:�j��C�|���u�� }�pG���7��w�Fڒ]�&-���ӑD;M��Pr���Yg�|���D)�v>1�߾��[�5��o��?�7D����8��8�,�ϟO�W���v��pڸ ��F���D��M�W �AN��ү\8e��3��tn�ڕ -��Q�wY�_s���s�LJ);ɲ�� -�?C�x���-:?�mb���ϡx2+ ����ζ��*��{��mM:y���`�7����d����EF \�����q\Syѹ(�[��3R~.��&�� -[���73YpN���b��פR}l w�hwF*�)>�J��}X��m�Ӝy��K��ĽEZֲ�=���+x���A��#�1�W�'����s'���F⺚�j���dE�������O=��V?.`�(�s��?��g�����R�GG -��X�A�Xb-�,8�}���%�nw��ʩ�ᡡI����`{4������'���ms=�u��y�-%D �Lv��^K<�2*���oQ�'� -����S?���p�D���ń�/@��?���dJyKO->�_��3�9,6�FX����՟���+ЇN�zk+�5�1� ���`��΅"4�k���ݼ��W�C�k m�\i[Otx���?jX��'�Y}pp[��A�U��Wm왘qX ->���s�Maf��I횃�5��}W�7�~�|>�2\\��,��\��]�z�e��33GT�n-�� zW�gg�GffTǎ�Eg���ݻi��ݛ6�E}�� x`���� �/$�X�Ϗ*໎/F&RtP����`����M�l����.�%V���C��o����L�4�x$Ҳ�׫; � ��m��� ��苟'�5���N3���$�9F�� �4��bo;ν� ���!኏�<�D�[�jl��j�U��� �f�y��܎v'�!�T��J�A{1'�� ����C�B�N�?��:���+��g���]j�jfd��\a��f}@)_�c�8�Gxp})��}����c٪q��#0����P��X6ճz��A��K\�ľ��>,1�-����`���֜�j�p�������OC�3/�1 �h��f3�׹���v��lv�_� ��\��<��,�t�0�4��h�d�dDV�ۺ�P~�ۺ�3��ZW��F��'vk�^�>_^���B������#CCmUj0/�ymx)҂?� 5�]��,�'��{rL��t �Ho��|�HG�̡ ��#�j՗�W�_�ҎM�o�5�&=B��#�� Yfp� ���s��ފVa 6ԑ V�a�%� jfz��>��)����#= Orjg�x��� 8_gL�;/�� �� ��#1iY�'����^ҿ�D�]�U�<��m���f�h����?���x�Mc]`��8$ R>��;3d'�;W:�y\������B��sf�z��澶����V�_�۶�o�[�񮰾'��FE�K(�|��|4b����'�q#���f�@�+}i����k�a��6��}C+e�'d+U��}}ۇ���}};�C�3SS3�ݛ֬���'��.��?�o5o��3�sR�}S|�`�'�%��,>�/k65g�-Y� -����|�?'��Z&�w���`���r��?>�~ʪ"�J�1�.�x�e� �־�T���?������G�>��ڳ �~w_yR�[Hx%���H�����`��~p�I�ß����Om� ݳ��mj���@I'%o���B�߃"��>_���C5i��R����(O� ۿ��pޗ�}s�>�4�"r���sgA����uSg༻dP���b���=��w���b�TӇ��o�)i�K����1���ST���*L�5�:��SO�$�����z�D�����eA=_)j�}�گ.�G��U��4�ͤ�&�ؑ���_+Zմ��,�r��H�Qu�G��7 !�V��3uI�j(c�U�>��-�n�U����m��<��������@t�@�~��f�T��4y���� j�WD�iX��<�ZYގ����s�ˑ�Ξ���Q>Q���*�!��.E�D�I�&��y��Ǿ�Ojf��'����InNNAB\Z��=1Q� i8~��#��/Z�_��G֬p�))�i��M����z���Ɣ7;Q��u3����}=�m��:�4do�+P�}��_����߻�p��#��Õ$��������m�,� �XAV.�M�Or=�GǩM��{�ε](���~��'�����G�6�)o�z���p�_�ie�pt���VRY��S��Y=�Зez(��0Y���_��GQ�Q���OA�م��-J��چW�NxO\��$%���a1��̂�B3~R�����Z%�qED��ޔUtFC �.u�)�s4l �&t��`1uy��P��E��J�����TY�E�=p7{:��L�ԓ�ł^Mh������tfy�&����s�g��*nj:�xԘvƲpn���P���O ���D�Pj�:�� -�b�۩���u��v��S���|K_/>�u���)���8���[�:k����q��suU�a�r�dM揜k���O���_���[�:��;j�����\��h϶�@�겦C+�Ѿ�5�9�5Ȗ�M ����5��cm�����$+�}�@��l�Ӝ�!{��{�*��IFM����TuNJ�+��\Q�H1ۣ��S]!]K*�m����ᾌ�-]qGTIw��F���Sm��q�-� -�yo�S'i�/Z#F�S�61�w�j��,�B��h�|����N� ���V�%���2��Tƍ�qC��?nX����@�lT_[Õ�{�}����э<'��t;Θ�Ӻ&ε޹'#w�)��[9��F���XF�եq_\���Wߛ�$lq=�Xe�ق}9����,Z�8 -m:�m~���fc�+��OY����~�(�gd�{*RU��Zj��gT!�$�mL��=����=�ϻ܅� 8��k���@��Xt�ٕ�*O���%���>k��l�-kw�/�$�=m�L��?>L�;�Sߋ�I��^��.V��6����P{��'pv�'�������`�}�� '��k ��a+�g�o�i"�I��N��1�t��!aW7ɯ�W�5W��D?J��U�5�5>\p<�/gOu��j,N�1��9Z�e.O!͛���|�5����T�p�mO��RcG̓�:Ma��A�d����B� ��=��M>��o��o75��:Z�k,ң�I�Dk~�����vZT��YOc��?��hY�����pn�qj�E� -�ܨ5 Y�U�ZY�hh|-*����|ֵ����RL�?�,-:�JK�]~�J��ͽS���B}X9����S�]�`,�#�.^/��e��L�ς���C��-j�������Ea�x�eo�� �$@E�Y��0�~X��*8��&��/�[X?v��2�3�1'��-Q���� -�/�� �cQ����⹬+4��I������.��1"�hbɦ�ڱ�֖c�ڱ5�c6A��X�QJ$/�N���<��Q-�u̽�F��e���`5�׆=Nj��#xX�<�n��]����=���/g���lv;��z��h��ׁs�]�F�qRu�c�����KQ���gp$�:��6���٫����_7=h`�;} ͞��\���#��=�º���0���_����_ɜ��&l�u� 7>�Ժ�}�A�o��0бΰ��N��̩�H �3���{��;3�̩.����epM����Y�x��A����54K�D ͆� -h�� ���j�I]h6�ա��`.����ٜR�В�{���ׇA3�5���i��Y� ����xfg[��賵�:wfQo{ �� U��v_ԟ� F�Bj�����`wx��� -�K���U��!��a�*��[��xX\z�u�< �ۃaC<,�[����n�FxX -†�<��K����2�|�����@8� G�Y�����>{�����SSK�LM-�����-� -J'/C�3kUAh&�}�W���(��M�B"+��O/��u5d��vh�l܎���Ѓ���Z3��qyS!5��F��.l�v��#E�ݡX_)c'}ր?���,[�e�lD���8�Z��P��l*N�b?'I���ڐ\(�J�F��>��$�-o��-_�x�Q]�����ưۗ7�������@,�v�F�+�vDl��Z��8-�8��2H�F�T�3H���`Q�=I��4�q����H�c�2�^��/0Q��P�|����O�J[v�N���}���n��~&ː�Ԛ�7�Z���j i���|���˜%a���t��z���Ѥ��와���Ɇ�i���x�&y�Ο�[FR?�M0 ��m*���f~��Kh� �I����x�q 5�8�_�������ή��ݲD���ȸ��p��Ȳ����%�^�jT�LD�Z�٠1� -�k{;�C��SC�?��A��\�_dH�!uL�u&��]0�l��W.[+S�+���h���p��.��>J8EY�� '����Ӓ:��2铼��f"�~��l�&��OKИrٽ_�Vmқ�S�Ea6��`�ʛ�9��R\�sG͡�N��y,a����9�z�T� Xcn�ot!������߈8�V�Z����X�Eh_��x{3պ>��������� H���=�p �� ��P�1 y��fJ���?�F��V4���!���!'l�ǻo�A(�_:�>xk"�J��>_xi�e+f/�2w\�?pI'�?�?��__\�A�fb��Rs�%��t��.�]q���s��MP��q���ƹx=Xc��Q@��n2��f��������� �P��5�0“��6�Ӻ����4؜-GBMQ'0�L�P05H�_�����O#�}�߲7Pݧ��>��_7��н=6����e����0l��� l�;��Z�亱���s�yX��M�>�a~��!�}��c�c��,�}�P݇�|��P��Bu �c �"��Q�����?�����)�rd��޶Oҁ�̡C���3�k���� D>a/���BÕ߃*�.�ʞ6���y���oa�y� ��Y�R��9�<6A���R����O⚉ rK�Q1|n鶫|�1s8�-ؒ]��ݥt.�;V�h/�Ah�G]�|��'�Y���pAuW�$�A��^H�ݒ�W -B&C"tT�YN����V{ � pG�.c@�w��t�3ƀ�C)� -��^�9�3��Z��j����BH%\�t}BBKQ�IB ��������g�����@�o�����F{����f�h�"1.%Y�%�O��e!�\`���o����n*�9��k�<�0Dt5��i੻�ƍ\[�������i�A�7N��әf��eb����'��ncP���N�Y��57Z��g�����ՙ�M-[�j:;������BL�����2C"j$��4w����鞞�Qoz���gx�0��H`K4�&��ӵ��k+��uz��Fx��.4�ȳ'�*��A�Mt�����Q{��X�ﱙcY�|��2 cq ��"^� ��!�8AjY�5��Ck\�W�����|t��(B�;߹���f��Q�ϐa��YJ�����8��"�� ��Fr'e����2�ۍ�bG#����8 ���uQ��-K\�ml�#q/됳��r�t�e���V�|���5׈k������&�s�9�c��=�-��ז�]S[���Ci +k��P�e���_��\Yk|�������6�ר��E$9�' W�O֫�,�V?��8�-C��0z�� �� �c����mO�L�ٹ�a��ڵSK70��؅���s2��17+ �����Iy��Zk�#89��TB=k ���{��2�C�܁� ��� -�F����*�6Z��$v �O�o{���$�>��]|�gƀJJs��Ҵ4�U�|q�2��eomOɂ�S����6�$+E*��AH��ܧ�J�#ɂRN(ZC��T��n{��-n)$���ޑoF�ive�-]�=��eQ���1���Ia�X�9����s�(6�s��Y9[P�&��͒Z���K�!k�/��zz�^��.x�CV�)� Ě�6UO<� ��6G��q6;G�j�{�nkDo�:5&�J���R�12't�+��N -ȩ_d����2Q]훎��+����w��Za_߅綅� ��Pk��Ec��a����~�#a���C&SС��;�1cj���Sἧ�ٓ��Gq4eDq9�����@$���U�0C�k�}o�P�^%Q�z�E���p��3N�6��X���c��=q�$��X�N����8aM��a D��^חP���Z��ڡ�(Yy�"ٹv�G�(ceM�.���(�5�s�ﶬ ��w��^4��P�je�7���u �y��?���ru������Z��FI�ZY8�ҡN��IҠWv��~���4.�ѥ9��u^�ѫ;�����u��E�� ̲��\�#Â*G�_�_n26I�L�žcົA�yִ͖��Wy��Qt��'�N�d���c -�[�\q������1�$or��[���O�u!���|�1�z0����C �q�Y�IDƚs�I�F��q�"ʢ�Wf �EZ�El��'Uڴ����b�t�[RcKs$�� ���D��䍙��p�WCzG.�Bqڭ(g,�o�d��^f���[��x���1TX1Z��H�q��qN��"�F�q_�����g-Y�T��XrΞK�%�E⑄1'�� ^>�n������,���Bg�Nrp!�����R&S� 1��4��?2�ʖh�r��i��K �_m����Uˣ= ��9�R� }ڦ�Ƃe9�]0�|�&��3A�]2(��I��9�S���$XE�G�;+����D�{�(�nQH��e �} ��Ά;�����J2��Ai�q��+�كqtBK�.Ț���=���"� ����_�&��� -�PQ$O2����m�X��b�e�/iMY�:����=J���z�9���$��d�ڨnj)�UQ��i�߮��e���I�Y��-DA� ��L'����2���$��el��qt�ԛJe ^k�B�2���f�x��1�J}�t���;0n�o`�$��0S�j:r�:��#p�D�qNQ�J��`�B0$N�E����Bґ^�A�Z�NFs�' >��@�Ӵ�i N�l�V"�}��s<ݬ5�uvc ewZ�w76|)[�M�l�z�;Z-��#q���P��s��&O�o�x�)U� w��9�r$\چl�Wo��4R�\פՆ▨G|�/��" Y�Z 1�m"� -��}�� xn)2�gT��Cn>-�Q�� �B����mMw[¡ l��KL���o>Nf~��3���d �5c���:C�597cE�f�V��Z�%�f��b��̕�A_�+X����slO�+� ���UiINa3��v�gYt��f��Ѣ펫�3�!��$� l��Y�b���9MB;GE�Y�9� ?]YZ��¨�R��"������CZ�*/�q��7 ,��p��>C��)�q �J� �|;@��ܠ�[���e --8���*�MlF"�p����ǧ��T���\���0�����T�j�~-ao$�TN��.��������G!�܇B�Lj��,������! �� Yg�̞Nkp� q���s��;��G[p|����j�Q2D7q"�H̉9��TƬ -g�cyNd9�ӿl}�S�[�qtԉ����x<��,��h�'i6'{���%�W(��� -}�� ��h������R2��8s�H@��P�TC 4i����X�����ʢ5����Hޅ�W�9g�N�k,߄�Y_ ����k,Z���a�����F� U2��iP�<�b�9G'��T��ъ�H d}RY��H%���w� -��B�׆�t����5K��IK�[)>�[��.�{x9����u���'��EN3"!��#��� >UH�E�J%�+MId�����[Y�'�M�N -�~��Y381���L�J��˽`Xa��TmVYP$���`��m�8��S�w�ڋ��ѣ�Y�Ѯ���^p�f��v���$ךݦpɬ���6M�V�(�kLs��h��$Ѫ�]�aR�A '�R-WB�N-� �)�6cϙ���QLd[��իs&m���u�ܓe׭k��[��)�~�]�<��n��]=o�L����i�i���8Q,=֖nm��%��t#�.�cC��?N@9R� -���^i��o`����@~w���bR4���G��=RO{��l��^lM�߃m���3iS�v�h�ZW�\�o���ȡ�{��& Rs���,5%:VM$L�)�p�ܺu�C��W>�~�'x�,q�1��8�(5��hL�9��ɪT�ͥ��6��it�R��L�5�\�0fʽ�c�#Կu�`" ���u��ڟ���q�P�<��2��Z�w4���Gv-5���^9E�׈�v�����L75�蛛���ű�h�J�Y[�:_�d�y�e=�2�mִ�S���^L#R 1/��-�2P�y��ޛ~r�qp���-�ߵ�c��%�ẁb�h��K��›�U-��Hѝ�2z5 Op3�I��4N -/E��P;`�7�>�FY�F�جW�U���DOg8�hl�'4 �O�7h� ���tj�f�0���J��!�������e 8 �T�b ��+3 ��w�b&����F!Qh�����XX�l�45��� X����9d����@�H� o��j �^p�+@I�tJ���L+HC�B��z�L3ʜM�3$y�P�$:�A�3R�H�٢:��x'��=�?z~e�;�V�c���2�����[�w��ъ��pg�=�[t"i��k(�,�J�KOEm|����=8��{H�������fRֈ��CӖh��H>ǜɒ 2H���zy�;a�es�6F����櫖����i%w�{��5 �4�5`:�9 �H9����T��P���LG����S�J@o+�C���g� �&Y�7M���B ,D��~hpQ�ح��i%����$�A�jT<<+�.���[,9K"Ś�T�k�J乩Nc�d����ݠK�չpsRo]5�� -�+��}�d�/��������A���m����B76��d�p|�4�/3�Q H�V�]4q8Y�P#}Ǹ���D�������؝4���\8ba[�irM;��m5���ȶ�b��v�q��^�ױL ����ny ��h�FKz�哶T�h �lܻ/���_�˸��^�ϸ]�>����P ��kgvA�'��(�KBڤ�P�e�Z���\a�2�tV��ڙ\aQD�D�|�{�h��'{u��79��q�[�}:�Qח���z3� ���MX�I��ܕ곴���pȠN�[:�vK_j�`[ ��۝ �wr6�F�0�[ha�zA�y�iN�7�}@j��}��_�"��O`r�t��������Ϣ�)gR��OK��z�q �A?ʮ���Kҭ�X�Xwligv��tc�̌��&K�ڜ��L��'{rkwl��]9��� ���Pit}���I�5�Z8H�8�3+��gV4����Sl���d��Q�܏3�X�WO/�~1�;���/K%��:Gn0^��g Y�M��Y�� l-�Mmj�� �vy�Y{›��� -�o�ڡ){�#D7�ೳGs6�7���^ߐF�W>C�,/l�RhI��'T�AN����S��A�(�W��j�z�����>�rJ��Ju.���]���'>q{�/z�� ��*Od���W���,��@~�j%�4,�X�����i�� \�{��7}Z��}�Y��G�s��e޳ Hwj -�VME. x���d+#���GS�>����И��e�5]�P,7��A-I�΄�9�|̌�]��hʺ�e���D�a�D���?�xܙ�ݭ���Z�} ӛ��ގ\��v����`d8�0�ܺ�苃��8:����f�\g::�h�M�4��92�萂������Ols$����C�b=_fP�3 `�CakQH 5$yr���h����"���% �Fz@��X��5:�Y�� ��\#����X9�6k�������J�={�|d�R��|�m��LT� 2�ow�_И�1��XΏe��� In:h1A�Zc�Nv��RE��*J8A"�p�������)X��ւ<���p�ۡen��x���T�\bn3�3V[�i -9��s���[���36�yƟH�*3 �����l�{��O�r�d?�e7�r�ia.:��T��к�DwA� -Ds^M���Ѵ�Ts�5���:�%��$�߄�9�� P�N5�y݂Tf�:V�o�<�~�i���c�xΒ��tC����|�{��>pAP�����Sj���`�=~�5 Ze���=�D�����y��Ƙ}�a�z���Q$U����I��"T�CvN�n�q��(�{ Un�Qr�Xs�:��If���.O�:����%��$�+��r��Fb���[f���-*wl�&=i�/4�����5�����m��3�S�׻AOu�n�Y��L���q�Xn^Z���n�=k�I�4�S��*�h�����lr����'͊]�ʩ��6l|d�sVo&�����PwʪSo~�1iz��[��\��B������!!3z�"h�z�Y�=�M���WΘ�{uYC��G%_��D���+���Y��vTo䲁"�H����75��z�ߝ[����-k)�Gl9}��ʚ���4�;x]3ۨ}ϩ_l��f�><[Pn�$Iz2V���eW2��o�c=ʠ�����3��lTBoh ����1���d'��_EG���z4�-sB8�݇�Y{p�2�i8kQX���c�@dtdtF���;�������� -+�e�}�o|���Τ,�i����*M���9~��6샟�v� �BR̯h�eWl���tt4t�)���A����I�_�`"����z�o�.��7��aEq�e*�����B���,�]YX�[`�uE�&��k������m��`_,��]#c��IQ_�\.�f�[�;=흁�Ό�o�O��1ׯ���>���{r�88 -_����k�84:T���R���3���tǪ���濊� -$�$��(H�ċ�;��3�8x5)�V����zt|�j"��h�M�q1�TY��j����s�dsB�J�Y��T ��NT�)����̼+�����X��=��wt��Q�'���t���p�ܕ�$�j�c=�f�M��])��B"�s� -����Z+7��{O�fpiz+�?^�;:�)1#{�������"y?@Y���Ͽ)}~���D�]�-L�2�[�HWw8k�� �G"&>r���7f��MBA3��Q�� ���}�ѐ�EN/�E�E��`z�.8� ~�p��S�h@c�����5�|�j8.��o����20�+:.�� ��2xl�5_p�0�fv{�cK����ď�g6C\�TF��G�R1j����S��Ź����]��k�x<������jq�-V7��}"��8@Kϻ��\�"`�0?�$���lX� -�N���͔�� �h�51$�]� d0��V6�ŐIL^�̀�_�ꪗ�����T��e��ӆ���h��Zc(f@Qf�������#��3d����7 '�ŅTH�W$A�;W_��~�㷎�%�ɱ����'�PC��5�� ��Fn�| ��f��rm -n��Y�8Ś��D�>aп��/}���_�~�R�4�t'x���o�]׹3��7���[�n��I[��}WT�>��w?@Ǧ�<�+`��� P��z�O����M.U@��`4~��� ��p*�-d+8���� "d�X ������7�˛/�|s멧�2I�`��y�Bˍ�o�G����}�������� �ѻR���A�)v�;G�Z�#��dK1%k�^N����V��\�r;��[D�@|�2��4HAC��A�������চIȓ$�-9J)�)"P�QDt��X5Әy���lh+oğ��1$�٥<>2��$��20��ij�q��g@�A��+��{���(�:�e ��_Q;��_l��_��?�������d����T�D����:%��@�h�@��=��V��d ,x���>� ���Ez�o�a�F�o�<}G��(7�qMTTu�Qy�������?���ٛ��ͳC�mio�r��Х[�۷^:T�0��l(�7���'��޶u��vt���歷�mk�n�z�ʫ6�r�Z��� �-ᡭ�[���k�P��TMd��F�//�Ѵ$�a٧S�|:_���i�^�ӧVv��k=��O��a��a;��f�*�Y��MLyp�\��J�o?��߄k�<�24_"�>��S���+��v�R<�����)��ď!�uI���R����V9OZ9����e⮽�D�zn���\�n��6��^v�2��- ��~��Ҁ��B���(�ė��b�~VRf������rLr����9�)��nn?u9�����q�@�#�\8�"�̇��~��/0Qɸ �z,�[8N�e0��m+���N]�^�?cK����9��a_a?���nL9�fD���p�]�T��+���o���n�J�ڮ$$q@��-�+.L`ƅ�IaN���7�~���֏ > -m �:��N����R��J�5 V>�޶���X��{�{z��G{������i�O����r��S�>څi������G�����gŇ�c����E�ڼ���qp��8s�P���5�}c{K����y�]���_`�xl��[��[�~M�wK�1Mq�2�\���F��E��X �����P��:��k8l�� -kk�h ��t&� Y��1Gv�P�뉛�[��^�v�w��ҧ'<ꭗ\m%�{AL< J��w���d���wO�}wD����-���k�`�r�r|���^s�U�\��? �deB�^$� e�L��k��7��R�< ΂׆���m��6��� E��>w��)�5��S����z+Gm��Ǚ��Gp]���,�$�Ρ�u:6�|�]�b3�B�0 ���ƙ�v�F$�����ݎa~v�m�fa!��~ a�tEhS�g��` C'*��a9k��Wɽrv в7�{c�u��2��T�s� -�������݃�3��_���p�����lx��6l#��t ;�D��yv-|�M�Y<�0��c_������K�?@X��}�����!���}�C����¼�e�b�(��p����Qa�� ���L��#�z����c����9�B,;�4�<܉�Dx�q�� -�P�j��ٟ��OWR����DB�J-�s�VKjv���� �@?v�M�O����5 �����= �7�������s�h�{��9�C��9�)8:�E��� �����"�ݫ6�Z�!��M�&�� �kLF-}_K��;|_K�}�V�Lj�Qߨ15j�1A�o1��F�^g�g���?�q�Q��+/���'�Ԡ3���Oe�tz���|2Vv�������˵�;Ch�87Z�ܨ�_jƅ��1�G���v�Z��s�(S�l�s�A|�����G�/C��ͦ����Ћ!u�Ѕx�u`E�MM�,�������ݢ�����݃�a�?�����{t��g�G��{~ �ل�A�P��0�/�b�m{V���`~��܆䕐%���j&�o$pY:G ∔��E$�������y�����}���sP{{�ǘ�$�u�3�s{7�Y�'�_��}�s�6��f�>�~(�8�e � '�E-���I��k��`� &u��.��̖A�5dG��f����{�ɀ��4�ORW.h�tZ�~�yP2Ց�x�~�������8dO8�>��`��JJ)�9��ٯ�f�W������}���k�wC�Lƒ��!/�L��욶6xͅ쏙�$��x��*��8_;\7h�p���>� �=�����`>e� �:��}�I�K ��C����_7��mؼDcY!�5�+|�� ;",��s#�H}��k/��q���f��lr:�/��N�L&���m����7�[$)�������� ?��x�u���o�f -��>�C\�u}t�X�"��U���wF64n�D�Ψ�dLHKl&e�XԤ��R��:�Π݆~�ueO�FCD�eca�n3�эl��u0�\��Zr��,��c��#�^��[���84�|.���t��| ��-���t��r9�^F��hv���r�x.��N�݉^��;�� �/Jp�U��24�,��ʎ|zU;�j:���=f��|K���I�Y���M��X;׷�9�ؗo��_ ]�����rr��v-&�b�q�9���a�1����h���{O�uoI�� �o+�W����s����ρm�� B��o l&hzi�M��l��� =� 7S:���L уb��sM�ok_�g{�J��n�q|�6Z MwR�֝k�kpǞ�������yo1�C׍V�n|�s �JwR�d���L�-��-�Q�b�]�r�ɮ���z����N��L��>G�� 6�P��t��V���]��B�Y@�.���� __ �r��g�<�����"7����-[n��ώys�� �: �����y;��G!,a?�0#�}�z!�$������!�({��,�w���"�v�\�|��,!�U.�8> eA?/ �zGJ^HhŒb �D���R�B ��G�WI�\B�����׮߼�z��^=���~��!T����E������7��ODg MWϛ�{���~� -We"Ѹ� C����.��6FW���fz:Sl��'�Q4��+h|�5ӽٴ=�d�{:�uX,���j��lj���ڇ���x�`ǣ���l��e_�����T�@��"�^y_���M�7?�ߗGcG�{�.a?+gg��Q��C����œ2��In�< ��z�T���hx6�J7��tj��pu���a�u�EZKiVxO'":�*�L[w�v�e���n~m߾��p����E�d2��C�$�~�$/��,�H�ϑ�fi**�v��T�S,b�(ӹGi���4[�����Gv��;z�^h�$y�M9�B�����Ӂ��J2/���f,�K�EHm*�M�b��e����྽�B�l��M"�C"�����z@rD�G4�?xeN�Y�߆/��v=����b0Xį��0ܳ�=��gN7�s��֌�����n,$�c!� ���C��! �Hy1d¡�4E�EJ�ܾ/x�*� -a��qPe�\E]b�c ������1�����a����k�g��@H�w�q�U���2�b+���n-9цTr����H�)����A'љIU�Z�:�,��V6��N��$.��Y8��E���b��� kGS�%OC�~r��k~FD�n�������$�Џg2!���u��u}��7�/ ���B�$��'km�:�[A0�ͷ]�ݷ���3pd�cb±��I�}���o$:���0(9]�f�4G)A������>�ݵK�cUn��_��?jy��n�R�1�ǒw�6���BqR��q_i%S�hjo�o��� �Ƚ%�>f_��>$�n��� �W�pn���q�@���%pN��l� b�S��)�u9y!�\Ǧ����^�^��ms�q���Ă5�S_B -�,�>�*���5�u�(�B����G����Z�������iP1���(��uuV��c�ȹޞ�d��� ]����L�5��8�" 8���$q�`�[���]c9Ir�S�M'ɉ7tf�#Ϻ��#��R_��w9��4!� I�z�h"�Ҭm6���<~�����/�}��3�Ͽ�(G���`��w�5z�$%� ��j��&��Y)j�Z����qHz���a BC���Kk4���D����EV6���D��la�F��;��.��;�����������c+���b�Iz�\����H��ڡo*�M�)2�l*��g��K�{$�o+vl����U�/��P��; -����������[����~#�ɲ�߱��mu�������^�������T[A0�b�7�9�y�aY@ -����]�:gPY�$�Y��F�F�Qu�y8�@�3>A��ctm'kEEj�x�,�(G.K��� );��6���l^��18 M Y��?�w�����7k�Z:��NB#K�D� Q.D?t����n�՟ѡB@�f��L&-Ͱ�@�!i �ɝ��3�a�� J["aS���a$�L&)y�s��U(�>�q�P������7�$iY���%�@�󋉢���a���C� �u�SY$ �w �����3� ��b���� -�I$&��8o�l���,e=)�V�G��<,�zRl�uf��=��{t�f��ܨ��5t}y �/��k\��p%]㐖��B(,�6�֏��m������,�L��[?��Cc�a���(í�G-��>ᬷ��ST&�cGޫ�o�F��ȶܓx���{������d��dmCz����J�ޕ}5��$3̅tP�e���͜�@�$ -��k�������>�� wH���|b|�퓪�^s,���Y���.|�F�����i��yHX�j�;�'<�p�Dx��Z�1�w�ma�'�0�P.rs��$�%�X��A�ϡ��.���\����q�{:!�U��bs�E���]l�ը�Բ���� �� s�;����*���U�zW��1&��9����l_Xf6F�y6��Q�� ��"�~�q?���g=����g��@�grca�c��o!��/���YӋZ�U�����ފ�v��Կ�'������r{��, � �2F��~,(�x��7��"x ��a�[���A���\���,��R�e��~�B:|�~�1�̗�g9�L���f�����Y{��F�v���~n7���g5c�5��e�#YA?�--��p��3����n���LZz'�,c�H_���L\6@?+�'d��g%S�����*��ibZ������>�Y��9p������}�ے�e��o �[��m?�u�w��Gv�;�ۿ����m9�c�7��ȑ����矟ނ.Ko;�/Kz��sd�wz���΃� ��;�e���3�=�m�+i��ٲ��w١�;v��wˡ�t�%��7ؿ��\�~�?��� ��R����9��Kn_6�>d���v`�y�\:�n-u��r��Gv����ڒ.�K�B�t���9���=rh����:�{`��(�g��������o3G ޾{��!���!���?rhώ��śY��ܽ���w^Z��G.8�cv7� �t���#�o9�Qݻgێ����ݿ> !336�]zp�~r�$� �娕K��^�����-{�nٺw�-����-Gڽ����9x�p����ve�O�Fj0ھ�0���sA������7��y���736����]�bֻ�oz�ojvlhƻt�;�tjplvl��6��Z��Lzw��d�񮃇v>�tݳ���=;���3;v,�a�����ضg�m޽[��:w�.H��;��sQ$ľ=p���]��qh�����@~� [�,p�����)� 0�蹀9��av1��#�(Y���W���"�\��������y�}޷��߽����=�E��߶�_���#�z�9���Wp�����o1���!VG�]^f�z�1��ֆ��1�S��}�bs�9^������D�1�[�=�ev�� ������a[`;`�~f)�$n���ܝZp���]p�Jܛð��ު�/�ws�_����m����o9�[*�%��>��ٰ=t�N� [݊�/�T�y8jY����e���:�Gt;mt��v>�_�=��h��}+�=�o3���<}?�f��^��ݘ�^������6��'�/�4.*DZ=s�=�tBf�=�f��`:��"� TEc����7������oef ֓�})nU˓U-�y�����p�̪�+��y�s�V�wG�,܂���,ǟ�@�xP�0l��AC�p���t�_ -�\��;�h;~'�҇��;�h���Y3�����SmSd�4 Gt)�Y��r�4�;��1C�ޥ�2q)��;��g��0��)f |����kP�;(}Ȉ�`�{��a��!܏}���v���iǿ4�^H�U�q߳ ^�_�����-�/ኃ�}��o3�p�>�4��������|�aP�����D���3����3X7���Ƶ]j�)n�:s� a&���j���- �! �E( -�Pt�A:�N� �|�d�1f�$��R(�C�����N�U�j8�k�u�zfs?�,� �3�V� � H� ȁ(AhM@��R�:��`&H�S��� ���MH�g��y��x���@�@D@�@$���$H�4Ȁ,ȁ�Pd^d�g^�VǷ@��hm�t�N�PV�^���C`�0?�` �� 0 �0;�X -����K��m��.0 f�,XV�U`5Xւu`=�����6��` � -���`� v��`8 � ��}���~p�C�08�������2/3�� ���Bp�\��K�e�rp8��W���{�5�}/xx?�������|�>nf^��[�_1����0gAV92�^�Qp;�| ���w���'�=�^�)�ip8�����@ƪ�������,�s|<'�������k�����!t����NO��*"���`��H�.bGD��bAŎ�t+��H�"������ٹ%!���<���{sf���̙�3���\�3m����I۬mѾжj۴��rC۩��vk_j_i_k�h�j�i{�!�Ӿ'���ڏ�>�'m��3�Y;����i��#�Q���v\;��$'�S�i-G;���~#�s�y-W�]�@&��z�d��~� -K�QZl�)d* �� -�"�D+�*�*�F��4�֤�hmZ�֥���m@�F�1mB��\D>�ͨ�S+ !gh( ����@#h$���i ڒ���iږF�K�h�@?�ҏ����)��n���Mt3�B��[�6���;�.��~I��_�o��;��~O�{�t���?��z����z������=IO��4���g�o�y�����wz�^�y�ͧ��<���^��� z��Ao�?�_���e�q&�� ��ܙ�d^̛�0_��ʰ���g�Y��*�J�2�ª�j�:��YMV��fuX]V��g XCֈ5fMXSĚ1 fV�BY g,�E��k�Z�֬ kˢY �eq,�%�v�=��:�N�3�º�n�;��z�^�7����~�?{� `���!6� fCX"ʒX2Ka��p6����lKc�Y:�`c��,�e�l6��c��6�Mb����Q6�=Ʀ���46��`3�,���氹l�����I��=����3�Y�{���^dK�Kl){�-c���l[�V�W�k�u�[�ְ�l{����f���]�{�m`��G�c� ��}�6���&��ma_��l��v��l�;d_���7�[��þg?���G��������� ;����;ʎ�_�qv��d��i��ΰ��7v��g��wv�]dy��g��v��k�:��n�?�-�'���8���q�׹�ݸ;���܋{s���x^�����<�xE^�W�UxU^�W�5x ��k�ڼ������o��Ƽ oʃx3n����Cx(��<�G�(ޜ��-y+ޚ��my4��<���ގ��xGމw�]xWލw�=xOދ��}x_ޏ����A>�?���|O�CyO�)|�G�T>���i|4O�| �g�,����q|<��'�I�>�?ʧ���T�8�Ƨ�|&�ş��>�������?���b�4�?˟�����| �/�/�e�����+�*�*����������o������������ ��!��L�I9�OʓR�T$�HeR�T��O�g|#��o����ʷ��|��w���K���ÿ���=�{������O|?������?̏�����'�I~J��N���d�d�EV��|H>"�d;�A6��d YK��i�y���3�,��l#O�~��'�ċ�&m� 2��%�I"w O#[�,2�����~���K<�_�W�U^�������� ��-2���򿸍���BT0���0��p�Sx o�#|��(#ʊr�_����(*�ʢ��*��ꢆ5E-Q[�uE=Q_4 E#�X4ME�h&,"XXE�a"\D�H%����h%Z�6���1"Vĉx� ډ����(:�΢��*���)z�ޢ��+����1@<(��� 1X �b�H�"E ���*F�Q"M��"C��L�%��X1N��D1I<"&�G��*��t1C���b��#�yb�X �'�"�X,�ψg�s�y�xQ,/���e�L�"��b�X%^����b�X#֊u�M�x[�#֋w�{�}�A| >���'�S��(>��f�E|!��mb��!v�]b��R|%�߈o�wb��^� ���>��/~�/�8$�#�8&~�� qR��E�8#Ί��9q^���qQ�K"_\W�UQ ���↸)��ğ�/aӉ��Tg:ׅ������{�>������t���W�+���z��^M������z-��^G������ �z#���Do���t��[�=T���=R�қ�-��z+���Fo�G�1z���� z;���A�w�;�]��z7���C���{�}��z?����>@P�?���C�D}���'�)�0}�>BO�G��4}���g�c��L=K��������}�>ID��?�O�ӧ������ }�>KB����������}����HJ_�?�?�?�?�?�����/�_җ�/�����+d��\_���W����o��5�Z}����������^WO_ߠ�������o�?�7��-��V}��]ߡ��w��/�����o�o���=����^�G}����_�Y?������#�Q����~\?A��ωAf�����)2C?���g���o�9������_�/�y�%=_��_!��zq#;�Ur������ ����~K�S�K��� j0��Љ�H��dž\��Aj�gȳ�i0��� �*Yl���R��nx�����2� ���3�e�r��Q�0*�JFe��QըfT7j����Q˨m�1����F0�����hd46�M�/d!9H�#�9L��_� ��a1� �b�aF�aDQFs����he�6�m�h#ƈ5�x#�hg�7:�NFg�����ft7z=�^Fo�����g�70���A�`c��h 5��d#�f 7F��Hc��f�6ҍ c��id��Xc�1ޘ`L4&���G�)�c�T�qc�1ݘa�4fO��9�\c�1�X`,4�4O����g�g�����%�K�R�ec���Xa�4V�-� #ztbRfF�=43e\�16=�'���h���*Uq��#U��XǛq��?F��� 7�3�g���2�K�5�x,g�����,ឱI��IcGKK�������������%%Ҫ�j�ٙ��z�y5^u-���]a���Q�Ǩzb�� -�X�}��>6܈wb�z����v���[�z;��O�m ���XB�"Q!��5�r�Uk4o?41S���bt0���W��L�(6���:��Xώ����g�z�wNL���wv` �bxg�*b�E5�.D X��P���R��wu�24L�R U��F��Nyw16}xb���i�c��������ڌSmƩnĩZ�T�q��UU>�*����T!N� N��JūZAE�=������T�a��0U[��-,^�wy�*:�a�&��� W ��nD��]�,\M�`�B�j!<^]���t�G�|�y�'35}�w�b�S�\\��#T��5j<�Í>&I�>��W��+��^�j,z�ᙉ�R�~�@�s�{��Ԕ̔��,�,��@Qڏ�W��HP 5��PdNG��/��Fd�z�ъ��F��DW���VT�P����G�X�N��Q� ��Q�Ǩ�c"��B /Nѕz -���["<�\XH���%�2<ժ%AO1�ST�RLN�R��)�c�1��;߰O.�v�B;6�H)���$�ywAsxQN�w�t�&� GN�9�6�Z�RO��GHN�jRt�+��O�Xգ�#���]�A� -�8ϑ��<�CT?C¼F �LIIOKLONM���9����4���>E֎�U�!j�ݨ8��~��[�eo_� -��G��#B����PxD(<"T��������n�j7R��ډT�D�v"U;���HUo��7R����ꏴׯ��ډ�?Վ�VE�hu=Zi;��r�l��a��T�i�;S٬CP�[$U�@ J���Z���0(ѳ����ݕ�����A��m��`��*�HE˛sW嬱�T(\e��V��D;.��pI���K�7SJ4%�p�o�":ؑ -q���G*ʑ�q��)G�b��8�q�#�K���9�I��� �k�B�k�m�i �ב��+�B��c��*O�Bij���Ti] -?{=!���C� q�3$Fޗ���5"(���Dtژ�<&%;Q�K=:�)�.~LVj��p�����# �;�S�1�`����H���]����`��'D����#2D���Y�ıF�*�}D*���=+�l&:����H���R-�7h�A�L� �K5f^��r���a/��mt�p����*;;\vV$��e')���dW��l�j���(�jv�D:&����RA�# -,:���͆ΎU�M�?E�D��[M�S;�8X�a�ΡHq��P�:�"�u(2\�b��P�-2��(�Z��)��(�,�*6�f������`Q�-�߱*�Sq���}�>��&(�*A) �6N��_W�+m"!X��귪�V���|�*oU�����~��~��U᧌��;&(�3Ai] ʘN��V��������ẗ́8U_��/T�U����uU��?T���W�[����I�����0{�ʇ)<� �����p�;܎��'A��~���v�6���n��z��7R���T�D�˩z�혠Ԧ�8H�R�D�z�T=Q��({9UO��'JՓ��+�W�2 �,A�� ��r��KP�b�ŢbS�M�P�#�o�U�!*Uq���U��IݯLЄu=X���Q�F�z���`�o�N�jǪ���W�#����Vu��W%X��V�g���� �׫�#T9�JP�ZB��7D�b�G�W�a����(A�J �&JP�P�rV%���W�3�>_�}����U �jKP��e$������)z���U_��/L�f�_����W��+*AYa �xUO�*��h{���g��T�bS)�W�8"�=1=#;%-%TS{*()q����0a -f���m�J٥��*ʣ�c�R*?��[]W��8�oUN9>b����5��g��r�ӑ�J =U��t|�K�K�X�t�k�˽q.�X�z��jr5��UlUq��CU��pw+�V���k*�Tq���U�h�=ю{bT��8ǫ�4�B� -O��Ӫ�*<� -O����+�� -'����: V�[U�VվU�oU�[T�վE��VB-�}�jߢڷD��8(Q�(L, -��Ģ0�Ī;즙L�Ѳ(�, -;��N���Xa� �Pe��F���xzQ�����RO+��O+��b�5Hٯ�6�U��1 -�u=F�L�*������^7�T�JU��P�_ -U��P������� -�u=D���b�=_=�U�z�!j�B��/�� Qm�����/�1~!�� Q�8DQ[�z�! -��)%3#�b�Q�;�c�ǎv�HO1������)f������*�:N��J�`��J��n&S仄f��TU�jk���U���F��f;�v4��h4��h���lg�َF1�md��Ȱ���l#��F��� {�62�md8���؆�� � v7��֏ Y�,#k�e��eʬ[ª�R�b�:V�0 q�ǰ��c3S��F`���V��: NP���w��,�(G��f!�����;^�f}&K�ج'����8T�a���r�V�>���h3�1eG�RDjPv�(���c$�� N�x $h'A{ :H�Q�Nt���]%�&Aw zH�S�^����}%�'A �`�h��)S��.+�'��#(/��ά$�h��3+�C�����0+�-ݝpU>���8��{?�I�3�L�;���d�3�L�;�1�zc���8��z��H -��r��#o�Ynq�l�7{�&�$���%ʮ�:F)��(Ś�ά$o��g�W5:{a�7��=&2�x�F� 7o��+��Q�;E��Qxǻ����9�1΁�q���X���:6�9��8��5s�Ý�+���+܃v���O�i�"�G�*�A�`nnq��I���P�8Ѵʎr�����c�;8ƸCa ;8Ƚ��wp��w��1�Mp��s�cq�;"\m�V;ކ��D�5�9�!�3����wv��FvPn�q��R���h��t�;2�O<���C�At�7��g$f�[w�j(�rh��v���:Q ���9�@7��V� ts<�n�O��� ����� .�9�N��u�0���8���9��sbgbk-�MV㆛}�e��(��`���xg�N��9hβa8�=]�\� ���O<̉e��0'a�Yz9ƷW���U�:C�;�%܉z���{xJ�����iY� Q�N��Յ;9i��p'v�N.{���A}(�)�rI�q%�>N���!��(�קx*q2��0g2™�r&c���I%�8�}�ڭO!��wb�s��|� r���9��c��~.�v�Q�G� -��u���H�V�}IpҒ9�8���Dp�(�rΟ(Y��-��[o��������$z�m���D7\a�lL.��jp��~C����d�f1h4�l��m��7���&ה�br�v+aW��SM~���Gt�z��f\��{'���hW�> ƚQ��T�ђh|]���w�"�|�vU�le�*����� ���Xwh�ř v&��d�3�L�9���d�3�LF9���d�3�L�9��Τ�Q�Z��Z��Z��Z��Z��Z��Z��Z��Z��Z��Z��Z��Z��Z��Z��Z��Z��Z��Z��:u�P�_�_�_K����d��v�nq�nq�nq�nq�nq�nq�nq��䶡.I'�Q��E9 D9�rb�D'�y[��h�Ntb�ec�ec�M�8o�u��u�u�37Ήz���8'�NA�,�R��Y�,�8zLJf�.�"�f��+MV��� -s�4X�2ګ2NE�Kk���*$L[ -�� -&eBRZ�h9��Tr�xY���)Y�D9��iu�����҇���E�'V�+��R��6��G�j��^L]����p1q���=%=�T[�SF��ט�̔t{���~����ĤQ)�ꢯ�i��%;p��&��;& � �����2Ȥ�e0S8����,��V&��ʂ��B��q���:���g���lޟ��bc���\Ԏ�P;fC�� u`6ԁ�P;fC�� ub6ԁ��˜ -��P̆:1�0KN�0=3���Ʉ�L&M�̔� �Ḭ��&M̰ b&S.�ɟ����v��~3�1K;:1Ä�L&f2ibf�$fXNb��L�0ib�3�r�L�t�L��c����ɤwFf�ѩXM���X�1�X3�3�x3J0�vf�ތ:�QG3�dF�ͨ�u5�nf�݌z�QO3�eF�ͨ��5�~f�ߌ0�fd�>Ԍ���$\�$u}� 7�f�jF#�h����h32��nN<}�=lF��ӳ�Ȝ4�X3gF��Ȝ��D3��z⬱jK�Z�����-������)��tHH{���(�!H4��h�!3jW�F;�J��hGZ�b������xp��� D���9���f&���p}LJ䪟Q<9#}����QClUq���K1F� -��Y�c���\#�Z��� ���}�A�W-4�|�N�Z�C�ݱ���_�@ʩ���٦�4�@K�iUm+��ඕ�Pmmm�mis���� ����Ӟp�|�� �:@2`Xr@��VF��G'�it6]H��K�r�:�IߣK�F�Oԛ�)d��9s0����)HSL?"�JZx�$,� -揄�a�?`ր�I*u��xu�Cx�i�ӓ1��­@��i�uVƒ��|L�Ex�,c`z6��cz��!\�9��@��gRSӼ��k`�5,_ �'�1��� ,��!���s1}��H��KHnb�!��c:a,¦�#��e�Ǵ/݃t�ݡ���\�+����F��(9EΑ<пo���YM��2gVw�L�Y$Jf�f�w���t�̭W�W�j�B sV5)4����r�5Q�����K�*�Eȹ3�-M��an䘳œ �k2��k��Jh5�N�<5?䌠��8� ����P��VG��0^Bx�2=a����(��vF� a}�=� ���.`�"B? �g0�n����Nc�5�D�{iF3i�k!|Ra�[HnaN[�u��P �1q���`�hZ'=kCo��fN�&s�D���$��%��tL��t_L�v�5��y�C��h��5�ih 9*�����p��Uک���z�H��cgY,�R�o�� J��ܕG>��o�_e�j���tk^}P*C� ������1���;6�8�m�M�hS���Hk�E4��j Zg���_�%k#�1�8m�6M��-Ԟіh˵׵7���� o�t����k���� �,X�W���Y0gu�0�;�"�N�@���pm mIc��Y�{��p�6����ޣ]�\o:��괢���V�hWh͔_�O�=k�Bgйp�3t��ܻ����᮷��)��9ݢ��;�7t/=�6�����j�G �-F� h�(���} -���8J��6ަ�2����&mw�A�! F�HB��"�ǜ��Ĝ�k"����Xf���A�0!E8K�B��9�VE���Wo�*�Y��G��U�rӓ����3������Ü}x���cz�w��Z%�0��W!��Z�� -��j�`�:�Y��z����� | �?F|jc�d�y��0m�[ �'†x�L7F��"���B���?�龘��u%�6ak���P93n�m�K���� -tr�;r��O,_�-%���9/iy��.�C�H>*���������W��� F#ҒĀ^/���eg"�x�m����_B��9s�|e����]ڪU���Ux��� G{{���0�a �<��G�GXa'��u��2 _�����*��a����nz8Zs�騿(ۀ��9�z����[����<�xfzN�����9�\Ͽ�ܼ*{��J����k�W�7����]�;�{��"���P�>]}��d���y�g�ϧ>[|����g��a�>�>�>�}���oYߊ��}��6� �m���7����8�ɾ�|g�.�]��w��V�ݾ�~~~���� �{�o��F��~�����)S&�̔2o��Y�p�Fe'��RvFٹeח�[�@٣eO�w���?���r���w��?��Wy^ޣ|����廖O.�^�����^�����` ��7`v�Wg �Z�jZkRGk�-�F<��_kI��J\i W�•0-�4:���!$�0B�8bp�?��v�.�))o;K*��HE�-R�a����wB�a7�/!|��9 ���r � ��~�p�y�~���6�~�>��!�� | ������d�i��-�O�0�V��A�a�E�����_ ��BXg;m��� �� �:��G��� -�h��k`�k��nq�D'V!� z� �ʅ^�B�r�W�Ыܻ��\��n@� �� � �/6[�F h(�Ct7�< xB��� ��/?�n���:R>H�/���cRvHI&���\jHMJL)��4@~"y��(��hZ�����|���7E ]�5�`� � -!B(�0�P8��!B -�a�K9!�H�$ߖ�B:� c < !B�*0�Z�x L� � V][a-�u���6�!���� �g�J�#=�`�(��1�0Fƈ�� cDa�(� �k`�(��1�0Fƈ�Q# -cD�UrƈAh�"� X!�H���\[.Pk.Pk.Pk.Pk.Pk._l����$�@U��s�(5(5�Դ��� ��Du -31 8�L��s� l����B���yY��e��������������������|��|��|��|��|��|��|��|��|��|��|��|��|��|��|��|��|��|��|��|��|��|��|��|����R�\(3�| ,��$�EV�!�2P��6G�‡>��1�KP��7 ���t�m_)�R�g�l|6��D�� ̇��"OAXl��_�'��v�b����K���/�YxN=�IP�Fr�� -�U�w]�0��p�d�r�� x -7�)܀�p�� x -7�)� ���� \���:�w�M@��OA��r���“Ʌ'� O&�L.<�\x2��dr���“Ʌ'� O&�L.<�\x2��dr��䂮]��,p����g> �����p�Eyn@�M�Y>�v���0� 3!̵݀yp�� �7`܀yp���#z�BS� �Oe]���;�� �0Z'`�N�h���:�uF��x�hz5(9�ʗ�$ -�I���T�v���|*���L<�/a� `� `� `� `� `�����>#|F� ���30�g`�����>#|F� ���30�g`�����>#|F� ���30�g`��ܕ���nj� �f@� a�' ̆�d�k!����>��!�� | a#��-��.�� O�&<�xb��v��Ol�Jj�����A<��7�,xR�!̅'<�| ,���� �_��� -���p#�`�܀9r������S��������ʜ{��������0[?��М������4&5Iy:�v�>f˥��/��m� 7!�a;� � ����3�i�K�2?e�/�j��U�!B��h������b~��������\�<�5�{���%h��y ڼ����О����ߗ��Vڝ4�R��T%(����ߗxC�7�*~�����JWc�J� ��A������<��-�����[�K�(�Jq�I0��3�_���XR ��;���vP=1iC�w�%t-a^�&���^�k=�ڊ҆��m����z�)� �TR�?�4���J���{@�s��� -�ګٮ�p�w��׋��yg�_]@�� -������s����m9p-��n��;�=��Q�ۖ��c� -�T>�� qo[\� �3�+�j\� -Ws�j>�w��}g���^���_́�yX�'`wJ�A�\(� ح�R9�� (��{Alb�P�,�o�p��.��c�j�̓ 'r���84i�7 G��%�<����rx -�i�#l������P��YS�� �^–�ճ���{O�� p�q�n��`��(��c�c���j�K"\ -�J���!��vB��|8��y���2�P=PJ�8+[���-r�)G�_ڟ4���t�!~����υkϒJ�9��e��j��'�t��e�CzȤ�6M�s��k-��֊Dk�!���B4��!< 3Ǎͷ`��rهr@�]����[ �S��� y�����k@�����a;̷�S��+ۓ|�?@��v������aG �p ¯�C8�$�SNCȁp�Y�A8�<�\�C��"�<��5�!܀pӖ#�@( ��!@��"�Jٞ��@��!B�v�C��#�N��.�B�f�����7�>�B��?� �0�vL$AH��a��F@H�����x�s�����$h/{ �mG�Qg!\ns{���bA�µ�p�&\� �.�p�M�27K���\�-]rg��RoAx»����&����ׁ7��x�lWK���p�R�;�@z�0�� ����}m��U���^�v�P�"\-��'u����yP"�p�z ��Cn����Cb�*�{K�q ��Ҳ�u����!r �l>��m� �� Ja �<͋X���l�2ޯ#v�\���� -H.����r�7���~} 8�y��.�:|�<����?%���� -w] �>ʟ��P��k ��c^@n;ja;��R�!�z?vrk� /�����B���A}�!�%M�]��He�L|x -��� �� We�t�J�%뻷}�'|���7�v��b?pg���r��Oqξ� 6�������ѵ���օ���㕁+�@6�V�r�v� �P�>p��D~�%��ضپ�yj�u�~�/�D��JТ[)J�57%�oG���T&r5�K�|��ٶ�m��c��ª�6����N�Q�?ؾ����� ������y���̵�oc�O|�sK����S�j^�|�� -���`x�1.���?V�*'W������5�`[G�m�[�$5A��o�Iy��$\�迍�?���j���UVM��#����8���7A��R����WT:��)��~K���K����>ar����ɡ�/}�&�?���U�>ML*�s��b���3�� -? g��i�-2�v:�mA���m'Pw.� -�!�Q��@�~�J��:�@C8j;�~�~��O~J�e��-J�@j wxr��<��5������7rϝ��Bx����ڎ�o��L�n;��ڎ����G�*�7�R_+���˖�������b�a�����k����َ�\�ŹX��\��m�A)u3(�H9)�y�"5�&yw�{-�%� �g��k��>l�]����Ak�����A�� ��;\���������\�s���z8������)z���0�+��}zm��������m ���l%gu�J%�t�;P��9�� -���v=�g�o��T��'�%9�^2��ҿH�iw�(NsU������[y����o�y���P���Ȟ:k��{}O�����k��Y��-(=����C�vq���L�|���'s��{~�w��c�<�^_�S���{��r��k�F�����g���0���1}�Ч�ɵ��;��M:�v���ɵ����?����� G�"u7�v�vy�M��)�pLO�N��~�z\)qɷ}���|��w�Vb�?�G|!�-��(4�*�� -�d��[�D8��m�Cm@�|�������[2�jG�/�G�p/��.�`�!����Na����xH���ʸM�z81ǧzKR�cϜy�*���BL��f��9ǭ������ě�O�Y�y����@�C����]�q��]�6�j��R�}�vAR����j�u���;O�g���5E�� -$��)���ys`N؎�X�1Gn�Y�}�z�C���5*���K�%�O�w�s��@ K���z=p���~�k�R�-q ���sA$B ��k���w.4�L�#�)s�D����D���UE;^�N�O�g�����H�|�~w���x��\ -���?8��<� �--���}��o��k�ȩ�V�П���K���}b���|�x�%l��no������v��.T�W7k�}�*�?� !̿�g�������֠깋��Ý���>L��{�]w��\�����o����f]H�k�{��O�lb��ݿ�� %�C7>�藧���`�;��q3�e��)W��Z�c�|� -��+;��������vUK�(}������[6�z{ J(� -E���?~�cS�n4�o�[o������ ñ�_Cq��#v��h�ѣ��]�7�]�r�'���i�� _h���>\o� -�{���ґn8��?��L\t�R�\dzԫ'�`N\���f�^����oj�Rf�ڙm��=rt�o���o��{A��� -�koi�+��.\M�����K�J1��po��P��������Ka� -b�.r촅�v����8�ޕ��QR{*��ٶ ��~��ga�b�:/�nT�.۫.71���x��6-T����c��U�2u�9��+��J�ao��,R��S����n}��Up%��u�dt-Ϳ��m(�_ҵ{h-�*���>�^���%y%����+m�l�K^�r�L ���M��{є!��OJ�K/�C��L�Y�Q޺��n"߿h�V��WK��J!���(vψ�S�� -��0{���?�G_��q[p� -i������+���D��Yi}]�ms`T�V�Y�G�S��A���eP�]��� �y2��Y����m������;�rP���O���r�a�O��s��|��'Y���J@�yN�:�9R���W �����V׿Dn�|��;��r|��ٶc'�5���'��tx� �a���9ɔ^�g���bꃅw����m/��_�ZQ�:.�H(����>`|�{�?w�ܗ�����v�z&�R#�~��u�W)���) �Q� -8� (��^�0w��$���'�>�Kɝ���{"��۲�,iV��G�I�_�Ⱦ.��W�Pv^��.~g���.�o�դ��;�VO:�m8�O:4P�t���{6��K�ܶͅ~]P���}����?B����2~�܉�%>���ہ�v���Q�/P�c����4��ȶ �� ~��o�u��� -e)ws�}��[�%�C���MH3L:�p�l�Z�{�s��ᷴ���D��;X��S -����WOʁsq�r'��5����:�ߧ�UJ�<�C�o����X��= 4��\6=I�-��%�/Wj������C�{��d%���~ �N�b�)T��R����Suz}�gJm��_��f�5�/�|�[n���yO;��5A���Z��٠ f����%������k�D���*F����k�l���&�$�����)D�g�I�cX�3p�U���L��44s����(��uR��yG t��"�ko;׼�>����ԉt� �����o�֫��ⷷ�ȩ���f��e�n�|���wy5H�Ⓗ=�o%����s-�� )�碲o�vC�q[�� 3B\5Q��ݩ]�bW����������n�����_1� �kk�,T���������*��+��^�����]Ǿ�/��q)|�)�����m)���<���r��^s��>Ǻ��e?��W`����c�Nk� �M���v(� m�C&ߓ5����oE�ލg�*���.��u_�˳�䴟�g��}TJh�O�� -�����ܶR^�'���X�䖒97�����?��(/6�YxW:$=��s*�I�c ��t;&O�Iu�x��6 CG���vr�c$ -wjނg����/��[�S@��:�ڟ��f���#�!j%IytaF��g�v�[ܥ�o�+wi��֗ܙ�p��v��g���DR�j)W[��L����k��5ӥ�d��O�6 ��i��u�-��1���{�]�w�l�k��{zzr -�u�k��H���1�%w��e�����i���gK����{N]�mQ�s��>�e=�4� ޷p����.�ϮW�p��ǧ�Z�ݝ������ �g�1C�W���1�T���f� r_�ܱދ�]����ŷ�϶Y�n���R�.GjKQ�t�E�:�LvƷ����]�ݶ;�rw��p]����c����α�I-���U\�;�Z� �btR��pWcn)���H�����Ƽ;�/VV��jMY��!w�:.����;��,�m�����(�K��]JMK����ӌ]J݃d��)qL����"�'H�(���v�� -�4��sۮ��A��Ѷ��i]@u�W+o;�T��u�vݡ�m:��:��@5l�dYH�}#wl�>�m&� ��]��ڼ�߂b(��WZ����^!��=����{�9���wo���G� ��5>�{t�I/� ��䮑���������1-�����{���e)2��wo_�Z�-�@�u�v��޺�bs������ \�Qu}�bQlQ��2e�~�]в��n��J�YU�uu�s1-�[.\��Sg�]F�^������=9.��;pp�C~q�E֠K�3�VQX��� -��&�������$�֫�WO`h�q��0�vf�e�}!)����h�3��w C�N4g������^ 6F���t�vl{�/:�i$�ފ|(�v�A�<�#�K���.sJl��늉�.�Rr�{�c���:�z[Iw����]8����� -i���7��=v�~jj�y�o���m�D�}LAju���w����-���f�\�����΀<������V�̧�{�Lˏ��d��UD��e�]��.D�L�@�O���d���%.� �XY�ۭZ ���������}����!yۧ��YD�6wf�}��� ��akާy�1�!���,����K��0�th�NN��,���b�h��N/��لz�^�Fo��;�W.)lkJ��!�L[�V�����.��@����WJ:/�-*��K%?�c/�^;v�%kT�ϸ<�{9��٩j�C��B;iK>)@]?Q����B���j���j �v�$���ԉ����vT�Bk<��K��-B\�o����rW���`ſ�2�\�3��s��� ��'S����'���^���|�M���v���wb:�4u��r]>�� 3+��*�B|����g骳�bdx�;� ���(�Խ~J�^����%q�"�����V�z��M*��nU�v(��ӛ��)tSڷ���O�(��'u��G�"-��Q*�II��ƹi�9�{���9u��\���J�K7�-�'����\�t��қ; �T�C���R��ǣ�;�r�K������6k��{k�����z`�9�LԎ��?�G����}��̳�@Ӕ�5�-���� I\g����'?��$�mR'@�8k%�s)�]��Ӷ�P�}���9P87l_Cy�(ɹA\:�� ϕ�(*:O���{)�|_ɺ]1wHiz͞�y#s.�9>�ew��Ek,�*��}p~.�K�����:q�8�ۢ<�����}�s�n!�3w�����_ۻ���ɻ����A2�-�} �u�B{Q�4(�+yVi}r$�7�n����0�J�je�`?e�\k1Wvp��i�^�p�8���'����W��~�;y��a -FOf���w���ͮ��������q}W�^��ǵ����zJ� -��"�����\�{[�Z��G���[[��B��� �3Ѷ�����/w���V��YLލb��������9����r���<����Сa��~Q5@ -0p�}��_k�e��Ş;����=ߢ'�ڕe��������ď���|�i��oap�x>j�A�7�u�3����'!��m�y�[���N�m�m���C*�~NU��4�i:���Kk�Q���Ϛ�5�P{�+�sIG��1��G�i���Y@\ޝ����B~�󷗸��$�^GzqWZ��=����n�H<����{йp���0\� z~�zFa����5���K����v�d18��=[�wNi�\����B��������l��i�J��ͽ��q���k���������)(�[����N3@q����^�9��.���mv��O�ҳ4X�'��,�*���Þ��;��٧� -��"w�=m�����=ۓ����'��Iz��~������i)�鶛�_��� ����#F�g�}d{ށ����"�\��f��W�W�=�}��Z���G��~G�?�V]~���7�:%3�_����=��)�`h':W�]�vq;ы������ޖ�u�1�/}�g�zk��]�1y��c/I�7�9p�V�z���],��)�孿a�]�����g��B�;g��� �XҩQ���eq�vw)-�Ȯ��c�QL�}}\W�]t�[�e��h����ŽsZ�=�w�� ��0縳�|��&�#m���8�|�8�ݿ91+�z1�wY��B�K�,b[����}���J��z�5t�;�h���������Z���ԉ˚�߶z�z��#a��!�uyŝN���Oq{r�9���q@�����ڑ�]5y��fZ2H���H������Ñ�����c� �Z�*��ķTk3�)&J7k�xp��k�u۽|a��k��P��D)�q�����T���)m�J{����w��.|�>v��G��7gԕ�=0����[�����<��@j���Q��%�9[�F��r��ޚ�g�q5O�#��w��������Y� �;��[�'���g��uIO�}5����Em���ܽ�"�?nN�f�3����c�����t��m�Ga����~$Q'_��m<+�~�>�5NjmѤ�< ���X�V\hp 5�>f�{�u�nXq�wTqT;_�t�}�]�����+P|�n)8J�:��5u=�S-�\�B>�|;_�)�.�p[��$\�:p��K��@���?�;f�s�G�v��>�\�f�[�� o���Dl;���r�7�+��xNÓ���X?/�;J���X�~ȵ=i����"ks���1u���s��i�Ó�����Hx��\17OQgj�=5�6��q��ީ;]d��\��p�?����}���LL^ZO��W[�_�W�w>��;�~���vH}�?� -L^_�'R��S��k�ş�g�S�P(ǣ��#��{�m{�^� ���O�O�F�_d�w15�����b�VP�w�V��~��;�пu�_�:�l�Zt�<T���w�xl�-�}�Nݎ>���vF�|�\�Ic�G�i��������u�؛̝�D���u9A�h��\���ď�� ��}G�:����Y�'&�(�����x���>���,U����6����zϙc���jw�ב]�.���E����Ex�U���R�,�N��K}����\�[<����#�����}E��]W�Dj�nq'�?�q� ��B��\���-j�����=�Z_�98ſo�;��.�9��j��E�Q�����^Uu~]�������=u��Aw����8��vh�GQ�d�{���]�\#�݋uK��u�>+iM�3���LY��������>��V�Z ������;���[p����@��ZS�zΪ�ɶ�ёo}���]���x\�*�8O\�=�;�����֋��ȝ%�T��-�[r���χ����u������j�O�T�_����~Q����v�n�<_O����o������3��,ϳ�c5��9���z:��Г1W ]������gu��o���Xhw�kZ������EK���!���+C�?'�_���g�V13���_K�`c�seZoxnIg�S��P�.8��P����c���h�)��T���Y]���H�}'����!�$��ic����.y-�����ky�� Z��� -=]�[|� Ŝ�_��|��4^S)U��+��i`{��U�8��#0��ę�[�_�����."����I�+]�?��R���s`��_��޴���L��=m; �ٗ}?��D&�m�����<��&G�\s������D�]�����Ww9~��y�Q@�(5�Z-^k�ejYZ�6I[���>�>�6j����Vm��Sۥ�־ԾҾ־Ѿվ��h?h?j���1�W��vB;���Nkg���o�9�vI��.kW��Z�vM����nj��&mL��0:�&�atM�ch�Y;�N�3�,�e�v��br�3ι��2o��(�X.V�W�k� �Z���[��x��1*�a$j�Fm����hd41��j��F���hi�1��a�Hc��e�5��G�G�njǍ��L� �a����#ϯ7��?��pׯ/�eHYR8Ay@*�����UH'ҙ����y��C> ��ar�h�kG�\jвl6����l1{�-a���l{��ak�:��}�~`��|+�Ʒ�|'��w�/�W�k� �������|/����?���g~����<~I�����/ƈ��4�Tl��b��.v�=�'�h�P�5z} ��>�^T�t�B�p��b��D F;`]��pGc�4�I��1�=�@OW�����>�Tu����@]>� �jG"���I$��J��)i����жk�Ik��oH��+����t�:@:��#���N�n@]�Hw-W�%�����>0�y��<�U�<�Մ � -#��ƆIg�$hmJ���$ F�{� �x�dÈ�$�B�ǐ)0j��c���T����4Iad��1�P�[dP�;d��62FxYc�YhT�{�w0� ��yxn?�kF_��0Sav��M0�v���z?|*W��n�5�@P�<8���CB`d�j���m"�&g���G�x�h9f ��q�=�N{О�7�C��h -M�� �S��H?���f��~�<�'�b�̇�eC�j�#����a��/�V��}�~d���,��3��}x^�W�x��MxSމw�]xWލw�=xOދ��|(O��<������G�Y|6���_�/�7��|=����b���M� ����1qQ�K�(��M��{螺���辺�^F/�W�_���W�k�H�Ql -{�Me��il:��fʑ�- -�,{�]dy��� c�F�f�l܇�� 8>���W�uh�Z �+���z5xV��ϭF�iIM���\󗔯�h��Z��]�y}�����_W�z��௻�u�]���D��<���z�ǵ �]?�r��~ -��<��߁�n.{I�\���rح>�v*h���F��*P�n��%�j�F"���"�+^���Y�Y�]�mC� �c�Pba�,Y���Y�V��gZ]�7���*[H{�=C �<{�x��'��ȶ��l�M����k ��D���G҂����V�v��a��ҍ]➤���H:��I&��k�Gx]^�< -4ԐLjF��ނ��I�m��5�|�d��S7�W��'�'�'��D�S�b�E�)Rh�yR ��ERh�%R����$U���&���֓چ�� s��,P�(�3��1��@9S�P��� (h�{:�N8P� ���@O ��T~������8H���e0\��@.ׂ����@7(��T�D�9o���3��A���i$�R�&\+�5%L ����I���-"B[��#���� (j;���v�N���S݀~r �B����� Z�x %����a^��!�b�x����Ji�M �C�PBiM���4r�ұx��,z��"�H�cy��[0�(|�\?A|�IvJ�0����K��y�]��5v �uvF��#}�݄�?ٟ��a�g�8#~� ��un����=��|��`�y^���J�q��2\�«�Te��@U��ՐӔ��� -%><��A~$����M|'�#^�G�#���~HG�g�.~'�⪸ -�?�=����w\%����C�A�u�R�f� �%߳��}0ƍx#RxKc��') T��/ - ���*?�I�����8a5�&����0�s4��h��(KR�h����t��H��dK�����-h8��`�r*��t��! ê��#i; i{�v$̃:$ 4Ϻ�9�y�y��8�=-d�i� �هP���c��9�j��6�o��_�/H0�2�ȸ]P��q!lȸ ��B�?��+���������(;WAK��$�\Ї�b�Ie�e���,p���<�-��*�c��.CZ����*� -i�)�#����29�p��� ]A�e$��e1�=�;��E!-9hy��8�% D>:��swȑ�t8���d ��Ċ<�/����r �+��(�l(��+BZ����k�#���X�L�2�&� :a-ȑ<�*H����x8rb H�&�+�c_� $~=��Ð+�<�K�<ys(H�q��'��������S�T����ƧAz:��|&���g��� �i��lH��s =� �/L��j�m-_p �B�@{���6� d�.� �W�+��H���#��0���������e?�|�g�3Ȝ���n`�;�σ: �MBEQ `#ш�h1�CE*ab�I2�(�K�xX<���G ��n��i��+�'X¯���u�:Ik�ȑ�J:�(���=�T:���N|,>��'�(�Ql$ A��L������C;� -؋[I}и��:�r��]b�(v�/!����4_�o ����RN6{��l*�ߓF���@�@_� e�����O��R���Y��/����8�C��)�#��2� ��� ����?.��Xqt�*���& "G�>g�ȑҸ��M���yqp����򹆸 .@ �EvC��|��eqZ�"�@���M��(��5�-�ny�R�t�?!�/��e6�P�t�Tљ�r]�ƺZhSЏ#}A�t��� ^�'蕞�A��e0��5�D�>A�?�{��`�I�\���7�<@�J9]�x���u\ =(���]�4F���+V����(��P��p���(��P��C)�j�F����� e�'��2`��o=�V�R���%�FoR}��Q�)|�@�A(ᛁ4�2> -e|K�2>e|{��t&]���Hw)� �$}C2���}9���R����d�C���d� �ρo-2�5�<��O����R���6|=�;d���6$�������v����z�!�à=D����C;�:��А\��Uzt } �5�$ܴ2ZYR� -��W�:XM!_�e�p�9��>�(�;�*Z� ��+���}�!P&Q˄z��,�7[�H�h�@��=�=N�hӴ٤�6G�C|P; �dԹt�p%u���c�v�gP��Qܴ����U� -x��v@Z�+Q���4؞�h`�i߃�"Pw�G�%Jۧ�����CZ�1Q��9'���>���{s@��6��+p�]��� ���rBP˩�ZN{�rZ�aޖ!m���\ ֚�\m��@��VhDh�a޶�Y:���8'[ᜬ��QC[!�am���pF� l�CZ q8a6n�|i7XqZ�]�.ȑ�CZV���p��k�^��v�r�<��Ҫ��~xHK�� -3���O���hmD�<�p���-֦8'#qNV�9Y�dK��V�?���B�Q�����R�0>�O��c��R�HY�#��G��U�#��G��Ғ��"��|$���PHK���b5���)<�Gx������C�e��J.����˓�TB�A�TD�A�^C�A���zM�5�����4@��*̭1h�Rږ�%�F�hR-SOKc�ڧ�4�Ɠ�h�֦�h;H����K��Ҏ��H�5 ��ڴ+� -9�h7�6l�a�І��^�Х�d�В�N�Ҿ�2ڳ�������P�@:j{,\+LC�D�Hڹ~4�&w\�)4�S�>�K���AG�r4����+�Qt�H��"MG����qM:��(m�zh#7�Y4�X�R����N��q�#Wj� `5ץ�D�d�mI �&}�> -�L�S���ThKZ�5�4:�6uM:�΄�Yt�<�5�O�%�ߗ�z���c��'��O��ҭ�k��74�B���o1�B��r`}���&��6xM֞�����Ɇ�!`w'�DH{����:�# ���;4�\,�d�X�R��\%V�I{$B�&^[@Z%��� H��A�I E��F�M�&�4i��o��!G�����zз����`�x�pi�������u���vM0�5��F��{���s �(Y�4�tY)�:�bU����FHyU��"�y"�UCj���V��2R[E��8��TC�Ic��&�?*i�)�V�VK��fH[V�-��� i��V4�G�j���^�:Hau�‚��RXRX��E - A�L0�H'��`��4F� -B�j���>(,),),),),),=8q�� @N5���'��Ga��1�V�V3�-7˜;s'�̃y@��-}=���\]����`��4F�k����� }@�,�%�h��V(y,H �/��?�(�P��@�S�O �?�?�(�P���� G���'�OʟP�?�(ʠ�i��-ʟ�(�Q��F�S �Oʟ(�P��@���O[�?mQ�4������gNk�9Q8s�q�D�̩��Q8gt�3Q8gbp�T�9� gK,��-^8[�\f�;Ζ68[�p�x�@��O!�3�9mP&�E�PeB9� �(�4H��UgWE� �(�P2H=�-��Q6��%Ql��F��,l[@*�'�&z��b�4iĞa�@���EHK�^�{ �K�R�>?��5E_8z�<���V��P�*�*���i�����`�o�j��k�Z�Sz��7�[��������&��~�tg�����Vg���^�� �0��6�U� g��&HK�` ��A�`U�* -uJaM�F��l7����&��}J�� �_ha?���6�5��^C��C��avj>Ž�U�G��~�p� 2v��"�4;Mʠ1��ЏX��,��N� t?v�]���#�B?bV� - -��>�Ml��M���D�&�?�6��l$��u��5��(��f������M��ݸ��S,�>E\�1�/���$��5Л��ś���uћX ���yU^ ����M�@ob �&�@obz}Џ�~� _hEzk�PX��p�Sz�7���MDO�&ךI��,6��$�a��V�a������P�;!g����V[|-���7��W�+��� C��z�ʣW���Q��?�� -�b����g�9 @�q�^�����^1�^����E��z��W���g�^�����o��9q�J�Xz��W�9z�j�W��\M�:�W,�b�⊸ -i�k��0_�GX3�����@�i:�5��NIm�5D�Xm��u��hy���iA�ӂ��(�<-hy�6gڜahsF�����Vhm�kLAh[�A�ъ�b8ڇ-�2LE�Ђ6�m�Qh��`Z�ah�B;0 -�1h�Y�fk����Z��dE�(-� ��F�-�i(���� �t���2��uGN�M����rQ���.9]#�tK!-y\#�q y\�q����G��k�٫��2��1�q�!-�5�����2��uE��<�!��@�y\_�q��:�zG]�p� �t 9]r� �t]��e �k���r: 8�Vȗ��;r�n��2��1�t���gO�w���Ug���e ����*�$�����.�]#�w]pݤ -r�N�n2�] �w��ų��o$�^S\=��'up�dr���� ��\or=�\�1r����!��k(�p er=�k(Cp ��� A��\or�v��� ��\�&�� ������R�S� l�|p��!�#�|�#�|��� �ՀBn�ܰ)r�!� )r�!� ��J���'VCn�k+Upme�ā�� O�<�q ~jX5���a�A KG � jX����5��a�F �jX�P��VjX�Q��V԰D +5,5�$԰jXI�a ԰����5���aŠ�����Ѷo�V}3�%��<�V} -���Вo�6|0���ІE>m�(�ᛣ����d�؛���LT ��h���e� m�`�ɇ�M�6yZ�Q���K"��7�(o�����/�����/�(o��K"J��(iʠ�递� %�J�D\q���D�4ep�%�_:��K"J�P�$��)��&%MC�4�(iQ�����7��$��)��/�(i:��IDIS%MC�4�������J�������/eP�$��i��ʣ��A�㎫0�(u���q�K&d4��$��ID���N�d+�&[5ي��VBM�z7B}v�mP��Q�m�ލ �� ��9���88���7��8�p���8���9��`8R6�^u��k!�(�@��3w�� di<���(?=�[;%�%g(J���6G�9�g����vzk�P��@o�L��z��v!�i��O���A�]�~�Y(u-(u��C��Q�@�(�����q�����؊8���Q�A9��c��?���A菝��X?��.DO�zb�'v!zb���=��P�[����`�D�B��z��u!�]����~ׅ�w�@�k���q]��օ�e]�^V��.D/kzY��u!zYg���u �W�g�I�#f�1}�A�G�@�x�P�A��������^����E�D]�J�F�D=�J�E��8ڟ ����L����O�>�$�V�Fo��V Ao�D�V<�ފ��HD�P�D�GO�h�DLAOD}�D4DD}�A�B�C}�;�G�Cm�;<�^���_��>�q�SO����/�e_���K7�҃5�~J7����K�����>�G��8��� e��/=F���Q=����>�����>zj��>z�֖��CM�5���.Vu1u� -��uB-,���yuW:�Զj���u+u+Ԫj�&e�U ��ʨ7����5��%�B-�jI���~����l��Nd�N����Ԇ4Ԇ��6���n�-@=����UA=hj@UP��P j@UP��Ԁ�QZ�P5Ԁ��5��uA � -j@^�-@ � -j@cQ���x��u/�}��u�@�}��ㆺO�}*��u�y���@ݧ�> P������}���Su��������u�,�}��ֳ��*���μ3h r?^ʇ�!w���|$h"R<��� ܧ7���GI�%A+��Q�BI�%��ŔEM�i!MD� ߦ����:H� �.:H&�#�DMd$j"M i"Ry�EIE$u����u�����DoT�CMB��=5@�S�7 GS�4����z�����Q�4�A�d�.5A�R�+MFM��8�����TE��*�/�Q�� G�%5��TG��:�#UQ��Hj�Q��GM�8���Q u���_TG͢*ji�Y��fQu���S�C �*j�q�e��{�����Kڡ�z��]�P6�F�� -����!t��DŽ�%t�M*'M�L#݇g��"[����M��Q6y�3�]�� @2��ٟ �u�J�g�2���G6���{rJj�z���xo|��<�R;��ҍ<@��h2��<�,y��!���E~ ��=�xA�� �C��v�;��d�N&�^0���:<�X��U�k�G�!�"܍�;���2�Ҍ�Ex��WޔЍ ��ǎ�t�BXaE���A��a8–c2 rk��+�� �p�4��' ������6�\��>�p)•W#|�����5lA��7�"<��(�S�!�CX���vKB���&#�����!�aNVj�0�PG腰,Š�#���B ���Ic*�U,ߠ�gQ%R�7Pq��+��O�n*&f\������s���H{�r�#��H�p�8R�9R��O��J�“�Yi�.F8�,�SNB��0]¿IkOC���N��L˷k�Z��L�{ -X� �^������](��o�l���sʬ��62c��f\n����x���S�A_1��������1*NW�l/T�j���*.P�j���z*���L��U���7��s+|o��X���u̸�J3�֛D����J(�.�� ܨi�1b�x�s�Rp�<��h/l�&�������|�ި��!@?L�a:Ӂ�n��F��0B��Dʨ� ���qǜ�.9�V�Z[!��E��_�� ��j��~�9I�>i/��a��1�j�0��2&�o��Ԙ0�P�)Xr2���%��2�NA/��g[�p��H���k�x�(�u� �w ��F&�����RB��1w�ƻg��s����1w��;�� ��x�V�M������꩑ZX'(�S#�,Y��;�>p��S��#UQ�SM0O���%��7�kVy ^�s���(?ɑ7ב�žG��{kʷ5�Z�6�Φ�,��f��\1R��O6t��1�8����=�}��>� x>�I��/T �{�|H6A��"ߓ��09AΒ � -� ��k^�K7b��ӽϦ�c<���x.��yt���/�����!��!��!��!��!��!��!^@a&�t��A�Ԓ{����)I�T�G,}`�,i � Φ�"��E�RI󨤋��I� n�[�,���� ��4ֹk{k{�y^��9�{�@M#I&�D���@QK�J��ɧd � �d/̽��9R��ܢ?��ܯ���_�xQ�yP��5���x���s��Cj<Q�yD��A5��xV�������e�ߥ�ߗ��?���^/�^��c��� -��*Co|��h��0m��:�h�K�oC���jlc=>�w�=0��������������aKk���JxZ�c������w�����������i���,6�Q.N?��6bm_ �[�A��X�&��s�y���A�* -8�(�����r�Tp\Q�iE�* -8�(�����r�Tp\Q�iEG��Cw'O��؁#� �{��K��.���H;�|�ϥ�ē���d H�o2�A�����U����V��|��A�q���b\��6��Z�}a�0Naqf�X��'�`��Ś}b�f�X���o؃s؃||�����ޜA�,�k�=��=�Ş�a�~Gz9���X�`+װ��X������v빎tW����dAƐ wf�}�d9��7�R>690�F����jt:���F����jtz���F���jt�B�m�����-�7�qD��aL��ɳ��0!G�Q�P,�'�㈔q��~{�)�����TI��res�m1�++�+�OB� !�M�� l��^`*����32�b⅘�!&ވ�'b�[��a�����nEl��Rk���U�z��A���d Y�T������V�b�`�c�jam5���ػfػ �{���4���G��b�b��n�)��覯��>�n+���f���A�nTt󀢛�L�a!��P���}k�}�t�� ��4��D`°?Q%̤� -��ٳg��8{��\�����?�P��X�~���� �pk8�5��PïX�1���pk��Nb DZ��X�g���4<�"G���(r3���"'���(r1�<�"�ȿ�Ts'��3��k�5�š�c�]��Xs7�� ���5�Ú�c�}��>X�`�y �<k�5?�5?�5?�53��ւ��6��8 �BG>.9��6�K�3�� 4����Z�9��PNs�9�B���E�+Չ��㙑k���7�[� �^��'���C���<��}��C�G�<2Q�(Ừ���b�o����o����J�~����~R�������~Sm�}�Iy��G�Q~��ʏ��$?�O�~����s�<��� ��<������}z^������������/nDh� -&�B�p��Cx -/�-|���eDYQN���"@TE%QYTUE5Q]�����%j�:���'���h$�&��̈́E ��"L��)�Ds�B��Dk�F��"FĊ8/D;�^tE'�Yt]E7� �����%z�>���'���Cb�$�!"Q I"Y��ab���(�&F�t�!ƈ�E���b�'Ƌ b��$�ţb�xLL��<��b��%���1W����P<)���b�xF<+�ϋċb�<@�|��;��z/��o��]:�. ot�")�B�&��;�.��Z|%wR�o�wb��Ϙ�Q���.i��8$�E����U����i���8+�<��"W�..�S)��e���(��uqC�Q(w.���M�%�S��=F��H+��:s9J�u7�����x�d��^M������z-��^Gm�l9�n&7����< ���o��A8�;�P�—���Xf2��_�ǿ�"o�Gvl���G���{���z���O�?���� �� -�D�����z��No�w�;���z7��ޅh~�hM)���A��*�1�K���yg���/�T��'��S�������4}�>C���ҟ�g�s���<}��@_�?�/ҟ��O���K����P�N���e=���@o�7��M��z��L��!�E���&\_���W��W ����p=B�ԣ��z ���Jo�G�u���f�Ҟd ��A�~�?����!>��C�--���ŁV���&`#��%V6���my4��<���ގ�/�M.']7+r�u�_ɧ\�h�9�a4�F�HE���%m��"���$��1��eCI%Zԁ -�"�D+�*�*�F��4�֤�hmZ�֥�h}ڀ6��hc}��2� �G �|��ă6�MimF-4�Zi ՗A?��A��ϋxS��Qw�A=���>ԗ��2�,���a�-<�M4ߛx�rq>'�FQ�)���!���̟�p:�g"��� �������6@7�W7y3���fT��Q��UL���]D�������ռ|�ѻ�ȕV��$}*ݦޣ(ϔ�����P d���r�X=�����h��X� �}o�> C��\���7�:R���W��*�? �F�����C^UɓZ��f�µ�Z��^���C�Z:�c�Tm�6_[���-�^��j����V�+�{m�vX;�����/�A���UdU#�A� �bh{ڕ���:���L:�N�3�\��>G�ҕt5}�n���-t'����Qz���y���b��1��*�@V�5aV�Z�8֑ug}�@6��`�,�MbS�,6�-f/�e�U���g��l+���\v�{� o #=���l>�O��|������W�Z��o��@��4�� ]���E(�BO�U -p��p�� p��p�>L�^��[A�RO������¿�G�FO��>�wz:�=z���1�S�a�{�Lyީ�p�� �'},���|W�g}<������Ƀ�$h� �Z��=��MA�C��B�]�� b8 ~m�#�3Ù���>��!�O ���9��\�q�8q\�8.D�D!�O!��ǧ�gթ��` MLԤ�kQ/��Aߠ��X���G�֧e6�e6�� �K��C4�aZ�Z�QZ �1Z௴ -��*�����:�S���4`hҔ�������F����|��<�0���;m�m�"m0�6hӗVDI�2K�,K�����jX^�!�jX���HC�p��h�` �B�Zis�!��P�`mP��T�!i -`9�0b���,ğ���uw�.b@3M����=�ވ]= h�?���#4�p�5�o/�E�A���3~{�e�Z}$���)�@Ǐ�4~����4������~`�t��p�A� X4�h �k0�. �M'� Z�.=(,�\�;���\�-�$ 0��L���8��y<�<`*op$oJl'�.�Md��B�!��Bt�,���+��C�u?�&���5��.z��7���\��cȵ� C��8�P��/���?��X�EZ<��}ĩb�2�Ȥev����I�r77j��C��{C$^�<�얁d0YFVhյ�Z�6��Ͻq7�@� �dI�)� �R5�a�SK֤ʚ���i�vaԅ�C�=7�Wf���C}_��Q-��>i$�G�rUmCci �-D��8>V�� x�|v@�� }��?�?B*b�Dy -���5�q�q�׆�Y�/���gɅ��w����t �k�m�By�S 5I�T�d2�\%�xR��G�B>^Ԕl=2�����R�IA� -Z�`��|w^K�g�i��wm ̄����4G{� cô=8+��Y�3ΊC8+���S� -P�aVP79+�|��-�N�����OZS�'�+��H���$}��>iOI���-�r���}: dh:��H�0P�x:I�*��/�3���s:G�:W> :O> :_�9]���j!�B]�$�C}�h�!} -xM3�h#�> �B� ���D��V���@/-� @3�� b�#Έ�/ ��K���җ��R�2��4� -����*���ʦ+����U@c�@g��k@k���@oS�@sS�j��it�1ØA����S�)�I!� � -H[��h�S���:�Mq~���#��F�B�a�[I:Φ �M��D�?�d���ȑ��98��\�35�|�7���5�t yv1H.)���b}�@������� G��]��T���'ʊ7,�*^���j?뚷f���*�@� �i��w]4�a�� �Dݣ��qmZ���^��&.9UWV�Z���o72�d� �FRH6���k��R�/����K<˽����6hA�~U�Z>��ۖi��~��(�����?&�����NK���o��x;P� �������juc7�8��G�l������������>��3����-~��.�lV�i�R74����ofV�������G"���ګ�� �5��ݙ��5�D�6�v.>���#!�&z��7vv�R���ŵ?���t�) ���:���/[���x^ �죋k����ٓ��\�X�7�-k���s�笭�h�.��D��N?t��M^Y�������Z��q�J'�Ş?��1������0���&�x��/� ����oZ�����0H��K�����%��.�����`Ϥ -m�o�αԐ�k󊖀��s�"\B�܄�jD:�=9�1aՌ�FF����=�|�3l���u_YҖU=��ɉ_���кa��+M�%���<�;g�p�k�հ��sv��ޱ�~;��a�o��>�����GԈ��3�o��Uk/u�>��S>S(s���q����'D�{�?�������Uu↩���z�ϥz4��؃�^z�D�W�M�"�W7|����� X��S~i�f���ٶ�&<�c۬w��~(�Ն+&�4z�Á��>?�҃{ξf��dN�6��y��ͭk�H�����7wTk���_�m�z���L#�U�Tl*큋k���>'�O���a�nSL�*�'*Xʛ|§oJf���M;�'ٕ -�r� ��3�fevM���9*��%�,� �a��b ,\������ı�#22S'�$��J �HO�o�5I����شĬ�����c���&v�L��9���m֕=Q�0�'��p�D�3���OL��e'S,��V�&ڡ�����Sғ��}�R�Mlc3�S�*5�n�s^�;+������v�oY>wbV��Oo�+��g�Wy�\�1?��R�Қ����"��3z���o=�I�ͪWs���6X2�0��#;5���o��!g*���-�+�r� ��_�g -����23��ɋV��A�a]��幖�u�V�_�_5�_�N'�}7-�T��jn���� -�=�Ь�_��jqu��M]Q�Ψ���_��3}G��?>��-��?w���?�>U��mq�{W������`s2��M���.�ˁ�:���O����a������کa�|뚝}΂�f ua� ,��0Š���8 �,�u���YZ�����R�^�*��Ə�|� �\Ҙ,`n�r�R�rI%3�$��D�Z[.�����.*�+���$%�4�̳��GX�B�:R�6����kk[j� -�\�RɤȲ��ԥ{�M�M� ��v��=�bZ_WsΌ�_�2q�{� (�m�1卭Gr?˳�_���I� )�ێ�\m��.Gٍ���j\��>��se�;a����/o��淎6E�x�ܕ'6t>�סŵc�}:��G��?�����9���WR�� Ҟ{t� ���Ɔ�gB��o*����S��[��˾�b����7���z�.dR8k�)񅤯_�μp����.tߗt��W�L� ��BnW -w}��u���ݤZ�x-�6n�?�I~f Ұ���K�%��/�����=��ܒj і�%���ť�%�O�t������c�K�[�����If�����(gX#B#��Q��C�&�b/�X�zXFfzjbp�/�^n��;fd���yU�N� -}�F��,)f��$�����y���8&dOz��F���ʗ��v��ŨY^^��+���2�;�ғ�f�x��j�/w-�V{�f��g��_r[�� -o:+ͣ -_}���ۛ��3��y�{�� gl�Uy��������Ⱦ#��C[?Z�����=th��K/��>���,��zخȳ��k�7B~٘`��c[�~W����sut'{/;��g����/~�11aB�����8�W�Sml��~��˱#󖞱tj���O��˜��J���]�����}2�頖���n���hT���'�{?^�7�Rp����:α�~0kP<�6"�U�T;_~F�)4\�շ�]^�Ψ��rP$������NM���]����LF<���A�sȬ�ڬ��QA�t��v��$��Ι,�l�P3��Œ+y[Lr��umv���d����<�\��f���ݝO�^�gil2t0�-�˫/�:�����\����sW�,��:�U���%���碬�\qv!6],/�M�5��,�B������5'`p�K~�8�����&����;��k�Ys��o\픖Rcu|��[\zs��)g��^�~�H�gӛ��{��'&��p��n� ݿ�틷��,� ?1����e^MIm�g�u -��9��5N�I>0��/&�x�ߐk�~��se��E��x�gO��>�u�z}t8ၰE�Dž,yw����_�y��[ʪ/ܟ��\��f]ڽt�;[W���ؙ��״h O6?��+CF���?6��4��+����bĆ���� ��ԝ{��7-U]~y�X\���9丑U�����>�4i��/w;Y�����).�x���/O�:��"d,�$I$�¯D�N� ��2 9�I*I#��xHe��j�S�; -��d�c�L&�M�9=���f�E8-����{�kUn;�����g�7/�?p�L~�]�y�w��ZW/��ʆ,ϯ2�l����7F�Ϯ�����yl� Ϧ�W����N��~>y��u��� �"�ԣW��z g&�������|�-�[\)x���]�G~�%{�ǘ������e��n�2f���Z������6W���vz��������ݴ[{���[�<��Ь y�Tn��_��}s��䁧t��v�����t�����c?�8"���C����e��h�#��>t��LZ�������Q����W��5c�衣��F=-Ө�|zz�4�:p�+��3�m�R1�-�|�Rѕ=�>y ��qE��v>�6rlڶ���3�.>�뤆9)�[�x�l1��jJ�NV6�ӓo>�۱�>��im�k��ϧ�}��͒�8�ј�]��?��[�>�m��ٿ|�����Z眪��w_i�9e����~��{��~����s�S��=C�. 7��O�VU޽�������SҔ7�mjr`�-�N}��e+/m���c�fr�˻k�QY�+,x���}f/�5��{�_�ھ"��r �~���/v _���GO���s� G�\;����?K����x��I ]S�+��\ٗ}N�|--}J�OͿ6����z����=6���N��}C۾q� B���V_s�8ϧ`ڗv.x�i}�I9a���H�'�aB�����}�[�d#��9{w�~Z����eI ��5��0�duNkkx�q�4ϋj��״^�K���L7 � E�\�{q���y��mvF��"7>fY&���5/��4ӲXW���F,�X�ͦ� F�U�ʢ��3-�����J���ע�e��b�&�NT�H%��h6e��Z�,{�`/��z�8�q�n��2��x���4cu� ��N`��*�g�|�vT�.�6'w�.��ϣH�!�����B���l[7m�-����g�N9�^�5�V���H��YP�K� '+�d��π���և�L�:Y��l�}ri���l��1X)4�I�lr�1Q�~*�~&F�e#�CK&V���8�T����'����S�x"��Ax�QK�SA�B"@`�q08��b�A�#1���Uƾ?�� �s%h����3l���~��K`게+�u�6�o�ק�?%��i��W(�SN?}�����Qn�Q����X_o�6 ϧ0 I��c�rM�,k:���^����;��e�r.i�O��}�Q�lI�ݥC��e�")�GR?���a�r|9 ���5�^���� >=�� =�i:�R�,h(�Qe��0�ly\űZ�X��Qu�� ~Ǜ�B��b�R�Z�{V��~�8//`oЃ��q�c��ⳋۓ�ON����ek��0�7E�ӊ�,8���$\��e?a)��Wxht�g�aFl��|g%�Vkip�՞J��ZmQQ�fV����]d��[]S�i=,׈mH#_�&� �����)C��FZ�E��O���`&tU�L�Q��"�P�l�U�&�tʚ��u9e���_�q�6�IKhiG�a%yƅ�/H�V�%[C~RK����y���sH�=�`�rym��ʚ2p��+\�H������"��^,�E�\��BE��Y�ߑ�xA�V�WtCש�%U$w�U�B:�]����� 6���pe���q~_-P3��%+��Qa�FUU�˻w׊��С�G�zv ��#;���U<�-�N mr� �����SX������ -��|�d} -łҠF�B�j��#2� ڛ���f,��o -x%�`-ѿq�.�����&YdYI4DU*���}�W�h�5jP�Tk8��G�07tk���騥�_ ���4���>}��̄;��S]���)~�nQౌ��>1�N�s��\j�W�,a_�Q���}B X���k̺�Q�[�\���wb����3G%E�4��H�/����y��{����ox�f�EPӆ�$w�$�>�""�pO� �H��Ks�0����[+;'����ӫ���D�.r��k�`y=���%e:x�F�B���a'�l�K�L�Z*{̳�9s�Q�f<��ӈ�PĭRk��L �+p0iƸGg�����T�'z��غ8pu�J�{ք@��$))�C�7s���QG�k;��dž��l����i�Skh]�iԷǙ���Ԛ��|�Ƙm-撷�'�g��+>�=>~�w6*e1n� �H��ׇ?�t/h�8X]a���c����/f�q�� ���<��~��GXh��� �$��O�ǼK y�o���(�Aa��@@ �EV DKY��uq����Z�n�6��+8JP�@dMP�@�Ͷ@w��� Y�,6)��� ɿ����HJv3S� (�����T���?a�b9���!JV���� &l!�����t�y��~��uN��ws{{���ן�ߙOXD�Ĺ��R`>�<��EjA�2O�4��9��cY`��LbQN.�cF�eA _J��y�����[���H � -(��U����Y�n7I�G�nM����$�"=}!��C"��B H��f0B(J*c�I8��A���v!H�Hs �g}�t,% w~�� ��� \9�)��y�Zm bJ\�U�1$�C&2�i�cW�^���� -L�36q��7dK�:C�|ą.��m��0�� -u���*��v��J��v"9LR��*��Д!�Mf�g~R�9�+��-!��.��V�p]&@��z�����^@��r�gEe��C Ͱ�P ڐ���\�qǖ]C�6�>���]`D��k0�\������2$Y`:_V@$�Og����#��:�'U ��4�b�|!��º���}�h�������4>/7�H$�׹O�&�e��"�@S�0Z{��� �4X�aq�mLm34o݅�������Eu�J��7Ak;��–�B�v(�a�G�'�f�Ҩm������;K�-8*��؄�>�,��,Z�[��]��V��?��q6QF��;S�}(#��B�b1(���ge��� -�K� ���Gs (�Ħ��81�[� ��_!m���.Кn��CS��ağT�"��((�\�c��R,�}\@���� YbSk3�Ԩ.�ଯF���U#_LҀf�A���=��H�2Ut�)��ۥ���.Нa�{��[��B�ɕ�S&���Kd�|�R_S|�p|��r��0��2p�9��������٦z����}|8��U�fy�i�Ҭ�P��@��|r��g��[�l�b����q[j�Z�����IUL�����|gk_�O� �<#��c����n�V@��ԩ���)^��#�s��s9g�����fr �o-4ɯ��4C;���� ��Z�n�Z�h�͕Z�PI�>�|���}s�2�ƶ�Ow�)s1� � o���G3��Te�s��٭�48�5U��[��]k�w5W��}1q����8���{�6��JA���������o�h׾X��-/ֿ�.���p]˅~5Ğ�rD1 6���(��{�M��n�E�awL�ZQu i�N���`�+����<�'��A�\�X_RX��&�]���h�f�cAê�C?0~�Si�"�L�&�u����9�Wu<7d�����1evB�^��*������#���AC�H4#rTC�Xʜ�q�jI�8��S�֙��Q�l��~��/�?�X_��Xmo�8 ��_�����\��p��mE���;là�t,̖ Kn ��G�Eq�ή+���H�|����e:�j��Ly�R�\,�+��!�,r�" 'Q��S�Z���o^^.����5���},��q^f� ��� X�׉�R�Ҽ��FW�")�|� PT��VlS�+* NZ�+�*�2��+�� sĆ-�jd,� � n�v�`��b]re�­4_�C���vR,�:�X+�.���PnQB�׀)�1 i�i�[��Ҿ8FH����"� A�श�k��KETB�r�%�L��Ch_2�TQ)��J�h�`T�"B酳�aq�\8����ku�]��eD� N�G���; S&��.�׾�ZZ�����H��1& �:����X��i�@�6�v���5&��`�q�H?��&8H�Ո`�6j��?�@f� ���ΡP��UP[fin$�Zdh�S�֚�(ҍqL�"E��OY�>������W�/Ȃ��T}�b��ؗ�B���퐼��\8L� 4+A�-���O�繂�0}t7~]�-�h�Gc�ځ��3�ԚϺ���Ԣ{$o�W��k��5d���� ���.~8��ᔷ���gW������%H��4�-�P�H�U���>ŇQ�w Ò��N�(&�pMgM6$]��z�A6F�۴�m��P���Al|5\�&��]`ab�d��_VH�f�Y�k24��r��izH�乄 d��*���w��O�4�j��B�\`��XQh�<���3X�8����U��]��K .�m�zݯٺ(w�,reux(|5���o����x�4�~�NJr���(�Y;|?ӄ��L��S�e�%}ӣ&w�psG�ǥ�h�s���e z���9sK%����m��<�N���w1;>�*�ќ4ӣKw:/h���1�4 ]�OO#x�m�Z[q���ɰ�z;z���=��4L�;z�6,���v���tx ��n����#����L�-����Z���yV+SZTs�Fw�=����[� J�ª�l���@3p�5�B[f�Lw[C#�T����K�4���/ -��#��)��uG�&����g��+}�5 tK������YDv�dJ���2�n�h'�Ƈ����߾k��N<������[o��*����"��H�$1����9�%��HXL���qv9�A�6�n�<�?߾�p}u*v@�nT��� -�ơP�УQ�6�_�%&���_������W��H��d��'.������`�R�Ι��gOD�Ԋ��!Sl�#1��x���>��@`�b1����������W�n�6��S�) -ǎ���؎e�-�����[�%Q+�\R���:F�=4��F��O�G()�IIk� x��p��7� G��S�Pb����!�����7lN���frq���J�:��KFEo�k��9������[L�_�g;���!m^�����l�� ���"*P�8��PF���7i�V��)�-'�g��7��o7���Q��)�����}��FT -�4,�` ��4�k� �KǾ�Q -m�� -Uo*!��3�Zâ�tT���_��8X͘l�-o!�`��+OK� ���D�� Hp��|���$��CWe(���f�dw5k���#" �Cz�`u -��zoNڞe���;t\&��5�qY�v� -D徣���Q��B��2��.�zS���@�����V���y��8�w��eG��Kq�%���B/e�K��zFp�&�ϗgg�`��8��$��� )��չ4����&�Z�<���$��_Q.��[�f����)� -�t���ڭ���o�%������MF�F����-����b�ǀ ))��L{�E���V���֠s�}��b�3�l����F��F���"�mTL��Y���U^��6c�_��*��6�����\v[fꉤn�<��bj\��Ի�]�>��l�)�}fAF. -U����;�;�8�r+@��נ -S��V��x�P�Lc�'GTV�FR��,�ڬq�{��P�X�q���}j7е�������2ƼIpwa8%8����e��BB��e� ?�@~ -���x�`�v��~O�0#�������?�>���c}�%W"8���O�^�����a �Z_����_�nFM$�ح���+�p�p���o78����G`��Rtݶ#�ٳ@0B���,�@�nQ��fq s��فv4���<+˓� 6&�!Z!*>��0�W=OE� �w�3�M�����t��J��/N��� -E� -N�r��A״W�H��Z���`Zb.����-Р�A����l)���N_����! ��Q��� �X{k/$G~je0�'b(��V{��5`�1l���f�L�w��'D���������#R� F��nٟ#�{gw��PWe�L���~}�T �u2 ����+Іz��q����鐺j{"HnOޫ0��-F��X��J�E��C�����8������/secJ���c�e�7�� ��r��� ��x=h3� ~�DŊ���Z =^���u|�t��w<���Q����[]r�6~�)x:�L�"%�u�R�i���3��G��L4 ���cߤ��z���(RI�2�ؕ�`�gw������Wg׃�כO�>����P���3h�gy�ɡ:��@�����/�s�ԗ�{BL5�WD��� ��Lh�q�!�����B��un-�%c��o�cڇ�x�8�0Ėf�0���x"� C]Iu�E�I=�c�%oTA�=��[zr� �3��8p��L镦���68��д���O�g�E����0�é7u�h�ϳ����%�p��\�bG\!C�kqp�\"��XxI��1���qD�;����~ �$�}�B�J������+�=�8������'a�H�B���m,g7E<⺘�a�����9�?��฻U( �X���ܪ�JX(�}+x䈈��\1��( H4�d�$�H��"�+l-��M�c�f�Ӌ:>��>'��HuR��jR-ޮ�M� �.3=�d�p��N*8{{�g�/dw�'{G��#"� � `�@l�P�GL�|�@_b��1[zT`���N.�t�����e`�^QI��}k�4��ܑO}`B�Z�rX���66yR�ݛ5��A� ��A�V6��f��pfV�Bƍ���j��i��ZA�lUz"�6�W(�29P�mߏCa��z �����v�gvޞ�ҜI�Omμ��E�O䳤����k�#�l|,6�J�2y0��ͫV�ܘ����<�l��d6��q]fr��g�h��wE�ȝׇ�cFW� ������������� -,e���l��G�a�_��}Q�` ۅ8,��C>O�䓦�>jV��!�!qב��b�Y��x<,�w���Wo(��mCA�P��t��F��uQ��+(��.m���H�9�\�N��z��RK����d2���)��NƦy\�)3J��+5[�6���/\��C��wO�;=���%�h+�9\�~�ˆ�yəέ���0W���@���5Jim��$�tA�� -$��$fW,mZ�C���R��J��� ]���q������ڼ�%��5���M�[2�څ����%^Z��DczH��[���!�3���p��퓷?b��ȿ�t�[����x�NF ����R_x^�? f�&Aq��P~b�8�eL-��� �)v��1��xi|�(�~1j�r��f8h}aDl�v���LfM��ښ�. 3}�������E�����s -.�H%}�%��vg�Н�*9F�}��zx�s8���Yzn�#G��2c> -E ��ňeʏ%fmO�,j���R�3{F�T3L�׫�pD��~�����0�j��ɨ�I��C�6E23 ��nǬ3ɖ>�'G�Y� KO~[�ŕM��0���+F��� ��+T����\9�t3Ա-{RZ~=��Ͷ�ʊ������;����v�X�`L�]���^+@g|E�P?�>�z��v~��SIv�P5sx�e2��:T~;s�Y�F����P�.>3F�>*p����B ���� @NM�ؠ��xX>(���7�%����~�r9��R���B��M� �f2�pC�J�ĻB����9'��!�+"e*oJ��zvL&R`�[`|��C�����×?Q�e�PDn����T���ht@ڃmjA��P�"�0����Ŝl�X�`BM}�(0� -�i�o�B5��jR~�c��%��xR�ԭ[/��s)ZJ��.ώ���l����6��7�Gdؗ�گ���M;i�<@�2�#�y�v���qW]B���D}%,���g�{>�ŕM��0���+F��� ��+T����s�8��PǶ�Ii��L�6�. -R�jog<���c;��66yW�7�� -�_�{*��ÇWo���rcuJ �.�f�L�Y�C�3���h�Zk �����3c���LA,��H����� :N����C�1��|�]���OX.�1��/m�P+m*H���n(QI�xW�Ж�:��=dt""SySZo�C� `2��z��M�5���1��×?P�ec���F� b~�:�˳S�<�(�RhO�PGS+�γ� T����(�#�P~/�6��->˽,���|Nyr"�^��f�EN7��G�l��� ���� �ކ�;uj�d����X��� �������Ѿ�'�h����w� ��N�F<ɓw.������A^�_.D��y��1��L�E�I��6ڷ^5�vI���tu�E�f-<��>Q�C K��(�Y���Ֆ_o� ���)N�/�ަ�NUUڔ�i��=W_j ��|�v���ץ���͜��~p��۶Р�\Ɍ|X�'����˧��x���#���M���fi3R:�?%�_-t� �.$��F`] ��;�Q+��:eHZ�ՔaF��і� @�+-�B�l��[����tʟ �x�)i�����G��/�\F�TX$`u� ˆ[�s��6#��$\R���x3�b�Pl�-�-3\;{�T�����a T� �d8k��F��j�n�x���&�v��. -*t�*zN�O��AE>�f2���x -��������A�5�F�YgA�?1�r�ь������c���&��/��•�}h��q��*��h`��=��3�n��3�s/F���ԕ��Eb&T�x5��쏮d��,�!كqP�Ԏ�r�Y'Bnp;{2F�,����r��K�Wՠ�R�,8��շt�10�����{�ڽ�9=u��c>9�}�lc!��*C~��+��+%��F�� �/��慿�[3 �{<��g����E���o�� ���S�}� -5l�͖QO�0 ���Q�Y�7��!���;�$�3JSo�.M"�-۷�M;@b�6MZ�8��g�V��ucE�w��5�����q�B>-.����_�ڪ;�XȚ(�d[�P�ʯg(���X���Wm��}��7�G)�j ������m~!DkW��k� 0���Ȓ{M 8z�HT+�"�"� �T6�ٙhJc m -�h�*�q� -���4w?϶�x�ui��?��9j4��bB����s�A@���&��]� -�  ���#V�;���H܎3�$�]0�ѡ$Kc�B��������*p,w�nY�����Ʒo������0Ur����s[Zӯ�O0a � �Ԣ۳G}�;1�!tƷq"���*-�r���$0�~l <��0 ��_ԟ4��`�v��Xp�S!P�js,�]|�8 ��^��3�` g����3'�{�͏�(�ҟ��͖Qk�0 ���)�����Ƒt�q{�`p�sq���FV����q��`�][ -ͣ�ߒ~�%��m#:����f�C -��Uhׅ|]<^�Jq7�ʵQ!�(���5���eњ��Wn3��Y -Ū5����\7��9�ª�W -9�5�+!r�0�*�����s0�$k�)�FU������B�� ��0`�y[HO�A3T)L�P�J���6�γ�5�9]�� fM�9�Hh�D.�}Nb�@���)��1��K�;��>�γ��3Rl�yR��`��$+4!�������j�=��|�2h/���=���r��n ��0U���#�w�mi�_hU�^��Q��%{`��ܿ�x!�еa"��ܛ* �����$0��}lG <��0 ��_�S�I0|;��Xļ�B����T���{h�4XN{Q�%�3` ��rh����׽s��]�g�/;�YM��6 ��WpxN�MN������&�C/��y�� 5ErH�k��"%�֮�(R���HZ�@���T���y4z�����hir������G�>߾�K%�g$����!�O������fs�!̼���R ~�����`gZT୐������o�ceT��?����?R�~��U�h����B���>*%���L�J��a- [K2�,�%M��١�|֩~�·��TJ��V���s�D�2,x!��H>�ܽ��c� -�v�m�)l2�Z�N����the�IS�h�.ZeB3H ��B�t�km0? �ޭp��2�b����Dc{�X�uP�#���[Ro�?@�"��� �����ƅ��@6�h�Ih1��1;9�jI��"{z�}�}g���q����Cf��+�qB{���_�;·����O�IS� RAF���%�k��������|Kn���"�'���v��3�-!D����W�p��z�T�����=>� J�FjO��~���Z��9:I�Ə!I��~o"H��J� ����9R;���-kb�=h��00��A��ftX��5�`Xc�� "S¯�a�y_�\1,Xr�A�]f�:���:��O?G'���O���4+ho!�'O�h���a��=\���C��z ���H�y�~��D�������3��-Z�1�+���ƪ�"|��<�j���Ț�b� t�)���o� -���Q� ����A��<�� P���z�:��դƭ?N�t�j�J&XM�{�)����&��Z��E�f-J�{e$��l�l����S�<��5�[�O��H>62o�m����Pj�!� ��O���$ "S�*C��n۹��X�ʅ� -}m���k��d�������D�v��� ��q�P'�Kr�Pv���;� 5�fz��_�'�=��,���/�XKs�6��W`x�1��:���Ǔtz�L�= �$Q����_�Ń�����Д���.�o���Ӷd�r%��Nj�T�e��n�~y�{F>��-����v�5���<ǧ ��Rm/$�����b�]Cw�s���)�e�_�#d�[-��l��ˁ�N���6PS��_�AS�:*]ڟ� �� �@�X!���������F.�`�&|���.�^�A;����1��L��k����-�� -���N����\p�[f�+��ᒊ^b�%Xf�v��V׽aB��{B����̇�\gd�Q�|)>kj�2a�Rw�J���h Q;�H�E��6 q��Z��k�k���)��Q��x -��`��H!�U��|��am4�LF�� ˹�|�Z��a�������Hh�@��@L� ���� "��#7~ID,���4[efO�5� "o��EL��3iS��ٟ�ɮ-Г�"Q'�Fi0΃�2��w���Ix$jز������N��X!+,�T*\@6䨃���k嫤F .�x���L^���C�>�� �DD�%6�ٳ%���*�OM��������6��I*�ZL�}WbA�CM� ;�x9ױ]���!��Bq82��'���߽��v�){���Q gC��{g�+�J�i|���� �_�u����� D�L\"T��p״��k�-I��}JM����$���؃���� �g㶏���c��0�TUYp��ܺ�k�?���7�D�u``�����FJ��T��cD$����2�ƺd50^��1.�(�����\�m���)��Qd�O�Q���t�y\v�E}v��/O��9k�L �S�����?�nx�v^�g���O���?|z{60�{�A��:կ���O˰�ώ�2��������v�#�}���P1֩ m�ōU'Y�@eIZ���?� Ȋb���i�d=0�Ȋ�X� Xq�}P�2���T���[wq>�=�M<���u[�;���KY�o{�?S������t��G�>I*�ε<[�y0}d�;��Ay9AA[����͖Qk�0 ���)�����Ƒt�q{�`p�sq�1��FR����q��`�][ -ͣ�,��%��m+:@2��f�C -p�Wƭ ��x����n~�k��DtvTȚ9�̲h�B*��9��PK�j�-�= ǐ�=J�T��B�'���9l\E��xw�8Yrk�)G���� Vlt!W�R4;C�4��=�f�R����j-�]��g;k<�^���LM� -훈��\� ��$v���N��m��1rw3}��g�g�X� ��_��ǒ���I��W �j@���U�b��u�w�>�q|�6�k_�pk��݀�2(�!�cnKk�u`����؂<�����Q��K�����&±�ѿ��� (i?L�1�����Ǵ&�p�zJ�? �o'����{* -QmOe���C��rڋZ.ٟc�(�����8{�;o����,�d��͖Mo�0����� �[��Tݨ9��JMϕ1�0Zc#{ ����f��MP�p<��}<���)��:4:�?f7���&C�I��jy}����*�J8�|�v ωʟQ�Y���if(rVr���J�/�-+-�]� ep�E����W��������f�r�\'��k6P�`�}^ԉ p�Hʄ��r>��a� -i���I�a_!���͵��!�׌L���0$;i1`���ʁ��� &��M���v�7���#���^�8�X>c�F]�)8� -5�J�Fu�V�u��9X`��V8&-�V�|�S�/����<�����g�= c'RN�E�z�J�O jᇛl�$�= TY}d�Z�_b��P���D8V�5[�*��/�$0 -l�^��ᙘ���� '�[���=a�؍e�w�]��2�F���9F�qQ��1�q�s� ��-���<͖MO�0 ����(wV�!�b'��g���&U�vݿ'MW>lդ�hٵ�'�P㫦P�F�� ?��q�Z�L�eŸ�� ή�'�P����%<'*/��[�2/3�L4R��lQ)����r�݂Rw���|~�4�J����5tz�X� ���G�ҢuK�^t�? -� -,R��\^��%r�H��/@9o���T*I넗� -�,��2\@�h�}�8ꭍψT�ڙ!� +�&L�)����X/"z���h]z���oA���>口��'�oܑ���m@RӾ4 ��B��m�U�}\�c�"�U�8%�QPۺ�0wmg���%�C�v��w -*6��W���i�������� �"UV�تV�V�G��4� �<�f��(��b����ǰ�0 gb�'�>��(���_�^�X�ZXe�vO]��2l�^^���8*�Pf��8���F�{��(�O��VMs�0��W�����[���!S:�)M�����uP#KiMC}�B�6� ����v�{뵇W�����M���;h�˵�K�|6�� �jt1TF����T,���$�h��>w��Ġ�1����:�XYb�Ra*�`gqt0,��0tK���H��JE!M�p��δѴN��2�Us���X����r�a���5�2��}6�� -��J��R��+���� C��q2�| �.��&m�O ���&_&����1�7��{�(�c�S�t)�,�MλHgwhƜ�K�6׭M�K�=�X[H�C>8����"��ei*En�x��� �_55u���Wk���8��5{D6� ��r���\�h s.cm��G�Rl��X9�V�c/3���Z��k�@nY@�X`�1���6��s�ϱ�Y?��]L��_ �-�2�a�cz��ty�4�8�����^o�'�-�S�Ik�T����Ɗ��G��Y�r7�~A�#��f:;-��z�M�c�Q�}U�p��Q��l��Qmp�`�f݉D[,~�{���՚ۈAK�5�'��67��kK�'z��:� �W�n�0��+�k5E[�� p��h�^= 45��Ȥ@R^����Nl��b ���x޼���}��Gd�%]z��H H�|!g.��o?\Qrݹh�C�X�����W�8�}�jI��ќ� �"�v�C߂fViJ$�����]��!m1�#��������������M'�'W�X&m����������% %�{������4R�1_�Ƃib�1�ia�[��l۶�g?�{����^����}����%Oo2�y?z�+�O5�v'�ao0��?׈� z� -��ȷl��M�����TX'����j�6�_�[�,2@ �� -^-ˆ���]�4N��H���,*-E��Z��p5�3�n L��$ώ.Q�?��iC �����j���EJ�����i�� -ڕ3���6�t�!�z�^��DI�Z.�? h$���l# �`) �q�*�m��P��c�Y{��X�̧HOJA��T�v;4���e�1I��a#�"����;�0�شߡvZ�r~ -�#-"4[gu�۴������0���i��OM�i���$�wlb$ԗr{�h��M~F�@���Y;�i��V��"!�G� 1�{h� /�RF���4?ºA�B��+�Gȇ�Tf��n�"W��MM|Y�B�h;�+s�����;���*3��� �ܑnc3������} *�e�Z���QTg�މ��w�W&u�w�� ��Di��}��rX ���`�.��H�ўtKp�"�f7�}"1��o� -1� JMv�{�-�7*L�q��٘�7��xk�,& -���q��� zd��y\L1~|�!u'��!� ϧ�1��0y��4��8s�w��:!gJ�qlyLu��R�9|��ڤ��N�� {�NBy�l���\������� ����Y�ԓ7BYP|������?X����J1��}��p�NZ݈L��v*BD;�+���ӓ��C{���Bq'��;<�n��k��A[�8����m4-9֋��5°��҈ a -W1������5v���,x��1���ޖ�0Zk����a����KK! -�G2����Q5.���f���o�pc廱��0ӡ��%�)���Ȏ���9���ٜ�gU�!x�T�v��3���LJ�j�E��3��_ߥ������0@�~�0w�4ă�,�Y�i�b��$������������|m�c�tISմ�����s�a�*��C��s܆`�EJ��V昐 -�;��>h�q,jgƆ���@b��Rq�‹P� -i�A�&�g�/�F�C�n�߻hQ����F�?� �l��8o��?�=��%��f������~���e��s5Ͳ<g6��g+�Ms�6�����C���t2�3�g��4[�3 Jh ����__��ȊHX&�Ͳe��������C�DHʳI�����,� ������?#����8fXJ����I�T*5�O��e���󌨑q�҂�I4�T�)�Y�exEd�c2��_�x��xEԒ'��8�I��`���K%p�&Q��$� -+�賓sʨ�L���3j��f�7L�Z ����K�����X�\隣��r�qi�%�(��HqB�L���Ň��$V� �Gm̆�Ki���ƣ�Rk%AT!2�6���=�I4rF����Xh�Š"n����7��D�e��ߕ�sk_�l{9Ζ�U�JC�7�ub9�T��B�]?n�8V��a�E ��B�+g F^z.����?�������{�ۙ�|{��n};f4��ou� �$�o? cd���׽O�x�VZR��1�?J�PK�� F4�4!�s5m� �,޴�s#�$"|<�-(|�S[�պ��^S�B��%J��޹�.� =�QCI5ģ{��2�r�0�ߨx�b���j�.a��&��,���$Q�����W`��@8�.A�G�)��k@�=���|<���,��w�� �#� z��fd��/;N>|C��@F� >�$��� ��%��-��a�#�B�SE�5]Q���5�N�!'+~O��Q�Ç��3����ր��;^��w)� -�3��L�f��N -q\�@T���m�%� K��nU��j���k�;;C��]b �+x`B��6�5�6�h5]=�y۾С�fW�o��U�Gz�w}`J�;%�����Z�M�1�x )˴[�&��h���� D�]����F�u�oh�9�qu=��#���������ؾ�^�!.�Z��C�l�jy�Z�9i�y�6㮘Bp��r ��½��'���,_b0 �`� ���v�u=�+s%�Z�NP�s{� ��i��>gnj����Τ�K8Ǩ�{C�DF�7 4H�,F��Q�>&|�u�� ���~(�{�*~I�b�����aS�����*u8��#���F��l��Bu˶{�WV�m׮�$QS�G:�m�ġ'����4�J����C��p�C���s7�8|���kt�]K��� �7�ð��_ ���ƵoX+�xEY3ޚ��6�dx��[g�����Z,��=��S���\6�Vp\� t *iXv�4�i��#;��z�|���4� �B#h��������Qʄ���k%l� �8|l��tr/�ys�;�2|���+W�'Rw�c�-��v^Q���+����A�#�mݽ/" ��������O�x�Ȃ��KO)��vy{5��'�p���K_i󍘳���2)_�I��It������ѵ&���}���[�g���(�>S��� Ju�o�� -\�1$�w���<�9O?gE��U�J��nr9� ��;���P{`J� C^e7�˞P@I#==�����-��s� �mdQ;m�+(ڎ�}#0 � �j��-/0�`��0�V��s�(��g���R� -���=��Kk��%��QY�&�:ǎ| �xM�P���v�g�90XVoT�Y���8���e@��k��>p�ED��c߁�|��p��iB��4ee�Фg5�xd�z�?͖Mo�0 �����0�)�b=m��f�B����,�8���l�)���iQ���"�����h�<:��/��R�ծ@�������RܮoRm��"8[�ɒ���$�Z�e]�ne�OZ�mcL&����i�9ĕª -|�4dr0^��o�H�c��=��Q�?$ѽ�*�C�v �gŨ3�U��E�9�C&kr ����B���1��!~�L�x�tn��;���k�(@hW���.1�H�]j >�A�g -E��B���i2��� -��2ST�Z>�f��*4}�^�ؗ@ ��^y� T��r�3h���睅yj;�t�ȸ�5���C�����#�&7�/ �*�5S�,�#nȞت^��� Zt�_Ǧ$�W��(wqc,�������p��"f���q����d� � ��B���a.�w�04q,�^��#� ` W��Ɲ2�{��(M��� ���N�0E�����[� ����B���0��D8c�v���k�$�d7w�ќ���3�kZKg�A��uK{��M���0/&�22H0�M��2׸��ȂW��1K�z+���F ����/�w_Lre)DI��/ߗ�����$����X�y�i�K���R�{�]Q������_��X���n��m��D��{W�Z��UO��J��G'��Ok1�ởb����l�CW�� -b�Q�l��8 Iv�߾i�C)=��wx�)g� AoB��%���a�Z�G����p�0��&#d�Q�)%?"W�O�uׂM1h�CG$�E���hӑA`u1�+m$�ⷨ�v��t�US��%B���=�VٵN����[�C�����d�&��S�Ӗ�����m���u����^~}`� ���j�0��}�ù��τ1�J��ª�����$$���_��T��zw~�|��C���G�p^Mq���榖zOq�-���l�rż�kO��}"$��lmΉ�x�>NJQ\0�L!UA���qA���� �F��t�͋��"4L�b� � W�^cl4�A����:�}\Fn(�)�]��k�ټ��q���e�I���2��=�V��e�{����]U��������ˇϾ��Ao�0 ����N���m(l�P �Ew.d���ʒ@�i�_?*N��À�r�H<��}b}�1�"�O�1?V�0����i̯�����۫�� *�ܘQ$�U�V�<�>}�"J�� s�y���:EY[������_�@�Rd��I������3`;=Y'�l`��Ŋw�r��w>x�5&�]�ō�6|*��'ׅ�ޖ�4��,J\��~��d����B�^Q��݅a&d�R~͖��,��2�r�|G��8ب{H2"�m���Y���1�Ə<$��ޫbb�Wuu����Pf� �� ����:�N �� �}cBSx� ʳ�pi�O(�q����n(�K�>��k~���W+��p��v�<|��Xn��D�;���pD���� -"Q"� -ݑ���͗���0����Op��Z%]!���.��q����{R�o��i�j0����?���'����f;p^�)���{��H�����������-or���,l6��5��Ͳ-lm+�/ P��l�j]��B9�GC+K!��̈� -�6-o�e�"�JBwܷ^k��2\ I��3O���TZѡ�-��jRF�ӎN$�T(K��Gv �S�&��Q�6�g�uC�l����0:�P�Uu�-��y6����E�� -��\~�V��/� -�.X 8=-�g�Ε���`.4 k}z,�C�hc�]h�������!%�>±�@r���:�� -e4�7}�oo�s��ԕ��z�_�m_����`�*x��^o�Ù�`ԟ��C[Z��rU�"%��[ 3ج��SX�tg�Ӏ�&��(����?�>���g:�'Qg���,���O����!�9Y�\�9�'̙��|o��ȒϮ?k�*:̼��ޕ�6m�W4*��o�����_���j�0��}�ù��]�4' -U٬����IHbqo�l�E�Kww������.' �r�6�q��ISմ�+�� �,�R �!��9B�S�"%�`+sIH�D�:k�q!jg���V -$N�[!��l�JC>D���c5/��GR�*#?�������F�����!�Tv��xT��y�/���k��"߽���g��������1k�0������ZI��b9���KC�Lh7UVS�$$Ť�>J��R:6�{����+���@�}h-q�eSM�6-�9֢zX ,�I�� L��!F��Xj�;�ƞ2ґ�vGc8>����R_N#��tpRi�C�M��BY -QR�b���KsLyz%�X�a��j:��CL[�z�=��~��^�KU��4��i���G����u���-�O[ʗ�(ϭ��k�0��{��ǻ��7M˦xrd�9K�6_B�,��/j7�q�|�A��|t0���'���B0�}g���{[=�ڦ��S)AƔ$��Q�\eB��%)j���s�F���{c��HM -J���m��֞+�ɼn�/Ok�Q�S��2�~w^�������%6��(&*~�Z| ����Y/��f������k>��MO�0 ���V�,��0�v�@W�@����"�$Jܲ�{��u|� b��y��N�l��,4��.��H:�K�6�x~Z]�,�L[��٥\l����aJ�:$������ŝ2ѯ|� p����\����|�i�)G�����v:�([�Ll+�.�ׯ��@�"�>��8� F!;�����p�ư�u_����ɅI�4�gR���e�IM���/���UpDi��Zل����$Skh��P�|��q���Zb���Q��p`�r�B�>EmQ@j�(�������vY�;��u�����b��G\c��>�3��to�[�%[&��答�����W���0��WX�Ӱ7���NH�y5�'�Y�cٓ*�{�� ��m�P�J�̼����V�vn�C4� -y?z-:EڸY!�N߿z#���.Wb)��B���m�����^S;r�Y J�����0�>�f N -s�r��M��N�[F��v��Ud���Is��:���������R@9��BV`#Jبa�0є�^�7�5��n"���&UZRO�e��Q�9i(�}_cZ��iA�&xJ@ِ���/}�xAFo����er!�Jd����"����Ѥ(�+ �q�[�S� UJ�3}�<�����> ��|�m�/����T�W`0���s�t����x�<�gB��K8��&Y�8����^jRw{8DB��ѣj���$� 2>+ۻ���^��g�C�$�x,���j\��c(rI�F1m�������������L{ L���iJ� -/�[9�=�6_�����uY+p���h��t�Y� �H����Kk1������;ѮD&#e���q-i&�`� I��7>*]HW��=���-n>:� ��m�c?�!(��ѴḪ��aTtriD�` -�1�!c)en�{�HE�D�:ñ�ۉ�8W�kH�UpB*���*:����x����}Z~�����jV#��R߻Љo��4V�1��O.1iZm�GvC�o6g?���˪�+���\܍/�4��������N�0��>��v�P�B4���2$��qL�ږ�F������v��O��Kg���N:�Mq����i��b]Ww�l� -Ž�kOq��Sb��1�D�@�o{�(>�֙E�C�J"h���r!)��/$��h����Y�2ϋ��UW�؎?�H7F�*#��1w�62q��H�䂒�lJ�<K�O�7N�0�l�����z�*�/�U��YԌ����4���I��Y�r�0��+4��[��!�=%���cF�Ũ�F���.`�1q�:6��f ���oWo���y!���J������T��$~�G�����,b�ZK�ei� wN C��\�j~.��ְ�d�qpE�Q? :���d�A@$-�j��`�[g�D0w S��Rge��r.l1%�3%s�,>�P�x���G^�<^s㪡`�q��D�D ����x�/:� E��z;��k�^�=���~ -�a��-^$`D�H({��DY{�O��i�����ꍖ��+���0k�6�=�q�t���vV��b���d�Q>hK�YA����=Ǫ���â�K#B�$S�x�� K�����%#Y�aw6��Dho��D8'����~ E ��B_¸*K�F�s��CIRm嗱��Ĵh��xu���^yd/��ts�}���fU-��X�����mf�Z�?ki������Mk1@���a��hOE�ڮP�d�=f�5'!���������� �y����B�C4��>�&�C��U=~zA�=����LQ�&%?d,S�7�q��tb1(����ߤ njHO�!�;�TZ�����$��1���%B'�>S��d�qjm�ڞ1/: }��J���Ee�]ή���-���-6���k�YMo��G��||T��V�r�0��+4�����!\8t��;ki �f��8��$&-��K�E��{�v�d�7�ҫrr*}=y�CօE��ܽ�F���Ui<��rrH�nD�ۢȫIl��nP��F�y�}�g��>����� ZXb�`������R%v���ô}�-� �T����PH­����� -�� �Tz>�VI@��+�\���u�c[{����}Ɔ$�X2�'�m��0vQrK��e�9�T�*�)�����ds�+rv�W)���d�;Y]��|?�S��q����Cl�M�W�4��i��2p=�^���]�X������G} �4dw< �[rA��0�@�;;3����,�z~�������s <�Ѡ�<��g3F;���x�o�l��M/.zq� ��c��4ߣ�IgԆg�ʥ�n?��>�rI�OБ��`�;i�C��lͻ�ay)=���K�p�E����m��I�9����VMo�0��WX>��M� U�VU��\9��kplk]sfE��Re�_~_�1�JgB%�����v|Ǚ��IH��Z��8 (P�q��A��hl2��薉���v� S8�'����� -�GʞIW���@�`V�`{\�˿)����2ZYT�i�b/J�W��L���#(��2l<��u���@+9�z���5��`mF� ��7�� -�,5�ЇH��2�f���~�f��u3��B��ӱ>Q�4�k��E_x���T��q[G,X9���}�&}��|v���+t�Y�8R~ --lد�!����"���v -4�b�� },v3��+�r1���Hf�f�f� o�k�j�X��|������eeP{3��k��a?���5����W�����1��0�O -�{� ��m��G��יb��kcz7i�щ�9m� �C���7'�Uu���3���4M��˟��[K�0���+B�mV�Sڎ�V�J/�7�i܊Y���ot���ǥo9�ЏC��|�a��J7�����PND��U���l�<��a��5sµ1�!�<����y����}�X�Q"�Ta#o������_G4 �k��98�8���lk���c]� oL����-V�|J�i��+� ����7@���+�*_��wK˖i���-��#]8NU���-*.��u喕�E���=���7#���oГ�xGe��Kֿ��xF�$�Y�g���*���D�tM���}M�1�0 @ѝSX�I`C�n$f8��)u�8��J,�O�Ӻd�H�T������I_���uB���2��6������M��:�թto- <�9^8�r����A��~�ŔMO�0 ���VNp`����� �a��4��@�D�;���c݄��$�n�����u���v�v6�{h�+��f�}�|�(`��Red���6f�"�$�h�+_���"%1(�ƘLH���B�ȅ�f��rd�ORQ&6�DI�VS��Q�hj3���nm�9Tt",S:U�>��K`TA{b~P���ƪB8���I2 HM�@��IwN��wir*�&'\}�F�\9"n�^$���$>���2� -a�3��f��v�l�f�[1��х����� ֖.��׼���W˱����w���� �(P�7x�����~B��W�har�k���W1�W�K���R�,ʿ��Mo�0 @�� -�w�.��U|H��m���ԃ��DI�ؿ_6X��q��.�g磁�|�,K�fbmێw7��� aV�rmT�d�1�g!en�Z{Θ�^#|���8W��o*F��t���H�)M/pC*F����x��_�j��+sJ4�q��Z�5V.�����GڑGqU�_7��o�6ͪ�/������zY��P{�o�)UY�������?�/�XMs�0��Wht�io�8�d|�'v�!��,i��6���&��IMwzCBڷO�/7K�V�<��/�Ϝ��&C�H�����W�.��T�{k����~�����ff3�@�w���T*�W��f��t����K�6<&|�x>f|�X ���6F'��/j&I�=�R�q��wwmg" OBR�s�|�Iʶ�B�)*�*�L�4P �Q����Ty�m��CKa�4K�`� `xfKg�ߥ�Ls@�ӌ*2]�����1Dl� t��rT!_� >����A.�m�6z�!��o�G4��״�Cx����� �EA�?�:27�0����'� �w���y)�Ubx{�x)�p�4�����mևHG���.Q 7|"/��P�O�������j�q���=w��~���cw#vH�}1|k��@��M`t���0Xt���$�a�n�<ɡ0Ή��I���C���Z�0y(z�j)��5:��¡� -��P��^��(rT-�|�!����g{�~z����_�ϫL�R������*k��2�\�¬��VV'Z�\ѣ(���4\/B�t��V%����= �0���Oq�n��"��At�࠮ӳ�%$��7�"��[��H>9 ��CcYb?�!k[5\K\-��¤��ڨ a�1���2�w�=gLQ�v'c$NU��"�ԒA`u���&���E זCT����g�f��*sJݻ��*�����#ӡU��%�p��<�'�6���s��\��s�}��� -�Y�r�0��+4��[��L�攙xb��b1j������+ Ǝ�1� �􆄴o��Jz��v� 2c�������G@2s9 ����O_=r{s0A�%n����"�o��Z�:ձ�_K@��$"��5���$��G$��j�zucc��!�dlWG4&V /��jSҢ�*�:�������ez ��H���9�G\p,BO��% .�X�(AL�X${��eXf�F8�T�+�q -d���he_]����H��v�N���-D��qt�.���B\�G��@�e ��ں������no�cL2��_Pƴ*�N(�[ͧ�')�~௤��pP��_�b˾Ä�!���Xw�� '�g?�kzۙv�� *�@v�x��G]�l������ �m�U��/L�R��p�` -�+Z5�>f�ZJM���jT�zo�Gq��W��j�K�W�J/��Cm��1[s�;�Ibf;��6�GhMG,������Q�J��체�JRz���Ț�:�)]����l[)�E� j�O��U�����ݼ��+�$*����(Kz����"��$u��6�i�` ��&�D�`Q�`�Z�'qS��1I�!�&�t��r��Xa�]����nr_(������;n�� �6ihH<���ʉ�MF:�����L_��^���q���gg�e/O`��K��i8{R5��[Y�h�]��P��0V������2*�_���VMo�0 ��W�/^oCa��`�qCם Y�cm�(Pt`���N�]Z�[���SB�x�����v��(Z�\���Fc����o?�� ���"�N�(R������U�%k�`�]x�,���j�+�JY�e�r�)��g)��@ JC!w���9� ���C�W� �P=>��>25���@ww�O -U�Js!+�"HY�գ��і�Y� -���^���"z�DcP��ϝ�; j��SI��M8nkG�"4��R��d#���,:Wyv�2"pC^p��-Z�S�N��Y�AQR���Wަ��v��(;� -(5F����ڞ�*�cw3bӌ���5��^ -;ώVdp$�͞g ������,���d�A���&��N���1+tH��ͺ|m�Oi���XUx�A3���|�k�����H�M��M@W.�j�!�/�Ð�����yjJ�穷���sZ�g>A'�n��vR��R=[��ë�\j���a�E -������Q�/��͗As�0�����Cq{�dlrȔ['��3#��X�,i�5��}W�q�B �� y�}O�H�w�Z�-�������G��H[(����ՇϜ�-oR�E���� -��& ��r��- `��l����P�~�BC��93���������k�7��Қ���h��l��89}3^ -�����[T���6�ɵ�)#�~E���2�V��q���!����]�XW�&x��,I��<`� ��Q�[���wi�;"u“����$oJ2�3�a�zdy�O��\����>t�1X�[�B�T-Ы�Ŝ�}�c���F���U{ju�4�䮛 le�� ;�a�� �S�ދ�p�Ҥ��\�#�}ܴ���'�u(/m|-�ѷǏ"�]���v -�����}��%iO����9��`�|^� �7�/�|v �?�x�xL:x����5=<���>�J���\Q�u՗�Υ)�����!�U�茟��jf��'_��E~�.3�g����%-6qV��%�[�g���s���U�N�0 ��Q�,pC��Hܐ����Ȓ�q���I��4il�ĸՎ�^޳�f��ҊR4���vt#:�K��|�>^�I1_e�B�"��ˊ9�+��Q�B��#��"i)浵�|C��[S>3����c������WBd�fte�(�]N���w�����8��9)�H_�9�s��� lt�L4���7� uaM#�8����$є^��m�$0j2��}B�eh9���5�@T���kr�7!�t�M�[��w�,%9Ii'���·�_�ņp��Z�}n �wlh�֬��~�/ѝ��5�l�`��?=S�yj�����&�E1V���"�쟩Lm�\޵9�~��0��P2��OӴ�R$��*��� {��ew�ӟ��w�9�X�����^�؈��]o�0@��+,�/4�>�)�r�i,��lӭO��F����~^��ַ�7.��ѽ\����S�e�mk�����R�f�|����%W��eQ�]l��|�hL���l4k�M�<ӥ񺶀�箪|��m[K��]�Οʮɋ҇����� �eQ���ڼ��c"%�������e�6uq_�ůch_������l�)�z���8w���1��$����)�E 2A�����4؄'ٍ�i҃/�7�B�,�2�s?NSp���?z�i�R!�C��q��(:v����e�Je7=�r�1Fj�H���-XdD>4x�XM�U�o�c;�T��,���5 �c?���.!YL�ݶx=�Kʎ��:�=�D���n��!��*��<`�ꄽ�2Eрu,��F$c\�rS6쉌 9�(Ƕ�۩���T-k�Ħ, ù�p,.���uI��������ʱ����0ao}�p,��)����Ʋ�~V���Mk1����a��h���͊֏KA�j�%fS �IH����7�V���do�<�����d�Q>hKG�A����=��fy��0)�4"H0���c)e��*�f�" ^"|��p|���z�t���W�#�8���T��O���R���[��E�>_,�ۗ B#L������ʝ��x��h�����)�}��.(�9���/-W��pԇp�*���>�o�ǽ�7>�Ҙ��7-��WKs�0��Whtj5���琌��$�9#�bT ���m�}�IJR��f2�!!��� -�›}.������wJ��*�:��V�o?)��_�\2k ..lD3��u�h�3����X�)IK)#z˄Q��IJ -�����v�b�������:Sr��󚧧~���.�)�A�cN�~�V�B -WET���Q0٭�I�&Q<��o�a=���'\��Xe@�D�F+ �� ���U�n�H�wa0����f"����"W��gk�����͗�o@��($(��@����^����@�0����Q�B2�%Cǧǚ�䠫`�2@K�!=($qe � ��^5)���n/�I0‚�0a�x��ۉ�e��0c���@��i���㥃y6�Q3-�)��QO��d�&���5�ۮD����{ކ{�߭��VpX��_���3Ɍa�6�eCo=�(�}d��~�)5�j9gfs'��|���/����VX��^2�Z�Y{��)}F*o��AO�5&��y���� �i��G+�7s�`Rƌo�*Y�l�2�Z�g����#:��V�/~Bp`?�LE�n�|� �f3���VMO�0 ��WD��� �v; �!!1�S��[ K�؝�O�fe�ƾ�ծ����Ѧ��B�%xT�d�nx�i ef�<��s6 R�" �3>'rI����®�(A/9++�3�(��o�/��WA�pf��O�[c�h�X -+S�vXw��Ɨ4I�$_I�>&N���3��'!)���! R�3� -U���u�]�kU�QF�MD ` -+sm�gk�@镣 !�v�����]�����P��԰��a8iF�g�5B�0ȢfF���׸�g��1I��9���8 �� %J.�h8IN[���οF�>�|4�7�XMs�0��Wh8�����ɀsH&=:;�f�X�i��� qb;�]�Ɲ����}�OZ��r>U���B���w�A&t,�I�ݏo������,�;�hr�B/E4�O�s��X��3@�Y�$W*����zTLF�M��e| -��g�5��9�3��#d�{1��X�^���"�3�6��b��c��1�z W��r��k��H*�e�z��n3� �5M�i~V�:,�/ ��$�gC�uí��z�~��$ � ��"V��+N�u�-�-D'�Nc�"���Q����W.��W[�pq��i����9g�6�-��)�$fa�#��T*n�Od�Z5�`�����"�X!!�md��퉽��w��vq�B(�ҥ��� T~��Bs��,���q�PH�$q�&;]��� %dX�8�%s����P�AWSn���Ų/vICG�*�&�7٪�?�Y~�i=}7I���|����fn�ROw������ip������r4�ͽb��<�a�o�{��?W����p��= �0��_q�nc�D�:����_��4��x I,��VE���=��F���F9_�&=E�5�7�iw�0�;���{��<�*;d,��V�0��T`�I��Yk�cQ;�j�m{��Iy+��؆o�w2i�A�i���j����]��}���CF[��F��p�6���*�C����f�����L�Y��/�m�A -�0E�=�0{݉$�B� �B:�B: �(��������n�#>=����������v�MY�*��B�z|��]���&�c�lc����,���׾r����Š�[c -u�Ɣ��l��<�\P`���QS������ @΍7ԐMq����7�]�Y��v6��jA�_��N�,� u*�M$1ab}Z�8rɆӡP�- w�آ9��H��r�4No�ePԁ}����s��D���H�-��k�K5�:h��Jm��^$�W�oyvɝ{ �S"�zO����s��h-y�·\�L���nj�GBoH�h�ߐt1�44:������v�H�Z���J��#8 -�N���AD&ءi�����x[�[��a�6m_�j�Xs�Q����c����Y��UU:gޯ�<r�� S}q��Ÿ́����7V�\��=M��t�n(��&9_#��f�^�G�.>凥�l -j�����¶���u³p�0�)� ��o�V�n�0 ��+詵�[���[� �s �r�V��]�}i��u��#i�f��̐�5���~��rr� -ŝzY��ͽ��' =�W�0�s�>qV0(k�265Jqo,��*.0c����� F;okA�n��fmc�s�� ���+� ��^���R:�K%}���Ε 1H�U�F!�ˆ\q�>:ae��� ̢�S��#p�t1&� }m5����F�Z��w�V��A<��%�(V��B���X�%�qmk�ӡ)-B�lp �|n��;w�N�a��7��R{��xA�W@�b��a�|���`�D�����Dٝ�� �ƨ��H���ĠĪ��y�{�<�{S*>ۄ�t<�@���k��8q�L���x ���k-���rs�gk}��E`7�vo�h�'��`|:8'�vz�z�������� ����2�y,�����^�� D�Gk��R��G�����z� 19���:�g�0�q塌�yJH��|�zy>����h���t��=�Ŗ� �N�u����1� �p˺�&���`��=ul��p�i���GQ����p}4C*�qF^m��C]��[��X�w�����_�{�e ��|����~�)��۵vAO]�B��x�p]D� Z{�g�H��s����1|Ÿ �$�M�œ�N�0 ��<��;+�j��'@B�ۤ)M=��!I����ZZ7��������Cc�� �/�4Wƽ��t{~��Z��ڪA�],�N�_g�D _�� G)�A#�Zk \sӰ�Wa�Y?�!8�P�J�ij�E���@���Z�8|���YCP�|)� -�) !&�����DSkR_�oKk����Tq��b]Z��1< -u0>����C���� fs}����2bǦ�ry��w�U�w$����R�x`}ehGA��Y� _V1��ġA��5W�Y�u���p��@��3�s��K\���D�+-psr\� ����,�߰��V����+�?& -���=O1 ����(;=��k>�J�S_�׆�(�Z���H� ��<���v��WF4�v6�7�k)�*Wh������խ��,Ub ۘ�-��K��~� ��[�$%EY��{WUξ@حW�0��0�W�:jrA - F��3hOdr�X̄H��������,Jр�9�f -�r�Ԯ9�@t��h�~�A&�|g�d����c�م���?�U!m]1(a� -�He�Y�q�j -�A���C&}�ݮY[0#q��� -���P��F�Qhsc�L�:��ϒ }�����i_{l�- v��B�N�ء�K |�8�z5��")�/&��~N��{��ޯ��w�"�/�Mo�0 �������0� � -���۩Y�6��Dɿ��.h ۺ�(z�D�_K~�|��F�zW���{)�i_��/�����>H�Z^�ڨ��T� Q��e����t�ٲ�ʭ�i��%k77:��j�S!t���Pe��4=�X��9D�B� 鈁���6�5��݊�04Fu+t��@MtcWO}m�C��pK�(���}�_���G��m�3�qn�������}�D�N�N��R�{�i�BZ�K�f��&%���;��������W5����}mB����Q�����U��cu�K�;RZC�sw4�n�]/砃#_� <�/��O�E��ۖ?�Mk�0���B���[)�.�B � -mO{��QVY}!�����c�%�%��A�e��y������ALڻ�X�� ��vw�����G�V˫R�#�K�"�OEA�E؆���HQr�Zc*���z�U���o`sm��}o=gNXHAH8��s?Q/�+� ,8L��dݭC������/�4��� Ƽ��[�5�)v�2q�P��W�$ -�њZ�����r`�� (�\:JY��4�e�3�A'"�C�<���b�ʲ�f�"tڷ)S<�\����[m�L���yT�/�����t��I�ɖmpn�Mk]�|�]nt���D'B�Ǔ!�QH| |�)�9*.!6�d�� 2�mrQi>��MU)T������S� -�2[�c�%$1�B{�����l�`��@ut]�K��w�����6��/~߆|��:B\�4UT׷�P���D��vv�y恝�}�yW!4������z�*�S��&�D��Oh�^��8�9���J���U�j�.A��U��_�t�f��zq�n���Ꝫ�Z����.�38��;�j���lϕk�2PZC�x�q���A���L����(��7�\64�F��Mo�0 �������0� � -�;m;(d�n�� m$�~�G��N��hg�%�$�|�ʌ����Ѭ�����|� �t�����ۇO�ͦW��"FF�6f|��?' �&~� ��X�$�YYi��3��;V�ﮀŜ>8��@�B���[k��yz�X���`�b�-�v�X�P�������B�k8��]��7`r�o/E�,�@%3^ -�aM{r�nȤʵ�-�.��Ʃ��i��kN���U7��Z�Oqfm�Io��[�IGt.@�\G�ga=V����R�b�|� -�$�������)�����ek��;];J>�n�t��g����n�g�Igɶ�趐���9�DNOB����TV��1�� �#]�L:��[^Z�U�}:�Sű\���1��č÷Ƶ����|���֣����P#�tE�^x�TM�u=ywQ�UJvI����C�Z�m���M�4z ���]�j�l��_J"�t�M7�vn�B���w�zT��~=]�pԊ=�Q���A��"�SM�� ���/� h���~����@��>� Ͷ��M��0���+,�i��PҊ���pN�V�3ٚ:�eO���3��ڶ�]-Q����x��v�ڪ�\. � Q�j -���=m[�QX��P�o����8\��k4�o,h�E��O�3u�d)����{�\�y���y���5�S��]�d������Ù�ͧ���X:�u_�{�v���� �k���tsڦ� -��U�KգS���z�f�P���Bm�"۟�s)�]gA� ���B�}�_y~��T64Ӗ��Mo�0 �����08 ֠ -t;m;�"�t�F�6�?�#m�8(2l� �(�"��L:_� �d����G)�k,����_?�}�,�rq�k�Rl��\n�—,��,lB����R�RT��s�B��Wq���%�Wu"t��V -���4��k<��|q%Dn\���S:X���7�>�`9���,�`'J�#��Т2^ك�)���&�B� ]�*�G�:�!��u4#s)��Qj�'�]�'&Q�#T�e�m*��>��$u�ˉ��t�^��q�}��/�3>A��U�t߶t�fۇwi���ꝫ�Zˍ�[���{� �V{>��TdkД�k�xJk4� FN�E"��踏�v���ϳn���Mo�0 �����0� ���=u;�"�t�F_�h#���e�-C�-p�%Q��W�)勝Ѭ����2��X�*e -���积�-�W��"FF�6|��e�f~�+��Y�,�Y�h]�3��[��;W�z�Dtfe������ �5�~m?�b,W�k0`1L^v�,B��_\�~��P�FP����߀)! ������,x-t$�-�)�V�'���J&6�]A-�sK!��0֜,���~H� ���,R��`���̳�h.@�\'�ga7U����F�j�|� -�$��7���7���;�l5Y�������I�v�s���u_�*"�|��݀h/��ÇW |����?�(ɉ��L�ghV++���fQ�nL:�S�*��&�!���4#S+��Yj�'�R�&V��@m��U s}$W>����n0'w�uzKG��C���N��שt߷tĦ�»�x?�v>�;W=�ZM��.�/�[�=��S��u�:���3R���g0r�������K�ߜ~��m�'�Mo�0 �����P� �  -�;m;�"�T�E_�h#���?����CW�@��(��(�]��ְb��U���=g�o������_�}�l��*�)1Jv��[�p]-�64~�p�E��3�S�ֻ;w�o���g/[ 9s�B -B�YҸV�S��R�`�Ӵ�`׭C��j���O�4��� �|^1Է`k�S� ��P��W�$*�ўZ�Jik���F�P�5�tԲ,�Ѵ�em�܍!=�Ddx�g5�[L���̲�f�"tڷ)S<�\���.���4��)�$��_���ϧ��=�\�-۠�ڛֺ,�H���p뛉N���7C� ��x"�;0S� s̸��@�Q$d��0�8��zl���xlZ�QLVB#�Ih9�6�X}���l�w�����׿@�_�����z�KGn��?j�]����v��Ы܋�w�GxS��ѭ5���څ#׻|A�ZG��E������O ����O0s�=��L�}��F��~Y ۖ��Mo�0 �����0� �� -�;u;(d�n�� m$�~�GڠN�fXg�-�(��+1b���F�BT�f���#g`�+�}������9�M/R�E���m���IM��n=��I ����:�sg���"��]�ow�7���gV�^HXuk2�^0�*�5����m�!��,.���P��@�KZ�L���E�H�E�d�K�#9�iO��� �T�V�e�����8�2M��~�ɼɣ�D-Í�8�6�7Lv-Ӥ#��V��#ų��rtyq�T�)_�¸ *�����|���H:��h�Z��NWƎ���1E� � �"b�7�|�Y��$�-����g"�OB�#��Y���[�C�D�Gz��tƷ1����`�tj��C��$�?$Dcz����ƵޟPX� P��M���uv��y_��w/�o�����<�[��*%�$���忠�mCx pS/�^C騅y��/�S6B��%��tĦ�j;�x�P����U��_O�.�b�'ԭ�tP����RSp(!%���$z���q�B ��N�چf��7�Mo�0 �������0� ���;u;(d�n�� m$�~�G:�I�fXg�-�(��+1���6F�BT�f�z�3���>d��ݗw8�M�R�E���m�� -�LM��n3��I ����:�sg���"���\˯w�7 ��ά0���g��e|`=�b,U�k0`1��u �J���**����ֿ�C�}{(g*��R�HkZ�+�pK&U��l�hu��4N-�L�ݨ�s2�N��!=�E`��g�f����2M:�pj�8R< ��*G��+����*���޴��|���D:��h�Z��NWƎ���1E� ��"b�[�|�Y��$�����3�DN���G�?��RY�w�X �2(�tU3�oc�xi>V����N�r$�?$Dc����O�k��Fa�6@ t�7 H�� 0��}�����{�}S5���8�y�K�T)�%q�N.� ͱh[�#��zi�ڗ�Z�7��R;e#�T��KGl���s��J�M�Sգ����t��Q+�xFݪ�=v=ٞk -�e ��]@�D��?��O����S��,���Mo�0 �����P� �� -t;m;(d�n�� m$�~�W$�� ]��YE>|%Y����hVC��ٌ���� �t��������-�W��"FF�6f|�诓�Z3�����,`�䬬������g6�/���'�Gά0��pdӍe|0�_1�*�5��ѽI�!��*�n���P�N��KZ�L���E�H�E�d�K�#9�iN����T�V�e�����8�2M�V?�d���tM�E`��gѦ��ɾe�tD'���Uq�x�SU�6..�J�+U�6 A�S�=���w&�b�l�rK�+c'�G�M�NC z*d�و�������>�DNOB�#柩Y��Ѓ�gQ呮g&��m���d��F�hN���ӫ�*���K�M��t�S.x��\��wJ���N���-r,9^���,�o�T�.��Zc�VYOIvI�!��@�K�Jq$ps�������U���N�?�D�ҥ#6�~(\X�(]�W��U�N��O��.�i�ԭ��P�����a,!%���R4Ht?�r�_ ��OeC3m��M��0���+,�i��P� -*!UN����q&���dO���3�ZʶY��DY�[l�g�ymǓL�{��BTΦ���5g`�˕]���׏��r6��M�122�1�D�.I�5�����&1HΊR�/�1�~a���rX-�XgV�^H8�h�R����*�5�۱�)K� -rW��**��3���%�&����"P$�" -T2�Б�hN������V�f��9��8�r�t�v��L;�m�Ա���S�y�l�&ǖӤ!:`�\G�ga?V�h��b�t>R�B�qT�O�¿�St�.�����Vn�ti�(�H�ӕAp""��.eC�z�=�|:�,ٖ]�x��Ǚ��IH|@�39+����c�!ʠ���ϕ]6I\���~@��ꯈ���+���T:�o���v�F�� ��.��� -�(\��z��G�Vӧ�K���5�����j;ٞ���2R��ȸJIQ!��}��>��D��>� մ�O��n�0E�� -�����$����ڮ���Q̚/�#����N�XF��  K�Ù3��.U�βR6�����g�U��ߗ����ן8[-� -eeΌ�}.�1�A�E�E (rR�Ս�%_炿�i��4l7�3/�(� s%��W��E <�q�ъ�GH5����O� ���D��8��w�*Hc�(U�,�D�J^K�)aKk*c )���Q=��P����S�B�F�\P� j? �A+�c�:��W1�Ǒ��&��&4y�xsU�^Y\��3�M�7 A%�5�?����B:�z�l�r�`�g�G�͍wA�t2�p8�ʘ����Ym����K��J&"9S�ž� ��`����SÚ��d%�>��&B_��� j ���p|6T -�/P�I��B�N�N�s��-޵�K�ϐ�sMo]:b�t�{u�@���R���Z���u����_Q���F��"[���u�H� ��;��� C�G�}��F���~!�e���Mo�0 �����0� � -l;�;�"�t�E_�h#���?����EW�@��(��+˯���Ί0�� �e�Y -�:T�����ǧ�R����*%��>rK�e�q��_x�,���n�-�:8�/����P���p��vRx� E��$j�+�}��B�ܸh���4�?Xv� ��dis��$C��01����p�r%))2��������4�ЁC��ݳ�� -j�XZz.�g��8ti�� C~�*t�\g�7������<�&�Z�4S<��*�//���V3� ��������� ��gҁ�f��+��q~�|����h��N��'C��Pi�#|X��+{�8�XA�h"� -\�kQy��c�]k�#��p t'��}�{&Qj`��,q|6T -�_��I���L�N�N�S��->�{�v�'@�^3�{���,_��\�K�‡z�U�O���{�]�� uk��:�����N����SG�t�mS��r����X��k5}w�t�H��N�[it�d�=�ц*R��\c\DRԐ����v��|�ȏv�dC��� �M��0���+,�i��P� -*!���8UZ9���/�N��{&i�D�X�R��V'�gޱ�q���l !��93x�X��Yο}���-g���Pj##cs>Oɿ�2 ��+�XHY ����:�g���DXL?;�/AA��1qf��腄=�ͻ�w�Gw� �x l��Eg�MJr���1b�� ���ƿS@h}{(g1��2�Б.iN�ӚL�B�l�h��RT:�,�f�Q���B;�� ��R�֞⌛���0�Z� ��KtU�)��U_���&sԪ�|%�~THh���!�����޲5�M����%i�c����>*�s�T4 ҹ�K�e�2�lL���m���q& -�%dz��=5+� -��8Ʃ ʀ>Qä3���i0�d A�iwc�&�/@V����-^�lg��d{�l�u>A�B\q!xl���)+9�;�z��g��ټlE�Un�*��E��$N�?�^�͕�H����ڗ�.`7�^��!�/�Mr���F;��x�tnꝪ�Z-�� G'� N;�N��R�}���>�XBJ�o\�Ҡ�Ht~���{����S�PO��Mo�0 �������0� � -l��N��2��(�H���i��A�a+\�GY���W�W{gE�L���^������B�����)V˫\[���`� -�ŏYƣElb� �%�Rԭ��\���p��*��P���b#�WRT΢��B�/��ȍ�xJ���e7�kN�67�M2�J �qY�߁+��Q!W�"�"� Y+�8a�kJc 8�-��=���V����yv�sA�6��0��BA��uV}���=�̳�h�3�M3�󰟫r��Һ1��)_mpހ ���x��|���3��W�e�[�:?K>�nntԄj�S1����D�4�> ,j�=E\B� i4���.�5N�<�@-���Ǯ5���J��ZM���=�(5��-q|6T -�-h����{��Q��^�ұ[�i�����S��]:f�|�{q�>C��{�z|j-_T_�p�z[�ԭ��Q����S]�����i�L������>�[�̏v?��e�?�Mo�0 �����0� � -l��㔋,S�}����ߏ���h ��z�E�_Y~�r��Nt���P�w��R@б�ᶒ?�z�^����N�,88�Jn�҇���"mS��Td�R�ֹJ���1|Q��|� l�EC�j'EPrR΢��J�/��(�O<����e7� '˛��6[R�� �鸢���׀c+I�I�Օ4�eN���:Kikgu�ƫ0�u� \�,N�q.��E����S(萸Ϊo����e1M�!t6�y�x�sU�_^Zo�kf�g,����>������{"�f�l�r��Zf���͍����TJG��d�:*Mw���N��m"�@��O}�*�#P�a��kMv�c�n���C�/s�$�����%�φJ���?���^(|��׹t�����v6d@�h��K�l�/s�.�5����S��S����҅c׻zF������l]��e���@i �����n�(�?�5��h�ˢ_�� �Mo�0 �����0� � -l;u;(d�n�� m$�~�W�I� ���EE>|%FL���hVC��ٌ�[�� �t��w�~���{�V˫Tj##c3�E���F ����/,`�䬬������Wv�o��� �3+ D/$� ����v�+�Re�c�t���"�������\ÉЧ�ֿ�C�}{(g*��R�Hkړ+��@&U��l�hw��4.-�L�aԯ9�k'wݐ&j<�Y��&�arl�&� ��rU�)�m�v�hteq�U��)_�¼ *�e���)�� ���ek�[;];K>�n�t��TE�@/����d[It����g"�OB�=��Y��Ѓ�9�� �#=�L:���3��CtZ� � -�ϯv�8��QF�e�4�w���rm��l,?(����9���7d,yߺ�w/�o��B�V�s�<쳞��쒸@'���枴���ME5z���&�U���N�?�D�ҥ#6�T���}��xU�R��j5��z��Q�v8�n�����E�Ǻ�s)�O�«zOU�o��՗.O�[�gԭuܨ�Rd�� ��#Pq���ܚ����_&Q#��c���ސɗ�A�+���gw�uz=���Żv/��`�H_j&x��1�5ݧ�+�w����s��[k�C�� �So �5ȍ��E�֛�����@i ��_�D�;$������G~��<�ݖ�Mo�@������ !;��T 8N����z�~ivl%���GJi�"�\�����3�f������� ���Vo��Ce�]!��~z�^���*�V�$8اB6D�C��j�X���e �ukm!����(�ニ -v� 8EF_#��^9HQi8�� -�{��J�ܸh���4������{"�j�l�r�`[����-���PMt*�p��L�J�=េEm����K�$�&{���š� ���E?M�عf'�5[ �@�B�q�0���$�#B l;�-N��N����R�и����\:v�W��R;� }���K�l�_�]�k�«zOU�o��՗.���3��z>��Rd납.{��Jk��� fN�G����Ͼָ��ϳ!m�Փ1O�0��� -�vj�r���6��q.Īc[��j�=�&M��6����/�Zz'��� ����-�&4�T������^-�q:g��>W��)���]l�a�dND;8W�c����tڽ��1�������X�`Z-�P&�Li0���n�]�I���.#�L����{�mm��cq��-�����9F8ig�8�Ll�>&l11w�c����(���S����Lұ���l�m�_rA3Y�� G��+9��N�����14W��94�3�K&�����>͖Mo�0 ������[Q�)��=u��f�B��X�, �&�~��t����#!�/R$��n+:�h�+���യ�[�����Z���E���Q�������M��5 u��f怲�Z�Ukm!� ���G)�j �������B�6����xs�6Yrk�)G/������B���lv&��XC�B���J2���j-��γ�5�y]Z��39G�&WGh�Yi*�}Nb�@�z�S(h8�H�E��қ�y6p�G�ҟ�'�cJ��������3/5 z��U�X�tݲƝ���=�o�R��n �k7`��%��+涴�_�)�^��A�o�E�g���?�����Ɖp,k�/��p�"�I`4f���p��$�~Qi�'���d -�伧B���X���qh�4X�{QOO�O�1x��C[��d���7��녢�8�^�,zH��^������H��!��k3�_T��;�]�� F�a�O��8��� -�1�^�r����ŗv � -�����oNY��wPՐ�LK0�kpWݮ�>�VM��0���7no��YJ�.=J��)���8�V��$���w䯤!�l���B.ƒ��潇g�n׊l�:itB?L�S�!�2�?���H���&�9G0X�����?E�&�*f;��#g9%Y�TBgf� w�I-=f�D�5��q� �w�7�!$��-\sz<�3XQ����-�7�I����Q�R|c�'4c�a)�3/y��H'S��/������L�a��2�w� ตy��p,���UGfm ]��g|a5�e��n��Y� ��"-���T���HZZ�P$o����72E{:]u{�nG����:1�R�{���]�դO���]B����z���q�F�� �R�����,�O�{8G�x�TqY�3�������R�pԒ���`}9�%�Ւ� K�h?��e��O�n�014�����l_O�Q��Z��)�p��=x�B� �88���y�܈*�S2퍮��-�k;8�&�c��;�ێ}����>}ϑu#H/��Q�� ���/[=����_�1�!S.�oE�ESς��޶C���SKk1��+B�u�[)�� ��� ��Y��&!�]��7هZh�'�2����$��k�Z�AY#����34`Ke6�~��=r6�Mr�2�M|K䞲,FS�u��O Rw�G)��$�64I��O�Xq�XEkL2j����ar�{�W�aF��gg�u������W�ZIxm�0��2�U�@^2rX�_�̳^� --E���a�[�Az�JcZ�kk�/hZ�Z�'/~��Wg��V�o� ~�_�x_��M��jJ�i���{�Ta87l�(��{�G�q����I{� w�w�wp�^mV���yitF?L�S�!�CF��}~�����E�󞠳�]�`/�G���l&B���(���̬���`q�_R�� -�ez�f6����6���Y����z)7�W�`\���~;G ���т)��>� �v��^�R�PeԖ�����Lua��2�g3�๓6�~�cf5F��u�hk��W8��$T�\):[��=��!�#��昮���#y�@Mt���kL��u��F�����Tq'����[@�s���-�h񘵪�<�Trv\]Ҥ!�lG�ﵛ��:%�k�@�M����|���ƨ�r���� ����ֻ��N�T{�0SƗnt�@ �h���{u��Wi���=q�H��*�!�z����„���c�"2�)��耓��p�D���?�ϛNĎx�V������ܪ�� w���/�V!4����t�����[�Y���,V|_c�dt�>ݕMk�0 �����5�m��e� v �qGQ3�6��?%iJ[+�Jo����FD�t�4������_J�\��{&_��.���NF)���6f�"�7I§��|�Vc���R��1����w��*S�V-1z��룙�LFB��l�P��I.ۘ*�'��R���i�u���u&}��6��2Í��)���Gl!hOl���%fCy��d�F@�����d�t1���z�U`��RՆi\W�m����|�d_l�҆����dǞ.���+6��1�F��Q�A���SjP� �?����IO��#��|�:�3���$Ft���˝3�a;��<�) �Q�*��?�?6̽���������'Օ�N�0 ���V�Y��v��q���f�&����IV�+��&��ݞ��DJ�����G��]�.�� -�_�������R�z�ȍ�1B���B�D�U��jv��C�"ukm�6�U -�n0m�Pc1 V ��A��jj��uet����H�jm#*����\�)RI��+ThKK=��NT��$_ySZo�Ʋo`4LA�O0� ��� �jԏU6��(-;�.�Kzo��<{ ȃ��D�'GfV?@�' e�X#��q�r2�l��cT�Q �8���NH���}�(�}wbK7#��|�m�:qJw�������w�cBB��oE�b� -~MM�A��k�!���_� ���VM��0���Ro ��*��!R{j��F��U��=�ɿ��v�6iU���ޛa��q�԰#���zX�+ �]a�6S_>�}�Z���.�5��نLU��2Id�h��p��%N�� -ʮ�3�v�e� -,6ZԔ�88���R�l`�iv����Ls -0�'Ԝ��@ -#= Kc�G=�@N���Ⱏ���iY�vM;�|�p�&nX{B�h���h5�+���WFWР�,�l� ���k� ;�;:!�d:�w�Z��s����)ϴE/����]�h�4B~�T���4��)a5���g�a��f�z,V�WT_-k܆?K����[���%��Β+�ya�1�1 tL��G�ܧ'�M�(��ʼk�9e�+�[�k�7�+�@|S&�%�%�<�y3�c�?t�T���l:���X��T�����0w�"�1{�>_�����V��H��<�� ��o+�7��8L��[�,=�s��G&��/`&?�����}���ؗ��1��S�=�(-�����Z[o�6~� 4`���Cg�Ȝv�8E�l{ h��J�I��_�C�bű�(qdeP��H�s�O�禌�.A� Wr ^$S!����������#&�17K3bk�7�!^ �8 �r ��f�2!����ʼn�: �&`R�`�����`iA�f�4U!Ԥ�ˡߚ@2]�t?b,����� -��sn�� nW� �f�3�� !����历�U���L(�%��� �<��a*A��8����&*"���2[X��SM�*E� .���j�� s$wA���N@�vr�ds㒴�Ks���J�[�5,��^��3�NO�%?� -v��h 3�#�ᜊ����>�y�|�]��`3]�DK(c���?��~ �iw\���q�~�W�>��xIBH1E ���5ڀL0Bi�W�*O�?MHq�u�_�۞�J�:V��� -'�H7o�X�JN�ۆ1� ���� �L� ~����ܷ����q� ȥ�n�P8�7��8�Y.�7���w.-��Қ�4��) �΋�߱��o9ۀ�y���CL��0wC�I���n�,���\��A�)'�ӟs�46�v���ě�45��:��mK��JIjWu�; sڒqm�����y��wO<��q�4�� -�^s�2�#X�˝� .��$��c�+N��v$f��0)!Ԣ'�T5�!�0Z��M���R�mͭF��D>��*������ n�����u���eiW��R7Q���a�l�bίB��6�yT����G��j\<;��+'�}�V.�|�����}���"�< �܀��T�1�� -֖�kb<�ڔj��? ����8=�%0g�ǯ�yGF5��� YP#���0��<5s�Օ��T�O�j d�L9�>�l678���{;j��=�ouƬ*���T�G/��ΰ�ש��ZRQ�h~���I��)��En��̫��\�p;N��ㅘ���Yy�k�l�����Ž��<_ \��W��~�Qd�E�(fSE�Xuϲ��Ӽ��������f�j�E�� �X����)�c��Y��Ti ��[��G�;����� \R(�%K�S{� -�~p޲�,g�O�ЇB����ÚT:���ӛ��<l�!Qsx�S*%ŽB��T�;=k�:(���~��_�Z�O�8~�_a��Z��W�=��.Uoi�`A����a�:v�v�G�����e6@` Y �RI������xf���Y&Ʉ+�Dv�WL'B] ���/������7}&��+;����{�\���<ѳ]�]����r���g&����9e|���c����3�Ub���Ý�Y�e��xsS�����a�u� 6�R*-��DX )�|�E,�`�����n_���^uU��Y,5�.�`ˌ��S�tP� �[K�JIܘ�N��W����{�|�ʑ�J;byN u�.e6ﰦAn�F��֤�7�6eKO7�h�u%��m���P���5�қ -( �U�}�-T��_��C�q�o����/�Q�2'��&SWh��� ��� ��T�ha�=� �U;1h(W� B��}���j �������H��� }!H�Ԫ_ >Ēc��c ���.f� 7��R�:S�V�BVI�*��3> -5�m�<�/�_�+䗗�{� %::Vb�� -H��ՈF���a<�����XB��D�d=d��b��b�'�;�� ���xq 2����mN��%�J���N�Ʌ-O�RB��…��6�u\�Z�zy �u��q�U�ی���Uh�w�J��Z�ij�k$�E����c���7���K� ����u�$\���e�)RY3Ӆڸ���ܸW��Udž���+�Or{kf�8 �Ҫݕjvz��;��Vl7X����Ȟ�%Ӏ�2�&���&��~�}��j�Ϙ,����I}�VΛ����N�>��YN����'���y���^�N  -�%w+鋐���j_����0���U�]W&�Z�����lz$��Y�z���;�RW8��[�Z H멗m7P���[dL���Wa݀�� .}X��@BQ�'O���(��r� �榉���i���=?��HueU�յnIV�cn��� ����׌�vJ `��sm�L�؈��Sx��oC�LY~� ��Z�(�Ů��Ļ�7�>�7����i9�Vr��aMi�Q)~v&�շ>���fz·�K�e�R�.��[�V ���-H�����ZmO�F�~�b�J}��C���&�h�*$'�~Ck{M���uw�y��̮���c�JH�d�3�3/;3���<dʴ�J�����0��˻aps���O�p�f j ��� ����a�O�l��jޓ������B ��ˋфjY�O���4e&��a͢�7� ��2��5c�ʾ�, �.V�N����h$Txk� ��v1 �<u��k^��șïq��G�� �"]g�+�d���M���E�*ǥӟs�4vB�ˢ��P!�S h�c (D��eK�C%aif��7� ���pm�e��s y�mBܙ�Ӕ��m��t1w�u� �[g�I�j�f��s<��^�0h�� ��9��Yv��J��oy�K����;%"rC�8㦦� �)[j&�RQ�B[�$P��MT�'��P6FE�W1�qh �=��R��!T6�7Wg��P)����ri��c䲨�Y���Z��{���zXA�^C�V���ر�q@}��rozH�^���F Ȁx��U�#<�|.t� �Q#��� -�}� -1�b���6�&K��u�Pv9 -Y%������*tԴvT�����<=3��� ��کT�~�(�%�F������������y��P���rHTN�� p�K�_h�L��S����}0�SN�{P$\@ڂ�F�x���1�H1���� �O��X���JDSy��̒PC�ҝЩ��0ۈ��,��v��� w�� ��L�}���i�)��Z -�H~|���K��)���vB�e -��ƐS �����`�3���v��?M�}��> :wBM5}�S�I��t8w�����C1�j�4u,o� ��E��tC����5 ����a-Y���]a��/�y$rç+4Y�7a)TJ�z'f�[�K�+!��U�7�Z�E�����ޖ��=�#�+�T{}���\���{f�=3J2)� �lzČe[�r=Gw̞sd�+m���ŀ�~rn� O�>��Ps\�<���\�| ��U�����_X����"�.*׈�m�h���Y�rs���r����{�:����l�M��3����׌�vZ `)�2�-�L�؈��Qx�SO�����%�R_�l�7Xxw���gyg�#�>ۿJnv�֤�)���d��y��1^}��)�qK)���j�Z(�vg�A�}��Zmo�6��_Ah�^�.n�~2;E�C��)�d۷��N1W��H�/��;��#'V���$�H�=��w� �.2Af� Wr�>x�L%\^����?���G/�LPc�fM��|:ȧy���h��b���U����4�S�(<�}>zA�dbj_�TSfASK닮���d1�rn��K-g�(��@Df�� n��(/b���sHi!�D�A�T~S,�} �~�a���"LeɎ��)��D��!*%��J� h�'Ψ&v��z�j$���k2����6(��N;��V�*���q4eKT�� ��)��` -�G�)c e��ԂW��J����[�#ܢ5ݚRQt �t�{r㱽$ ����}�A; c���zI����˘�O9�n�+O�^7�{YE�!(�-�p��� �<<��������M������67����Y�K��|�i�ș\��5��\BB�>=�\,�����n����Ƕ��r���޹��k�v@>z��CL����A�ܜͮP:Q[ؔ��M4���r����N��Q���*�j!MM,!���jX��PI ��������mIK�6v�dw�<^t����gO\8ɽ��׌��|��rׄ�3�c��p<��҅S��;O��0�*!Լ'�P��x\ؽa���4�bp���J�� F~�)&D>��2�������snR�R۲���ŊPDu�I�|%���`�(����9����}�-T��?v��*���ȕ+˯��eN �6SW h)_�zP`O�q{- �.��U��4��k�������il��H���nyG�� }!H���0>�\��b\]��Z�&K��s����N�����T��ic����}9� fU���r�.r�Z�+��O&֒�jD+�����o�u����� sr��+��rX x�'J�3œ���7��+;�B�DyM����%F'�_��(�Xo3;��+}�NUR%#�k{��b�Dn����f�����*n���@!O���l+^�M���1U�G��L1����3�;�c ��<��ov��lf�8 ���:�{m�f���W����� � �`�!}O�3!=���Ǽ!�����7�����a�Da��Uu��X)�蝮 �����̮�,r�n>�8՚.W��2���-j2���\@_���6iM���Ϟ�w�dBIw��pͶ�A�o���]�=Ꭵ�pt�ep#G�'��@q_+d3�)5�jw ���+d\��~�@BQ�;�J?�(���r�\榈���i+7ǡ�9�Z�}�J�wm2��E��+Nn���>�|���B���+�1��\�Ir��&�Ψ���&�5�>���fjO�K�D�Q�.��O��ү�d8�ߏ��\�o�8�����n�b�á�d�&�n�4 j��-�e:�EU������R��ERp��%�3�yI��1�l"��*:��;z�a" -�@F���/�������8��$�1 ��igdm������QRz�C����(�h��`��X�녑ˢO�@���=�����R�1yh��T 46�vZ�j��t<� �ݫP8� �0���DDT_0nA����=������ ��@��3A� -F2���{�����e��d -~�|]�.�SNu�xdJ��/77LY��2�dF��ф����Q�a&�J�����~�t� �:b�p ���HY~ʐ���-@ -|7h��_Ӂ�o��j��)�#=c�8 �!�����0��f@��N�]Je�N��%6N��[���o�" -���~�Uƣ��9�`=�}�²�!�ߥ�}�.P�T����/���P�=�yq��@�6��6�Vt�zþ"��b�&�7����H�:�<�x���bL�k��^�XKa������QҞ�9Cp�|���z�N���6p�~��0$���*��訸�b���_"Ӓ� p�P�1)�^�‰���\��4����v3�!�'oBĀ���J�~��r -�ݭ��rP(&ypOA��{F�\�-��=W�.������:B�l�����H�:iLP� -)jSF�^!� L�AKb���ԯ� Q%pZ�Jk�/F"x�;�4&��2��n�?���o&��L� W^C�su˚�؂�̀Z�ʠޖ0����q�0ހ�2�dJ\=rg���˂}<��V�D��Ә����G��X�2�U&�0j#m��(��N�1 o�7�G�?H��1 dT�Q3%��l�~�65��HMaT�#����|/b -�ZP�����ʫ�@��xw� ����/�Ņ$�JWDQ��.��u��?zWâ�YU�kzY������@�Xi��$���?��7��O�Ä�u7��uT���y���j�gLF��2��� �(���X^sݱ\g�"��"����N��ET\ D,"�&��b���VC��6 ��z����� �l�E�n�a��̇Q�������u��P���苫V�^� �?��.*��q#%G�.�~�h�{�h�������� -���Y �,�[(�埚u�M]P��E�ԩ!=��qnV�����8P�_�7�?O��F��[�U�3�:��&�B�)}̣�TK�����o (� ��� ��v塩�E;4(�Ma�3� - -�^�ӕ�l��s�x,���ѝ�BX Љ �������� �%�M8a���'�>���[25c���ٕ�<���3[T��T� ���Yˢ�Ћ}���d��_�Y��N�ke�� �{\�X�&g0�R��[v���T=,u�6��Yw4�=.ʇ��DA�>�"�5��L,� -Y�sӈ��Q!�(����K�(�c�+�;��~(��S}t���f�€U��+ kG -��\ܸ� T���e[�L���I`U���C�[ᵚ���m�H�(���{��w�{A]N���������.���O��g���M��kWD R�k�98(Ha"���] ��}7� yr���L;R���$p�ӄ�n9ِ`��f� @rH�޲��!/P� � ��i'b��f����3l�vxa{a)wh�! /2��^3n�n_R����.����T?p�It[�R�u_���/���T�9�;���4�|���a��e�aJ�f W�ޘ lc>�v��"�����֭�U�u��[n94��%�V���<���Y{cW�J�[x�ӑ�4Q��eL���%�[� �k�G��k�~7�cB��z��I�M����U��r�R�{P�U��Ԝ -�"A2��@�v*Wi�w��׃�1� -����U�}�pтBS���3ڜ3L"R]ڹ#�2�F��sf�P�r���G�n�(�,������0��G{�T����C׵*-$·���U����:d�K���8Ew�-v �H���%r�\k�r��1�-k" -\��~�3ߔ��,-@m��bq,�%�R��h�����yWB�q�P�4�b�fi�鏦�wpӎG�d(�]F4�3�����t#�2b�6lm�_�O7�� He�P�!��p�gT��W$���<���QJ��2�7l�K�E9/�Z c��u��6%�����K��y���3�1,_`���Ϟ�>����H }�SW����bS���<'�[s�� )G���F���Ș�8���Cxx�uv8���{z�^�E{�q��28������� �MSB>���?{֔�>�v=�'= ��R���U�co`��;�O��̐�\�'�a�.�q�\7Dw�#��Z�䊶����g ^u���i� -X#��y='� � -���P�c%vwV�m�Ӻ��Rq]��-~�l'�Mf�{��:|��v�����<{e� �q}�(�1o:���8ؿ��|A\De��uc���e��l�T�����_BF��XO�{4�dp��e�2�ٞ�?��� ��}B�1���wW �~�h�O�������B�m��9�}`,�_&�O����ʥ�ŚHL�C��i6�u��۶�T2I3�V#'��T -F�<.�>�������$����3�Z����#���C��0��c�&5Q�TQE$ZV����f�H3�\O��U��������R[0��Ő�< ������b�y�����9�8o+�����E��%����1T��z����5S�0��@��_I�Mw��E*����ۆRw(�(�C��Prߖ��������*=^�zx�"� �zJ��j�`���KV��w.^�{?�����Z���-��k�����T*�䢵�CP�W�V ��� ������o�6���Wz�t���a�]� �h�moE�b����?��\ٵ�jqd E�Ǐ�����E�� ��F��W��"Z�D��qt{s��{}�b$w��d������d8ħA>��h�CgE��B�qt~}unD�������i��˹�qT>l�w���,<��}�61 4���a��A��fj��_s�{)�Qʕ�Ǚt2�J��8ʋXI�� ��P�T��Ѱ~��+#>��a�V�i1a2�Ə�� ��S`�q�q! -�=�Xjlx�cdd#�˼r ȝq�Ƒ��ׯР� �a��v�������jFZ�I�E�o/Y9���p��.�6`gF{��y����s9�J�I��0����b�U���C-�O�I�"���c��x�&����xr{y980�Z�.)���-��$]B�k��-�p�!a]]�-�����[�8��n+��%��S����X+U�ۀ� �[`a��BL�0 �l�9w��Z:�vb*UB�N9����Rb��S�i�=Ǩg\)�BL�(!B�TO�(�� ��/���� m���:F�;�����'�G�X��^���5��p%:�y�k �̑Y��s[-)� -GX�h<�zC&5J�yO�p��x\��$a� W<�sp���ʮ� ƾ�)D!�6*���Ć�8�vSjhmK��5غ��<��>3�� �Pv����ͥ�6"j��+��n�������=T+g�}�V�W~��jbHY�bBK�∮��{���Q�`U���ة� ��m��A|@�c���f�%���tk����m�&�䕶�Ш��ƨ�H�w"2�60�}��KL[X�h���ƾFN���.�U��-�F��a�[jm!�a�C��Ny�G,\n���u��I_(��Q[ �B����&o�6���uS�Gwh5�{*%����#�sk�r�t���"�@C��H�� -�M;�ٶ`*��ω����{d -e4����Ͷ�A�d�w3�)�R_��&��a}¶^���'"���,�|�s���9J$���de�Ĺ����Q/�䩞���5I�ƅ��hx�q��"�F�"�x6� �0x8�� cG0w�R{?�B�P[��~jY �����Y1�� si�SaE,�p�j�7��Oa� ��}ԯF�;��R'wa�'�Ĉ�!J,�F��h,��D�a��@���9|A��%�El����~�)7�aȃ�:�@W�Ћ�ݣ~��a�vad{6~F�l4�e|�b`\1[- ������) ��u0�(�? a���,� R��k)� �򯇗'���N�,fڤ�D�6� ��"�ѩD�)͏<�W�q���oA�AfT��X��=c�J�Pap�R@�1@�\��f�֬6f�&���XK�Z����?����|)�[�Y��� -\� /2g�Š��j���@�8� E�DC@�?"�bÓ;pv���`_�-:���Aaf�"XIR��9c��^�3�����>���;�E�j;%���,H��"�d��g g}h=v�t0�,���?~|q}~�kz���E��E�W;y�c�j� GM��P�L~<����7��S:��rv# x̛b�sbqH��������ي�^%���~�)�v*�d���!���}W�=�4n—2� -����B���Dʋ0U�J���A�r����(`4�)hca�;!ۭ�Ǜqw�AX�� ��)(P��B���`Q�[��Ω�PiH�����ۭ3��˦�#���1����Z9`�[L���r[�l�����a�P� /RS��|Uo�u&��JL�CBU�� �*e3���=������ :��u��l/ o<�YVr�j��(T��N�(hc1�c���=E�3��ka�=� �U;1>�e�� �Y�1�{!���H< ~}� ��A }!�fܪoPb T�������D�V@�����zqzp��N6���ʸ��MtZݞ9��e -��buN>�a?��Ԋ�jF��kQ���9y���뮖͔/E�7p� �(8��(�n�|l��'b����o/���`����+(=��@�y" +�+09S4A)�Z. ������l_��<�n��sc�b���ބ -�&���Ǚ�AH� -��6�6�T�謭1�fv���� -Bk��lz��Nvy;F��������n��`���FOe0��p����F(+Z��@Nj ���P���ڼZ�uR��.�n��V=3mvʒ���ʮlgצ-Y��|a�J��o�n�f�S!JE�k�3�b#����͐z"�-�C�()��+�婽�Ļ�7�>��� !tU.�*�yaYS�d\�;�jS-Ҧ��LO�Uw��L[�h�D:�j�ZH�^nC������[[o�6~� 4`��[lCg�h�v��E�l{ h�8�J�I��_�C�bٱl��)y��H�y�}��\Hj�zr2������� �@䋀E�C������x��ų�ϩR;Gj�M��_���ԋ�q �t_I�#���w����"푈��b���K֚/�2���(P�֑�4�}�ۮ!�c�YO�O!�w�������T3�M(W��Sl�8�ˡ'c�|+G `B�/"�0��OY���\�_�G�Y���"��z#� �d �9�|N��HЉ�����&��fF%�8ա7�� (�$o�O�=�����q��n -R�v[�(-�V�‰P���|�zQD#�P(M��'�j��"!m��2�тl��'�93R�� D�,��$��� ؛ie��ȥ���rI������%Q1�l�|jF~n_[~�aWk�6(�R��r3J��_�Ҹ���������k�f�����Զ��S�S�z�CT��%��,B������b�x}�z�� 4�j+ ]eMbj�8���m-�fl=rm�K � Q�?���v���6��@p8�����P9��Mn�7 =��RmT�)Z=� N촐�*��BCS�-c �hD ���<�N�L���M�T���vnn��l.��;�N����[6��_�mV����)=7s�R�Z6r9��2�0��*>��WUʩ>iug�e�ڋ�{)�dk3yn�-!3h1�6�������ઝD�| �|�ei��{Cf#=��r�Sܹ�|�b��&0מ`�_ Nk�C6PN�~����?"O �҈���l�޵��)�������U�%G�k��:h-\ڢ�~M�ӵ�*5�g��>��e�W��fg�:8fA���C��F�8Hg��k^g�:�n�йO=D��I�]{f'h�r��Vc7��̄��rv�έ����;\�0o��,}QI��˟G]a���?,|�(6�:G����E���􀳶������18��.�O��Cz5�P��ے�3�<�VYS���-4%�5�����NY��E�J��T8�F���d�����ΏW*1Z?Z���}W������@��b%��җ4� �E -�~ !�֋4�Tn��l���L[��J�c���+Zq򱋥��D�"_=vܔ@�R�Bj�L�X�� P�i�����'0I.ė$.V��I���y��3���D }̆?KnN��"!C��?��j��u����z������h 1Gs:k��_�SȠo�/��ZmO�6�~��ʗ�����P!��� -��m�!'�ܺ�ة��K}g�$da�ݒ R%$�f�y�xޖ�w�\�+��DoG?E T�S�>O����ػ�7���2VvM�+��c|�"Ջ�7�&�XVJ9��/�?('�2b��` ��$ -���o;���ڻ�:��N�q�Es�c0�dQ�R$�iĬ�N$�(���ʙ�"�B^�O!�t� -�>�O�;��R'_£���CdX�s��M��)��')��@��� es�̡x�Ș- !�t�q�����3�Q�̅�Qp�k�q�ܲ@߬3x�+4�e��8����� o v=��|�b����V����:e��n���v�\���^�;A_JUpc%���"˘��ߕ},C�;G�֯Y���a�M����Ul���H�F�ā@�%�Pa b%�B�+�q煜hX(���SPL8� �E�(.W�\V4��$���[gF�~s��ն�-8"h�ŕxt��s�͓���_K�����d)`�0��{��䥚����x��Ye�Wie1S�wc��*L޽�+p�Q��k��6IJ���u�� "��F�����˲_�)�Hk��c�S`��/ ��z�F�H+g�Y2�= �|T��E5�O�-Uo�8��s�R�k��DZڞĄ�ꀧ�x�Y�,�������n��wY:ED/U�Aa7 -��_�g��������n�pp��Z0�t��4���+a�z�l#v��7 ��dZ�� ��rn��J��|���)-�����g�R��5���zƥd�,�iJ!�T��Up��K����F]A˄��t�N�:�=a��#,H���5���:�s�y]���R)�NHCٿstf�� �i)�| �4�.�0Z��9/�\��wv�\0�}ӫ�Ve�uq�)��i�v�`�uR��ޙ��׺����ĭ�Ԍ��~c}�+T�o�N��C��0RZ���o�Q�f\���Z&;t=�#�Q�˝6�ނ�F���hk��(�����۝������>y?�ʋ ��qέ��C,��<Sp�?��&J��w���%����hZM�3-Ŏ�ڸov�MuZgς���Ed�;'�*��Fkj�e-����8xN��x��t�*��ʷ��+����K 8X�(�.��,��'b�K�����B�:z���PPz�׀�D�V�V`r��R��l�W@���� l_Jɢ�n� �sc�r���ކ ��@K��L:�¦�C�50�Ft�k���?3��Dja�7 jvM)@��Sތ�gpg�P -F��p7�@�~���OU0��r��5�++�����ʯ��,�dڼ�(���r� -�澊���e��ǡ-��;��fv�;����W ��� )�uC��Q*�B��;��ǥݐz��m��c디Z)����P�= �;v�����Xm�*�yᰦ�ɹ�&�ʹH�Vr=�W=��2�)D($��U��C��rr0����mo�6���S��@g7�^ ���sڡ@�i��]@Q��+Er$�}�)ɑ+�GV�ZI��?�wDGo�� s0�+9��#����/�����O�F��ы�Z���G3���p�O=ӉZ$��5,"i.�8:>;}/w�sH��A���XM���a밣��`�@&�z�T%P��ahY �)�����l�TX|�s�c.��8�y,8 ��� �4�H��Ѱz*�) ž�a�e�k��S�q��\n�%n$S��Xn��0)�*>�;T舟#q+] -v�ԄW��:��O8��F�B�v�P�w+�UͽW�R��m���$�q�P���� m@&J:C͊8����X ���Qo�Ux�����U�,A/�ON��RF��À���0.���O/ON;�|�W�Kʿ�+b�ϼ ]@�3�~� �q.!!��\-�˫���{4�8�tv+.][��(.�cDZV����� � �9��a�rs6��F:�?6�"�?��rxW� �L�f��%v��P!H�bj��=�jXI �PI �nU�'0?�-���&�w��\�{Ax���+���\��3G������e�p<��ʧS����[oȤJ�� ���s��$�3�)�U.�`�]�kc��y�QH����%�ѳ!�S\p ͔j^��R �X��;�<�p�o�⿁a�l�b>���nV��&��ٷ�BU�qy���=T9'��F�\��:0rY�Đ�tń����ؓzA�^�0_���b�MLZ�������%�il�� H�n~}�= ��"�F�,��?`~��>O�>��um �R��윔���� -NB����o�):��&gNUʯ���"�[t?�R��Q`a-��F���w���׉�JJ$,��;��(��uE�`�x��>��C�n�<�� -��=�W�r�~w��hX�Xƒ���*J���(+�n�6����چ2t�-��pQx�nX� kr�˴/���E[ ������&��6�b����M�&�ݙ��h���RS7{�qj ]5Z���8��1j>\@_���nm ���g��#��Ʉ�Pܘ�#4� ���*73��{J}at��x���S�~��Va�]Ȍ�w���~H���2���?_!\Z0�7H�y�Yd�E�h榋���e+��E�7�.8��*����]_�=�j������׎N7-RʵV�Ao��V�R��s��vz������� �-���?e�O��Zmo�6��_Ah�^��n�~:;E�E��-�d뷀�N5��H�/����^"�v��� -��(��Ç��N�\����X��88< �H�B}�Wo~�5`/O��"ɭe8X�q0s.{1�� �e�^��5Q��\�qp��b�c��)،G0���g�O���`�' -oO���;���K��saE(�p�q���ߏ!�t� -͎��U�LG���mq��Ȉ�a�,�)����%��(�� X��c<�r�x�X���� (��G�V�z�97��8�� p�#t���hX�=�?�̻ � -���U� QX�SC�֟�h�m�&Z9�͊9��� �D"������zZ\��� -8�� -��sI��Dx������4L(�������|�g��h�D�-��[�y�tҀ�W���N.������r����?{�� \9��\[ 2N,.�f�\+L��m���� 0��<��4�L�9wS��&-q�fB�����W��f7㎖�qd=�R2�´ %��`���(�?���[5�0��D�&d�s�x�E�G� <{‚,���B����`1/w �?sD�B,�ܖ+J��#��Qx� 2��R/���s�KF�p��*�`���Z�`�G�� �鶡l~b( � Ņ����նh�s����v�$t���t�D(����*f �f F� -���������w���8C�Z�Z���*51��������1����\�ZzH��J�S�-�ĵ�x�����BzZ-12r� �7W��iV�B_�������tHɕ$j=+N�O�d�H�m�l7p��gz+j*�9h7�quzfT�����|*�~�Ԋ�jD�������o��Uc��(�(w� �U�K�B�|�}!*��7���j�| ��cwQ�p�� -����Sy��QZ� �2!�%ȓ��Ӯѝp����#���m����u�lbx��@�H�V��@t&o�a��� n Ip�1QZ��2�̸�}�����9��7Ź�@���<{#$ ��e�v���@��j�n���5k#���~� 9@�QwχL ����� �\�]���h����(��Q��z��������Po~"ۏ�!�=�����v� �[KB�kN��Ѿx`G�%��=i�Ώ&�G*ͯ Y( ��ȣ��Wڼ��C�ު��!r྅�8ͦ�����R^س�ěV]���VM[���V�]���lQt�r�т�,��kO�m˺�O�Jyv��o������4`/t)m�󙇞Z6��.�@��"Ep�,��Aoe�����p`+��y{G��%�D����¢���Aj}�guι����e�����]~*�n_L��(��U� ײ�P5��r����7߆��R��'y�M��}�=��5�8�"n+� �z�vY/� ��e��)�n�7��_�2��43�I�h��zH_{<�{�p�5����Tˎ�0��W���m���${Ap�!���3ipl����dfX� ����Tw��Œ1�w��;���j�����+w�Me�N �R���,�*�:,r��Q�O����珟|�(��GLA��Z�:on*��� q��J�χ^���ql1n �����Ě�Ԫ�6 w�D-Y�S���Z2�Faw���r����ˋ�dz�X��%u�Q��3��*��ȇ0DQ��<~��~��C�U���O��o�6���Wz�t���a����h�noE�j�ɑ�?���HI���I�ز �H���������"d�r%�ѫ�/�T���at{����#���ŀ j-�����s���ǧ���D-z\��4b]\_M���FD� �� �Q�>~���,��ևX����05�,S��y,8�L"b�86�R*,~9��\p��&E�})ͅ;�({Я��1�b�ؗ�1L��p� %Le�C/���5�-5Zb�Af� -��� ���o��Av��K\� �ժ���FZ�@2e���:F�T�0�� HG����=�����]�oЫ��JT�.r|{Iи{� ������)� 5K�T�ׯ#b50�r����:�Y����V@V�����9^J���nX/�O�c�&����x��3��rmR�\[��t�G���6�¹���}5�[,w�닷(qP��V \�� 4�Q\�Öc�P�l=r�7@�bs6-�0L2��� -k���ؔ��h[�� -�1�L���%v��P!H0 1���=�jZI �$�i������Zʍu#���� � �;AX�����)��f\��#Gg1/� $�9<� �xn��O� ��:O�ΐI�j�2��9��(I-��U.�`�]ٵ��ȏ<ł(��Ze��ѳ!�S�s �)մ6���̅b�W�z$��n���a�l�b>�2�nZ��]o�������e�{�RNP�F�\��*0rY�Đ�tń���]/ 8��8���6��_���b�MLp�h��=�S{����U3 �� ���{<�E. � �S+�� �}��}r�%�J* -�'�Z�:)�; �� :������o������:=�����bw�m*�~��Ԓ�j������S�|� ���Zi4��UA�7kH�Ą�%��}�BGq�D$�!n^�� B�a�^�6� -���{ $X0�[>[��LބR��X)�T��;��o`{(% M��©1t�S��w� �q����Hz�t%�v^m���h�mD������dBI(����M��@�e�w3� n�=��0z�w������� -f�#Sj�Ԯ���Z�x�#�څ��:¥���T���"�.*��@1�*�'���^mɤ�Ļ�*��kے���|g�����K'_3:��@H)�Z��aJ���H=R����r��J}��j���»�v�����/ş$��5�LF�ڙ�6S>��i�EO���gB ���k��r.��:c5D�LJ ��a��W� `aAF�a�DEP���~iI�Xi��c��l�T�7ㆇ\p�i -μ�AL3a�$J�˧�b�P�K���y��$L%�"�����jb�)ځpV� WD ��ɏ�����j����_��� l��!v -$Q��X��o�����i��у�3�ߩ��~��fN+Q1B��c{M"H1n����GFJZM��X� ��1)0sF������g%�샀�$��gT8)E ��Z U'�-��@#�yr;��L�;�6Y� l�[��霤�����%D�����b���\�@1H��l��K۔��:/�3a˾��^s����5����M 7�t�����ȟ��r���UޗN?�i�ZwĖ��*�f!Mu,!���rY��PI I��*'anAS�b��9ݭ;�W���aYs .��NQ��k�Uf>s�y�mB��Ó"���X�t��_a`G���fb%��w�j�3{�$�� ���e�i�,��k-���<Ƃȧ�Je���qC�cq� ԳT�ڔ-5���˜��= �/u� ecs~�9�ӊG��_Y�4������E�1T*'�}�V.O~��,jb��Y�bB����s�WPq���[�*X[V쮉�@�6q�"�#����8= ���3���WOޑ��4υ�$sj�w�B��S�K��D]IE��d��@�Δ��/���G�v[��vm�3A�v� �T���n�.p�b�� +�� -,�%�F�����/�u����� sR7�+.�bS����(��)m��1��]���YH��\�Ռt����V�7���̃@�������j�**K�� U�~��s��zgѶ�n�K?7h�x~��C�ͩk?g�1Y�������+6�� &2�g��h��P)Q뤮�˜O\�5/�d�R;}�p�5]�J��./'�9*:v{�G.�+�T;��BS��>�����)�dBI���pͦ�A����9�;接�p�s�^���z�ݶ(�Vnn2��}e^� (k���@F�����Ҁ��B���f�}�[T��M s��Vn��rR�M�r*��U�V= ��c������K7_3v�i���,M��Й~�M1Pܺ��<�_�pI�ԗ,]E��+���y���Cׅ����ӚT:���ә�V?J��!Q38�)�QK)ZC*Н���ʯ�;�A�?��[mo�6��_Ah����b�0dv��i�i�ɺoE�1��H�/���#)�N��JY -��ƻ�����I�ZL%�qc�V��e�EB�b:�r����{�{B^<0I�%p���d�\����Q/��^�w}kXBƅ������3�p Qt�mN&�����B����^_M���qsH]n�|�?<�Ӕ���|�%F��N�a2���̄���-�I^�R0o����(�2�G�f���*��-3"w@az -��0�KK�JI܄��/����4���#O�v��P8��tI�J>�+{d����hCN�f�Z �)�L���3�����B/`�ͨ!n�x��)/�����הe���:��V0��"O���&(�ե��+�-A� 7�5E�,���Vq}�0�zDSm����)2���` c0~�G�p���c�Me�.p��Z �)�ǶG2��Z�ꄻ�0B��V�P�$N{B>�:"6�L�Ap�*ү�M� -6+�߂J.����}T�Q&"@8������m.�� g�M���.�z�&䃪N���B�h����b�X\�|8|�K |�5��%� ,#���Z0�l=r��N�b 6�a�o2qqևB ��<���>h[��u����M��*J!�������X -��������i��J�Д��0֍�v���M�� Hw�Z��^3� �IX�� �{���2�o�%�)�~ �; -v��03�R�yG����p;a��o�i^jp�T��km���b ��ە���4� ���\ؚT��զl鹂�P���I�|i��� g�([����\@YpQu�+�.���>�x��*��~#W.g� -�BŜ�g�L]A��b�C�����;��ia�=� ֕;1h(�6�-BO��I���j -HЁ�u� 1̓�B�̩U��>��c��SWLQ�Qa�m��R@�Δo��V�:ITe�~�G������o��A����Eu.Az��E�wS��������z���:2��VI��s�q������D>�"� ���8定�Y�h�� ��8]�4A���~M렿�] 7�Y[ظ��(�<.(_۬��3�p�&B8��3z]-�+ �-�,1��N���a�|l�暌#�NӮB�He�t�Mh�#�C��X��7������ݹ���6�l���.�f j_��F���4��h壔� $�o�X�G�"�N*7��an�h�Uߘ� -{^m��o�2+�ѵiJ��o�������׌�vJ `��sm�L�؈�1��h3��H`�+x���Z_y�j�1��� ����b�4�(�y`YS�L��uF��[��up�z�u�Jˬ%����G�V ���MȠ���V�n�0��+FF�Ʀ�!H��� -$(�B��*Ǚl �l'��=���F�j�eE9p�x&3��rV0�k�v���͊����%�� L�`̜�#���2E\�u��3p�{)�s�A#�L��~���N����~�9��_j*G�|F�ʈ��;ae�,S.O����p ~S �G0�O�g��h@�? �����fdr{;�1�)=����� ���1b/>/_��%�E���w0vL� �A3�}mu����ϛ�[���Sc`z[Ȑn,�hIM8� � zM���8��3��& ��&f���w��_ �e���b[Yt�U�3:� ���َ� 2��PT�p�J���ǰ���4��t٧*�f�Qxl�6���t���e��*�ґ�]~��B�1jTѠ����F���ճC���e��qhuL����\J� -@�L��$��;�����Vr�R)��Oλ{j�����費�=�q`�z����� |::�֥�L������0�/������˟͗Mo�0 �����]�ކ N1l P`��΅,S�6Y2$:�~��I?� �I`Y����+ڙܬk K �x���ѕtʗ�-r��n�鳀���DY#p������q��h�TM��#��Š���\|����0,��d��� -s������0�I�Q���F�4����i kT�Ɖ%j�Z�:^y�mGÜW���o��Q���k.�r���6(C�l��h�pI��]`���J{מd���J�3)����R�3I)��B�3 �|{dw�s�˸��Z♴DTޕ�բ�v_T���K�$��1]8O�A��Z�����@�:����`4P�`�glFt�iv��j�dj���%HW�x� -+qsD���ڇZ�q�&7���I`���/��"X�!T@�)���!w h<.�֗]���?�5�'��F � -�@�d��"Kٵ��%|�V�Z΂ٗ��#1�� ��>�ڬ�|� x�E -�"������Y�Q�O�n��I��x�)��P��X=�~��� nP����V���Lo0&��"l��Ԫ 6�9^R�mJ{�X�u1uKsR��I��ÊϤ�;��hϛL7�j-�$�?���S��^�K��S�c ��Wp��t@;�@ -\@\�B�R�j������4���m)�iy��9<����!���J�Ukq� ��Gq�x�3L� Κ�E_Z�P^��J�[��6+B�qB���b8/Vs��&8��W��0Ժ���Q:�:p��E����l���8�]s9������2 -� F/���ړ���RE�t�t�����-����8�_ N���G��Vu�hwS��FW!1to�t�„�,V�����vWJ�e��Ix[�e=�;�o��� �^Q����OȲ~}t/ �����~�Y]o�6}� T�Ou��Yj�b3��E�bMQ��(*�"�I9�~�.)����؎"�ؓLJ���K�K���! ЂJ��;퓖�('�c���|�^5N���U�X)s�w�ZGg�&�N�y䉇NuSI� ?��3��NYH�qHU� �;ic���+�z,�R�����T�0r���E�Z �+����_L��Z� �ƛ�c���4†�}��;S��.�{��� wi:X"�D0Vi ,�N�Es��^s�5��x��h�"x���u�YhZ��d|�����Ƹ��.O;�-`]�$c�*���UI*Tp�[�w/��v���rS-�Na�T ����Z����� �N� ��pN��L�����e-�Șh!3�٪�A؅_����@A�����Us�sY�t�w�� �ɏ��`�EX�*"Y�!'#"����Lu,9dIz��� �/ -P�����d�I�H����ֆVƥ�����˧��0iH�����š3���&��TB�@W3�e:��?�d2�/��Ⱥ$�f�47LE��r.��'���u{��zo/6O���J\0)� l�Ab�E�)* N�|L`b������@����wx�؎񈀂�d�B�'�D��E�Ry� �j1�5*�� P9�n��n��z�n����]W"�x��=��X�FgjϘ`�9��#�,RD/2 �ɕ����T>.����� -��:)11Z�I�H�L�#̾<��"������iD��"�8\�)�dH�Q8J^b��G�x^R͇��Ǒ+[��ݎs��8�_0֭e�ڛ���aقU�.����`� ��E� Q�TC!��uѠ�]b��$R�Ĝd�8.��6@���U���x��Q��R�ZR5G��4�`C��H�=(����$Q3�\�Gw -�����_|"�/�8�knZ�$ldP���c�cM���� �t)LN��*M�{ �����&�Zi���KBn���]��~D��Y�*K�e2�;����gJ��DGјk�#x�e=�k$���`m�#���XB�1����S�_~��f�G�'�g�K����V�V��4W2���D����~$IP��bj?�}@#�)�w7��˫�m����r�-���NZ�Z.����v�]�]�֋���A,~.��'�={���t�ۨ#!� -�_�R�͖ۙ��Dφ���J��k��r��WQo�0~�8�y+е]7��D��A�����;� -����@ҭTk���9���ߝi���h�Ъ�5���:j������3��M.��@�ʶ��s��Z�v��, ���BW��{ͥly̡/b��z�X�6a[�j�A��ɵ��)�t�F�nǃ�s�62#2 5��?W[:X0n���P�h�ZnZ+�6kk�GѮ/�U 7�;��_F<�1�۠�U�v�+��b0�O|��{Wu�Ʒ���/�xZq�.ǣ��e��Q��� .z�A����Kx����J!�-����I�S���f�$��ow�*|����ºN\��Z�y�!c�L�cBQ�к7k11>Y�Ȏ�m�Z/$  ���7�@�c4���4�B����b/�G�G_nX��w$��s�� ��ʙ�s�=|a�KKo���Ք�n�0 ��} -B�-�nCa��{��;�DO�dI��y�R��k�v��� )��G����`�T|�������I�ǟ��~���7۫�] -Hq,�r���i$�d�mz�D䦐Q0L!t��NtP��%k��:���@;��#-���)(�ٛN :T����>x>H��o�N�8�)�6�ضY�%�L��} ��b�g�`�(��S������f��)cD�Qͩ{� ��Eca�noEa'h�!@�I� �Y�%R )/��)�q�8�mU�ͤ�y�q��bQ�0Cm����S�c(�J������7>Gpq~6�93�eiFQfm�e0�Q���D-���hAZ��(��UzygQ3��d��qE�୷����(s,PZӼ��8eq�/�j-[������r���z.�/��(JYn0c�|=� #b� �Ee��Q ���N �I�sŧ��M��Z��< \��q�Rl���9$+h�ِjU�Ӓ٬F��4�JK�˒fJ$�g��k�Òi�$i ��[�b6�K�)j -/��j�d!Żq�`�~�Dg*id�J������8#Ch,���q-v9p;����pP�n�O�~}vM~���,0ΑJВ ��#\!(|��c� L��%�0����2U���fQE�QC�8�2T �ʙ�� K��E��0�69��8 _��!�!�鐞���ʻ�ZUe�̞8ŐW��|�j*\1�@�� -�5Mg��u��c.Q��WNq״~+�=���n��B�� ++뛝�mw2���{z&����uaB��5��� �h�u�?��Aڛ!�]�>��f�r��BH�v��=,��!-����� �]�/ v��J�����{���=�a��﵋~�@^Ywkt��C[u�;��8:��*gگ��rz��fYĊ�B�m��Yr��ϙ�) X�.�/-N���9�j�pU�.��2��-i?��z�Z/q��zw�f�x>g:� ��aO��w��Q5΅L�����㺍3>upO� -m��5���s������ o|�QFXM��w��!`���c���* -p�y9�2�ν*�v�}�r�y\fHI>�����޶�j��{S��� ����͖Mo�0 �����0�)���V���K/�,311Y(�M��d��-M� @|�E��C����ucD��B~�}��v�U!�>Kq=�ʵQ!��lC!kf�%ˢ5��zf��@Z�ekL!�c�}į�G ��ȑV5��P����7�"�5����vC�u�z 5���9KWhJ��fT jRVѯ�KeB4{� 򦐞�f��t�P�R���6����Ϝ.�ӿ39M�9R��Dž\� ��Il�^.t�o|L.0�2o��ҫ�y6p���sA��� -K�Ds�^��@<�@ ��Vރ�r��A{�^�8�Cȵ�F���v�T ��G�}�mi�_hU�c��Q���%{`���wb�t��0�EM�Y�N@����$0\�}lG ܦ�0 ��_��4��`�;�o,b�S!PDjs*�M�54q,����'vg�<.ʡ�;d2�^��ỻ(����͖Mo�0 �����P� ��=�@���@����,�8���lo�W��#-�|R$/�B��C��k��3��d�7 ^�����b~K%�c�Y���D�uykV�ef�������+����m#�$�3- -p�������3�b,��@g�w�7�{Q���F�bE -���s9�� B��PΛ5:LQ!�^ZC ���G�`-*Es���`�gF��ȗ� �NZ �4�'��/s`0�b���ߥZXF�� td}��#�m/~u,�|[.�|��N�Y��M��U��9X`�p+�D��|�S�/����yl;)7Yϸz�N��NAE�z�J�����M�~����>�U��O1-�h*7�en�V� -F�܄�1 ��������&&�0�E݇������`�uO�@X+vc������`��V+2g��<.�!�9f2�^��෻(��?���Y_o�6� 4`{�ݾ���H6 6 i�P��@Q'�-Ej$���~GR�����D��f�g�ݏ���O� [І+�����F$S ��e���7�#�a�f�5� �4�(���u>ǯY��z�I�s�YD�R�etg���%DD�LA�������� ! �r���{��G�-4N�/������Ri���u�����ַ���DdKE���=$N��b��'.l�&vW �6����&�b^��_9�L%7*��Z���j��2J�0������?��� nw˨(c�@\RQS�I��a�/�0��Ǻ�k��T��H��;�*M(шאF�Y֊�ql�e��V����]P�:ZЕ��È��D'�NC -���~���y��E!vc��H<|x��H *A�(h�!Ý���hw���ZE��,��h�� Qf#�0�Ac�P>�޸@?xt �Vj��^4�3�ت����-W�c#J�ʽ���k��䌆JZʥ[�;g�9��O��Z��[���m -�J��n1�ͪ�~au��-r��E�P�d�@��\�x$N -���V�Ϥ=-'��[џz��7�}�!�Z"�2�����^\��Imp�1�u�5.�r�D�@�|J�pOk���d�{X[ ya=�N��C� :�L�"lõ�Q@ I�y���\;eF���.|�v-70��A;j�^B���2����.(1)��{����+^� -�W�5e���w <���� �A�� Ǿ�KԻ\iW��rl �T�k��Vm���WVc�mS�x���m��K��<�5��I�����{Q��i1�VL)�+*Ou���Aʨk5�9���3����Ë>�l�ȯ,�۪*s���PXo�Jc]���Ei��gvIb�Y��]:�^E�hi]CR� ��q5�l�`b�C/u��l/��x�T�Ç�������_�s��ݫv��a�C�{&�}+��7��˛BP���L+mG�\Fe�~V6�xL+;� ΃�ڼA��P���c�`L ^8�c�d�Wj8�6�C��G���� �q�b&��=����1 ��3(�d�1�/�཮��OS���{���Z���Y�ڢKP�O��)�r÷�|8�s�c��Uz���)7�E�2�rB��}L8���V�ig����C� j�Zk:�\��2������r�P'VAW�ɺ�� ��S�I2�1A���� -�3%X�����Zێ�6}�W*мx�ٗ�hmƦ��E�M -(PPm�K� -I���)Y^_嵥]���F� ��g��_f�̙6\�atw�."L&*�r:��~���Lj��$�C@X�a4�6��߇��|��jy+���DdR1�>���yD$͘�i�ܐF��� !��eLZ��g�J�X��6Wm��_&JK�-g���?��O���_��Ȝ��~@- ��$*y�00���UB\Z?%���r�~��2fg* �)jYDhl���F* ��'��s��j�E,���T��,L�I4ϝ�$QY�:�A��%R�tF2�)�"� %�}+�f)I(����1�N3[h|�+��v�s��G�t𵚌(�fm:�W�M��ta�ؓ��>p;b��b�v��)��g�'��1L�ML�GH=��F�"�&3/~��z�"��aD����Ƃ��z�� �g��!v�HRh��M�[q�{�Qݾo��o$Q�8���;[-J��aۂ8+����e�{ 0��ŔO&m�x����D��s��m�G�rYJ-���o-�P��o���Ꙭ�)�uq�.2 p��V�F�s<=V�Nr�޿3�|ΤSq a[[�<�;�� p�kۿ�.@c����ww�~v��-��p���S��KF3(�S��{�e"������������>?l���`<]>ljO �(��S����v�&"���,�$�,�P^�{XAP[pVY�Y -���_��醳�`�������g$|m���0��7��������Hy2�R�G�:3�$���`�F�Od�/8��v�����S �PC�g]�h*k*���v�Il�? ��q����@�X�b� �s;n��:�q߬)�h��J��Gt�8�Ls[ Z]R,$]P�V��'* -+�^+���,�Z{�+�! �i����V�����ZY��`b`-�qM�h�n*Z���z�M.(l$���K]���4kj�n��bH�;KR=���T��fA�SB�������9%�tw�;���'�/Ps^���1=m�5� o���O3��p�5��6�L�q������ ��VrZӕ�"0$o����\I4��{���mZInљ+����6�E*����p���[�-�zO�4M1%/ZG�q�T��S��p@����=,Y/4�y�X����Է�kB(�[rc�)�و\���NA�>��u�u�FU��C(��gLp�L�q���,-�N&�uͶ�x��M'��`�%�q������.��Ppk9�n��⾳�<��z~�����oX����M -T�gEUM��� �#�F�\�_;�rK �ܽ앯y��eXt��a`��2���q<��~8>Q6��Yl��7�]� �>?�'�gy ��;���3r]g:0h4�f�leL�><:��Wg��9}�N����S&t���T����Z���G3>w6���^��VMo�0 ��W����b�S���!�C��,3�VYT%9i��h��X�� P$G�O$�#)��X���l..z��**����ϛ/�/\ ������6䢊�}�2�z�r%=�,�,x%`���O���k�:ο7ؠ+k N*l?.�\����u� �hc�0jDƠ�\�F� g�N*�!J�s_��� ����ͭ��4 ;/S�� -C�~a�c*=Ĺc��q���&��-S$��XQ�e��Q22QY�襊��H��'�V+s��.�aֹpMat+���,�*,1(�]��.�vyw��� -j���aB$x|h���d8�\�pc�m�yJ��?i����1�︮��j֥C1�8A��+ߖ��l!�S �q��ᏤD�X!��s��Y�� �Nz�?>���<<�����J�N�- �j�nm@�Կ�t����V:���kw���U\ o�'h^�T���9��C�?��)�1���������_G�d iK�Oh�t��Y� -C�ͫ�?�S�w���� =m]�k�܄��*�9'k���/kz~����������!�-�v�U�_�i�<� ��������,�5cظø5҂l��`b���IM��&�tm3�VMo�0 ��W����Vlq� �6tCvP`�e��*K�$�ɿ-;N��isI����)J��U��A���H�����>c?_>\2���M���k��2�1MIJli �J4��;�`Y+�����{�52мBo��Vى�ΦgYY����3r��P�h'ގ2���h���כo�����f�A�UM�˘�� #re�C'����ڒ�ԡ�H�t�n�nRD��P����Y�@y��"dlɕ'y)��^�RɰΘ�s%[���jc1Va�^8i[� Lec�Y��S�L}_B��qkX>��a��9�d�k�kG��ᙏ���Q�H`Q��#�&FA�H���n��D#:��w�St������ݢ: ��}#���@��h�=�>01�M����h�q�=k�$�z�[�q������_Q;�>l{��L؞������c�f����1D�#���'�_�w� �*�,c��r�8,�aJ]����SI�����cE#@scԡ -��)o^�N�=� -Kg�����\��sk�=�E��S��}ylܷ��G�֦y�A�Q�$�b�y�����F&�C��c����3s�} �wq��=�M_��w���5��TVoƦ����������Z��YK��6��W*�^�v�R��`�i�-ТH�C�E�,&�����C�ayײe�#5=�F��o��ٛM&� -�Zͣ�ɫ��b� ��G}��槈�Y��1I�%H��X.��)��4Pkq� ƺ1�g��{��\pI�Y¨ -����z\�8l�l���R^u� -Ip� -�4v������� �¥g�Od�!'��?�k�D�ZH�J)Okm�}ILS�څ��=�pM����~�7)� ��ӿ�,.�/Y>^��N! X7i�� OGtuwc�*~�B ��oz��D�]����@9��ݥ��ۓ~�����/4��QC'5�z�_��d`����m �B:��(�u�ޢ\�/y:���UzM'x�F��s��œ����ʺ��;Dߞ��P���s�����\|�۾��p�ʯ=`�Eo���^$$Fg�'�1'u~�=8����� �nrI٩�璔Z�:�nz���%�u��͌��K�x3��Qň�jy��sɁ�$��Kω�S��f�����ȼ+���p�, u� �C�P��%��cvt��l�>هl�6Z �/e�VY ������1t�s�)� -���o N�(T�5Lj�@�E�P�_汗4{����:�_9�� +�揖fyf;w-8 �ݑ>��,�7z��� �b��h�-O��ͣ�&^��p���/�ټ2�>�S�>��[��l^/��V=s�0 ��+p�]�J��k-�ri;���u�(8bM, 9��/D)���v��#@x� 9�]wV�%_���ZzC������ӛ� -n�W3�tJ �>U�e��R�"���u����E�\�>�_Y���;LA��Y�qm~0�]pء�4���{r ��|;�2��[j�]�92�Q��Gm�R �8k�fk�l��u�7� -}���z�"�䒾!SK��hL&�0 C]�5IT����;�XP �6bFK����t��x��l�6+_���#c��n�e4�С�D\`�ĭo�`=O���pk����~�J$���Q��Q��t���=�:��[G+��OXi�c:�P�p��Ґ�a�a@����d�N�i��.\�/[#ӷ�V�=#�C�&r�� ��KN^�NМ ��_'��5�ܵ�����7/C�O��Y��Bz�w�<�qr'�DEQL���Sı?�Q�S�q�G�B���1�Ũ�~�~us�~X�'/$N;w���=����� -��ʿ�ρ�zkVf���Z_o�6� 4`{��� ���H7 6 i;`�����[��H�I����dˁK�,��ɻ���S���� ;0�+�Lnf� �T��v�|���Տ y�z�`�ZKp���$wN�4��h�s��Ǚ7��%dS -�L>���I��XM��8\&���;B�� -��Vq�G��D6�q�~jv1%���U{~��������ۻO'dGE��7ׁ��K�b_�'v���q�.����ż�F�\e9*pu�M�3��e�����p��;ny�wO�D���#.��W�q��e�k��B�*t����Z�$UnsR@���(C(17�Fq9���� ���J���i�q��A�J��aDn����������3T�� �Oc�Yg>���%� ��xĐᗔ���x���N�,��B4�腨J���$$b<��Ρ|��q���X�r �4(�;��/�b�x�_�B펔��Z� ���riǖ��ЂK�o½킗&5Cd�w �H�*%:�l6�NkX�3eC(�#dg�@��,���x�@<D5�mC�?{*φ �wlQ�����g���@t�D�C�?�=�V�У�7d�ґ���N�z��%eq�+q��{{j\W׋xf����9(� PzF���h�2��ja� ��j �o�f��xÍuSƱ�@�A�`�/��.�&2�G*L��A]�ѓx��y$F&0�Cv���auūW���� �����g�x��#3(u#��X/�z'-�R~Q\�-�oʧa��ũm��<���mB=�|�g.���4�oG �hR���\ɽ�gTO)�[ -�S�S�l�v�L�RUlf;z���, ��6�!�������^<��j��hLN��MT�6�]�e��~J۩�)�� d%��=U� �'<�DQ���2�7L�:��;��qǩ��-z�� ��J}�[�3 �g��Q�W�����py�ep��M�w�ơmVs�f�wʌr<���Gw���TB�E�J��!p�;_�Y>a��� ;~�b��ӳS1a�=N{��|3Amu:�k����,�;� nt����2__T�g�ܞs��+�auQ�ܺ�O��� ��p���Q�>\kP���S54��p�w��q7�Kԝ����Y��[8��_ V=���ӷӆ��ԩ�1t�6��P�<3]_�IB=_�]�Y����/>��$���l���]�˴ ��u�?Ŕ�N�0��>�� j��� $���s%����KH�'.�u@?�����\�kX1�w�?l�9�}��]����#g�ݦ�F�IJ�%�[��$DV�І�[$RԜ;c$���W���GΜ���� y�� c�`���t�|7(]/Z�~ �����]���3U'�J��Gep�H��1a�i�9@��b��0gZ`��[V&*��5������\V�g@%J�_��k0��E���Q�Z �*[�r�z�Wo(��f ���/P�2� -���T{o��*1�Y�OŖK��0���+F��� ��+T�\@BPΕ�N���N����ݤ�>�)�ebgf�yf��.��:it�>��3@-�B�e�~Ϟ�}d�8y ŝ��]� -�OIBҨ*��Y�4��Y� ��J�k�e�y���S�ړ����2Cۼ���/����K���+G�J:�I%�&e�5��E��4,0��MF�I+5gFdʈ�/;ae� �)�-��Y���%hHv�����9�-ţ="K��d�q�DQ�'Z?#��K�K5I�<��-�����P���eKI=c�ۏ� -�)��%��w����� �%�:S2���\�7z�N[�F���pv-���re��4%��D䏇��v��d��lc1���0���Er�qۻ׫���PWeq%M���k�q�B��g���q��nSʾ0�����6"����c�4E�:>�{)~�O�I����W���ym>� +DŽGz@Nc��� �GV�5��ϋTOq� C� HM�V��;���������oq� �� ���Y��JF�3���*n-�������V��|�p�/����|��0o�7�Z�4W�`��M�Z��&�\Ә7gn�����_�W�iӇ?��_͖M��0���+,��[U����n/�Ti�sd���&��cH6 ل��c��y��3L7�$+0Vh�O�����:j�_��)y�=L�d�tV6��s�(BkR�e�7.��S�UR���m�7J�q)Q�[21�'.�B��q�R�x쿯�� Qp/�H����B�-%�1'xL3&-P�V$B⫘�F;�ҐC���J���Ө��w�'R�?;38[nD��p] ���<��&:#mx[h�x� q�+�5�� �4�����Y��{��,�)��us+���L���)�.4>����s0�V�d�,���:��I�����¼�8�V�w�],�5JX�O�����T�� -�d�q�S��v��2�F\i��gD�dy�iC�!�/s��l d������R�#�����O�8־���!�z%*=T����Е�%�N~�We�̧�1:s}�]��:��k�����P�M��ǂn�g��/^^u��G#������^�����Jc���5K$�@y --b��@G����f�(z+�{ޣ`�w��X�X�1lۗ�}��8X�)j�pz�HCsp�����{��۾Z��~����Ymo�6��_Ah��ml�oI��p�5�ؙ_��SAI'��Dj$���~GI���Ԓ�)}��{��}��k�H���'u�w����Қ�nߞY���On@�"h�ե��:zW���I��<�����)�Zď��ҲW�4Q.��!Y���� Wp�)�������Ko>E�W�Zz�u�>�������u�6�kX��j�i�h{Q�`���ѧ-b���vO� -�oǓ��G��o6��V�yZ�׃�x6��+���:kw�p|:�0� oo�Ч��n�]��~ߛ朷�f�U.�d>B�Ͻ�,��|��o���w=�{w_�=�VU�)�xdWEj -��b�~0z� �hoˆ��Y�+ ɼ�*r�'�Ye�0�ۓ�]��>ؓ��f��"�%;� -x�s�������a�]��Jvp�˕�U?/9���a��g����0�z��jv�g�pg�9�vJ��}m��i��%ǰ?����ϑ�d2.(M��m�5;eW�u�g�n�L��ݯl�٠>���L{��Zـ��1��]������l�)��u2+D5��Q�Ӭs���S���!��2T����=�PGiI]}i�4P`��f��q�sX���Ҋb'`�%�q�o�Zư��(W�H3��+�(����%W�r����v� ��@�s�X�G�5 ���{��LvȽ��y��~{�呼����E -IOq8� t���uʟ:��&� -4�&�f�jB%���}"�VXVB D����{)!>�7e ��b"n�P���;�}A��֔�q@5��s� -N��Q��qi q����M �|M4 � ��G�p _�q��\�mv^R� �H������ �"!� `�X�+-B������̄5���N0��/�>GTb�4�> ���C��dC�zg-� �\fk�*�{�618F<�����O� M��g|e�1�fãI�z堗(%&��{�l`�*� @�}LuO��ܨ�NR���� -���鈲jH OKH� �¤�)!�9= [��R���?�5��#%�bbl��P�,/��J� I86��\of�G��7$6'�,ˌ!BR"҉�^�{�ndo!Q����`~� ��g�O�B�`�ս 5ʲ�$V1��5�Bc�n�v��İs�dss�c��?�غDb���S%���ԣ)�¹�!�HC�,�vb\ 9v`���Z�$��<�b"V�*VQ���z�y�y�Y$ɔ�I�G�"ŏ��h*$�@U$�5�ui�W�x�a�5�.�}4�j�%m ��̲�kr*M����� ]��B�z�я�tDU�jL��㖸�����������D�^/���7�8(&�d���`T�� �"�Y� �-͔sؼ��H����r�=�iǛ��\:d���E� ����V�>���I�W6��/6U� 3P1�v&@��9ER75×@,��"���y����u��?�4S�J.�q�\22�Q�i�T������S��H�\�=���VMo�8��W tj������"\ @[i��[@Q#�k�ȑl������&nwS�M��7���P��륆�W�L�w���H[(3�&��>��{�^M��o6~�TD�e��(������ e���F�i2ko*���X����i۵�W\�K��m�a"�/q���w+� $��$H�iR -�y�*�r�m�I��Z��X�Fӕ��l�kV���Ev��t�&��]r4M�P�� ����~��V1OPbR)|���u�W�JyLc 7�E�d����+�-��3�l<�%p`ɭT�����j��S�a�jV�m���:"V����%S��`��� � ��eRh� ��6K�A�р2�]`��p�A��G���!x�����AX�ݬ���c�^;e3u����/���!�s -9�*04k//?�~�rw �n^��)|�T ���J�UC�<�5�����:�;��j�L��q�����,�>�N�5ʆ=��kF�To ��y���0�mU�ܸ� ,�@)��ԵCn�!g):�V��_[t����sT��xr�$;H��0�% r�����~Z~(g?�8I��w��o���m�rR��!5�mjλ��8\0�k�8�<\�l�K�7Kt���8z|��C�G�}�؁�\�)�j������|ez�&َ��m��;�k%[ל�Ϙ���4t����B%�-����ʾ#�Y�Q�30%�5���"܏��2�� ��5 -����p�A��1���Y���'._�?y�^��u�:��t�c�#}��>Jfm���.}TD��8��A����?9�ِ�[��MJ+th�?��o��3�nƗ�<8���X�� ��h�� 8����� -]� �WMo�8��W tj��ܽ-�E��@��H���Y�Ф@R���wHJ�㏍�,�ؓͯ�yog���BB�� -�&ɯ��Pq� 5�$w�>��[�o�\2k�6+;IJ��шFiUV�^� -���@QK9If�u)d��b ��8I�`�6}0ƕC��~�O�x�fc���� \dh�ݕ �1'�$)���@#�ȄnM�u&~�h����*2<u�vM�Lj��a��FT�H��M�[d�Vr )|+*�9'7����2F��Zе�B� ���,�)b�Zo�M#2�9ڎb<�P�Q�׃���9��!w� 1�x������!���G�jK�5x�a0,a�ɕh�Հ���@�-�%Jj�A������hZ5Ka��-g��(�;K�ym�^�wT�4�kp)\� �;�UQ9�[�Z7�p�ڠ.@��2g�B}��`��� Ȩ��U+e��Cب��5:�����W��N�;���7�mE鼣¦��0�}�^� 15XL��e�F9�S��gh�\^�~�Ǘ�*�y^��)|�Tt�ب�R�=U]�4)%�븦��p��˭31u�-xV����3 _!�I�oqű -��B����ϻtw�W�F�H�Uq���`�2����9 -Kbc[T0����,s��35w�K��}?���_HF�������~�9�឵�^�:��:Z2=i[�5����t�Q��Am4u�Ѹb��p�5���� �� 4�2�~.���wmyNq��;���$ɴ�G�w5�9� �I<�$���J�� �����M�9�A��x�%궔�R�;�%}u��(��(�p��r5��}�?.�9��ZW�Bɬ �<��G ��\0�CZ�}4gCʣ��) �5>�ӞR��1^�����h�p��Ͽp��!J�q�S�N��������V�k�0�_q�i�.��ƈӇ���c��c9��X�,Iv��~g����dq ��|��w��}iy�-4d�4:��-�F@Z�L�u}����}���P���vITx_}�c�UQef���cgEy�T]5WeJYKr -J�N��[�,i�Ign���^d�q��8���Gm�M� ��K�D9*G4��T*�wITթ�"X���X+�� ����3"UFl:1(;ae���+x�Bܫ���˸s+| �����fp��v\�S� -X���r�Q ��� ;�A�<���3��B:��E޷�Y�Z��]�:�]E|=(jA P�� �!���T 5�6-�+Q[�;j�Z���K��f,����X�R�^�r t"�u`���\R�u���)ܵ�X��a��C��hY`E(k��jO֎�׋6�#S#{�|mu��D����f�B�Q�c�#0�>L��,��D��߯u�=�*j���JQl�MpH�Xh!0Ut�F���_�-e�++��|�b��/�x���� � �XBO_����<���G�j+�f��X[C�< ���q�u)�{v9� ;���Rװ���ߜ��iwD�K��7����E�97|�&�b�C`�v��:o�{��Bڗ��� ��}��Y�U����!�g΄��#�j��ҤZ=����_�9?J��� -<�ʤn̆�r�9�u�� �lfCJj�:��U;���0�7vt�_�O�yp4)q{�M�hG��h�����iq���C����'�VMo�8��W tj������"( @[i��[@Q#�k�$ȑl������&nwS�M��7���P�����}P�L�w�����Tf6�����{�^��!o6a��D�2�y4r�+�jd���eU��$��7��30b�� ��, ���Wc\�2 +�5�m�~"�/pQ���V�AHR-fH����:�UAJ+ZO2�Z���X�Fӕ���m֬,���4�6�#&�]p4��P���5�����TT#,S����Fps���,��� -8J5p�|i������$^����ز d���R �����BO-��r�4���uD�MR�F,�"�E���-�h(\@�/�B�B�y��X�5��i����F>"����c�d�7` ‚��fe-� ;t�����f��(���p�)H��д���t����-0�ix�g��K��g'$��j���I�9\�5V���!m�;g�t�#DV�n{��l�;�W(��k\It���ޖz��f�?��U%r�22� "�@B�Gn�1g):��5��_[r�g��,� �I����a��@�Չ���i��R�}��$�?����g�����_�9�C ��x�v�vkUy�^;�6���� �Klk�z~�p�K|�^�C�}�Ԁ�\�)�j����� �|e6���7��j[��x����s���x���4 tرυ�Ͼ��T"�j�S� ��.��%���$�[x"܏h�2^�(��5 -�ҷ��p�M5�!��j\�7�'�`�?y�^����:����c�!}�֝ %Ӷ��i�>*"�u�K � k���?��lH)���&�:6�~�`� -H���<8��Z�� ��h�� 8������ -]� �VMo�8��W tj�Tڽ-;EP�@��H����F�$ȑl������vc�h��įy3�=5y�Zh��e�4�;�+4�V�̧�ͧw�����ŋ��"��&L��ȝ�r׸ʮr�T/3�[��٬��4f`���Y�K/&�"4UW> � �m�a��/pQ���V�VHR���4��<�TP�Ҋ��̵�V�G�֢�ta8��F�5+Km�]������.� -�f�(*k�r�t}3U5˔'�)��]��8˫~���[�G���o��g-�&0�l�Dp`��T������~���b9V�M����#�&�3P9K����9� � -gP����к��h,� JD�t��o����F>"v����c�d�Wg` ‚��ge#�{t�����f>+�W�p�)�H��Ь;?����W����8��C�b`'$�5j���I�9��5���C�.�I�=�Y�Ctۃ�gӗ�+�Bٲ�_�J�#`H=k��*<��Sr�22� "�@B�Gn�1g)z�� ���4\����,� �[Iv���v��@��Ջ���i��V����(�����g9[~EΩcx�����;������ ϵ�h��f`]b{��\{�����\B��C����䗨����+;&�B���|{!Pf�ޤ�'��Sc���ܴ?�u�3��g�;�=�P�`k�y!�r�#��O03�M�-�$ߡ�F�����p�wW�?�&v|�z�:�����Y��aP����.��#��֝ -%����i�>("���J � k��⿞�dH)���� ;���-�~����: �~�_�w�"���ֿ���iq��ϏXT���WM��6��W tJ���ފ���"pil�hn �Y��$A�d��w$J����u6uܠ7�k�͛�j�v��Рʚi�[�kh�-�YL�����=���&R��7�0M*"w�e\6O��_X���#����@H�u�Bɬ��<��;A�����p -im� ߜ )���٤4B���[���W;���p$����� -W$�����tJ�s�sk3t��Z�O�8߿b���X�mٷKY�P�Cb-p��M&�����N���ߌ��h�GNw}�C{f~��|�����q4VjՋ>u�"@�D�a/��:��[_�ę�h���h�\����S'剾�(t]k��"�zQr�u�c���������;��1�h��p"넓q/JEf1���r 3馽(/���L��`*��*RxЭ��w:d:� �~������X��"׋� -��X/�D<�����떢��������D|�*٨��P$ZeS����4�L -H�ȘLQCp�'�\=D*(,v)�Mi��Q[`�]�A� H�ѕK�l����*7���-.�a�k �uA�:��<�D��d������D���S�7�����pi�v`�b)�2 -�x ������j��K�o�"MD�Q��!� 3�Ad�����N8�X�cMH�t�a죵?9с�v���3�B�a$T�Q��YKE�]$Y֓G�ؠ�,3#�xJ�C�tA�vڍH�I�>I������YFk�X�HS2 &�+ -ǝAC�ǿ�h ��i�褈i��$��#� 1��@ � �fC�>��!����5�la*�����6H��uZp�N 1�Q��®�fWK�K����'�؁�BM!7zh�8����9%(L)� �`k��hq�'� -{�O� w#�8ASP�b8�����9!�$�8��h�ݐ ���ڂD�U�:�[�qAB-�=���F0�F��^g��69��R&���`"�0��"vj�m':�2y����9�׉z�"��Z�$OmX$˹�*�Ͻ��� ��w�HEW*�'Z��s�R� �=d�-E�QN7�֞�u�^$��G����fÏ֔�5�&����W�M{��1)��4o/Wr-��г����O��e�A��d���N��,� j�%'�|�,a[�,�5ѷxQ]�m�+:������e�f|�3s4+M=��j�Fs_��t+���p]���o�%/D�p�������mD70�Yq�l���`�ZX7s.RF* �)�T���ڼ�����v�g� -x��lB�x�2?��<�d�a�}�N2lu�T��)�)�(�L���[4 -�](���O��'��v�a��6������]%S����&����?���������R3�f,M��.�8_�n�l��Z ����y������"�_7�K���I�P�-�/-߼V��8����l�o�󟈾�$�Bm��8��o_�0� ��,�]����j���3ǖ/��h�Pq~o)�\z�kN2A��nf����f�)���H˚w�b�km����T}۹�����J^�Χw�+�9#ޤ��=�Cظ���%*r3$���RY�x���똛4��h}��� (ָ��f��O��t��Ƚ@C����,��E�6DZ�]o���7� �!�����A׻���X[o�6~�8P�!2�}�8E�@�nrA�PԑŅ" ����CR�ĉ3��>d���B��(�����/|:w�%s�r���ޜ����z6P��� �F�Q6n�� -�B;��� �8������wg8� -����y^�]I[l ���mwZ��C���� >�J&f� -'r!���2��SE� ,Y#��"�g�Ū��y.5L�x�q+�'t�����k� -,$�jr���*τr�BHFA�d�'�i�N��tR�3,wZ6�h�<^�x�y �x +�NO��`�W��T E�-�bRy��D9O���R[���ZS� �d,t��-���r�[5-��?�$�'+�{���a�U��_���8�5õ�x^0����R�2d�[2$Z�/A�n�\R�B�lˏ����%�4��3{˙m�5F���H)&Mx:�����5�����%2�ob�Q� �gT���նR���^�x��ƗG��ݡnL��� -$�6Z��q�����!�����m�\k�3(�_�7t���W��r�8 �z����^�*�Sp�(���ᔸ]�&:� �VMo�8��W tj���{[v��p��E�6ؽ5���I����ߡh�nb7q�f�7�C3o�{r�~��Сʚi�[�kh����O��/�=��o&R��7�0�"w^<�]�*�� R�̠n��f��ڣ30b�� ��, vW/�LpEh�0.�%H6跱���߾�E�~�[�;!Iu�(� %�Y-t�a��*�V��f�-��}6Pa-ZM��O�a�Y���Vާa�9H�1= � �iv���F�!�/7�3P5P��L8AH�r�x��� ,��� -��8n��iR���W �Z�m�ܲ d��n��,5��[ ��Xi�����6��@�,�"hD�c�;4Πl��H�u)�}��X�5�����=V������$��ؓ�m c%�;k�D?+a��gw^Y�Lm���We���B��� ͺ��?�����f7 /����;!9�Q�HՀ�'��p��Y[�~�v��O�����{D����l�{�W([��[\It���^Uz��]�p��NUȍ��Ȃ��Y�s��F�R� -f�am�i��q��sT���$;H}w7�e J��������P+F?�8J�C�c��n��!c��z�v�����_.�9�F���C��u�챠r��F��s)#���83��m<��%���BN�e7�[ x����sX�H��q*��c+ )?[;�K�`���R|����ol�+&�����j��u�24X"Y`�4 -�ڇ���#�}}k�R}��������U��cP�n��N�3 :G�d�;Jf]��i�~UDzt�J � k|C�C�;RJk��I鄎���[�6�a�҇��: �~�K�W�7�"����p��➃�/���ſ�V�n�8��+:%@VjO��A���"i���Yl(� )�������q�����"9�ޛ�N�.k -Z'��&o�� ��j>Mn?����^��pɜ:��4��7�YF��T���T�Ϝ� ����d�ވ�b2�jt�q�&q��y� -`�K��p��g�y�v�����5�9��iGq�:�|��L:L�N�B -��&�ɥ�!�.�d���bO�~���<����e8�Ɠ2�uM$�4�Ё��n�WH��@(��F[��+0\y �a�ok_<�ʡ�چ{ݗ��n����W(ƺ�O�JA��� -��O�r��u� Q6D����� �n�(gKk�,1�;cO����&_Y,����[�/Ԑz'\��pM» �`�ĽIb7�$ ��į����7���'�d[Zw_�W�"M���!?c��h��BnJ�������㫗�U�Ѯ��Dg*ż�F��+�ź�~��Df��>=�]�3��G5�Ɨ��>X�O����xğ6>,~�A��?hm�E�Y�<��oH�w�p8��v�bz��G#J���'x�d7�~xn�}wz�&�qh4�8��Y��<��E��O�xZ�s���*t�?�X�o�6�_q�S dr�6v��p1�P$ ����Ng��HJ���I�N+���v���_w���݇���q�0W�����* �}�ȴ�C��%��l}v������%ӟ����%āL4��aP"���/�}Һ:�̚`��,������}��9���N ��3�^ٳ}��n< "�?�>ۜ�wQ���O�{����|����X�o�6~�_q�K[ ���!�]� �"u��u��P�@R����HJ��ډ�����%�Hޯ�>�3��.%4h��j���^&���\��$����>Lߌ�d�Vv�,���F#�J�e��u�Ѝ�� ���d��E�&�J��8I��vo�`�k�*��֟����t �p��2CӞ6X!s Xǜ���`�b��"R��$��L -L�t����*�=u_����!~�ÖQ9��.)7In -�VAX�L/��+!%����{ä�@�䛮*�A+��(8�]�A� ���H�+��‰[��ys@9ת�ڐ~���X�Y�`BRN7���(�,�Cs�rB���L�^c1��G�`�G�}��d��r�ܢ����ރSk1+�A���H��]�i%Ȇ|>��$^��y(�Ժ -G/@ӮY KR�}�ֆɺ��,mS�&��u��>�H"�}t��$a�VD -�ۻ���*�-�)|���u��e�%șc' �Βm^��-��˚ ��9� �� th0������t���j���RʨV.��,�6"��d��: �Df�?��J;ؠ���+�j�/�O�T�7B"l��w^����.�B()a�/�Z`�^� !5�L��i{�r��F�n��5WW��������y���L�K��C�F�-��C�y���k�Yh�O��^����=��Q}@����!�g2�F^���c�,��7���y�>]!�9�t��G�yH}ѴuUQ͵�g�B+_f�����iyq\��R�m�C�N�e֙��6�/��ܝx�H�qvw�h��t�[I1���������m t0Q���q���g �I�^��6G��Į���ׄ�nS8��a@=��75'B��Nww���_;5�;���?�]xAg#�4/}W�Pl���p~6�U�nN�1X �m?g�kQ}k��N{R�ޕo�w����U��X�:X&�ؐ�(1C��b��A��}��ZDƣ�~[8�~�S��(<��`������q�{��O㟃�;u��-��7>R쾹��)��s�%2s�¿�Џ�ۇS�2_�+��I��K�0�b�Ec���չ@2k�7/��Q�s.1�CZ�Í^W�ـ�i-�%�/��wk��n���$��A���Ǡg��o��G�Χ��?51���?�W_o�6�8h����� ��"�@6I��-���ř&���o�#)ɲS�1Zh{3I�w�?w����64Vh5K>� ��:j5K����G���\2k�>Vv���U�� �Ҫ�r��*tkxE-�,Y4_��%�۠��Y���;��7�vM�GƝh0�|�LZ6ŠLH�gIUgR�ȱ`�tsE���nԮi�I��q>�܈�^�zC��Yr�,�J>C -_� -p%�6� �BL*�����hZ5[a1M&m��0�tA�ė3�F��`)6������t�\0�9l�+�@OCbU��W�#����&u"%Ʉ��Yе��`���s�jG�8�2c|>T��3:���5�/��+ R� ��}#��?ܟ����Bؐa��L�0D��І�:�L��Q�_*�+J!C�� -���?�}�[��W�a -�#;6:�+OU�=MJI�u\��B<�$�v�'J��gu�X�6�L�aP� yM�?�'���@�.s���>=���9RWSܳ�<��4غ� RG�9sږ��!��4`�����Tv��[��DF��e֙�@+��5�BP��'�|���nS!k�̗�KΕ4j������S �ڨ�lZ�2�;����ޫ�-��AH� ��\� ->� -���a� H��{�= �pϕol�W�C ��V$I͝� -��[��6 ��Š5���=�Cȍ�1̃#_ڳ@��{C1mp��Z���C�!�P}��0G}CW�c�q� �s}�mc$��� -zlٳ� ����;�ўo��[���)���][�(OQh+�Yd��|s�Ƴ���Es; ��B��xl����u�]o��A��8ҏvM��_>\|�0��?��_tG5��{�U!V5��_����o�p� �q�̿��I�W�G �5�����6x���N��S+�@tm���t�*����pwdZ�7� ����&h�8��c�`�MA�\�N��H�p�+�7�������N��"��z�w�ť~������x{ ����=��i���E�� �X�n7}�W 6@�b)n�"p%n��4��1�rg%�\rAre��3�^�N��T�b�o"9;3�̅C ^�fVh��j��^F���D��0�0���U�G�\2k���FK��~�V�|�'�����������h�B�"P,C�3�è\�'�g��uL��d��ɂ~��s� �D�Xj~].ic� �uNBB9\����h�-;��ڿg��x:������_{����؞�}�N���د�56�����mc��~��'W�>�¶csf1��X��At�F)�ɶ�"R�5����SVH7RTd�Zux���F���ΨH�n�nI�/*'z0C��X�:�}�ZG��Ы��k-�P�@+�n�� -3K�Xl� �T�&R���%��|Ů�6�J�GW�\�كwZ�U���FH �~М�0�1X�s&e��u[0ư�I&� ���������E��T�Zt!���3˗��5�k(,�c�$��D�=�b۠+�z0*�Ι�0�&�R -y:h� ;SP����~�R��?#�/B��3��+����P7�3� �lj� ��J��I�7'K�8�y��� -�P���֧O�ޡI��6l�ڇ -D�+-��)������ -�����J���1�3q�I��%s[i���S��o�,��<0�xD��x��2;�-u��y?��p��n`������A|"=zOS�x pߢ -3�w��%� É�_͙��)����������P~����U���Y��$M���r�B����,�/)��b{��� h�����Y��ڐūp`�i�@ȍa�r�%�<�����Ts�� T�G��|[Qە��ƶ5ϩ���n ��t�����w28��-쿻8�ٳx��h�gt�bQ��2w��k*zh��|(�rh9�L�|o�6�q���6����"9e��ߣ����5��H)�λ�3�G��[��{���_��vf<��{׋���J&w��ʥ�C�R��b?����W[O�0~�W�i�F;�iB�A(�4 �c�8'��kG������\z��J���v����;7�sr?�0Ec�V]���T\'B���mx�����^�Kf-���]/s.?j�i�ʳ<��-��m � -��z�)*w�,z��m�8v�z���x�õ��)�|��0 -~σ)��Tvd�hK���%mL�7��F(�c4^�1m��v�s����`p��o �O��m��(<� o��U�����0�O��^^]/cy�]��0����Q/�/O_{��Q���6�M��6 ~-�<`1�b�u��ɲ�|�� -+b!��u����(�G(&��M�r#rG5 \O� -ß[X���+��Q��0��:�"y�(�sf���95��=�h�!A�xf0EC-��d�GS��b*�5b#e�^�A`�N�DP�p<{k�\�Q�M�t�z�ߙp�R�� pb�ctNL�0��)H� �Τ���-3�J˕�I�s�)\JC*�؂Eע@,�4n�v��CA�^�yJ�1�GrE�5�k-���NLP�)�J��<�ɖķ��0��Յ�h�Is�0���e��:B\��s��G�3�"�O��������@F��}ko*�-��M���Bڸ��smH�u�HC�T������n| - -�h =r��� C*� =g3�� wF�La^S^��-a�b߀�v��M?C� ҊhM���;2)�O��7n��Z����k �<,�д7��d���h��΍І$�R;x~ ɩ�IL ��7�9����-�_հa�" �B��+��pE�w,-i�2i�޿f]�4��l�gTO ��ޭK��w�|u��YKo�6�� T��d㤽�8�4� ���qQ�d��8fW"U>k}��,K��~D��[Hќ�o�ypr�q�&dJs)����YD@0s�؏�޽�5"��]��jM���hjLv����4�f��� -0=�XD&6I��� ���N&�""h -:� �QXԿ_�#�I� f�������DdF��3 -�Œ�ɾ�%n̨"&�� �^q�W={�[Hhw3��+�w#o�0|��b>�؊��Vڗ�`�p;�ʇN���Osa -�T!,�h��φ�~4���5󄛼ev�pV*js%0�.t� ܂JW���ʴA�Fт�xR���c���¯��T`� �P�*9�b�U�񺜿x�$c6��Y�hs,�'���$(Ý�Gu'AQ+��%Y�� -��b��FH%�멵�rn"�Ӧ� -�����qu���b��7�!��4�*6����0���/�p)�C��ȚʧԲ�!��M���{a�]A�{�ywd8�d�M���(4f:�n|`��Y/:f������1nYT�;�s��yr�s�)K>�AqF2��W���W~��n �_��4(Њx��_��M~uz�9�@��څF��(��t"��Ml����uN,��.���]�W���8'eܒ%�o����h]�j�L�Q�s&�B�K��TU�,�ϰv��3Gl��׏b���[u��� �}�mȼ���� ����2>�U�^���l6�Iq�9s�vB���z��k;o���ԫ�.h��+n����˞v�?�[[o�6~� \`'Yd�vQi���(�Y;�����D;l(RKR���Ëٱ2�H�`�o�L����!}��s��#�� -~��t����DL��s7�������w��J!X��E�^���ۅo��}��SNtWɨ��c���p�9�ω�;�ㄨG�㾬Yt�B��Jc���qл �G�2��ɮ�u��B&�����O -� +װ�ތ����ݰ�'����6�>6��s�q�p����m��/!IH�g9�;~�4�Ϙ)l��!eT�\t�,d4*6@�8c��Cz�[��b�"IS ) E"���/:�,!�FhNA� -!�5Q�I����G����&%DLV:EC!�� �$�D�3�0 -E�c�>X�;�hxwss����Q��Xez�^;�:��m w(�<�TH��Uا Q� ��M�L�؀MRFxԬ����nU�;�^��B�y.��$�>B����b[qE�(��޼��^�^�� # ��v;^���1�Wl%r�0U�(��y��;�{85�*�U�U�I�1}4�� �L�9��u8�%?�E�Ƃ�o�1Dd������>����p�/��ݨ �+�-������8�H0�y��@�l� -�{�%`� �5��|��$ tg�x樃�������P�R�͏��̖�%��z�\d -] '�o�`�F�˱��b��f �N��ĝ�Q�S#,6�Bs�P��Н���w=�+ʫu|�.9Y–sf��c�$t 1�Q<�-X[Bl;�у�sD�E��-�E!�#�pƣ{b���kM�T�?3��������Id,���b�f!6j���\L� ;�����φN��Q����{ M�[у򡒘3�29-�ͨ-9��+�E&�8ʳY������,��K�x�̱�Z,�]��q} ���H�3�+�;�j'��!' ��p�ݚ�CV��դY6����7��5��(M�[Le�Nb9CpK�����U� e�֮�g����r�}�������JmYKW�m��b'Ⱥ�-�Ba - -n��r�]���r��[�����C���)璴�5�\E��>4�c&#S�1�c[�𥕦mAL�t⊦�Q[ r%��B������-�S›�=K����Vʿ͡pV -����Ӄ����Ј8��������`(W�c0p3�� �U��Vg�s ��{�ڏVމ���[�4` -*� �e���&��X��wn4����ȏ����)ݞzنXI1˯Uj&$6Ei�֏1��U�c��eE�%����+/7H� m�՟Ĕ�� �TS�k[���rl�1J���&����k)e ������V㭈��9�j�Rt��jD �u�#�:S��L�2Gx�7o �.�s�<�tε��r�M_�ٜ���8)<��A(N ������<__S�x��ܴ�@ -@OiB ����[��Y�v���v����ϓ����_̙��s����&6�*��rd��C���Uh� js�|ʷ��a��B߯?�؇���b pb�*�c5����y4��ON#5ѻ���� �c>��~�P�@"�'�$2��(%|2��X�����&�r��7����~��2�~m����,�% [#W��nGc�P�Z�b�A�)h%�� -�� -S�������&Dib>�}^<}�-g �kv��@ͭʴU��d�Fz�/�c�D\�I����$;�s�_����0�~ �.LYa,m�ʍ�����Y�^���B���Hj07(#M�%��45�%#�.�T�9��E�([��c�ZДa3x��d 8TT���ߥ����<�ڛ���Z}�j���\dD��C�m3٧ ً�=�_�� a�w �����*��r9�����q�W[6���̉�5�>+����5��WM�( õ����} }��P��҂XJU� �cFق�XI�����'z��xO�h頻��>ߎQ�_�b�"�v������'���/���9��y�+��5P�g�~��>��ݴ��:�j����®y��ǧJT��-�OI���0�)���Ώ]r�����2��C��C%�ܵ�E�߼����VQO�0~�W����@3�MBS*PF��N �k�8N�ͱ#ۉ`�~;M�h'`%���徻��s��.P1m��Qp���T�\.������Ig�}*�1���D����K�W,�T��$���4��" -���R�sbX�����(��� N�TIc�������h:������:���o��-Z��&B�_���`� ��Ҳ�Aؘ����p�����fg?���E�� ��(�h!?uy=�o��x��t���񺮟���3�Ti�H�tR� ���"�� �Q�Qw(~h9m�7<���((�D𺱹$be�+ؔ�yaQL@U^8�A� ^.� -���$��@�:�̖Z6 'J���� Z��䘗r��0!��,c��;� @�{���ӵ���M�o�Y�r��/����q!5�]�U��Of��z��`߆��UŐF!��s�+�L�e������:��Z��q>o�N�bl`s��J~2�f]����i��m�f�o(���%�����pփ���F[�S�Үg��f_V���,�I@qc�_���sg��NxB�>�Y(�^+c;Ov���P[���G0xW7L�`d�)� �@�, ��{ȴ�7>�ˮX���>�ǰ3JT�Ƭk�cJ�o'�b�սt�i��O{�/d�<�6���7 ��� -1�N���K��+�{�ղ&������������ţ��u���Tu?V59�5�!��F�4/��O�g�D��^"�5{���uv��W�n�0 ��+�6`���0�)�nCw(04鹐%��*[�$�ɾ~��8i�bIg$�QMR��t^h��uʔ)�8��Ka�*�Rv3�������Ph��q�R�{_}NZ ���f>(�'� -�Z�}�a�/ɊA� t���X?� ����+πg�[.|ʦ\;d�<�Jt˙r*SZ�Eʪ:�*�V%�K����K#2m�}\� tª��}A��jb\�{t�K��L~�CK�@L���E�w@���U(�T�G��SryƂEgj+(~�>0 ��qnj-!C:�r�lq>&k��0��kKa!��Y�•<�kXqK�x�<��30��ڐ-,N�R�`����'eà���j�2�&��-U ��L+�] gRqe!�o`i���bI��x�=P%<�E��c�Jyh��)�F�o91Le�$T�z6z�w�T��(�;1����ⲹ�>�T�0K}M�j�����m�wh�/��tr�vě'����ً���K�\w�,σ��h~��n�������y��i��i�03<��]7 �GR��͡?��<��j�����C�?���*� �$Y�� -�NB�J̿jtd3mf4�s���]T1*:�S�6�u�Žc����[D�%9�,zOٿhS> -�C �$«�ts��������<"���w�������ǥV�VS���2W��C��+M0��r��-�ֺ}����������x� ->WE],I����郃��~���T�P���6��>:p�Xɛd^?nU���������_�����zp�Ggx����WMo�0 ��W���n�`�X��X���΁,3�VY�$9u��Gٱ�bu�4_�-r>�G҉��B���F'���[��ɤ�M؏ɗ7�����Bq�.k�����C�����ԧ}�`0��J��9jI�FFk��3м@Wr� k��;;�����Jxc�W�����'.|�f\9d�<�R�ǹt2�J�E��*U2d&5WݍB0��2�=�脕eH�)�c�;x$a0�O�Ģ>�E_Y ~QR�s#��8z��Rq�"S�7 �*YHghI쟵ї��ȳ��"�2��;A��ݓ@<�,:��L� Y�7�'� &|���ݘ����%J�ٶy@��ɖ���Js`�^7nt�!e��`��3��>�#��;p���끝㨭���Lj�ЅEj�P���x��u$p�A��+iب���.V��wx��*t�W4���dLR5I������Jr&I��I�Q/V�����at� �~���UY�j+ ��Џ�q8�J�\��7�A,�aF%5�mVg�V}u�W=��zb�rϷX+��1{�\� e�-W�����H�YS�}.E޶�J@jm�P�l7"n;O�gu܌�#Q�B�rZ�tol�c��W��0��F�>��N�d�kYT��8ʢ*{��4uM���#�x�$�uGg2o�h����7^+�x��x� P��~_γ]�;oމ,�T�����q�+o;��^��g�Z�o�6~�_A��x���l����8p�瀒�6��Hʈ��EYR��O9�J����;��t??��,@i&E�{��G@�2ab��&�w�<���s�5Aa�{�ܘ�NWW�U2m�>�L����(H �b�b�a�c��1�G}\;t֢ک<6r���g��]�d}�$2��vC�1�"?�U�G3���I��E�с�;z�5���-�n �"�N�)�\�A"X�չs#��B�9f�H������*H@��8FK.��w��&���P��v�Q�����3�X��� ��sf�Ei��b�S��[�-6�Uf��|�� �=�ύb9�W��+����p� �Zº���sR IN�d"cT�-����>�P�)]�lxL����6�l��ҍ���������� �1]:��E9�V�$ybmJ� a��^zwS�3޼��~��o]�A:� -����6�#��#�-�Rg��}�Kd+�mKe=t�۴RS=t�Է x��=m�ޗ�2И�Zc�՝ �C����"૒�Щ͑)ģ��J��ŗ]o�0���+,�7���RE�,�h�۴+d�)V�A�aͿ߁�vA�>®�������G9�^?��Li^H_L^`�$-.�m�)\����zv6��h�@,��3c����I��I�8��XZQ��J�5�&��)�a�#Ir�KB��w��5�3�����i:Mx�e�x+�.�n�p����&���.�7�����(��n 5Q�lK�p}��:�u��Z{��j}yz������ �/GH: -��gw��j�RG�����oN_��෧��0���܋w���?��DZ:�a�� �y���� ՙG���սҟ�˱��A�=o�.����Շ;w��m��8����w��X-{��q��G�Xg�/�M�#��cd��""�� 1��8%B3�s�c.��ڸ�b�i��J���P�j�� �T���܃h��lbl��!S���Yې�;g�L.��ť�u��۶��]v�}R5�>�(��0"1<�����u�p(��^�[�:{�FP�u�$�طu�i�p�J�-�`G; -�v�f���L�d�B]���.��|M_ZxΊ�7��1�*�27��{G]~qI�qȮ�wՁ?V���;�Wmo�6��_Ah��[/M�vv -�(�PU2��a�J���(R i/��;K�dM���}�I�{�y�x8N�=���@�B�5>;��L���Zqt��E�]��d�*EЙ���Ѻz;�uVm�\<�q�#%3��m�Z���u�,�i ��L��8�z���I&�Ҕ����M�xvd�e[����/Y�D�Oc��D?V�Sp � ���ut�;���{��ύ�^���a@י�kۋ�����%^�an����囫�+�Y�~����m��q���C{�\C�b�����u�v��Ƽ��~����1]ǻ=��q�y�ϖ�Y�O���1}�xH�l: h�-��M����s�X���������?$H�s�������3��[�O0�wl�ת?!�'�~?z^��s��_�?�H��M��j�1�&�8�j���8���qݯy��TQn3-�7I�w�)>�LO�;�X��E֙�Bi� -�8��mʊ�2]p�Z����Ae��4.�$eUcD��Qf-�O��J~ �E�I������F����]�t�ޠm)��jP�PD#�R(M$dx� �N�����l��d�e�8y�+*�����MX��:�}Ψ �J%܁��tD��"��P_��VY<�A�ɨ���b�CY�������'i#�M�\Zb�,2�Ā��؝�Ѓ���17���n=4����3�+��Aӫ�V�}��<��� �n�l������?H�…"����)�S!�����:_pr OCVB?t�m ���5BЪ-wCT�/��`����h�������%��PQ���+���}s $�8Rp�HQ="5R���گ�����"��[&&��˯٬Z�c�u��ŖMo�0 �����0�)���e�-;���Zeɐ�|��Q��&i�&n0��H&��(���J�%8������G��H[(������Ϝݏ�R����6�����$d��.�zd�$g�F�?�%�H.93�_ ����;�� -�\�F��Xg*��О̥�*WZ�&㵳��ߓ���86m͸�K�b|&mE�aƧ%0��b]��GK�njJУ��tKm��lY^cQEf�R���+=M�2`�0�\ Ϥ��N8�� �!n/����P=���͞��L��+���+@�kN�2Bw;��t��Qw�`�LK���8�Hv-���yb�ϐ!�";�8�SyM�L-��F9��=ʗ����W�`�l��g�4�94��+���p�Ɏ�m;ci�6��GW�w�s�J�o�>��pf�����y TG>�����{�i�Bz��@�� '� ����R٣��N�ӳd��r�,\0Lѐ-�,/��i����9�4����7'���91�O c -'��sbӗ���m��� (�~_ަkg3�7���\�Feh�z0!-��e����R�K�ho�&��{@/���?͔�n�0 ��} -A��ۭlÀs�2��2k�%A����G�rf$)�Yۛ)�?�����^�=���)���#g`�m�ٕ��f�ឳ�ꮐZ��(؄�w��s�����5�93�y�vк�/��Wk H$]Ό�!8!��qR�1V����S@�m�50�(P��t(t��*�Zi������J�$�@+���:�|����Z[�k2�� �r�&mOb�7�)-{��C:��Jc)C��/e�|b8�A�a�4ף<��u��SZf������A��!���g���1Qb�;k�z��fуEm3 ��o����LI�����ñc�?���ࡥI0�1��)�2x�q����)�Ct���B�ָ�ȧ�H�E���4���&����R։�u��862�C�l���x�#�f��|���;<���xwc�,��'����#M���.t����˄���⅙���L�,n��� vڍ��|ϴ7��� ݚ�r�0������0i�N�Mp���L�+F���$F�ݧ�1q�صiL�w����]�H��< � �b�7���c������i��o� ���M� �R's�4&ZG�MGG�$�����6�� 2���i�Yj�4� �j! �i*�4����i�oi�0 - �U6��R7�kK�'��t�'�Ҕ�l���͍�:-۹n ��[=� 3��ۻ�T��s�M�C�0���E���0��:�b��m�+��h�;]��or��Ҥm���eO>���\ZN�e_�ʧ��|e�|6���&�C��Á]�t��;����v�Wv.����i�?�ﻳC�o�9y^�/���m_u��������?X��������ɧ�DW���L�q�k�.�E=�4F4P`\���gL1�L/�F�K�4X��t�>(O�Hc�!��T�b%�%��ԪM�G�OmHб��L0�ybGT"Z�#�'��ýw!a� �ז��/���t�� "�V{��c�kH/�݉�3�^,qy�N�d�i 69=ds�����y�4�^��\�o��F� �~��$˨m@�#�;�e3�&&� C}2�}�w�ޣ�ޑ�ü�|�Q�d��V�͕`3��{��򣗨*�Ro��sa�8_��K�W�r6`)�m��U����n��+��M��d���� @�lE����� -�Y_�J���P�&Fw�el�\�*�[�Y�J@�i]�[��?2Z/���� - ���!�+��Z��E�4�g7 ��ϝ�8����j�%�B�2D/U,D-u@!�H�h��$Mq_ו����H}1����U��j����Ayr�{ʶn��A��mÕ�~��g���4� �*K� d �1v�@�;�)�ՇyvZJ�%��[Yð��.<=}���$G�g�uN�[�^�d�I�Da"�� ��O�P�=��]�6�R[�¡�� �B?�I��5-��m���U�Rw���/�r&��3�;cN��ǐ�P�q�\B|)��Rnp��K8�� gp��,��Ym�V���7�e0�[2��h�c��{B�ƥř�~/�=�*��+�O��`]�U�� -�0D���e�6ziڛ= ��1]m`� �(ů�X=x�7��f� O5 bqS�H���[����-BS�g� -�X�b�s�3�2���R(=���l�=�E���Y�|���u�sU1%�L�͛p����op�9�a�A�zS���g�R(1ȫ���D �(�cJaɿs�� R�=��U���x�l��79���%�y���]�8F+���O;z'wm�؎��k{r�Z��J?T�S�L�MaCG��E�W������W���׈bOZz'8w �]�`�M�S۩��QXވ��9N����q���Az�RTk:���`�Įo X��� w� �C'��8��>��USotm��B�Z������5a08�? ��`$���/� -i)Q���.�طZ˽��yN�[*2��l�A򵺭 ��|���� �J.?�.3�Р���)qxy�F�mE�X�5LA�5]�5G�=i�1D����o~D��CW*�V���\5���9~� d�H��������2�Kc�:��mF�l�>ԓϽ��'�d����4�<��� .Aû��j*7�.�`M�'�����1���tD�<"wNn�~lrU -w���5�w�u�j>�s8�&��o�'{K\��L��/�qp4h*�G~�����},D\��dn�p��񏧵{����#��\~1Ν��<���BhUD {��@XS���R7��Y� ��˱�Gb�I�� t6�9����HN�0�rm��u@�Q�;�L���ןo���7G:J����?��3��U�����^���kk�M���"����c��E��#oz���S�V���8u�[z�rc|�acaܟ�9���8�jĮ slYB<�oz�>�\E�C -&ḃ��nj�=+��"�NuU�*uk�rȺ�T'{5M��z -���d�0#f������!C���@�QRT��9�W�V����<�b� Mִ���W��l�Y-���Q"�kՕ< ��\2 �[AEH�����S������J�ݙ���z���y}\~͖���0����� �mUAVU��K+Ujz^ f��B޾�@7U��E -ǁ����xF�]eD�䵳���z/Z� -mw���y|w/���.U�Al}&K��c��hU�uẕEN<))��1�|B� -�Na�!�*�5(�����H�c��5G��/�(��ʑFu(�a�Rx�*�[0>���:�F�!�59F�X�r!C�[h �mȟ&S4�s*7N��(��t4 ��grS��ɗ�L$>j��`�3���^�jG��d`�+���L��) m�R��67����ľDB��� E}��5�h{Ծ�,�s�ɥ+F��i� �]����7������\35(/= �왭꽟��N�j���plJr{� �@�7�"0*ݽ\� �X���5��"^���l���2|�?�&.�eލz~fw�AqSe�9�q�so�~s�I��]��YKs�0��+v|jg ��8���p�@�p����%#�Iïg�W��ANN���>�ow�m���\��ZM�w����:�n�~�z�>��٫�Kf-�fe�A�\�! i5)�"��.����RN�kd&g곎P,G[0�Ӡ^<�^D9�L'��뼐�0[gw� e����[.���­�AQ�Rx�B1����I}�y,5����ZnD�(f��*7�p�A[J���/����Y��A�&��̟����9V��*��+ #�ڐXX���I���\3�J��� -�"�ZaE�8��`��rh�j?Е�5� �h�V��� ^⹶�5 ��\+����V�|�}{ib. z�����(����-�H�#���6���8J�U����)�"^|�l͸j��U��= U0���cjRX��.*gϏ�y5fC��#*��c�s>:�v��Saˢ ��G��w����)�a58)r -��}���K�5�~������Iss]��CđPTh�J/�$��ڱ\�-����&2)�/��2�v0���{�.����A�F��ʝ]ݵ�7�-��$G ���`Шj�:%2z�9h��އ� � �R�]��[lW����6��xZO�A;�U���t������1�j��� \ׇ�?��Z�Q �mT��a4v�|�uo�#*�S|�s?�ߙ�e0 -��e��!�ou����ʭ�G��_C�`��y���]=d�k ��.����إ�u�hDBQ��Yuvs��9_O��b={�)[ 銆?; -4���;���bD�=j��'���ٔJ�,G j� (@'R�f� ay -]tk֯��ٙ�Fd��8^C^js/5K�Ȧ��� �h��F�Kv}��~���1eFa����o�X]o�0}ﯸ��&u��MЇV�I}h��X9�M��ؙ�@ٯ�u!�MW"<�s|�o���)�0G�5c�e�a�2c�k���W�����{��ƏYB�m8�֠� -i���;� -��k�.�f�����9�� ���=>9 k|p�֭���7} xBO\�1K�����h�s�U�� -�1+�D��D��3b -#�H���;� ��@�Aؼ�b\:��C &(Í@6l; �3��[%�c�a;�h�bWu�2+WDE�7�!�� �p(�G~d�ۊ��A��-l�" ��杯����Uj^ -"y�ǖ�ĸP�@ )9�V�k� e�>���ߣoxd2O������� ��KK#b�>��e��.�5� -U����0���5!S�N �m�t�Bvj���� p�'��n��ڿ'S���:� �NI��&��?�]�KW��� �����B�A/�V�N�4&�rdzS�a!�F�)��94A/�����M~p#uo'��FpP���2��f�"��iT��R�j�ږjWR�)m�� w�/_gM�;�I �*`�))mh���C -I���=+$o�!]ZٛA�7��" �N��Q=هG�(nm�� ���;���y��f����ry -���T�K5��%��� �U�NP���p��ޔ9��"kH��Z����D����m *U�&�ѲTGO���|�������0�i|�o�!^��e��p��<�a: -�i���/�LS"�/�v{}5ͣ��;�=c4�����XMo�0 ��W���n��(���@?�c!�L�F� IN�?Zv����$s���H��G�9��e�`��I�c���+�¤R�b��t��;�ˋ��P�9����,��E�:/�"5�s�>rV0��J���͹~1v����9�� �Y����� `���L��������SY x����lʕC�s/E�\H'��_Ŭ(%�B��j���OR#eļ^V� +�* �!�Τ�h���\���9�k*��}i5�UAE'ƨ�o�h;ۤ���Uqk��`�Ւ� UlJ�deq��N�gu"���M����&yC��s�Y�' -;��=.��ޖ�������^�hʃ�K��p����NU݇xnhQ]��ރ�D�ձ:��hzD��Zwx�8��\���2���ګ0����E�����:ֱ#��'��ơ)���=�,�)V`��3lZ������n���� % ��fM��k�l4�[73H*��y����Ef������;Xr%Tc'��d_IB��@�h�|ߢR���C� 8Ò�}���?S�ixv� � �Ok����0bH����c1�� -�~�L����Y��{û�Yf�6�d~�4V�bn8��}��C[�sm���CՖa�6�*4Ԉ���ԡ�K�&I7D�� S�^N%�����YCB���9�+P���S���f$�)]-ᦡ^� _p�>���(K��f&�Gj�ǽ��?�D��z -O��]�q�=� - ��g;|��q�=��#�V����m�΢�j��觳����y�Ԙ�*�M���n��\�&/�3�\m�L���h\�Gp*�4��K�u������6��?��͚�n�8���),�i��3�j:rR�0h��J!N�-5��}�5!�M�j;���\U���s>��ŷ��=���Z'G��2�fB.�V��b�o��.�4. -�ˢk-�r�w����V��,{:��lyb��:M�������,$�^���ԧ�G���H2Y��,���`2 v���t��|>� TCgY2M��>T'���+5HȒ/Ԕ��Ж9���yCѣA�޾>9>�WnD��P����+�n�;�1�u���xҧ�؈���*��O�ɀF��^� \I�1�&���`�b������^�Y{� ����.!F\l�8�Q�u��Jc���ؘ�0����;N[�]]��b���N0_ �}��_�����y{9`�����h�lcm�5 ��U=hM`�؄y -�Zzq��Z��8�LO�� d�0a�'02͗~sY8F��=�FZzC� 6 -3� Qj9`� ���3�!��^8拋�kS/�#�.c��0[�w�?�02֊�| \z�G����F���]�)0`�����\�)̘ ������3�CS1#�kz��[��U��m`�x��Olf ��!�gFxƐ�����n���m`�،�Q� ��� �U�� �0u�Ԣ��� �v|��0b0��Ё~@�S펝���06��6C&����p���~t�S&�؈�L2�a�`�8�D����L��:�3�1��a�0`���{g�xQF��>.�\f����"yRZ(�e'eך�i�-������Qb*RQ>w��z���E��q���M�x��bU�L�${Xm4��,*3KğJ���6��f"����.5K��u.��N�,}=Tu��s5��pᬐ�6��f��9�s�sY�pܞ�'W6����h�f�,���vSO&�s���Z�K��w�(��[�� �31�Uה�O�f8�Zŭe�r{`�l��R��Ve����U� .+�L��[����ۇ�%����zk���/�ʱŶ��(���[�#x�C�y~�#������; �/������'�H�7]]��1E��8qQ�aw��c�u��٨��Oo!D�~����h��~���ǥ��3^��gA���U�n�0 ��+a���m��0`Ů]v.d���Ȓ �N��%�Iע͂�E�=>R���S0����}^|b�Z�F�M�~�����vyS -Žr־bm�kQ����m�~�1� �^��ݡFǃq 4��[.�b�x|��(egv�����q�v����ahM3���9zʀ�>8.B��\yd�R�AzYK%áb�����J���cS�ƈZ��f<@/���a:�r�a��"$���1ח9 ���j����,g(���9� ��你�20k�9��@�xx���� I�~vEޣ�;ܣ�� �,b<۴�ȋ:�I��1fM�d�>(��C�I9i���c�x��P��܏�{�Ə�1��ta�:ny���%��W� �N_<d[�Wj��oޑ`R�_P��j��x���7 -��������.�֙��m\ŤD,�)0�I����͟������[>Hz��J��|<1E!�(aG>��߹��%)&��é�H\�����c���"��k��A������vjĴ��%�k����aO٬�Q�n����bo��dN�Hn\����[vsY��.��W�o�0~�_a�}M鏩�H+Ԇ�j��>E�s�Uǎl���B(m�:m ��s����O�_M2I�`��ʧ��#J@q� -5��C��園�˃:��Z����t�\����:�Gy�'� -�g �dPH��[���Ü6�(����.�W���E�K�@9[�D�,��ʳ���ܛ�s��cʕ����A؏Q|��݀�1��;9�CbP�y"5^�x0f��i�NB9B��x/}�����V'h��^�}�3�(�4+�����v{7A�mD� -��t˔��ӻS� �l�k��g[nu�������i���>�mY�3�o�{r����c\�:�����u;j���^��ӣ�-c>��s=�@�k[��)8^�%nWg���w>0i��t�W�XX�)�ԧy�H1���br鱩�,7"w�p�Y>Ǹ^FRa`VՔ��X2�x�0�$<�"}�-�93� �D�/g E����J%��� n>��X�PA�N9�lh_&D�͇*��b��;�i���B�| -���QE� E=�f!��R�-�nF������Ow�KW��!��`��49���5Ŭq�k.��$>&�5 -�0ݗ�_)�L�FD������)T���=� -�����m���f����˒���@���M�J*�ڕK%ˎ.ˢRIN*p����fw��^� �r��4#�%)�_ht7�q��S���� -~���Z �3�S��[����/��@߿��6cX)d~��ݢк��͍��۪�r��-'�F�l��5cw��J����q\U��-��w/�B�$�y��95_��+�%���b��2(�5��?��+ʨ��-�zŨݙr̚_�����VLd��O�Q���6آL����U�+��#r�#-P����$��sF�^ܴKI�kɑ�V=��׷7�;�VX5�U%j�D��������4< �g~ �9N��x{� y��\PE�+1�E���� ���h�V�`��(zR�O�sa�D���B� |��@�%�+VK�ow7k0��9�QϺ�9�U:�(�)q��¨��hY_��VB����ZJQs��V�{-���*�3�������'C�c�y��.��"V+�����62$3� �iY3)����j����IT ,zGJ��$��~#֝al1d��R�b�����m���ƙ�bF��[4^�M�d0fdû �/�DF͙Ο{>��diQ��k8���Euk�: �'��,BQ W�u�;���7$��E)�]��;������.�|�&܈E�iѬ��C���B����6�vLV���]��1���A�At�I���͓�l��0�rgO���X�.WS���$��m`A-0��d p���_���QUKsN;��m�Q���è����z!ǯj �%cy�$�\���t5��e�bF�Ɣ�ʆPN�%�VD?H;��_U$�g0&�=�$:%f-�?��)�VB��X� -L���D��]/�߼|����j��z|8Ne�o`N�^`��( �<�]5p6@�r%��V�b1�H�Uf{Ag]HB^�Ը����~'2>��$���Ÿ�J����?jl�u!J�B���D ����t����'��*-���]3 -c9v�Ƀ�~�9���R�KlCh��� -h7OdଭF� ���ƓЦ_�"�;(�D~X���W����l��¹�e�A��'��LC��x\��!������j+���N�җ�*h�~���WoF-\;%Ћ�>����j6�}�P�a����7��Qe ���h��W8�߸l���������A�giWˎE�դ�9*rv׽�xVHQڽ�6M�\�$T��B��K�lLF�ݘ��qLzn�<��T���� ڣ�vGkc�bdԽ�ј5� EM&�"��דV�_*IjKP˱�#�h��X��(�kJ�6<�(���hz��z�"�@�D�$���% uc��_��#��My2dur���w��= )p�W��$_�"U��@��>8�I��$�U�3�����r����:���]���-N�uU�Z�k{��:k8 -��\{���R��$�_�g�@��5iBY�^I�#������{2��� ��ݽ1.�wb�����0��H���d� j��-4(��am r��.�>� �4�O��rU���u�����fr��Ll�$.pM���$��<`݀3�j:��G>�9/󿡝$�5Y)��ހ4��"]����ճ��}�)����li�-���Ǖ���J�`�}�{��R� �f)����}*��^|�׿��(�$���{����@̬�=�����&>WkN5�%�`�v���y������n��[ǁ�� �"p�:�k͎�fQ���^B�ʁ]3�Qc��$ ��s�� ���� S%ZF�"9�߱�u� -Y����(\V���5��~p*��~\��ɚTm�d��澢A�n�?��x^��T�C;�+x�`�@�+N�` -�]�D5��P/bC'���D�SI�ȑj�Ad+I��a��"�Q��u= kj4�ȁ��۬�;�]����X���T������솤jr-& ��eD��M��� -צ�B��ġڊc��jRU��L(>������[�a#��-L�*[1�k�XIQ �c[�����I���� U�m����̰� E��}k�d��������/��M9��%���;t��\%9���7�ѐ��ܭ�� �>���FuV��>.��sE�����9�)nuW)rW\�ljk)�;�+W���R�7� �u�q�iQG;��6 �f[���aI��P��d`{�wQ�����J��J 5����u���bD��Z��ޓȍ%�:Nޓ�`D������HA>�"�y��;G��Ï$j��'D��-����?���W���he��)�c��v��ęt���9���E���ۣ]W�(�f|[�:#pCm����� -D-�JQ�Ґ���)���}ԕ8M�|�`��e���@1�˿�����j�bǾ��� -���o�M�6?��U?]7�,>����&c<�&!U,� �MI���k�^��g��]��vT��P0�L�_�kE����h��r#߹��S�������B���Yz3��Z�T�m=aS���qj ���3��6�;3� a��a�H�~CuUm��[�Vy��ۜ��% UR��_+c��qʹ�I�����x�r��m�%c�jte{~;�E`l�ң�='��b �����ݦ%#'�w�>�nvi�F�u�?��s ���|�� �<���p�0��>���Ϛ���0�qІ���us��'�+���& �)t�d*�3��f�G��r*� uX��0zz�G(� 5hC- C4j%�l@'��u�ĉ)S� r��g -5�����W�r�L��:0."j6Ǐ��#d��r����2ْp�Ő�:b��g顂�s0 ���a�WS�h�C����z��ћ��s� Z�ES7�"i�A�a�I�#qV�� �$�&ؤ���I�i�C- �R�B�:����!��^��O�W��_�JϞ��Y^���#��&G��m��Р�F9���xF�/�}g� d��r�h��� -��p��t��ќ�M������!����:��p�5�L�3�k|n���}}�;3 �{�b7���:�a��%h��$��n2�8ʩ^�!U$㻜�0e�ʯ Oc���ze �OKK��-�������T��8�a����7q��Q/]�ۼl\��sNQW�y.J�u���7�Y�*������*��y�mT�k����+F�h�5�0� -�̏K2�fbU�z���diw���4W��O�ղ�O���jAu����u���K�B��sfK��eDr��4� -�����'�e��P��v瞾 ��xu-<�in�k���,�7�˗��ZMS�0��+4�CJO�N�L��v -=3��I��d��뻒�P�Dz`��?�}���]��˹ � Wr���}�H�r.�������o��82A�!��4�lfm�}0���bV�jy&��f��B��_s:�������H:SP�,\l>�8!d8;Sy��J�,��:6VSfGل -�7 �笾|�����v5ʊr,�C�%�7��|��X(v.� 0L��"k�Լ�6C,,-��v�#N���4�R�U�D��p�iiXP��,��2#�[m ��J�4���`A��ٌx��P�֦�/��������k@a�� 8�?��I�b*��ݡ_MDǚ�����!"n�G�����# �t��'�:a�~贸1�4nX�o���(����B��$�'��T���*bv��٩#��1��������"�W�)� �)�z�/|��  (v'�o��rF�e��.bOɳ�@J9Y�@�3�>��O{3D૤M�Pg �9�0o����� �s^�P�B�kƮDz��z\�%�>�%LR��R����z��L�� -� �Q�\��Tf�o�ЪI��x�ms7�Y���R�mw0�h���5�ep�9���cJ%|g� �N��-��|�M�� ��xO���϶:��.�B�o�7H��%E� e:�d�l�Dl�l#�J���?V�v�+�j��n��G�+��-%?EM|*�����rA������ �.��yIJq��׈��Z{G0%�^���d��'�c�:����?��iZ�d��.�.����*�+u.e�>s -��g�� ~�W���3�0�"\�w��U�A��3�'{��k�8��>p��` ��z5�o�]�}��t�y{��� -�ʜ����b� ��5��6�� �R��is��s ^��0�Z7�0x��|�S�=����Ȥ�$5�n��zU�^��IZ�J{��\�>@�)�W�4C�MEwO��(�fչd s�f�L��殟t�w]mO�N���3��y ጱ���jD`_�S�&��+���D �� n���f=�����K�zGO����U;o�0��+�k�[H�֬A��E�,6� )����aUv+'{3��w��H�w��@�>h�����,��B�m&���}�*�n}�*#C>lC&��m��j�*W�ne1&�+ecL&�k����A�������L ����T� �7*��l6SM����T1�4�(�VӲ�A���τkr�;.�Js8�5�6�ܐz�]���E��j��x��a�N$�=��[�{�t[��a/M���Nz��M�a$��oG��YE��X��pR6�sw�d�O�����#��YӾ�n�l�@`���-}�U2l��r��(�����S����L�N�-�Z� �c�4YGQc/>�%= `����X�O+���|����3�ו��t�^V�=�A��v=���;O�.�:�RIk�,�/ ��r�G�˃5ٻlg��-<8yv>�������U|ͪ�L���#; �8q�ʎ�/�)���;�N�ٵ��ͺ��o��1O�0�����w�J��bDP6ǹ�V��]����I�P:������v�>6:$���ay�� ���B}l7w� -֫En�f�$�\��H|ʲT-�>V��(�QP����mk�|E�5�|�$^7�Q�U�V���W ��Aهj�P6�_m��+�% i#����4�E�5粳lK��m���z�f�`�,�`J��T dC6JZ -����cKP��`=$�CLmF|���Bi))��b[/�Q��t˳�U��0�֔�-z��j - ��{l���ݦ ��Up�td���#��v�Ӯ�?��:jJl��N���DA�/Q�� kLk~���5���<���͖=o�0�����X��3M;t(bY�P�)"¯�G���=}�I�8����H�p�>/^T^���Ƥ�����'�o�������啀��E��L �إJtD�KQ�i�����!)*m6�?n����p'Iu����T��?U�f~P��5i�������h�S��gG� z���Y��7S�{�oF���R��$@։�TT�V�ĒI�jw\�km4m*rmt��4ۊ~Oi���W���IE�M�mf|G���5�4t�x�G�C�����V�۫�x:�,F��a{�O�j�����r,���L�-&��4 ���^*��,�&b����;�gF�j�O��0)�!�+������$1oF>yjt�����<�ow���*�2�;� N��xV{o� �D:6�:�� �>�����X>�ʇ��7}�o�@o� ����$K��w��s��0�g��1'��̲~"�����n�0E�|�h���l*���PT$�]7�c��3�b����!U˲�{�h�f⬭44�v�P��h� I�R�>��f�򆐥�Xh��\��턱�"{��i#���Z �Z'��ިJ~oIy�t� y�U8߭�@, 9���_���"4\C^���F�h#~�1 ���l`y��5����1� ٖ��b�S����׫���������i s�"���MO�0 ���V�pC��4�����ĥ�4��O�n�����Ѓ��y��i������!r�8�� -@�H�T����s��$WV�鰋����E��h�k�i=u�Y J@��� V��KN\N6�TX�>�rd6����@D���1,ycQ�,#����1%b����8iwQ�K@M���^��M`T��V5��-��k���KܤTu99�� ��� -x�S�%�����b�eH�p��C�͵�:ۅr���!��|F����%Ƀ�g���~l ���q��j?`�� �v� �T���-�l�� �� M=Њ�n���F�{=�M&��0j� ��?w�8� i;�L��4��(F�Ѷ<늳w�T�N�0��+�i��PR$�*μ�g��eoi��q���s�>2��:?Z��M���=hU�N qy1�=p4�ɕ�!@l�� �;̲�\�*Z�,r�Pό)ĩ�� ���,���� - 1o{�;y��P����d�KŅ�� ( p�E��:B�U��D��TiH�a����v�(PԺ��A�Ρ��*����S��=t̵�h.�5�G�y ���IK"������<(}��8 z%�lw��c�g���v�����\Wܬ���|�r�]ɢ_rer�?� (=�����:7�Sy�*��_�X3$y���Wi�٠�6�!heRP�5�����[����̓gi(*]k��G"�N ���w�Kb�v{���������y��{:����S �on�N�Ԕڪ���[�Kγ�:~�ZKo�8��W�o��- -;E�$m�n8.z4(i��H���h������!��,ɚ��̓3C�>?9y�����Ǔ�x,��ǃ��˿��ϧFqN�&�g�ǃ��i8ī��D<�p0C��I�<�RU� ��-i �A}�|v���Q&I�&�-����6��f*K�0p�8�� s�3�'������i�<�k���7NCpj��X3P/���Y~4�O��Me�]�A�o�t�)�0A3��� �^8���&9hY�|CP���i�͕�K�0���+B�]�M��q�� 8�G����4 �k��{�v�A)�x�o.���W��j+Z����rv!8� �m!_V��+)�\[E$��Q!+�p�e)��*��pFQK�i�-佊�]�!p*+�S5PP -9��3!r�����h�P�+�������?�ڂ�$�Js!7�RJ+F��HX�E�24�Žmt*�����L�o�.�ׯC�O鈽�}�%3�xn�� �P��Z�1�g���l��#�S�}C�XUѿ����[o��5v`F ,�Na�z@7t<`�|O�@Ũvcn�y��i��;Q�5�` ������f����_ߢ<���;�V���0��WX>C7����²'N]�m5q&���-{B[��q�f ���lǙIߛ�<3j~�n���!jg �z�J -��U�. y��}�F���U� �(�c ���Y���7�r�E�bPRԝ1�����:KRXh1zPX�!�+ί��[��U���]T�~e�� -Y��(E$ �ư��.JH�U9U�� aJ`TA{bqB���x�5�T3 =�pu��Sk���`m<��Ky�ϖg����n�ߡ^6���4CZ�:�k�L ���v�cP���ٟW�{���*���}���'NL�,���v�"�e�z�����BЩ�y���鳮��B 9�[%��(b��vm�!�T�^���ʅ* -��^�?�G�B��a?8�L�N0�`D�B -׷��Q���5�$s�}��{�y q2�1w4Sh�� ?��|�i#�IǾ�/߸��~����~�.'��� �����U�U#XQ"��2]��5ۙ���u�LC� ��W�voh�@W~E�=��͸f�Q��5Q���Q�y�/��8���c��SP��a(�3�߉<�󟽖�n�0��}���l��P���D� iA��L������;�R��U�lj���of⤼��~`�ƻJ�ټ�N�ڸ�_�|x�V�����V����JtDû��h3tC���TĠ4�����a���8�c��JL����@�#u���Z����g W��CG����J4�FNI��ǰ1N��(�䬵��z}7��F�@ -�s�|�H@�{�|��a�Xy��(���Sև|6�J��Y�Q��P@�: �c���T=����d ���.U�,�,{31�=��vx:N��A�vgig��Vl���Z�5�5^� C�� ����s��gZ�q~]>�x�&�n��ސ��^D���O#q<�� �7��z�����&�C�_�x|��^����b��%��K:SK:S';�瀦ٜ���h�<���dJ�}��)�W�����?��R�E^���P=O�0��'�԰!� Bl���bp|�}A���u҄JL1�{���v���'��(4�vs#�!��[#^_���W�x�3���3�{) ��>Z:l����F��Ӹٻ��0Gm���Wj@���lF~��.h.��6s҆�i��b�˜v�ӟ�I�Z2�'�1���I.�����32p�`W;�(����"�������l���S��NE�J�47���P �&�����Tv��6�P�����X]��6�q�}Ā�?�s��Ӿ -�ͺ?���*�_m�[7S���/�]�S�8�_��S;�B?^nn�������i�����r�bKI&����R�`� -�|�������L�QA�(���^FsmehC Of�4��@����d����_� ��Kca�D����,�H �*"F3b$��b��Qow&@1S)A̤��5�2�?�۝W�WRe�05q�#�)�~J2�(�1� 1��E��O��q��咚��E�<ъ" -�b�U��@��v}<� �r�]��J%��!�)m��D33���Rj�~����/_�X=z��&\%9���īڦ#R:.�q.5;;�p3(�Ñ!9�dJ�����ծ�[2s�֋�%�H(j28 L�׉��ЙTuߵ?���8d.ݝ J����t�� �@�v�':����Ɲ%١� ��L�� 5Q�2"ؘ�ڰ��pa�XH]�?Ŷj�;��\�B����� _۬a�lWC0q� T��ә1͘X_I�m��U��Q�:ʸ�� -ҭQm��)ϕ�L5EUa�����.�iw��G��J�k;�o*u��ɷQ����x�N�� T��6M#ʾ|��ܡd�H�Q !68&��G m�h�q�H7����? �j�ˀ�H� /\{��ZA�gSH��Ux��1O�+�%~Ġ��,�y��4��*۶��u[g�Km��ݣ�ÆP3`ꭗ1��Q+���"��\��%S�:�!��-�&Smc�)( - &�qh������v��+��b��ט��A�}��9�v'`/a�>�;��k�=�3'n�Fϵ�n{n*@�WLI��"u�U1��C�K��`Y��ΐ��U}��`Z�ha�����ͺ�j�߰ȁ��h�.�o~��c�����>DN�Et�$��\�.@�M����"$ �0y-�R�N� ���q�� ������WQ}x���s�+8�:&�9'�ʏ���) aոI����p0~��XD�Z}�5;ċ�X�8{CR�)ln�w�Wg�q�N������a6���τ=�Nd�CCE�O4)@qP�ـ}�����c=�Uź��L{s�x|3y�Hů�N�~B! 6�Z͞���c�y�;�eLt�D���j�tW���/�Qi�x~Z�X�~��zdD��8�N�]��G&fyT�Z �F���Ӄ���]�Y<u^ZR*}3��yl W���M75K�����N<�<"4W�Jx���+)>[�Ż��j�Kx����v��G�5R�F��ݤ\Ǡ���Vt�^�l��-��T�=R���.7,��r?v�o&x0��6�6��q���F�.OﳢcB�t�lv���~*��n���J1l��͓*�=xa0��ə�|�����G<��V�#\$y�Z_YIҺ��w�׎\Zhuε���y�T.�ױ���k!/X��y�nn����2͞Lζ�����R��:��g������a3 �����}B ��\��v��P�S�ӡW�7`,ɚi����iےF!�XGb �v�Nt[�!������bX. 7��hxQ\���}+x���~�0���ϣ�l�O��h�|��NMB;�]��^m2���K����������� ~�W�H���`@��tݠF��#����s��N��t��|q]�X��2ck�);����Zaw'i�d�x5XT��� �W� �yd��[V��􈖮2`�����7��) -kvl[W�7�-\b���V \����Ӂ@��>��@~�4�mB6��Z+ˮ�I��7���3���E�cn_���«�CM#-�C���6?�n����>�7�;X����2����_�w�y5F���}��S�V�yh�\�7i7^Z?&��s�2x'�� ,6���҃_ hs]�^�*۞,R�RGG�d;W��l��U���iǻ>BG�ї�Z��m�ZJ���T�{��!6n�;���t�[VB���U,� �Nթ{�Zp*i���A��[�f��w�( �r|���䗛��c�T��4�E�g읨��N�RѤ��dL��_蒖�T�N�j$x� ϖ��c׆yJA�q"�䅡Gg$���,��L��BXE�rn���� �ٙFh���d���)�� -�|>.�� 4�U���9Ғ��4��L}����#�R�d�+�d���6��� )�;�r�v�D -9L�~\��+��T��3�OeG��Q�D+�5s;�� ��ӾJ�W�g�@���@ �e�����]�!��gq�293'��� -hcyS�s�����'G��l�w�4�0�s/��2�aK�@rK0�BޑS`�00�~�Z&�X��i?�X]���R�� �2��j̕.IAA�+������� R( ���t/)������ioG�ῶ�omX�皧ۯ�My$zt�B�a/틿�,���%@��rA﬇������b�?C?I��*U_;b��E�1�4��"��wUq[��nn�u"Fz.A��E�l��?��V���4}ǒ��o��N1��:" h2��bVu^���rJK��2��'��0?�+�0�kb\}��n��]9dM1��h�N��S����R����p�)#�a - Jj��Ӆ�j�:,9��v�4 �]P��s�OQ��S�Ϥ�EH:/ -�m:B�K� �2��c*G����'�9�TyQ��{�6nTe#��I7r�1|RK���4R7�ߛ�W37�r�s����3^�)��P�Υ�����q<��R�����E�7�v���A�]$���r�����k�� �o\��{a��A�}�S��b!����\UY�S�g�Q��5]�e�����0UR����қ�}�Ժp̹[ު�c�DXv@puNf �G4�����F��.�A-�=�S2Q����%,Xg����X�$����^�Ҁ^��;�X�biU�L�w����R��g��� � ��01�"�����|���PB�E��O��4M�f�W��QP��*�#���i��2���J���ݟ>y3�;/���?f��6�P�6�M:o���e -@�,UC��M.�b�g��� U.����{�µI�7W��g�$���% �Qy�T��_8چ�d!|�X����S4)Z"{W�}�d�bp���\�N.,���]�� 0mr�'{�_� W6�ZG`�ڽ1���*��wQKd/S�a�2ė00�<�8_G- ,���>?>��728m>f�2�����+�FQ�_����!_���.����76�ь�*J�:��5.X�(Y��䓭e�/I�g����@�Z�4�^b�0ޛ ` �����8�'+l�a� +tP�� �o>_���޷+�ݗ1Ң�Y�����7�2�R����3�\]G�֧_�zr�?�e��� �r��o߿Xp�R4 Q;������H�d�&<׸8X{nu��Q.����q��Rt�3� �M(|�ƣ؅��k�N����WEo�bʻ�x�\�w1�8��FH��{w,����fV��V�-K�<������.�`���(� ��5�$X�Wj�d�.|�LϹ� �bej>��Vf��*� +��ߏ32�̋� �S,G�yRDH5�*=���L5-��>�^�l<�e����ƪEԮ�X����c`�O���c�F��3u;�~��:T�nkv0a�'��^.����6���oZ~+�x9����Q���1���т�\[�ЬJ]�S�X��c -�z������r�����|�vZ3���Ӊ�&4�h����P^&R);�ǧ��`pl� ��Ahe8�L�c -�;TV�p3R1�u�����*�'�Ȗ",.}"Y�5����6�/�\��7z�BY�4N#������G��l��tXy��q\{���y�� qa+��N�G�M��{f�m�� [*cXs,�G`;�\�)��ڰ��[��A�Y,�1q��ʇe�~����0�a�>��iW� Z@V��VSzZ+(�͕� XR+F^9/Z�χ&R�T�O�m4����4�)�㣪��M�dH�;6�]?�P�hr71SLN��0�<�|5j�[�� `�y��1��Ů tn���Hl0H٥�*�{�F0�*�� �l�Ha���cx�S$�{|�,m��!��~{ e�5�+��&�E�.{�U9�|��|t������0F�y��q|�۹`�b����Q���]�s7 �B�o�Q����4H4� {���������X.���CK�uL D�öm?��� uLN'�� �=����B�Z���XI�39L���cl8.��b�����`��&dF��6t��� �x��<��<��Pli� ����g�V���;�ɘ�-��B�8\��^� ���Y�d?�����f?6}R�q���Yv:����T�YjC>�#���1�C_'S�)_;5^C&ڂ�\!���ªݱ.Y�7� C�W�o"JرunK��1���IX �,&*T�E��������'��<� �N�6#{(Ϲ5���n;AT� � �)�gi�&�ɔ�>�E�M�� -n{Hr������A��q�/>��g��A��=$k�R����,��Ҧ��h��%B�2|{���}��_� ���w��A}%����<�{�9�~Җ�O*X޲�S�9�+$��S�[��!I+OT5�<U�ngرg� �}Zg��gi�=F ��8G4g͚���B�\�t_�-l���� �غ2��o���6�o� �kn�c"T�.崪!���l��~Ŵ����5����!.��U�D�X�\ҡK����twj�?�]�INa�������a��߾����A2��MmN�_����� �� ���G_2u�9A]�k,X"���%��t�dI���� ��E C��펤v�@r?ǘ� -����\ ���K�˘�� -ᡒ��˵V�媬�0�������z� ���c�/��=�s�p��*�1ž�����B��GQ��jiS�����O&kߪ��|M��A�)�nQ�~�����2iP���v�3��m�c�%M��X~������ُwM�� "���Nq��b j���v(�ֵUl�Fߦ���.jI�:�� �z�N� +%��f��֙]�)9�r�nvA--�mj���>�M��+��U���� ۽��x�4�[���4�J]�M�������m+�;C!�{e�T&LL�� F5;�1���Eʂ')����gD�Bl�+��I�I� ������ Ə����i��g<�O�!��0��N�4+��9 ��5�F��(��B�f3��gL׽����Ƭ� gE�Am��X�v8f��� �b3Y/f,�hQ�_���>3&v����X�x� I$-H�����}u�v������TT}���U�X��>=��Ġ `�盇JS�b�1�ֱ�Nי�������]�F�IC��/�(�n���'��#_�E�b�CW�Y6b� �b��UpN�h�v�?jcf1Һ뱧�HZ,O���sU��'@#MH�ᇱ-'�arGE�G.x��^�tl�s�?fvιK��q>����j<�D�#α�������X��L�� ��H5[�����6L����L�f�ְ��n -:b"��ws7 0�#���.�juB��������9a �t��B�XDw.A&���������&xΨ�.������ʸn4&�ں��^�I��\�O��~�xz��"�<���l��Tj?��A�J)%��bb�'<5����Fr|l+/��@"���x�ZK�l�>*Ă���V��xS@=�P�\��\�K���!5ӥ+�+���-ǖ���F�@]�ؔ�4Ἇ�j>� )�1��2� >q�X����d�R�L����NȌ) '�՗/�V��y��߻'��3Y�i~�E��O=^N�O�u�0��>&Y����V�肅 �^ ��3&,�v'�v��|�?��,�)c�e*]�l[=�2��YxI.�RIa$��L�͙�/Z�� ۟��dm70o��#�$�L�I�Pò��ת�Tѹ� �y�@J.�m�-���\N�l�S.��ݟ{�?��eR��5.��fӱrY��m�̭!��$j�uP�&�h���L.vx�7,�� �kI��aj�8��r�-��IMy��a���}h�Y�z�օ~'��j�5ό���4�/#�zx:�&U�OB�J���h7!g���O̊#ʥÅ�2M���� ����r���Sn��j���s�]:�v-�:9�R�*���R��a������->���z!g(S�-T���>s�n���"�� X��������ɘAD�����"j`4'��e���A���^�8kGP,/-C'��5�)���^�&e��j�x}� z��½?\�d�=�z�x���ۉ6{P�p������W,3b^���J(�׭�$�Ի�)�M4���RMc��L��� ��'.���*N�S)�[�ɱa͈��ę�K�c�b��s\��lԃH<��˪xV��E7M�L<%���ZW�ǔb l!f���sǰGav��Y�L(wi��� *Qr�Q�T��h3�����h����sһ �|�@���D6�J]۴�еD�%&KP%��ʀ2ٶگ�{����r�ZG;�NJ����R�/�c���[�X(u�=1i� �Ơh������sS8��u*� R��KߩpB�T,�D����� -��� -�Ԟ��h8 j�:�B��`1� �Z�N��-*ԓ����M�DNZq��R�8�7��*!��_T̼X$j]V�W ��N=�zR�S?���u&e9]���J���)�\��-�|7vȵ��l�p�]S膞go~|�;�e�4� ��7�5&�?>�z�pW ��:�;S���e�ӄ��9�r^��� �LU�Sx�B�S� xhX�nU�����e@/�=�A���,1��Z�+�Ǹr#u�4k�}�Z0����ŵ��V<�u��_1�+����->�<���a��*1��k=��P䠋^��lZ��r�������b�,�����2B᎝?噏����"j�gŕ㙬1:�l��n�}�� d �v'�� �!;�J���DE8K������C;1 UA����;v� �7�a��rm����u ǫ��p/p��H�zQS�e�ꁛ_���A�(�[3+�kڇ@�Ts�l��/����_[؞U��ϟ>_]_�=}�/ySTX�r�4���} H3�FR����/��ˌ}5�㑷/��Ya�_��3Ok�5��p��֢~�����������Gx�)�gA�S��U�k �S��#$���5��o4������z{RxwEt��U����UQqj�Tɰ" ����9+Yn�lܓf��*$�~��0S��M���F� ��+�������O��&�-���$2bc־���+��+��p4�3 �.?�؉=���2@w@��1���P�Q�=&�����9o90æ�Y�f�1.��^���o[�J덍��郢�X�{�`��I��o����_��/=T�\I]{�\o�U���ho�_�f��4ӭ��� �*��&�RM���H\�u��3�gF�Yr�M��ݕuǼ�] -M�}�����R�Ӡ��u�8���%� �n���gR̼g]��;�Z���0����L�4��[ � -��Ϭjo̗L��pi :�Scz��1]���=5,�!#����E���S����R����,�fZ��AjMI%�jxݟ��S@b�z;���q -�T�E2 G2��ҰڭY-�-e�yF�x�ݖ�;��.X�ǜ9= -6j���Ӯ�c������A�#|��R�������p�n\�C|���������`��S̰ӒV5��3�*f����������1Ac�)5��l��+rn�L�~[6�����m?N3����g̚���fjq�>��马DJF����]��^�E�r������lf�>�t�����g��|�vP�� N7���T8��X�!����E����VC�6�'�������p�F�"�d�͆"W�Ь!��a�i�K<1�Dh��#��bƱ��'��A1��ٿ������w�}�~���as0u"����^{��O���:Aa�֥�#j�h��rV�w5{�Q� L��N�,�[��5&�ڡ�b8ҧdV#�G3����=*�n�Pd3w ���K�^X��C*]`�����T"�A�v�+�t4�ܙb�B����ڢ�=δ�5�t�ϊ�����I�T/TkcF|���P�0f�� P���Q/�zn��#�oShfsQ7mz:�=@"�N�� ���:�^�7�,��!|8��{v3(s�� �t���T�>R��k�*Vx�B:\���a|�Իb���=l��������� ���3�:�v:hg�O��͗���d�b’��[w�z��}d�1�S��h/ �5�3m��H� -B�I{ E�H�+��v�N<��X?i`�!�a1�(��8��'�iu��)M÷�^��������?O���!#��U��A�l6<l+�NY��w����yY�B>��� 7�?dGĔ����������ȇ.��0�F1_#m�sz}3K'̏�}8zK� ?�޼�h�[� -���W0�B�� �o �B:|�8o� -��T��[���k�Y��I��D�7O}��Q���%��@&Ҡ������+�1O��4{�b+�A[v\.�5���܌�3`eU��i�w�'S��~�ݭ��_���X���KL�dU2Ũ߸��� f�R�;���(���;�7��S�X��;������/A̧��;�d���]��b�%���� �G�n~�壟�De�;�׿�ߍܥ?�"�r�+��������5u�wj�ב�EK����3���i��09܍�����;V&Ә9�N� ���w�k�Ig }�A��xF���t�#*��V��"� H -�b̒[����@�c�uʆ?�g� X}������I1�\�*�֮���]_���~{�����g�L �&�T�)�^M"X���ܵ��b ���9 e[>��J������ĂԳ�5a4��/��M�Y����!q4�g/r�wv���A��@�d���7�G�y�� /�ܞ�C�2��n��K�k ���n��uȑ@���i�X�r.��Q],Y�R��\�Zx���"E��.%�& ������jT�.Iz48=x���|�[9px�~�y��\*w�g����s%�s6Bq�60BG��4�<�֍��)�.w�U����Û W�����҈$���qs������9-g�(7�TW�:�y�x���i2���D��t)��Aq��rZe����}Du�����N7��u9滼+��DP#qƐ����.�U�.k%/���zff,qf��g�%�5cP��C�P7�(�{[�k[p���3l�/R�[)�X?�Š���� K�E����9�C=Z��A�5��D�b�u���J�|��g�J�?�5+�gg��M �e�ܡ$g��;d(�@���1$����\�����TMz`������˗p�s+j'��z�͑J3/�”�q���s -�@ -�D�j*�"�2(^<�{v�29s �kY�^.��[�8����^����bg\'Ta�J!Ȩ*��u����[6����Q����D����1-�­��BB6���� $<)� &W�u� -�X�VG�?`��i �C �'���I��%�K��l����b,vO~���0��LO{���GXɘ����כ˷����r �5�/���7�����1<e�,������ZE=�B��>��G��N{$�c�3�Ӟ}h�� !'c��d\��I�S�#t�sE����P���ӜG�#|��� ���b ��n�#~�t�x�$��̴w�(t�I>b$*�biN,US�H5��'�G�Y�nO��P)���t ��ޝ�i�dT;9Swؒ�3����\��A'��3۾�`Q�#}hA):��N�-d���27��� ��f9��Gcʜ5��DH�פ�6=��4bѩuвG����8Ҕ ��=���8|�c���~��c�]��\��'g*��K ��č���T0OҜi�F=�*^�� -��� ��G��U*����U�WȊ`ԫF�#�<5��"�����Do���� ���lNdbX��x�Y�d�����`X�U)d�gȠ���ݴ��4�dF��գ�'�cXI�]���H���Lce�����k�y�U��+"F��t��粵�s'���n�8�.��T�)���G�G�$�S��`~��V.ni�P�H� �E�ˊh�_�Snڳ2itd�L$��M[���Wp�@����Υb��B\F��(�e�"��L��>6x�����}u���~+���*���mg��Z. Y~����E�'��9x^�-��f�H� ���T��n���%cW�߅��i��L����p0\�X��n������\�� ��T=���R¡��V��8��`;��.j�g�ɮ�V����<��}�<�:[6y��)4̘�KI �OC���6E�x �d���R�]��X��o;d����&b�I,I�\��J� ����kAnrh*Zc��p�\����b x��ӹ�н�\�󀊏C� E�|/��9�Z�NY�C�I�9Ɯ�f�F ��Ȱ��%Py��ҜS�i0��r�!���>�,�W̢�,=k.�:=vKظ �cEg�."�,r���k�-="��գ3s�is� ��b��j]d�G\����#���!ʹ�'���˻��QކX|N��`.���޽�.E�k, ϰ��߸�-�A��i�e=�C��-� �JG_f�'�b`��z��vr�J �}X7�_�{<5 -�T)S���'B���y0�/z���0�=��D�>�r��5��i�w{ �Is�P��a��t�8���� x=g�Sߧ}�\-ZSu*����r�,c��+����y�yo��Y��{��e����k,G� -9]�k� �p�U���:�_BocK {S����޿��|~\}Mc���׌�H��V�7���(��xd6��Z<��C"�wbz�; -�N͊�|����4tA�;T�\NƸ�)(_0M����k'>�ĺ��$h?�<����\�P.��z朔 �Iz'4 ߹tv _�~#�Z�¯�a�ŀG��c�g�z�[�m������3������1.�"+�!5�ϊ�A��T]1��T�ʡT]���G;`�ޯ~O^Wj��)i܇{M%�wB�u~^���_�����J����[��F���-���{?*]�L� ��67#��N��y���_�WLc�!�T��=�=y�p�S�?��ˣ�ΛG�W��Ǿ� ��:__4n�}B���4�14���G F�uRf�1��1)����/�*h �Ԏ}CP����4/R����y�k!�/c ���؁�hM{Qk���� ƾ g�� ƶ#�m8��vH��;Ӿ� H; H����i�w�� "r������k -m�;�*�Ӿ�֜��뾪��"$��>��OnC�5.��n��e~I<'� h�jwP��v�<��kD� Sc�{��<����c�І��J4��0 R+�� 5�6�ݻ��z]�b��k�װz�Ѷ��É�mm#z��?��>%E_.� -~A�a�b3 b� ��Y%1���o��`����1�yR��F�.��a>�U,�����؈3<����gY&x��S�֩���7B�M�H'�!fC,#�_d��WA (R4�tO �m�E`�r���+��r�H �~BW�4y9��:L���������� �5 �������e�S���-�VA�f����N�M<����&S���):P -�o���6�0,�p&� X�C� d�U���{���x=�g$ҧ ]܎]�p?[�L���;> ��5�Q�n'� 6=�3�e��S'��:#ۗ%���k�brm`����6��v�O���U�� �ô'T�Լk�F��N ��ls�` -q���g9��@5T�LP���)C�r�5z�jn�ˀ敠+���s�fA�N�-Zso ��h��v4@a�e��El�,�:1\�Ǧ�=^��c�/���d{�5�UU�t�y�a���e7k_����A��]�ow�ivB��b# ja�u�m[���4��X}Ǒ��hߖ �E�Dh��Y��i���"�gӁ�� ��0���"�N�|6�<��f �-�|�j�� ���ڰ�#˦��:�@�}q�y]j�s6��V4R��\ق(���0w�����u����L�t��%�Gn��_����c����ݔ���0�����4pC(�J��'v`�Lҡ�m�'U���$m��Rh�DZ��x�4�㶵j�ɻB�[�� -�����/�Oo�k��|ȍ�UJv��+��!�R��P��¡d��Vugm�?�АYAvh�r�b `��c�:c��Tޢ�|����:��2 -��B�`#j��n(RI�dW�Е��o�{��Փ~�Mi�Y�a��0I�+��0��t<�ٔ�(;%�����T���T6��A�mT�A��7U�k�����/��������y62�������a4`�O��փ��Y;�ؑr�9J3�_�k=�?'������ļ�7���� ������Q�̈́v���C ̰;�gj�A=� w7ʟ7�A�[_N���q���*��f��>��w�[���,4�LشA~��jr�\4Oy6 \��WKo�0 ��W�/�n�`�:l+�öt����Zeɐ�4����q��f�"�%�=HS��2��@c�V��n��T\�B݄ޯ��o�{p>= �d���l�%�e|�V�,�b��(t�5܃y.e�]��F���b���R��z�b��@��Kt\?���E��]�͙��u� �,�B1�^Q(N�y$5����ZnD� p�fe��"��V熣B��`c�.� :���y~ˠˍ��萑�r�,��y�BS��Z��;mjt���ސ��6��:I���vL�Θ��;\��ZR$�e���N� �ѐ��AQE���*~෎�������O�$�E��c�G�n�]T� �gyHK*�:0c��`ٕ6)���c�ʭ����G;��2&7$<0h;�)O��+���ՠ�/Rn�te����3!�ٷՈ}��TMo�0 ��W:��v;=�ðKw.$�N�Ȓ �N��Kى���\6�&~>>�b��o �"9[�/���jW�]W�������w�62F`g+�I�+ -�~�k�_XLE Z@�S��m�R�U� � �l1z����������lѦxt�ٱ��h�TC�v6��i�C��'���%u�D#MD1�Dz{���P:T�w�PfCV��Ga��ie�ގbV`ԁ|��v�0O���WT�):`ꂅt�\p�>�����20+�pdGSO�~��!`��'��nDp�7�n ��\��������6�>Z���>�p=FH���� -�ƅ� 5�3��l��X����n1�qӀ�8&��� \wq��FF%�6��&�k��Y����fc����J*�W-��g�M�Z��5ÿ�ƴʻw��� �j�S��a.,��F�[ ��cx�'�{��a�� zih6�s�=|�� [��Z]s�8}�и3�݇����촐N>�.S��NY�m��J�����6�I1!b��L"[ҹ��������2m��-����# � -�����ף�=���M� j ��Ҵ����F[��8��X�m�<M�hyiŅzױ��U�#�N�ĔA�K�z��!��'�� Hk�~��.BX5�QLIc��٘�~��Sl�&3c�P�@(v�6���jb�1v���t:�i�6��7����^ {Eē����u��aOO*�~}Ѿ�����@w۾_�[%��*��v�;��׿*�\��5@>��񥁻���W�� -�V]���o�j���: �=�ݻl�R\W��k�U�n>h�ԃZ��)C?����3����̶�� -��GZ��f�%�VYd!�yl���&q2ߍ�3j���'� -��h O�� ]�� 5ة�ۙ��V���Ƀ ر -3� ͠��1uAw0���q�.�]��xΞ#Ļ��'���e �E%<�$��ʐj��� �t�U����)i0�deZ��7 -���F�`���:C���U���RCځ���X�!���F�|�"�n�m]����!��Ej���c�t���MI�FB,Q �����Ӯ�5 x tS^�,�L��&�ϭ8@U� �Q���T����� ;Ǩ^��S��w�<�U���sBX$�K�+�|����穧Jĵ������I)x.��v�tUf"�:;�Ù۔ �|s�I�9�%�c%&�#!&Cd�>r���aۤ�MF�|��ӗ�<��Zig���9����"H��J�I�^_���o�о�bBL<�"MG�7h�9�,\����uN�J}��t~��v��΂Ԕ����|� `TJ�Of����"�U3�A2c0��TfΆpChn�޽]����l��e��D͆���:\���,Z����R�0-y�h��h�e �c��ƆL��*�_���5�B��̆+HX��~{�%�q55�h�%�Lp����������mHSϑ5x�����|�|�l$�Dt� �]ms���_�Qg:�Lz�]�6M�˨�S#K7�������($���H��-�z!H���1e��}��t�ӗ���� -~�x��"<哋�pp����_�� +��e�.S�g?���ӛ�t�/o8�gJ� 4�3v�hs�.1#<²�8����\4��� �B�<\i�u��U;�f-0��'�����H�#&���>X`��j�P�Ʉ��Y��Y����n����MF��׽��CF���A��]�js��Iys��� �j���_�P�tO��H�;#�7τ���--�����������}����\�/eHA��ה�x�vw����^�U?��u[k�^\�u��ioS��~�u��U?�)��X��=�^<��w��������]�w���z�R��A�l�^�տ��v���:�yqZ9���������u����ˋs?/{���I{�dITޜu���XV��P�S�)n��x��a�ϓ,X���m��n-8*�y~�l�􀟫�v�b���"�9��i��d?} lƜ�[�Cޠ��t�� �'4��/۽mv:��u��?�%�j��#Z0�2�]�t�~mvڭ�t=QBGQ�����1�L���5 ��UtDի��l>bԔ�(�,}c�Q��3MG��g�F3�F_+:�$��X�Ab�4� ��8��E����H����"`k��m=jFI=�<��H��(�y�%�\�H��BŽ���$I���%�e��F������Ϝj�k�i��t*G 1�l�9 ���Eҭ��.ѾE�1P�mP�S`&��T!'�?��˻^�C9���-c�v�6%h��J4��ք��@���Q��*� I��Y���EJf᜙�e<��G/ �hB�����u�ᆐ��<����c�2f��Q#��5�#R���6�"n�I �KJ��f�1,Aޖr�X�Y�%a�oe�9\����ZTm�L�3V^����D�U��V�)��-�x��5Wo_�Q|6C �5�B�y��j�H�췶8@#b|���V:�%�[{Lkjt�LtS^���5� @�S�G�tl��{%��XJ�2eWdrU�唆�X+���[�p[ -� -�U��A��[x���y�xwR�1З�\Š��m$@K�����힓���^�b�� >���!�'�K��cE��j�uz�U�p�B^��;܍�hD �y�����ࢯ����?-�k���-��qqz������j�(�ZT� � &�%�9ڈg�Ģ�.�w9�+��3�����m�N�)l����a�����_�z���'`@q��w��(�u�@�{��Z�vĬ�W��mM��rH�8A|�`ڰ,���\�ĖF(@���SU�\-�{��Ȩ��9]���@�����u��ؓ�h4�I�0=}l�>�_\�-q�:���|s2��yG����v��8g�w(6�ӵ�,�G�����\crB_7ڽ���ʹ�B$KU��)�!����94��ҦyÜ���Y)W㪡��J�xAK�X7Y�=j-�kZ�d�!�r宪�����Qs�5����]�����$��.N���'[byԭ0��<�r&��u���E}s����QY kE�ԝX��� sg�K�F�~N�%���仲7��HY���d�������Ͼ"��!r�S�S>�/�?6�"���Õ,���ZO�#zՎH�}���l=���|}IKҪ�PDH !�2���U\zU�xTΣ&]e�1�R,78^>�`8��f� ̹��w�r��&mU��7�r��셀x-�ڵ��嫪Ҹ�ߧqߖ 8��� ��\��������|�d�[��m��6P��UEN$춂{'� ̄zm� �������vI>d?�Q�@WHQ��=2ᴤ-�}�Z�)��\��Uy�ER�g~z��sD��M�� �}U��[��I[�ҾE������n�{_�r8ʳ������y��ŵ<���K���][s�ƒ~?�����ʩr�Pvg7��!0"Fą�E��(��"�$����o�zP�lO��>$eI$����ח����_�ng���r5]�����t�����t���Q������w��m2�V�|x�z{t�^��׋��󻛻�ŧ��|�b�����g��Gb��Y7��7�n��]6��?�?��G���d1_�����c� +�yz��H�Q�c6��߿~�R}�q��\��?���eo��>�Z/A��'_����bՅ+Aǁ���5�O�������|����}���_�f�� ����JϘ�� -�����W?���ͯ��z���_?�1㋔�c� x,,�A���g�E�,���Yd �:�}j���� ���-R+��0p+�cJpO�ar�B���6�� -�(?a��b�!�үd�Ѝ�� ��X# L5��_����� �V���9�|�/�KAe�8��E���(l�|���l��C� ��b> � ,���i9�a���Tfk#�>�?QY+�tr6��T&js���T�)�B1�12J���-��:��T����kV�[�$��X���@�����@���-2��Lqf?�\��6�� �q"QQfulA��;ZM�$�Ώ�s�j8ٞ�y�mt:W��1!�B0k��&��m�k��NE�MW*�XTk�M�W���hԈ0��u%:�.w� ������"H�/�8eC/��)�gC4�_�!cby�f�:�D':ī��%�� d����C�,��K��A>�!����̷5G�̩f���V`$���"1�����D �:��n�Aj�ry�ݐ���� ) � ]�ڎ5�0�0t)����\æ���.D1/6�����mQ��ja PÕ0�t4�)x�:�a��(�4�1���(e� ��?��:��O�i�3�v��[�[�M15{램��o*oA/~jL:�\��b��;��^A�o�6�S;���3��K0y 'O�$R �f��tGL���&��%�_�3�m���qSM]��c>�'�J��"E�Y/Okw�>aʶ-C��J�`nPo��n֩G����/�)aRMQ'��_�������脭�{��n�hLZ%��`�'��e��{�$�Np�$$ɮ��:��#��}����˙:6ұ�*����2~K�}�6���h5#�J�h ���� �ڄUM�� B��昉]Jk����y͖�l�n&��{h�;uiP�w�|��˚����JS���ۥס(�T3ġ,L��Y����%F�xx4�x��a�s �@Ǣ��̋�rH�õ;��O K���c>.T#���.�\q�" ,���}�O�C�k�:�����OXn�|K�>W�A�M���FOV�R,]�iVf��Nѱ� �G���A�g�6L��eZ�N�� e�:x���uS��>�'�ت��T�n����ҡ�dY�>)�z��A�ĸ����j�C����',���y&�* K��柬��\>�Z��(�si�si��Φ]���H}�R!,劅l���c�����՝a��&��J��W�/ �8�R[D�%"��r/tg����R��4�#xa��tt�1�T�Վ����Iف'��%a8�<��@X��P�E�*���5^$��:���-v��)WD�a�Ԁy wB:,��]݈�LR*� ]���N��u:Ȅ�I��"�m�ǀ�<%3� T��'�<�8�� z�Zx� k�:'#�V덄7�P�O���=��#�ꏂDɽbl� ����4g���H�U���ga���@�Ѥ!��3M�"�i'��J��0��&�֎�k�FLhۑ��F|x3�[G�0����OX\�� �s?���� -wꔉ���K�ax1�k3��� a��@��G�t\�:a�ܭc�2�A�5"',R�P�]B�?�N�S�S�.���3 �I���e��f��c����p�����'���0.z2�q����0�r��Y�|LX��E;b#�(/!R�I����p���]^�@i鍰��r��ãBX+�����1a����ew���t���@M7��9��v��f�z.g1Ԯ\1<�S�'#SK�j�ax����q�֪� ����� -�5�!�����V"F��btR�p� -���;�M%��~��䝢�A��F�y�Q=nQŹy���zM� :�b�n� {���8���5����3fv3�6�!;U�Fz��T�JD�:��&�����0��O��a>�P �6�ab�bȏ�9m���p:����tɘl� S&�Ҍ�ec�Ԕ��'d�d��C�6$J��^��F����4;�;�C�*����MÎ�D+*X�7bh֘��x�N�|s�1:��T���9�sGvx$1K�~U��0��������,>PhYhP�5�<�9�|����y�i��y��As[�9�7��Eu���Wp�F/h��<����O 8�D�(�i����Cv�� pc��f�b~Gc���Cc��#���ZL�rTpg<�uH���8l�� �醍r-Lwf����1݁Q��>�鮋�AY�m%�9���0�QQ�^��Y�9��;L�Q��? -7L�MTp萍y�A�M k�rb� -��uxxΘ�np8-e�p.�0�5���cX���ؑE�h��<�\$�!6ݮ���Ѱ������G�Rc5�y�9؏3�qP���'A^: ��a�Ht-N�=�BY ��L������t�@�.�� /�����G߬2�ph � v�n�a:��V��a@v�LA�/h�$Zq�j�J"�&S`�cnx�+�3�}��^|�����z=��} ��%w���dTx�1^}�w�$fȇE'��K^wH�gU�v4�0ߜ� �c� Te36�3������j�'�Ƹ6��z��Xv���!���.'�%皋cؖ��<���\�9L�j�b���"r��syg��8�n���B~�6��< ɳ�.OO�����A2h�g��`��"b��L ��;<����^���tnAԶ҆ע�� �1� -1�c*w@���� ��?���%c���<� �ʴ'��+ӎ@��W��%�@��;��SL�f&�?�s�� "y� -�YuԢ���j�A@פ�e'�s��\���Z�B�R�<ʤ��O<���!*�e��_���ɖ��s����w��b�/7c�.�{ ���%{����u��瓼�]�����|_�̲������S~U@�����ѷ���3��])�*�Lo�Y�JJR�:9�Y�RX�̲w���W}�1��c���Z��eʺ�^m�a\'����s9�׋% :^9XK_���������Nnz�U�~�_��s��d>U�џ���Y���m���ǯ��D������j�� *�z���)Pb��B�G���W0:!��x̪�9�]w)�!}��`��P������b:ў�^�?��J��7].�ݚ�[%Ï@��}�U�^�^vW���0���m���>}��x�4JF�g��.+��r��Ո����^k�)V�\}飏QCcMP�z���]-��<_>�ξ8,�FNj��m��Fl�d�b��G��l:��T>J�����A�;����u�]a�{޻� q�>L?���2��~zTx�����V�z�~��_pC�w��ԗ��o�Ұ�����o �� ���j�a�w8���_���2���w���e��+�K����Z���魌�i���P�q:���W0Ş���?������D�����*&Qs2�&7�zwR� u4xA}U�9���u��;C��`5"�/��2���-���,�w"6+I:;^Ix^˕#��tY����1q��������4��g���i�{� ��ED}?S���~:����*[+�{?�Y���K����V4��ד�d����>E��++[����| �| *ZI����b�(�r��XD���w^�0>�����(,�;X���ʸl�«Q)������N�e������Τ|�Tz�O� ���χ|=�^M��t�_ `��w��)!z��ߔ��ٶm�;��T���]�ic����b��o���p�vakq{�^Ò���0O6R�7����+�OFVj�+�{v5� 1zw��]�\~t�io�0�b����e�v��y�}Ҳx�،����ճ���>z$��ݎ$�*����0>4��i�U�/���d�*�2t�������u�*�ڻ�V�D��_B��o�z�����n��Lo�̯߬����O�U����i�'��Tp4T|U*^l��6��fS�������y/�Y,��z����3qf�Ͻ,7�j���<����X7��OX>�J�GD\��7VO��c�r����ئ�l�^hyz�0�{���&��c��-q�N����k�˨�f�Z����6�������(�P��&W�]c���<��a�K���ܣ���V�|�;��GX����+�8��I��f�?�ߥ�y������-V������l���̇�ිiS#�, ����r{k֓Jj����sT�<`5�L�zC�&[�0��[�Rc��_��ժ8�0��� ��z9ۛ� w2���7�����O�/ݍ����ü��9�|qS}u���f��n�L���অ"7����&wq�g�Jk�;>y�}E������2����(i4��+�\�T���n ������q�Զ�q�J:�>f{M�=)��!����;@B�N --�j:uh���Hz��Ԟ�7+b���I��.;T�ԧ�g����ќ�F�e��ٽ�r����3�no<��ݖ�Oʮrp��6/k�ҍ`� �%>� �(���-�V%[�)����֑�_�)Y�J�o�Q�sZK�f�~h�����z�ޟ�dOI�Ֆ��=�&��\�k9޻���>�R� ��U_̞���֔��+�IQ�PO���o������#����H���4�Jb�|�h�������$ټ�>���ɋp�7[M�7�u��m%U��z����A�J��~��U"m�� +�B��ky�z��_G|n�O�֚�+<��3XA0�v��ݏ�.�[Z�?��k�"(s�+�Lke��Ib���D�\�Q���Ϯd �N� [��J�0�$z�2�U����� ��w��Z]S�0}�Wd����������,��� �2���V���Z� j�;�0����snz�i�r�0d�p%���ѱG@2q9�z�������T����,M�Z��}��8RG�o4�H,DիK+.U׊K{����-hj����#01eP�f�g��R�X��5���.f��B.^�ULIc���5�v+�Ȅ�1O��qV�XO(v?� ��Nc�����l;��_�[���ׁݵoj��"�qA��F�Y3ؓ�B�o���^@W�~)����"AW�~-��y�|�+\�\��=@�p�����:֊N�*,�Be�EW����Mm�EW�N� -��e��J�,*�K�Eg7���A}���U4G�mPc3���3VSf�^� -��WZβ����iՋ�=��M㒊tF^��yl��Fq�� ,A/h�J$v!��!DD�#�o�g4����$J ��$v�����p4ر����2��1z�p��.�nvm�$��� {�x�l2g�>�'C�k�rH]���I���<^Κ/;�$q#diB�+��J�\X��{S }������S2w��^'Xz�?�`��5��J����'C�ŧ^��r+>��R�;��O�6j5ָ�p3�:�Ġϵ���+!�_���sXs,U�o�.�y���2�>_�ʁ�J;�PzU�i����f�v�����X�ʭ�n����yQ/����־��߮/�:�T[���C9Dr|9�m�����6���je�i��� �\�������GO)��.�^֔E�ڊ,ѩ|��y��4N_������FI&\�M�ɧ`�U{_T�l���v�O�`p���O�0���+,O�d�Ӵ5 �H��� �q�7ǎl�k����|��4%iS���1w��}�. �f�@S� W2��O�a�$U)��1�y{~���ӣ��������S��I>�S5;��FFS�F!b|!��J,;W:#�2��$39�,���c�N�P%��j�.7���k�~#��xD�aK,����p��<��$�Y�%��Ԥ�&B�?ţ[`�j�[ ���r�c�X�Jݏ��ҫ�7�)͙�,+� �%!�`��DKd�9�e�x��҆hՈAN4��WD����8J暍�W�z��p�w����1��V2��Z��_�?%W���)� �|��8�3�nT��ʔ���>Dd��^DK�@�X���>"_|�'�kR�&ECRI��E,:��Ev�|FD�AhJ�r�@���wN��-'��VѶ�y�P_�8���4�Pl+R�ᖏ�R����2o�^�'���Uc[0tw�wV�YU���hb��οz����Ex%098巾�6)*6��̰|�Z�J�qa�XN�e�*C�.p��i����C��lf "nm_T��j#�˲B�AR��"u��>zۖ��1��6���F��U�k���V�:G�-�j!�u5<�b�����" � 9�<}~QS�:a�_���|��hQ�޾�}�2�l�Ѯ��]�t�C(�u*�̼��L-qpsɤ��Z+���I�R�-�:��5ц�C9� -�g"=N���޻� Z�y����OX�2�wo�m#r�-����@e��ꥍW� ��9�~ì�4�C����u�\U��8��� �*�7+���ۿh�!AYTg�y���r����@K+�|.oU~A�k��"�\ם�?1;��VX�e�L������S -� �oO�͖QK�0���)B�]�M��q (�瑦�5�&%�vݷ7M[7uέ ��k.w��]�h8�2IJ0Vhћ�5%��N�ZE�}>���d:�\2k�sV6�)b~��i��j�k8%�Bʈ>)���]XJ���CD��dDH�Jl�s����x� �L���[�J,2<�K&�3KaE,��MDs�8B�ӹ ,Y!q�\�0��L�Xj�ј��r#��u�h0��t�H'"��T2Cp�;��+iw������7�ką���}@B�4K!/BS��u -~\3K�Vg=_�PA����<����e\�tc�bW'��e=�^�RԫB(��M�$�Xud�j�{1^ �Bv ���5�%�@y�c�������_�@���������d� 0w��B��a�� ���i�0X�����0��rp�����׽���]�/w� �]]s�8}���}K��jwB; 8��:`ʦ/��hcKT$�_ـ1C X���N� -�s���kI����R�.�o_�1 �=�#<,���������/��0fes����RI<���>y|�!/1��`e��<�L�Pp���!dc���9�*��a��G.�X�દL���#�q���W����zV�bSLě7q墤O�~@����x1�ೱ(�0�C��R�K)]�������*�� �z��~I �ɀ�Z��^�v��_e"��� �Zŵ��� �=a���� 💁{vc���e��R�}�'�d@~ivR!CJ�� #��+d9��ntR�譔xն��Fm*%T�mDZ7��D�͆%�{۶�����v�K�XQ��u�)5ˉW�n�iV+� -WJ���q�F�ڜnH Ww��X ��T�jG��Z �/��s�ʷ�)��6����4\c-%�-F�u� �'Q�jwkT���5U��v�v�`�F�]��X-xT����i�aJ��m�N#<Ŋ���V��Q(f��ϘӸ�5\+=����9ِ�a��U�Ҷd��n�q\�n����veQ��:�h��J@b���^q�Z�>���@���w���o��esMC��#/y�"��(@|V6Ǔ~��T� X��j��Gј#� ����J!���#h$�1c��2�T��(Q�|B���)A�v���Pa�K��Ab쨡�TaIF�R��,�ºyդ�/������7���0A�D��yP�ټR�-� Q����ʇ��R�#��P��>C�饸A�/��A@�R��ҧF#�@0��QT���ȯ!��@���!�Bl�DS � `�a�c��}B�g�3�(%��M���})�,�����VL����Eb\Q7:yJ�٬ߗM�I� ��IlKm $��'V�~b�L������'� u���D�|�v���w�Y*�}8 T�8������q/8�,���þU���#������^��Y�zq*��3�2Q|�/�f�ɂ�s!z�~�x�M6��3��a�s"~����E�1B*��TC�EŪH4���瓇�'��vm����P*C��%aMLW������R?1�p�S#�ZBW��!����A�A�r�;EV�s��# -'�V�V�,�!��'GqS����ەY`J�,�Ƈs堆 -�k`�t!�wE5���G�����O�� #W(Ϣ��*��W�~3�1 �N�%�Tf.cne����98��:�N�~�?�0�gtW��3��p�@��=��n udԐ�-����o�ǎ��b�Y��+�ZF�"��9qfw�/�/_r9�������a�����0/�@ V����Z��/K,�ݫC���Cb(i�UtvЉ�sh㠆 -�[p%��.����O�u�>2l���x���B��Ҷ��)�p��<�N�ze�¾����X� �} ����N�F ]Q֥3t�����y,�KE�p �4)5#�>���R�{^j��A�� �F��K�����Y;�aF*���VA��r�uh[��<����Ґ��El1i�H�m^Җ�����h�ړD4%Ap�*,⒵&팛�F�:ɬ�IZ�~�>d�7[�R�4��[�ͧI���n�,$��NV[���oV�� ���8"z��B�'�F�]�1��od;��t zс��zF/�2���U���.��\H�s /����;�� -���T߂��5�� �e��2�7��r�Æ Z�'�b�3�7�j5^�/c��� ����HkP�L��ƹ���t���(IJc��.$N�s�v�U�vV|ܔ�B�Ŕ�N�0 ��<��;+�j�h. M]j-M�����'M� ��i��o����7C��GȚBܯ��Q�"�Y�������M�� �لB4��1ˢZ��UvX�,x%��.�ְ�2z�� 0����BL�a}�S�4�h8,�c��s�Y�ȍ�f�꼏�d�KŅ��( �dRG�S��4��+5���H�8�ޱ{eU���Mr|�Ayr� �m]�� � ��'�)a*�ȝ7�S�4`��̳�3�l������c�N �X�\�dp���^m�P[���� p�qni���w�qO��6�[���h�VК|��^j�:�S�jT/�����L�N ���s�y��u�/�UMO1��+&�˂'cX�&�Dc���'��0��6�� -�� j�5x���{�f^���Y��BȚT��-h��ɌS�4��8�6:J� �MHńٝ&I��n�r;k�$x%`Tj���a}'=���l�# N*LŪ� -�mt�p 4ָ I;��%^YX��׽����S�v88�PI]��֒<�s�2m�tUƃJz๋2�c�y��#����v����Í^����W��?,X Ol^ˍ�/<���*d�KũIP@�ɤ6eE�2���T�2Ӵ�׈]���'�1��l��Ȱ�˧& �j� -<�n�$�O�Xz�қz[$�f���= Uz����姠��z���P��=� �k���`6PY�{_����A|t�` d��� ��m� ��Z�o�0~�_a�}��i��Si� �� -��9���8:;���;')����$��V;�}����Φ�O�3��F��z �#�L:������t��-7����t�����V�Z�4���A�ePx,Δ�x�Īa��� D]~ճ��j�X�g`R.�����b�-g��$֔�n��;`��a�K��bX��Z�|��X�/��gL��wǹp�&׬i�� �������hñ�. �j&�]i��0��q/�ExD��X+�06�Lo���ݛ -gS�[�30��S��� i��r\��t(�d����F?@�%Ӫ<����b ���ݻ���X�H�+��锻���%�j/o����>l�p�w����M�LGp��8=S��ק�L�U�F_�����+�V����X?5F/Z=�U��"�eV,�<��k��o窦s�g[�8v�ܧ�N����}D��Vh�*�i�Q��vO��>c��1��v1�C���2�7_��i����:��:������"� ~wa��u���z,�o+�nL��,H��Yq\4�G���9 *��뚤A�r�����A@�R�ݕd.u����p��f��'�,�/|��n�� u��YQs�8~�и3���BsO7-�sm���t����F��"K�$C�_+�Ƅ��yò��������}��`�p%{����1I�r֋n&�#B�/_t��� �,M/�[���t��<��TݝKf;F�M3!z�PZ1� ��$��� 3)&���� �/�%������O~�w���z��^��0�* En�# ,�F6Oa��͘�:�����n���d��������G��0;W��HT&�cL���D��jLl/�ba?��r�z\p�c.��{Q�ł;عĢ���_� �<��Ո�$�6��Evΐ̒�i���@\�AVzFr4�*Ka[xKD��93�`>*�C��L� -�z\�9�`�`7���0�R��*"�M�G�j,,XW��n'�[��fز��L�熸�#�$[�h�;�t4��,d� M0j� �+c�z/w�0����Lc�et�j�����x����j�"<�h��܁���św�`�O 8:�N k�jh�%�%;kXX�>��x9 -˯1�ꬑ�*����W�^��;FFϪb���C w��nF>��� '�v�˒ җL/��r�����r��9*ǝ� |�(��K�悚�<�O��ݬ[p��[y��S���If8��y�r�mT�_]���>�Jr�ԍ�4 )]XO�"�PQۤ� �'5$3V��ʍ��j��l����`͊܌}�����X)�Mp� -���Vj��$�)PM����[���R� ��P�����J���� p.�l~�L��`l.�^e�f1�?n���� ��/`nk��8�� ��� ����ۇ��k�����X�0�_>�Jӈ��5�%!�7��+���n?ӼSZ+�W�����"�<�q -�-lXQ/.GA,(́H@k_�Y��[؅jᣲ�y���gmÒ/��L����l�a� -i���I�aBkQ+Zj/Gc4��*#_�0$;i1`Ҕ��*��苍&���am+oБ�W>.y����g��� uf���+ �t,��Yh:���`��;��pLZ��� -�P��ӝ�yh9�0�����N������uz�*�F jᛛl �(� T[}`�:�_b���xʅ�{[S��2w �d�Y�tQv=��7�b#�,{m�U[:���y6Ѽ{@�u����B�Xn��c�{����&��듃ĩ�q&�������p�z�㓎���>�[��h��U��5��j���ɫ���]5hv h��K �~=���Wzo?�^:t��3͖�n�0 ��y -A���m�C�� ���2 �%C���+����i��#-��?R$/�B�*k�e��30�f����^}���r1����;LxNT~�"o�˼�l37@:�ٶ�:�?К�FBI>,gF������x鱘1CC`2�N�w1���^@���}|;� I�� � -�ެ�TiE����H�,��2؊J����q4X������ogg�NL���P��90t�AD��R-�C�"9_���g;�G�k,߈;3瀔�ki�J߅���*`�0�p/�I��z��ie����yi;)�Yϸ�5��H}�����=�*ժ]�?��*�W��8�ʙ [�j?���A�l��X���E�a�C���(Ts|lW �š����3��$ޝ�7�^�T�s�0��;��8 �q/j�!{���R�K&��u���p�Q��]<ݘ]O�0���V�G�>�ijA -괁D����qN�Ďl�k��w�$�B3�B�hW`'9缏�c'��ӄ�@i.E�;:|�L�\L������'���XB�&x��}/6&���8:��,��C�׊y$ʓ��}�ÅGMAg�A�+��Bz<�HA]^:U�.N��0�|��B*L�̗�x4{dF��G.4�J$��*�81���E��pa` -���W�����*��lë�d|=�?ˌ���}�sp���~�9G����c��J����+�M��2,��3� �h�(3}/�������p�5x�͢�ey�pkt.hR�QWd�)�l.�d���$����\��pœd -46���'�Z�W�0��+q�z�bgT�@�*L�GI��7��l+~y�r�ff� �XBq�l�u�^�;U�4� ��x!@�^��^���&�j�)�Fu�t����Y����^��r�����텭��n���U����݅=�������_A�B*��V�i��VTAr !�y)�z��m��-�������]��� ��x��{;~�0�Q�� �\�4�1V��()� P�Q�*�2�Vq�,�\B�-}]3� #�a�ӹ�r��������w?�5� �B��$����S��Z>|l \��8Э�.�K�]<�w���6߭m��?d���Y%��; ��K��0���)���;���R -�I=Y'b�B������G^$YvCȣ�K���_3#��nf4� D�l�o{o9+]��8㿆�o>pv׿I�12�lc�'��c����_�Y�&1H�ʩ��6c���ׇ�ջ�=��(~(���A���o�BB�[�E��oKa�`������<ڥ�qa'��<� ʠ<� ��oDWD���H�U&G���n鲩���K�P_�Dt�{j5:nt:�R�M�;�w��E]4�C��f̸&�*M*�%���|�]O���(m��h�����Uˀ�؍b�]?� -l��{*�r���ʎ癌�ߞ�x �^]n�A���?'j����ݠ-� �8�2����\?E@��1>9c�Z�z�/`O��b�P���ӥP0k �7�p�*~6�B��K`��&��!��ݖMO�0 ����(wV>.��_B�`8Mi�mi�޴��q[�&>�@h0.m���맖��E��a�����iކ -���wË�#����:C�D��S�x�e��4Va���%��x�\���f�]��F�Ԡ��+Ec��]𾮿�T _�+��^;4��-����z���⣘��8˅G� 6��%�#a�yY�8+6 �[)��ҽ -�t�>ta�� #�wS6Ա���%O���H�U���L�W咁���.�-P��24z^�ʳu�<���r�Fn9ɶ~ ՈM���1�:�G�]�W�e�'��՛&�y��H�����e�[�q >m���q�!��2��_��ƹ,�`� �ב��� �}��g�_J� ݕ�n�0��}��ϰ)pA(٪�-BpBˁS5�'SǶ��e��8�n���U���2��?��N��h�[XSLƻF�� - ��6nو���je1%��.5�c�*G���73G\���+k�e�Kk���w��9ధPQ#�ඪ�@M&�Ӎ�"r]}LUeKO�y�݁Z���$e∊ѢM9�٨�pm���VҚ�8���Q<�k����b -�%M�l(߇��Xk@h����#(d��9��H��xyV�ݭ���>u�Q�)n�ډʗ�WC�"���en���')PN��I������f.�K�W�x߆~+N%@#�ۭ�ʼn� -2�W�4�2����u������U�o��3�O�1���Q>9�G���^>�'rq��'�X��E���P��r?�a|�&�����Bp��|�PW�r��͖Kk�0�����]��R� mH{hBB����=D/�q���J��h �bܛF�5�7B����x��ػF}ؼW@N��]ߨ_w_�}Tp�=���� v�Q�H�TU9ڄ!t�i�H����hL���ao������v�y�؇�EaJ -ZJ55j^�a{P[��w��=���B -�M���Q;4)w$Aa} -9qˆeߨ0�� %;4�e����5^?�a頤#ɕ�m�r� �K A;#��Maa�3R�i�8 هL�N����y����^��92iMS�~tRD����q)�+�S\���e��2��8,*�m0���4�<߰�9����5m��r��.|�G��nMS}ĘR���^wn�7o��ɩ -�Z�<�3E 8���@�\ -�[��6z!]�.�f�%�'�T.#N��e���13�?����g���RMK1��+����z�m�""���N��4 �٥��f�۪P����e���v[-R��b<��V�J�M!>���;��U�� "نB���>�"��Wn7��Y %`�S�ץ�]��Oϫ�6(��-/��X�+�|�\�j�l��J�9���Y&��kiB�X�VG��Km4� ���Η���L��*�S_=�iϱ Pn��;rC6����OT \KO���H�0V�.�dg�{<�͎�)-����b����g}'�yy�m�q��!�-dz���p�v&~"��?Ŀ�#��b^Fr+B�1�K��LM��p����{�ޣ�t…(�3���r'�,7�8�O�����g�q� ŕKO�0 ���V� .�C��7T�P����������I[6�[O�c����Q�Ѱ���B��O�UnI�)�Kyr!�j1˕�1B*��-��̲�}�n3��Y J@�i]��R6������`�Ra!��k~1� r���ZW� ��A*.D-uD�%�ڇk�T�&��w�����Ի��yj�t��N�ư���yN֠��Ìg�.��"� � �E@� �����8۷ ��[����]6�>ͳ��{��yRہ�H�7������ЦhN�����,�������-��jR�lg* ��t��o��2L)�M�'H��x�N&:��/*�8��`X9�S̳�^��V�n�0��+<�V{+ -��(�K�4�5��P���ߗ�d7 ������f޼7�d~�m-l(��P���Ӿ2n]�/�W/�(�\^��"3�͎ Ոto�,�]�U~�p$���-�5v��8l��'j엖yK��j^ �&w��'V�%K@-���2)`A1z?�6��F�Bu}i͘�8��c����^�O�q�X�I,�o���1�;��I�4 �&���@X� 9�o=�%�����A�.V�!�[̳�X�C�[(����p�(�!PM!*B��)�/��I������T�����f�(z�M�9LEN���IOt�CpP��_in���'KȲJ��X��qLql�ޣսE�d$ka��ov{ۧ�|�5 =dz>�@���{���bh t�F��<�2�5 -K(S��=�ˉ8s$e�wB9o6�0E�tHxe �$�Bb!���-�OG�5��*#��fpv�b��)=%|S�QEDo�a*/Б��|���qԳ|��m�2SP�j:�f��*4]�N�`��;��pLZ]��5N�� -j�w����dc�2��H}�����+�:U�-��7��Y�G����Vu�?��e�AS��pl -k�"U0�9l�Y`���{lg �š�����? �/'���{.�Zq����~�M�˴�ݒ�F�qU��)�q�7��Eq�y��VKo�0 ��W�/�n�`�� -lذǹ�e:�"K�$;ɿ��W�f^(��II����D���ZB�֑V{�y����]�~����=���U*$w�f�2Vyo>$I�6�2�>n��Y��l���g���z��k�@���3�wl�R����wÞ�MX��҇>w&�I����3���| x���+�t��y�ILfK�r��O3M.)� ��#��B�\j����@',jBצø���9�d:h�7V�?��k�����$5�B!�@��r��>I�OK��}8�z���X���{���.K�~���_�&t�^,Mf��!n��1pc���� = Ѐ�b�Z[�z�&=�KZ;��J͕�>?� =�� !=��7�(®M����S͘�˹��Hp��������Tx\��}�-vmE��n��?���,H�k0���󺳝AA%a3�>La�Wd��ks�0G�;jQ����e�L��3gcf�R-����V�o�&0�����T+���~���*���"�:F=(p@�$ ��η�' �hϿu~���Y���̒/�?�>=�u�ct�����e�<� -���`r��K)Yf�깛�������FK>��MM��i�7�a��ul����ĺz�����24 �Y@�򔵬@5a �DX��OH�LNYaz�ߘV]{�GAnBh�D���Z�4`�[��6=��;�/Ha9h��r&�����m�1�Ţ�7�>����%�'<^�W���^�h��ׁY"�vu_9^����:p\�y���W��:�\���4/O��\�����Q��-�O��жV��@�e�bu�˚{o����� -� o;�~ o���Lx�1���o�6��B�y�e���C> ����{����B���z��n��L���Q�}|.�ʞU�nZ�խ{([�@�2�61׽bp�� ��n�B{�Q�=�U�턛��͖MO�0 ����(wV�!�!�N !1�(M��"M����O��0��VMZ�n�}��j|���`����g���P���\�_qv3?���1�]�s��:��5+�23�LE�J�V�R 0k�����|\δ(��BB�;��������c�~��C� (R���O��8s$e�WB9o��0E��Mxi �$�B:!���͵�G�՟�*#�:38;i1`���́��� "��K���������G>�N�8�X�c�V��)(� 5J�Bu�6o��mr��� �F8&-�6���P���; -s�vRn��q �8��H}�����/�*U�. ���5� -�A�;��z�V���x�P���D8��5�*�r6�$0 -l>���&&�0�E=���ß��+��� -��Vl�2ܺ箉�`��^_���8)�Tf��8z�k���8 -���w�V�j1}�W z���J�:���BiM��u�Վm5ZI�b쿯��4�7�RJ_F;�3s�h��զ��&�V9���b@��J�e���ޟ�ap5;�r��AtV.g+���,��ĬL�7E>s�3X)s�) �59��r���� `ʵr���﯋��ۯ�|f�FZ_x=��\6;�J�Rj�ؚqa���D_�mL�e�gv�:�z�ƪɯt��ƚ�R1�2F@�s�@�A����̵p�R�m�L(�H� -���J�"ǭ0>� \צ��nPa�a��KI�5$z��^K>X�UX� U�m�6���vu�/��rkiA6 -L��g �1^#�o�f-�O��(��쿋���ت��_�4�ˀE -yM��F�3h�W�Gj�A!�.�!����h-n�G�#�8@(�+��������`��{m�Z�kc ��>HhH��lj�B !_22{zB(�ױ�I3=�k���A�aE�9*)�Iy���%����26sw+���=��k����O����2DF.m�֥�H}�����O�// d`Щ�~��.�˭�Mqsn��!q�'5� -q ��3ɯW�;S�.�{Pܖ���6��ߊ�i8������i �F��Bo��0pހ�쬵��'��U������X��C�4�,�Y��ʘ��J�uDZg���k뎉�Rw,�D=ց:H��׼��a�:?��N�����C5��@py,�tA*ۗC3.de`�p)c���t�px�ٱ� ���|*Aw;�Q=��?|r)$ -�~��ݻF��|^Z�T���Ĵ� �~�g1����s�x���(��F.)�ur��W���2�ޗ",���Ѓ r'���@B� �rF�w9o�"[��A���X� [)��E3 ���Z����{����m[^�{a'l��$g �ʜ6h�Р8HOPr�I�m៊��{�\��Xr�U_��z�� ��HE��Y*R��1�Ũ8`�4,� �Za�Y'a�Ϟ��}}$j��p@���W^4��R��lާ�ߓF���ȁ<�ٵ{�����>7t�y����9_狰ːi�BWY����;N�YS� q����6�U���Ntp�e����� D=3`�=vr�>��bI�B:��}�/x~^�6�� ��8,� �M�����-�i�WXY� �I�NJG$qc�2���;�(�(�E�0��R+W)�:�D�pԀ�<�d�*s�V\n��CZ���(�s��U�tyzA��A �_�6�[ql��<� qT����*寱�}����6�Dѡ?��#Y�{�gh8϶��$�>��s�I��uPp u_�'{D��d���!#�˫`ޮ��U\��<�ĭ� -*z����H�۬(�4���JRTb��v-��V�X�7�6L� ��uWG��8�xyq��dv�����t:y�{�M� -�Ԛ.t��قF\(@�:Eɧ�\��|� -�,��"FmW�(��8�Ste�u6��~�{���u�"�w���kc_�d�_����kP�W˕�<ň�IZ�\�ڑ��Z<����A`®�M`�� ��9.�\�H����\�^U3Qb�+� �v0XIn���e7�鞝 T�{I{���f��ku�c׹���H!���9��)��3��>� }����.}�����M[�~u����R���Y������j����uT���Oq~������t�n4����\�o�8߿B�; ���^E�E�vP̴3h�w�(6S�X^IN���H�v�L�&43yL�ȢH�H���^&�MA����?z�vā -E�t��c���u�o����ppl�;����� ~�%Q��^ ����Ʃ��{�<� 찘O�$<���P��������*� ��:���/��ΘKf,�"(>N�#!���w�t$�B�\�#�� U0�*x�� 0��E�Y�&��q��[`��d���:'ţl�cf� �v�D��vv�L�,�Y����������)�yZ+r�̌�ƠQP0詠P'���IIf� $�0��ði�]�!�1&,�Cd��f���6RJ�,6��$���ݤ�f�)�$�=&T������yg"^ �8=�$Bs�{ ������\�٫�'�ŻQ/�q��13�ћT�s?հb���#��U��2�0��� ��:. -�{zZ,��_��@C��q�{PH/�Q�DZ�.ܐ;_~�[��#��@��R�Ę�Uʸ�T=M��l��P$p$��u��6�����?x���cp��ax�塏�o w�Ҩ/_��wW�n���8���������߻̨ 05v+Em�Fq��[��s���fBJ�)O#\n�# �A��n�H �i!��ҊD��� K���^���N�q�O����v�@l�H��ШJ��D��*�5Qz�� -SZ3� �Sd�2Lӆe�dg 5�D7?�c�� �V���HD�I�D�R�1n4\k>'ó\�d���O���8&�r��.��M�O��#0K��r�\�o�(�L^Ed�џ���y��� zIR� ˈ�c�ޥ<�):��2o��n�w���)���X��:l���CQc���� I�4�ӥo���D�@+� ��C(��UC���c9�؎�v���0:F��(<���4��&O�a܌'ynQ�2���U���K�7o��k�}�(�V��&��� ����RC4L0�l�79a�ЈJ�_�W6a��t4V�����r�5�a�n��op�=�:���BH+m;L�H\i V��o�`� |�ϋL�$�G$Q�40����"�<@Y�l���gNvjS�=�ZRnQ\#�M�,�yv3���J���MC[��A��a�} 6��V��D�Ucx�L�A����^a�R�v8�R:�0x� -�ŷ8�-<��4���C3�Κ�Ko�"v�Zr%ƉވW�=A�i�#����"^+W�q=�Kȗ�]���3nX�a*Tj�<�2�?�D'�cw��S���R��ѷcɟ\����KT?�����>��޸cFwZ��)�� ����3WX���c� j=և��Ȁ��>��,?�,�7=�<�`#�ҧ�A��"xF����1ٺ�����إ�\�h8�͗�hW|?Ʉ�$l��������'�o5k��Ϧ�Kg�3?���g"���7��`��f?hD*ф��d/7�;A�ǹ��g -Ʈ�++{J�8t�G#�t���I6ҹ�P�0D5f�HP��~9���gz��7�M� ;�帧���$*6�8 ����3z�?Ԫ���h�$��@I�:.v�u{��,��o�u'���8�G��9�!��zݚ�͔6��ª眹�� ��;�i���' UZ�.֒�^ge�L�G�[+է"$�����\;���]7��?2F��CוèAR:g�&u���� -<���6�g�W�5(J>�S�K�cY�R|st�>����Y A�r�l��ƭ��5�yx�)1�VS�R���� ^,�E��5$�9u�,MA¦ ��cԪ�w��I=qY��"=r�F-iJ�f\S��^q�Q��������I�_�%�VrU����o=���U���Nv�ڣ+^������E��W(��pG Y��ޏ}O�w����Om�=�+`~��5�.��� V zwY�V�V��=�i�8�:;ߛ[�Aj��i���|~*�Yim/J�I)���p�G��(ҕ� xb�U^������6�٥[T�XUʢ�[jv����n�RT ���`;L��n?52���|�m��tk�3wK���,]+׵ݝI��(G߿8�U��yi�4�t٧�s�T$����cP�u9pNJ96��2ڗޠ#�zX�j��c9�� �1Spp -5]J�Au�V�u�v9X`��N8&-�V�z�S�o����<�����g�����?��g� -�J�Oj�Ǜl�"� TY}f�Z�'1�X��Tn"�ܚ�H�@�^�I`�|^� �ᙘ���� _N�?���T��b?��{�8 �q7����0���rHeΙ���{m�oQ����;ݗ�r�0��}���@ 7���C�r�� p���:�%��N�G���4�6dZ{�[�Qv��~y���Z���Fg���=��R/2������ .��P�{���XEd?%IX�le ��i��;��l���7��/�ڸ��c�y��r���#���0ړk7컽���<|�2Vr呁'NRl�K�e.��6c�ɕ�4I��fGW$�)�ȕwq�� '- Lm�W��8h\��vD��� 0N�&,��V!���*S�M����b�q������K]ɾ��r �B��W�O�'����]0���?t��V����}�S�Ɏe} �>�9�4���dcۇ��}wC�c������� Ǎ;��{ _�KGRr�������������ig`l��sT��֏Bu���9�L1���#X��p�P:SC��Zt��>o��8��qL4j���)�bOE�S� ���(�I���U%E��ͭ+�!���hn63u6�H�Ƈf�v��8��8�U�+�mC0�p6��!�Znko'����c[~s`6p!��^�����,�j����l��|�!��N��toᅱ��4� �XKo�8��W tj��@�Xv -�A��M� �[�P�@��x��Cʊ�YDZ�VNE� �o����ui�}��N��Fo2@+��v9;���� ޝ��H#B>l�4+����W�����Y�q�2�Em�4��٥���C%$N�f���5�U��13-�G���8��H�Sk �EI�I� D� I�l!L�  ��fy��εѴ�f�wĂ�ؐ�´�� 6��̍��2~� ���)��*��5�\i*@�$rz泹p�H��@��/�;gڽɸkj2n���� ����'�:7Z>�G$��66 �a�z<7�]@u�^8ύ��� ��{�A(�#=G��*�W��CB����+�(T�N���+�i��R¯@��s�UWsԘ��3+i��Y>�Ps�@9�4q�v�V�o�ZD�jJ��n�L@b -[�.��}5blpow�� -���1Bs㎀D��Hi��&N��1��0��NJQma)�f�}a��I)�gG���W~α��골 ��n���qڴ8@����/���r��6��v�����h 2k�����E�>��N��ڔA�,��v�ɽVG���9n�,^u���=A|>Uta�+oɈn�K���l;���!jr1��r�t"�5O����F��`�ϳ�SsI��X��ur�%�� <��Lfܮ�&ǘ�:u�"�J��hJ�'��:)ׁr)L�����H��/w�A�^�� �g��d5&�O���w���� ��1�x\��-W����$�xx>�� -;�<����'���h��gNf7��Bσƭ�@?w_��}���sG��kB1ɯ�g��B3�}p~(0� ��ߕy!B��2�E�V�)����J�gK����V����� �9c�g`�Qws�'j��D���r�V�P?� �� �D@j��U��;p^u0.(I,�#�!�2�� Cl��\pu�~�j�%����a�?��sr��B�~/��8�v�/��Mo�0 ������!N��c�!�Ƈ�Y�km�dHr���1��]�\��L�5�W$%�o_K[�N����ja2�_b��_n�N.�Bq���Ŭ��E �����@��� �Z��-�~1�Rϸ� 4/�U\`����`r0F;ϵ�����Ô������FE�̈T� ia�-�]E�=�:Y�V7��O���3�s���o�V���@�GK��Y�|헳Y-�W��(���-g�s�f�<�,�c1�����mGJ�f@�1˹rHT�d*����Uu��t�y��D�9#F�u漥�����������퓪�1���›���&�1�)=q���!�����#�:ae��~aʪaL-r�8h� �Anw��]&���grN1�(d4����+����te(���*�Ы��jM��+Hws�t�cX;9��?�:�.t����U�L��d�k�<���ܴu��n,V�=�$&_Hw����֜;O� �_3j~��?�Y�o�6~�_A�ah��n�a�8E�8k�4 �dE1 M�#���T��w$eEv�;����XE~w��ww����T�{І+ي�o#���˻Vt{s���y�� j ��Ҵ�����īF�d�zhH�M�YD��蓒w�Dp�6"��`2ʠ����G�9dJK�-����tϯ.#rOE��s�pY�X_(�-\�{��e��X���f��Y]z��?�������M��U��<�ﹲ�T$��pY ���#�_޽��±������u���q�˔��)գ�~���g�N�}:̵�h q=��퓫�ӧ�1�k�ڜS˜�P��lw�ORF��2 �B�]�'��8"��r֊T@8��>܎ZQ��g% �\�#�"�'Ws �`��Ea$L�(`��$ܐL� ��!����*rӹm> C �B �s/{�n\d 1��O 쑳�n�(����@�����;x �F�� M��=\���b���Q��4GmG47��A�T1��Z�`©9�C��Ƹ�cD5iv����h3��V�����W��S��������0�ʒv3����*�KȐ���2$7;���'�>b� Ą�x����\��Pt%��7���ltΔ�*oc���9"��q� A�4�c]�#c��o�B2w��.���Gh����yW6Ecs�=�ъ��qk�ϥh4j1���A�7a�s>����g��m:gV���z彈�>��T�]��d�%�k�� J��zȨRo���_�Wt����ϨF�}� ��tq���%/�Q�B�^��Fs� ��a�Bk2`|�18�8�\r2�U����{n�0��3��9��`s-���+ώ -^��L�Z� �O1*�����23�9�a�Ȭ�c�p�G�VD�����}�m���� --�&*w`B�]j�;�L� �;iR�*<:RF61p�鈰��tu��2��ҵ $�N����/*'&Q�@�@����]c��2v�R:��0�Y�����Y��NRVfn�)����UR;�rΜA�pd�T�� }��"u���r�}1V��u�w����a�Ā-&v��;ϼI�Xƕ�_�Ha��$I��˸���A���l�s�������ߵ\�Z��N�nou�O�`�U�U�MG���ـq ���vSo��w9a�M���w�E+t�d��GE�z,!���d�pX�6q(u#�s��8N �gè�Or-�_����;{�O��܆��kn��bD��%�����S�bUL��k��n�)W��'�{��i�;8_�y� ��OM�>���Ö��Ys �� �ܕ�C����@���ߐ�'�6J�m�_x���b�`� � j�V��s'�^BM�q��-�$�(�\����K�ۯ�ʌ�V��C��{���U�o\���=����� j!Ê*ĸ�i��J$�:��'˶��-�b��{������_�4������͵d՝��*�n�Ζ�m�CȂx���w�[�׊�pK�⬭-O{�u�U��{��iU7���Ȅ��ڍ����_W -̲o���æ����R�n�0 ����F�Vv2�֥�9�e&V���D��ߗ�7�б�HQw�;����SvJ���W��R�¾T��w� -֫Ea��d8�R5��Ik����5��Y�d�Z�K�BaO�QA0��X,�X\߮���9��)��۩��Tr2�K�3> bf��N��eW9�+Ul+��-\0�2ѓMM��d�Ʋo`��E�`��MBØ�@�oi�T}�,��� �M����\}�+����&� Ƌ0;XB��Pu w��x�z#�H�$&�[�l)�ų?��Z`��T&%Ӎ腾�ahQC� �۸�?'�:�����@L�ܙ�Tn|��R�Q��j=����[�o۶�}�7��@�?<�M2�I[��.Y7 �`���� Ej$e�@��wGJ��؉��2 ��I�x��|x�/���7�d0Vhu����X'B�O�/?��߈}��q,�� +{e�����iPdE�o -ܑ5q��Rʓ�G���LK �É#�x��1�D�����o;����+W�ysu�������pY⛗~�Lt<�:���b� s��墣j�Qs��Q=�:a��nI{�Oq9�#0��d1�ɉ�$J���2�#!���DE9�"� ���ҝ*4.���6,,Q�MY�s4�;�~΀=����u� (��%����R��d��5 �Z�%e=j�o7)t���smU���Y0Dp�43P� �qg\��ގ��ѕF!�-���_� �"TF8HXjt�� 97ထ��<,l��[��� 8.$~�\0..�S��hsm��c0[܉t�z�{.�p$��S.�'ڷĴ�O���t���9~����):�~e�}��2�T �����1>��x#U8� K�������s���@����M�k�M .���N�F\;�FO�$���\ƥ���V8 Բ�@{�q3>�5.e����@�5C`/�BOq��|Ɛ0��S�R��@a�@��>�f�y�s5cV�\V�q{m�Ѯ��;.i1� G����� -���D��Y���7������h17ߎ�(e"��Ϲ���fR@6��X� "��芣@�U��¼�)�������wP��4V�����⒈�L�ڼ4Y�0��2&��-2�ό.��������V����li�8W�=�#���B��麆8H�,G�me������9@'�r�� �\�@�7��ѥ)c�kY���]װ�@�@炂i |&Z���������N��j~�+5" no�X��{,H���"kn���hcj�Ɗ�`f�ay~/�,>{ ih���5ۄ�vAH̀�6C���p����B%pӵ-�������ܦ -I��` ⮰�#�$�}�٧L�m&G+���i2cp#����N�4߀��ax��f�a.���e�_��-�w00f�A%U�J����>� ���@U8�q���-��i̅2H�� lGZ�֔���}�G�Q��QϽ���w�;c^�/�5S>��.ƬAׯ����i8�nkz����$� L%����{��w�P�Gg�u.ę�%�x�ZJ��Ձ���m$�і�����a��y܁��E�E���|eK��:�W|e���$e��P7;���/� -�|Uj�#��U#���Y��q�~ݿ���0EB$]S�'tn�g��>���圊+�� �!f�m�[�o�"������F%?�D���Q�R$�SS�-uU�p���(���~#����(XЯ���:o����1��0R$�s�p�'��1t%�.�]����+������d�\�A(4Νt��[������,C���o�iX�]�T����i�ZK�����#ꥆr� ���rT���k?z���o뗵IÕ�����^P�{�S������-��U�vʤ��ƫ� %��~�t��;0;�:�t}p�������4-�ׇ��v����eϰ�X4�\�W1��]�1]�US�:����Ї�[A�'@-0�t܀�ܵ��@�f^�r~#�2�U��ZP�瀽Q�Dk�Dj4:��4%ƻ�g�m#\�e�ǠSn����]�-�XA]���Wg'���G<�^ܔl�h�*��� �?��8 n�)�w 6��@��鞌$�k�����9��"#�cO�-��+����u]����m�y���y�V]�3,����� �zK���ߛ8P�n\�`΅�;��gnxuwnX���b��ᒣ��8iV�3�i_�� -��^hv�4�&>w���>�|�nL�ȳ�}~[������UXw��1��s/�����h�*�M�:�����\/��:f���P��W�k�Q�a��V'L�����߄K�/~����V��Y�Շt`���<>�?���WMo�0 ��W���f� C�b��a� �v.d���ڒ!�i�_?J���M�d � -D�����嬮`�>������� �jg��O��?���epyq2֕ -d� ��dnޏF2:o�ƸٹE�3(ڪ�dߜ�wW���5W��g`U��Q'Y7X���`LuSa����ƴ�e��zד�Q:�F.������~�?�<�W�'Y�� ��^ �(��x>ɚ6�(#��aG<]�7N���0N`О2A��Iw|�� ���s�� -��Gn�Ed�Y�f:�[�7� �w�G��������K�E��c�^����B��s9��L���B`�� ���-�M�G� -,>��� �r��8ud�&N�Y��Ν�s�/�d��2W���Ɇ�n��J7��S񒠕�j>�)�ϋK�$\{Wt���~E2�B�egX8[�{q$��`���_x��O�B�O-Hٗ��,dU=��G��M�p��}�/o������ Ϡ2/��C*":/u!���J/�b(�T�#�0G��B��S�DB�b�����ȝ�v%��F����ΡY�,X#8� -�" (�$��0�^�O�pG*ɗ�}�Ҋ�]˃�žNkt̴&NHR�i�W�8 E� �42bp�$����Hc�Ӓ�n�����^��L�(9,��uM���ݽ!ڌ��#ك�ڧ���3�?����K8�c˽]��K����v�Bc{A��k���au��K���ރ�F��#$,H��SGw� ��"g�n_�Wh������ eO!UAl����a~�t2��5u�}���0C�~��v�J����G[)�D������f[O�MW�OZd�lq���>�o��f�е����@�Y,�9���թ4�>Ŕ�,����%����|�ŧ�)���$9O���6�J&�eͮ�Vs�T׿�s�}�[ko������M 8R�@������i�q�(������搳$G���=��F��؎YX  ��<�u�%��Z�9��9+����Li+efg�����?�wZj��`�ϊyͳ�O�f�T�jd(��+ 1m�>+�Z3�/Z�+��5�F�tV������ q��FSM&��#ޘ@n��o�}�n穩���sx-��R� -� �<+�R{�KbpES��pn0��{��l9Ѷ�L�q�/�jp����pV���0'�[Kn%�ܶ�s,��o� ��B�G1�r&<����r.��v�#Yy���KO�5�Ĝ��T�tt"&+�%=��>��ub"�˶�T���QT�e$^H#&$,4�TU�K�b��g�������-o�u�+�W�B:V �bb�&i����x���q����TM� {3� -"�!���Z%�|D�J9���o���G�#?���THNw-�7�ʋ�B=�|)[�tG���t��)�)IǗ�!'y� ,l�^Ap\[y@�T���D�X�n�-D}���K�8��C���UI )9��c��@�: nhW!$���I�an��]YU��2���!�$i����By5QZ��YѴ�8�)#�u���ڟWЯF�c �iqI��B�D#UO&�,Σ��4+�+�BL4e�0f�eB�Pb%Z� �{e[qi��r.?=rЏ�:�����:�A�[��s#� ��`L�']C�w4%�$C�oi �i��O!�ׄv�+��ט�ΊZ]Q���8��3`} /;���=tY�aX�e�N ��<��x�)���r��<���V[V/�c�;��C �����_�"C9�P�ӡ��Y�*G-�ƨo��:R���7!0[C"��Kh����#��(RF -¶��ӧ��C��� � C�l��p[XU�0l�P���cBR�V�y�J��2��H�p'�΁m�}�HW~7ӟ��t�SKۚ�[/z�KA$��I��3a����@�_2�)��ށ��9��/���g� y���4�����U� -n)s*/A�bA�\���7��(�5L�V�d ������։�f�ͬk�ă*��ߜb�Ɵ0E��ƌq"P� ���� P���L�3V�P���s�S6�qݘ��ȇyT�]�A�Uw�ukۼ�2�?S��>� p -�P�+*�@� -�8���g5�E ʂ;l|{�پq�j�T���2��Xp�M�0��)@�����1 r����/ *���� 1�ı�ts�̻O�g.�-�u����7T*���ېde� �L�����|�T ��ÂL�v�Ԅz�w"^?���� �UW7~$�����ZM{��i�œX%#&�� �k�|_���kH�'�f -3 -������� �sX��"A�89D�D�]��d�b���C�������1�l��Ÿ#1zО�w���۲�4��(��M�a%�m�I�sux���Zd;﹁,����:-�.E����w]T �����K��=��j��_�JӺ���b��PI�%J����$O�7;�E�hT���7#CNj��3N�Tq7$���x#м�3�`ح Vu��?��€���xIƷn}�ҝ��nҵC����H�z\Jw���P8�l�t* �#� ���qA������2���� tE��Ϫu�w��@�Q�uSd�Q˕��\��D(�G��]�����?c%��B.��v ��9��M>��)ݸq�e/Z�ݺ���a�f�i�Ի ����U��g��o�r�Ux��@�~y�w7���T�x׳�F]��x<-X��t_����s$��r�ߓ���~�Bju��<�K���VB� ��������%��u�1�0���t=6t�I����D��DrΖ�A~>����hv۾,^�dl���6��G���uwD軦�~� -��r�'�kRq�.Ŕ�$��\�7x <��$�K�s�#�����o�v @K%;�r�f����S�vo}�� -�0D���e�6ziڃ��[����m!݄l"�|� -�a�.��dq��P����4����еUc� Ef�8��OJ���G�k��$�G�V�����)� ���K� �XFxXI�`H���m�P�ģl�����7S�z_h_�Zmo�6��_A����nPI�5Y�]S$-�a Z sq�0�@�tX��cn���8Gj���i����O�޿������oߵ�ߣ��`��I�p���"ɸr������^$e5P2meC�+�/5B��z p���GڰԌb�|(��*�7s`�X�f�DA��,-�΁� 3p�WV#ᘫ���7�+=6�26��h`ϕQ -� [�1�ˬ1_H|y�$�a��6����2>�R�\��<{�����)��Q��ͨ:�Z��g��A�l/���l���B�����]E͚�K���L�;��H*%��� ��!L�i�ξ��p�:a��C`�B���C���/~���/��\���>����6�)2�� �2��nflEٷ&'�hE&��N�|��Q͔Wq���ZG ��a�� �0�B�p�d���4�F�'�i�O�x�=aSS1 �"�R����=Ze��c\)3 o�da�)�H�+@�=eč[˧�Q��%��o] ��ү����[��`j!���j�)*��X\���gb���5�]��?مP����&���V!J�85�\��F�y�} ��=19�o\�}�FC���r�AՄ�/���c�"O�i���̿d�, �x�V$$��׉9yn!�7{'0�*��9n�%�Y��jH|�lA݂.f��܄�T{Qࢀz]7/��j���,���!7�U�M�t��>;6�,�$�h��p���#�����):/��� ��=b+�),>��)ML�������FF�]��/���"K%� ������&7OP$�~� 3�F�-���.���r%wvr������[#�2����t8]�BR�1F�^{�kY0���;ȺC��*��V���C�1nȠ�j�G�ol_�-F�Ŀ!$�)� �5�tK�WG�R��B���Ţץ�S9�cp^o��Im�y7���A%$�&�,�Ugg�s/�w5� -���P'�Ƕ�]���/Xlއ1���z�1�a�U�a��I!Ӣ�t�4��;�0?{� -�&���{�����B�jĖ��m~��z -?[k�g4 �,�R��QV�PahcGH�]��:tS�%KRzhR�n�:��9uW��j{/h3�P�ˑ  V��+�p���u}&][km���1j[2�c���g�l�6�j�������`�.(#��A>0���� ��G�y/�ґ�G6~G7Mݰ K�R�����t'y� ����$�\�������3�0巴߽�o������3�b �k\Q0 =�L*vX��y�F� N8nhS3SE>�״h����6p�[���9<2���F%��Nu�a��&��hF�W6 �k�ט�����Ct|s��cV&bs�Ԉ���<��m�+>��!����]#k� ���)9n�7���2hZ{����xB:�])q`�ۼ���E��pCiq�<� -u� �p͂�b8+�Ai۬6ۏ)��˘�ϖo����O����qе�t����f���(��gQ- �J= %�G��]������^d��2 ���e`(��� �U~,�GfL�=S�mmU�!{^V�����icƅ�� |B��1J��.6$�z ���g����T����IH����h���p6E�Ea���o�� �= -[�B���D<q�2����p�bri�����cש�� t �Z��C��u�2^�K�t�R��.)�o{P�f� Rߙ ��B�K|{�v|}��]C���ir]�9���9��G�s����c����5~�U���U��*biK��~9���]�z~���a�wU |�zoYb;�g��%n��U$d�2qDŕ�Ѧ�H�l�XnL2�XûJ����C7��E�:/�^I��j(�%M�|1(߆�GM���42JL�j�Y��?��ޅ�c��aX�ʀ1���ykIu�|���.�݈6��,D�cD����om��%s�[Z��lI��}P����l�e1����%��� VM6��A�8& ��A��?6\��X���Isb����?:�M&�)��46�V L 蠧 �������۳]���G zױ,��� �V]o�0}ﯰ����ҭR5�A�Ĵ҉��J�"ǹ��Ďl��~�n>ZD�����b���{ӻ�&1ـ6BI�v;甀�*r���|������Y�����Ҹtmm��qp�I�i�� �1�S���إ7J��7��n'�!��Q"Y&e��� .���g��D�Ɛ���Z<8���!��8.W�X&m�:������?)ٰ8���" �cſ�C��0M�.���+�%*�8�c{�M#�h1�ǹ������/�'�w5�EK���ثIߵ@���} �7�e�wن��? <4��{�݋�A�3n��؃������Ÿu��o"�Í0"��;��Y��!�f�mEhL6õH-��*I ��/aD�R�S��`3-+�%��q�2�b�*Q��U��U�; K�h�Pϕ�:d����{RY�����UT��zE��V~Z�����o )j�P�����R�Qq�p�W4�A\ݫ?W��(9-X,~�J#R _UVD�5��3IB�h�?"b���� ����{�,ߜHz]r��[����U�r�pf�����:��3��w/r�6z����f���ŔMo�0 ����N�aqw;�b�톥�d�i�ɔ �Y�_?ZN��]� +����|HI�Ŧu�Ƙ��J���+@2��t_�ۛ�o�)������@�)Uj���X�� -��L�H�(Xv�U��ӽ����柯�.g_�%ƨ�t�)h��)C�R���@i��Eⴋ��l��^Y�xJ;�>�$�ѧ@�� Wj�]�Ě�ͥ%-꒍�#Ql���7��w`2�����m�rj��0������������H��v���6��x\�,�dG�����[�4�h��?��hk�lm��m�BW;k��k&H1DL�2�3x��&�Q�I�c��S��##h���``~��N�>�\������%��_� ��������[8��e�::��n���fy�o��AG��/����(�n�㎢�F\b�wG���G&7�}���U�n�0 ��+��; ��]Q`��z+�2]s�%A��d_?�v\wC���f��{|��_m[ ���+���t�W� -u����[Wˋ�X�H�K�j�û,��"4��ۅC�R4 -���B}����\�_��|��AC���o��S��� �B�R�9��b��Әp��c���>S�c^����ΰ�#�z=�)Х|iÅ��M�X3�鸡D%Y�]�BWZ�@NK���a��)�7?�����X<���S�?���?a�E6���]t�� �n�X�Ѕ�/'�\�h��s�����9�:�����F��X�S���@�Ƭ�@i��Gⴉ=��X|�NY�xJ�>n$��O���K�T�]�Ě�ٚ�%-꒍�#Ql���7Fs��d� ,s����>�� �7����@C��pl.���C$`i�R o��XYܯT�H��G�|����<�0Z��_|ka������Tjg�Pe�$�C�$��9�;�`4A��Mr�`����1��.q� F��iO�ۑ�P��v�4%?�k��s����~v�������V�6�����˻�/nt��*v�QP�� �(��� B���b�����󻛗��7x��UMo�0 ��W��x� ��m1`��z+�2���%A��d�-�NR����7���ޓ��tSXc���B����V�J�e!�~|z�A���$WF��lc!VD�c��j�W�r��E�bP�1�����]�]�;}���kUОXYc�R�aN�Q���� @�ko�FK�O�l Âai`��%|�l��(r�/0�1��/�� i"�G��հ\�Km4m ���Vm%7��8p�ʩ�8��[���a�A�ڧ�wuA��_��E6���`���{];]����a������nO�=�]*�6�S(��sb:7Ç`��������_�\y����$B� ��}�s#۳#���U}�%�y��_]���@+�s�M���1��;�&�:V�u����a`����c\;o-_������0hi��c�����P� ZI%-��ٖG#a�ಝS#"�z�#Wq�~<�Ar&���y�Y���ͺ?_�i������r�jW�I>���?;�x���~�ŔMO�0 ��� -+wV�!�  @�ㆄ��c�ԉwl�z�n��@\�Vԯ;I~4�L0&�P��=H�W�� �p�{��h���SI�T�1s8�2�a*?r��Q0j�+ԕ�gz�x|ws�x���8S@������y@|��@n��F� n<'�8�r߈�r����86�}\~z�} -t)_�p�F�%)�X�5�9�ɖ�Y�*4��-�%-�R��E��t޼��ց�DXf �ס�8Y��7X�g�_��< ����j˳u�<[��5��W ������ξ�#�vI#��I�� k� J�l���X{�hĖ�v�[g0g��O��ޏ\�߬4%���\ ���J�G���V젣�����J��D��,���M��6線�܊��~ŔMO�0 ��� -+wV�!�  @�ㆄ��c�ԉwl�z�n��@\�Vԯ;I~4�L0&�P��=H�W�� �p�{��h���SI�T�1s8�2�a*?r��Q0j�+ԕ�gz�x|ws�xe�g -Hט�6�9>�o�6��m�H���͂��GR��UN��5��ư���OO�O�.�K.�H�$�k��7'6��:˳B��t���E\*`׻�TޔΛ׹�:0�hˌ��:t'K]�@� ���+"7��gA��x[-cy���gkT��F�j��<�a���w�c�ێ `�1�Bu��c�`4A��Mr�+`��2��Nq� �̛����� �����������"{X)�ȶ�Ԋt��_WU)�h����Ea�� {���V�[ѾX��U�n�0 ��+�o�a�S�t�V`ioY�cv�$Ht����؎�l͚���,�z���Mka�1�w�z3{����U��n?�z��r~��SIv�P sx�e���&T~3s�Y�FA�Y[��ޭ������͗��� _W -�n1m�0cjJ�_���-:Nc�Y�k�k��#�cV�c��ΰ�#�r9�)Х|iÅ��M�X3�i��D%Y�m�BWZ� '��^��_X*oJ�ͷa���d"���6�������w�\Ȧs��x�ҵ�)�g�y�Q��Q���� ���F�1J�qR4@K��ѣ�I���6��Wȷ$�d݆�l��ލ� �>음 �ZB�^4�?�c� RW���[`$m��I���!b����F3��D�vr�+`�vw���6xd���Og�>Y��>�ӥ-�����Ms���P���M>u��M}���ڻG�S��W�߄|��*N}���{����VM��0���‰n%]iA+�ć��m��q&���-{RZ~=�8M�]�� -�-�Lޛ7�'�\wV�v��g��U�Ҷ)����/\�/red���6�%�/��W3��ʭg)�A �{c -���ƽ���Z|xw� �X�a�R�a8 -������-�1��% 5��fܧd�r6R��0�.�ӞY�TT�Z�ȸ�$i5-W:�RM�B��4z+V[�܌�C��R9U����v� -�����@�j� ,~�$>�^ -H}�@�e���v�<;$Ƚ ������$^Xn�ܖ7 -(7k �.N{��Ka�'�c���&���&^������Э�F��:����� ��@-��?��d���E.�Y��Cuw~�]��d�U���� Z� �$RB@0���C��@I %r���FX9x��7'�wz�'>���㙤O-g�?��su�$�Ho��ܦҏ�u5u��{{.;o�̿2�.� �z�}�oO��?��;Ŕ�n�0 ��} -B���x���ݰÀv��� -����dJ��,�ӏ���i��&���GR*/7��5�d=U���$�K������B����4N��L�R-s�P"�B��r��Q�ꝫԵ�����j���~��j���0m��>*5���J����x�v��������ı7��b����ZN�p�V�%��X�5{qeIKt��\�Dl���7?FqP`2��΁�]��>�r�Ÿ0R�������X]���H�Rs���6;[Y`d��ˠJ���B��m��$,�|i���k��_�i�q&��L�Y[�/~Υ�^�[�;�;�}������:捝�����K�=������C '�� h)��T_k�0�8��x{�N�-������,�cu�$�s��|����I[�� �7Kw�ߟ;]z�� �1D�l&>L� @�\��*�������W�22F�d3Q�OI§��|�6��Ġ��1�������<�̾}}\�o�$�\�(���� -�'���;�Hu� �h)�3�J?X�Pr�?q8&&�r6Rh���X.�;2�/�(�4��G���p\�sm4m3����m%3� -� -F)�ʍS?�c{�Q�}�j�A�pA�şpjG2���`���ɮ�.�4y�zX��+��1:"�+�סL!�,1p�q�׃�� [3�)Ȅf Wv�q�v̘�e7�orbpw��*W�=Egg�4��g��w>F�:�y�d�A�$P�B��m�y@>��:҈Zop�i�k~-?^H�`9�?c���U�㻿Hc�B8Px�cWrX�sԿ����$7띻���u����z��K���|�p�i�������1�@OM<�ƻ�]���ŔMo�0 ����N��v -;�a�[���d�i�ɔ �Y�__FN��m�����|HI�Ūu�Ę��J}}T�d|c�Rӛog�\�OJ�tJ ɔ*�`�E!�(,B�W#B.R4 -�s���������\���R�sK�( �b -��~J�zC����mp�"q���do�_��EV0��ΰ�[��l�)е|iÕ�k�D-�fkSzѢ.٘;�ƛ�y�77L&��2?0� Y��C �@�0��Q��9�� -��,mWj�m�+�����Mv�� �l�~%O�V;�_G[�dk�,�+��Y���L�6b��de:g�B3MP�d��3���AkWxp=�s�t$�a��;�Ȥ/�W��6����i��^�~�����c�n�������b��1>=����\w�㖢^G�c�w���F&7����ՕMo� ���#�]����7j�V�Ҫ�H�q���fW��`��~�ڕ��r���yg���Moa�1��n�V:�[��������Ηg��2%��.5bE>T�aZ�Y8�*E-��m�'������U4|���`�FC(��S�]TR���.�j��=:JS������ӚոC{�(�|�����c���ԈN�ħ$�d�~�6�(c meM�2N�݊,�2���z�X�9�IG����0j\F�� ���h�~WD����ڛv����� #���(��A� #2��F�0rqOU����c1!c��ɵ���K�|;i���ɽˢ��^1���)[Ǵ�H�}[!(�������GC���ڢ�ܩ���ض�%E{f�E<�i:c)���g&�����������C3.a�pHPRQΎ��6��H����ܜԤ�����ph�jAg�mm�X�=������E�H!R"76l0�0ѠbhS�K�`���AM@GRS��wO�pGq��Q����g�.�G�.:�>d��^7B�F���*Ƽ��rr��Y�?���WQ����eђ�����U ���9)!�xY�9ݸ�?�M4�<��|�r���ŕ�n�0 @�� -B���x� C�bM0 �n��� -���ZeI���׏�� (lـ�fR$�G���걵����w��0y�����q�R�X}y�Q����T[L �إR5��SQ�4 M����)j���R�x�����y4�v����-����Z G� //�� �Zr���U�>Q�,��9V�K{�8v�}���:X�j.�m�8���>�k�P�5�,$b�ue���^AIGX*ڷ!��EB&@p��q��KT���ޑ��x$��7��lZ�4-�H��%n|=Bm���q�&��XûR���F����ynsX !7&��Ⳮ�8I=P> ���[�ՠSNd�6��5<n��#ə%0iN�ҏ>kH�'0�\��Z�A#�Xצ�G �Rg9��@�=8���� C/I��̅5�~�s�_/�}��I+�����O���@2��T����> =�U�Hk����.��O�Z^Υ�q7���Ee�9��������8F��7h��K��t;� -��r���1O�0�����'h`C(i%!u`�J��\ �g�.���� �*����w���}���s��Ȗ|��� -���������Q�r1ˍ�̞̐ Պ��,K�<����ܣd���w�P��wT>o�h��ͪ�Lq]+�C��o�(j�.f�!�{#��v�)�U:i#�j�cT��Ś�l��)Mr�0/M��T���X��&� � ua���A��/8G�O=c}���~D�9��nO�>kyv�+�.X�F��R=a �IL��7�޲���r,T�+g�?Hǥ1Dd��MI��E09��k�$E'��Em`�>hg3�m�UZ� -m���3���]���l�� XlC&*��b8�ՠ���-i�P��d��٩���x�5��x�P�Gx�j�BKZɸ|~.��C#n�܉2����H׍��_�^���ƹ;L�����:Gߟ��;/s4A@ ��2QJP�L�k�i���;BEX$�|J��l �- W���S�q�o�L⠼N&@��E��v�����\�+!�� 9�:�k��W�c .dL���-��9B��� ��a^!'݃����tZ��5˃,�,A��Z�D��JL�;nב@h< `�z\�@�{)$��.��t�r�)bLBP�#n-௖ۥ��� �υ;N"�,bdm =#�|*�ܨ0;v zZB-m+�Y��HE�I�l�)r����v���a�=���?�)��&�R�'V&ނ -\e;��gɑ��������Ҕ��&ƍ�E���} 8�|m=��� -g�= -j�{�G�I�+z�c@��9U�H��}�?x�J�x�oQ�ź��Ӧ�v���u#=�7�>\ -��v�ǒ�h�W���#bvbJ^gg���J'��]���Ǚvm8�I��\�@�N��$0j�X_�=��= ��;�>MǓ`xw�m�_e�Tҏ�C��ﮈ��rXG==���NqTe�GnƧ�}���Y4����͘�n�0��y��Ω�ފ�r�l�(R�(jd�H�yy�);vj;�ݠ֑������|Vi���ʚ4����ise�i�{t��{�Ó���{`c�Ӥ$���}^�����g�����F�4��fl�.����gkb��~~p���SFT�k!q���(M�l� TUk�А_��v�;RW����*C�8Dܣo4%�I��iR�1���*SZ����Y4���/�͘Ko�0 �����]�݆"��qj���n -Y�cm�dHt�~��<��mM��)��#)���ۼ�0E�5i�e�94���L��������]�����&%Q}1�jP�un��4�N&P4Z�ɽ5{����)>��f.�&��xe�J�|� #*���ؿ�5J��͗g#U�+4�;�^��"sm9�>*�2t��}����&xM�� %Ӥ�cS�U���E���J�$��W�$��CcHU�zpqQ -�J�9p E<+C�X��SY�F�Y��������bw)e��} �/�<� Z,E��e3���瑙��]@��}i -n#Ǡ ~��p�j_�_[���c�2GA ~�|k:��y�8Az�o~����t�/E�ɴ -c^��\��^�H��N;�Xhmۣk����o^z��Y�`�>�����Z8N;�o6:b:���a�ud��T�l�F�G���~ٔO"4�]�^����6�D8ƥ�3�i<�*Γ����|݊�@��C�$���8N��ž� �_��T���� ���6���rXE==�}���R۷܌w��ԪW{�h\\�͗Mo�0 �����]�݆"ɰ���Ö� -�L��dɐ�|��Q�Ӥh�4A��ۯ��!i�}[V��vv,� > @�\��l,�L�>}�mr1RF�,�a,J��j8�A]ֹ[,�0x%�h��gg����׼���RaM���Y�*�|�'�� -C-�_؊�b����HW�� --�N���֞�n����*C���;/34A@ IZ�E!M@st����X��;�<��]r,dchb9�h���9�����I���(Wq�(%�����\�+ �� 9�9Xh*���c .eL���%��B0��s��aQ"��,���A��I��H^�<�� -t��ij��t��v t��c`���ǥ�=��BB�Q�B�mwJט<�$dq?Jx1���j,� -�o\]�2�>�8��2���%�l���yWv�Uav�j���J�F��=��4�hUsSdΙ���V5Gö{^7�~�3��MԦ�K �M� -\e;;�gș���]@\�ci -m�B�F�E���}����z�1���h{��$����J�w�/ƀ�q����-�&3:t�J><�7(�b�b�i�x{pv�u-=����D3��l��<�x�v�^wD�NL���̐�C��9��{1~z�kׄ�pLK�23x�u������r�*�p��h/N��4{����� �_e�/���T���w[�~���QOO�>�U��C��7���>w��, ����?͗Mo�0 �����]�݆"N����Ö� -�L��dɐ�|��Q�Ӥh�4A��ۯ��!)�].j3�A;��o���*Wh;�ğ�ݗ�.�g#ed�b2Q5�!_ ��)�b`���+ekL&�����7^�ޏ� � q��kg-��s�kt-=?`e��� -���D�xg���H׍�-�~�VC[ﰾVS��}�?z������$i��R��f:�\M�L4��"��] -,ekhl9�h���9���v�I���(Ws�(傤���\�+!�� 9�:�k��W�c .dL���%��9B��� ��a^!U�,���A��I��H^�<��t ��Ij��d��v t��c`�&�Dž���BBhP�R�Mw*ך"�$dq%?Jx1���j-q�<߸��d�}.�9p e#kK���9�]��F�ٱk��ji[i�r��D*�Lz�e�M�;gV��� ��y�H|����n�.�}b`e�-��U��}�x��)9x �K�/M��Qhb�� D���׀s��֣�Q?�pFۣ�Ƹa~��T��g|1ȜS�����ѷ��񵧭���^� r2>=�3�w΢�0���͗Mo�0 �����]�݆"ΰ���Ö� -�L��dɐ�|��Q��Q�i��Xr������匾-j3�A;��/���*Wh;�ğ�ݧ���/F����6d�"j��C�4US���" �W�֘L<8;u7?o��o -�|���W]�ki�D��5�F*��z'���U�#]7k�z�v�+�5�RS��}�>z������$i��R��f:�\M�L4�*�"��U -,ekhl9�h���9���v�I���(Ws�(�Β�6�� ��Kp%$?`�! SsMU���cw����ɺ|��ba��,��F�0��*f�@^��ᓓN둼fy�%�%�X �(�\����M$�����:�F - �A�K���A�\k������(�ň; ����ݲ��ꪒ�v]�K�$�"F֖г1�]���˭ -�cנ�%�ҶҘ��T���@ˆ�"wάpm��9v��x9���D]������kP��l��b�<91Sr���P�R���ĸ�A܈S�o璷�G�~\ጶ'A�q��|o9�rE��l �9�*�qߢos�㡧��Ã|�� �-�6��{g�7ҳqz���R�h���/=��G������S�2;S���P:���k�?=δkÙpL*��27x�u��g�Q��f+�p���Y �Q�i:�Ûso'��s!H��2|��"��q��D�0:�I9�q������{g�h��ʎ�͖Mk�0���B����e�S����-���,O졲d������e��ҦML >�5�y���6�b X�F'�r��3��d���]������"�J8Ǽ�v /���Q�YUT���4P��lU+���ssw����c/m$T�3,�F(̮m^�����3-Jp����v��A���XV -��w�+f'���<����2�����ȁ3G�P&|%��f�STHۄW�H�,h�2X�Z�\��q4X����2�3����&M�_��(���� "��K��������rG>�N�8�X�c�&83SP�j:�f��,4m�V[`���kᘴ ڬ�+�B}�6�(�C�I��z��a;��w -*z����Ta��P ?�dk�G��X���K�j���B��v�X֬E�`�m���(q��lG ܇51 ���'��$>�� �{*�Z��pힺ"N�e\G=?�9F�qV��!�q�wo ~���(�a��͖Mo�0 �������0�)�v=���e��,36QY2$�I��d�nӏ�� >����(��6�b X�F'�r��3��d���[�}�����"�J8�|�v /���Q�YUT���4P��lU+���ss{����k/o$T���&G�r�E �����������JA �\��Wx��_2��QB����{�N���#A(����A�)*�m�+k$A<� -�D�h��p Q���T�ԅ!�I�����wC _�`����FXF��td}�O^m�~u,�|���|����Y�: M��:`�,0z��I �U=]�고���0m'&�s��a�D��)��Y?C�S��JB-������`�j�lU��C�?4��Ǣ�f-R#Pn�ƘF����v�]X�@�����O����� ��B �۱ ��߮��`��ɜ��8+�T��8��7��Eq����͗Mo�0 �����]�݆"ΰ���C�� -�L��dɐ�|��Q�ӤHӴA������>$E9���� }��f������N3�wr�廀㳑22`� �����ᐟM�n1�H�����5&w�N�կ�+�y��†8��}kI���+k �T�{U'������H׍�-�^�3�Ɔ{l�����:G���;+s4A@ IZe��&���:�F�2�w���H>y�K�[>���wN�Ʃ�c�u2�՜!�ĥ�$� -H��\ ��d����\S5��� kp!c�Ο/Q,�ڀE\^`t � -�bf � >9��k�Y�Y�.��0�B͕��W\�#��x ,����Ё��0RH *]j��B�ZSĘ�,.��W /F|s�]\T2\?�8������%�l���yWv�Qav����Z�V�t=��4�h�pS�Ι� ��F5Gî{��?�)~vu��+/A������Ǒ��������)�9 -M�ă�1u�p.��z�1���h{�� ̷��*W��ƀ�s����5�67:^q�J�<ȷ(��z��z�O��>u ��;�6�֓`xuj��o:u*���P���OW��`9���}F�8*�2�-'���>sz�, ����͗Mo�0 �����]�݆"N����Ú� -�L��dɐ�|��Q�Ӥh�4A��(���>$E'��Em`�>hg3�m�UZ� -m���3���]���l�� XlC&*��b8�ՠ���-i�P��d��٩���x�5��x�P�Gx|x��r֢������14R����([���t���R��[�l���Z9L1j�s�����w2G��U&Ji -��sm4-3�xG���O)��������jտs*7N��I���(Ws�(������r ���L2�`�`���\q ��5��1Y�/�(�m�"n/0��y�T1����:'��#y�� K4K�%�&Q������u$�����:wF - �A�K�6�A�\k������*�ň[ ����k|~pqQ�p�\�s�$�"F֖г1���S��F�ٱk��ji[i�r��D*�Lz�e�M�;gV/��� ��y�H��AN�7Q��>1�2�T�*��Xr3>=�3�w΢�0���͗Mo�0 �����]�݆"ΰ���Ú� -�L��dɐ�|��Q�Ӥk�4A��(���>$E'�o��� }��f������N3�{r�髀o㳑22`� ������W��j -�X�a�J@���;g����Õ�|���BaC��=?xZ ����H��7u�L��w|0�uc�FK�Wm �q��k�0Ũ���������MH�V�(� (`��εѴ�D��",�O>��R��Ɩ����U�Ω�8��[&qP^'�\� �L\:KR�҂�^.�����I��L�5UQ�+�ܰ2&������ X��F�0��*f�@^��ᓓN둼fy�%�%�X �(�\��z��:�����LX� �{ #��РҥV�� T�5E�I��J~��bĭ��Z�5>=���d�~*�9p e#kK��q���r����5�i ���4f9�z"i&=в�ȝ3�\ۍj��]��l$�� �����K}�X�x *p��t_,�GfJ^�R�KSjs�7:��c��5�\��(cԏ+���(�1�A��-'U����2�T%#=�[�mnt��i+��A�E��O��۝��׍�l�^��p)@4��͗KΣ�_���ˎ�ى)y��)��j(�Djv5�V��gڵ�D8&�ws�<�2�ӓ���b}�@�IC�$��4O��͹����S!H?^e��"��a��H�0:�Q9�q�����;g�h�����͖MO�0��� -��m��j�-��i%ʞ*!Ǚ6#;�'i��q���BUj�#�g���!�WM�X ֡� ��}� �4�u�w�~pv5����1�]�s��gykV�ef�������*�~o���^/o-���ߍ��|�壆�I�����δ(��B��۝���/�����RA�\�W�N�/d�yF!GE -���u;�� B��PΛ5:LQ!m^ZCAq��#d�������h��3#Se�sgg'-L�¿%|��ADD��jamK/Б���|���qԱ���Mpf���# �t,� -�Yhڼ������7�1iA�YOW8��,�m�Q����r���k��a�D��)��Y?C�R��B-������`�*�,U��C��j4���"�f#R#Pn�ƘF��[��p��$Fwԟ0��`�t��,��k�v,�/��q,�:��� 0:��rHe����{m��]G�=�X�n�0��+:�v{+�A��E�"�E�,�)�#/�!m�Jl%��>�μ�f#<8�U&輲&M���&�F�\�q��]�����d �����4)��~�O���s;���w2���:Mn��˟�N��Ě8�ýS�O�����Bb���Q���:<���X�!�4� �r�ĵe?ƨ���-�O�;�< R2M -�=&0Q^eJ+��I�,�$�#N��c!���p�AuZ~�2�V�]���NE m��P��J����h��c���J 6/�������Dp�[��&���M�Z�W� �t�_��:gݍ�P������x�sb���tdal -� �r݃k���u���d� ��,%_�1��i�,���>"Y�:$��܋�T�1 -���u��q���C�гAB�q�W�f��B�I�� ��G.r2 -֭s�� ~fDr�RL����uh��&�v�E� -�n0Gm�7�CT��ȟ^� r��A�>��@�2'm�Rk��d�L�hn)ӊ�R�]9�@�k!��b_Aj�`V.�o��`6�"K���&�%�m����S��R�1/�!��(bEZ�7z��a����D��Iw��Ȥ��O�*�%yhzYh���;���|�RHg�� ny��f��#�{�_����"?�� ���6��1�p�ng�|������: �m�?��1fX}E�^��3����A����'L�e?#� Ľ��Կ y�3�M+/���_���F�E���͕MO�0 ���V.�X�P;$(H;��]��[iw���  mG'���q�$�X� -��4:c����)��e�������Š� -Ž� �>c�=O��meK��k��;�`�(��[�g&���N�ړJ2.Ԝ\���w���51мFo��-��*c_���2�Z5CZ �“�26��#O��؄s�e!��U�lS(1�恃\�m�P�4�PF�M�ke@+̚� �f7Mޟ�&�6Ļf��b�����9'^p�{f,�6 �����KM�ȝw�ޢ����f�ȃy��|�#|i�Ӂ������х�m�������z��C#Ҥ�G�͕Mo1���#_8��B��TB� -R -�\��i�്g�&���~J�3����+���w���6�F�^�R@��~ۨϟ�_�Qp�<��if(ŞՉķUU�E� ��'�8����\�.�߆��f�l��)�����ͻ��ڛ��Hdȼߑ^��Q#=�;U5�����'邙�$s�+�-K�(��֎K�E���pgٶ�Y94*����z]x$e7�&`�~��!A��F)7�8�X�����!9�e��/�% �X�i�|ZW�n����^�UNzX}i1>>� 3K�eo�!v[��s����5�YP�z�b��]^St��cBt@��=�t�.fh����k����8�&5���+J�q;"�$��Mg�����@{�,d"��J����ɐ��_L�ł�'Ԍ���'��t��;��}���6�\%v���<�i��:f7X;��{�)�'m��7�/�PB���(�?0���H���d��J�m'� YJ���VMo�0 ��W>��0g� ]�"��!m����@!�t�V���d�gI����� ��k�D����`���*��<���YA^+�DgF������%�}=E�-���(�UB�zXx�D��{C*+�%jﺈ>������- E�D�p�y���(�v^h��}�'���is�j>y��6��� RMZضl8;32UFޅ-̅��8�y˝ (�`�:���8L�秣����4 -����h�N�H�v�}6�r1_}_�/ŭ����A��0Y��:�{���Dʅ��I� ����<��vN�RR�,��:U�Li���ƭD3t�R�y�@��j!��k� &�E�!7 ��-�d����mS:�!i�� �綖���77˳�>�(<����:�[D� e�%���x�e)������K~�.,�h��å���l���)3��P�����ڮ��Am8�e�{['��ߚ/̿����N0�ڹY -�w(g�Ŀ� Y�줌c�Gߨ����#^ʀ7c �tm�9��BI9a�6���B�^U�J���H{�f� ���%5��m�e+�|k_�3�� ��>[i�U6U���%_�+��(�cl�A�A0���c�TW''����͖H�y� <�_��?�UMo�0 ��W:l��� ��[�� h��΀ (�2�h�%O��f؏㏴u������E���|T��&�l�+kB~tp�i3ef!�2>�����^Oj�=#g�C>G,>��y�ٛx'9��Z�|h��N�NQ��Dv�` -�rgF�� !��Y_��p��XO兆 �Ʃ =M.G� 8%��-R �*6�*��ƣ0؂^M����~�����%��*hrͬL��?�#�1\� ŒJ��o/hA��H��<��h��~�s�$:� ��}�|�0~�s�Qԏ�d|�w 9�� �_'ɸ��$Y��x��a�ₘއo�[Ƈ���m��zkZّ(E�� �!� -��@a����P^�J+\��(S�V�DAJFW��J3�ҩi�1i󢂈Kg<���;HdS��j0�n�UDˊ�*�!]m�\Ӽm�+%R���d��=3�3� f����-<Ve��s!1Bh��6��m�����.�0k۽.���9��ޕ?݁� �%��.RՋ� ��rk�:��oPO��f�Cq���`���;��I 8��X�������|����iҶ����l�����j����X�4ލ�E�����'M�tr��X�o�6~�_q�S�����b�S8��K��rQ(P���"�*I9���(��yN����"y�w�݇;J��wI �F(��^��%W����n�珯�ʬL��� E�)�*�c�ݚ�==�8u�}_���Y�/��u�F�Nsr0.�2���XG3'���jg2������= -�$I����^':״�EY%�𴑌�\�u ��Z��V�m���r� A@٢�C| �=H����qH�3@���.�N;�8�G ��Ki��'t3]�klx� A50��-dڃ�����Z,�Y�i�-�_%s [k��o<��94t��}�~^����cm9�T�{je�\Jǥ����a-l�5��M~�r8G����k���*P�2\Y��ꦽ��ꂳ� �����U'l��{aM��p����ޭ��э����׳(��C�y6nj.U��8 -O���; �5̠Rd�l_^�w����š$ -�W�.xk,A�1}� ����C����U�6CW�-a�[��>��Z �ow���o��a���7�/;�fZɼ-�4�#�������s��}n�!�M���O�z�-��F=����dr����Fw-�Ҝ*���gh�K�^�DRe����'<���j������-��wvX��qh� ���EP� -No!����#��S�P���VKO�@��+F>�q���*� -�R�(P�JHh���ֻ��:!?�Y?B��@9ų���}3;�h��T0C���4����49�I}����c�{;#��s��ڥQ�}��$l�UQ��6��ge�Z�4:1zb���,q�KK��huZ��*!qӯ}�Fk�{;#*+�%j�:�>������9Z�~�L�&��I�Q��о�������ŏfB�|��gm��RL �Y�V��cs#3e�Mk��LX���Ü��\[�_$����/�X%���]��ݣd"�DB�4 ����<ɥ9#G)F�FU�) -ʓL����褥�s�A��jJ����v 4�l����X� =+�c���s4Ĺ��������-�����3�>�(<��sX��3C��9�] �|<�����`����h�v��lM@h�X?Cw�=N�R$C5<�hj�Z����8�`��a�B�M� ��1��^�J�O��q_����/p���W��5:%�&"�E��4/�-�S�˗�}�����g���}at���b�6mI���E��}�(oИ����-�)ع���*� �����xC1ƠM'K,��PҘ0�EH�)�O��RXR s�s���jǏ��i�eCbN���'B ���0K+� i��JҸB���B8.�M�vCq�P��Ο����P� ~�ux<5�îܻ�S�N1 ��V&��+-H ]��K.��E�(�U�ߓ�� -�VH,0E����s��n�9Xc"|)��W��`�oJ���xy#�n6)�SD���J�2�[)s4�m4a3�Ȓ�P�Εb|��E��{��,��kK��������Z��TN=�ԙ�]���w�@��F���8�xF�'��H�o�ga����&�m̺���R!�-���I�O�dUc��r�U)�XA�M�\�9םݠ���%���r��v�r֛�'υ�k��T�N�0 ��+�\8�� �v�` q����&nk�&Q���I�1bBBHp���b�g[��}k`�!���8�� @��&[�����B�|6ɕ�1B"�X���_fY������S��ŠT�1�X:[���j(�^=b� �V��T���� -q@�M��qz���N�YFRq!*ib��,��>\S�� ��+ ���$�C�c�\;U����p�Q��ʵ~,q����wy'p8/)��)����$�,�AyvX-϶ƾ�xg5�h����1x����Ei��K���A_~����NH�i@���Jj��4�kp�nK=�^b���g+��D񚄦]��l�����Ֆ1o�0�����v��nE!9h�Ȑ%m�% E�-")ܝb�/Eɮ�hj��3R���}:>1�\5���_�O�� -ЛPY�(����>+��^��if��=�i�dYMں��j�Q2&�`�9W���av�0#k?�"+x��9Q�u��j�ϗ S��]=���:T��e�����XO�,��j��-�l�O�mi��u�ڮt�Wi��2�:L[�M�`J��0� ��D0`BӦ-�P:� R#��)� �!��5腡��l[�� �6��^p���GJ�5y��u� n_4~��L��Q�Õq]�@е�3~$�ZL}�48���i�E �<�xV���(�ʲ���/�������'��IĄ ��Fai��S�a٧�g{�?�������o�@>�k�~��#5Ć�M�'�j"���*��(�#�-�}R���?�u��# -�:گ��7��^M�-�����Kw�]O�6G*�*��96X~�����W����a���O�wYGIH)�K)� -�� ៟8���t��T�R�0 ��:�԰q\�pl,P��(���}�RڿGI�P(�;`�lI�=�9�|�XXcLƻ\\L��Ӿ4n�����٥��l�i�RNv)5Q����i�C�7S�$S����\�y��7�P�S ��4�b��&Y�M�q�K�M)2:���[�M2�����ma�� ���J��f�Q3���;� ���.䃵�@�� Ƒ�C�<����1����%��&�g�\�gT�_J�H�4k��Sv�����&[�oB�q�7�@��(��ʱ""�� ��ޔ���8��J�|��ci̼�F�0�AqTs<���ڟ�����KP�ˡ/�^�{���X����/�PD :�� T���FF�_z��{�|�$�@�1�Ny����>�W�U�*Oi��w���+�RAN�0����4pC(i%��qq�g��eoJ�{'MSA��Vܼ�����eSi������o9#�Pf����{Ζ�Y*��f�3^ه$ �ܖ��fn��$g�Z댿��� -4< -�%gFT୐����i1c,���~�����3��I|*�xr�$t���z�8y8 I��3O��˝�*WZQ�q[�Zu�(#��� 2�\����/��,d+5V�/�:��F� ��P��49�M�pa��*��F��`,ol���`�z �?�[>�7�t�JN�}uޮ{? ^�EO8'�!�d�O�`�Š(��#:�K����%�T]�ȑC�#?́dMp�A1���3� �N~��8~Uw��Ŕ1O�0�����w�Jҁ‚؊��X8vd_���sID�!u<����|�ś�6�G����Y_ @�\�m�����խ�M����!�mHDE��EW�jr׭-R�P��$����m��h% ���6���14Ra"��Yy���#�yX�_�&�wV���� �ӊhlXc����q� KH�V�(� \�uЙ6��D4�*�| �r,dk(�lGs5�9��ގ�(��1(W3<%bW!�� ��ǥ��@}�y�|�n��qtd���C�0Ә����[�B��� �C8T���AP����3�^u���O�I��'�i�T[#_��C�1&�s�m�[�?���̊_�.�O[��>��K� Ŕ�n�0��y�������09����p����. y���V�*�8س3�W�\��&��+�P� @oBm�^����ݣ�u�*��D�/{R�e�ORfU�6�a(<��d4�sJ���M�y��vz� �x�!EmP����R�J}MK�u�"z�*'c���ŗ�I�Q�fk�h��,���:�'%b -�������;�|)�.g��\0og9]&��TL�2(+�mp�s y5t>�\�8�矏rڇ��<�|��K�1���+ ���4�u7�s�pl1!�� LB=�����7As����u�r�aT��W]o�0}߯��<xC�-�6�'@0�<9��j���v�Vڏ��Nڴ�]G�l���_���{� ?�JE�`�4zD� �R�!��^��|󞒏�!W�9���щ�Շ,�֠�T��|�,������FߚOV��+J4+�U�È�������!�����amJ�v��e^[g����[�ȼU.ף�8$�;�wK�U?CR�|�aqS�\'��I�/b��ɐl��E֛j��>�j{Ս[��)��a>- �ޕ�ou��Vu�4^���kI�z/ -W-�q!����'���+#<�ʣU��������3=a��-�.mo�E��u�(gs��y��M�a��8���#�&�F���`o"1�����r�?�p])�z��h�I�qDo��|��0�����T=��0 ��WZ��q���H�v��|�%:VO�\������c��%>���fR�{|O���}�a��\ �z�z���օm��ۼy��v}��S)�P5s�!�$Z�uk�~��DFA�y_�O1l�Grv����IA� �V,�1�T���q�l�M 3�1� WlJ���V�� �fg -Ui�P��%W:�/TK��0ځQ,V��B�gS4�ES�h��$v����O�N�4���ٱ���Đ�:�qj����S�K�҆O?'�+�;���S�R��!ײ<1�ش��f ����rGaT���^�'q�I���q����=a�$Ӆ��?�?�`�0c�M}�"��# �0�ؑ�E��2!�~�7�h?;���~5���+��^�� ?�f�:���N�w$x}h� c��k��A�4��Q����w� �\��������Ad�A!p��ڙZ�.� mt��k����;-/_����û���?�U�9t�$��V.���?,��u�1�0���t=1t�IERQ��#��-�A~>����hv�.�^e��P���� O�~wD�ڪ1n��h�S -'�J����5SR ���Ƌ��q���� �T�<�$a4����m�PN�V��F������T����UMoA ��WX{�Y�!��RA� R�q�fg�݁ٙ�؛t��xf?ѤЂJ�H�g<�{���<�i,l0��n��Y�����q׫���髷����*"gG��fn��X��nK�p�9�A�Y��>{w�?S�^��8� �J�*��>�#�e�M�a���Sb�F��,��1d -c ���� -ktz)oK�Tgy��2������z�}0�`�p�J��|t�w=����.�k�]����gԌ�_�۩�����N���^]�g�B~)�3��W*㔝<�-�t0-��@��M9�T���� -�����r�Hu�M���ح -‡�������VD�8� �}� c���W��,��cG]�|�9��������g��n�y�׆Rw^�\:F�J>�>�8_ ��@����\�Ҏ�h<�Մ�_�2�EC�x䟽��feSgcke�#A��p-� �S�����{لk�W�*v���ʧ!��q�(��Z}�1�췽y�s�97j��'?��z�ց���q�Θ��k��'C�˪32����'�.z�8�`a��XݿE���S�{���R�;�j%Ǥm�59�;�Z�\g(�?z��G)���+E�[2�# �u��ԸS%J��wU�г���f�������HY��RAN�0����4pC(i%@H��8V��m �ײ7%�=�����RQ+n��zfw���� [�m�o�ל����]����ꖳ�l�K#B`�ن�WD�.�b5u�S�N-P��l�S��+|�<� �gV���P���i6a,����0�y��`���,=�h�F���b��8e< I_ -��@��ܕkt���M�]Sݭ��0ێN$�(��A�ٗAz�(Z�$�.i<��~���om\�n�Zm���6w����H4d�c�h���r�a >&;������S~�q{���W���� c���_��b3ē��I@�P �B����X.%�99��S}N"���4Z� �Y��.��h� :��.�T�U����S�N�0��+V�����P���8p�q�g�X8�eoB��l�<$����{wf�t{h t�v6�� h�+��g���vy%`�/Red��b3Q��$a��/�ae�����5&���ݝ��Z�� F/fb�|�6�F���E�$i��J�Ȱ�Q�h:f·�Ѫ?ʇK�dk(�\9M&4rNƩ��⨂��nA���"n+��N���6"�`��Ⳬ�d�_+gY�*r���n� ^�eF'?�J[i&ş����$X|��zYhm ��l�6��i�t��O�^�D������MBq Xaස��� ��,�q�T�r��� ����@"P�04 }���T���bR�_f���9��_���*�S�N�0��+V����J�.<Ε�l Ƕ�Mh��m^��\�y��tے�ͫ%³;!�D�L����xA��Cs0�}#�k-W�w�2�Q7'\����ʙpP�tH��A��VBd���o�œ�N�0 ��{ -+wV�!�n78!!�����Q�N+OO��m��߿e���r���0�%_����뷕z}���U�^-J�43�dϕ�D�]Q$� ]hh��(G�����#�-=9^��A��A���@٣t�|z[��}G�f��H�Z�8X�Xs�;˶���X�0��Nͭ�nΘj�� �ڑy;�)�l� ���Pr�i"��Z���MΚ VűBD�C�z���8oV��ַ�׀�yf��w�u6uM��v�bX������MZ�tx�h;$�����GԤ���M0Ό���y�#�ӏ�c��ٜޭ,�'[}Ŕ�N�0 ��{ -+w�!�t'�!q����&Q�m�ۓ�+ �������r�w����Mq-� ��;%�7W��ժ4NA.��D��̪�m�C_xdI�h��)��.<���w���� �C�ڠ����Z��3�����a ��S96v�m1���%� - �l��v�����Zg��D��0֣q�Pc���+�MJ9��]0[��$�b2Ɏ��.���\0��oM��O1$N���춘_ʉ�3V^҅��_YϿ�i����;$�c� �?5�I���[���"���0��R�D� -�SMO�0 ��WX��� ��B�ƅ��n�&Q�������@�-������l�k-l1&�].����i_�������Z��Xdڪ���.�bCn��h6��C�)jugm.�k�#6��T�)(���O�b��ؖ�b�)2:�� lM2�����]i��[���Zu� -Ǔ39Ec���z�:��ت�<&Qd�B�Hy ����\mU��9�c;M~b�^�9���43=Oj㔝?Q�0�h�Հ�m�g�ET� 8|�cgY�B�t��I�G�.�Q�֛�{�Y}=��� -�>b����ܩ��;PuS�oqָ��vs����uR�ˁq�t{�U�C%�φ�{qʎ'�>"o7=dgv�d������.�A;:vs��|� -�T=O�0��+NީaC(I$6(s�8��±-����Q�T���~������j�X�0&�].�i_�������^��Xdڪ���.�bG��hv����C�)jukm.�����i0�j��x��\���Xd 6%� ����C2:�� t&��XC�\���F��Za�ZK�c�L�����z�>��ѩtLb 9��).���s]��]ڻD����6�yO�*y�4�2����8e��K:+L:�@<3�}�Lj�0��@sg���� -;o��K�8�����Y�a���c��!b�������~�о_\��������f�ɿR�g�׾� �����"�� ]O�@_�L�ޑ1������O �����R�N�0��+,�i��P�J��q��X9��18^�ޔ��qMSA��Vܼ�����e]�4ڌ��o9+Qi������枳�b�J#B`�ن�D�!Ib5w�SX�-P��lS��W�[\9%ɂ3+JNH�x_�Ҵ�1�BM`U���{=�L�'p�=�h�J���z=b��<����o� �Y AZ��N�k��ɸ�r��u�f�ъD�27(?�� H�E ���u���k�@����ۡV��49�M��q��*���1�h�������50��v�.����UrB���v�넩`��/z�{� �$�|: -�E��Gt*�ќ�K���$�#�zG~�5Ȋ�"�����d�3 :����T�U�����Oo1���#��6���X" -T�TҪ%�y�!�5�ʞ%��;�J ��`��������1�]LŒ(��$\��e���琒��Eam*��=�_�~����\ḁ�T��s������$5�ѷ�tx?���9�1�����m%f��*�^��Kn�e��1�D�Ȓ}]?i!/����IG|���������kC�ȇ:�w=2�oRQ*�F��$��rm�Ɍ5�ME<�"�������:�ƨ�ɉ�ʯ� -3 -( #Hp�Y���/� ��,�Q F�![�Ahz���AR\c��}�%�s������x������!�\`���]�f��7�NV����h7*9A�6��#�i���0v�o�ɡ���z\�d��2��)M��U .�n�R� CTd֨k4Ժ��Ab�<7��urZBK�E��'�ޞF�� �?��?�7� -«,��p�㓚[��^�万{k�a����������_Ŕ�N�0E�����4�C(I�aW��q���cG�I��=� -��!D�W3�{��r]8�|�<\u�E�MG���D��>�҉D�ȁaNJ��&��O�ő�� !]�V ��2l���]��<�keS�J:z�M�ÇY�FɊ�7o��jH͗�7V��M0F(s(��7�~l׫`Nc>=՘w���>|��&����h�&��?��ء~��I9ֹ���=JX��w>���@ �� ^/WŠ@H�>M�@��3�b��h:m��8��u��� -�F��A�zu.5�d��"t�FکA[�ƚ����H�,���=�i״�(�lo^�Qk�pm��-�]�'��_ ����(ش>�c���^�S�F��kf��h&k`-T�6���]�I�^i.�Y�ϋ�d�����l�6�GC����(���fW�M��%k~!�H��=s��/͔�N�0 ��{ -+wV�!�v�@ܸ x�4sY M��N�ۓ6]� �8�mG�����%_l���-���ZZ�Vھ�����V������ ��kf�e����_���"g����5�O-�V�� /"�I9��[�}E(�*0IŅ�� �X�VS��AW�h� -�����S[M�Z���ʩ�8��ʾ�A����?X�� !�ʦӄܒ�|��-�Fyv(�{I1>#�k�#n�2A��H,N�$mW��b�BTΙ�g �wRq&ם�Cr���>�P5��Y���Ú~�q8��I�,�g� G2����o���jGC��1H�h/w�<��s �3<�{����g��e�6?�M/տ��y6 �/�\_s�6���3}�i���&����N�8q-�:}�@�J���~��HJJ(يeR�=x&���� ���)v�I��o~0��$Rώ7��������#��s _��x�z��=<ħ�<��p��:+lZ(u<���/J'7��h ����y.�����w?0v��OMR�%R�gp���8o��ǃ)W�y邏����b�$�$5W��<H��(#n�#}NX� L�,4���c>V }f���.!�����{>����;9��� ���縝�1����p����[ܳGR�ޗ4���V�dna -�g�����׀�J.��2� �"��)�*���rd���d�kV`�rM'h��W��C2F[���RX��0�D�uX�>�mWA��y$0����/q����}�y|%�n d�ʸ�Q�8�-�IS85�M��f�n����>:� �1��s+����g�O�����7��.�??��Y��� 6�q۹�G�8��������rc��V��p�Z;�MP��T�ہq�r!�BxS�?ן�m��h�K�Y^؜����32i�ɣå}}�@'�#p.h�[<�� -�c�ԯ���j#��%�dH~�K�^�aT��M*��;�j���k�]]c��m��Ct���FA���������X���K]��F3�1|�$�K��-�A� � �������p��Ԗ�<���SԜ���$���5�~�~i�7�D������N9cb��<��2 - �}|s��oH�K�pvWU嬛;,��½|�UhO��qG���0[��= ��/*�P���]�\���Ԛl�J�G*ޙ�0w5K)�P�iMMn-�����O�M�}������d݇|�x����hz{T��G�\�$�5M-���͔Է}�_j�:��HSr%��z�g��}���:�X�Z}M��jj�s��hW.�X�ѳXώ�׋�V*�< ֮ O�m4�2�A�!�ˠ�W��r���' @(���Y�z����+���\*�у�@0la���\��г�T�?��Y�ჶ����9&�!f!��R��ءW2�׫�5��>׾i@���� hTf,\�+�oݕ S�L�������8*$��g>�͝�=��1���#�mC~i!'����n�v�h�NI/C�ϫ�e��ό|+if�P�=~S��r.�r�fFK�}�GxJڲ�R�����+��=@t��]a:]�}��yNcl��A�4��KiU�:�=���b�r��1qba��م���1���� <�¸�4�����*\Jq�|���C�0��%�Ͳ�����.F��������p�"��Q܆>���l=��x:i�Q��z���%CEh�@G��0�}�`��t2��^� S*��T�ʀCۨrڌi'���4��=²ż6�_�E��¤�u8�B<��B&f��b�*\�@fV����&���ƙ���à�p�F'3t�6 ���DV�@g12�ȩ<�$��wS�a��b��)�0�Hi߻}� �������}w���y>.ȌW���마J�wuY��%l3֣� -��pn�~��(k8���2@#m��g����=��b�J-B*6��*F��$�]� -{��I�A�h��Kv���գ�uk4��5'$r6��/f��&D��h�ؑeS����$d�: �@�JN�^�+�b˙kr�:��}��@��2�V~ a�� �r�v�֮��Tg���,]�Lmc� ��ӽU��[��C���\}��X�b(����q� ��j��A��4`n��< ��O�D<�N>�=��%��=���a�w��M�Y=�%;��N�&�b�E~���q흎�a`�z��.�d�Ag��? ����-�L�����O�qX|ݖ�n�0 ��} -B���m(��0�C/�P����4ZeI�h7y�Q��tˌ�됤|�(Y??R"�_�j-���-ħ�Gh���}(�����.�g�22F��6bI�ϳ�G3���[�,R��h�)�7w�O�VW�A� ��1z������g9�m�5���F���=�>V�F -�b˰���h K~�� -��&��H�������FӺ�)�N@�J�Y�DX�r�4N=��d�����A�j�i|����l�6 5��=��:]m���W����L����Ǡ�Nz�'�� �Jm��+`�E��m �f�"�Xo�AP5����ةK.���Y.�f�t�I� -�uh�@�u�����h�+����e�ء�.puI�--�g�c�7�|����$�_�*�3{`�T+:�vsd�ِ��S�8�Us�����Iw�)�����n����9��J���Ε*հ�O�T�n�0��+F�čM{C�lTp$g�ؓ�PǶ������N6���V�^"�g4��{��W���=�H�6�rs!�r��߿}x�F����VF����F���mU�h�{����"W1(�hL#>�0HC�1�r���F��Qz{PȽ�Kr��U��F��"q"&@���T܈N��"K&��{�Ԓ!����P�BV�CE�IHک�8u3��� -�3 -dĂ�9�G,~|��']��1�� �lG{�PԚ�C � ޑe����D���唋<��{�+��< <�$E�9�j!_��� I1ư(G֏,���w�C;�0$f��=��ن��P\ ӧ�����Ż�}ߣ��@]<9�']��E�&���-ѣ��R�]�����.97,����:gNd�_����0��]���}� -_��u���W4�3[Z �sz�{؃';z��<��U�Uo��X�n�0��+��=�joE!9���>8=���R�@R���]=�8��d+.z3%zw8�Z^�RS�N��� P �H=�د����\��B��s@�������sШ�M���z}�`0ʕ�؏<����r��2�b#�2p�{)��T:K%������xZe����~E�Ho���rO�&R��"�_�5v`�NXl��7��<`���$� u;��9>~MT�5���� �V��R�o8W,�2Ȍ)߆!^ݔY�ȇ&Ԋ$��|���LA �%e� �Es��!��T=�@\�� 5�-���r�5�y��a�U�s�^ŗHi��[��-�檾'Y�K����a�/ r$L�� ��~<�>�=��j��3`{��Q:?~�|i(�ڨ��@��v, 4�_�V �9�&u(aG8nz��fhjm3 �p�k�.��y�7K�k��ό3��e���Kmi"�]'��,c)��4`i�2���+\|z6��E0>���sS7fb��F�bfu����Y��:���fV���0S0,���{;)���&J%  ����b�ǰ���֢�5v�J�cv��^��Q=�j�=q���J�C�Dv1~��f@�Ѡ� m����y�Ztj�ȶ \�Ύ�vNh=ѹT]/W��ux*�f�h��{���0�@�����w.��b�,��Q�@�y�eT$���������{c�LrY~:)���+5�ש`4Y��T�7�����>Sm�@��o�!#bpW�k����A<#%>��Ǧ�LO�.s#9!a�4=ۏ��׮l��D��r��o��}�g���'>�����j~-�9�>��a���&�S�����ݗ\�5�w�̶�r�0o0��FI*��Oo��t����g��9�=�y��,�aJ����U�q���fKh4��E�b�"��9�o������CW���D�&K��� �������N��N鿋���~78�@�J���}� -�5�ї��f�ep�Ű1�K}����qV?����;j�tOt �z�s��V��:��h�;�W�ܖ��\r3-�Q��h�*j��Q�ؓ�+;����w�1����Υ��oz/��~2 -���%Y�c���3J [�{k����͗M��0���+,���BIW+�+!�@P�+Ǚ4�:vdO��g⤴b���*��$c��̗�䶫 k��l��/�qV�\�u���~��vy�(#C`�lC�K���d-��]���"x�Y���ow �����^ά� �RA����:���c��p��T�ћ�� -�l�J�*�4���3m4nR^7��*�;r(dcpi)B"���Ω�8�4��9(���\E@��U ̏��+� [��ԪdN��{��0]0i7�(�c$b z �C֬?���W��*��A��d�;��_���oE�?�Z�nj�S_OŢi�2ST���8����*4}�^Mx�{Jd`ʃ�^�pF۫��q��<��X�|d\~ݎ��(OQ��z|WPY��Պ�> t�6ޞX�^���=��5a&�һg�8�S����t�k� qM����� '��+�=�<��r��.��8��:���0��r(�N����u��.JD��X��XMs�0��Wh|�N ��a�80�)�쑥M,"KB�ӄ_�J�ӤuH�|8J�w߾]�]e|�(%��uB�4yu�2!���BM����oryq6f�:G�riRxoގF�:7��zq����e �TR��׫��V��EKp�2H�z����!�|�y�;��~V����|0Ds�-e>M&T:H��� �Z΅���/��T��P!y[A􃞸f��lV/�8f��Ha�4����6�iA�d��̂��"~iq��l�6]�W�7��,��7`l�ȗ&`1�ګ}`�����xT��;F�dZ����*.���>ы�w�_i�|�N���9e�m\ε��\�9�[���iiֽ ��s�ཞ�>�����fQC�J �'8 TMDԉ��w��PY�. -Q��� �jڞkp#�*�����x���N��ֱ#�(�#��������ZK�}xl ��:����Vר�(�a��� �n�q��}� �5�M*��^�����%�j�SP`��۠U'�*>6��j-��Z1����v�Ӊ_ЋA��V�@�}?���G�����g�N��W�������1E�oI�b|7�M�=���l�rJ�p���i�Y}u���)j��s�5rQ�x�$��_$���3�syr�o$�4���q�ӻk��(�@yxlW|�X�'NI�0��7���S����Ω�#ORpLĺn^S_ ��FV�� ����E��u=԰����F�!�\Xq����_���oŕ�n�0���#�wC�J�BU+q���8�dc�O䙔��g�d��e�-%�r뜙��Ny9zw���P���;Lll�U�����{�۳�8M�9P�:��CQH�黾��& ���vp�R��>�_E�а�*�#��`���ٖ�@鑻�,t�ch�8iÕj�#T@�ٚC�ڠݾ�dD���v�|��i�$�OV`�����@�=����CW�A !)���Z��+����^'�1-s�i7H�1�?t -�}������Y>���_AμRގ��ve1G􇴈�.��Vv А'z��)z�!�r-�P���L^���N��Zy�h�\�v .mhp|Q�6�A�m(o�q#���NHqHF*�.��a�{ -�2ƋO���;䵁��{�����c�<:�u�:�����He -C��<���2�D��C`É���}9����\��������_ �ɮJ���Ù�_O�84+��B�ky.���m�XKo�0 ��W�����m�[�: �:�X� -�hUDC��د%�I�&��ZEo�ä�}|I㓻���N��d�G�2@#i��|���<;������Xj��f�&����c��hT-�ݍ ��Y�AYk=ɾ��_P��Kt��8ɚ�zm�`�D��Y�"� -�,Q8o�����a� ��jX*#t7 -bXЌd�I^7�0�NZUy� $-�(�S��`&�����A ;G��7J�@�i�1�M���xP�U�h�?8��}m ���a(�t��v囆�+a+�b�4W�0P��{�%Z�Ws���$����:�y��Sz�� ";�A���o��['�!��� �)���;(�8��T��7 6|�Ke�i �86�U(U�pٸ��=��2�,�!�#%���O�ļ��A���,��� �� �>|z��O�i�V��W}��p��kA�B���2&�m� �F��IZ~�`����1��� �Z�������T[ɾ�������aIn���_� �Тz�k��55З�)�޿� w��Q?�}��/����៷������č�X�͖M��0���+,��[UAV�U��jW���2f��cB�}��6�~%(R8���>�Ez�Պ�`��O������@�����݇Ϝ]��R��s,$k����$!Z5US�n��g%g�W*������u�u -�9Ӣ� �W�W�����1��k�� B�֓���:;� �Nl�3G�Pf�ʅ�E�9*�}�k$A�C�J��u�I�)��+#aLv�b4���"e|S���L$7��2�7��#�{������+4��L��K@��T��Ehz���U`���;ᘴ z��5N��j�; ��vRe��q �c;��s�.Fַ�}��FP�0�d=�@,����V��_�x�Т�n!�ʚ���@��/�"0j��]���3���7�{��E0�9ٯl��k�~.�W�sh�2X�ݨ�'2g�2.�!�9f2�~��wߢ4���?͖�n�0��y -��.魪`��jN�R��sd���˾}��d�l�]��r<���<����k�:4:�Wל��&C]$�qs�� g7�X*����%�$��F��VuYg�[i��Y�Y�(�����_B�P�/Ι�ZHH��[_1CG�37�ݛ�^�A���(l��J�����p�Hʄ�B9��0E��Kxm �$Ȃ���A.Ek�E�h��5#Se�! �NZ �4����oJ`0�b���yS+,�]� :����%��W?���X�=f -��SirT��u{l[�F���1iA���k�B}�^w����d#c�c;��s -.F֏ЛTa���~��6�O��@��G���~㧅M�±)�يT� �o��XF���e;�.<�@�}����/�����'���^ -��V��2ܺ�C��2�F==�9ƐqQ��1�q�so ��-����� ͖�N�0��<���6� ��!8�b�[��q&�h;�'i���8 Q��*5lj�����c%�iK���N���%g���P���z�q����"�J8�|�v /���(�Ѣ*�̴ 9+9�k���+��l�[ �ҜiQ����������c1�:sCҟZ��S����= -�K(S��^���8s$e�s��t��B�&���@dA�W� ����2q4FÚ��2�_�d'-L���Q�W0}��D����Ѷ�Y��W۩G=�G,ߞ33��Pӱ49���t���)��w�ᘴ :��5N�> j�; ��vRa��q �k;��s -.֯��Ta���~�������@�����㷅M�f±*�وT����b����������Y L�Q�a�g���d -���B �۩ ��o��y�L�Q//dN��g��C*s�d����߾Eq�}���Z[o�8~�_Ax�Ex������pe� 'N}A�O-Q1����T����Ŏ�M��E,�<��\�sQ�><�3v'��*?��~�[����+��Y��-�{jIw��`V����ގRV���G�R�l��zy�-���@�q�C����rK�����P�6^9+O?����,bkS�$���!4wdT��0�$�_X�E���0O�a5$,B*q� fS��^?J_-d}��D|(ME�]Z�r&�S���w�$/�Kd �MS �ս���]� O�<&)�wf�O���=:v'�mfģ����KpL����DM�/�O�Vؾ��%ts(cOw�U����k�� -y�6�׫q6��Y)�;����5��.���6�)�V0�RTC�߷s@Xe^7�'� -��O��u�!�U��V�k���vԣ���Nb��k����Ǎo�W@Cw���D�ϼcߎA`S�"�:��W"߮J����t�>*��^R�6�njq j'��4�8��>y�㋿C4e��\T�� ؐ6v�!ϑ��bZ��ȥ�I� 1Կʼ()V��jege"|���,A,�\�h��޼$�ږ�?Ve��FpolC����5 �&�"��{g�� nbYj�[��K�.���A���ݡ�g���e��kͱ`�V�X����2-6�߭ȅ�q� w��L�j���w����!�D���WW ��;I��װ%��[DO�����d1�O���1� -�r��/_��P}8����X�Ɵ��S� &��������F����K�&�ZeU�P���<ͼ,�m���dr5��M#7vD�4�W����(���ؿJ�s��R�*��Vm�ܟ\��� ���~��-�� �� ��=��މ�X��-�x�����h4eG�G�A(}b��Oo ���q� T>�� ����njW�h�1ĵ��<�mnf_���]0j�:ڟ!���i��l\�s���Gt�k���^~Y\F��?�iu5K�G��n��˗�)���IՔ�l�Ϣ+w1�� MA��g��$�k���� :O|�ܮR��У���G*η?���P�.= -�tL�m����֧�׷���}���*��S��O�\ EM@���F�T�א��B�. ��Aǧ����P��Հ,�Li�����;0���7c�\���,|)@�� `E,EJ-୼#>�C~����9���:�'D�2@Vh�ڠ�d+4��p W��Y'�\T:�֋� �-[H�c���|6�����v�A�����.qd)�o�6��w�W‰��-O�QJ�f*�Ƣ��θ��D7�I4eQN� ɝH��u��o) S�����B*��ް2m����=�Ɋ�7����‘0�YWM)� -t.�:�%�ƃ���<��J��̨�M�Q$�1�v������]��O��>�&���݊]�G��2���|)�>Ւ�^���yL�f��U�o��l��l�C[��o�����x��~���q~yM��ŧy4���k�g��%�p� -���Etٟ�f'>��%"�Ե9< ��/Z� \�^B�-���5QI���˹�������U�v��M�yv�V��͗�n�0��y���[aH7 -�hZ+瀢�Q�ȑ��/EY�Ӹ� �:���g�߮KK�N��O�� P �K�H�s���3���M,w��v +��q�ը*�ܬG)rV0��J%���������W���*.0a��/�� @�kB�����Z�,q�I���~.�Xfh�w�Zc��a��� �s��R:�I%i���Δ!�w��׊&�G��n�=3"SF�j��� +C|��d�����S��� x���U ��̼,Q��QK�.��T�[�7݉׳� �Z���>kx�BA��G=��8K��{<�H��Cx��x�@����U���H�h/�#���\n_\�/�;zZ `G ������ηLA�> ��\��TW�i�6 -`U��5�p�����_��WAm�^�yj:�0��������x��)�ز��>-��qN�Fv��E��>1U���O���n ia͊g -/@���1�R���aL ��z �?����O��� -��/�� wn�&q,�U�� �0Z��reN���}i��YG�g��Z]o�:}�����p��m���"u�mm����G��(�[IԒ���=3��عv��]�h-�g朙!{��c����N������%T�X瓋������Z���ΣT:'08w����Y���N1-b��ɕ�:�DR��E���v�W��}K�2S����h����� q��"�GW ZIz�q�j�˯�<+S�X�j�Ke�o�{K8���E+��S���*�e�/s�;��O�7�S��<�EV��ɰ�8 ^+�J�� ϘI+���*�[� K- ?��� ���� �u�LZfyK�1d�ak3��X���/ZE9N5_�2]��N���_>��_?B!����ݧ;��i�z5�u�쿳���BZ+�r4w -oA@!-/S��#Ʀ�B,�g*�W�Xt��U�9uʋĚ ���ї�TD2Mi����f��W���WY��"X֩���S���fA#ס8i,�X�*_ڼ��ؘt����ù��Ҩr�a� ��ܪDY�O5�|3���$�E+ӏ*n�[��ϯ'��,K��|V������c��CI�-"�¿r��7O8%pn��]0��`��* `)�]�"�hpT5R��m=q����@]ar�~���;�:d��E -��4ƊT���{f�=H'J�I.�*V�\=�#�����S��LK�^�ЮB�$�G -�W�j��s1VB�0�0ҳ�3tZ]I=���,��D�Gy��zH���z+�F�<�>���~���=G�AOA^���H ���K�͸�Qj���{� � -��-bEڨ��D�+KB���8�:�R�-� �����K�<��I�:�( � g����9�!U0k�J�Ÿ�"U�ݚ�� 2<a��қ��9�~ƫ\���a���z�{��KniLRW��E1<�7�Y�lK�!#���M.~"_;���s��,^��e�������2+��r��i�҂���^b���h O���t���JV���xr1{�0 �loJ_���>�P���t���*t�p���֫���`�wa\��_�����i+~�9[�J��8M귫o���]�c%�s�<�x�W���#BfnzxФ��vX�~ &ℵc�~��-�Մ�J�D � -�C��n�3v�����!��5�g���nb+��W�>��L����(���B�wU0-�l -�-��-E���QJK�â�Nt��D��6ty�T����̆O\U���|*�]:�16��'��R�2S��]]�<ѭ#>rC�Q��f�@5��#��y�� r���(�Z��61m��}O�%Ee8#�� ^������j'�ݓj*>t��k���'ڧ%\l����3��3uk�ŧe����f_����y��.d��!W�#c5��Dvh�I3�i�`�][7���4��8 w� -6���mb!��vR�;�C�}l - ���*oCǽ�hE8�F��; ��@�l��K��ps�e�\Yê|���K�KɻC�F��x ��Vq�����!�� �F�����!5?I����%�is�N�|9��(>����O�ޗ��������~��`�x�G�{o�� Gc���W���XM�H�|��(�e�U�W>�D��N2K%u����OQ;��+��� ��:x:<�1���Otb�j8����n�G߮���ɍ�j�m�|{���Lyw&�X�rn���Q�w�ﮩ��t:�ߟ��}���oCq{s�H� G�q�� HB�����I:�f�ʥ+�mɃc+������h|�J�[�T�T7�A �]\�_-����F�{�,��v{�$Twu�X'2.;���i�fϓ��Kc�_�L��/d���G~|��r �墡�S�Yw~�������A�~� �Zx��-�&��h��9��Հ(��5x���s���f���e��NH~2I�������#�c�.�����o�؍|y��,qy��.9�w�ܗ�ŕ�n�0 ��} -B��w�a��C�= ��\�Sk�%A����G[N�,��a�w e�?�ߤR�m[ =�d��ćŭt�k�+������-oJeeJ��.U�! -����Eh��ۅC*RT֝���*#:z ��|�d�)H����Y���4m���q��Qu�Z��RY��A1Vh�����RH��E��ki -H$ɨC؛djc �*�ښ��8i�Ci.����W/90�h�w�|F�{�Z�0An@�ڹ���E� �e���?+��e�q���;���NxOO���W{af�_����}�� ##q� �>���mB���FV�i:� -�����^�╓�3�j��,0����] �q��ػ�.��z�lG��KsΛ�����a;����Y�jP����v؏��tq��7p���r�N\p��};�3�� k72�?_��`7����}P=���P��ä�7�Ż����[j�Ŗ?�SMk�0 ��W��d����� -e���vqleulc+���s�tl���ɖ�������3p���R���AX ����h��F2j`w�>���H�m�^����N��Z@�_����mo�0���)����!�&�t��ML�I[B��Ҙ:v䇨��sv�l��Q��E+ٹ��~�\29Z�T� W2�Fo#����e}���9����Մ j Aci�(���8�jT�e��# vl4�H愈��|��5�HZ�))�f�މ����!���T�������4"W��R��ت^�FE5��m���Do��t��v2��J��z�����Y��n������ռ�|~��z���W��x>���vz{��8��?�=�P$��D���5�YeT,���p��&�J��� �NةĊE�f�] -�i^Z��T��l1��jKTF�,�%����6�߈�P�ETC<��͠D�%�y�䙳�"�0(��i X�"��DM��^� -�W�17 ܍2<�`���@:c�U+�����{beN5,�� �SYf��Igs�6t4��d�CcuPo��%�Tl-v;M��p���֫v8&��4X�eW)�v�y��d��"�����B]�-�쓍Q!��Cӝ� M���V�sẤAl�dhJTP��$ `'�רf�-�����ڸvq���0�ᕯ�A�m=��p.�b���量�Rj)ɔ&e�O���qx�;��[��t��(�Y� � -�'˱��l�%ع�n�U��a����EW����+$����L��9σ4��%���a}Ը����_�oMS:����4%�Es���84�s�����E�d���R����8� ��͖Mo�0 ����[1�)�b=u@�e��,31QY2(�q��d�^��$%�"�G �W]eD ���L~[|��v�M&��n�\Jq��H�Qދ �>�%s�=I�jQ�uẅNo�@lK � p�����z���gA����o9�t�ȸ�5����=E#�G�Mn�hU�k��A�O�!�g�z�ob���?��� ��81f�Qa���@��cbG������`����X�s!PDjw,��{(���z�j]HR��B�a��Kq1�,��}n�8QSC�m�k�r��M������As���y�a�������O:ЗQ@/�鼃�# -�h<�\n���~A�{n�>����X&j0g����|<�ӷ�Xnj��go��f7���dvo�*������9L ��N�a�Si��ŠLH�n�IYeR�.�i%ݙ�l�0�hO�,3�����&��@�e�!����u���������`�Ib�)1@�V��|��w�0�+SU� C�#7�"8�TY�h�'B��Z9z$ũ��崥V��5Є��gPD�#�������s+�7^�5U բ�u&pk����$��͊���l�" -֝���;� �ʨ�]��O��K-��䱉���/���{Jj�NUE�S7������ں;���)��:�}��6L���s�okڹfw`��~־):bC�}�&}m���6���«��}&�s�l����T�:6��B�8-D�)͆��W�=���tA�VB�� t��?,�� ���@��Nr��/�~���C}�),���n���n�����7�Z�]��i�9j�[�� �����OY�B\��S�� t�z��Y� lUr�:(Rw����!�ߦ`X��� -ڼW1N�3�_�������}O3��'�Y8���#�Sw�� -t�����[~�,ܓ8(H��"���so3�.�l�ݱ��j|hs_#"�{��ⓤ��>N������Ú��͖Mk�0���B���[Y�R�S -���,ObQY23c'���e{���# ��bi4�>���jSY���.��s)�i_������ǥW�T[E$���L����$ ֬.��of8!�R,k3���Ÿ�%�ۍ��Cx)���j�a��W���q~&Dj��B�i�Z���*��Y^�x��*�C&R��K���L.��`��Ln��m&k� ���C����N._���e�C�4�`nm�. q���Y�V 64�$Ʌ�C�|]� -t@$M�d�AyP���DH�)i,��<���/��"�ձ���b�"�آX�ƒjb����� ����p�n7��ߥ��n2����e��u;�-�o�s��j8�4�ԝV���d�~��bt7�����ۆզ�F�K���3�Y/H�0�kKnx���^��ଲ� ۗ����\�X�a�g�0�b:[�����.�ܒD�Ygc�OD�R��#* �8��U"_���&y$6u$Z%�Z���D^� ���J�u�l)� ��kn� �;�{vD ��ĪH�R��be�6�]�M �SIb�6Ig�*.�h���!���^{ �8/I��bsc�hj�۫��&_u�}�L�͵,ᖊ��d�!Y�s��u��ߡHъ�`�dcT�۶������ e���p�Q� �*2�J��vo���JpsT�W%{%�WQ�]�T�W`�O?�2h������r��_=n2uSKI�4Ɋz���p8�{��+����4k=gQ�����)�s��b�����h�l�tX�V� _�ut�G�d�ʟBri@�ʴj��ʫ��7���Z�D�������}(�,7�]~DŽn�������Z$����S��05�~6�q��@������sxvy����Wj�s��o�'�>����C|)�� ��5�9=�]�0�����8l<�]��}C�>b�ҢH�92e�����F���W]o�0}߯��A��0M� -1ڡMPѴۤI�c߀[ǎl'����NB@�`c�n����s||�u�d�IR��B�~��sPLs����2>}�6"'��zLRk NV�-���u����9�ˎ׵�E$-��G��H�6� &"�f`sʠ�R���Մ�g���V�Q�o�a<�N�G��t������8�k�H�n�W(�!�.�9B9���fjwsn���فy�a|��/�`�F�/���Uгi�h>�B�u���}����p5�PNa��Ot� 0���w��:��,���*�"�N�Q��*�M ��X�&��b��1�7<)�t�0�~Ed� ��1œ�:��7���������6�E�5 Y�t�9M� �ר�ڠ�>�[p�B�XFm"�������-���4����;9�h�d��'hbG���k8�U@��qD���-&������S���L��r������w�S�j1��a��jO�l�S "�B�^z�fG f��d��NwW[��,�efy��K�ٱ֬E�5��i+ev6��=�Y>ʤ!0��a�{HS��w�=&c��m��^����3����ġ�w8��|�X&� Q�8L�s`�� �'�0�����З�h�g���L�zHh����ۯQm�E�|).t����=/�����X��*U�QT��V�@�oUP��*�8���J^�V4:��#���F\�Az�"�&mM��!N����7.z�_=t���u'!z�?�`�L�͖�n�0 ��} -A���m�Cў:�@�s!�L,T��v��/-�]�um��4E��H�p~�m���񮐗��R�Ӿ2nS�?��o?��Z^�ڪv��5Q��el-B*�]8�,��b�Z[�{�n=J�T1( ��w��9l \G�o��S�p�oY -j�)���jRDRdt!��F6;Mi��]!zMP%Q����j--K��d�>�K���`��Q� �u��Q� -��A@����^:��v����\���Jo�γ��_$n�y��G0�ѡ$k��9Hz�>�\��W�Z���N�-k�Y{����m վ�6@��S%(ɏ��1��5�R0N�� "|�@-�={���!�=Bg|g±��?���(�i?��1ۿ���۴f�p�K�? �O'��+�{. -Q�e��&΃���H�É�rh�����׽���]�g�v��]�o�6�}a� H�6k��h:���ͷ�������%:�"�:�r����{�_$Y��ؖ��-��%��}��S}���0"#�(.�A�i�I�0Ȑ��������������*E�a���{{pՌq(�=� �O��q1�I�:d*�;h� w������8bC&�r���Thڋ�$�aϼH�4�?}y�>��:݋�����i�AF4J��/���ʠ���^�#�=��!.4�a����}���}����I����<��dc�;퓣���_7C�����=mu>d����y��UJy3dQ�d�����G�''�v;���}�d��?]3�q�����ź|������� ���;�/���W9��=�m�4�ώ�e�}�����;dz CG��a{��!�L{J'4��>�h\xI� �q�{<�z|Ј�^�Q�sA#�Ĭ��L �5X�alh�PA�'!OX�e2&Z=`$�@h �ƽpE�����V�'�i �ɼ�L ���X=���oOD&!K��N&��/������@^5�9 ��q�v5������Q���A"�Dx��*[i�t�'���a�H�:� H@�l�$����fĜ��7NX�%`�Y��%�E;C�g�S)�#�t ��#F��q�+͆߬��FK -�b< E�2rt���\�>�e�Pb'���RЫ�,�Fo{j'��b%�(/��K�b+~���c<�B(q�d:g������Co�6�AޢQO�@����m�z����}�ۅ& ?x���b�����K^�}��6�1�� �/ K�q?�o:�څ`c'� &r���8 G�^s� ���� [�ڒ�0��E�#�:��4�e��y��?� -�¸y�S�{� �����Y:�'e�`i�+�������P�2��,�"$l�l?��:� u l)ee�:�� l$�<�ڲ�zB(�A(�X��S3~qŋh�lT���m J�M�E��7�/��. -�.��� �NWA�3P�f�6�#�>Zi �s��=V��|��V��h �%�)�& Ч#�#L�����^����q�!+�� ���(?i�<���/�w���^�.�edm�|�b~��a�t���aB؁ ����~�f43�}'��ji��31��ި��Ԋ�h��B�MZ�4�������!�t �E��06%B�]vςԔ���}��j�s����T�n�?� ���û脖�m�+I����P�99���(����$PRN%�~�2�݁f1^<��f�m��7"z0�e2��.��/� �V�Ӂ���� ���|��i��Vz��t���@}P�-d�㱯�`��Ө� S�*�-��K?�g+@�8�(����\i�y�J��]�b�F���b!p�h���ß��>��pފ��1�����O�Hw�*o8����w�~��ĉ�Y��<0�”�[P��#�mz����ٝBA�:`آ���+d�)�/�΁��u8���+�U��a�ed/�*+Ƽc���� �V��`js���٪���t�2y��E�L�p ��!����7��4tSA���:8s��V,�--�f� _CC�B^&zK�58��[Q�Wrӂm[�Vѹ�$�ZUɞ�x^G�8�z�e��;2Y}���m����Aa��b'����{���������ϟ���Lt:'D��YU��`~N��|N��/�#��$��l`�R�Db����ǁm�a��bFs J��W���&��ht#�j �3ۑ��������Uq ���ڗ^G�����] \�%.{}@P�m{=aʰݣ�5�˘4�pLj�G�6��cW v~ -X�lJ�����1�X0��^���ۜ#�1\��3�MXd��}X�imi�柧�AR��^��:c��b�-L�0���VƗ6[J�����/��cx�Ƅ+A�����'M��)�ڎ��}�J�v$��x\N�%�Ld�16ITq�j���9�To�>[8t� -�!FBC.X�!��1�!)�M���=3e�}Ɵ�Q�xdž�6`���B@�?�I�Yv�+ -xT�0�#|R��hl��/��c�Kig�5�z�BK� S����#���WY�Ԗ�8��#���}����7� �S����m������&͛O{�G�Q -�����f�����Ic�����|`t����e&)|��>���k|E,dF��DܓY*����tG��}p%��A#��Q�cl�L�/ sqB(��͹�����5[v}�5V��Yk֋+<㱍V���g�֑BA��~�e�H=�ʸd~���/�+�[�Ő�[��%CI��`��C�u�O��٘Ԅ�pp4p��1R��L��]HT+��b< �d�����`Õ[h�)ҴM�.0�Gi5�-��x�S��Gǝ�Qz����&�Z���k[i�9rK�M�%#��=v��U��6F�i�"{F����/�h_�IN8�܊�4�i���6��4gP���/ɿb<��Amy^r"���N ��ʉ�BVl�8��n��-�m��Pb;�����D�K����E �۲i����`����U�m�4�xs�pZqzɦ&]J��ez�8�B��z��=k~�6�I�8�����t���H�=�w�h���y��<;���/�bo�`����9���/�0W�{ň#���ڀt-��tiʔ>�u6`mR5$���tW�d~�����`n|Z�#2����W���?�w�U��^wK-%�i�j����5wҫk��� ��￰g�NXb�>�.Y~�l�j�}��� �M��-5u���� | Z`*@�I ���:�B}G9!/�V�wwh���&�`=��E��-�����3;�)H��3�/� -Qse�u�|�϶ E�|�5���B��D��څP�(�����_�GT�[������4��f�+�28�xb�߲e����M��N�Rw]'YpR�O�!�4G2Mbw�{�.��ۆ_�"XXz� �c>wL �a�ɣ�x'{�H��.��ĉ�`��D�W��a6�LFh+�YL�<@g�g2���녊hrS<�kz��I�K86���/|���靉���}��Ƅ7>(�䎺�V}��#>a~{�u���4�1�b�F6P��4��]�� �#��g�mZ�z] V�@��� ���E%Ɣ�>+�[6�j}򅉩�&�Mő���5��ܞ��f����~<�q����q��`�S��?�V)ֈ�ۖ�׆?� ���_�DR�%c�ND���!b� A��a~g �l�j<��2��� d�,�� l�ނL��c����5T��h�NEޣ��e��_Pw�P�i���'�jn.�=A�|n����o����L���3��?�\�S�8߿B����°�W����0G _E`�v_R��$��%�$ru����� vl'���W�����V����O��&DH��q���~ �p���q���������#��R"x����X��݆��`��i�Ֆ�i�a�yǭ�1gX�b�'2�9nE3O~B�<)�\?�#Nc��3*����^("0�HH�x�m:�I���)�>��u����^��s����U��&� ��/f �˝�ǝ��%ܘ`��4��(SdDb��=��Q;a��������M�{c�{����n����Y���}M9|lX��?s�������6����˛o�ޟW��_{)�_?4 k-�}�Դ�����)����0��i����vs�����5��������L?}��n��D���Į��5��Bx ���:n �'I �G�:��2�%W���(0��~`�u\a��f��ē#ő��h�� g �>�� �B�b!'����u��! ��-��P�ǍSA�D@�#齈Hq0�+�HO�*;�yY]*�zTM�[A8�SE��heh��Jr�+���Y��&5 ������&ҚAC`h���T*�7��\�U5 �uȰ�l���z�(i.f�$�B�ia��$}W{���.��>�? R�?���̈́S&D�(��w���|4;���L^��Z)C��|-d�(ǭ���,�v�غM��'���Z&�A�qF0�A�y��B �E_�-0�g�{������9�ֆ�?���(��e�%>��ghjF�yF{�����S�#�[@΀�@pG�p�#Uc�h������d.�i؉����M�.]3Xk������^o"��4c>k4�1��"vV�e� I�R���m�:i�H��E1Qf��xm��0�@򽞥�`����@}�u - �O1#x���brl]:�n��qhx�X�,��`\! -��i�GC.|��Ѓ�0�k�� ���uj�Nɬ(E�W*i��� R����~����T�a��'O� x$/�dgh���k��,3mW��Q\��K�3 ^kt�1G�[a"�=� -�"E3�㙹 �or�J��~Ƌ��POL3q�/CO�'1�*;;�<-���f+�2��l4^��ꦪ� ���^�ya�Y� ���A�G�`�2�$Q&ҟ� �]������eqr��W���s��x+�\n�lbvF�H�GP$�u�P*�4�\�485��i|�����EFf��:��������p>vF<�U�Ñ�����^y�X����5�D��m��t)��([+��Q�Һ��M�1�|B\�����=�Gբ����.0#�Z�m�UH#��ϖe�=$����R;9s����T~.M'�w�|�h@D�y;8IA��.R�7�r!�p��G���H����Dj�JrI�[��GM�i�g��`� -K$}�3�l�7����>0٬���+��،׮������2�`t }��!s���u ?��������S�WڸGG �@I�qH<���m�'����5��JR�f.�A���@�CU�ւͷ�h7�a���.�^�ue�V�B {�v��yZy���*��5���)��nc�Z�=�����]�k�o�Q㯿����;h�¥���L��44�-��Z��::S73�~�>ˎ�}��]�آ�*�I���[�����Y�U���� wv�ۋ����nzܜw6��Ep�2G'�����!�&>�"�h"V�Ԟ��K&y+^�-7o�v�����G����M-���b5uAZ-F�n�mK�����Z��]_�J� -wmƶ�� ���U�L��N�rr$}��ȱ�1na��"�ژJ�Xl�d���jA��VX#9.~:�<�"�_�.�H)�l�ZW�:����1����uܰ9Ϩ�����/kj_v�9�)r4s˛}��J�����ѣ�?Ҵ!cU׾[۵�o�?Hj6�/~Z�B��r�B��N7�N7�N7�N7@A���y @A'X�更�?��L[��? :ʺ-�uv>����$d�G[l�f��l�<ʾw�#dGZ^.�L�^{� �E5�R��jݹ����������*�l��1mlU8���n���慓�͖MO�0 ����(wV�!�!'��g����&��vݿ'M[�[5i=�q���c��UShV:eM��gg���6Sf������gW�Xj����%<'*/��[�2/3�� P�Pr���N�c.�P�˙�RHHxg|���0CC`2�;l]�b~|��{E -�{��N��3G��L�Rh��Z9�*�h��-�$�B:!���4͍�G�՟Y�j+_;38;�*`����ȁ��� "��K�@F�� t���Ñ϶?�:��X�Gf - -~R���Y*}�6o���s@`� p-���z��ie������k;)�Yϸz�N��NAE��z�jծ -e��k� -�^�[T�ٱU��1je+7�E�v-R #Pn�ƘF���Ƕ�]X�@�����O�����`�uO�@ ��X�k��5q,�^�� �`tG���2�{mտ�(��_�� �YKo�6��W tjg7u/���q���A�\r1(j�bC�I�w��;CJ�֎븻=Fl������ g���U�a��+k��o�7���Pf1Ͼ~9}�Go�_ͤ�-6~�U!4G�)}M��)�jb0L�������S%ܩ�xfJ��5�FH�g��Ƃ�W3\4����}�?�M�Ce�n��j[d r��a��B{���>�ʫ\i��is�Xge��W�d�]X�k+��O@/�j���M<����_��*��]�<� � ->�%8 �3� ������f���f�pdS@��v-�g`�� -C�vX�#�pK�(:�� ��F�8�~�ȝt碇P!�֑�/��[å - �"����:ȯTs��$�.xsk�����yx%�\+���_\ c���;�[���2<�oF�[�F -Qv��u�; ܗ�N��C��� �n�w�e��#Q� vo�&z��u��2~'�������'�����Qq�p.��K���� �JbڢJ^����� ߗ�뱸 �!1lD�pŹ��}!�4�@+4xR�g�/9�%��Q������%12���Z�s#c�8�y���!�> pL$dR�?*&ƹn+����l5�^�D���t7���.�k��;n�{ �����g�qDW�l�L_F��ϧ)�����w�)w�T(���k���� -c 9���y% -��<&����M���M����s�/�/x�����g��1�ン}�[����zJ��AɎ�T4���������Y:[E#ľ7��"^)]/��7�i�ڐ}K��PM� V��GW�R(-rr�����>:�wpc>����1��M��a�o��÷�7���Ffԏ+ؽ����F[��[� -/toG��#��\/�^�ޚ�}ժ?x^��9�f�8�ʶ�k��(&hk���u�<���f�����&I$�E)L���/i��?W�&Sщ��l'��M��6I��za�a�㵽��^v��.��j��L[�o�$����*�����p <�'�4��F�H�:*�c�*�aQ�8 ���պ@�0$djgwD`��E&g�ۆw���@��O*�Yfci;;����:A��W#��.)�|��P�F�b�<*iEK�V��~ԋ���x�8E$���3X -�b��D6si˒�������l�D�f�ޡ|�E����N#��$O�g&�iOl�Ǣ�\���r��T��1q�� l=D���� 0��������_��[W3:��I���L9��%��J��it���UKs�0��W��N 7���3�Z�cG�׵�,y�u�zV���-i�-��ﱏ�զ3�B����p�^Z�jm�J���˻��� ed��m(EK�* -�.������"�+�`L)�:gX�a��R�`��Y�y��������쩟+� ����� �ځj��s�s=���iJ�.� ���ӳ��u��=m!�kZf�A5ċ��q֞TO��SO��5oܛI��J�3B�J7Z��r~Q�g����#�t���7�Xm��6�~�b�J�/{�l{UuN�]�����~�NBN2ǎl������`���`�l&��gf���n� -��6\�vЬ5@���I;x�^���ΫV$�1@�Ҵ����m�N�Z�d�Z�$ں�Q�\�v�9�Ѵ�L�^Z��d)��E�����+�V���L�R�߽�8��?�n��1��~�K�|��P�hZ,ic�4�eF2\Z���h}S�U_iڥ��~tw?=��ߺw���O��o*�W�Vy�z��>�#�k���O'���{���ݠY)~�<�å�u�W?Ys�6Qq���q,4V�ȶ�1 aˣj9ㆇ\p�lY -K&V� ��D�g�* "�f^�0�J#H��TsV/�*� B⊡^����Z���J�ݎ�:c����K��� @y ��B��8FMe��^du�ӳ|H6O�:?�V��wy�GV����S�F#����S>����������0��x{q��P,�Qxz\��{�̵�eU{U_���3�������n>#YƽF�8�w�ʹCB�N�ו�8�>�̑.\|�i,��/���}AaN�Wr�t�\��.^fi,�ЕD�R�1��S5'1����9�����jR�O��)�$��^��Z�� A�D�mn�ԠG�Y�ؔ�;a�����>C M=�LO~���1��V����y���W�PrB�՜IOPǣ޹`i&�� L�� ��ۡT��~�-��� ���@�\U -�/�.��9Z�(��+��(�f8Tg#��_�~��u9<�0Q�9�;�=�_W�s(�1�ד����ب94ī7T� ~OR��l�;��Y̶n2��;lR�V��t]���f��|��$o�ob�F^X����p�����?��gPZu����'�WQo�0~ﯰ�ަ�u�&Ҫ� �V�����q��ñ#ۡe�~����A��as���ﳝ��s*��Z��qvN (�c�}�0�~�����%��`��>M��>{�β$����Y�)�R��[.���lr�\��R����r�:����:�\��u�waw0 o�vJ�L�ߘGc|�y$5��Kܘ2C�,��<��^�ƶ��&؛A��‡�~��o����A�o�[��u�� �����D��x�/���m?h����4\7�C>o~�3r -.�q���Ygw>3i� v��˩�"R��O�<��иPL."���Fd}E�N�9�(�TE �%�(xB�93#N����Nc��FU�FZ��}�:c�r`��&0�D� XVK���1�8�{5�e�WV���ɹӋ�aX�����`���Bt��V�Z�;sg�O�3�6$+���|�ޮ�X�k � �*.+���J�c��w��������6��X��;e�$�76�B�w�s����;gG�i�-"W���g�Po�jӵ{b[�R����� -y��Y��Y��� �>K�1��������U6E���oi����֣p�\������ָ����:�<�k�x׸�I���-��H��U-g^ޣB��� -(���X��^�5m�H<�k�jLuEn1�IT_�����b]�;_e����/�Xmo�6��_qЀ�Kj��:��"H�k��q�e(`P���B�I�e�~GJ���n�-���SB�x/��=<��v�K��B�^�iE�*�\�Q/���x�:��'/��d� +ۋ2�7�6�ZEVp=m)tmk��R�^����%��;�ΐ2��@�m��E�b��� �n��uL�Z���~xq}3<���D0f���N�&y��X��Z�Ƙp��d�r8B�k���l�=���������vx7��_�O?|�>k�]㗧��� 90��-���e'v�_�w5�4�_uvpm�x��㟟�r�.Ӽ��8������^�2i1v"i�caE,�p�^T����br.��A�61�p�e��6n3a�v��[`�pB��� ��!d�ڍ��4��5�Z���3�CS�w��t�`�.�3�)jyl� �u��7?@~8F.��zk��c~hK��z� $�颷���Y���t}x�ՈD����o� �,G֒Do��`�� @���H�N����%�T��oP�.�!����~)���W"��"�龝����%���.�7[\j��R�/��?G��˶���},{�*��]nX��S�[ȷ��+����V��J�}���j��e=��z&��ѿ���P�c�,&�0WP��LR�����m���`a�;��g+/����S.�'�0��3��:G���G�pk�K�6�Ѫ������`��� -��&���Y�v�>~��Xmo�6��_qЀe\;I� ���� 2�h�&�20(�q�H����_�;J���^�-�[�SB�xw|�{��o煂)Z'�EG��P'&��~��^��)������� k7�r�˓��V�2/S3�k�g��J�Q�����J���[�����@�])E�b���+�ab��B�F�������������o7L��h�(H�|j�X�dR/ic*,�EI2R{�G �AWv8XZ�d��z���v|ws1����������g���g6^��Mژi����"�(�a$�e�.���X*����b%���ZJls0E�XYz�S���m.4N�y4�((�]�7�s���z,���殱1j�Ei] -K��h��Mp� �܅xa1CKq��^�܅i�?T(y�Bb��6�w^��o����v� 9�(<.y؜.`�?0��8���̉��K 7(�ո�>qWJ:<�=7RTdh��Q{@�Mzd��9�(�7��h?��3�����k-<�ѐǤ�}x�I�� CR5V�%33��A�d IF�,�=�+V#dL�~��U�w8էhY���_KѵP��t��3��Ɋ9tQF����d_O���=�xi�"HpX��P�B_��'� �B�q�2��@� ��e���TFKWEL�����QP�p ���-�ƓmwUF���ѥT�)Ù�ΰhs�m�!`B��Aޝ��<7���3,�q���t�>��a'3��Aq�a�8y��_8�x��p~|��𠷍���C,}8�5���/�p ��T��F�E@����������7[�Tx��[f�236���ɟ�E謉6 -�E�#���@�p33��=ѩ�J&�A1r��2���E�œZa�d ` [m;��AXK��:��bp<�E|�r�����h�"��@�U�.�N�l����nQ$yWw�Q��N�:8^Ƞ"�:"y��I8�uU��[�$$�XZn�Ѽ] -iC����n �w��~�ޤ�}���3�kP�x�VW�e��E!�"�ф��'.r� -�޹J��w���CRౣQS�f�$��(;�6�m�Y%�ځ`�9��J5�82#[�7�mm��M�b_;;2��.c��&���2����N6�t :tq��2&�z��K��� 2€�� MH@��ٙC�4�b�����7Q��5�XY�2(#&i�)m�>� L� �7�J2���""�Lj.S��g%���3���^`H��4�)�0�k����:x��Z�u[��i��n9�ÑU�� -�;+t��6F���ҝ�j�lGǠ���aR�8�y7ڣy~�������K� ���1@Au�j�;?���J��׽|ŔMo�@ ���� qc��$��H��Z9g30��zm�zQZ�9�&�\�N$�) -�v�KM<�I��h�+ss��#c� �)'�T�ո�_�6�,k� �0�� �~ē�&\��� -��8=�\Y�d�C~#�c����Р`���� ��k��������[-�B�ǥ6�q8P ��k�0o�)�ie#����gW�d�a]� J�'V�c�,Or����+��i~g��큶wZ굛��߽SAN�0��+�i��P�@<���j�l���-� ���$m�"!� ��z=3;��Cc����wR<,��Ӿ2�]����ݣ��jQj�)7�$E�sx* -F�P����r���o��b�yن -3EJ5I1������ޥ[�}���Q�\@�����tN�3G�P&|+��f�STH����H�,$�2؊J�B�4q4X����2�wgg'-L���Q��90t�AD��R-,�C�:���Ñ�v?�:�S,ߘ;3�Pӵ4[Tw�i� -�> ���c҂h�ޮq -�]Pۼ�0/m'�&�w@߇���SPѳ~�^� -�ՁZ��&[� -��UV_تV�Y�j4���:�f/R#P��ƘF����v�*��I �~Q���O�����'��� -��V�2<�_]��2�Em6dn��yܕC*s�dܼ����8 -��W�V�n�0��+<%�Z��0,i�=�HR�hP�(bM�I���wHɎ��l�Dj8��ᛝ�6 -V�4:g_&�����&g�|���t~4�{d�}���4�h5��-��Dcȼ �V��]rw�D-W�@���s�/����dc6�� ��9�B���x+K� �)�BO����qrVq7|�A��r%�,���˙m %c�Rs�����wiD��X�˸�^8i��46�8�A���x_p]B�>��޴N�g����I�KJ�S�} '�Y6��0�NC�,X����O6�o��n�r���4�}#�C\���6(��P�C�#`����o滦�t5&��TxEݖ��@�9޽���D@:0}�B�&P_�����rGHt")����$�wB�9�������cv>���ߜY��7��H\ה -���v�IEJ���֊�89����(Y�}��(�[tP0B�SV�����b�7Jढ़XI��j�'|Z7k�ʱ�����\�6�$ -.�����>Is��!#��,'<�3���0����)����f� \�cx�w�3O\E��(X^��q��KX,���q��3I-���'!ˣ��vf%�p�9��*]�]7��~��k�T�6k�fa�0��:�uL��LǞ����}�>q��9���@���ȍ�iJ�H�\9�eIԗd�L{SS�� �Q��i2�|�T�e����Z]w�:}�_��{k�괳jgQĖUo�v��k� �&�����T�_�sdž7�C�9gD.�?-c�Œ����p҈$�=c �� ����E#΁4Nx�X�~k4��q�H#�t�`��,4�<�����H����g���_�i�J����ӑ��|�P��['��4�h8�i����7V��N� I~��hlLUۋ��MD�o������Dli@��Ώ���iUHlk@��+���4�:W��VTN�m�=w��KC��7N�o��Դ��?��=�E�>v߁�x����N|Z7Ν]V�����;�*��ZS�zpd˼��t�Kō=hV�ou�2�M��Ӂ3, �4���‡��AI�IK�,o4���������V1x���qK�'�Z��z�L��n0uܾ������VQخ�AŃf��L�"�L*SC��9�Ƈjr�����K�ҿ"W|��N�mJ�x���s��5�&�4h�*>�Ӡ�G���ǃ�>!���wm��}�BʼngL��*>�Q��o�]�Ǻ}��g(�������-l�'N=�B�?p\s8�kb��.��n����C�~ �Z�ݺ1��>�Ҧ���s�ا�>\ =�v/ �v�^&�}=J2�6���]�e�?�����ݓ�ފ�%��l�7;�����6��I0 -����9�96�|A�pw�"��HLĺg��,&ꐓ$(�Z��d�y�H*M@H�i�a��P0gt ���Y�m�m�EƒM�3J��C��)b2"��&��0��t�� �1É:���+���q7����$�)�ꘂe2d�8�EYt�!%�8�p4��@�3.���� ���j�/�^^J� S�[:�X$q�,�:ϕ� �-���x�2� �R���c������)��q ����uWԻԘK�9 Qn$�?��r����\H|���U�(��#(��'ظ�yU�z���ܺ����'��_��.��ü|��f-.��e��ȇ�� �eQ+:X�$���9�l���ڷ $�Ҕ2��tZVA�Y>�c,�٦��H�V��H��h�A�:�\�Ŝ� �ʇ�+ߤ ��?m/ޥm����9K����5I�������,IB��r�%� -�P��Q�C����0�A7��i��D U_>�MB���k!��u���J̰*<�[Uu������l'![�k_�_9�Z�]*�3R�����U�DQ���򽏔��I�t*�_�45Ԉ���RE�)�����h�����U�k�0�_q�9���1�PZl0����%�U��t����ܤaKʚ�'��~wxt��-�0D�.W���3�$���|��᳂����X#H������/Y&Ѱ���o�9��(X���j����`�RU��5�F�U��0��ѕ���cE�t�F�|�|��< -�}NT���A��Bۈ -"k&�W� K��U��:��et]�O�Ma�y���� �ڀ�u�z�Uh�`] ( �!��v�T�&��؀�砕پj@n��6B����ލ�C������?k01L+��JԻ�u5���"���n� -�˶FǝT���~Н�����.kbN�tp�S�^��XClА�׶E�Z���FH�r���M'S�T�iZDzR�O���yg��� -�����P\{���+O�q�%ntWų�mg��j��ËUPl.0~ܟ��|� +H�|8]��i��c�K|�qh�8D�VB �o] -��#��|��m��Y7��x�;_^�5m����J��P8{������ 3�}- tz{.�I�ٛx\Λ��G��q���<����������(K_��o�X�o�0~�_a�}a��0M�UU��^��92�^�m�&?���$MRHCզf�����;�m���B�9X'�����#%��N������݇/�\_]��d�4V.����k�hdr���H����d��1�^��9�X�V?�ie -�62��(a��qӌI��G^��p.�H�~SS&R��B(&7]����c!� SŸ_Z�SD4ʒ /�A�1#���튈 -��Lٺ���N~���V.N:��������̽7A�{HJO:�e_��P�qw!�k��#��aSF��ԫ�C��|?&B��� ��u�j����H�cZ�%4������j����V��L����Ңu5 v�F(/���{&�����xS7;\?-�K �ۤO�M? -���'b���Zn�Y��.��@��B ĉ�h� ���E�4�j�@1ϙ���!�r���,s�-������p˪/T -�7�?�4}�`�>��ޙ����ks����W���Ι4xP� -GX��C8/w#p���o�h'�n{i�sR�&� -&���A�i]�r<8�����$u�R��M %�0��T�w��5Τd��g���&�ԠzF c���7�t�[�,���GU\��W]O�0}�WX~��Ș� M e��N��Þ"ǹi<\;������4iZ� -�.{�ݛ{�9��v�|>�d� -�|zr��P\�BM|:]�?�����%�����>M�˾z���4���X���$��>��� �St�s`�ӆŦ`3������#B:b�I��r�2t��$����� ��uL�����M8�1 z�+JfL�{��ֱ����\�ƌ���`�t��x��o��t\�W�Q\�n��a��.�g8v��ݟ5��=������؟���������u�/�XZ�Ep�]���U2lr�Q��a�G ���Χ ���t��˙�"R��O�<��hR��\Y4E��Fd�z�-1�ث �ܨ��L�x;-\g� l�ǂ^⬃"��@g�{%��~AA|9�|ZO �$�lb70�09�$�6�_n��TǕ� � 2F�Nà�<"�B�+%�Y«������ڐ����,�R�����+q��-�K1T��)a�@y��l��`ȵxM�DZ��hU��oUʋ+B[t��޽��n ���i�PLK)���x�˲F�A���z��[_��A�M����hK��3ɞ�u� �Ib�u�ºִ��Ng��%G�T�!^��t�뿢R��n� ߌ*͕�Ž�;|�2�ʾ�,�V���u�%if`[v�zRZ�gM�ƃ�I�0tzXf���4��� �0�Y ����TMo�0 ��W��x� ��ة@�� ��+-�$Ht����q�5CP�L���{�T�=w��� �R��/ -�ch������_ܭoJt:g�`�+e�㷢kMl���9���w�R�����F;Wk�m�cJ�����;�Q#Uj2��oJ�EGy�s�}Vw�?wc&�9�(�9��q�)е�i�J��eR�Y�����lk�,*��ف���#�i�.�n2eL6�� �8bli&�p�4�)t�a�F�D�'|��ils<+��fʨ�� -�v�-���������$�@�oB�/�djw� - g�oB���&�#,Nf8:�� �\��xc�k��G��a�!�� 㣲�2�ܓD{��S#1�'��[b4�Ǫ�x&�K��^�[��_���!�p/.OYLR�V����T���6ҎhpY�>Ic 4= �.�� -�!����)~�+�r�� ���V�n�0}߯���Ӓ�˪]A+T`q�U�O��8ċ�#�P���\��ݖ@�H�q��9g2���b�МHE���w�G��f܎Z_� tu���2���\Ռ@��iª�'eN���k �X�w!�A%q��˶&�q<%*�.��b���/U�4ddJ�V� !�0����>�7��]���\�o_����Ȯ[�M}��_�5 4�l�N�cHx��Ä;I��1��e�(�dL�����<[5Wp�n���v�5Ȱ� -����V[�V no�hڽ��ɐ�OwL�Ӽ��b`#����X�֠����}���w� ���㲎?���|���wvV��� y���i�؍�h����Ŏ1o��_q=�A�+;qW΢V���v�g ��?���c����MM�l9��:�Q����a4 �c�:���G�+i�a!WL��z�F[F����D�$Oy��^& �K�R�!ց�D��:]�,%��H��$:d�2��4���Xm"[�K��27L�7 n �4���2b��0V�����#~: ́�4�!�\"�� D#߼�]y6M���T5�/�`͜�شbE�A4R3��C�Im��S��'/p8I�c X��tH>�'���P�B�R����K�r)OG���6�|�� ��$j��M|2_� Y�G�u����($�Lm�8Q�c늓Eᳩ'�i�"����P>#I(���0B�]r�;��E�0�~'���W�_���;��uܷ}�D���)�%�.��{�G2P���_�����N݁.�ՕMo�0 ����.;-�n�`��zPl�B����, ��&�~�G�x���:�`��h��C�ί���} k -�q�Ae+2������'�۫\i�� ��ct���WW��7c��o�.���CvxC:���GF��`pRa!�E�w{�S�46hb�����xc��1�Tք�[Ŗ�����&@��&U,�^�B��Լ�(PI���-5���H}��p�ʪR[u�����"�e7��y�ABB,�m.��b��c� ēc-��꼗g�"r'= ��p�q١�'P�<��s�q��l�{PC�/�����`�ĵ���w5��5� W�@�0�3�w�T{�(�`�>���e_�l$�+�Z�iW�:�c��0�߳j=�{\��/��V���T -ݛ_̿T��!��Y�1����j� Z��c7O��u�y�D:�i-^�������K���L�?+褦�x�0�)����W�O�0~篰�>{����R�R�v��T9ε�p��vJ������?�&� * $,q_��:�u d�.v�Z_ɦv/ge{-��F5��7��+��.RF@���R��X�a�*l��TxAJ��G��(�1w ��ڐ��J � ��䓦�EDl�~����Wj������N�! C��q-��Xm^� ��9�,�JQ٢3���m�4�D���Pa�l�r���o��H��0�)t�XG�H�XK-V�T`�R]|��f-�j��3�6�+��L�0έ�y%5R�a;��%�0F2����!��>y�~��i�f �x�( *x� I��MP(�0fk�P>Q o�k{��)����8����#l9t����X����/�L��B���)� ��"��#�z�Ƒ7����eе�F^ϫW�\r?��4a��s��ﭥR�v�7�����d��޿���<�Ț���N4����+�O��`M51����2��O��eg�rLrf��`4��w�o�U�G��$��X�7��{�$�{ҷ��~�vכ͜���Ԟ�i��E���w�w�*�M���3���6��];��̙�_ks��q�>'�d���j�~D\�#g����p��Oɚ>���2E�\�W\x�E�M��Z *B��f8�5�<�.�8,���@zM[z��P����3�<��@-����F0@��Xw*4 �i̬��|�Xq�J�K���F�/�Ai���J���M�!�V��Eb�STE"Gl���֌6�I�.��i��jދ�q�@acap�;�D<+/��:'9=ܫO�Q�ݓ�i�a�6=aϛ�� {*NJL?��o�� -�=I���/��S�y9��2I����:�K���T:�~L+^@�k?���/�.E�Ztig]#o!}��"��m�߭nQr���j ��z>�H����-A)x!�-ݴjU -�t�En�V�‘�.` �`XoQ���5n��B�-����Jۂ�Yy�O7�������M�Wn!���Y����R�N�0��+V{��BI{Np@���i �m�7U��8I(��p܇gfg\����q���x��F oBm��ķׇ�[��jQ�S���S�[�x�T��q�_z�� 4�s%�P��H�D�����d�Z-�6ԇ!�c`A�U�FJl�K��D�5s���V�Y�K�]���n�vǍ:���T.���� �8Ѓ m9�Gքj^c��=H��,%;r������v!>��Ӂ��H�� -�>O���4�&�P��G�׳KD��$~��.��Bpz��~�!y������C����#I���`�M�R�̺�sHG�tQH��O� -5ڹ��[�n�6}߯ ��ص�m�(�8 �I -�4��m� Z�l$R );���M;�o�M�K�4gΙ��pH_|~N4.���c�� YD�|}���{�>_��,R���3)�?�]���fYĞ;dW�0@q�$���B������D�C�����w]�4K *��(�צV/��!�Bb*���h<�r; ��?G���c��8�Շ?)�[ ' ��z1��e�*a -nz�I�>��[�k~�_��_9���]���鷖��N�;q�o�ԇ��� ƉP�s"Ȅ$D.{A�O�*@��D^ReD%�=5��9ɴo����od/�S���2��A���&���-g �*��~��@(�RB�I$Zp"�ӄ�B���v5y<.�O�F1�};(&'ň�� -�a��r��w9ȜSg�9#��8�s���$Ksb>��� 3�+=�d�!����6�4�� %��$:ϱ�9�39i�1~r��T�`��7���~��{E�ծ>�"v��'�%�]�^�d -r�����(���@(D��8M�Y�j�ql���0u��D���VXJo� s���Qn3,�l�3ѽy�� k�4��� 鞳 �$�Ǫ.-��R��s�� �Hh��hS�n:�E��?ut�-���1W,��F@��P�15�!}#�kݪ���%Zx(R��}=K����� �hNd���r�k�3��̰G�P���;�;<�@Z�v&�`��! x�+��g]�[J�h��Ђ�֙�����U��'+h��P�����%�3@�:8W��wV 労���+��5'-z�"~��m��R����� -]*���}�We��L�HL��[S��J=Z�$����8y����hSzW6J��+��)�^f��Zƭ���A�wW۾�u�r4-���B�zr���q��Q��FzL�5���N{�%.�Y�x[��a��$�S+Τ��5�F3���Z��'�$��[wEh�_�[,���ا��hd0xNEV��2��(����3��g�gp���|�c���':+Gm���1���`qz|0��nܤ�a�P˾h�z�Ӽ�T�%f�r�2�ژ��~���~�3,|�X��O��L���mM���_�8���s�|7|��&U��Y��଺�U��O�ݰ�GV6�[�����Ѳ�����zP�3���;Dc�٪�ET(�2@��2e��S�kK]Z�8H�B�!�!�I�R���)�7 ��-Y}+��X��-�TJ�z+����A�#wc&d�m�Lo���I�S�H���V+�jJ��n�O ���a$���x;��oa�'gQ��g�(���W��m ��C��8|� ���[U�A-_m4�D��.����A7��oWB���a-t:�c7�k�����[O�b��`jk�̝����������^X�ԋ9{ҧfk��!��q�J�������UۧJ/N����� �c�FVOyR{��q�8��� -��X@�>�n�ݎ�/����oՖMs! ���΍��u:^��=5�I?����4,0���W �#�7���N/� -�}$ /��9:/�.ٻ�[�����)ُ���3�� ���k_�i�CQ�h`��6���Px'43�Jv��B��[76���[�� ,Y�ێ���� -[��߷6n'MqE�m�.[w� �_��d W��s�e%� ˒�Y����hm� ��0��yX�F���2�6 ��N�N8�T(��@�"����3�k����j��>;s�a�~��\I <lm�`��ɀ��F1�m�"�>Ď�O�}7E�{��E�����ͺ���2������E�9?O� H6W+��4^���o�k��iKK��F��yil�#��[{�o�B�t�PItw��\ro�_�%$�,Y+� ��>���5��|8����m��ξqa�;ze.SԞ�o��ϳ��_B��H�9Dne�Z�6��aYo0|D�S~�[�����%$�}��9��m��Ո�i�;�4�Ft3��|Φ~�� = I������Lڵ�T�i�p^�svt�(`}�>q+<��?������/�����=���n�97�d�q�����z�Y�KR��B���Nn(�u"�*����%��U�%��8ge#�!�A�I���' -0��S��RF� R ]̧�C����@�J�S��Aoug��R����Ζ��sDŽ]d@��Ez��G37�hѸ��G.�N�0hY~c��\��+�$�J� -y�&o���30@���Y� �&��'��j�w���L'� -�;�:y�!�2��YB17�hJ�'���Ҩ#[�hߋ�j���#�XdF�Y,aʣ����E��l' �������� '�O���=f � ex��m��2�F-��π�z\��K}�d�����0���/͖�r�0��y -��-no�CJ:�0����yk*KI���]�6�Ĵ!uq/ �׻��ڕvv��%ق�B��~�|�׉P�1��������͌Kf-Acec�9WL�W�"+��(p�5����2�O�J�}.v�wN�b9؂q�i�跛�2y!!�l�R��� -_�o������<�)��[a�FH��1-ʍ<��oHY)�\��YԬ�w�o��?�e0�܈‹&\��b�ĀD��e@�AyG��,�LJ�hx��<�>� .rp�N&���gQY�z˚w��R+dd�LPTC�� �a�)�^x���FZp�Q�� ���"9�� ��V(���v�<���� l�*�ˈ�����M���3��N -Q��G�d�7���N�ó�;J�B�k:j%��î���i[TQ�$�-�_���_���1T���ݪ�N��=���p�t�O^�>�tf ۿ��z���3"Uu+ N�����1�!��X���`���,i �T���pi�x���a��Px܌�wGB�y�����@�.�)o��~�����GF���a�J���Q��T$j��ٲ�d}�,������[�3CE8��Lt�ϒ�mP��8�1�����Aڀ���_���2�J�n���q��3��ݙ�o�0���WXy^a��i�Ut�4uн"ǹ�W��l�������%����sw�o�����Y,���J��i�G@2r9������7�\�?����,��M�M�7��j$�$T���4�y$J��>D�E�W�t="i &� |/o<;�����1HkTz�v4�/���Ri��׃Q�w��u.���G�T��{�����ܹ����B����S���'8�K c(l����V�t�Z1���o��|h9� -��)7<��۹�%i 8[�M�mKdB'EkCx!�y��D��QI�{7萨�� �2�� ��()��V�,����F�$�Z�K�"�[hlB�)�R�4���}�~s����A".�(GT� ��i:���� -�h����{�*>O��j��k�V��ђ`�!�E���zᛸ��Ϡ�V�P.b�Ev������.e�y5�4�$Ƅ����m����<�U�����LΔ~*�lG(�jط�}G�%p8�X8�B� �@-��=.(a��V���SBl,��񹜪���_;����J�l�r�F�J~�T�/�u���%#4 �]ts��0�Q���U�L��k�k�ؽ!��-�����(o�͙�o� ���W ?���6Mq�)��IS7%�^#��5+p���;��]�4�S� �����qg2>[��,A�d}}�H�2.o��f~y�)"g���LPc�&� -k��q��QUT�Z�$��h��"��� `缬��~I�ՔوHZ��(�$j�~���1/+%Hk?�����'J(S��h�c��,�r* 6��� n�ITթ����g3�i-�ę�q�j�)� -�l���L�*��&�5$*'�����hv��X��Z��s_�4�pI��PV�XE�5�0j��Z���8n([`[��^,�P!i�7�O^B�%ŷgu �^�7���uq�[kI�B!Kų����߂��M�a�T��v�<�Ύ����C�@�T��2������{�6��y��sC*ť�`JU-=�r��B��������k�^n-8��x�[�Pn��]��ID� �k�Y��b����beA������f�v�b��?H�}: J�3��k�� Ɩ���`�Cj���|�oS� ��V��Tc&�Wς ��᪹�2�ځN�w�5doX���F�t7՚�k�9�̗�0�s��"�U��j��'�V(��]�dgs�Y�m���8�(�xe��3����T@��l,��FĐAZP.H'�;C����0:� �m�Rb_t.7�C8� 4�}!1(�i���1��N>��* �D��z ��]�M��^���a���=�%���v�mP��he�s&��NĠh7�9�ĐK�F�^����9�o"y����9� 0W�A����vR�\,��5��+�y\�>�y��8��Ϝ��U�n�0 ��)a�������Sѝ Y�cm�$Ht��h;��-h��I�~H���~k`�!jg3q��&�r���L�z]}}�K��1ۘ���O����/,R�P��d�K����5Z �\`��� -3���w�r6R�'�oocN���K*�D)MD�$i5�;u���&�΍n�i+�Pт0L�Tn��Ӈm� -ڷ�@���0~ �$�� .��)����:X��3����/MN!S/�#dn&�\Gc� y�����1�C@ץLLfv�irdg�`��#$=�Q��=_i��m��X���0�qWv UVK���T��v3u�;ox����7�hfgrb�{퐭����?�����m��0 �-� ~U��6�7�'�Q�� ��o���q6�_��a'M}��ӻ��,p�o1G�x�d��z��{#/~����ՙ���.��_ݙ�o�8������t�p��vO�t�]!uw+H�g(�&v�6���8_���ѐ^�&83��x��q��2����R����G��`����<����/7Z̧Z,tۙ��l�S#���\6��V�!ӹ﷝!L}`e~3��C @��Aۉ^t�BF?&Նp|Ѵ_0)���$��������Cԟ�++Gz�M|ɞ�G|����U�c�0��H���>��LulQx���nM���O�r���w�~/������_��O���O'W��:r�������U�~t�2}E#L@%ڢ�� gmgJ}�� ����ܬ�N8���e&���}s#0PI��<4S<�b�0`���G�վ�������,P��]�w���N��G�!1x<��9�N�?+51|7�rA�t�a���5��E�kf+0s%�U���{�C��lzCZŹ�d�R0�y�w�x9� �L�/�N�����/��#.nIVn�9#YHٲk;� �*�%}�ݡ2w��M�#��# - -��+�b�u��C��DJ?�p �\*G�. ����� �b�ql ��y�r�P�e�9��q Zp�h� ���m)�o|��K;��K���Hꋩ5��D�B��L�u�b���g6~|���fh���Q�����K��ز��]8�(>2�XB�ao��~���?՘MS�0����_z)q{�t��S))WF��XE�<��ߕ��B&�� ����>���J��Y!a�� -�F����Pq� -�8�~����Dpzr4�Y d��(ʝ+��1= ʼL�l����������3��ј7̰��cK�qU�۝ EQJ,P9�l�͋)�E�(�H����o�1'�(ʘ��8V$B -7E�$������36��D��øy��<��?U���r#J/�.ȍ�k�t.G(��"K�������:���j��9�E��ix6�ᠢ���Eø¬�]�Ӛ�II�_@,��Y5�ێ�L(&�w�_�ȟ,� ��@XZ0m�C�nb�yIR��z<ϴ�Ǚ:�Z�����1o���t�Bs9���������A�]����V[p׌�L��0��S-�-$s�H��o�Nw���K����P����&*l�t��E�6���̉��j��Ri;��1�.=�8�0^p���Y���f�����)���_��\1l� ��7�}��b^X��]x�C��;�P޼pȅr���uQ�J�Ţٶ��8�!M�=�����p0֫v����n�7��g�ܝ�C��Ka��������~b��T�M��81�C��O3��s��?X൮�ڷuK��k-|���i~���pIX� }�\7ڊ޶v�V62��Qn�nL���:4Z���5^�ږ1g���|�E���&�]Nž��� ��(���qдW� :��D�~��x����#o��w��M��� 3[-)�U����z�w�Wjnuo�t��=Җ����g}6�/���h�\���ms��3�5�@��o���Ԣ���� q��/�i#i�7^N�U�ցA�����Z�a�'�W�O�0~篰���ܪz[�[hG�xq�;�] -�z^�,�j��[�5�/��Ͻ�A��Z֘����RƋؖj��G0W������~#�L�' �.�Eq��b/��5���d��J�Y���8�C����7N��1��4�%����Û��@nI,�����͈�O�u|���F��C;w��[�.������a �IU�=J�(�ܭ�fјߞ\�*>�f�ɱ�l��4�P�;f�Ḁ�,K�CP�z�7�i| ~�d��4�K�@#�x�8����`��y���ݧ䷌��@7]Ł/�x[9n����i�G� ���K}TF##vr�s\��TG��*|�ft��Œ1O�0�������aC(N%��̕�8�TǶr���{��V����|��O�b}�<�]����� � 6��5�m��w�rQXo�!�klEҽRyZ�6�� -N�������d��19�`:��X�q.$��蜴����?g?S��Ɗ��xΆ,FȞ�=1U�I��Py�P0���s@m����������`c������5r:���ը�{'C@rw�U���+���B�L�x�y��t$Tc�%�4�ɸ�J|�>��_Cnb�������^*�t��'Ŗ�n�0 ��} -A�5�n���l�����a�@��X�, -�&o?�r�4m�eȒK�4���h��ec�=�h������4��-ryw���W����*F��.�&򟲌���}�ˑ�b�RT����AeA�� ��. \' �S D�4�2�s'B�M�-4�(>�ư#� YGMa��~�����\V�F�M4���V��ma����*�Z�8�<���p uaQ�Na�u0�3.46 E��aA����QFb��DgW��J��(6�ӥ�T�8����$�C0�D�rٶ�q�0b����sm�1�*"��Ӏ�Ӆ�8���Ђ<�/j�J am��P���g�h���;����]���m����fxa'�\)�0���ñ8�*0� �*K�X� �S�k�:��c7�q�՛�� K����&�=�ٶ����B��e�b?ۯ��tc֟m�,���[3��� ��-��ԭ� -|�$�����*�E}�y������n� ;R?�Ǧ��}; L�{l���,<���q��s����3ݢH��z%��w��p��WM��0�ﯰ|/�m{�*�*Q��RE�3r��5�e;���;�C�aaK��-��3���3�/֒�`��*�w�[J@q� -��鯟�w�(��܌�d�V.�+���(��ȬL���9�)YdR�tK(i�#;�;��#;�r~lƱkS�y�G K��q��(��^�f� '!����d�e���V�����xlU���T6�X`# -���]57-�̪j�Ez'� ��5��DӀ�2��%I6`�.�� t��HT��}�����,`���Z�u -��03'�a�[u-$[�k)3u�z�u!�ګ6P�J��v�9��+�+x,Wv2�Vx�ֲ���CHK����A�<������X�U��-�Y��z+۰<��㪸�c]���-8��>d -l3W�h�G -g�����3��@m�ǔg�t=���!�'w�;`���ݞ�O�܄�^�(N�3�Ja��X]J E��@�F����8��4���G\M�C �Z���c�A����a�9�9�#l��U�PP ��ڻ�� OB�oAΤ �s�Y�$���VM��0���h��PҕX��ʱ��Y�c������L>��e)��E�[�ͼy�r�xX"'G�R/'/`0d]�W��w�_)��^���@�C��"���($��E����Eb�`�z_�Ϙ�e�o�`=*����J �O)� ��PH�[��Ǭ���]˓6�R3퓔MYgg���%W;��R������&�k"m,�ړ����a�p�����b�A�H��~����@�0d�c��6�d���N�m ��r���2Ւ�Uň�� ���e� -��8����0P�g�"nφ�B����_q��{>fU���67�5E�KRϤ��]����b� ������PNn���!�M-� �c#�'p���7�8�N��e1�~ �C���ػ�oȃ�+[�1�e&�"{rst;2��$�@΀I��ݍ 1%=�kxhF8��b�b��q��fL�Qn�ƭ��Ϧ W��{�60�}�{�[����!bjc$��#�G+����`�p �r*?��͖�n�0��} -����[UA��j{j��Mϕ1�0Zc#{ ��k l��l��Hp���?�gD�j -�j��N���7�@K���&�m�p{��jyK%�c^�]�s��>�|�(�23�BE�J�6�R �4a� %�̜iQ�+���w� ����^s���|xyE -�W�Nl�3G�P&|#��a�STH����H�,��2؈J�R��q4D�;#Se��. b'-L��Q��90|��D��ZXF��td���|���qԱ����11Spp -5]J�A5 M[�u�v9X`��N8&-�����PO����yn;)7Yϸz�N�������3�*U�. ���5� -�E�G����V��Ob<[��Tn&�ܚ�H�@�6�,0 -l�����&f�0�F=���ç��_���=a�؏e��^�&΃e܍z's�N1)�T�ɸ�����Eq�u��Y[o�8~�_ae�]i5��Y�F]`�i3��\FڧȄx��������I��ҁ*�y���>��|���yq2�� �V9��P���4�A���'�|n����jMp�� ojL|Y��O�\T��V�G� � �׾���蘆���E�C�!��!��qD�d#�m�aa�S��#3�ِqf� /N����4���&�4*�W�U�� �\����m֡b�A'I(#��4�{�`Q�N��hʭ߯��?� �?�ƫ��̨"f��L���� u�j��sg�'��(��TO��0JrM�Hf*Gd*�ni#B�m���d�I��QQ��HX���-����-"�d�t�L�p�����󟳡�j�5=a�Z)~����P�@$� �zB�9]kG� ��8���}w��LL��`,UDO����$3��T��1*�KpN!|<��#�)�⌰~��e���q���?1 -`� o�q��Z������T> ��q8%c�x��(�i-�s+(m��l5�S%� -�!3s����F������;��� Qat�|Y��LBQ��Zk\���Q�(�21����E�Τ�K�9��Y�j�,���oe�&����%v>m���I,�y�Bɰ0���f��|"^Np�]�� �JB#s��`��#t�ߜ?d?G����<�q1� ���_B��cd#�e�P��!12G�mvpn�� 3,]/C��*t۬��t��tƭ=!å�1�IŽ�ٳ�|ܖ��� �::�S&%A/RhF�M�H�7��Q�M��� Zf��F��}.����i��d�8��n�w��s�&Pz�}�Ş��'9���B����zfw��V���U5���qlK�:V���WJȲt �����RX��� ���jC|�+�N���5�]�mլ�}��%2*��~��v�7�!�s9Oo'6�ob�lǯ������׭�8�/�����U�4�9�tN��A�(7��܎���E��p�����^s3K�`%MՔn�X�ԓ�l��������� �gXY�o��\ ���+� 7���;��e]��+��/��x�ƺ@�� �Q�g�Z�b)�f��.�Ѽ�EC�Q1�1r�^�R���6��ҽ���Ta��9���+�1:(W0��.H����*x����f+�p�@*�!����!��I�6�3�"�?Q2�t�}�����/*�S�^���.Q��LQ�k@��4Km.B�����B -=� p-=(B�G=_ጶA���<��\�r�f�n?v�y�*v������V���Ԣ8 ���[�G����*�=a�]�'±�ȭeap�M������O���p��$FwԷ8��`xw��X�S!�Dr;���8 �q���� ��E9�q�L����9��]���w� �Ymo�6��_qЀaָE� ���I],@�fv�٠��ͅ"5����#)ɉ��ԑ�a��Bݑ�=w|��~����X�U/y{�&T\gBMz�_7�_�����W].��@���%S���ݝ�"��3��c O`\J�K�~�%�X��`{I�Y�;���MuV�IK��9��%�R� 㮗�����u� ��� +R!�[���L��΅b��퓇L�Tj~o��܈�т��>��,�)��=V -y1e*�h�N�AWnQ�)k-�w��C_݂Z�CS�/��m:�^N҅�1��gѼP.Z�v"R�A�4� ڶ��o~����F�N�yJ��cȘc)�F��;�`F �8� ~���h�hS�P\�τH0�k�� G�hV�9�Ul�J�jP��(��]/�ӈ]�ؒ{�s�������N����g����g� ����*�ӿi���P����5�����޹8��A�H�Q]l|<�)��A~����c4�,���-.�z���=�a�A�������.�f�'�Ғ)�p��5m*V�1q��d`5��sl�$���bę�)���@�hi�s�63�Cނf&���T8J��S�����]j)Y���%�sT��M�c���`�����������Ҋ��������fbѮL���9�{��W%���t�U���x�!9b����J��~�I�/p��z~Ʈ�Ad~l���)���3rq�Yr~�!�-#u\��y��&��6�b������� �1Y>=��Dk���(��g�85Y���Dj��)�,Hp1��H��D�Qj~�T.�E��7F�K���f7��h����x��3jG���1H��ghrx�����Ut�j"���ù��E���ٖ��ѹ -�=��t����}�>�c���apC�����d�fY� ?����t�0N��Ƚ���R��Q9F)�Sf0�&�_��yF�(~[9C�Œ��N���鉢�%���)�4}��J����"9�5����xZ�~��T�)�t)3�[`Q�ؤ��.�j�{ȅ����e��i�7�ӎ��X���|9��-5^�ArӺj��n�#�4>���o���.߰ϵ�>+����8��h �}x i�l�u�Z���Q�e&U��M`>����y8���:v���e�d�z�A����B��#))���� F��W�bu�� ��+����ӥ���Yi�&q�T��y�Rb��nh�v� �FA;:W�_?�[�ϷH�c^�H�6X��x��>({�.4�{����@��Q�T���fk��ƒ���.�,A%��˕��; �6��!�6Rޡ���,�Op��VMs�0��W��b. c�3d�K���redy�dIH�4����$N - -��YҾ}�ޮ��.s Q;[����*Wk{Y��/pZ������X���e�����bd����ikL!�?O4��sjH�� F/�_\�/O�i���n�m�I��U� b*MD�$i�Y�uԕ6����metJA[i�'Rt�_;U�~���� -����a�a�|G��x�0�AK0�A�ʠ�6�R,�ҧ̝3�<��Q{����{��Kd��e�)�7�zW}G�U�)D�Xo oB�����vt -����>V����Y��Ҥ8�Оf�"3<��NF(��ވ�1d�8�4�eyDfRe8��� ���1����km=�"�r�C�$"u�j��>\L&O�g�� -���;n�@����8�C��?aog� T-����{P~���ckXqX5My��e��uw���%�?tI�3���N�6���c8?�3�� Ņ����cݘs�Go�_;斧C��l�T<�63�Rn͝+M3m��Km��P@Y�fy�\�8f��>�5rhn ���!���N#�U<�����gm�u�/͗]o�0���+,߯���Z��MQ@�Tjo�'��`d;i���!�k�E��rN~��kc�W/K�DH�s^�� y�3��m8�ܾ� -���Y?eXJ��siÅR�7���y�(2�r�eI�B0[1f��a A��D8%6���y@?�T8W�߉��5f+m����4a<�Q�ڱ��M�ch�ȜhաV;�o�� �Mh��|��hE �}�@D���t�a��`��{��ni�(��(���DSC�hJ952��5%���ñ�ޘRϮ�!r�q0��SJ�I�t�L���?2�v���9���J�x���1��\��#!�+��t&�0�+��×^"�-�S�X�RI߿ -|jQoGX���^I��O�L�uj!W��*�A-S7�km��7I��UPAN�0����� !;B�q��͆Ful�ޔ��� I��3����=N�r�����=����??v��z��ٜ�,�l� _�*���؆s�YTN�Ѝ�|�ڽ�Io�������h ->KIB�[��W�6�eI v�eF�b��v��nAW�"�j\�� �gJ}���!Nzo��p ���J��˘<�%�T�з�L��:�T� /'�va��撸�TJ敛�˙���A��VJ�j���QMO!��+^�x��͘�6��x��GzlXx[P����K�v[�z��y�̼�j��,�0&�]�nF� �I��[���ezu�`2���)A~�R�4Q��<�Q�A���!�%�vim͞g�{C� ��0!�f=8�_TһDq)��݃�|�1M> I5k�M� � #�'�m 3��^��p;�$� ���P��P z+��7�r|����mBv��F��*��� -"fۄ{���ٗ/jk�l"�s�8�z��6��k֙5�Nم Z�J�~�P��� �i�vj �4 ��E<���FhJ� t/�M!����7R�~���̌"��|�߬�k�V7�ՖKS�0������L��N� - J:Mnj,��[�h��}�v�� ����J�>�}9<�.rX�#mM$> > -@�l��U$fӓ� �B�K"�dž"�y_~ �eV&�z`�䔀���HL.O+�`d�TJ��h����=��@��d}'�d�����1y'��D*sB��j#��ȼ�jM�+�*έZ�b}���.k��lQ6�FIB ��R�y��W΀�)ٕ��Iw�5��t�G���v�6�n����a������o㟨��mCetϟ6�\^�?��Ʒ� ��g�[��W�DVi鑱��`�}�B��]C��l0���3�^q�O� ���(1-����I&K��j;��&[�j��FG�ܕ�r���<�o������' ������N ���?Q'� |��H�vPWW�P�K�+��?C�ܯ�&N�V Lݭ�]�oZf}dW��\K�݄�o:�=�ku6����G���w��ď�;ս6�S���M��oh�>��X�h�FX���mc��R��m�Z=з�GИ�o_����?�^i����6����x�����Y�n�0}�W\內-��P;41MBP�!�G׾IM;�ݮ�빉�ұ��.�����d�^�sf�x���B���F�7�@͍�:&_�n��&���l�s�e�����]��袜�¬.4��Y�@�Pj����\KW*���H@�]�8�0x�����@?3b��{j+6u�2�IƔ��g^��0�����B",a�T>�j��������p�g��ʁ74���%�jz\Z�����c�� -���_X����H�}6H���Ң<���6+3u?�����b��4��\�7���}�5z�gjd��Q �}@�A���|0��D�J��zA҉�(^���e�e���bQ�F���"6���T-�50����⥖@-F�ͣ:B����@�� -��Ɵ�6��-���r����\�s�f�0B���jv̙�~u<LJ>5<�U���äW~(�2*�#�w -�$��y��%�G�� -V/�L��{��n ��w�K%re�L��CD:Pa�|� ;�"�TC���Rk#5���F��sm#�FA�'Y�������6ՙ5E=�'��#�1�v���\�3��\�e�?qw3߲�'~�-��\�W'��)��G������y*�q�8��X�YT-�;�qN��r[rȢ��vo���:�3.Q�d㺺D"k��!���m��b��\Ew�^n�%V(B�VN���24���ǃ��Y� �Љ�Cd��t+6���PDk�:� Rni�q�'�غd+�����֙�Cԫ�E��U�v�U��ww��ݝ�N�����z��#g�S�L:2O��D�g��O/Ք1O�0������ְ!��[7���\ZS�g�ג�{ܤMHL������?Y�Mm�!r���� -@��4n����rz/`1�dڪ!mv1[f� eR3��%53�,c�����X=/��ZN��Ҙ�^\�� ���T����&����\T�FY�у��S���1)�$]Xһ^0�`<�{���wy��!����1N��@@���4ʁLy�2�9<�*�y�i�FuE���hVK��h*�P���h�,)��F�#Eg�'�z������{�7���B^���_G��W$������a?>���\ :4�cݟb�3��k�i�#3�_��Ν�VMk�0��W ������B�7� �@�i�1��x-���4���;���&YR�Mo��fޛ����faa�!�J�1��Nymܼ�~��/\M� -ee���],EC�~�s���i��d)�A ��֖��n6�8���J����� �P�E -KE>�����Y�J**E-mD�$5��q�VȐګ�z��7� �*���!(�hލ�:���S� d�b��fT�]2`"P�)1�5H��/� �M�� �\ d��֕�0���'�l/��Fit���; kq*�$䜪���y�u����������,�28�m�"���"� �?V�he`B���]>A>( -�v ;���.�"}�"�+c�`�����sT�Z��~_/��u�0� 058O W�XYY$z*_�%���y�� �k<)O�oE�e7!NE���ހ�m#[<͹ݶ �.d7���yE�!ؿs�)>�������o ����ɸ�3��c��UZ��<,{��]��9�f�n'U7k��)�&���:A�f��+:倊;�_?9_���2��^�ջ؋����L��1��RAj�0������P$���ҳ"�kSY�&$���NR�z̎��IoOc�#�:�h�i��@�%?�O��o��a۬� �V�˱��R�ֹ�>�֑X���C_?�6E��� D;R�֑���� -@��}��z?1��+��`gC%�ʖw��m��IH�|r�����UW�̒\���}'[Iz�:�$G:�TX:�G�nZ��P"�9��c����~��"-��\B���4��� �s���tN�٢.������RЯ]�+m����/���|���n�0 ��} -B���m�C���a�B��X�, -�4o_�'��a؊u7�"?���}�=c��rgQ#�q�Z�����ı=�,��4�K�����`=�0���=I�<�a�)�%Xy�Ÿ w�����߹(�_�*��B�ݘK��0����Q.\` �BmWh�H��8�gҚul�vv���n�}uiH�J\ve;��7��3>_U��:��${}�*T\B-&�����28�>sɜzX�I��޼�huf��Ы3�~�,Ϡ���d�_���Z`�U� �8��������B����E,w�2�'Yɤ#�3/x�,�b�Y$��<��_�e�@ǭ0���+�}( -L��� �.�`���������[��U��IV���xtW��0K�{�'�"�'KuԿ5��-�v/���o�>�ȹ]F��Qb�(�O+���.(�Z�τ/��Ãr�D�Ah[�������2��=(ϴ: �n�WF+{A��>��5崭�׍���voH0�G*ZPx����/����Py�!U5��M'��Ot���ȼM��~��R���aj�*T�E,��g�+<�B��ݔ�\cǫ��Te�)����d9ʙ�‡C�%h u_FM���h�@������ o����c3~�2@�k�Qh�<(��Hk��m�n6�3�E)hD ���W���S�3��C���n�A��f�ۗ�K��Ɣ��+mN��S��EV}��fк�J�ś�x���[�kT�T�M����Fl�w?�>ڕ0]��)��i-}������?E��eQ�n� ��+Fs�ioUɡR�U�a'xP6�F.���.�z��}�4���>Q���4R�/��;�,wR���Q�ҁi��ZWB���;'ԗD-��wZg��� ��vv��M��*�#F�� �ZKo7��W ��$�"%=���H᠀ u -.9�ˆ"�$W���;��3�c���0��.��>j����۩������o @+�Ҷ�~�|�S��_��!-�aR41�g�� ۦU�vh1���T�1�����-`�C+$N��ek���x��qj9%���� ���qRT����Z�_+m�Y�����,��_�W� �n#��M�$�B��3Z�c :� B�mZYwv�4�ww� `t��~.ą�0�=r��6��A�B�6B\�x��u7��0��l_��1vަ�)�)T����]�ǭ�҈~Z���n"��c2����kײ��U��h�J��ф �6b[Z��ㄍG=ӳt����V�?^����""���G��/�k����i��іwɦ��_0g�y� � _,A[2½P� �A�) -DLT��O\ ��E ��nJ;k�l��鑘׏�U��E�X�ds�:`��ݏ�^�����>!t�1H�&�'��ƽ|:V�:������%�ʎ+�IӁ�Җw��׽�B� �����@��R���CR%~��!t���vܺ��vaU���j=CK�tΒ�@�m��,G����P���|�)�dˌvˌv��v�c���|r��y��3w�a�4�a���hH� _�K�L�JEF +�s:�Ԑ�*zk��><���o����Aa�� ���� ى���ip|4����.�*�C�VD��G��� -�ȰuQ�Uv�l'($C��+�l�WnC�'}&AO�<_���v��?HI���#�W|� �p\i{�����GT ��P�q�O�� 8J�3=̧n���-y���F*�N'�����{Ґ�{�L��� -����w�1�!E�E ��W����8_��4�X���?�gQ�.��p�~�j���}��>|��@�:*2�ܥ�^�MC���9�C39�븀FX��n���0�p|�̂@xѠP|u���ʼ���Oi�ǿ;�{`���(i�E�+��1=��j���'�*�.�����&�ž9[�S�Cߛ%K����a�Ul6��'h>� ��j3�v�� ��a�|)xE�v�p��7��][�Q�ޒ�C^J�+� �,�,fHЖ�~���:�+�'l�+x�*�GM�4j�x2�9�a�YӞ��(��h6�5��}��N�0 ��{ -�w�!�v�=q����i%��������?;�}�S쮝� �l�/�a{�@ބ��S��/��G�]�)��9�<���3s|RJ�6�c�[O�r2m�\����!��A���'�*V���9��C���KA�i�%��eBȬٚE��k7�(�&���1�!A�$Yf�8���3h�� sS�w2 m -�셼X8B�������K��\+�o�"�$1̓ �!Bc/?�C}K�R��Ӓ�d|������)����y��+�j�ra5�����j�0D�� -�{��V�� -�J i�Y�׵��+�uH����8��g5z�ك���{q��a)��G)-5J���yx�b]���&%�͘J�1���*B:�R�R�����}ov4`���-H�����R�↩Z �{��������a)L�8˥l�O��/���lQ#)���'���8�d� �� -K}�xR-�x�(���r��L��,L� �9i 7���} "�S�b�),�?�'�Vs�����1��u������.x�_���������UM��0���4pCU�B�� -�r� 1$�eOJ��3v>hE�ݲD�<{޼��q~{�ZأښB�^��F�J�o���y����M�Z�f -��u�q�r���ae������o�B�nw�kBFv�TX�!8����;��V�7YUd�KE��e�H IZ�a��l�(��*�UekՏ!� �kGL ��\����H�����,Hc/�g=,�k�d� -1�H�/�u�Y3��\=Y?I�F�U�537�zo����[]�l䒝�ɝ�,�E� -���o)���X�gq� ��#��9L�y68���־Wd'���9��3���_��a� ͗Mo�0 ����l��no��Z;l��C��,ӱ6E2$:M��G[v�&��lv�S����R��\��輲f]�_D�F�L��8�~7}���,�Zx<��qT��[�eQfvun�b�dy��8��O�pE�@_ -��(4��&g���Y;"�lF�-G RONHG��#�$H�u3WF�U+�Vfe����uz�TI�4�vQ6z�NC�/�Z�����8��������9-�RKu����M%�¼N诡��a�����U���7B��9�"9��b�����R�4�V+u�Z2 ���jP��_�E�̋���7�XMo�0 ��W���$�m�[�`�aX:�X�kU$CR���(�q�&+��6�C�J��G�I��j�T�F�ѓ���u��R/&ُ���mWӋ1W�9 c�&Y�}�n4�հ*+a�C�~�,ϠX)5��?g7��3�Jd��]�8N��x`0�/їFԏ��%�.�;o���`�a�3/y�,�fj� -@% ϕ�wi6�q++O97�*��'/����`P�����F*9[3�X��v|)5y�������j��_�<���W䑒E[��S�&�w���-U��O�Py���(w��y2��k�9��,r�R��Ұ������1%�K����F :]���p�0�=��wD7��Xqo���^�]e16�O��!���M� ��R���zAe � Y�$c�­ #M�N͚�W� Th��W>�5��Kp�)�^1zPYS��U�K ��n�U�� @x}��F>�]��R#������L �f��m�F"7�z�i�= -�GZC�|jc����q�?���*����H��Hg�rQ�3E�YT7'�c2�lX� |i;��3�ɵQ��R<�)�|��ejvkaukh� -��{�`>R��ѝ -��� ��J�#�_ �1g�I4}����3 @��:!�?���k,��Ay�כBZ�A��������}��av!�aN�;XF��ٍ^��U|NIv��s ���K;⮿�M��υ-5���Bwwwg�}��+��KM�����Fp�J"P���D�7&D_C^%_G�s{]Z�3龓�����x�#���/��N�S;O�0��+NީaC(iԑ��+ǹ4Ƕ�K��{.IZJl���{��ͷ�� �d�����Z:�K�ֹx~Z\� -��&��*%�a�rQ�;)���:�~;uH2E-�j����u�bJ�K��N5��Ҙ��8�M2�]��j�q?�Z�=���Ҕ�Jل)2z,+�=T C�^������t4��/h߄���.��SY��7dj9��I�]`uo�÷L�ReAE�@x�R�}���dS\��F�G8{�% �<��o0r��=��m_����FpmS�7_A��q}ӥw)0����29:����M�����m���P�|�������2/o�8��H��!}f�ר{Ƴ�ZQo�8~�_1�s��N+誥t��eWн�=!'�������쯿��m Z�@�$�Pl3��2��ǧT���J���;��2R1��~�����|�|׋3h�4�`am��ۥ�N��b�ԑh�FG$��`��ܩy��h2a?(�s��z)څ��&P�Xh�f�� 0�Y���� n�~���N-�L�+�d��(*z(��H�̒��4�:&)�� B́�D锹U���Ww��}��*�R��X,{@ m�j�WeCZ��;Dq��&v���BG�e���/�}���k�P�����PfI�H��c�����V�Pjw5��ׇ6"[]@\��RE� �G�3��J���J�V�!���m��7���M�V�r3N=O|��J���9��2���� X�z/�8�z��m�^�^sܰ%��T7��m�D^G��/�����i�/�n��i�`�G�˅Υt��V�IP�&r@�/ŝy��x�P'?w���T��X�!���2�u`�W�~��1?y�� u�<:yƮ�R*o�Eۧ)��܏C���nT��1�K�P���NM��CJ�fs�^]�Z����OP.Õׄ� i�ߞ0�� ��3��i� ����]�p��:i�njt�e���o��raR�v ��Ӊl��jB܎S �u�jZlj�ij���Z�Q}�V���V����YK&����x<2�:���O����4����j2��m��)��-��(��՝#��.�{�|U�IҨ�?�%E�������}OP�=���L�G�z]���?�WM��6��WB[`�no��vI��-H6�Ѡ��5X�Tɑ?��3CI���n6I��f��zo�ٛ]e�BD�����2���z�}����u��,^͌�1*>��<+����W���s��8�i &SEc�<�Q��.�2�t���Y�xtd�J�V�� -ų��A(���0��4y��J�w�����ӫHA�g��b�� �a���+�H�yV7+�R:m��}�ެ�7�R M��8e|U�o%��Tl3����4!p���DݗU�5�� �J������4�#e�d[�Շ�Զʎ�q�k�WsH||���Zn,SW�նDSr�!��hm~�ݽ�M��:�MG��@Mp��57{�mA���W�i[��h�(b~i�>��k�`��Gݽ� 8-�S�>oy���_��wmX�v�8�&�-�~�ZH�3�� L��+���)�KW�7)�����?��Q�}�Y�(;�Yp@m�9z�ց�ut���|r:��� PO�(Sg{��Ө���%�kj�m��/���5*��c�a�m�1�Հ��Ɓ �`>E)�^t����ெ��g�^LI�V:��8Ҥ��,s䞰{�hl��_�S\�+p�0�$��K2�x=�����;�V�]�8�x��wiv�9$���8IX���78��F�Y~�$�e �PpW��oR�v��e����YFAd�/h��\y�0z*פ����xR����WG�H>�5\��m�j? K<��A�_�R0z)o#>���K���c򜧛\/�">~���'����j�'i��� g7G�!M�"��{�H����=.}E�Wۥ��E��,��0��� ���8*FF�ˆr�uG��[����𣅥 ²��{gt��!~ -u�2ƪ�Y>�X��h�܎���'�3^?ϳ�4�%\|�X]o�0}߯��O[�7ɦi0�bC�[��7�����t����I����k膴��v�s?ι�N��I!��Z����qD@1ͅ�я��÷9;=H���\�l��+��1���Q���H���a�+)��Z��ۯ_>J(@��(Z�-)�4�,:= $ �~h�e7�z�h&��_���.��u�bN�f�`0����e.�r*-�XG�`��XX� )�4��*��G"E[��3��5ˤf�ꡟˌ(&�0]����W'������Jܑ�h��~s��:��i�UF7-1���}������ �A2��FDw��Mp��@i��\��IA�g��x5�]� �� �@��{a���@�y�HP`eZ�N�9�&|t�ĭbҋ�I���0��F�7�(����*;�^�(��s�jU������\�j���Z�ۧj�TVУ f�뱘I\3� �#!��N������ܳ�4�5T�k�/����b�����5{�܁���6�@v�;ɨ� ܹm�b�W}.$�J������;ş9fI�Bm��ƒ�z���|�u1� b훟K��ߵ։��P��os/�ft�v�.?�� oԆCg��y uq#���p�fWmo�{�X��v�Zd $�`�� X�A˥���Ɗ4�*|�o�s��}�F����*E��ѱ_�>A�R�Wh{MZ#ӕ��\��R���o$ڝ\Ca���7�FW���dy���n�1f_L� Z�9?���隴/�麬�?Zv��/~[�u�68}]��3Qmx"�kr����ރ�0�e����j�kR/U]G���8T���X�o�0����|/�n�DZM�&MZ��nRo��'�l����B �9pA�q���/��ORIƠ�P�O?�>QW�Ȇ>�����J��n�\2c.ΌOk󯞇�^�䑚�2��ќ���ҧ/"�%�>��iA3�4%K�䌃O���Uw7����2kf랁p �h�6�xe�l���ӸDc0JXh�f��4f�%�2+�|8F�B -;�i^�R8:"c�^�bc�H�P*>��n �"�(!�*��=��:3�&@fT�*�壺\E�4G���@T������{�uzC���npI��o��M�mh��AK f.%�C|d�[�x@obM�S��&B�d[�#�^��9K����������@�"nu�DkN�D��(����2ڜ�ܘIqvne�m�:aH��j�*C^�Si�7�s��-I�w �9����YF�*C/�'�TC ����gsUt$���<�j�*�YY2�Q,p#�@��вMH��G٥����E;��G�9>*&GA����m��X}��',,��⎒|���ɇ^�ݜ����l>&ѻ�G�o����+��� ���tWT���U�$J}"��(�jw�fhҟ_���~��*��i�IzѤ�֯���3�35̴fӝSG�߁N�3�Z��Vʞ8�ЎAyJwÅ�"�=�� �����ද��M�Υ�5�#o�l��=�3|!��֬xX�R�����$nj� -�v�U!?�Y|Yv�v��X�n�6}߯�����(l/Yl������͠�QĆ"U����P��q;����@��圙!9����+��F?�>D���D��it�����>��M�d�mVve����4Y�臑B7��G��RN�͊K)P�����4 -�����$G��^\.9�2[gw�(e�b�1'x;\ +b!��L�����څb���E��D�Xj~�~-7�p�1p���K�i������ ū�, r�0�1� ��(p�����&��d�u��=���ln%�s|6���g��yYG�]~l�uE?H��r�d�*\�)��Vܠ�l��K���SG��@ -)M�(!��ێ�g|�RW��{w]�o[ي �b���F�f'SP��w���ǘ� 42�?o>ρ�8 0�f%8/�qά;S5k�$��Y�����9AAGR�ӣ��Z��G�+����\@y�+dz`�F�&;�{~�I��<���-ybtE�����7'��.����\���a����U��<��7Q�msG85h r����� ��$=���NC���3����˺x�k�����+�~D��,�K^_�3�ۋ󉟋�B�77�*���� ,��v3���t����9M0G�J�Yv��%D��<�R� �-�a� �G�0�qAl�\��hO�x���^�ͮ�� -�#����i���頨�|*\���'ʷ|ӧK�f��9�Z�͆%����z=������[i�E�&c��k��B ��~�ZqU�J��3��{l�xGpkK��\���:�r���փ��*Ϯ��1 �T�^w)[gs _� -�ע�z�j -��a ��j��Wz�.�4���[G�)+%�1��Tt|�3��i.����R'�>Q���$_���C�PE�GM�]^��8j���r�ߜ��s{��m��d\����M��0���+,�K�[U%����Z���3r� ��ؑ=���w�$�-��@$8�-c��������l�+k�i�30�f�,�g>����C,���b�^ V_���IUT��L `��,��N���L�93�_ o����6&������ -IM�k?��%�)�n5��b �y�d�s�=�+�U���m�+g$B‘� �&�Ԑ�8�n��T[��5�b/� -��%�`��0�u�^D���J8�ۊzt��~�� ��Q�r�Epc����2x.M��Mh����.��g�k�t ���m�V�&�M܋0߶���G-�����n�3��W�����\�{�,��U� �v�cYY��!����(��9�!�^!K�rʥ��;ZG�vk�v+��+e���PzD�Ae�9��D.o��˼�Ѡt������2~/�{.�l+x�@��m�k%��)�S��5e�+��&X+�9�x0MLZgs�ڟ����C{z ������;p��������p� g�"�pʷp�F�6p�}azջ@���~�&�.^l/O�I�����R��~����u��el��[N��y?����R����2��T�N�0 ��+,�Y��P��w�<��� -i%��=^˺N0$�v������m�`C15�K��_#�7l�R�����-�r1+��)�\���u��N)��a,o瞲J� ԝs%>��-E�[JA*q(N�3�°O9v&s���Z�=]ɗ6��Z�D)�ܘ���ݡ� -�eS96oC�oP2� Y��m��B`���*R� "jÍ=�ꔡ:��L� Ʊ�g=J�j��(F��(D�8?�|��xVg=��������Pb�lɞo��;/�ʺ��M��β�~c?�ZR�����@��~��%���[���_V�d�w�1O�0������İ!�s%`��B �ϲ�U��q���Y����ݽg�� ��#��8h��n(Xn]x����xs��Y�j�M�P��q/�*�����P��E��k|b�&�!��r4�4��zw��-�,�`����n�!��|+;�3!d1��";�?�X�-�Ƴ}��X�l��R\��>N�e��r)�R9�2ӑ]{ޫ��u)$t6�1<�� �)QG�LKm�s�F��)f�����;}\E/��{μZ]�� -����������A��IM���WMo�0 ��W�/�nC���ݩ�ҝ E�km��r��׏�G�6)��^z�ŀ,��|�"���}aa���wS�a�^:�qwS�����G������cGS��X��Ǽ�y����aSP��کXxY.00�' �R*��f�`{v0)0�^��R��ʩ� �K�A�8���(�h�v�2d�ƚ����ZZ�\0N���WK���f�^ �`�d�/���'� �C� -��i!�r�EX\�C�_R$1�"�Up7%��Fw{��C�R�4bh#�Z�k�v��r0�����]c�/��je��0�ؚ��Br���B��4'gdC_^;�H�92�Tr�8�-/g���>����R�woo��N���AϯAh\����!�ʽ|g���!�M�O/����deO��W��K�N�� 0N�f�H�5[��#�Ǡ;�t|֬�Ҳ�<��F��Ι����L1;��������N�#��<�Q{Tb>���/�{���a��u�����:���:;52�]�|������{:Ҧ�:ƫ�*u��=t�TV�p����s��'oE��ZiP\�F���D�F�ٗ���\Z ��Y�P��d�s�F��%���'�5h4������ƪ^������׭�k��-*b���d��a�]>��<݁(&�7!�7+�;W��-xv����tn��@1�w��'}��$g1Uf�Gm\b\#��N#8Nr����r��"��;�ϯ�@H���!��zg7����e�tB�{$�����gX�~���t~�U���Cq�cܳ�տ����ݠS�o iw��T�N�0 ��+,�Y��P�����)M\H�(ɦ��I۵�8 vA�-�N����T�}�`G1Y����5��%��<�=",�J;���>I����(h6��~�)�5B�uN�UxS���RP�$��[�*�>�ՙ�1�^O1U���Yb�\"��U�z���ʍ�#,��u�X� P�ц\:�m���*�L��I����!E;�f�U�;}T,�3��� -��j'iP"5�hi� �\P�j?`���ө�%���_U(�uO~��.���9i�U8��_�س��؉�Ob+qf�>Pjl�\��f雥��� ��_��\|�Y�r�6��+0<��Zu��d:�3�%;�IRW���I�+ 50(K��.��Q�b���D�o��a��Y%�,A�d/:=�9" ���������O�#���U� j ����������N�E�Չ�1�Ed� ыFJ� �AڈH��I)�^[��^�eJK�-.·�&�������d��f�%^;�w�=�bS��]�K��]�8�K s�Q'��ٜ��h��oo����n�_�������ؿ5�}s��j7�놓 ?T�|�{�Уϣ���n�_~��������d[�%��>#c5.ԗD�-�2�,���V"x��2'\v�B�M+�l��Ga��ѭ��� � ,��W�#��XH�#�Pq��bY⍃N�1��^4��@D�&�Y9\rç\p��Ei6���T3���0�S��G�JR�qdžP���*b@��aU$��j���y���n�J��aV�;e�Tc���L�rD���!`2]k��F�������@��k�f���S����5�żguv�T)Q � &�~�v��!��� �+s$=0%\-v�Ԍ|E;��I��y�`��LJHļD#Tk��.uݶ".ݝ�_|�T������� ���a��^�y7���)�#��-��iVi&���Ղ�%o-:cV��I�_ۂ��f�?'����3��T0��?=~]5#����k0h���:n�� ̉\� ,4��7[����V�F�����i���$�e�9�����P��ѕ}����ʵ�����X?�œۤz},�V���%H��!���L��O�m���>8�jW��=�%\�%eK���rky��\�dTX�qS��~�'����s䌶^�6)��k�kg!ǡ��‡L�a���7G���-v�\��1���̷��ZH�ʕ���eO�e�x��Y��H���F�Ee����>3_����/�3M�aM5�[L�j�������������ܶ�� `w��p����KL��α� -���tp��WAu�_��ɍ@�4^���뷇û��7��)�h@0�zWЩ�= gHI��A&e��w4Z 1��u��q�q~G ��6��׽2]M :]q�'w&�ۗWnH:���aM2��&Հ*��93�_�j9�ҏ�]x�� ���K?�s ��q�Մ`|�cJ$܇/nU�n��S��[�q����x)�b�]�� �d�[�Ğ;�n�싫2,>a��p��#�xY���sr�v�a��͗M��0���+,�7������ծ�R+U��3��F�!��5�DM҆M7�3�c{��a[j�tʚ����� ���2��_<���a~K-�c�ٸ�D��(�֬*��ng(r(9�k��j5>j���*��93�W o��~�;�b������ؿ�²��[�9�� R2��Λk�T���]�+�� i}� rQk��#�z��fe���њ��IT�&m��(��u�������@F������'�� ~�,b���*8� ]K�+= -M����m -@`t��I�d�]�2��6yah��5J�T����am5�ם��+0���a8L����mЇm�*_6Ocr絑-�����f� -��o4B���r&R(���_�N�j����{��ؿ�hS] �!�X� �C�t�h7�@ Q:�؝��a?)×~޾A��J�Q���g1�"����D8M�D�a�c��0 �Rm��� -��p�Na�����$.��� _�T��N��{m�8 �a;j�${����R�97�}mՐ�h�9���G�����XMo1��+F��� �l�*�P)�QC%8E�w6k������_�x�h��͊�ڙ�y�yvf���f�`��K���� �¤R/v����;'ǯ�Bq�OX�}?����65�#�a��`�J%lf����x^h�;�W�-��j�����P�ס6�\������������������~�Km�/�k� e��jIk� l-���Ԗ�]�᠉�@���ɇ68n,�顂�.����솻����+D0��0��{ ���� ˸��@o)��Zz��J�m�l�P2���\5���腓6���e�� �<�: ��5������hm�# -*�D�20e�������a��r["��������:7i�{��rX��J5<���GO<���@�M��@_��Ϗ�杌?I}�h7� � ^��g��؊o��ܗ'&uoĤ�؄����m_�4![Axiv3� ���)����>���f�0�Q0�M1�_$�W��y&���}jn�m?���G.E|T~�r��ND���P��so�La!G�R�ޖ[���� 8Փ:)�,��,�Ń��5�Yr��(��}���~�R� �� -I���My�P�u�C?s�ͯq�"���B��ԏ"u�~ͱ=�x�����Y���]H�G��2ؗ*=A��o�>fVSOP)o�ҡ%�Q�������=��/��m -� g����7�][s۸~�_��C�}��۾tR�;�lo2Ů%O��HHBC4��__�HJ�D��J�XE��p������pAYt�����"��|M/z����������� �H����L���ٙ�t�b���FD� ���$ ��ސ���~�wB�[E8$"�����s� B�4��H��]�S�x����kg�w��đ�~u{u}��q��g$��o���N�y�y�ӏ��3�H���I2%<}���|�����_xu]z��v�x�=z�ޏ�|���oSHK���>|5�wt}?����^�xC"g��ފ}��r��8䆒��!<�cO^�&8Jԯ%���T�1 �|����8�Z!i����Z��qKe���ؼ�� �Q��6����x�wcu�H���SO�D&<��/��OiP��'e�')\fT���8�� ɯ�oR�(���`��cƄ��E����$O6~��Y��?�u���h��݆*��[�L?�*g��#�9��Γ�K�`pX�����uޥ�vG� Qmu���w����Onw��:jā��!���N�X}���ݩjJ�tC���.G�L����&��'��Ꮟ����4�_4iqό���T_�J켗=NB�LZO�M3�Ss3N��~&�Z�3f[ ��.�� -�����ݱn������:�V��^|X�i�0�r1���.4�,DrF2��z�Z���}���bE�8�:��x���B�*v[�� -RSY6�=R��dx��� �]�!��wH��7��U��'eC(O��@'����ů( - ZG��.��>4�L��f(!lx�I� �"r�D�~t+lbdڀ�P�ܢ�L9_wϩM�sc��Vb�+ʉ'V���I�([����9� �p��7kA�Q�~����� g, ��odڄN�p�\�6L����]�9��ݙ�7|2�I �Hݑ b脛� N�7'��i�Ǡ�4mRc�^Nx�%Q��)V /y���,�6K��4�E���G'Zl�d�%�Dl��vLxH�T�{)'��z5�N�F^�輛F�)�JZ�*����w�Nn2Z� ��f��(�e�96#"{�:��'��2�mB�z���`�Huٞ�%�V8}7�Ҹ?_ 56�"d��n͋/�:�Jm�j��V�Ğ�!�xq����ð6dXy�R{T�'Z0�ʍ�,��=�;&(mY���c��4\��!k�%����_c�R�P��2"�ږ�b#��)2.[R�#A�^�¸���mY� ���R�������t�R�ƭ��h�mHk��?�g�9��=u\)�Hֆd ���6N���1HIG��P�յ ��w�J��l���k�iS�~�A�� s0��^\^�"|J�,�t�� *hSN9_�dJ^;* ۝ ;���kZD�FͪU�J��"zJ(O�w+�`m���9���l䂁p$�����r���h �P��ӃȎlɯ�I�j�A�q���d��pc�W�n 5) *����fo�Np�@��3)��N��.��?����W����n� -]����mؙU�U��^v�N���} � �����uf� - ���Z���A�zw�_(�ߋ?`��;��pP������Z@?����8p��$�;$���y����Qk�K��B�%�/�K�)k@ϡ�#�;�Np_`p3�w�F�N��-d�}�h�F�Җ��|�X�@��:�2Q����aIm3P�'�3n��h�;آ��A��`��Rk��"Kl�<�]KH,�� =\��w�nC*?�#�J�\/�a���ۅ8�i��5k8c/Wd�L�D�.; )��ۼE��� ���� -���Z�,ua���ӎ#�}�l�{�l���C]� �� -% �L��/̲E���Nс�}�&ܡS�]��;��j��m�*�[h[�������;��F)�2��d��̨����ZKo�6�� t�w������G�@@l�ՠ�q̆"U�r���)˖���z�jf�y�0ͯ�� sІ+� -�\��L�\>��Ѱ���z��5��aiZ���������x���J�m�2M�h%tW�$i"i&� ZA��q��!M�ܣYikM�6c`LN����bJK���><��^�=�dNE�/�xx8Tl"{��Ŝjb��pi� tF��(�m6r6�<ۃN������+`��޻���w�Jqfl�oG�q�at?������0�a���3s���T��Mð�A��c5e�L�0��r�|�s�'\p���8��"�K*�eR�`��Ũ'LE���CC(@��*bg@��}���h�ʛ�ռY\���p.o8D�-c�$�󄶪��������Mj�0��9Š}�vW��Pr��� �r#*k�46��;��$���l�f�F�}�X�� Ѣ��.�`����W)^֫�{����N�,�Q�-Q���\e���p�yCy Z@�9'�3��D�5���W �1�U�H1�T�@ј�4a��")�Z�Z��eo�-������Jgu����Ԫs4��ȧj�C]:�oC��Q���ƆÑ�Iր5��C�L�ǩ^�}��"�M�۝���D�tu�� -�r#a8�]$�*�+X:<^��у -A��5؏p6~C�F~A���w�pB�l�=��U�1�� Ρ�r��O�O�0gP�P��գ���:cI ���6��M�4�;��+��gj�͗Oo�0�����Y�[U�FU�H�Z�j��1�b��h<�~��6 -I��� �`����!�c���)k�~��30�f���csw�����U,�p��b�^U�ȏVUQe��2@�C�Y^k�����(�"?-gF��*!!���i�����&s]���휏7�P^B�v���(A+�9�d�s���S�ҊN ��H�,��'� ����K�Q?�Y�j+��P�$���I[z J��֬˔�y(Y���J;����±@�Ȉ�L���Tyev��3/�LH��C4k�a�4��7ޥ�T�m����k#[���O��Y��z�a�虙)(8����u�u]7R�#%���8k���|�vRa��qt�����&���ԛ�t�/�שV�R�����p��hS��[,�Y�4��eh�� �t&Dq��aֆ�}������ Q�ֱ�����n!�f�D�a�����(����]�p�r�;�K��E0�p��{)c�)0|r�&.�e\Gm�d'�񱙚Cj���1��{�^͢8 -����ݘ_o�0���)Ny�o�D�����Z�h�>F�s�7��l����;'��R�R`<;�w?�_�s��K���B�8:iG���L�q��|��9���Q�Kf-�be�h�\�ݦQ���~h)tmkx�R�8����@�F�!�B+�(��3�G�`���@�̮X�BR�ڂӳcʅ=g��o�u�r�����`�dI�Ǖ�c���%՚t������پ$��V����&�g8#m�L��˧�O���ob��s�/6.�%Q�9��5��:�ۙ�J��F�?[� �����C�[ 5�,�Q�����w���b����?��Ϻ� -����i�(�3G��Lx.���Z9�*�h��-�$�BZ!�\4���爣��Y�j+vfX�$�P����e ���PD��i-�Ѯ�:B/���g;�G��X�]3� -^R�Υɕ�����V�6% 0z��I�f�\�2���yGa�h�� J��T�����a]5�׽/� 0���'�T��2��6�/��ڧ)�����BSi��Z���F��-g"��B]=�)1�T�v�*�>�8��;P��1���r��8Р=�/t�����!J��@��2����u���P��9S�cou�(�7������e�+�j��~*\F���cw�S�G�a����U0��G���k!{;�{��5�:X���Պ�0F �KsHm��d\\��Uc�)����C�?���͗]o�0���+,�7lw�����"mڴfב1�fltl�_?�����]h&�����y���ݾ�d h�V }�xG (�3����\?�~��nysɌ!n�2 -��?F��-���~��F9%y#eB�ĕ�P0��d�y��s����*05��v����TfN N\�F���*�\\� -��X�'4gҸ�V� -)�!�5j �B�:䬑v�\�8�g�;�S���v�"$@���M�}^�O"�k�eH�v �N����v�?�Z��X�`3� ��^J� 9 ���3 ��=�1C8�Q�W8)�$�>�(��4�A�� -��6�8��&Ժ��察a|(�I0վ��� �[�?��;oo!�'�-u�A`Wn��br�rM K���WG��M*��™�+.b��Ae��rp�\c�N4����L[�ީ0x�t`��0,�gV��}�}�� �x�����B7f&k_+�J��9|*���㱻�!ܣ�@����v8 �[� ���=���S`�d�"΃e܎�l��ƈfsm.�������b�]4e��q���K���Y]s�:}��������s:H��1ә4��WF��A���d -��+� ��yCBh���=Z��U"���J��/W����˧^�8��o7�LPc.��,�M�;]��4R�+ �c4 H� � �J��2��P�,i"i&� zA>8���!]���T�b��a6���ǻY@�Td8��/�őb�P�W>ĉ%�ĮS\å�'�Ag��S^��f���O���J�m�����n8���5�37���8�&���@�N&�Y����g���]�hc�F�`�E�+�)�� ��@@𗖳�p� ��v� �,�U�T+�y�a��+�0���F?� ��9H�q��J�c�e+,���fZn�/���qJ5���7`���(���a�5ĠQ``;�o����!)^f)C''8��mzM;�JB��k��L����_�Sh��U�r��ů� ��mb� ��w��P)Q�=��=~�E!RP*�Û�tr�]o�2vok��:!���oV@u��~��0sW�'�8��� %�il Ԃ\�z9��i�� ����D��Y�w8��Ʊ�y����E �*Z��H�v�k�\�&=K D�2/�����)Y�����ъ��O���/Cx�M ����\x��X�4_E��:r��2�]�:W�۷�Uʪt�U���n[��!����{��?�H(5p'�0PY��� k�%!�}�.������s���B"µw��r_�{|!�1�6����K���@��ky\f��f�D�����g��$�'T���}��>�R.1V� -�1�c�]ڻ��2 wmt}K)AuU~�,�����3j^�^\-qhBVH�Ѿ�V�fT�`x��s�sx%ѿ5��< ���v���6[p�Ƽf��D�ڣ��:�8c�����i����# g�g�~S�����L��t�����ɡ>����B������ -m��i��c�W;�k -����]�"����n�!�H4�VG����hX�� G�}����Ņ*��b[�e�v|���͗Qk�0���)���oc�)c�PX���9����%s:;�>�d9YRօ�d�o9Yw����:'~�*�Z@��I���g`�͔)�s�t�����],�p����%�$��D��uYg�[�ȡ�,o�N��ոR��(+a2Aw/E���P��Ǚ�+�>�.���#0�;�<�}Tr\��[U -����(�3G��Lx.��f��J�V�Kx��@d!���A.MK�s����?�2�V� f��$� �I[y\J��]� "��� -d���@G�kqx䳝ď���o,�‰�������kir�'���� -ضF���1� ���k�Vf�>�(��t�A ��*�x�6� ���=|�>��J0չ� ���K��ה�yc��?�����Z�H}� i~�M�U?V�~�6���>a@�ͅ��1^Ze7�u�v+R #P��9 �Ju��v�S�@f�0�D�s` gg�?�^�\�؍e��^�&΃e܉�l��c�1)����7��uo�s=�/!�R�Mn�R��^�8 -�n���W�N�0 ��+��Y�P; nHH�yJ��$J�i��Iҵ��ul����RՎk���N��. �`,W2�W�K�@RŸ|������5F��QJ�yci3 S4�~TbP���k�yDT:Ƹ�$S�M ��H�V�g�P��ki��6M��D��� �V��=t���\�5&�X'0��X���\��^^�a�?$<1R1��Y�� ���΂FW��0}�5�w�-�(�0M*f�����l� �oЭ9���/��%���i'��ҽ���L̦7b�@�!ƐU�?€� ��c�g ��1v4��L��s�/�y���C�Sϕ�0��P;�a�4b/X'�*�pƏ�� �J��h*���I��$�z(!� -�M�͖Mo�0 �������0�)��� �rd�n�ʒ 2F��'ٱ��� - �ۛI�"�W����h1�v�����*Wi{W�ͯ/�>�Z_��H"��-b��?fY�V~�+wXY䌂P�)ĭ3�{� ��A�Ra!����@�o�A�t\p�쮕B�q�sW�G)g��^� ǰ�v� �e|�� QKC(�X�V��jҥ6��B�}it�^[i�)ILS9U��39�TО�b�\��7%#AB7�eSH@� ��Xe�t5�˳���쌩w�\�G�҄��R���� ��UG�;��y �}KͲ4���@Z����� ���нɑ ?T���$���4���&��M�8���=�~���P�Ι�CF���ǎ��&S�� 8U e���N�!M�!���mF������V��4Ac� '��k�g�ȫ)w��r1%���J�G��t{^����b��k��;���Wh�������7t4'�G3�����W�r�0��+vt��c�ɥ�:!�3��Jeɣ���W�����&� � y�������׋R�)k2�u��a�2���|cp=�J��D� el�}5J�0V�J��РO� E�u�&V���^"U\`������@��Jc���*��Z���>��\ו�gVvk�� xN�q�3VpMa-y��sE*WZ�eƪ:ת)Y�{Wc,'$�V�ڊ����@NU>�–U|��L�7 -��u��6���϶ܘȡ�����y�"�d��i����ea4�+�v�!D�qJ֥R1]����5�%��fb�G�fg�La@ ?T3$5�9�ӡ����>���}�.��(��*1}�(a�0� =���x�����>x&ʗ ���r9��=~s~��U���/�� ��S�o|�������LJї�����$ �{����s�;�{�d�7xo���e>Ӊ�H����Ȓ�*�ei?bls#����D���:Wq�㓲j�R�/�],�q��U&���F�H����X��� -��_Es�2x�$��Cy%~�� ~���0���O-��P8� �(a����L�{XY�[�K��� �� ��2�a\\�f -�� S��~��R�-����,#�ވ�L��XF����+�_�G�T��mkz��.���������S�K�|��w�r�>��h�Xhih�XH9�h��iN�Xz9�������,�=�[�[,���8��\��"� -r�-��i�4o:o�Wa%�g��ڃ[�=Y{�|���%>��s�� ���o� �uxj=�D���ߥ1����F������q��*������m -.�!ؾ,��Xd1>��@�ЎPC��Ec�xjg�E�@#��Ѣ��~�NU����i'�|���,�+أS���3���3o�S0UO2k�/t�*�VԹ� -h{�<���?��Se����R��䔻�Yz9I��N��S�en�eָh�Md�\���[���U��i~�m��6D�u��F3��Zl�BB-�OVG��෴/?��o] ��q��A|�R}v~e���b��J����i�� �k0���[y���*i�d��P�4!h����ŷ 3KJd��.��ϊ��_2O��A?�M��Ί�f���!Xje�Xb�] -Xc�ꗇ�����*�<�j����"������)g��k����!\�W��td�q����D��D%�;ٖ���ʶ�`��3�WfcI�@�����b��G��85��Š��W�q��Dס��Іn�,�<��+{v�-3���0��i��g�|� �K���m:9D#������8x:{�|�"\�dXzݣ���E�75W��5/j^̃�%���Ϣ:���U}L�����5lgԱ{���q6V%?ҡ�G�*�A�a�i��N{�E5 U4Pe_�V�5(-V���Dž1�p�����)[@Fl?N&�o7-�� -����r���I۰V U}!���:�= -��Q/�J�Sn�r��I�f��Q����P�J3c��C�U�dc@��@m�p��5�h��yG3���+��H�ӳ,�F�3�I�X���#Ƒ��;Ņ��d��j����n&�w�{���:07��u�E��R_E����$#~��\�a�i�gyW�t���d�{��al���d�'���)�3�u~�|g>�w�֨ˉ����IʻI��{宔�;r��,'�+ YuI�x$���Zl�y���Dp��)�]�܏�����p������jD*a��V�Վ"}V�y F� ��+ �D����<�DT���� 3 X\&��2�5���D6S��tGm;� -�sޓ�I9���.�A����1Œw^ps����T*�vH�(����U�Y���z&p؜ Si~����e��X -���q+�I3���y&��/h�,�y x o�G�zVi �\M��ߗW�Pe_����^�4���d6�g=��h�m�R�mJ����:��� -�9�/�Q؉��!��`�P�,2(I5�7M'�+��Fք�[����#-6�uU=^�k�� �&��f� O䓢�<����F#� -��"�-X�鏅�[��[\ﱍ $jR#�,]�Yrf+���L5�)Ϙ��l��tjgu�1�#uP�D]�7&�˚�� �^$&����"��mL<Ɔug�b����q�j�ܘ��~ie�g!��w_���V~�ae� ojc%[{Yl_g)����{�D��`п���zQ��x�����f\A6�ܢZVpQ�0���E$�J��k���R�$�,���ּvF��U���U��웙�����z�sMS�c�� 4l�~/�t�sc=[�������;�wD���dw'ģu��4�_%_]�f�r� �VV���O�!z�o��X��Ȅ�5E8��#i7d��K�;����0%�M�=�p���Á`�����ul�!N�㡲[�7�X+�Y;i��Jg�cR�&�.0*b R$Dž!�Nas�/��E�`ZN:�Pa{�] �X���#�|�*j�*����uo�.�᳴M��BC Q:��ս%Y}�[�����k�Ce\��z�{s*ܹ�M�l��y���vu��Z��F�ɭk*{W��Z�?��X�����bǞ�?��dwJ�v��L��-�rk���w�����lG�˺WܐK��ܔM6�Slz{�����镏:)y���0t�(��]��W`���{��=U��-U��5G]� z�4Xpv��)j�q��X����q���Y�a���!sBֵYukY��F��}��2��$��(H׏�C�_D���|n)���ݽTF<�O]8q��"�<�0�7Q���1b�����'�3�����zщ�VsI�S�WL���JSee���:�٠]�?��Neg-ι�s�$ٙ�h��뽽�Nb��*��`��U��m�nގX7¼��%���n��>��po�d��ɍ `{C^�m�:,� -&�W*�kw1EgP{��m ɕ[�����P��q��Z�Y�`Ա����,^��M��κ�n����*rQ{n�s�ˠY>�Af��@ގPe�R�P!�7��$�\�/'��f��=|��ۡlv�VZ���3��Ȓ��{ϑ�NSO�)�at� I��f�:w��O��W(ܢ�s�!.��|UY��Ykcx�ګ���g�����#�́'� ���?�_��)5�/�B[i�D��������s'Ԥ���aa����{u�,���vC�� -N�}���q;>��,����z�/����b�f9Y$+������y�?�u�Ԣ�r��l��T��%��k5��\���.VV���DqY�=����>��:��N���r~�<� -'�f s�m�/�����c�a>�0���#�9�|�� �'��Vdž)���N~A1{�S��ݞ�B�ՊՙA��U"�kqu�Q8&�d�R�Rꦉ�A{s�7�y���nD +�����qC'ҩ6*������"����~��^��9D�j���ckٟUO��Po�·D7��S�CJR� �vRi������H�1�U����'���;�ٌ���#Z��v<ūI���K \�8�['Xd�x_��Q��X3l�oւ��Xh"]�<�*�oVd��u�N�neZ��;-e�hFBʪ7��T)c��!|t���o��6��HV��V�A��nlS�@adŴ�8&�a��s�,���:8%��I7��t@G#�:rǣ�kʕT`���N��#�aB��o��U���e�Q-fx|�����ה�s��T�����C�xg���pL����"*E�B��Y1���L<ɪE|}E��]�����ϼnPs�b }��⃬�E�s���}��|E8� �\9�O$@���R�6��!�Rd6��M��!X�EijE� F��|�&��;�X=��q�t��T���(�'�����k�|�})������R� Z��vR�=��G�U-�@狍��t���1G޵�Ý7�L���{O��|��9y�� ���g+�Dž�cm\۫�'.�y�?����0�{B�YW���y �z���?o�~~�S�,�v�Z��t� �T�d~>l#t9b\݄�� Ц�y���ڦ(���/���m��d;��W`�/>�r��W⤺��X��&|wV�I��|<k�2���U��9]�ˠ'�*�o� ����(�ף�`P����o�Й`痥�x�J�&�43��ћ�Z���c$��o+�j����mo-�0�l���&n;���Q։����N=��}��4�g�1O`����ִ�["�|q鍕Jm'b�o����9 �ś�*ÖaCĐ������`�5��>C�r��n�D��(�p�h���;VՐ�x�Fr�̄Ə{R�+��C���zuMA��V�t�����K�\�Xe?q�Q75�Bv�䈓)�N˝B��b�5_�#�����[�S7�q��_�Hο�{�Y�f4�:K�@�I���g -Oy;�Yؘ�ٷ\:�a�3�F}{⢲k_M�VWַ�!�[7�Ǚ~��F���>7���ƈ9 �¨*��[�,~&[fi������J�U�ة��o�@ 9H�7��D }�妱g�C���60G -�ab�S�_������Me��M�P�5���n��Q/ت?l��_Ž�o�e���� -�u�A��=����uok�����A3�5ȸ���i cg����{M#׻H�ye��tш�<��^�B��o-&�}%�kX�"^�w�M����d�g�k�T6�� �� N�28UY"_Z�8;�Z��ګ^�5];�Fr���P�����/�0{�x3��;������l��8���$yI�}�x/��D��'+��zWP��{��/�g�FrHOZ%��y{@$rM��!�S��]���ݘ�o�0���W��>hߦ�PueS5��A�>FNr�7��l����;'�Z���x"�w?߇/�9��P�d��N@����I��~��9���I'���҄����K�M�V>�S�ؒh�F'� !�`���Y����J @���Y�aP ���t�ѢL��Me���z�LZ���b8����è��G3& -�>-e��T%�P��jH3���s�å� ��i����Ӯ�"�׿\H=kL��f�Ũ�L�����~yc&�{̸�1��� ��bb1](�cVە�C$Ώ6(��I4�-�$*#�ap;Ep�j �����V � h��3gڠ�2�bӘ�S$,�_A�E i(��/2��3ࢰ�"�t9��e��4;=S$���[��X�y 0 �5����,���@c�r�J�m!W�;��iؤq���2.0/hq��ע�N�M���B�}�w�띓���YJwmӄP��r�^ 0.L����x���Ÿ�\�D��Vf3Գe�-lu���uI���X��dvڃ���*mA�딵�� ��Oȧ0= j.ap5p�� ��R�X錽7��"L�U5{-�`1=��x�N��w \2Q�� -�r�� -�F[h��f��/�����x�VO5M����-A��کJ=�m�;���Y�MH�9��X tE�7���a�P9�.����; �7[f���U4(W�5~ DG�VX��<��ݹo����l٧�Ơ��Mل9 ��=�L��v}I��:T/�8�k�F����Ʌ���Խ�C?�%Հ����͘Qo�0���)Ny�o�D��� M�@@ս!'9��cg�C�}��R -]Y�� �89��w>_�s�� -X�6\�0�h��2V ��0��|��1���Y'���҄����S�M�V���Вh�F��r!�`���ɔ4�d)�g1�A9ؘ�=����2i����h�u:\�����X2����b2MOT �*�ta�4�nj�piq�:h�����N�2������Ջ[M1�P{�N�)ݳ� ���8 fL8���� n� ��bl1Y-g,�+Ir2�G;���5�,�b��7lL�<��A���%�Ӫ`�-ٞcfL��T%xj̻�_A���P=��(�f��� �I�A�b��΋�T���ʓ{��X�LjM�I�i�{����ح��Rb_�ZxO) 6?��{h���t��[Q�u���Wp�k�Ć���vt}p2v��[ K��85���w���~`�6}����a$hĹ�\9�@��Vd3��u:,ly4��JXyz�UZ0� ����檀ib{�*e�����9�&K����3��P�)���og�P�Ǟ��y�u��"zY�O��y$�+y�d���gl�\��4�7�k~f��N��YV��œ��w�[������<�#�O&��.b*�ބ{S"�P������J�+F6T8��G�~T�Y��y��U��G$�W����I�r*!��r�>�m�i�d~7��U�K�2�nݧHs�)�G�9ܳU���Pg�f�ƌ�N�շɓ����~߾r�C���):/��c������R4v��-И'���� -�n�V�4�5�P�t G��Q6��VՓ9�P�'�� ���-ך��.��ݿ͗M��0���+,�7������jWZ���&=G� ����x�G}��&�&نM7�3�c{��aWj�tʚ����� ���2��X>���a~K-�c�ٸ�D��(�֬*���f(r(9�k����>��NBE>8gF��*!!�q�o~�X ;��#��@���QXVB�v�|6'���#AJ&<�ys��J�V�Ox��@d!���A.jMs�s�Qou߬L��?[38;�*��-=%|Y��.��Y��h_������ŏ���o,ߜ��B����kir�G�i�6�m�^n�cA4Yo�8��(�M�A���l�F%U��}�gX[M�u'�K�5�K��&�X��6��6�/����1�����BSa�z ��7 -��Mn9����/�Q�Z5SS���U�߁j4�����[,ő͡|�h��H Q:�؟��a?)��~޾A��J�Q��ͳ�6��n"˦W"�0�s��0 �R��� -��p�Na�����$.���K_�T��N��[�M�˰�Z��ƀask������뾱j�]4��q����}�ݘ_o�0���)Ny�o�D�����Z��U#'>��cg�C�}��P �h)0��㻟�/�9{�%��X�U��N"@�i.�$�no~|��Y�S'��Z�����Թ�k�M�V1-�~h)tmk�ƥ�q4����9�-���b9=� ��\���������֋iWd��ra���p�=���I���fL�4}RI��\g����zH3f�=�F(�4��������n�"�׿�K=ݙ��c�1O��� z�J��Y����1V�B -�G��3�|� �Y)]W���0Z�(G�Q8��tN���f���C-�M����fК�9f��E���1�HX���J� -�R����Q9 ̂�~����6ը^��������2�-wn�;b����1�KC�2�O�֭�L���B.�wB�Ӳ����CDŽDA>��%�_�j�?�7��LJ#��A<���N���tSd���]�C���U�5��0}�ƫ�u;G��rc� -�rZ���� h���e�/�7%���c���g��Afnʴ}q�nR֜������O!? j�`p9�I�R�X���7��"̔�� {���"`)=U��N�L�� -B1٬��b���AW�fZ� �����x��L횬��[�WuS��]/8��#j�p�^sBO���o���@�r�]�M�w8o�̐����hP.�k���$,��yjo���@�=�a�٢O�)�E7� �� s4�{ -4.�!���&��$xu�^�q�hp�����'� G��b�TT�nv��T�r�0 ��+4���7�I�Ne(�2�1�����Jg����n���ph9JQ��ӓU_�wH�ߨ��K�M��6����\��j�tJ �>5�g���J�U�cv+�\%2 -6�s��ݰuI���� 6j~��>��>t�o��b���]�_�'T��Ĥ 7j�]N$�l���d[�,O��c�l�a�v���"8]0� ���&C6�H�X0�4%L�=Оܾ�L����@CV����U��#�<���+��@5�dF�K�q�":Z���7Hb.�j���������UO���\�1��0�32���t�5=��_lQ]���� �[mo�6��_Ah��M�~6;E���X�bI�~ (�lq�D���������ƌ���,��ǻ�7���R���雈A.t"�� ����䏈�;{��[�����Թ��^�N��H��4׳FDlX*5�n�T� %!w�y��Qu����+���T'�m�$��`��u� 7��\Y��u�I1�K+c�������$d�U� G����GuI?�Fq3����8OvO���,+��'�e̝H���m�49s��������-+�� "�Fv_��^��JO ���a�[%�c0f -V�<�ɢ@g�c�/2��I���@>�i\J�\M��ٶ���3�l.�Є �����������p��_�^��V�� �ly�&9�����\� s��im|�̹�xe�G�� �Ϊ$�Aϟ��Jw�c�ծ ���vc{AJ-+ ��.��"H�!am��ać���{��=���%�(����Ưww��ZGn�;`����7cV�nֱ��&� ����VDk{ {�� $WL��tc�7�\Y�Vp��SW��E1��[we�6m;�8�R`����ag��ⵝ�s�w@�%�� A�QFi��i�'1*l ��U��)m �}��D�7"�om�/�P7`�]K�3d�;2�q�P�ږ��mD[�}0�,޷N�9�pO���a8�B,sj�%���2�r���L$s��܌�vN�݀���@�<�ϡCm2���PD������%ݙ���h�E�G|��u����.U[��#\8T��=|x-A%�A���G��!�g�g�q��u��� ��M>E��%�t�8�H������H�]� -����1�5!ˏ���U�D�2�#fA{�*�״"o(w�����K~&�@�o�p~ش� �-ӪSF5Y��ɩů$�Rh�����y.��G3��s��]A�(��#жH K���� �D�8����g�|�7����}I -H�$z'��i]������VM!����������`L���@oAA7����i�6 �kQ��T�V_:�}OX�m�ւ���������� PJ�E�]%޺^u\ۭ %Z��d>��l=��gQfԯ�y�,��)pZ����g�빍0��H�� �=ۧ࠵ܺ7恿��{���O�S�n�0�� -�{q�UUj+�� -�ȱ/ĭc[�����nU��{�w��\,ϭe'��xW��#g����K�߭�9[.f��2%Fd�J� �!���&h�;@����֖|���3'[HA*(�PL�b�Xg���\no����ʻ����=4�2[��$m����f���[{UY����N22��*R醶��[n!�:�Dki�� �<��>�-�6^�bG�� �1�\�U�(�lM�Ш��Le����!W�\^�8i���&ՐT4)qL�6��i ��9�q���Bu��&G���)�5L���e��ɪ�PC��O�9����>�8O &�'=���XMo�0 ��WF����0$-���@���B�阋-i�����Ѳ��N�,�1`�D�"�&g��` 0��F�N�F �P)��4���������MD��e�X�i�;�?�1�Nt�S�:��bkDIJ�(�ѵ.>�*)�(�^�u�����i� �^wz��K]@ ��v�7�;e: �q�� %�3����������q�Q� ��;��-&X�[O#M�� J^t+j#d&U")��7�z�0�y� Ujo�Sg�2�$,Y�1�ƒ�Bo�0�*#�[k:�Ba��6���M�-�~�������44̏i��`\ s�� -w�����^�)������Q�[��NL��ܝ�%k".l��U�jx�N�Wpg����� h�I���q��*CG� -`n��H�ˌ*�(�JI�g*�/��}�> l�������l��WeBA'��%���t�aW�(���حuƍ�k��ά��.'_��A�ķ��M�EZv� �=/�[go����,Dݧ���2����(U <��`0�'��Ed/�JX��Zs�նwkpW#ЇHe�w�"*ر���P�*���y��Ǡ9�`v�r+�G��HI�Ig=m��ٛ��I&_��Z��m��[������ ��~oj��7�J�{轖d���c���6��������I6�B)�kK����9H^~�.KRa�R[[m/ ʰ��yM�i7{���A�`��;���3l[`���A���� 7�pS�_|����C ���r��r��wC���%!�+������+��- �ܙ~Q����� �l���NC*W41��=�,��[���d��Z����#�P�q��o�L��=^ �Ob��?�S=�1��WX�� ��l�@PEpWG���b��-{�J�=�&�C:!>��y�7�?�F�����w��Za������LJ�o�ku��[[�R��C��9}0F�*���U@6%[������} -u�*��%��NO`�m�Z�1���N o�4h��P�����*�V#P��/��m�*dmO������K��ff��%��E����,����\��"���`��{�"�-�� -G_|���ө�����ta�4��b�O,�P6�i|E�9D�MXԫ�HN��kR -r��ŝ������Y�u� �8e̳c,U�Ḵ�u�������q�Y� ^�OB���Vn�G��>F��h�P���j9��N�g�����|)�1z��x�@���?�3�#.���H����o�mc��Y�O�0�8���mBm��!mb�N{���z�vd;����i��-���[;������v�l<���� -�:ɧ�� ��:j�I~�.?|N�{��Y 4Y�N2t�8m������(t-kxy)e'�+䥐x�r��b#���I�����@�ke�)��f:�ߟ}K���ĸ�$9���9�g�ŠTH�&��(S)�&B1Y��BHL�y*5��^��܈�z�zT�``�����AҨ5[��+�7)H�-�z��Z��.�!dk�9���� ��\]H's4� -�}�D�f�JH��`�����l*`��KO���k�+t�/d?Αh�H �9�!�G����r���nU�Vb��,Vf��CJI����no(,L��`��~ �0��89 -)�=< -��tP07A�;!��UYm�E�l�B[�/R��� -�q�y4�y� -�3>dj���Ͳ�b�P�� �� �L�Э��Z��S+�.��r�bi�6Bq{����O��� -4͑+��"n � -��������6T��TYz��S4�.��叅B�tWy�vb�¬���V��V�fiD]w.0�A��yTh���^������E�Z��O�Y���{�G��[2j-�qzc,��+hF6 -�P� /��Hm�a~�LF u�e���ۉ����8� ��^z�.�ѿ��x͛u������h�� ���q~\��Tk�z�c�c�ܻ�V�Ĺ:���e)*�i��3�R��R2�6�� ���Ÿ�~�QpT������Z�F��6"~<�rf�1>��4�Q�b���� -�ן5�,� l�p=��(1���]�fi�/�e��Б�b�������u�Z9�w,o���p�ԫ�������ob���zu�E���oqtz:�m�A���X���6��q3d�lp��=tS��N�U3�l�ۅ��{L`��.���M8G�n�u��[�o�6�_A蹳�a���R� �#�V�I���ŕ"���a�����ڎ�(���%�l����wG���<�d -J3)z���OȐ�I��ct��;��?��,�Tk����y�1��.>u�8 �#�t� -<e���a�/�������Ni=/x<��B�X�rH@]��� ÅM�ʀ�F���o�: ��Pa�����m��}u��Ȕ� �}���Pc.���#~1�����0a`�X���>���гM�}��G��g������j�~5��7��_K��Ԁ�b��R߻�Ч���P����N�fcƙy�yi6��:&�/F�Zm:P,5xH ���X(&���$D��]NU`2% -�S���@�9� -!��Ь<��#�i^-��D��D��\���R�c�2�'2�� �#3��3�,?�&>Fta.��H�K�w���607��@�LP���;�/P[,Å�L��ڽ���0R�'� t�ȹ�.��w�_�}�u�rp��Տ�1��Q§�mxā���g:�ԥU -���+3if���r|0A��>�� �V#�H���,MO�����4~��~ <�޹L׆/���bĄi�D>C��')� �ĝRb�emT̷�q ;h��"�[w�I�9C�,a�\a��\�LU�I5MK ��U�>U�Y�m���bH����51t��@���s9�c�Z�gX�u�p ���r���k&9������5q>��G�����-�Rz�Y����.j/���Z�ic�Z�BG(�=x�|.B�V�j�� `��Y��Mv�N!`x��Bޛ���6+Í��g�O�2���q�LtHdK�����*�t:�gt�V��[j'Cԙ;�"'Uxe�`���^�� U3 k�F�1�:0���D�T�&2�y�)���)hPI�ڇMe"���7*��U�t�'g}E���k��q��C\�Q�f�Zi��]p�U��os�����m�[I�L��i��� ���F�ڣ -�͜B,���Ɏ�� w����^5UP����H���`YClރVW� �_#���q�.'tΒ,����pZ^LuS�rcǐ cҐ�ڻ�Ś*�� -M��7�%c�q����X�����]6P�Z�{e��� T�V���?+]=�&��U�Z�62��|̽US7�d�V�~�2���˦�Ò�㸷X�����Ks,fx�9�Z(��P��f�����ڳ�-�j��I�� �Kj/r���Q]���Gv�uEl^}$�5��onn@hw ���/��0�+ɶ)ғ[ҡ��:���)+����[��0Q���d��E��w%ܛ�� 9��U��ȭܡnN?��̈́�JN������]ɂx�CO���CPI��p�g�(�.����Q�D��=��APn���Z���T�L��d����ac�i#���ػ���$��ĩ���Kئ��w}�+�� OL�7:.쬶��Q���d��v"�j�m��}#���֛f���lr��XM��0�ﯰ"�l��P�+�baH+ʞ+Ǚ4��m�I?��L�Ҫ)-���j���{3of��vSl�I��������:�jO��_� ���U$ -�����A�hއ!��MnR��V���"`YUq03Ž�@��Z� ��%8��A��?qs�X$KS@ -]{��rԶ��_����ʡ���?������>q�q���A�r��_����,$n��TI!k��Ew�6BfR-�B�e��7� + cL��xw�a�8S����^���VV1�rw�e�}��v"�-a"�-6'��ڛ�}$c�N�2�2�5�K�-O�(�A��:m��R��������j`Lg��Q�zD�lEa����7��Z� *��Qle�-�ˬ.[�M��4�.����E'-wDM[�%���p���E8��|�� ��]�Ɍ�`.U -p��m�H�u��o,| ��������ɓ��|$_������^C�j�ǫ�xCG���~�H��Rm`��G$�:-���!�N;�g#�����$�9�c�D��G��d]�"c�kBf�c�/Zδ�ͦ�n���S�O%����&���Q�S\���>2��Ѥ~�_g�< k�w�jP[e�}"���e�4�|?�^�8�7��&g�䐵��W�m�%68n���J���k��ѣ!)�� GcA�;X5�!* B���5�J�@U�C&h�� ����2�seW$�٠�=گ;��`� VҲ-�j"�5�R���.��0-�KM -S��XbI�3G���[���_��6�H<}a��EoN��ԧ0E�5�'gM�sV9��e4R`�����k����w�?����q-;7���� ���������^�8�8U�o�W�n�0��+<��XMNEa9� As(P��9��qŚ" ����R���N��Uo"E͛7�jz��,�yit�.&����g�~�ߞdp5;� -Ž:�}Ϊ�,���V�4�Ɛy'��r�ݪ��-�k�� �Y�ؾ��Lem֨����t<�[�Y:/���5�v��=��'.B��\yd�R ˥���J�M�lS(=����D!�҈B�h�q�p��Sۄq�{�qX۰�*�ˆO��iK�.�,�w�lf��pK5�ʔ����� (�oc���DI ƕ� ��r �š>p���&��XUR!x9TQ�P�{{K���� U�|9��l=8@����њ�R��`��� L������:{�yS��XH��sV�5n�u����O�6߇R���P�M�ụn�8��^ �>V�G� ц*h|��Z�;tЦD���ǒ2���=zJv�N��.�ɱ�޴�I�"չ35��� ږ�25m��Sr�ڣ��]B%ƺ/�=��$�%�c|\����?��Iv�4~ܠޣR�;E#�0�4 �K�<������7qƎ�����aF�����8��SR\���)��%�O���olf_��=%�1�O�^s9;F��<�nID�N$���褜�>��$�\)��[�K%Lߑ �ϓ�uaYQ���:Lk(�X��'`��6�ƾ?%U�c���Hj�C[��!^��A~���&G�i�ާ�K?K��mj�����0�}ũ;� �R�n� ����!ݪ�$[��M��sB�g���^p�DU�t��=�{�Q��΋Rv�|^o���qx������"�n���79 &c��L_�b���؄q�@*'+E�{��{�{$)�t�������v%D#6��9\"KZ��g�7������_��6�H<{aC�ޜ�� �Ox ?����q4�3�Y:r��p�����3�/ ZH�iXO.�����ʾ���wן.<w%�[������M���VMo�0 ��W�/^w;=-��N[�,33W}A�S�ߗ��l9�h��p�I�|��ʫ�YXc�|�.��7�!��R��n?}Up��(��9��\��9~+ -���M����(Xu�V�g�?t�:*��a��`�������$-:������v��q1�;�64�Z\� �:s҆+��6K"�f2�pM�j��C�b -������6�� �����!�R�e2��,��vx� �L�ʂzH��$��>7���`4�R�zJ�\�/oԿ,&�d��1�ѯ���-���3�;ٺ�(8��������l$�Qpl��}�(_�����ǔ��}�7.�p�p6�OZ5/��\_�����-z��ۮ�@���G���Sp�s��J�N�1�%gRY�w�� �VMo�0 ��W�/^w;=-��N�~�,33W}A���ߏ��l9�h�nP�I�|��ꛍ��Ɣ)�F]/�+@oBG�G����Q���6V� R�s�z����$Z�>va���UNF�j��Q_��B�uT����F�����@M.Zt�9� ->3&�!���j�w�}�v�&��*�m� 7j�m�Df�d�2�d��F� c'��m� @Lk�y��m�IY���vFܓ'!u��D+��{t�CJf�m�d���ԓEȴb�5 q�6�%�!I�Ey���cb��� d8�Y���ZA�����p�I��Cnnڟ�M�dp�md(�s�?�Q���},���&�a� �_g��Z2�t���d��Qp�� 5�fb~��gL�HX:��Ԧ��E�|+�����)�!�W���\��%F{I T�%GZ�2���*P�y�|3-�y|��r����e��2�Tf�s#����7x���8j���rX�� �`v�ۨѶ.�UĿ�rW�[=w`� -���X�9n�Xs.,ȇ����YM�ظ�}pώ�?�[��S� G,��G�/�dt@���e���xƳp�Z6��=�����\���c4Σ���q�'���Ȼ&�vG��Ĺ`�a�VHk���L��n�G��Bb5^�o���X��[B�y,=VVN��Ę�����>���ё����ug���w���(<SNz�;�k���c[栃�6�����t�h�d"b�c���ʡgX�����QgW��f��FtΚG �x[h���U�Wp����^�;I����WMS�0��+v|j��S�Á����Bό,��YR%9��+��@� ���"G�۷o?�g�Z��Ze��ɧPq]��%��$p~v4�9tY�,��7_ӔN'�2�^�(���<���2Kn���B[��?l0�jt�q̒������L�Fb�ʻ�ڵG˼���4��Z9oNO����`9}b�gIɤ�@�g^��N�BR Yb�\��A(&��` -�s���=���Sހ��D����K���5�����}c���pZ�w�tg�n��j��.:����#,�V;��m ��5I"�rm-r/�ᒯ*d���N��� 3�����Й��4P1t���Y�&�cȏ� _[,�Re��E��o u�;Kj��G5� >b���nӶQ�P�M�,���:'ں|T��og-�굃iC2=ןm�:(]PEk� �22OͿd��� ��*����e �R�ZZ]S����ЕL�'w4�^��w�W�=���C9�<�t ]:` ��N��uD%YUߨO�4�r?e�.]ǗЖ1�`2�����o>d��������u�C)i�����4qֲ�:cp1�љ�k-_]��2��L�|�h�S,�- eJ��S:$�9N�o�R���Aǩ��55��z���Eo@���>�v��D��W���� �yr�b�v$��Z0nH&�^һ��5��Z+/pG��eI4}"��� g|��O�������xH��}m�-v޷m�"��Y��^��2�3�pS���f8k=Ն��p�P�l����1y#č�eaы�gd��Xl��?����o9� �QO�0���V��loj��Q&$���ʱ/�ñ��v�~� a[ )tx��j;�����>'�E%��Zeɇ����b� u�%�W'�>&�hr0f�:G�a岤���)�F�4\/F -|�,KHQK�%�F~����(Z�3�A�����䀐���� -�w��S�zm׶�q�gZ9ok�3�?�f�\Bh��(�YRP�0��BQ�5�$�,��ݶ�f��xdB��L��y��Jܓ�F��Nj��p��:�K"���Y)�O&W�p��l*㗭� �T�K� ����ȩDK��Do+����aog���:<<���_Og'�'�qړ�I��k��_$8ׂ�xq%���=�ab_���]���a?n]`����*�h=O��9�uo�kCt���s8TbA��ej� >q����Қ��L�#ԇEP|���u�cC-���[�����a$_Z(��)�\k^�ߠ٣�,yD{3]���p���'���& { ٵ� �Y3y�*�?�P�):��̅���/���Q!�K(� - �8,^�v~cOV�ν�f�=ұ$`�Ë�Z=�k�]������h씃��G\��n��gbQ�k-�J��h�ߩ�,�� ۮ����E�-��{��E��:�sk{ -��&�[���!]���#�9�V��P2��Bg��c,�c�K�1�+���^�-��>�ж�VP)�Gs����.#�W�7��JQD�_v:A�_�j�T�]�|Pm���V81h���h�s��� o��X�R�0��;�7no&�f`8�Y^c5�����뻱�Bp -E���������jm�O���:/�N٧�G��ɥ�K������ N&Gc���@��OY�=N�F���Y�4��;����J�Ԫi�b�@� -��S�[��#����� -u���E@ǃq_��4��>�Z�J��٬_c�3��EHY��G>� EoRs����enD�����z�p����l���&��מ�z��C�I6��M��(�紪�lrSJ��lX��F��0�&^S��sE�<�?B� ;1�29B0@b�6�.�`�����fvu}z6��8���-:=E��v�ʒ� #�V^�I��'[z7 �VzµI�zK�� �褠3�/gE�����I��7M�����%�_k�K��LZI�����yE��L*V)��H)~G�W�B����0�7\w_sK��r�����_p�J�w���@-K~��Q��� L�_p��}��_;�f4 ����+.�lH��6�0ȌQC)�1���e4ݰo���o��F�ْ�2|,�3A�%�X�A�9�?�!�z:Ӊ��$����beZFug�۝g}q]���o��R!�xցE��� �w��n6'�5�(=kIQ�2)��;��5ǪI<'�SW��d����L� ��,�z�t�S߈`�_S"��¤�5Iu'[��k�Jh��/��2� -�#�uK![A��7�s�'���(U&��#��^��&�V�ޒ��𦌵�M*E�۱���e� �._3}!��56u �/�_��dԁ/Zuj����gر�����ģ��L�� �N<ޘ� n�����{�a�r��3ʛ��)�!� Q]��!�!�k:������82j};]�`��� a�MG�%3s�͝� ��V���@�V��v����bӊ���zVd[9/+��{�i�jv�zt|#�LLI�&}�R��a��r���OO�0 ��| -+wV�!�l$N���. �q��e��x�7��­�y��/n�9�z�ɲ��~u������7�������Mi�b�I�c��(D��1�|Zy�E�FA�9��kp�s [J i5�����4�SF���á�;���:�����*�M��|�R -=F��k����n,�9�;�9.�KMV�&kՠK 7�5�l�G7��ƪ)�hC�'�m�=E�L <}�t� $ni`�����r�Dֳ�'0 -C���z�-���^3\<��9RCQL ј��;]��5k��]S��ً���+f7v��3.D~�bp���V�o�0�޿���#���j"T��5�S��ǧ�q�ƪ�#ۡ�߅�v�� �}<�|���Kzg�3s�N�n�-��dR߅�����)���QO(�P�v!˽/>E�"/2����g�i�T��˜��Qܣe�� ]���^$�z�h����h�y{u��f��872�i����ޕ..]!�4��w��y=>�ʲ�K���uw r^�F�ѽ����?��L �~��y�llW��*5�� �"���t����;,.�ʱݔ�SK�DI����d�v�k���c�TE"q�����+�½$��C���� �WMo�0��WX>����VPU�ꪧJ�{F�3$^;�q(��'@STZPi"U�ك�y~o>��岰bHƻ��q�] -pڧ�e#�����O).�gCm��ÎF2�� xw^�e��€PK1���I�E)�SP�4�d���2>bX@�}����c�B%P�0�3e �������I�5a5�e�XC���D�̾S�����F�FS��� ��q�%�(=Jp\Gl�'�%� �H�o����h�H�ʂ����z1�|o�!� -���/�xo�` m�۰T� �5-S�J�kǏ7� -a�2����a�X�m�]�9���n� X����w����UBT���p����z޵�Wz����4b���hN�+�IH*�Ε,(;J�� ���o1Ȍ�Z�IP�iM~���1��鼧����gC}�����+�&�t��u-�m . -��\�-� ��&=X����_ѯ"��:�˒e}��剳�%Qm`�v3mc�wXO<�)�ɶG�o���ۤ_��J�͓:cf��Hq6ظtq�Df�lW��쎊�r=jZ�� ��4��L����J��$'�_��͖�N�0 ��{�(wV�!�nB�����q���[-�$J�ҽ=Y��$8� ���u��N���֬К�_M/9#�B�-��jqq��|6ɥ!�(6����ɲM]���( ^r�i�.��l�: �b]Ό�!8!��C�M2�0�CG`T8(N��~%�^^UV�[�gAl�3Q�BR�7B��$�g�b�5Ү�)5q� �fb}ee��|�}���[`2���������EC�Bl�q5�N�����G�-�&��cUy�.J (wV�e5v��ǂ�|��Ќ� %�����.��6,�!��%�D��d�cP�+���7oƟ�����-��ϻ����x�2u0q�(����}Ŕ�R� ��} -fOz0��qBzp�/��;@�E 0@:�ۻ!i��δ^z�d���K��5d�B��2x(�(+]��;�����#�e�(��1\l#�MJ��R� -���V%��t�0xI����[��3z.�1������j� -SR���OZ2h�����Q mt��N-s1�ת�I�E��)�0N~�a^e�>a��,1�,dr.�N�����#�)�F�w� -ē3��!���br��� -؃�9��٘B'�����7 \�[��ȏi��f]���m��ĥA�.X�z�$[���[����t:�. T(����Q����G}��38;+�3w��jض|���S���������A�c��U]o1|�X��9xC�.�T T� �=��3q���������僔�&m��x��<3���w3s�%_��� -��TYS��ϗ�\��r�0%��}*T�^g��F�ݍ��,E�`�8W�O�D�|}�Z/5�3�j��J���g��pMբ^��h�X&���PtIN&F�z���dK�,�� -M�l��zt�/:t��H���tXv�t���ޠiz�^�!�����)q� ����^�He+�h��� �~N�Z���&q0 -����Z�Z�¡l���(v�Um`�����}L�Ε��{aR��hV�=``)V�$y6��U�`ul�> -�wFh���VG��$�n5����lL����I���dlH����b=����d� ������8A�pَ��������Ko�q-����z�\e�7M�1E.�����b��>ֽ�h���ґ����>e��>�$c��!!;�v|�{R��*�\�b\�]����ڇZ�ps"=F��uT�'��TMO�0 ��+��i�P;4!�nH(�H�(q7��q�m�k��[�~~�ũn�[+L�_���Bz���Z<=ޝ_ ���U�ɜ��}�Œ(^�%GE\F� �T�4�s�xX���yN��|�e�9J���xT�m�� �j���l�ReJRS-�2��I���pe�U�Y��"v��P����*�����\�oc�`��FbΠC��1 �w����+�:�r�zEM�(��R�<�&���.0��U���21Cb�=ӗF@&�ǧ��j����~���2� `p�fU�J�U�1#M��m��X>o��?����d�%�7a�>2�a��[w��Kw�y��m;L��������[?եɝ�\�N hӓ.=�=�cZ�C~��VMo�0 ��W�/�n�`�ذ�c���.Y�cm�dHt��׏���i�v���"����a����%���-ě�kh���]����Wo\�������m(DCԽ�2~�uMW�ۙEʂW�ޘB\��3��C_��X�b�%bt8?�[��U�Av�J�,y���4�� $I���R]j�i]��/��!h+��#^͗WN�Ʃ��5`P^wĴA��0��@ 2�g �y(�:��J�I�1:�zxl�ul-" ���zo��� ���( �! ��ȶ�l>YwL][��2��l7����%"FKR�����o(�9$N&n�@���ϳ$�~�A�N��}]�j�t�:,�1�"�+r������ ?���� '�zGp?�x���[�Ӏ�Zi������?��]�cއ���x;x��kxŘi��޵�M)�$��n'*n�.�9*����u��'��B�w� �������ȃ4��쑃�kX5Z5�XG��D��ӏ� B���2�K�?��-�^r���>%@�J�%=�J�9��$�ĵ�e\��l)M�W��*��W^���[Mu�#c�"��m�J�yˉ�cs��1È)T#�����L�/�r��6���߽T�n�0 �����n�"�/C��cЦ�R �e�&"K�D_������-� ��6?��{��\�{ -����u�AY�Z��j�����G���J�R����E�MY&T�޷�PX�2����շ� ��ԣ�dX(zԴ�r�Vk���j)J��p�>=�!ؤ/�R�ML ������#7lX�Z��1<�a�f��)f���s��U��� ~�;�B :+-T�� $c� �OZ���5W���$�[�@һ���;ns_ [@�B�� n��>��U��� �c��m�G`��3�g�l�����ݜ�h���'��^��e��/������iC����"�����w�SI�BzD�Њ� -� -O��I�NO5a�!�Qh}�ni�w:�L�: -���ݳ����-���^�=�B�ucNo���\~Z�?�XmO�0�ί��}t�iBS b��il ^�/���\Z��NX��wN�ҕ�64 |���sϽ_�G�r0V�{��Z`$�h��|yw��Ý�P�ZF��c��O��v�q�] �g�X�)5��* �L�l�L��W��zp��X_���kW}�~}vwq�9`9W���W�.B*��.rn����Fj#0A�zڛ��M��.OO������wr�����כG���G�����3ЏZ�L �T�`�#r8}wR ��+K�\ZJ%�d�Y���i1ϔ;�I�S�j4�� -#SG��&cn�iY*�TGK/޴�2��MXh8��g�[SӍ1�� -�J<��*W��b����h��Ѓ27��D���1D�S$��ˌ��.�R��!�Sn���y F#Hu,����dt'b0T�aF����o�%`Ee����6[b�R�� X�2�� $�� /v{r�R4�+�Ѻy#V|�Hv�V9��(�k��S��x'�l��⣶��X�i�%N���W]��I�[DW9���~2� �eTW|�=͠���C�,��ͧ�ozP� ��T˛�'�Uwy� ��5I�K���,mU� �\�A�3�ȏI�aשg��k��Q�ȥ*��5�tA�2��#�7Q�b�S -���:X�j�3L�o$s��z�OXշS�u?�\zP�6dN�`w�E����L;'u�4P� -��%\# ���1��v��Y/��z���{��h�,L�v���&�6�W$k�)h]ج��xU�n31-�Λ�5a�raKXX����$�YcP���k@/���l��Tx�mX-��{y�S�46h�\���,�7V�}�ݘ @��T��Ԍ8�@jo��T���-�۔�z2R�<��1veU��Zf�Yyr}��l�R��>i4w\�JKx o l],�bq�R�ϳ���{����P���.L��I£�^���?�����Vx��5{i�����l�l���v}m]3�Oq�ٷ� -�����1�"&��0���v�G�xԌdd*�ؔ�P!�[�5ơ鏸�ۋ��\_p�����sE�-^= ��Ud�w�yr��/�Gfz2 � �nO�CE5aCu�:����b]7n'©�;���������h���=��K))H��tbZ%��q����Y��%M�R��@�#wo;˔\�!Ut���̳4����T�n�0�������u��`;]�ň�S$1������a��†��|�<@��v��&�ƻZ]��Ӿ5nU���w�� -n��#�ekՋ��e��"����‘����.Y[�����~�z�I�#�5�s�Vǝ�@5����;�h�2E�e��^"3�`�QK�:�1EA1�P�M4��Fv� -��f�e���H��Z����\� ��M�| -�~�m����l��&1�;�Y�����$��.d��R�V�s������Ȥ�OR����1u�9 :�f -��&- -��f l��WH�z���&����{/���oe��C��2,G9&��,��_8� ���7r�f�A#�؏c�@��1�����P�,��KӞ�%����J)��}혪r�,��UM�1 �ﯰr���� -��� XqAB�ĥa3I��������C��.��1N���ԗ��AO)���z�Jy��?u����7 -.��v�3�a�5g�o�JV�8�&�&���I+�-�k��e��]��Y�ǎrDMۍ1ԨqzPw��`6q4F��jn� ]�;����-{�mk��u��u��[�n{�� -� �uAߌ�����,�A�.�4:ע�!^�#� � ����C�C�U��"y�umQ�~�]W�'RGL"�)mD�Ds��K�v�hFI��v�^2�A� ������PC���<��܍*�ʃ�ǴL��N*N��2Q)x>�A�#.�j4�m��r�9܆��syZ1 ��=d�>X�h���]�9\Qw�z~�.��pS�����z�q����s�J8� �+�{��5`�vhL@������z�'�W��삯�A�����1O��� Ɠ����j�/�� �~��.�&�wrOH����xjவ��:Z\�hA ���_&��PQ����/��3,?1��: 0<�I{i"�h�+hX��c5ݳEv��޾����E�C�����������y,� p��K�F���a"o��v�s+�6N�nG��T��x�J�tk�W�K�nK[_w�B��*��G�B�:^��W�k����� -�� M�t���/�#Q��yb���I��y�9 长V��J�s��"��ӳ(��O�Y\;.ҟ��A��Q�'����CtOӤ�d��ZQO�0~�WXy�ަ�i�&&��F�� E�s��ٗR��g;-Ў��M�;�֤�����ξ����R�1hÕ$��'$S9�7����ۻ� 9=9�3A�!v�4�d�X}����q5�r59��=�YB�Z�Aru����3;�� -���IK0e0�w�޾jF�҇ ����f�L��ӗ=�Y 8R��+%B3��2$b�"g�cnx���ARՙ�N.���pb��\�L(v�<�`��Z�Se����eN"��֪)��9/����$z5�ͽL Xk;���ڏ�g��{���+���h�5�zy����z��Χ�m9��w�K��,�Rg��T�o`��-� � ��k��,ˋ�]�u�&%8b�3��S�� f�u4�7֥�����N�k� v�Y��a���K��Y�?#�XSS���j�d�R+��Bf漗��|V3�ק���t&�%� -������s���y�S�%��F��������w�Og@]w�]�b�84"�|��ּ;���g�b=�>M�-2.�t��^���`��r"�p���V� ɓ ��Y����偃�I��T�e�b��jM��V��Z��.N�+l��_s��%��7`��>%�� A��.� 0��]�̴�LnF�@�H9E��z���� �{D+�E����<0rQ4����rN ~�ZE�-�ӲW;�E4f@��O�}�E�+��q�����,a��v,�������랛���麎���N6��V�?{�=��M:6�|��������_e���C�-?���Y���W�����}\�:d��?���� !�� 0�V|�J�ҷ�z��E���hzC���j����ps�+m���oE8׸�<�}k\9wk� �k4j��h���ѓn� -�F5��n -��n�vlFs�pE{���ŀNv�٣!5����T�z�� ��߲{8E��#7�e~w���s��T�I�SEq���/��E-��%�΂��˹^�HF��61�Y4 4��kzL��]���N�͕MO�0 ��� -+wZ�!Ԏ BB��� irS�Z�II�n��$�6퀄h�V���שZ�lZ��Ζ�2�P@V���[�^��ί�,� -m0��6�����S�U√KՠM�'1��kz4�T�J�ʚQ�8�K�H,Եו��sNG���&�4 �wa��a����8��Q���j��}t�-��H� �|5��5��*�>Q ����R(�D�R����B8i�eJ/E~m�e.L�2���)��Z�H E� �ج#�d���o�d���8����M���JX��=�����m��TMo�0 ��W�/�n�`��6���ۀ@��D�,����~���Ңh;��M��������Ř�w�x�x+���q�F|���潀��I��L 8٥Fl�‡�bk6��� �T��t��������g���WN����8&W#�����6��������J��Ft�fG"IF�ͭIFkhlD�5�8i�\�k�^+���df&M � ���`|ʠ@�F��4�a�P�̅��"���ۭ7������j�ué��9N�@���{�ӄ�u� �l�a�ћj��eg�|i�gPc�#+ �� ƫe.Eo��1�q�7���*��_��kHŚ�6�#�8h�3�j5����ό��x(Ok�ѳ�y��P�k��C��{���!NŸ��S��k���M�z�����+� (��~��7X:�7�W���Ig�c�?��N�=� ǣ�jp��U���c��?����2�$,Ŕ?O�0�w>��;5l%� �e��8�����6���IJ ���{��~>��|�,l1� ����uh�_��u�x}'`^]�*"�bO��㽔���.6a7�Ȓ��n�-Ţ���y�xyZ=�4F��x吢�x.94JqRVW�qѢC�t�/�zU[�|Nr���՘��lEj��Ċ�.E�,�rk���ޗ"�����1Oh�U˕�� 9U�^е ��P�b�Ɍ�A��xȄ�L!�ǥ�J������S+�}�_��W��� 3� �2�K�{��wH}� ��{E�����g�����'������P��XM��6��W t�)ТX�i��Ҧ�{) ����,E�$���)��yW^{��Ep��<�9�|K��9�δٓ6�k���z!]v��D]�G7���i�,��?�������礵�`�G���T���-���n4�$�4Z -L�l!VDz�N�9��Mg:G�fH>�C�H2A�|��P��2�8�K�i�>�>���iEw��6^qL��FK�W:C��{hL�b }�k��TF;�2�uJ��I���B�o� �4 %�����3[�I�����Z�o�6�_!�}q�/ ���ز-@����(�s�H�<9���e[N��mɥ��郼���x_���<� ��J��w'o�d�R.oF�����O� �� h�4�p�X�:��I1-R5?����0�J!F���R>�!���5hb��` -������m1��M a� S��������㠚�(i�I\λ:��%=��ӘT%�P���>̘�1\"ܬ�џ�ñ����S�����5���2��|�|���}�/��I�ꇾ�_�Ys��gngk�~��减5��;f�NU�d�)�3 �,A�„�0��ȓ�u� ���x? -�2�n].�X�hZc -&Ѽ@rA�������j�4T�Z.%W/z~Z(z/�&�R;1 TEv�� �א�&/����VM�^���Q���  =�r������[P;��I�.5�0!b��vIS�y :RY�Uv����,y�5�ߏ��A��q�6K��TpC�����I���ik�*�]�i��=� M�2�/C�&}C�F{9˭h~�*c|3�i���,V��S��ъ�s*������W돐��{��9��)*l�4X,�y���cn#����-�;�Z���7�?!���lԛ��d����A4��~^�.�, ? Ưm���?�A��4[P2�:�`��$M#&���EWU䍵�Rզ�p4Yw8las�pTuf�/(���sj��Ij���S��>�m��=2W/=-����9Rʐ�P���&8H<�(��P�~;�F�£����r� �i���AϜ5�2�"c8��Q�9�C{ȳ���u�����,a�m_4�W_���T����aF� ��_��n�ܴ`�?�'�$1-��j��V�S>�DK��>�WK_�A(v��mB�2�l[.p����Pd~�ɢ�q������L���ʲ����c��;Vp����p-"�~��7�y^S�ӵ6�m�hQT�qR�w�{����s{ۏ��{�F-�IȽ.=�FnQӴ�T�A���mю@onnibo�@��I��O������&��r�!75u����;Gm�w\����N��g���r��'�a#�o���R ?Jp�]��Xo܋v#Ue�=�o���􌉣�<��b8����SAN�0��+߉ᆐ��xmoH�q6��c[�Miy=NҦ$$N��g=;�;����9�c"|)�[�M��ߕb�y���Z.�q�2�S)Z�� eFElc�G�����w��>�1Y��; -�C����r*e4s� �!��>�e��]'m��v��5[3ý%[Yg��;���a �;3�,]S�`^'8�L����`BG�y��֬ !疄�'|�y��l���P򳉊:�M�i�AO@=/BuL�`���\�<���)f��z�^M|-ɩ�Q�z�䔜���{��"�����N;�M��r������[Ko�6�� tj�ݸ� ;�v� d�E�K��F6JHʎ��;CZN�؎l�T��%�^Ù��M���!�l�U �_�~��JE1Dw�~�-b����ɍa�ra����c��Wg�L��Y�gt���r�L���� h$���`J�@����+���;��9رJ�x�Ja,�9�ybQƥ����V$�ˉ0b(���ATVC)�QpY�A�q�T%C��{I7�$Z�eg��K���i�8+`�j�U̎���Y�[�`+]0;+��i���[^�_r��Y$�+c#��ڏ���LC!��=OU��,��+�ލ�� ~�D��0Beo�oP�1}�7����x-�ߴJ��nЮZ����g�q�j����Ec��Z��![��Eg6�/�j1B�B '\�!O�1@�{�1�OJ��T�"�� -�v�dMu�V��j��關��5�J���t�t��\o(�4�Y��k:�JTQ�7t�?t_.Zs�,=� T�=��JPb}�ܕo�*G��Uz�h�gaD<�ۉ”�R`�3 �'����G�-�N�lD�/+�M�;گ"aM�3-J9h)ڸ�#I�'a4��$��K�l�H��yp�ƀ�-QW ,����V���E��Ъ� �jv%6PL$����G�ʵ�Wc�(�Z�P;Z���Q��tX���kBaƘ93-��ĵ���]�G`�p|�R�� �|����=���nǻ$��I*��[v9��WJ�� ��C6�:p�$�jv`�0����(P�ܡ�xg0�T 7��sdJ3��-#�J���.��JߣZ��c(�}�9j� �E�;�p�-?„i��+���hB�c���d�zWo9��r2�vō��ZO͵�-��S�̕������F�V5���f�ǀ �w��X�� vTE�)v.�y�Y����q�C̶a�weBk�ʭZ�q$�pc_��� m��g~��I�Z �I�x�k���/0y�vjG/���D=dWP�*�|�MR)�� U�TA�L��� i'���Xk[�Y`E>�S���O�y��)r]n2����L5HŃo�]���~�Q�/ m��Ty�M�s�(�MMvxǦ}&��)n|4�s%����q7\��{)�#�m�B*��C��������ܯM�/'0T��x�“os�8�L�P�����b�� vw��d��S��*�<����8���J�?9E�3境*}��'(!*��|v"�hn�Y#����0d�"��� ���KӸ=F��B���d`�E��ʦj|$s3_�r���x5�)�=>�]�zN���ZX�`�m�Ռ�G�E/ԯt�$�Fn֞��৳��[�v�f2n�v�&�*;�^5����8U/mw� ݮtc�mã�%56n����h:&����h�P�oT�;���� �:�Mͤ�v#��!�;���JZ�&.ݱ,,h�Rz�04 ����-���Rɵ�V~IJ#`�����H���k(��;�o�>=3� -g����dwz�e;���N��A��{����XQO�0~�W�,72^�ij@����I�&!ǹRǎl�4�����T���2�%����}w��./ -sԆ+��ÃwP2�sy�����ۏ���FLPc� K�����$q��rV�jq �&F3�J��\�+%�ׄf HZ�))���JIs~�0�E)�@iM{6���U�S�\'A�)i,������כ�������m�N0W,��5K�1�l]:.-�b�ѝ$����3�k�jr9>?[����鷋� �l�@;Syk�)Q�͌ՔٔL�0.wN�r�\ι���))�Lp_\R�I����a���U0U���Z�3��Z�9�AM�I�9�9Mm�e �2,&��i�n]R� ��j��'\y��Z�����kl��� �Կ�� ���_�gu�Q��~#�¥]+�Q�d���W����o4�U�{\�w�� ( �)�B�}�8bU�� �#�p�[�܃ޠh��G%X M`L(�P�n�b�+��`�] E��Y{�[�>����ִ~)��� ��� ���*�i|m�f5�;�7wXz�o8ͯ��5�nH��p}����3[�"~)�/��j��y�lo -��Su�����6(�ႛ� o��η��4 70ߪ"2 -*w\���~1,�n -�g�xV�'����2~��M�5��F~QZ��D�V�����Ѷ��ւ�%_K\D��C@�؈�!���{.���.�Հ��@��&~��.s���_�O\?.�'�"�@�/R�F+�I�)qVx�:k����t��ϏA��� >hGI���_�UAn�0��ﱚ[QHΡA�^[�� ���5E�Z��%e)nڦ������ Mշ�ފbB�y�� 8�[t�F~_�_��vyU�R�٥Fn�§���"lC� T�h����6�ۣ�V�C�©RP��Xj��_^ Q�@[ߞꪣ|N�DQjd�l�S����&�h��� {m1�Svڑ���F[ov�2 ���X�0��cq��(�0�Z�̎�8S6��� "O�;A<�BV3d�G'�x��c;���g�:��"3ޙ�u�[�I -_�z� �1B�S��6Ҡ���%��M�^�CR��G �9�BRWcF��e,���u��r&ٹȢN?�ܽO,m���@�J��?8`�ti?o��8v���D��?:��W���D6jwi �BE����� ��O�l�u�e��zNho}3^3�"��oH������UΘ�O/oD]��e�1�0 ��SX�i`C�i�^� �.!5�"��8@�O��,O����]�J: [,�z�|�h�W���}���r�VZ�*m煸���杀��E�� �نB4D��,�դk�ʭ&) ^ �{c -1�����XΜZ ���3�+[ �T��g0���:��qEh��� '>3��÷m�@�Ը��<�t`Y�RQ!ji -$I�q��A��hZ��K�cZ�J��Ȍ]9U}X� ��XJP��LJ�t �`zjm�]u�V�!��zo��G^���ݘ�l�;�� �C�u%�%�Ǩ�\{��syq��]y���IE.����Q�l��Ξ[�k�G�<�-��Ɣ�խ{���!����ߟ�`b��_�$��7gd�G�D���D Y��D�����ϳ���b��rO5�\6�q�, %7��D�G{: -9�#?=:^�Ə�0T��]m����W� P�ob - -��!��L�o�����v5��k]��T��'œ=O�0�����w�J��� *��q.�ű-��&��K҄"�D%K��н�=��mWh1D�l&n���*Wh������խ��f�*#cn�1��K�־���)�A (c2��[��v��1z�0Sp^ݬR�l��(r�԰�/92�?�(�4D����:�\M}&|�=��V��ca�©�8�6�C� -�o��~Ը��#H�x��,���l(Y�&X�޳���b���g������ _n���"�y����q�nΖ�@��irlL����у�_C{��y�|~�v�4Q���op�Ι�:��KpiK3���WL��P�Q5U��~�4�:�{ �>����wl(n��TAn�0���%���b8T�h+��q���ؖ����ZR�e7�����t��P����� �H[*�������#��d�K-B6��U��)˨��+�vl0f�K�Jk�^#g(J�s�"zF�18!���824�Қ}%�����b�1� 9[ -�A�"*9�� -�PZņ3WZ%M��O$�)�,��_]��W.� �ڵ�=q7p�l�+p<����#�Ue�-�~s�Nx�/�t{�'�JHz����DO��dN�(��NT�V&���ڼ9+��C��H�m�ʖ;6O�t\�Mh.����" i�Vĉ8���!� !��2�w�s�I}$׵i�E�p�K�7ۤ�������UP5B��_��V���%>�w��!]�o�RMO�0 ��+��Y�P�@H��� dq��e����ڱ!ħ�T�c������f��Ř,�B����7TY�X����� ��In�N dاB���2ˤ��:T��y�,E�`�8W���7w�֡�� -�^c -�`���xb~��cc��nh��z -t)�p�V�%AM�ٚ�lm��u��� -M�l��z�Ɖ�Dh*2�#�<�]��6�8�֡��h�����'Q���職At�d��-� �,�8.�}P�߉V��m�F�� p�%�L��p�[�ڵ�}�z�Q7L��6���!��P%���M�}C�j�vl���}�&�N��k�v�h�iy�m9}�~�/��!�(�<�zo���Zw�?��O ������u9�U�n�0 ��+�o�a��À�vY�s!Kt�V��I�}�d9q�.Ŗ��i����Y�o�[LL�7����&X��������'7��8� ��s�z����r��}��i�Q*NFA�q�Q��7��Nh��Ҩ��9j����E�+��ϒ6FB����-9��'m�Q�v� -X��Y�-1��H�F�M�h�D^�C��$����.��9��&Q���0���ˡ1��;x�B���W��$�M� c�Է���]]�ٳ�:��sNO�P8�vL�a�[�%7�gjyM �K<�_���RW϶P�v�Q�˯�G���:���q� --�L\�ـG�wdsaW�[�;fý��6wzku5 �& ���;�V��.�� ��e�!�s#ec���N�9��ҿ �u�g�_V��E��yS��c���}�K$��h�������mv�3�u��ٷG��M8��ֿ�XMs�0��+��Ía��P�L�0e8��:V+K��*M��Y�Im�ѦNo�%������zx>-����H~|����q��u}����go�ڪ/va$ ��s��hPU���R��ڑ�.T&�S%�Ji�v��:{#�Д����䅏�Tjaa���*|6_�)]H��@�4�d�l�o)2z9��`Rc �F���5���)�XQ�f�ש����/ h417B��j��:�W��K�A����L�E��l���� e�`J�D�����v��l���":A��aN��Z�<��:9LZ>S3�`��Uvl��2��ɍV K�zT��>�"2Ǵ8_}6��@l�+l���)_��ˉO��La��g�7o=-���Љ�^q����c�?�5�צ��%��&�Q�9�CCp��nS9O&?z���'�E�ݝ�)�Q���o��~��W�Z��7��^?���Fr� k/m3��ă��-]���N=�qE���D�L >����K3L�p���XM��0�ﯰ|/�����!�J=T��T=3w�m�M6쯯��$��%IC�9���y�y>Lt�,$YZ�UL?�)�u*�<��f_�}��~rqɬ%~��1͝3��Џ��T/.��)�J)c:�X -)%�` ��v��qrGH$ -#�����TK ܱD����T��*��:]��y�)a�uȸ�iƤ���cN�n�V$B -W�Ԕ���B1�^Q[��S���c;�'�r�y�ׅi��Ɣ0�d�l�T�������w�!�j�1 -w G��w��`X����9�HR!d�^��Z�B���Kc�.�3��?�fJ�c�h-W|�-�{�ץ�� -��&g��~,�t�ұ�����PEܧFţ���}ܿ&y�����O���r�Pr�T��Tj["����S�;6���yù=��Vr��znXx��S��L���gD�����'��~t���S���Oo^��0���S�Ea�f�ݖT����T�稕x>����)�p���QU��n�W�B,���K�ڷ�O(�����eJ;�U7t�Z@���삘�6o+\��4��ȍ�xy�B��~���Ev�ϙݴ2Mww�C/0,n�ړ>��d^�o���̼�_�R�X��ٛ��G���ˣ^l�O� ;Q�\��_�Y[S�H~�_ѕ��a�q���K��w}K5�FzM�S����=I"c� w�-�t���|�ک�>r %�V��E�d��j�;�����ϳ/��("�XFUk`L�{�W� ���@rS�4�H?���娧�rƚJcI���]�~~��� -S22p5yީ_��K���q,2�~ �ʧ'?�Ű�S��+��]!�ČCX&��\[���R~m��T�zWs�NǾ��~E�l�j3M�0/��B~ò� � �U�b!a�;S�c4Ȝ�~G�M�\�]��@L{V�ʻw߇�۬]wl����H��y���@C��r�L�MDo��}��n!3�lt��]�޸�D����9�n[ǀ���&x��,B{�є��է~�-��`�ˡ�DO�Œ�V�|�� I��E�xĴ�P�0�)ƹ�E�(B���#O|��sxIsk9Q ����jP�p=Q'�DT -8��5�s�erj7�����|���'$�<�ɹ�BU�o�������1�w�⍎J�3Y�R�䯤�2�.�� 6�֣��(�( 6h�*����zC��F�CV&%ZGܜ�P��xEj�������6d����t�cfԋhם�ۃ�2�$��g�� �x@� �Jx��2���F�ݷ�#�3 -q1�; ����v�╥�=�g��PU���k�E��)�������.�( �ܛ������\�q�����,��J?S푀6�-' bT�%�ܰJ0�q�h�<t�j�6��b9z|:`{�%a�CU��1����w��9���j$���m�+0�D��/�������Z��0H`+�Jg������0� �R�E�X=�;|��J�D�mVm��Mv��֞�j�Uj�:��B���> -F ��7�2���#6ל�{��Of$��k�,)}�?6݁��s��-�Ǚ���,�5G���#�9~K�®��Ǣ�]O�mވ�K�pkC:p�������qj�S�_�r�� $��م�%�X�$��a��d+l����],��F����0;�I�0�����[�� Le�{��P �c����%CO�9�J&���0+fb���`Fd�Q-y���6�5����d�Wt��WVh?�T�h��� �+~KY��*�Ԇg��YMo� ��W ��[U��!R�*Jmr^a<�ŀo�����ow���!Q.���ޛ3���*�h �0)"���#F �L�x������1���)'� 7X��֪/A�Z�*U�\] -����y�y��墐��&�~����#A20�P��qz��B!�� �5��{3k�vP��R�s�z6f�]F$v���n#c�et�\2�bƙ-"�� �oG�N��DҘK�X7�0T3e{��LU>n���-�����Z [(�Υd���08v*�gaJƿ��#+��塸�0�B�����*��� -6������ùJe��Es��MN�7�Ȧ�6�ђ���;@Sg/ �h���bjP?�E�#`�2Ƅ%`5y�������=: OL$Ӈ�����ی��y *���E��!5-_�Y ��Z=�SѥƝ��9&����s�����rΡyw��Zgn5A{�\"0}�l��AY�9��4�H?I%���]Y1}��$A�&E�(>h������#n�� G)��?'��+���T��+�1��MǑ�-/P�5�R�9B��1��b_�W�B� 8�֤���5g`�Uh~����ë��]]^̥�3R6>�y�$!iV楲����x'9[UZ�|i׵]�N��WsfD�R� -(]^06Ǣ�P� �S���`]o{/'Q���[�� -��(�D�2�|%��|�^ܠ� 5�:�e�ilBG#t���&���L[�n��x� �.&mQF��L�lƓ`Yȁ������B��.)�w���y2�4/�#D�C��rf��Q��v�G%����<���ߵgˣ�� ��`W��C�'m�J �U���پ��?�����1U���p17�^� 2}\1m� � �)L�2��Ry+�gC�ʜ!�U3��uʏe�vw �7�!��Q�&�s��q���|5N8'�c��8=��"��7kg����Dv�i��p ��P�&��E����� ���ic[ 9+*���XC��<ЦPXo�$6tx�#��n����M>T_�ڎ�noi��&ڳ��P���:!a�s��i��{�ߑ@lq�5�:zVs af�>�? m�d�^�'u��� ��-�SB��F��bw��h�:yc�k,O�u0�_�7u��S���~�o�q�l��� h��\�����fC�w��9|D��|��ZQo�0~߯�x_��MSh5U�TU��5}���/�vm��~� �Y� ��C"A���}�s�Wi�������NjNDH����O���䡫�wc°�����"c����^]�H�"��`FZM�|o"f��d -s3�q�����;]�Ch ���1;l{��L$��8 =�m&����i;�6�P���SMʨ�|O&���)��yDn�Z � ��2��(*�E �b�/�]/F -�Pa��b��(�L&��悮~�^K��� ��3�<$�i�� ��)(���ƭ�����Ms�1��0*�?@�#�����)�G֭�4^�xT2�M�#�{�;�߀��P��S�V�J�5��i�K�L.O��r+��6�� ���� � pqD���m-�l m��_ �lA<�S�%9��,W��C������9,���*�^���f�>4TF���ZD�$�G��j��ƽc-8�{ ����ga��Gnd�<�L�X�a�p��#?z�n-�1���c�z��r������9��}��?����&jep�V�m��CH{����C��������G^p��{�>��J�*xyP���I}��±~���X�n5� ��=LJ��A(�UZ�m��4,Y�W���L���� l,�����ji��au��Z��o���׸ۥs�{��K�K�2�i��Bꌪ7������ؽC�ͳ�3�jL�mQs�!�v�li}�i�0�8}�eԁ2p�ܠ�Z���|���xs�I���.�����w�^�m���`�����M�7���w���Sx�0x�ֽ����'cL<>�Tuڰ��EDz�z�D@fE/�jc�͎h�B��1߱�ə�׎�S��������u�e��O65hw2�N�'�8P�W;`����PT��'jS&p�����s�#W�o�������WMo�0 ��W�/io���t�a�u۱�e&�&KE����Q7͒~�]r �||�����]�`�m��:+@oBf�|Tp:>�cq�1Q ���p(֠XY�x�a$�`V:��k�>:�H�)�:�Xh��j�\�G#|d��s�����<=W�Ԯ����,���5�`� �*��z�9�v��U���>æt�������Ow���r�S�.���_lM�f�E��6�� -%U��2u��p�K�c/�J���.�h�,�� ������-�6�4�F���?S�S����஡�Y�ݽ�*l��.5#��� -l˟��#j|��r�E� .B�2��!���s� ��#�*(J*BD�6�[� ���[Eۇ���c���K��� �=%x[C�y����J��S���@�Y�4���]���0�(],�r����M�F&�.4I����C��H+������,B��>�z�̒���b%�0�dL��6`���0�4���OK��@b=� -f���o�ڬ�n5�s�]��:;ؘyY3 ����vR���@g!�;'_�A��F �ag��}}�_1F=?t�y�bg�&�$8��|.~m��(v.^�굵�ػ��s[߿�|�E� �6�M����B�N�`�7���U���͖�k�0 ���W������tc}���z��q��̱��d���� -�m)4�B���H�H~�i����7�R�Ӿ2n]�?������\[��` Y��,ck�P���e����B.�=J�T1( ��}��J�6���s�l�3Y -k�)�(�������B���lv&��XC�B���J2����j-��γ�5��.�ׯ����F��"�o�����5�]���BALV�H��۹Xi/w� �#q�/ȓ�?�1��%Y{�^��@�Հ ��V!�c��M�w�^�8�CH��F�5Я݂����Gȯ��Қ��x{ [�G� P��������3���X���Ti���t&�јͿ�v�"�I ������O�����`�uO�@!��� ?��0�i����^^ȟc��(�����8{�;o��Ey��_��YQs�8~����o ��i� -���!�����^\9����6��@M���,�\��h���z����3�eCp�y��C�QeTb� -WX�!�G~z}��6bTHHe�z��h�C7�7C�� ���<����14����� �@>��B%^(ˍ|j�<���b=� �wP�6�A���p�� �V%�c?�'�E%��`��Ǫ0��]�p�ww����E�h����L`?X�QA� �ʙ^�2��2������0jV�Jj��J�ռ��d�������`D�?B���|�z���*`�$|e��z��w� M%��.l?E��V��e���PJҦe�x%~�E(û0x�UH�}4x��H�}2x�%H�]<�£�7-+N����,49`ɣ�%&�lڀj$�İU��(?����� &�A�񷁲,i��̲����7h�� r��G�ϲ�uG^h��eYȲ�ԠYV��34G ���Ah��²~�oݻ������ �A� ���QT��8UOɎ3���P_J���2%1�g�Lc�^] -��]��a�8YK�(@l��0<����\k6��u4��8� �9� #��Y��r�'5�y)D�V�"F۫9�l �zcEh���s5$5�rD�Ec�y���-����~P K6�Q��L�Ѱ@��9[F1@K��@2)�K�����^ -��^�`N���p�^��C���<�]�v�)c�6B:"�'uK��8�B�����&���M���.E?�/���ov�hE����RU([�I�� �sʘ�<�w,�<�Q�9;lg�w��nL����Ms������g��`�-n�^��Ç��Jf��(<� $1�Ƹ6�m��2JT�T���?�#|�,�)d~Qm�a_����]�T�}�N�����D8嶋qY�����)񪃨*vd"R�TE>��|� -��X����J�b?�G�� +qu��.���U��3Qs�y�29��� �Y-[l���L"��4K;p�e'HS/m�k!����7�*N�su;;�����tH�s�k��RW��+���>� ��������x�IpDwr�U�S������:V'ޒ�j��g{��AnӖN�3���m�d��U��y|����P{� -T���~)��Z�{�j�v�%� �R�ivS������X�n�0 ��+�o�a�S� ۀ`Iw�E��X�" ��&��Ѳ�fC\̵�G��)ڎ�w[IJ�Nh��w�����:j�л�7�)��^�\2�:+���{�!�К��dz7Q�#g9%�BJ������D�-8�8Tk����hzEH ;*s�:�i孖Gq+Q���r�)��y��%�!$�d��R�Mm�B�,�{�.ByX������7��OA�H?�"�Ѱ -�����j$�;s.�-�\g �V��=P�R�-�>�+&P��^�G�N�B -�O�����a� ����-� ��x�O���� -�̘�)�C��l�V5�J-�Ӝ�6�"��b���)'��-����kup��D��{!4j����D- �~!~AO�� �Ka�� R�xeP&qT�թ���Pa�:l�m*� �� j�'G�p�ɹ�l�^�9���1�twX��=[�qA�Ek Y�9P�R�>:�$�Mu�� "i)�zl:���L��׫g8�uW���<9n�^b�tTH?����G5d�e±T����@�vk�s�F��q$���De��W(A^ -��;�Y�"{�%���٥�Hu�R�G����KH�Kk�n/5M�&響��O*ף����8 -��ŖMO�0 ���V�pC��|K�68qISo5�I�de���K;�`��nu���c9n��P�u�U�N�� P ]��g�azut�`<�Br� lV.c���,I�54�)�r��'� -������ӹV�j�@� -����hf��i���E�)�ϊ��>c3.2p�{�&G9I򫌙E.�Q'�e����Z�R��h6 �%�1]���E��.�d�ݢ_X~eBv�������i1� "�s��E�}�:Х���=��|�H��!*����F�7��k��� �ŷ�Q(��H%�w�M��G}]�?�<ڊ�:0 ئ��1׻� ��&���-�u��?2�C͐ɯg��� ��6p�r�<���k��� _Y�ap6�v-���3�pd�V�<���]�_�.[� �F��9Mָ�7�W[o�0~߯�� �!�t�MB���I�c�6׶쓮��sriڭKi����=�w9'���̰9���M������Ni;M����_qv1>I#bdlc�sD�:Ihu�s����&1H�&�1t���;g18o޺gV� z!���~�ː�c#X X��u��syR��F��PKA�sa -ڨKq���8��^��\�KO!�"L!�����Q�Bx � �7��w�3�e8��F�3�a"��:�F�2�>8��Z&0���% �Yu�Te���t3����\hي�3sWŬC�� �}a�2���e��Z���PHt+����g"�_�F�.�EftY~� -���+�f�Y�ceM&�X�$p�z\��$ۤQ��-�;˙�@K�E�e� �8he�EҔx�aSS�;�<^xO3�Yo*Tv��}j5�a�\5�ڢ�y�G.� e�T��.� 6[d�̗}��"s��I�4lgD�!С3rY��-#k�{�D[�^ �(�.w��_7;bX�>�M���ۭK�oB)�KT���%�S����w����G�w�S��(C�7*.�u>y�C��ߡt�|zSا�`Z�����@]"�v��r����91 �\�']P:���b�9~��H������7�)�v�k��~�5����T��Nߤ5�N+%��ՖMo�0 �����0�)��k�0`�N -Ybj��(Ht���Ɏ��c��vȜ�EQ$�����|���� �B|���N�6�?�_�}p>=˕�1Brv����ei4�״�8�,%`Q[�~����@6^��� pr��K�O���Bt^�3�׌N�n���2����\�P+��-�����2}IŅXHQ@d�F�+Mi��M!|]Z�p'�ΣI��hR�%u�6�*�I;P��m��.1Hp�Y�_����7>�"�wsy�4C�eH4�;*N��6�:(7R�po�FO��Fv*e�dj )tE��}�A���e�@�~��.ڱ4yn �20 a E,�W��;�&�  w}���Gq+Rr�R��[����=G���+�7$��v�sH�R�4i�at�E�b��7f� GMUC]\�k���������`O��*_*-N�j �D��S���*"�Q��8�Z��(O�b��P���ꈪ�ôxU���ԻT�'Y�4�8� -���9g9:ʼw�_��N���O�i8x�uA�|��B�:�Wh.F[�KQ��]'��_���;��C�ߕo�Sڧ@�K+�^��ߺ~� ��ߩ[��=]�H{���B��KO�0��| -�w��PR$ +��lj�cO/���L_�7)mU� H{��������FNL �E_ȃ�/)�k4� y{�{�P���^��"��S!+�p�ei� U08�y����b0v.m�s��#:J�ɘ�^�����̦�/�iO�f ��ry��u��F������(�*���\ȁrR+�z5�X��u�� ������=Y,�'�u�P?4����6p���8 -u� `��V�x��yx�XϳM�y�P�BZ)?��@4�م� ,.�7��MК�P�M�t��)m�I�bʞ!.)t��u�uF��G@L���\���T���U��YT����_��5���*M�;��_]��T���J�V�B�� ��q�RrW ^�u�Pn0�� ������~�ʚ�P��kQ��@�ؾ�,�-r뎴?A]�v�[3,S����ռ�RN�_�u�yV#��VM��0�ﯰ|��BIW�.H�p���Ƭ��ii�=�$m�ђ -���3��ތgR^�/V���P�7��R@0h]�����O��Jq9�(�W9 6��5Q|W���:Z\�P���b���?�������F)�j Ge��i�_��h~!D k�`sp�}[�CT���U��-�ҙ�2TɅ��Ȥș�r���;�T2.�w[�.(��؆����h��v�I.�% 6���}{���nw�Z��LA�&2�:�;+���˨�'H=b��6��Л H�"��uљ.笗��4y*�� x0'�� -H��~A)�y���a8�WB�ݎ���u�����W�0���2v��U�.XX�*�}A �� �'lN�ѿL�uYi�L¹3��Mq �TH<�w���b�+�S!sDg��=�r�c�(N޾�Hh_J.��b���>�D���55c}��D�������3��J��{�"�EKq��VM��0�ﯰ|��BIW�Zv��@�pڋ?��w�eOK��q���n���GO��7�x��]c�B4�*�f��3p -�q�[~x�����E����%g+^�wE�N3_{���*bP��6֦��� t���+mHH l$r�D� G^������ �J�8{�C��Z�� P���ާh΄��������Y$AF=�&i��}��FZ�r1N��6uJ�QI��;��*OI?�����\�}��Vv@���}��E����q�ҋ����%8Ü���}���2x�u����^�����b�]0�F3.�^��L��2q�`���E�,�U��Lr�;N��?����1N��n����&sk�����7c�� �1R�`�%$o�Bz�I%�.�v�)Y�Aj��m� -��I������9��Ą R1$i����5��H�0D�-s&�TB�����0 �����DM k�%G��lnD�����w��B 1oKo�s*�`�� L|�j�7�׸U��ez�����8��Ӝ�%t��LY���sLL�1-�A������X{5�36��8]�� к�v����'T�����ۦ=�Xgk��,�Ч㣠�5X8ޮ�s�_JBH�2�U惂��#���! {Y�__���̍����i^%�ȥx҄��&�B �%O鿖� Gڗ��C�v�{�֥_��8�m�Tn6�oҡ~��)!N~ՖM��0���+,���BIW�e%$H,����Ӎ��-{R���I�]�A��J�h{2�<�;�^Ն-!D�l����pV:��C���ݾ~����*�F����ƂW��}��j�+��jf�$g������Gg18�o]�9���腄'��~������VV�ac��g���`/G�jY��08[�Km4���!H�Ʌ��h �-)��z5�9Y'��de�)_L�����]HB����b�r�Q�:d%�ZX�#H�)H޴=����YO4�a��'���p&ʈ!�p�X���,� -�����!��'IO�� �a�)6mq7��?�E2���KB��X�X@���q��N�ԏ����E&�t�O*g����j� -(��� �f�J�ƶ���nWY�!�sW�&���ʛ��(�� -V�j26�����U(g��N�Z��@�렣�)���7@6���Y:��.�(�?��M�ȵ�N�� @�Q���S~J�/��;�I�����(w��ew�^���~�J��P:~JCq���)/#t�]�h?�F��ўx�����9{ S��}�I�ğg q��WMs�0��Wht�n ��e�遙N���&ȒFZ��߳v��i>���I��V��������0����)?z�X锶�����y������X#�`S�#��IB��Ͻr�Lb��MKch㷇+g18��8���腄���|�ۘ�cc�#X��u�vf������nǍ6[ SҠMI1���8�{1��J���mf�h%y;N��wAݡ8���0صU!݂hn�@h����6���]�Z`�J��K�eʧ�D luF��N�A"�U%0���% V7�S��(��H�Ĥ+�4�@�B�Ȗ�a1w�Q�:d�BX�K#�)����-u�0w�#'����,bha;r����э�i+�2�(j�ZT�akmt,��N�rd�;��؋@��)q�Z�ua,�L!���jn�_�4�Z��6�(�xݺ�ɰgO�����޲սf�aN��K�&s%F��+�z�r����Q﶐u�.����ܠ���/��-�S�FO��i7��X���Y/��\�ܩ��".d_���:�E(�(2�L�3�5��;��Bb���|*�| �S!���y&�\���P:^�����|�t���v:���'���}�;�(������ݟ�oҺ~��m(N��WMo�0 ��W:o�v�8Ű�b� h�S�B��D�, �&�~��|tM�� ��fI���(���Ee`�!jgs�z�JZ�Jm���>������P#�������Yƣ����-)�A ������7�L�� -����0z���j��E2] qAh��.lm�Y��ª��ګ��H����D���:�BM�\��a�Dd%Ndmhd9�0[��5� -�ԯ4l�� -�3�U��r�JIja�� �3W��#(*i����P����2`��n�a�5��.R��5���͜Y�W�@ ��0zU"m�Y[t���w���m6�:X���<�N������`hS6�d8]��f�2�K7s�='�m%����j�f�l�K�YQ}���� -����,�u't��?q�����tO��z��i���/oWH��l���zg?���_e�j�7�>����C� -�Uɿ��HOꚋ�F>�����u�e�([O���)�ף�;�: ˾����:ʢ����玀�o�e�����?��NP���9�s��׮7�.���t��4E�/�S��:�X�v>�����y�v��ӷ���x����'i�?�A� �~ՖMo�0 �������0��X�=X�S�B��Z�" "�&�~�G�~e�{Ȝ�EI$�W���r�p��D6�B]L>)@oBe�C�~ή�?+�����i"�Ş -U3�/Y&�I�c���Q2 -�K�d�����9Gw7�D���R���v�Bu��g9�}E��~�k�*k7�����pH�����M�.�K.�\;BĚ�� W�li��M��t���^��&����)]0�ac@2�F���Elc\m���Ёe�M y�<�&J��`��\�=�G��q �"�����'�&���l�w�CN�){�Rk�u�z�ȳ��q��� m��<%x�P�u�a����x昇}HI\ -gsli�K�K�tx��O�@��Z��ы���Ga�,�\5��8`����N�BY��E5�2 �J�~5����?Ac+�A�f�S�r��76}b���^V���i��� ��/�W[o�0~߯��No5�P���/�&�>m �m�N��{��$-�2�Q��-v�����g|�\(R���蔾=�47B�yJ�_�}�����ɘ+�=Ac�S��`_% �F6��,GB��dV(����Fg���T� ��p�+J4[����f���޲��2�e-|m�q��F�ʁ�ӡ��ᘥ�d��Em��2��z�%s$�,�H`n �$۶㤉~W���'s������=JX�O���Θ�@ z��e)�̐ܰJ�-2%c��f���V��Nڀ3B�Y�*ǴIL�pC�Αu���A(���F�����2��4� %����|��Vf�pN��֒Y�@��U��as#��Z���{]e%8��a'�b�.����nZ3�K�BUb�켃p/5���� ��߀�/Z���󒘘������ Zf��k`FOs��p ,gb- �I�Ub�i�6'���� �Cx�v����-;�<]�����'ij���nOƽ�=�^����!eX8^ z��cJBH�2u���SG��į ǰw�u��}�=ʅ��50��m�Oz��?�D����W�/cmÑ�C@��I��玲K� c�繛��� ��M��!N~�V�n�0 ��+���a��C�v�!۩@!�L�V��v���(N�%�2`�s3%���x�X�m�VtzW���{)�_�[����˻R��o -cuJ��]*eC>*��,4����R))�������;�ަ�4/�pz )h�\��K���7B�!pu�7�?�K5�iG9�&��ӶecH�>�7���eg�A���m`t+W�U��-�>�1��/ճ��p�5�cF|zϤ�iC�\j�� -�$4��a� --Ҷ���,�b@����T�5$1 P���Xh��Udy�1*��e����8G���̆`Ϫ���}u �v�����ڇ����,3��E��m|��2���ҝ:�'���}�&]Y�f -஝� -�A�/>s�H���=��‡oЁ� -�q���E��U -��a(꩐���R����m:��/$��F���3��Og�����g藇a{<����T�_�ߤC����?ՖKO�0 ���Q�pC���I��vISo �I{e��d�����h��?�y��y�Y �5 ��^rF�L�i�_��ל�{�Xj�Ȃ�`�s"wE���ev�5@z��d�u��8�����8x;��ș��j�7�^���&���V��GՄ(��J? =�D�䅤�O���@���,�TiE���Y�ՒA�׊��a���T[�V�K���Q���pU� V�6B4��…Ĕ��P}^3�j�}�'��XJ���0�v‡� ����ld�K&�C��ƷS�� y�X�ue���"������� �u1��-��[#��uOP�n � w^���n(�wա����此p�̭=G�l��n�j�U� �M��� S�ہ��J����E��Gb��VMo�0 ��W�/^o�`���0`���ԋ,15WE$&K���I�csPd�QI��HQ*/Ws+�"������4t���q��ͅ�ӳR[�H�.V�a��"�&��V\Ġ��-�M���>��@6�}WI -�����h��W�u�� QŠ���m�}��E�7d:�}��BՑ��\ə�����n�Ĉ5Z�u%������N٭�&uJnHז�C�� ꀞ�^B���3��S��lnae��N��'hKB��+�ljK�B��:�d��r�=(Q�� ����fOtS�:I�V����߀�pt������(��_���y�.�pj;Z�� ax����b7�^�A����jPs��_�� ̈́��5z�R���p���:�4!��ԃ<O߻ATn��%ر�9`�7hN��0~̗��㻏DM�[ �� ��\�CHı]�A���x6���o)�����s�R ��H{�e�)N��V�n�0 ��+�m�a��C�av����,1�6E$�M�����X�pn&���G�����ƋRvJ�f�Z -� �R~�����˫�x��`pȥ���;��Z�:Z�.���H�j���O�7(���_!� �Ao Gm�7��_� ����-A�yX��rH� �t�!�#&���#Z�v�3Ң�<�{��N�v�!.�!����[��1�;H���K�� - ��O -]�6Tʕ���H�g4[�]弣])cSyם� �����Mr��C��M�9n�B��b:<5&�&�Ai����v����.D -�9����j�`��FUc�&PO��;x���/K�ٽt�ίI��y�ಮ<\���` �Es#��؝��gx��[���?1�4����ًn(���Ca�"���[��.�������s��.�8�>]�C���N��C�~�_xG�U�+U����4��o�N���WKo�0 ��W�/^o�`��`/`��z*P�kS$A����O~�i���"�n������f��P�ʚ�]/�2@#�Tf���w߼cp��J��!@T6!c�{�$q�p��v�0HI�����:~yXYC���p��D���-��l��uZ�+�w�F�n�|�%���&�/Y�=>�2<�_\P��\d����RA�J+�g̕�V5e�>h�AbiE����.kᕣ��uM��!0p0�����H�7@{�����49��:�#���2 l��{�k��l��Z��P����M�&��n�mae�w�t�u�������~N� �i�8�bͪ�f3����P�S5��37RO�aP� -t���: -U aZQ����̼du� -<�/�KB@�#�w�7{��g��s�;�b�z.`&\�E�sIJC��9r. r;��T��s��ў"��h����K}�ش��������L�i�$�����ՖMo�0 �����0��!��Àv=(d���)� �n��'ˉ�43gE��,Z"���>���ڰ|�h �n��3����������=g��\��m(xE�>dY�\�nf( ^r���� ��/ВG�o���Y����������i����6V���0��3���6��%��-zx�m��2~ I_ -��@��쇍��FӶ�.�n�f?� �(��A����kGQ7&q�R��>0��#K\Y����2ں�f�Z���ٱ�� Y�L�VCL���r�a > z[�\[�}����|����!��P�����Ңb]����q$t�u�,h����, $���J��a�?�{���*��� -4j+4�y <$M�;�)�K+��U:��\��/����3�=x�g��}9 -��7h�L挃���U7�ӦPS�(q|+�p��6��}D���uk�]�#�M,B��NO�?������q�"���q��W�n�0��+<�Q{+ -�A��@�P��\(il��H����w���b!2 ٹ 9�7��B)�Z�V胲&/?@��\�e"~ο��$�jvgZ�llB" -"�9�xu� -����A���,*��Ż�k�[�2`d��� �����Mf1� M���s�"j^(�L�w���m I*K�B�V*�TiE�D8o 3¼Ad9.d�if!��Uwf�T��O�l�C�#V 2[2#JD�T&@�;��t���4�UZ2*��3�޴�e�?H��:rTؼ#'�ci�4�o`;rC\�T�:)�H�[��vݠ½\b0�z������������I���6C�S�m@vA��@υ�۽�9s�J���/��c�K���h*����Zٟg G��Sg�A�@j��_�er\�l����2|�~���괨p�uO��Q$r��GV��z����kp�geU~�>\�S��:�0u:�<�v"�z�g�Nӽ?��C>Fm���N��Ki7s����n�~��T����^<&4o���^rMN~��#�t���2���P� �fj��7]P*|i�b�?��_pC$~ձ�OkB"�[k����Ч�"���/�.�8j(���MO�0 ���V�,pC(�$�@��ۤ)M�5�%Q���ߓ�k7�p�-��_��,ƛ���������Z� -m{y���f0 �22FH�6f�$�7�'k�K_���"��yeL��8� r=� U,X����>��2�&�B9)T�\�g���@��%el.MD�$i՛+u���m�|�]7��4]F-�d -�r��[k��*hO (��Ƥ �����O� -h�S�+��.&�G�eH�vC)g\`��}w�o�1$�����.Ś@��=�IS�����w0��S�R���� ��/�z@�����b��A ގr+�<� PK�c��nY�M�{�󤚙�V�c�/�/�j�KH�fn�/�U�N�0����4pC()E $H��r� 5�^�ޔ��q���8Hܲ����I~�L ��m��G�����}*����� ggÃ\��6|B�N�,F7q -��ʂ��U�1����‹���d|�-��`�3+���𮱫�]�����B߱��B�ƾ�f H� �kI�{���e�3Q�'!���08 $H�e8�A��h��ե���� -��hI"�BY�/]�& H�E��ĩK�1�ۚm���z��[Fs���V�Z�m��N���b�8��Ka�]��ʹ� -|t���ghuH~')oQ����M�:�<[s'%"�U�%��#t?m�.'�e���A���hU���h��iE�5������o�7[�vv)���UŵU�|��7�Q"��䏼��|[�����n����r ��U�N�0��#�i�Z%E�+V���r��{Jͺ˞���q�&�Pġ�@�{U�$����إ*� �-ƾ��@H�Q��� �:� Cr�XO��Zdϳ�Wd���/��i�.S�oh��5��9� -m1>_��΃XC��R�W�P� uXK��N{�u? e{t�w����}[����;o�0 �w� -�{�f+?� -t�c���lbD�Iv�_�v���n�����E}=؇�J����E۶�}��������B -,��C�n�TR�;��^sᨂ��1i�Yx�4�p�JA�������xg� ��VB$���42}_DbZ�w���(�1��xs �$�=� U��B=��V�����Ե>���O��|U� ��1o�0�w~��v�vC( R�*�`A��u��œ-�I��k�H]��޽���\�g�d=W�X< �Z�*|}y�/V��4N�s��sX*%�����`�*E���;'�M�����Y�f�-#�>S -�н{1*��zP�)k�Wco3E�A�^Ԅ�z��9]�,!A,g�$����eK�s��������'��I���Ϫ��TMO�0 ��WD��� �v�LH;LB0n������$J�m����Chc�F��ѱ�����p��$Y�uB��� �)�u"�KD���W���G��K����Et�h��GC�4��`�,�$ͥ�N�6�C+��(��3�C���"�T����R'MB(?R�b��q�hʤJ2� �‰XH�EDMKQ!�mE��7O4���:,�q+ z���T� -�4S]�̭"X?�Z��ͅ���aPs�C�խE,�2�Jd2^fq��������E߼f ��>,�^r>6�z6�e�쮮Pw#������g�[���W�.%ձ�>���^��w�B������h��D������4�Tj�� q���O%�9��|�~���OY��wC<�;����mPN^�5o���3;� *��W�n�0 ��+�o�a�St ء�:�R��%�֦H�$;M�~��x=�E�6� -�J��D+��" 8/����{J@s#�.s��j��#%狳9W�{���� -�~�2�f�����4��;NɺV -~�����撅��6�-��otk9m�g�̹�>0�Ƶ�1J�j�['t���ߝ� s$�,�H�G��=��g{�Ch*��,�Q�\̓q ���_������5S���A��l���T2�rj�B�H��L�=��๓6`s 7�b,��� [�6!��8�ө��Hq�B�-sX R�j��ɚ.� h;kp�*� ��b'�:�� [��2!�G2K�&+�y���DYK�TJ}�*S�����������@��^�#�H���q�3�_�qZ��Λ�� DŽ��K �ʰ���f+�����P�Z��*`X��Kp# �s N��k���d�_Ȗ+�a%�ڝ�뷌�$a��h�K-N]�*��O�3�;õ(�a�� ����4���ӧ ��Ǚ%�b�;J���'0v-C��G��������w�O����UMo�0 ��W�/^o�`��Z(����r�e�Q+KE���O��]ZE7(rE�G�GB���2�@ -��L\�� -@�\��C&�L�\ �_��� &ې�9���$�4�s_���"'����6&>��ݒ\�&hXYa�Ra�b�h��i��w��ٕSs��GgW>�my���������о[��; r1x��c��䞆�e�����x�ek��<����<�O�G���@�)����^JG��f��LzӞ�~W-�1���ۧޒ4�&M}0��d��g�tخ�H�I���#׮>��u��~����Ϣ4is��VMo�0 ��W�/�n�`��+:t'w��,3 E$&m��䏸IФf�'K����DRN�ߖ��Y��o���([�y��߇__� ��^�J� n6!sf�#I�l�殴o����VZǃw�?�|}��� -0r��I���Y�D�cz�*k��b�[��S�&@q$gb&u�^K&�M�� M�Ʉ[��p�H��Q�D�ҪB[�h����q��]��f  �Bhҝ��+o�7.��Tnmi���:�#�-+%�[�U�E�}��8C�ǎQ� w�]�X2C��9����qh��.�Ώh�VGXR�S� �&;)S/D��-[�g�J��&�-2ܓA������5�eK�4 >$���������� �'U�C��~2�'��^H���>$�0J)�=J�:ot�vyR�1�2�S�gis�� $�xU����D҇�ק*�z�N�6Z�����4��b���_Y���TKk1��W ��mo��*�"Z�Ao �͎nj̄d�ǿo��Z�^ ��y�{ I>ڭ l�M���h��. 1�N���w�22��6�av�Y��k\M��E΂W�1q�{�-���rB�X������P��ix�+��}����6��rdORq!�X�V�p����Ѽ/�k+�)m��;H��IU������h(Z�� ����N3��x�"� 麯������>ja�5�h�Pzf���}\�r ��0�Э����|oG%�~��%"���#�%�KPh���D>��V�7C�$_w4�2�*Jܘ�w��Iޒ����m�N�7)n�h��¿U��� ��TMO�0��+F���m��ruŹE\*!Ǚ6^\۲��_��ORD�e�z��Lޛ7o����1�������\ @�\��:�����ӋT#p���(��M��i�K_���"%1(��1����>��r�ZΜ��=F�'X����w -�l&F�� �T9)l����!&@��%eb%MD�$i5w:�\MU&�67�nU[i����i -�r��K{�]<Pn����$X�C�8�:N���6X��s�;��>�&�T���e��Vr�M%�5ܯ�B^\a`�p�����60�E��7~ -�忰n�=猓t}�z]��'�!����4�ƣ�A�� ����d�zM��KWt�k�Y��yw�'R�w���{�G[�&��wU=t�W՜*�|����T���wI���Ys�]������~���KO�Cr^I-�h��~0����n�0�w��t;10U( K���.�e1��:g׾���uK�:0���������9(&��e�@ 6��|���Ο6��4N��T�Y$��ʩ����`��Ax�˃��9�K��Q��giv���=�RІ@��‰�g��D�LՋ��v�v}��o(c�7G���-�AG�kȌe�ET�~��� �^��~���[��ɶ�K[~]����=O�0��� -�{�|U(Ih%��[�9��l���=��HF��{�G�tv���Zp�6��i2� P���}Ʒ��x��"�JK�Y��g�"��B�*��-M� ��Nq�r�:\|*�|+6�Q�2HŚ��8Cـ�R�u�f��#�ReГD:϶��|0Xz(9k�>����4j��z=���J����F�=8.Ψ�ɦ�"�.�A�4��/������ҫ��g���l��#.;+C��7�������'��+=>̞t���p�������N�0��>��6��T�P���%�q��U�.�/!}{L$��x�����,[ηs��>X�B�e�Rj,���%s}�$���e]���'���Ƙx��s�����ֶ�`qM����RXU������A&? �� �bU���}Q�[������C#*�r��;T�j|=+F�ɥ2>�-z�� o3Y�c� E�2�T�� ��d��3�s���ތ�^F�B�*pr� � b�<��o� �3�&�G�ګ�iҳ��Zwb���= ��/��Ih����.�@����B����gО��{����� -��8v*�U ���7��n��Ua���{�� ��쎭괿�qGТk�D8��t{���cn^�7qML���+��$>��A�T���p���&N��������I9�q�L���u��.J��_<͖K��0���)���[)v�R�Ph���ma��I|�R�[B��bT���u!��D6;�X�A�� �@T�"g�`�ZCK���l��3�K����L�QL�������&1���:�)�OG\�Q�<X�cq�.̔�����Y��M_�W �5�p���T_�|�3�.���= ��vR���q�m;U�=%#�k�mi�_&��5��Q��P܁�굿��=@���3��k���)m�Y`X�=<�#nҚ���/�k��Y0�:��X�����T������y������ȟc�(�6���8��w����,�/���N�0 ��{ -+wV�!�n� �87�)u=-����ۓ�k��8�� �[m'��٩�|��-l(DîP7�k�+�� -���pu�`:�hu���X����˲d���W�;�,T�l�M��[�F8(p���5R���B �� GvQB��q�/�O�.ӗF)�R�H -�h18�Mi��]�|SZ�&b����V$�T��e\wf레�xI��\��Ƭ ��aH5��&8��O�n�T},ώ5���?�&��x�z��`�H �� -x/��'��@K -����T��N䒫wR��ԍt������X{�< ���Ύ�+ ��T�!Q`!�N��[�L[[j\�&Y$��t-6����u�Q;����wl%�=먹������7 ͙��1���OO!������ֿ��1i��7/��n��k���V^}��᱿�^�r���Q��ي���*Wk�T|�:?q6�Jed�,�m��&%S9����ݮo)1(�֭1y�����Y��襢C�V�02�1V*gc�6}�gQIO�uҴ9��dU���8�~ s������m5x�--~k�����6RH ؔ %Tewn�B�l "=Ɇ��� �����@k��z��ŚQ�S� :G�.P�K� -�@װ ;��`�0D��@5O�z'`�C��@��@��@��@��@��@���:ԙK��܀��֨+�xiW)H��ck��f["����\E���=b� �V=k�0��+�ۭ;! - --m�B��K-"KB����W�?�B3dHL���}轻�3�t��l�X�dF�'wP2Up���������M��|��)�ӏI⭉.u�v�.��X�B����g�5I+��2 ���H MoR��u�fN�6�Z�>4�o������H�:�8��-�<炻}Ft�  -���.#�x�B�\(�i��@� ��7 LU:r,:b� �b�IƠ���׾̭�EK���������)< �Hw� ��5/7��ݗ��o��d Rtx�R-6�eqi��5*K��1��9��W�?�B��Ky�����t�窾����G��\�9%[��6#I�i.�r_z;�e *��)~��X�}��>��0��6���C%'��C�~+���Ŕ=O�0�w~��;5l%�P@����Wb�ؖ}����4i%T�"��}����Y.f���c2ޕ�zr���ڸ����<\�0�M/ -eEJ��]*YEn9��$TA���!��ecm>�x{D�,kNԘ�P����dcyzP�H�׻�6IH� �L���-�M9�H�Qc�6�Hc mKiM�o��CG;;O�^I�ժ�&M�� �סӸ�e�u�5>�HMt@ې����C���� ރ|eBw�{�wD&�+T+���'$T��$�=ɻy��:(��O{?gzr�I�\X+E����JHgZ�3~���@9"f¸#Q����@n#.1� �\/��G^��;|� +xW�~�UM��0�����pC(�VBpC,H���r����ؖ=�6�z& E%ZVT-▙�|���Iv��-l1&�].^-^ -@�}i�C.>z{�Z���*�V�|إ\l��)�Z�M(�n�d�Z@�Xˉ�|�Ƒ�jLAi윃��!���j� ��' �"�sQ)�P��$Sk��Eh -kt�Ɖ%V���t�0�{k�y]X�� f8�h1Hо�y(�-�a��k�"|Z9("�R>FC�JQ�m�{��&�pCj/i}RHڻD�������'�U�W?���i�P�dv�Y o��A��Gn���"RPxέ7���Td0tx�|���dP�+��(p� �}�;������j�t{�ҙ<�b�n����F�]�;���c��Sa��D�lj((��J\�o���`O����:7a���-&X�����;Zb�X֗ò>)�t]��y��=�SOл�hV��#��t��ݕMo�0 �����k�݆�N��6�vP�k�%A��f�~�lg]�ҢA �f�_=E緭�5���-ه�{h�SڮJ�����G�˓B#��6��!�g�'k�����"�$��3&m���Z�FV������d9�<(Zl+ �{�5 " Ҳd�01eX�+m4mK��h���� -k�Z�$Y��cNV��_��G��� ҵ�HT�+gTj��;��B�P(� �-(P��2>��w�|`x�Ӡ^54� �f �l��Ir�����@T�++�p�Y��V�iū�.&a`q���nO@���t̵��q�d{ �o<����m�Cz�� �]���W�b� ���O?5�A��Ν�MG�S�� -�j�;��|A -�a��b�-Ճ��Q��>�� P=Ӡ~H$W�Ӗ��a����4�_�vb�j���X����j%�|�}�s��k�=�U��(�g�.}��,�*�m����Y�o�0�_a�xC���Ɔ��@�/H�c_3ǎl�k��|4�:R��u;�V����~��;8�e�L�:itLߞ��47B�ILoo�^���lx2��9Gp�v1M���G�N�4fv��G�rJƅR����R �@�f��q(��Ø6��B0�kfp����"k�%�6d�%`����r�8ϼ�13倒�t2�J�yLskPg`se6Z �ܜiQ�������^��c1�:s��c� K�I���= -�%�)�!֗tb �9�2�PΛ :LQ!m^YC �P�g� ����e�h��3#Se��� �NZ �4���� -`0�b���-��Ѷ�Y�㑯��?�z��X�Af - -���SirT���v -ئ �>n�c҂語�q -�EP���0�m'&�@DZ����b`=�^� -��Z��&[? t��V٪N�^�4�� Ǫ�f#RP~��1 ����v�}X�@�����ς��d ���B �۩ w�o��|4/�23�\E�J�6�R�PCS�$�~ -U�S#�$����RHHx�H^���!Й�s_+MXn� ~����l_녝�g��L�F(������ /��`%��2؈J�B{�8�~��T�� C���&M�1)� ��`"�]T �h_z���?�aɫ�G�g,߬3ǀPӥ4T7�iu[l��F�c҂hU��8��&���(�s�I��z�-Џa�D��)��YO�W���9A-�x���_z`�*��lU��(Ƌ�M�&±ʭىT����bL����e�a��I ��Q�a�'�pr�� ��B ��� ��[��i���Q�5�+`t7�ʜ3W?���ߢ8 -‹�VMo�0 ��W�/^w*�8EP8Ê�ҭ�N�,Ӊ6E2$9K������4ү �!�(=>�Q��r�P�D��);�g�Z�R�Yʾ���`p9: -ŝr�.es��IB֠�ץY 4��Y��j�J��ŵc��]��,��� `(�v�k�o������� �%WM8�ȳ4�PF��&-,�����yK����L�]�I�o�e�)����z��#��8C�l����*�����G���-pm#��-t��k xA���)��rȀNz)zs)�,��~���)� E�����P�%:ae�IH ̢�`\u��A�o�S�D -%�Z��-ץ��~�d��%��xU f"%H���� --�{z{u�{+%^�B�{ʝ-+�tJʸ�|}Wu蹷��X��F ���>Ǖ� �K cTDlU��Y �){�(�w1�'V]�Is���\�%���W�!�-� -��_�p;�-�gKO����$5���R|����{�U9V�q�g�-����X2N��`*�8��(c [�u��;g�5�㯨�d̺V�O��t��-��qOp��tl�,��?}b)w���������������h����_��(1����7�Z��&GF�+͐Ng4M���͘Qo�0���),�7�ު RM��Puդf{���k�F�Aȷ�1d�S�&��88��?��N"��sE*�N�ϋO��&�z�_��� %�˫H(�����4C,�0�E����9+(ٔJ���͝�^ (�oK��9�� �ik�ﱼ"$�A��s�-o�<�`�=�<�y߹�T���)q�Q��n�rެ���T�1-�Ai��7Ia�K�K�CD�`uߌH�Z38;ae�@��=z ֹ���Zy/)�������>�� -h�_���6w���~��uD"ؿE������ڿ�w���������Ki6��LA��m�]>�qG��D/qj��nP���0�M'f&������TAH�{��L�lƚ��� �%ЋY��'Ei ��w��V��Ɔ���`���-̓i<��H�0�c*�Y6 a�np?�C�F��i���t3�Xg��x�`ʷ0�g������/@X��= ��7j��6nw��i�F����� _�c��y� �QOOhF�h=&�ʜS��{e䛽(b����/�XMo�0 ��W���nÐ��:t��Яk!�L�U���`?~��xi�����%��A�=>>��-��vv"ޟ��V�\��D��^�� ����X#��6NDA�?�F�:�����"�bP��1q/�ձ8��� -�r��K�Q/�=p�`��aܼG=!`�4�O|A��FO��mx�D͒�}þDR�2��i�㬈���M�۩x�07��z�:^��&��6��#�i��5�h�����t\�cV�2;�{D�H��>�7���#uu�v���T ���ֻ���.�\�������i�������eN��F����ʩ��nWis�(}����0H��� i��>;N����� �k:���b�\�Ѵ�+x��� *�iX�bU�ј� �c�ĭ��+��"�F �Av�>�LlJ�:�Ofs���H������l��oř]o�0���+�k�$ۺ��~W��*ew�1��ֵ�m"�_�c @�E�M��bbx|���Ĝ~*_e�c�V����( @q��N�o����0�tvp�%�6���N�̹��`���<�]*pkx<RN���(a��N�@�W�9�0 ����gApʵ��)�|}��_B\�� & ��H6���l��QcE+0�������'!����B�K�d��8�2�L -�8����_�O�%y\�y�Z��5��_�ɼ���Ix�����8I�;r��q���s�+���?�W `�黳���������WMo�0��WX���B��PX�A�T�r�Ibp�Ȟlw�=N����K��eM���y���d��/jE�`�4:�/&�)͍������ٳ���O�R��s�_�.�b�*I�j�T�0��L���R���?\��A]�YpP�hV�k����$�õ�!i X��gB|���;��cF �P␡��r.�̥���h��Jv��fj}�3�� �se��a�m��V6�I ��M�� Ȉ%ߵ������:7R���d�O�0�B���\g�����7@I��P��Z��7��xq'i20u i-��T�Y�m�!���1H�F;����Xۆ��B*xP��7�Ѷ�F�m9�����q/I�A$�� -Y%�ɿCGFߑ}�[˖k*����݂!���G�Qw�'l���̍X��n��C ��i��b�h%GXq|�WHBd�`��ߨT�X�[�*�F� (���E��x�V�Qm���+ּ���լ�����tr�c �z��3@ �� P�Ő.[hN�SB���`Zl�(�u�$��/V�v��6`�t#�ˠ�u#ő߄�&R(�~?O�C��[p����gO��SG!x�$J�+�e����i�_��Ֆ]o�0���+,ߏ�}v��#�li2�ҤJ�qL�f��vP��s!��QM�V����o������V���L�v;�PAd��} 4yu�u��G8�X��!\S -u�e]ɇ��&Њ@�h8�a���+U��T+l xEu� =�NB�����`a�GY~3@�AO�yMk�{�����$%���.�7�X���� �V<���=��Q��(w�מ�y<�|H��'�$N}� ����x��q�y��|0ʳ����{�����a���c�ľ�r��e���7|�!��8�G���?�{������5Ӝ�Sa�G\���<��h>��i����8+�O��Cf�)����~��)����+j��jᘳ{.�Q��.0�+7��p�4+gfº)9{\"��|�x�^E5Q�6vqD��-���vN����Q��m-Yu:9�X�\ UmNK,*nM�-���ET�Չ�{����%v��.P!TT�F��m6Ip����ދR��,Z�5r��x�v��m%Πz����~����Lг(���9V�恝ӏ��/�VMo�0 ��W���n���m�]�;�L�\dI�h'������k�a�n�d�����@�!��Y���Mh�+�>f���O���]^-�Q1��lc�T��}��u�+_�͍ENc� ��1Y���� XUc�Jc� �~oy�����rw�\cY���� iB;94�]�<<��P�|)�YR*�ȊI�̖"�d��Y��P���2��qS8��W��-`ԁ�&�X\C-��ݙ�� ���uTL{����Ez@�_��+W��t��3k��p�`�:��C-�<8F ����1 ���'�6!���~�atG�*� 4���Twd�jM����n���T�.����I��9{F?��Zd�n�`� -8���T�?W���"dz�i�za] ɠ�����*�S -5w�n�"� (ȧm � � �&#�NЪ㘍��V����w*0Ax��YL�b�S虨\��G���/��&)i��vK�l�O-��I��c�y\T��*���?�4?aow��{���;6! K �pfu���Tzo����k����TiZ�M��<��CbŢ>A�t,��s����l��u��܏fuUq�y���$z= n߬�n� -�p}l�] o�} ������}����1���JC-� �:�����Լ -�K��V�К%�]W�{�? v8��/�eC��-9{̵�}�6�$-0[ �<P�9��~�p�$�%6� �X03��өߐ=��:З���Dv2�h�#oA��F ����A�\sP�=���R�VF.l09�~��敌�D�����[0$���o�l^U���!:�a��<��]1��k����ŵ �u�N^W��nv�=���u{b�劗��7�T�n�0 ��+���a�`zZ�Z���,э6Y$:M�~��I��9�H>�=땳Uka�!r���|�N�6��7W���M/Jee���]�Ă�-�t���״�8�"%�鬭�����i�_���l1z�pb�诧%�����X�MF|�1NJ\�"/Hojtn+��A*�D#m_Y�Q���DSkx] ����'��7N�5�ڒ�3�F���(j}��~$H{�Iu-:���8̘kr��+��cJY�Õ^��c���jPF�u�C� ��d�A@�0�s ��"�Š�K�Z ��J� �;���5 �:��2 ��{ ��%U:�4���oc��k�,��7K�b��/�Ҏ��Fvv(9�y��֬���k)�h�5���gf#���H��7���A��3�o�͔QK�0���)B�]�M��@dO ->(� �4�n�4 �uv��4]�N�+cO%w����kr�.Y����^�/)͍�z�����5%��(�yO�f�3�D�7IVc����c �x�))*�2��p'��u&P�Y �2�D�h�OF��P#h�7���y�Ռ�$��K#65"�ܣc3Z0�{d(�v��^�RI\g�V���g���v4�AZ�+���eϝ�8nJ{4�~��u+`�4�� ��˦�O��2��6�)�))1�ݗ7�����m�O�]=4�VK��o�%����3��@bs��ª����x8���>7:�*��ӞϷ�S��}�WF��Qk�|��� �yL�}XJY��\z?��As���v�0د}f�a�}�/����!��3h��O͔�J1��>E�ݮ�Dv[�d����d�YM2!���ۛͶU��VK�d&�ϟo�)Gsk� B��*~>8� �D��s��oN/8 OJiD�,mv��S"Yi5�S�p>p@E ���5����k�4Oc��9a!z!a��c_��0Vœ���̬4�D�M�+r���Z֨�,�HAH�x#LWI����LG]k�iQq��Fw��f��N� -emP���.Q�)�b��=:C�ސ%s,����]� -@mp�>9L��U�,��(�� ay�&�� s��^h �?�؏��z��V+��w��3�]@�J`����@��}>��n�D�r�$\iO&�ر��6�3�jg�.v�?����L�mX����\�~�X�@w�����a�]����ð����~�a� �� ����Z[s�8~�а3�� �^�ݐ� $�b2�tӧ�l����\I����H6�H k��[d}�;�s���Y��DH�Y�uvr�B��<��Ӻ�/z�B.ޜ�1��a&;��R��v�N�i�� #�-E�B�4�;��������bxFd�C�ie�_/� tr&f*��z���8��SsE< b~��s,�Z&p�2�j����s���ջp���u�� �3{P��܏���������ݚ�/�p�=�w -�_-��]����Y��f�@��ҍ[��a�L�� 7g�7�0Z���?߬���b��`�#�j3UX���w|w��,��v����閔i1e��+Ͻt�����Dz��2��4x����b.��z��w�h�y,f������^�׿tn����:j�'g�By��QGǷ�~�+nl����, "��J ��ty -�#8�h�iMp, �SIS�촒4�iXB&8�����=BFD��& -�J�4� -Z�)A,5�� *�)��ȊСD3n�iX�ۑ[;?��X3|�����XZIԴVR=�Xf�hf-%y�% -��9�iUƛb��Q+[�E4��'�$c ��\u'À��?�H �" �%���Q��l�`a5D%�o#�>C�pUG�?KԲ��9�>6(P� -"�DH���� {���3�A6RXʭ� V_S� ����,a����b��a)yH���o� �p������8Ԓ� �76� ���Tۈ!��IMx]-�b0�V71�}0�!�6���lHI�=}�<ՔG9�0��u#D��y�hB�W'�"�ՠ�U�m��5IU�� *�Z��U u���(��"��*Z�����T|�Pg�o�U��ٯ��������k���` Q�EE��:F�E�=P���ϴ��W�ܣ���tYj�_�{\|�x�p�,$7J�P˺���g���s�LH���I.��B�D��_�v�7)xWT/'h��mC�: -m��۩�da���*��1�_��[yjݜ��Q���vE���*���7V��^w>R�Y��B}T�Y*�[���Ro+�:}��T�m��^�k��(g�)���Z�K��%�L�e T��G.�n�V��r{=��K*dsc�ML�P6Y{d��H3�˱�Y��p�1���/��$1����4�_�duyh�>7��.��eukp�Yz5r�0eZm��H+�4�����$�׭�w�K ����"y̍hAp�󃉌����� {M�e���Y���OP2�\�|�,��^21<�s)���| � �T��J�xR=�q�f��q$��.�&�+��x7�U�\6�T����_�V���]Ɏ ��RGܶ� _���5F$� �jN� 7U*'4&�ja�f���EC�lU�ida2��嘧����J�V��}XѦ�)��e3i�X��R?�a���^�.�/������>��m����WMS�0��+vt/no�� �eh�����&jeI��3ɿ��$.P���A�7�k���'����+3 ���ħӏ�*Wh�����Շ���NRe$�dK����$ �N��n~j1&���6&���(8�D.��B�Ra&�og���Ʃ+��OG2�gD s�A���RBeԪk��J�j5�8\�Tn����l:�T�>2SP��m��a&M��J��;̤[0��B\x�Y���k��[���1���Z��;�|����c�׋�6.CJsǟ;M�g�{������G����4�\^� _�F�}SB� ��к�h���T��L�޻�i��&k����ѷ06\6�Z���;g��Uqi�������g�n�����E�!vw�T�������|��М��ڪl���b�V�L��MD5a�r�`۴��z�I;s��Zڀͥ�^ C=D�Jϱ�,k�f�.W�} ��*d���"�w��㮆�.lSDwS����}���?R -�+�Z��M!q�]"����#�3x�6����� -�����P��������:��±F���Vw��1�5Y!5�M���[�+�k_=S6�U4$v0�+p�jm%8�j�0�������l�#��2?V����x{s��=����I;|� ���N�0 ��<��;+�J� �8�4uWC�D�[uoO�n���.�[����ڍ���bb�J���!�3�f�/�����a��Q�� �Tb+�"W�ІڏGR�h���ߵ�Ap�����bmmoTG����ؓ �*I�FJl�M��D ��8qŖ�Pb�+��Jvڞ&&n&��T֛���>(��A�L0� ���4h`��u<��сB����SS�lt�� ţ��E���]P"5����-��8����jjto�cж�K�����L�t��B�*����t��&�;n�A��$��G彽Ng�T1۸��WMo�0 ��W>o�v��EQ`@ۡ���2k�%O���ߏ�H��N��m� (f���(yr��5T輲f| ?�F�D��4��������.&R ��QqE� -��H�24H�w2���z���F��Ğ���9�BH��b8��`�KB����[k�Y��m�|�m�qԦ�1����fr�'AJN�Th�TʫXiE�4(�%��I ��$��Rӕ�'�j�?�2�V>v�6�K���H�3gb�Qm�O�_�2��Pq��$�%z>k��p��VM�zq̮��j(�2\�ҳ�>��N@腨=�� P�<��S�@�h���{��p3���Bh��˞9䢆LTb�ʜ��8]%t�������u6o�q��DOͮ�x�b�-W� ����hB� �!FF�m EaS�4k�x�������j��1XR^�q i�v���m��,�J�[\;Lѱ�p]�.��b�ޭ��������nZ�)��V�#�n�� y��� �� =�h�6{����Y�fR[s�{n��'��m�U;z��,+%Y�y���`o`}�t�|�E}dg�G� �k��Z}���Л�� s��������s,�_|�so!\rnG�?C#�=5^]�s�����T�o�c�Ծ�>Y��t��f/�/6)5~m����?��}w� ��t�!�|���|�g�h����0���w��1qj��� ��_�N$T��B�4)�F�SFZ�L�u�y՝To~�;L��C�����Kl�N����$G�2���~�{������<ʲUsltv��~���W�F���͘Ms�0���� ����d�$���M=1��5��Y����6'8!����}i?�\�RŖ�VF�Z�X�y�����c�.�����k �O�1=��E��H�[Kr���7OfWY����6��)، ��a��I -iX�F'r0�h���+ [J+#��[�A�Ɓp��1$G����q{�d\J�WKsbx�Jg<��^cj�\�={��kMAr�:A/%��b1�5/�aNڣ5BH%6<��! XQH�����gvUI)����߸u��?Jo��0bp ���t��n���3��PF��/G�Q�ł6�E� -�q���N�s� 6P����o�w�����&(*���b;���O��n��#>_���j/�v/�~5���ST5�Y�cflg -~�������T*4�����6� -D���%�-�2NC�����Dh����W�#�14�z��@]�0G��F"x�[}_�e���[��6ʼn���|w�`��e�}�c�؝N��Mk��~=C�� �����.��~Ş7k��I����#B\�^]��}�ws -ba����/q�z���FII]%+���o�.8���}�h,���X��#��5ޞ��\/����GJt��� �lf�C���TPr%��z��a�� ?T���E����uʱ�0��ݧ��n��1��O �9K�rmz����8;����j�9�;�;<�B`���_��mBh�]�#��6�8U���M&��O��V�G�>h�)�h��]��zE`��d���W[͔�N�0E�����4�C()BH���U5q&���#{Rڿg�h�#E�bi���뤷��†B4�e�j~�����q�L�Mz� �@Xxgw��׾@W$����#�_�MnM{�ơ�+NB|$f -{PQ�&8�]-@�ي�SH]cl}n� -|���]������w}{T�<���o��9���ߨMtw)�ᷦ�N�6���:�`\����0��#͑ɽ��c5=a<��������ę�߅�g?�{� ��͏��m��@i҅t� �XKs�0��W���SM{�t��Lڴ�e�8����j����k��.`c' m�����AB������.Jst^����w P �K}���ۋ��NOb���@��O�,�1�h4�3���Dc�� �J��}�Eznt!��K-h^��\`���/K�'�,��u�E�ƭ�v�Y/���U�fV�i7ǀgt�EHX��G>� E7�K/3�dX&�V��5��Z��A&7"SF<��z�p�� �)m��Xlqk�6O: �����΍�����1Jl�#E�w�L�� ���i�7D![:,�Q갛kA�岃y֣�Cm�$�/�G[:� ->3yG�҃'��ບ��Mz&��m��Q����#��y���#�Š̹���Q\���x��:�{�B��� ��!���}���R#Ȃ|z�%H��>����S�鷋�m�1�Ȫ���ڳ}n�����,�s�D����Q ���ܧ6ST���X�-i��[9k�+��pU���2|[ؘE���S7c:usLN=�=�e����ژ�����n9��j�ӫ�j��z�~����8�j�ChJӑj�>�j�K31�w�_��O +��?m�`Μ������]��?�W_��7\z����K �\����[f�׼>�[��n��N�>�3cԮ%3B�������lEhW5#���jKq����'�XK��0�ﯰ|ohoUY���n�f��2fH�5�e�4�� a�*T�m����d��̘Gz��%قuB���[��ץP���}~���U�%s�`�r�xo>$ �fcJ�[(�������2�?X�Ԫ�|%j#��jp�q�h�8��"$ �5(���o,����$��P`���+��{J�g^��VL:$�NB -�Ϩ���PD�QB�� -���i^H��ev� -��2�u��<�J��d�&�p̵r�6����9JX�G!W��o�B��n�����.e8���mm�~-v�&A?��2>�=�y��)� ��-a3��?�Fy�:UM��ꟶ�4 =�� �X]o�6}ﯸ�, Rk{+A��[�m���� Z���P�JRN�a�}��$�qbճ�!@ J���/yx�T��6\�$�a�}(S�q9M�O?��1���7�T0c�^�&�f֖��V�rVf�i ��F��I���?)i���vb�f��@�M�RL��x���7�� ��M�K(M�2��$ʙ0��>��:��j"x�7�� sV {- �0nW�3�N�J�ҿlR�K�RUJK�T)�@��.$P��� 8=�%��"��r�Lj� E�(�*Q�������V:ƃ�~��j6��Tث -9�t�� ��|�(3τ�v,�38i� 0�Y}��H�YU�m��4��Yz81�~����[�%$�S�����{>+W2��%�_�׵h���S��*PZ����b���������3��o���=ڎPI�r͈����Q�fH8q�y�0Oю�K�ƞ��7�mq���p�v�ӊ��66Y��b�?�w�C���,�_��r���'Ġ�Ͱ�̘m]�*l�� -�z��Jť]�2>aZYwZ�c_*:�<���N�4�JK�uI�g��r\�5u:Y�R��[{��x�0[��u�2nJ��Ym���lxi'��Tk��q�Ke�9v��fv�v:�(u�cK����H�ȋF�S�ͽ`�h���̯���f٪O��M �Ý�n��J���"W���l�T� y���W���"��a]7����Am��T)�j�M�[+�%Sh�w�#��̽� a��՛i�sQ`�I�����GNRȲ*���b��Ѣ�I_���l�f]Gi��ؐ������C -�Οq�zz�r�����~�)'A������e�0�?b[��5��>��e�3Zؠ��y�u�뙍Z5}XZ#$�H߼�e�:Mw�7����!݊�ި�7��~ ��^��;����6���N������I�dvv�yԺi��Z:�ޯK��9�� ��R��X�>]�Z�{=o�I���M�O�k���������i��^>:*���c[�dF�z�o��wƞłd��5 �O�g��ټ~<���.���J�{(�a�z�/�ZKo�6��� d��V��&�h�"�vѓAK#� M�$e���>$9v��q9��d��̓�<�g_�f� Wr����)��ʸ� ���>����ᇳT0c�K3H -k��~��zeQf�'���N�+!��,]pS2���l���� kK�Φ8�� FZU�}l,�<$9�q��\p�$�VS����v�0g��CI0���S�M�c�һ���T�Ғꐪ)᳄����ן��G`�8Ώ��+4��q���pi�'a�L�x�@Yey_f��$i����)�4�o�V,F��Lq�x���Te�8~��pS%�VB���!�״�Qµ��"\� ב��X�w�X7A�Ƃ�\�%������9�}P����@QTu$Ç�̹~�+�b,���z��j�)�1��BU�2 ��J �@h��xn/��-��h�W@.�n��_6{>-In�)�v���%I$���`�1��V���-�q�sqc��Zqk�(�K�G�K�%��N�D�Ý��;�X]�� �9W�E�1j�b��$c�D��"Ǚc=rR��>�2�y�у5 �|�(�����$i���k"E�l�ռQ��ȏ�� �j�B�C?��>��� �OnJ6&��~:q߄����5'P��ҠҴ�ڀK#�2�O&�H����Cd4J���|l�<V�¹N* c/:�ͱj�����\٨Cy��W)�S�S��c~U��K�\�|�stL\�������"��S�RJȖߣ�O�2�9�5j��y`Wzs��ee�pmP�]1y{`6Fܪ��;���Kƨ����Rn�eZg��ls��Ap�({pE�cw���H�e��� �Σ�&���m9.kk� �6+��Y�]��K���y���O+雝����6����mx\{j�M���=o딷��5R�1[+�6��|}ٶ�C�^5Ó�mkAhf99-��uMFݩ[��6�>Nz�iG��(�����!��vA�W$�3���D�dV�0�nRӁ]IR5g�A��>�T%1�f�q�S.%�,1���a'��2���9^�8��[�?�T���1H��'��_91�M�,q�m��p��fD��&��T���J���E�[$�]-�M�7h/”�<�"�-��P& ���a{ec�����6��4#�Ԋ����\�К�� �N-n)a�����t��_)1w�7h�h���k� ���n=7��ۏG��Zj�9�����3���k%��w�q�N�1;�DӍk�,�ۇ��O�,^�����$��gs��Ep���~�\��,���Fw'�AZ�����W6j�_'�Y���`�@�IJ���A�}��TzHs�r��Q��� �o\��ƛ%ZЃ���զ#q�z1�6i�}�*�-Xay��/���������g}���͗Qo�0 ���+����ަ -����i�*���'�9��$J �����z�E:�p���l��][IҀuB��~�|�׹P��>��?|��n{sɜ#^Y�����6���1��u�Q�������2�?Y�~k9�f)Q�g���¿�BbhT�����柅(�WPe`mo߱=P␡� -�t^l���� 5V#p�<��r(X-q���8�aO�Lj�����V�ו����J 0�E� ��C ��������[���~�,�c�B\�)D��Px)M!�Uh:�]�P���#��.W8)�UP;��0�+'����G�4}^��e�+�2���:��B19j\�;qOLm�v�n���Ua�E~ -q�5�K��{� �е����{{��~���p>{���x��X�H�q�J�,�0�k�����D s��> �U ���a������|`��^ ���2|q�}��2����3ޑ�9���܌��~��_��_e��R�n�0 ���F�Vv���;�C'C����, m8_�N� :w�w��N�S#�����~E�e���k�� -j�YU�LΪ �\C'ߴ.h�hô�$:'�����웏 )J9ۼs�F��vdAy�Sa�jX� ��RMB��G�UA�k|�4��tYm�ʴ�2(5��*��9sˎ�TCZ��썻N�E�� غ��� ʘxv�0�q�X<��D2$����1���*}��wf�'邽d9�|&9 �?dY��o��;m~�R1n�0 �� -�{�v+ -+���C�v�d���%A� ��U�$�t��#��;��f���O���� EL�ǽ����� �����`�U%G6pɯZW�ʇ�Ҵ�$� �� !����mB�R϶�ɺ����h{�l� ,���A��&������y Sd)J*�ն��@ٮV���&P,V<^���w>x9�C�)��6\'�*�v!��O b,~v�0�y�X<��B2��䘫�1yw�5��V�o̍���9˞������!��~���h� �R1n�0 �� -�{�v+ -�A�f�С:2E'BdI�h��}e; �e��;��;��n�)e����3���?���W�zS��9�2쳂#s|���m�6{m��g��=娑���N����ɛ�`� ���9 �!]��Ɓ�m�4��N�L 2k�x��Ͷ���YAZg���kw����3&`��V8�1�Ŕ������i�'�!y��X,���k���Z�� �=�1�K��W�ц!��,���iry���R1n�0 �� -�{�v+ -��f�С:��$BeQ�h��}e; ��k7ݑ�; -��S�ň); - -��G Y -�>w� �ͦ6^�,Js� -���Eʂ�x���* ˜ ��གo�o�&���l�N���h�W -��{L ��1GmP� -�6�l��qb 6���"f(dN�aJg���r tW^ڰ���Ad����.��y�'q輛���/�I��d:O�g�3��$�����x���zBR|�%�H�^j��ժ�� �#ɞw9 $ �?�f/'��k~�R;O�0��+,�԰!��@b��)r�Wz��Y�%J�=n�V�:��w��!]����F̅8Z��~� -#���c�����Y��f�@p�������bLE�tH��uD1%�V�!��ݾ}��T�����9 -��w�C@���$h���lVJ58 F_�ܳ��L����m�=�\W+b�ޅRMqBp�#�(��NC��� ���H�� ]`�]ੁ2ͦp�f���<�(C�J��Z��e֘[����=ʁ�9��GƑx(�!��~��_m��R;O�0��+�ۉaC(n':10S�����c[�%��'i�n�������ލ��L�+x�A�7��?*���?<��m7�q:gQ�}Vpb�/RT�S�a�<��ɀ8��)�և�u4��6oA۽&���"��u�9j� -�v��Q���m^9X�r&3�gN��.�Ms��m��a�2�Ȭ�� ��%G|V�����v׍I���`Z���f�h6%L�⬱x�� �O^�9KC {��򞫖w�F�| -�����p�����e�^�L�o���W�n�0 ��+����6�q -l���0 �a�@��X�ly���?ʲ7ɺm�[�|��<�]� -�h��E�ޏ�1�B�T�����������D(n-����,s���"Z�ˬL�j\��� �R1���;�S4 -��-����œ�� �I�y��}8SZp5+,븓"fs�,2XJ+���cV�P8L�#�9���D`u��� E}�f�F��D��9�s�.j�G���(��ET&����k�'��A�D�@�vV������7�?F�-�6� ���=m�r0<6� l�N��.�!Q�o�Ҷ���n�=MJ4�Hj�s4�~��pt�}�6(f��*>�goR[$�Q.2/���Z����|M�?��[�C����t%�[�?Z���p���+(Wl���F�h`5��(Y؄�񮏺o ��hGMg񃎜.u��\TS�a��][�K��ᅯ�1���#���(�jx��q뛀|�n��x�����}kKW=+�(��q� �͸�Ԑ�2������,��{C�'��2��c�$l[@{w�\�0�_��g�S�������>��$�}�� ���U����^s����:����ȷ�M��R1n�0 �� -�{�v+ -+���(ڡ�!SLBT��6��W�� Cн�<��6���j�\8ϫ'P0:{_���P��C�ޖ�*9���uE�tH.N�@�KFP��{�v׾MHI���#BykٓlO%Y$ ���~P��I(�r�}W���1�J��ն��@ٮV����B��Xa�‘ w�Y���y>��`��q�2.b�#�,�Ԡ��g' -c�f���<�$CJ��Z#�ˬѷ�}`n�$���Y�$�F�C�Y�����J�_�R1n�0 �� -�{�v+ -+�ҹ@ۡ�!�t"D��6��W�� Cн�<��V۩�j��./�gP0�.� |�?���nVzˬ -9���HzӺ�u:�6N�@�9#�n������݄����?�f�.�A�'�d`�7+�*��Bˏ�A�kKPb������6��(:�@�Xqx��c�8��d �w�x.Xe�E�L��� <7�1�ى�اYc�4�3ɐ��S*��������*}`n�$��^��I>2�.��,��'���6��R1n�0 �� -�{�v+ -+���C:t2d�I�ʒ ҆���vdHЍGyw���y5Pf����+( -[N���wP�ͪBo�U!6pIZ�N���qH4gu�7�m��nDJR�ևK�]�1� -�#N����nVJU4 -���Q�j���0�ܣ�|]��{�mJeQ �g�b���]㼓���7�M�\��ƘD�L��85�1�ى�إYc�4�3I�����]{�U��V�̍���k��g��Ş�C��{�!=?���=O�0@w~��; l%EH10�r� 1�>c� ��8M?E"K�s����b������ }�d q8:�VQ��C!no������red��m(D�쎳,E#׸�^G9 ^ ��1��������ޟ���T,��)'� ����)rC�2��i���]7�+z��rC!ji -,Y�u8�A��h�����;m�Ye����H���S� �׎�D@��-7 ¶>�E�(U���=r�x�R�3���[�}d�N�t@F�<���!���M�P�=��\�u*���BP7�IWf3��K^pd�Zvu>����Q����o�DOPbM~#L�g0�������������]�ɠ�^n����M��^�����]�y���>�5;,��b?o��{84�e3��N^�-��o�V�n�0 ��+�o�b�S �Ew�)�e:*��D{�ߏq�4�Z�ݺ�b��3��#%���x�l)T��� �֕�q���F���4N� �Rs�R-b�,r��Q���U�nWw����VA�s�+���+�ң�1M�W6d�����5�j��8B�`�{�� ��b��wdjG�a��l��, -��",D� ^��bO� F8Y��`����Y�V*&b4�ͅyrG��se�k�vL1=��ڠݹ���t�!Le �)�$(�$�>�m6��y;��!iw� Sz�1��O)�c�G�^��!��;d -#�H͟�iн,�d���Y�/"��Ӏ�XFm"i�{o�'q�I��ao� -h,���m��'xX�g��b�>�� �X��W�����m���?ܭN�K���C��:�!�f�p#f�缪��G[�/?X_��`� L/���|z"ߠ]���Y��mX��Zߓ�6~�_��K��۷NȐ;���1�e�'F��XYr%�;����m��W�m�@��~�v��]���1�dJ3)��o�_="��w?���=���r�5Aa��^bL���㨝%Y$���U�8��}��l �ͬh�hh<"h -:�!t��`�`� !�P -m�0���j8�4� �ӑG�簂Q�x$À���j�?,�"f����l<���7E;~�������jSHP��TF9��߆�]/�\�p�4 gf���<�,�1@Lsnz�Ք�=�"Сb�As�P�h ���r�k"�7Jr�a�$��S0��.bFM�a�J D�Pr*�9�97�q��1��_^Ҁj��]:�Ĉ�g��u�H��ͅ����D9�/��-=B�̡�{<��� �+���&@� Y�2����D&W���E�ZZ;���wo}� �R{Cܮ� ¡��h��@,�Mo �z����3f��Q�����LDa,Y�[�#�RA - �ۙ?_���K���o���u�]����c��8�3�&��V����2�T&Ɏx��Zը�����;\�bX�|C�@C�:�'ߝ�R��2��?�إ����pT�q�,�ض��g�]����V���i ��s4��mGf� Ɵ�UR��YR��?�c][f)@X\_CL���7�p�)�K]���Z� U�t��E��I�~s)�0�6c�!Pg�|Nr8�_7��@p� nѻ����v� ב�㶣�IC~;�/��}C�e��]��X&T�@��xR�_R>4&+��K�j����&̪]w'�i��;r���+���{��9C�r?��z��V��zK�!" ��H�*ƞ�l&��$K���6'�:X�V�p1�a[��o,+R �wwT�L�J�g"�M-m2M�u��pdz��<0�<�D��[v�",en=f x(㵈|I<���a� -�s�m,}(,b�H���(���&���x�R�>������Z�.жOj3��P�pٜ�f��}{ �I���S�&��]�Q��@źv��ӧi������&��I��F}�ݟ����>C8C��x'�������O�J;�u�&��6�P~ҍ�9���gu�����w�ZMs�0��+v|� �&)Sڔ��Ѧ�PNY^Ǣ�d$�$��������u�a��N%kv����v5龛�24Vh��^��ׁP�w1<~�ƃw�Ϻ\2k�+��"璷����( �dO��X�=S){���+E�F'���b� ������E���8q��b́o�aܭrR}�Fbt�f6F#.�B�lQ� ��4as�W�P(&��Y -4���W�0�@ˍH�\�Iao -p�@i>B�<(+,t�Q� �iQ}�v�u;%���Şr��_T��#˄���M{^��R���=\F�pM�{C�q� �r8�gfS#<�Ey����S�!JT� �>(.���˲U�Y|��ngA��6ƭk1@ -3���|�S� T�&%b1��K��]$l����,��� �b��C������D(�WZ<=���D�m����7$�I����;��˸N����>�v>|�9u���n��ct<�r�P�}z���%>��Y!��5ؿ[i{G����&N,a/c��b;Bx�'g,��PX8��&0i�0B~�Ș/��jQ�f��߃aD�3�&E�@N D�4o�d8<]�������4��qx���>���!��a���V=w`ё9�Rڡx�J���S�Xf~H���W��k-kd��M~-·�J�� �=U�y��j��8��D�$髬�Z~[�' ��g�V���l����R��b)/�C��Gp��[�)o�^м�6�&��1lZ�:oq�&�ֱ� ڜ�����I��_�#a�x���������W��8�����s���7�5#H[`�S���h�/a��b��>���g��k�RӰ��^�K*�fHi�� -nxa35���f�v���9.�ߌƍzw��ɰ�,c2���_�]��6��f�P��$�l��\�v���ݚ_o�0���)����ަ Z1�M����'�8��؞�D��������A�����|��.�].c�RP� -��ޝ��p"B��}�nz�潇./�z�a���u�[#?�����B�by���ZE c}�'�f?�w��&4� <�q Zb}�*�]�!ԃ��Q�@���2���ϕ���ܔZ&�����l4��=�b�@��[��CA&�C!�)VȬ�f�Zwj���О�����_V�[�1��K��a�[���!}/�L�����Q��Zzj>!h��46>���M�J3H&J -����E�I/)RAç�i?:N%����~��ײ����W+���1�G��NюS 7��+X�k����"����r�G�r!D8a���p���d�z���{9�`>O��![V?��_k���k�Wp������.�u�B�Xm���*�c���gPi��h;D����!kM�B��k�+�%6d������t���[���/��\{kn�� �hB9&H:�C$���Q�-$�,�[�������A �^״����W�%���t��U�����\Ծ��2Cw�KP�λ/�ݭq��� � �4�<�}���U���nWu�O"��/����q��XMo�0 ��W:�z� [�"EW�0`@�z2d�i�*�'�A��G;��q�6.�K1��ޣHQ�-�-�yeM�?�~� ���2w1����������@j�=����|���"��<��Sy'9�Z��VL�1���(��DΌ��QH��z�5sx��@� ��0�������ur��△��?�.,~��׃j1-ϬL����!�1\崊�?��Q{� ��V�9�SpB2����G%c>��Q^�J+\�E�>/�sC⬅��DҦ��l��O/m=� �2堟f����"I�N�2���4V�)�y� ���1��?(�|�Y.C9��T���0 ��WD�3�B����ā Z��4u�,ig��{ܴ3;�Y`w��[�<=?�٩��G��@�ah���k� X�]���Ϸ�_���z{U[oRR��;����$��]��~��DV�!{��/fh?afh?��U0#�h,4z ��+�j7F#N���!�L��T��heh-��fi�ذ���OB�w�u�;� ,C_�K�ɞ�A���!Z��v��%,�d�E�*���Rh�«S|]-��ڃw#��Jy���TA^�3]b*��_ ϝw�`��,�7k^eT&P-h�OQԉ"���k�82��k1. ��T�T7 @� -p�[�����kUB�Ad�c�s��h:�g�/d���M(g�A�̖�8>���o�r�u�^�L�̠=����+z��ҳ�4�lι��9~9g]��p�'���f�Ŀ��C�O��@&9�,J����fm��dKCg���U�n�0��+�k5����R � =�$P�*fK��riX��Ê`;E�6��޼�`v�5�ov�[�h�+���യ�{,��O�>Hq��ʵU1 -�X� Q��e��&�~�r@YD-E��-�Ք�>���;)�j!���cp�Y_ ��6Xh�Ql@ �EH|��v��޾x'��#�� g3Ҕ�SKN��EX�m�����e����T�n�0 ��+�o�a�S�2�ע;�d�2=��%�����G�J$��v[�a7�zx|�#Y^�Vl���Vo��}kܗJ~������R[�`����‡��h�����*"j)�dm%?���񉠾��h�pj���J.�)��B�� ������`�$lj���e��� 5N)")2�����Í��1��Xɀ�@��&h�S���q���F����z�u gp�hq���I\g���>�,i'T�$g����t�Yʤ޷Y&,�X��H8sd�?�k�A6N�-�Y:�r^�DB�U�hJ���ձ"ޱ�5qr��q���ϩt�fD�yea�o����_�yq��j�e;d���}z��B�ɏɄ�[{�u&M~7;���k�rtnĞ��l�x�>٤A��������l�����}���˘+y]�o{sb�1���]F�u��+�̣���W�%'��,��}��� �K#�7`��T�n�0 ��+�o�a�S�2`�v;�d�2�h�E�����G�v$�ЮCћiя��鹼���E���6﵂`�u�G��}���V�۫�z���+�c>�T�a7�x� �E$�U����w�շ��;�� -��8 �����WJ�������K`�N0.�����C�-z�Ud��V�3> -��E�8�x��@�`�LD@Z�L� 2�,�j9C�x���27GKn`�QY�eU�U���8�/���%��4���Y�G���d�;lW�1�ɤ�i"S�X���xj��� Ư��y��UF%�+k�&�DA�8;a${x'�C�$w�C�0�:�R�H�Ib��n����Uέ�Ad�ôs�� h<�gJ��>�1���%x�g��뤺>�{m_�7��c.�ѵO�ho=ϲ��*���^�z��`��D��Ҥ�k[�m���͎��� ���+� B�o� S�,���F䜚��S�n�0 ��+�o���S����CO�,ӳZY�(*������m���H����=��ݏvH�ߨo˯ -Л�Y��Q���/�ܮ�q:%�O���MUI��C��~鑫DFA��kԃ�?Bf��d�V�^���6ب��h�����9]��;��������֡�&&m�Q�vI��9�;�lk��Q1��^��@.�]0� �i. L�ld� Lc��>� �ܬ�� 9����Eb�鴮�r�Q��L�|��Q -�HQ��R��_Q�!:�S:~��3�y��+5� 0����a�f��"�&kj�k��<�d[�,��� A7�`�z��=��ԩ*w��.�����ɠ��V -��xVU�j��Ԣ��C�P��o �m"�9��_ ϭ����kwB�I�u�+���N��@��1�:V��xy&��F�V׭�an��K�#B�A��l��=�/�76Q�k����3ล'��>�cZ��m��9�����Y���^n 6�E�C��-�l��������xÞ�9��ư�@��8��ks���Ն�^�G.��$z�]-4�u��y�� ���Y�n�0���Z�V�3m����I��SL�" �r����6 My�}����㑆��ZI�`�Ъ��V)�u-�cA��L���&�9G0Y��n�7_� ��ٚZV -|�,���,�O֔p}QJk�ơ�C������\�FB ʻ1�΃e^۩�)�B~ mv�-�r�)���� �ýp�R�cA����: b��I�V�gS4�Ӽ���0$;n��Șpݢ�r�1=�������n�d�<=�=�%��[]O��qo+v:�zC{�M�,3��$P�����v��r��n;d1L���Q�(�w��A��l����Q�ބ��ߕ���p}��nyU�S)��R���($Z�Ml�vᑋDF�:;W�_�V�΢g^���6X�1�+��([lk�}r)p0�)H�ٚJ��K������Y�U�/B�� -���Zg�K/e1E�\0� �e ��d�F����V�X�}yqZ_#�+����3�A ]<'e���]$'oB3q��vr2u���=���s�l?�k7U̢��oBB���Xj 9��E!�m^�F�I����cO -��q��zG�F�+k�����S��x��,�P?�� -��R�H��o���û�#̔ :1�}� Q�k�f-�� �~dr_i�a菒C�4�d������#~΃L�#2��(� ��q��-'N�6����2Ğ���u��yjgf�v���T�-}v�͖=O�0�w~�光6��V��H��T9ε9�ؑ��O>��� ����}�N�pQ�FT� �����R -�ڥh��|�W�R,�g�6�H4bK�̘�� h�Y���g8 ��ؔ�D�U��� X^�� -n�KaUT( ��o��3!B�lJ{�I���g"��9p�ҽz � -�B%�^i��FjĊQ� - 4ȻHeb�%@��A��n��N'��>l@�cg@h�]�'`�P����s� � =p�m��dc����i�� ��K� R���/��7:ƣ� -]I�3��Ub`�ҥSَkH ��La���G;�< n|O�@y�vCn���i� ;Q�5�0�K�������}���Ea��.��T�n�0 ��+�m�a��C�~@�v -$�i�Ғ@�E���e7)�z(���N��G����}����[�e�Y+�>u!޴�珫O_��\_4m)J���zϜ�#�*�s��W��Z��V���ݤ�"���`�C�h{(�zh�,!�J5=�h�m�E,l��Vrp��Y,P���`g�u�y��o�;L�v -+�x -��h�S/�X$�n�3�X��>�" J�At�>u��Q�V���~�s�N݅\���V��a���s�/�x 7�����݃�TUb�/ x����Ee�=[��ْT��N�J5�I�r�H������-��F���n�%�1.�Y����&��,j��V["{XLq�٫��!,�87���41�Q��U'�t0��ޫ�b������d�������gCP��{8�th�cA��A�JkL�}��TMo�0 ��W:m@o�a��ð�S�&=��2�h�%M���ߏ�#��n����#��ݩ�p���w�z�x�����q�Z=n?�y��nu��SIv�V���Z�Ch�iራ��n��V�1�6W�ÞR@M�*Ɠ��`�S�P��;:��Kibd�kաM��h�i�5|�U��I3�A0Z�p��rB`Y]�)�uc��Y̜�t4��iоv,�)���_V�]>k��A � ���S���2���_ �5�`ơ(&�^D���&�Ȇ����\����A(�i��M�Q��Y}��P��U#��xs��Q�͠�W�%����~�꾘UQ�J�i�|��t�]k�D+/Rm��ᲀŃ��u�]Ojނ��H؞��q�ցJe�_%N xL���hm���ZhLJ��� ��7۫�o�\�_>�~����> ����H�hAuB����+�''@�:���Gz=s��DF��{�Ŭ����xo�����gP�i��͖�n�0��<�%S�v+ -�A�S�;t(�lJ���o_��[u+4�����GI�]_[�A �.���g|�n���Շ�R�-o2c5��bG��37_��Ѣ�7��XQ0Rl[ks�S�b !�-���٥p�j��\���F� zWѨ;�2T��������0� >4 �f4��jK1쐰D�|�e<�a�R�����n-/]̞�c4>����$&0����#���T����h���M�7ɦ.�CJ1}�w[Z|n3:m���l~8v�D���1����\Fsq��=#��� ���}�� ����-@����pl���֥� (���q����w9� -�\&O�#��0�� ��{.:}��pO����2m����0&|K��a��͸z�;����2�~��͙Oo�0�����k6i��)I�'�bl�MBz�(�-$��LB������z�����c3�}})�1kڼ*����g��2��y�4őY|����l4I��m�S.�)~���x줛���W�7e֍�&���PS|�1*�����4��p����U�vI� �Z�36 <���~N��T�U�PT�ϳ��I��_�S��.{�<T��u'� �#b4�jh`��������D��(��Q�a���1"��1x҉���6!#> S��m��Zz�Аn�d����0�{Lx��MC>g��H^��dTmX��|Yi.h)5�f[���|�m�P -������\�U��U�zjnOC����۩��u6g�y������1�T�KR��yb��U�{ŕ0s~���&����'�ߗ}�h�/�_cs!wj^0��W����@數��ǽ��\\'�2nv���V=��0��W�'��a�\q �hh2�X_����!��Hv� L—�sC��}�Vo�]��[ Q;[��� �J��}�ا�7�^2�]ޔ��!��b"��(R����na��$��5�b��x�[-��� F�%V��w�7�t6Rh%�p8�^�k �HO\R�jnb����cXk���)�rR'��a^�(���X�t���� ���7P}Q��V@j���T��i5�ŏ��!UN80����V1p�c� �k ��8��N|�̸�uצUB@QF��#6e����0�1y���X��PD2\+���/�:'��*�:. �5�As���y�j#����'�T�w����f�����җ�˖uf7���n�pf�9?y� ~�7:��(�t��TM��0���� �V(� -!��"� 7Ǟn ��xR���)��B+ �����7��v�,l�� �//�WA�Ј/��]��]^��ʔ _��k����r����vᑫDJ����_�������F��!����:�]Fj-@��I*n�JڄK6j -7&��X�}#b�Z3�/���P7W�A�6��c8`Rd"g������`p^#��$dQM��#���b�-+����c�:J�Li�=S/ �C����+��4Ng#@h��bE�F8�E=���\T�����j��(in� �g������U�2��Y �e��ɠ�= ���o���)�!��p���$����b� ��f�U\�إ�Pj������x,�1�2��8 � -�����!ta�s;����KE���K�!<�nN(5��G�� \]���'�XM��0�ﯰ|o�ު*d�n?�ê�U�^"c&�c#{H�� dIBwKVj�������x`t��5[��ʚ���� ���2���}x����Hj�=���|����j�,�Ȯp��l�j��_'V.93"� /.�O�W��b�����Be"�D�� �� �3��,/WʫPi���'i�U���лY` -Yj�*.��S [&m��o ���/v5,�;����do����~�Q"�@p[2�7���� 7f�Hf(�D�t/�7�ͬ�P�.=φ?AR�䑶��"�hX�~������������ O��D��N�{���(� &��_�@�fVVEg�O��) �hC7�(m9����L{�$�����vH� �X3%}�[{�����:r�3Q�9��)��~W"���SM�7�9�{%������# �d;fgt�6�"�pNl�(�/��?�~�f��^���H�`���gX��.���WX=��Ʈ�}�Q���^��-����X�j � K��� -V��x�B}_�N�_����ަ[5�or�7�o A��S;��ދy���U�V�m�i�*��>��FuI�2|⊧�:���Q�C�]�<�J�G�]�'��K���7����%�:P'PM�'����z��Z �>*����=���?�����o�d��L��o�[Ks�6��W`xJgZ��u:�3���z��K$WZ��l��w>DY%ZMMs���.���.��=%�l@i&�"���׀��d��j|��������<�Tk�/ ��Ƥ�fxu���X>] 03���,3��w�ިh�6A�)�`;���2gI�!at�­̄�!�������GI�|[j2��E�`I�.,�1,i�͵@;�YuU>�Q�e�Oq�^֑b�A2H$l�Y�<�Kb�@���j���� U��)6� S�Gw �Y��C��=Bȵ��h�HD�|`���9o�`i�&��ky�` r�l��#�N�V�(�ƾ8П�(o0n �d)�C�%Cf-���$5�{�BC4�8��B��A��&� %I�DF��B�ɔ(q�R�vx�R��6�h��G�9ܶ����%(̳P��'�c�h%�v"Ϥhd�uM#���O���d�d�4_� )5�Aۆ� )k���(nԨ��Ȳ2}�5��^A����X��!(�C��{�w�L�s�pP��0vt���*zn7�[�%Z?r�V�]h�m&�֓z�A�h� -T����JR��^�d�7W�fJ��a�+&^K���/wS�֍�eߑԍ��^��[�(�"c�e�7��� +�JxN"�@�S�;�� �qx�Y�839ꘅ�EgA�N��,� ���gE���r�g ��'ÓO1�;7� -�= � -��P�\K����R�8=�p(���F� ���!��T�Óc����]�d��A+*�j�u��y�j���D��'��i�S�b�VS^Z|q$�z�d�G���]�A��N�J~�'�&I%16���^ķ}Z�GN�I�Yqt!|�پ:te۞2 =?�!�G0�Z�إ�Ơ�03���k�������|���BM=�u!q0�?�� ���f �g8��N���`DZ�j���E��ʽS'�:h���9��c��u��{BZG�H�u�_FM;&=�_l���fh�_nb�RR���+���?EQ����f壃S�c�����'\�L�]�5"�(���4�R��ب�H��� -��T�$�����L�>ォ:�ж��{��B��]�G��BO9/J�;���-��vy��������Q����'���c�s/�'�G�_���%���S6ՠ��m������Ï�� �3�u�Xj�s�$���i�`�@���P)��{b�Xr����<7�`����t_$�w���}WV�G�%,�g"ZS���}��MĒP�I�M�X -8q#�%g�kXէ�>״7��g?��ca��F��~�V5��'QK'��n�q��Y7��FI��n>s�ɯ��Y�n�6}߯ ��ule{�vA6- - �vQ`_ �YlhR%)o���PǛ��8V�$ݗĒ��9�s����,�X��(��4j6�����{���o�LRk .Vve����xu�g9�� -\����r}�� - E�`s�`U�O��2dZYg -洩L��{� ��̍��J ��:�V� aE"�p�Q��~BQ٬�N� �,��]U��XfD�0r��Ӽ�O9?)\Z��H���� pPN��6�����H��A�Cy4� ����!�[�RE�~���fR{�O�wJ\�ǹ9�����9�^V�A+���}C�Z)��&�]%�b�\+�%{c_i�.ˌV��r�F�"�#:�>kwG�ʢ�dI��m�� 5�.[����l�R��8�T>�^���&���jb窩�ڎ��ka� ~f3`W��T��މN_(3O��� �A���;��+N%�[��\^��x�TX���7'�9��Zq�T� �{��t�%fO*���fBr��s��K$Y���l��|�T -��y�XC�*�@,X_Չ�?��`s(8i���p�%�V����������2��?]^NO//.�N'g�������6�W9��Ӟ�Q�T���6�E@uװzWo�%Ėe[��g�I3:�M9��FRm�y�5F@d�}�3 �S��S� Sx�3���T��^�6�Sō�FL/8�f�N�b���X�E�i��Li���U��j�5&�*��^n��}z;6|�;~@��,G��_-�2u>io�Q/{�e�]����%ý&7�M=������H��9,`�s��bK��G]��H�j�a[�&���s��f.�a��. ��;ây�|��sa��O�M1'�x�1 ������빸��*�V�.�^� ��Ro�m��\�����M��t￑`�.?�$�u���E�����N��:��c�|�z�U-������i�6v�[����x[7���7k����U��(�[!XPs�[`v�9�nGט���0���y�O�s GoC)�)�������^��]�B����@~����)�Y+!\� �(\�{u�]/��B%�^���K(G�z�^�� ֖��P�h�U���U�A8� ���8'�8�,^㬄��� -o��� -V|�8�W��e��p���l���{��͖MO�0 ����(wV�!�!'��.(M�5"M*���O��0��VMZo����c��UShV:eM��'g���6Sf������gWӓXj����%<'*/��[�2/3�L P�Pr6��N���o%�uE9RR�/p�H(�Ό(��BB�;c��� c14&s�c6R��y�)�:ދpb�9��d��B;o�ʩTiE���h $A� �E�ij|�8�������3�����&m�)���X/"��2Z�^�#���|���qԱ��� <2SP��2�/�\�дu[l��/�K�Dm��5N+sԶ� �]�I��֌ ��~�D��)�X�nC�R�����NX� t�*4;����+�#B�l�F�1��.E�a�M���(T����@� kb�_�}��Q0l��?f^�X�X e�vO]��2�E���=F�qT��.�q�{���w�Q�;���TMo�0��W�|� �JZ�Jp� ��K�ؓ�YǶ<����c;���*�-�e�7�۫�����;�r�Bz���N|����+W��r�2�S' s|�49�Du��x䆒0L�u�S;Ĉ�:��nx9"E��K�syЎ�&�#Bj-@��I*�� �b�V��ޒ���X}�k�oӴQ�� -c:�4f��- ��_��~N8`����lI���j�)RŻO�i�.���$]��p��3��G5H�,%9/�m���X�-�}�LU�\&���2��_�|S����ּ���]MTf# ,�%��p�'k,N)��Xe�r}�fj�G����I���������#+$+��V��!����{��+vH8�=���k�uۇ�?��V�:K���;�߯��ۼ�7� -�t��ll�5����毀��������mSo/�͖MO�0 ����(wV�!�� N !1.\P�zkD�T��u��4]a|oդ�V+�x�8�_5�f5�S�$�|r�i3e� �ߞ^pv5=���1�l\�s��2��5)�2���E%g�J�?[� -Pά1 �'�i$��gF�J!!��O������[i���8�B��p�8�� R2� ��7k�T���u�K��@� ,D�ij|�8�͙����3�����&m�q)����X/"z�2Z�^�#�-�|���qԱ|��;2SP��2�/�B�дu[l��O�+�Dm��5N+sԶ� �]�I��6�K��~�D��)�ذ��^�Z��E�����UhvlU��G��Z�ʍ�c��]�T��Y���(T����@� kb�_�]��Q0�9ٿ̽�D��p��&��e؋zy!{���R�]&���^[��.���W<}͖AO�0 ����(wV�!�!'��.(M�5"M*���O��0�탭��ݾ��'���7M�Y �5 ��]pF�L�U�_�W����b��s̋�KxNT^G��fe^f���ȡ�lYi��7k?J��FBI>7gF�J!!�]�S6?c,���d�Wm��?����"�վ�+�̑ %����VN�J+�$�DK �P�g�`)*Ms������L��]�N� -���G��/r`0�b����Z �M� :B��+_m+u,�b����)8�� J�T�$4m��[����Z8&D[�x��ʜ��; -s�vRn��q�4��H�9=�o�U�U�4�~� +��n P�f�V��wb<#��Vn"��Z�F�܅�1 �B5ߗ����&&�0�F=���ï��_���=�(6cn�K��i���Q��d���)N�!��g2�~�U�8 -���O͖Mo�0 �����0�)�a=�����] -Yfb��dP�����r��k#@|$$��#~@�uWY��]&�,>KN�¸M&�V7��Jq��J�U!��B&K��[����˺���%����L�����UJe-���i���H�T�V29y,��H�#pEx�p�E�$������ � HH�љ\+�lM0���v���h�" -��Uci�8Q�������z�0��r�h��}ż��U F]b��sj -��,0��G�� ~� ,���tf� -�2�N�Y{�>o�@lK@� p�����z��q�_��; ��rR�=��v;��;E{��Лܚ~��x� �'� P���R�������߄�p�J�[�[���#n�Y`T�{j�n⚘���� �N�V�{. -Q��2|�"΃eZG�ߓ?�p���c&����z��.J��/^>͖�n�0 ��} -A���m�ð��b���.�,3�PY4(�q�~��Y�e� >����(N�ʊ�t��]����¸M&���}��ny�j��!��L����$ Ѣ.���N����©B�4dr4N -[��BO���4�(��'ILPC]�YFP[�"�ԙ�(��L0�����l�h�2 -� %lTki��P�L�����z�0��9h4Q�оfh�d^��I��D$�:��� �܎鈫�O���_,n╙��瀌�si6�^�f�;(� -��SAh5T�\�qWA���<��T�����:��*�����k�maͰf�S<��-ȳ@��Ewb���b|G�o�B8� -�Nf�|�c���wqM,a���� �N��9�^ -�BT�� Ï���`������_c��*����ɸ��w��w�I�C^�͖�n�0 ��} -A��ۭ�ð�:`@��.�,3�PY2H�q�~��Y�f� ��E�)N�ʊ��w����(8� �6�����p+���&�V���(�%s�9I���˺���'�Z�ucm&y� P>���ujR8U�JC&���F�:W�k烘����$1@U����ڀĊ���ZY -fk����e�FϠ�( D(`��K��h�ϼέ�σ�I����U`�L�J0����ϥV��]c(�x��O���_�в+3Eo��Ҭ�� -M��W �% � p�Hh�g�\�qWA��N�<��\�bϸ�>���C���=�1�&��_*Ʃ0�� ȳ@�Awb�z�ob�@h�oh&��V�&�|�c��^��qM�a�z��? ����.�*� �BT�� _�qh�t��6���7���j �s���� N�-��8m�n�6'�:[�����p��A��JoF�LXP��,��b��7ebh�!avlN�\� -�ZE�q�@ (�S�����O�L�w!��Z✕�h�X<��FEV��)��Nɼ�2���/��P� L�b9Xwm�C��!!�*����.@wwx��L��2X�J,2<�s&�3+aE"��uD �8B�ӹ)�Y)q�\�0�Xݝ��|ٚ��r#��u��cD� Ȗ�lDۏ*f� 'Тqe�\�l{�àe9�r͸1�Wp H(��f.�Mh�������_�+f 7����k��&�M�^��3�v� ���ر��ɫ�XO���ͺ���FS�t��F�٪F�Q�O�ХG��b��(�~c #��]�0�kb�_Ի��A0���?b�{(�����b��&��ߋ��P_���)���ɸz�+-��Ea��t�?�UMK�0��+�\TX[���VVPV�eEo��S78MJ��ǿw�����ʲz����{�G��eE0G�5�8���Q���9��ˣSg�^�Hz�l|*f!��8�(�gua���{�� Q*H�7� �`d��� -S���@Ra���8G�� Z����Q�\{�k�a����I��Q>\`) -�a�$�U9Y�҅�f����e+�+�"�<�:�l m��tp` ����+� Og������<]L&O������`3��������h:��}��tV5S�&�yf�1{w}V���;kڋ����DŽ���K �:�ؼU�q�Hd?�j��������Z.��@� ���-����%Vj�=����nĪf�����KXHde���V ���Mڤ��i��߼�?Z7��?���WM�� ��@��������Zim�l{�a'l1P����;6��n�m�J�z��f�{3 �_��"kp^]�w�����FH�,路�o�Sr=�ʹb�4־���,��Į�0�DCȼ�T�R�7��Z����R�F�D��e�ř��N��emԠ� ���+�٬|�5ǃ?��z�5�%��_���$/hŔ��ZzYJ%æ��)��=\+�b� -S���l;� /��?�7��I۱!��H;4�����>�"�`X1dBP�J\�|@�g������ŋ ⼃�8M�����>U�QV8��!�����.���>�")7*pX���!�}"���ӭ�n37���f��p��d�8K6��x��H*^�n\wn�.�i�-u�%��=7�� p���0Yy�&��Elm����C�x�]3�x�glR��ƨ�T}\h��N߰�J��g� -�sAɟi��S3�9�Zp�����}��k.��%�/}a��ړNqbޑDB'���&� -�ﳚ�+j*�D4���|V����~ $Bg�s&��@�&cM�sDy�6u�rvP�u2[|L�yD��n u��NyN��o����˳��=�mQ�N�0 ��WD��� ��+��ܐP�xi��v���I�]� �3����~ -(F�ɧ��Mu-D���Z��<]�J�ov�EC$�8��s�S��*w٥����z+�a@�2���d����rI�wN�hP6~�6F�Ky���!#�LG�e��v`�"���V��%w�-��%�e-� � {�GO���y�2-����O�Ż��d[L�s����>/��M!��)+�}<�?՞�䓶V�޵�z����?�7��o�0 ���WDy� -o�7 ��~�!��Խz�Ipܮ��I�cl�����Ӊ�Ď��'���|hI���+���K��Y_�������:_��L�*9�X�F$�ɲ�Z�&T~X9�,�ժ� -ݎ�m� -z��;OVR�Kt�Vδ����X -��>S*�6��$��vbJ��K�e��x-H�]�r.˔Q�X)tm(�@Q���Y��DB ��p�����)p -]y[��_���2�I��� s����� ;%c��k��L��͑�I��JtO�7�A�����V�#C ��n��|y��j5_z� �wܲ�}�0������;�� �ZU�@U��#>��=��4mO���|>�l����VKo�0��WX���7���*.nH��LӉmf�%��8NXh�Uţ��8���f�y���4�8�q����R�ծ5���?�y�B���Y�Q1��l��}�eQ�����u��B(��݈X�a�/XO-�7�^;D�!"����Y -�`�4�R]%�<�؟ Q��# `oj7a.'�cP ��a�$/��B��͗"R�+���R��)*�)�菃 -F�æ1h�\I?6h�̍U�]c�Z�t�z=.���_2� >a��$9AɊ0���N8٬��&J�ŌЖ�>֝����hE3t@��p�[c�<�V8b�� �'�yjGu$H�o��Z�{���2P�_�'W��d74�v��@�A��K���s��"�ͭ}b�~��I�{����ݟ���6���Q/� -�n�*�>3�x\��GXAib3#,�ǁ!� �EU��Ϭ,ޱY���rf��?tI�^���m��-�4��SXI��ݔ�N�0 E�|E�=S�!� 0��(M=L�yล�{��@،X:v�{O��W�E�E�]!�ggR�Ӿ2�w��R\�Or�*F��],�9\fY�fa*��pIK�� i���˶�F�p�As�����) 1( ;�c��{7�'B�� ��T�=�]�*���φ~x���Wi\%�*#��\ȕ˜EVl�&lL4�A�]!C]��-��o�ZW^�����5��[��0�u y�� �ž�=ӭ<۞�E� -M�&�k�вQXC��>�#X�G��٨—Oi���R�n"����)����b�|�ז�X��i�כA��O�`�#�w�=�7r��%��~�/H�.�<p��ݖMO1�����wY��  'B����̶�n���v���� J�x�{����g�M[Nw��c2�U�n|+:�qM%_��{)��Q�R9٥J���(�5m�~7v�E�J�MGTI�O��Ӹ�`^g�:�1u�R8��(��v�T�W�d$Di�[��� ��1����)ԉ{W%7@);u2�&�ڐ�}%CW��+5�;���Jګ��z?����&p�Pކ��,N��E'xri��ܶ�hY�ܭ,��y"�5�B���i ,�˼�=K=��p� ��g-�5�^?�J�ha�l 5�0��eC��/��[bҲ�u~�M�P_=�h}ueq�N>͓�N�0E�� -�{j�!��* ��� r�ic?�L���q� -!Xu9s����o� $����lz*xj�ץ���99�b>�5��fO�l��R���&�a;�����b�"��u�O�6:���`n�Zd)�v@Q���R~Xg! -�"�ϴ�?_j�1������p�}� -�4 D)tE�����H �fk�ˍ%[Y�ܕ2����G�.<���T��P�@&���p� .�3�]z=�� �b�g���F�P�a�6�b��X ��3�+��A'� ȿn��*����z�5��p -���}��N�0 ��{ -�w�!.j� � ��TeI�"�$$nս=V����������4&���A�}�G�AG�»�ח���k�W�áH��G!X5�L��`I����{�õ|�n6vRɽ=e�A �$��/`�H��q�� -�@�U"Lʏ�;yXHfM�g��*�0� tM�ʼ �J��h+6�o����HN�h����N� ��>�d�.z3��� �f��t�) L���m�kcLL�~���:����rq1h���!P��q�]������aSYoJ1���Ĝ��K���q�U������M��dž���Ә2�sB0�d,�0-����ꨫ)���Pذ�[���W;�xҘ��;; �����T��Zk��>ڏE��b�K,�`c'���jW[��ync(�{��By<^�L-����ozh]0�����3q��i����.������W��w�9���)SKYz��kK��S�fI���Y�{��>�/͖1o� ��� -���nUe�-[�v�da8;�g�p��_�8Q�K'6���� ��8 � &��O�G��io��%�?��6G� -�co�+�2�gN ���p�;W$�i�7�U -ĵN��%Rd��”�d�m-Z:I��^:s��N�H��d�����u�^��bN:�@9 ���)Ir.V���W�L�;`3/��,�G�[���(I�X6�5��Q�>���F��bBߗ���<�h�.��-Q5� m�\�8Sm^-�V�;�n�b/�_l������䓷�/���A���͓MK1���!w�z�m��ғ ~\�M�m0��̤t��ُJ����g��}� �r�k��b$�]%/��R�S^������٥�٤T�DvT� s�*�\M�&h��:䂢�b���d�҇}�i�B0�Hɲ� - J%ǹ�D��4�b��i�[^���X�^�~ ����k��H�N�>�lB)�&����+����F}�[C�6�p[ɐjk:^���'�������W�C�5�T4���M�3���zDN� nC~�q����Ъ,���R tj��z�'r�l�r(������љ�M(�_������B���$3��D[�)�s�T��F��eX�<��d��׈�t ��󪄥��i�g@5�^;�� |��\~��O�Ȭ�<�Z�j|� 6�?5��U�瀫P������B�B�Ins � �l�6\Z��E�M.c]��@�׸���[�2��y�.d����0�ž�0K�'�:y�Ml��)�f#fj�,S��@�y8�P���R,��k秡�|(,�3, � s�-����][���x�ɷoڿ�8Ƌ����Mk�0���B���[ ���R�9$��Hg��+��e����tw��$PP��x�3��Hnηމ f�1��t�^ - &v6ܶ���˻R�/O�H��@�\1�3�J�H����" +�F�~�\+�@��f����~iV�A�)����)�ʹny"Dc}r�10��Ö��AᅾC�O�=�Qc_�^c�{�zJA lM+{pT%���<�2���fTT������e(����\4�E�c -�b2�&.\�����VN"JV��7jR5 �U�f��~��������k��g���ܮ�MzK>#�s<�G����I%N��!>"�q�� =d�1��黎�u���!� �c^]��>Z:F�b2�A��_��[KL_�n?�����[��λ��8����@�j���$�&\T (��7�ɷR�����%U��;q���_���Z�F�?�˟�WA��0 ��_�� -7�ڽ�]఍Ҕ���&��V�'m��4m�m�G8:�l_l�Iw�f �W�d���-g`�-�Yg|1���=g�ӇTj�= ��g|C�>$I�&n� -���ģ�lUk����^� -h�S�g�;��Q�wB‘Š���p��XZml��Hm=p&rO($e|%tw�I����(�r��wu�U��2B,:��wae���bw^�r�3iK���ɨG� �օdrk�A�&�}�ɐ� �,ł�k�$]�A��J ��Jr;�+�N`B�{@�7_vg���+=��+��0� A��C��th]\d�/�W'v �PL�����z�=!G(���Y� ���O���<��G��V���" ��o@W3G<�F�|,d D��r�a9�6�G������J�cb���O~���Jv���y�����'��]�Um ��m��ޑw~� ��Ax���U�tDcCu�kn�ֹ���9�=�����ݬZl������� 9Z�*=?�rTՀ�=ʇ���AD��%"M����UKO1��+��eœ1,��r��H����vg��ۖv�,�ށ-�41�����|�i��aSi��5)�uO9#m��,�����9g�A�/��؄���E�P�usWئk��%ge�uʫUX�禀�p�i���̈ -�>�ڝ��C:���5������}�|9y_s�����K���\[�Җ�����F�x�Dh��O�<�H���Qv�����~���*��>����)/�@�*�\i����:�J�t@)j�C �X�X@�^9��3i+����VZT�s[D��2g"�7�Q���Y���[ď4Ҿ���Qq���� O6p����n��X��P��k������=i �1���4 k����:&�'�� p ���e�����pk=���8˭��9�7r���ݖKo�0 �����P�)��l�e�@�L���T��?�y �C}�v䛟d�,ov�-F��U���V�o��T���Oo�ku3�* �JΎ+݊��H�,���C)8��=Q��=?ү]�[���V�:��ܓh�C`�/烥���̯�*mhT���i��x*�\+�b�ס��9�Z��vA��,�$�V����,n-�ڒ�}�C_�Э:y �S�ƛ��y8���D$�2� c�C/�=���)ه�����,.��š� �8��׿��k)6(��:b�L_���I.H� n0N`���w�&9�IsF�c1�3:�0�L� ��+���c��lraYIL?� 0? ���_ʑc|��������A ��{z-���y�ʃ��u�j�LU�v9�ݓMO�0 ���QB�&ݍ�!��4���F�I�ݪ��dk7�ą�h��^�v��R�<*k>�_sF�\�M—���-g�a/�Z �LxA��(D}W��6}����+�^n�]��j���A �83�tB�7��$����5H�PWM���r����/�1g��U��l�s+3m�[�D-<�� �2�<���+G�s���$���G��_�+� -�wj�)44�tؗȐ�������6R�� -U���m�]�i�;�2B�K���+G��L���5ㆺ���ܜ���킵��Yc��T!��zh��8ˬ�?9 op����=o�0 ��� -A{�f+ -����C�N -I����"}����,皤N��$���4��E6W�5l�w-���sN�N�m���>������QF ���;�𱮓U�]��X9����i�=�O�}�`/��v'�Μ��A(x�Ϟ���c����G8��'�U;�����K>������8�;>9C�U�{a0�{�Zj����0H��D�������K���њ}^I��C6�`TQJ�)o�bjy�H��i|Sg��v���\�&$R�̌o#�^;a#VA&�ct F;�-��5���Ё�RD,��u���j{V:2)���S�[���(r��m�ð� Eb�U�U��=��&��[c >��V�t��q�S�rS����R%v��/i�x)�y��T�z��b��@�P�Z}���I�8�tc(e�$F�=�{Lh�P�J��NF�z̘��t���/�t�1F�p� ]�{,s�� ҳW2�#_�ւ�x٠��� ^�D�\�@5�no���U��?������W���e�`���淮�a6� -�X��/�O�7�z�����a��> ��Rpx��5@/p��"��IpB�bH8���8�v<v�� ��_���C�d���/3ѧq�t�Q�@M���� �qt�Vb�]���/R�\�?���{��I��A�g�?�'�8N��"�}�䲲p�M��~S�m\\�UΪ�7��t�����n�0 ��} -A��ۭ��=@���L��dI�h�~��v֥A�fł�GI������jh��:���{)�jW�]�����w�R\�.2mT"ېˆ�LӸJ|�+7$8 ���;crَ�)� -z���* ܂�RX�B�J�#��$��ƫ !2l��,���C��莧K��9Og-p㪭�m%�*�Ҝ�Z�V��~�c� �Kߕ'�h��e19��+�K���e9m@Є�#\�]��K�9wd����]� Y�0@�E �U�M��8S�2)��w�� j�Xe��[p巹Ds�s��Ը��.Ԟ�/�&����Й=��Z�����4U��3�#.`�tLC�8@u�!�Ƿ�'�O�2�"�*r��� k���b׃6]��D��Л���B��S,wa2u���Qg4� HW���[�.�cr��=;��+�۰�;l� �$�=�G�|�DW�S�Y���Y:�\��U�R�0��*�c����S��Q��c �b4^쿏,;�v+�أ�Q�t��j�Gg�0��[~Y�� � -�����������zs�(+Rb�ا��D�W]�U���Xy�:��l;X�r7�gۍv"��[!-�E-8��A�B�����ś ����Z���j���q���9�>�K�92 -E-� -�2P"AF�;��4����8Hkf�� ��b��:(i�zZ��$�&R6���b�X�(�4�g4�w�}X/4�k�& -�*pUm��Vv;aH�����U&'�-`N�{KA>�"�J�-�bZM����^����e�3�MU� /_�� �7 ����||[u0F��Ό���oY��:'>�}:�g4��w�m X��P������a��v�l���&8��K�/�OE�ǿ��^5u���͓�N�0 ��{��w�!�t7�`� R����&!N�����MC\8�?�o�#W��@j��>O�GP\�|8xۿ><��՛ʑeVl�XJz�Z�6SOۀEsv�����p�/jNN6��w���쀜�Û��1��RՀC�yq���Aq��;�%9y��'_��ؒws��w�ۑJ�ҫZ��Z���"�0��S�!(A,@/q}����7#�tׄ�w7B�<�Wʦ��e[9ͽ���K����˘�*�$�O�wq_��N�y)�o]�AO�0 �����4pC(�n������ܔE�I�ݪ����&n~~O�����`v�}���G)�>~|{}xFط;M�2C G6x�/JU��S���D'� ! S�3��n��l���88^�ю��%�/ru n�v�)E�2����wݶC��:Y�� �X�x��� 7�+�Ot ���r]8��ԣ@i���/N�Aι����o�V�,�u�K_�/�[�o�6�_A�a؀��ކ-I� ��]ܢ�-Q6g�TI*���ٵ��5�Ƀɲ�x�����|���iÕ�{�J�a�D�iP;X#'��aǶ3��ƴ�,yT��h|���� �瞭����Z:���R�������>QJ<�ށ�ꃴ��UnU qM�63�'���0g3*�,E_���cc\�@^��6�W@�A�>� ��}������D�`�;-uɽa6��&�!\;5yC�/iVۉ%��{ڧA|+���P&*���[iv�Um���p��C�����q��L�`% �� A���-��n�K��r��\�Xs�m���I=�i����4�q�bRV��1c�4���e�:�B/2���U Dd�K#\�@!�t�Mj -) ��E��ˬ���*!�>X���!B�ϑ� �����3tj: s�6�R�™!��� �����D���ee����C<�K�O��CUyh���+�����3�|~��������]�����˸y�%��T���p�� �9�M��B%q�J�(�PÔ�ݱ��V��d����&kW>�f��N�n�C�-��U{��v�M��P+=z�k���������ԡ�@�����~H+���cd���}[C8t�4 �nm���X:��nt�̍���U���r�!�9����Ki�_�FH{��q�+zj�ڥRM��40�ઔ��� -_��T�c��f����6�]׻ٳ�<����@\{�l�~����>��l�� -x�\�&gv� -11ŷ�84����[�0��X��0���'q���du"�~����A1�`���d�7Te���7���#߭�0c�f��s����@P6�FC��e�Kg`'"�(8�{G�E�������~���yu���.ί��7�W����5؂�y�C��c�Mm��������^#��~�ɀ"\�HBhfX�(t��2P����j =�Y��Ì��p=�/���Y�y5��6 �y�gu��,�e�cPw��z��t���:�X�aXq� �[��&��.��'X��� ���JtYs���`��X����[�ўۭ��@���M�6�� wC�@M�������VQo�0~߯8�����7��N��I��u�E�S�$�����vKï��I3V��n{������>���ٮ��Ec�V!{?~�U�3��!���z�����d�Jn-P��!+��>E㪨2�+t�5)�|#e�����"Ό h��h+�b��`�1=��X&h��T -T.*� ��N�!˹��`+�H��Y�I�H=Ad��tSE]L�}���4�:�ц>٦FT��C�K�υlU \��p~A�<��:�X��r���S� ��ߢ�ԙ-������D���ZC_��M��t-��w� ���Y�ً�R�+���ܬ�즪�q ,�,O$�u<�S���E��K��6P�67�oFHh����NtA&(v���$Z˧+Ш�:��`����o�v/fw��*�_]�,�__7JqU7g����� o�ZD+���]�����N�1���!�l��/`�J�O�R� /���}�Q�@f��i�:@� whOǰ$�H3�����G]���I���` c��yxP�l��ײ�k�^NZ�}�4o����5�t�|o�6ͣڬ�Zbc~�Уt�:�[-�Ä��&LԌ{NʑNp)~����;���������j��@��`��>\�_��F ѸYoJ��> Q{� �Qbx}����Mu,��;1 ����Ms� ���;z����i-gzI��iӳ�UD�@���� �q<��D�v�}��ez�h��:it�>�`�Z�J������|bp1;� -ŝZ�]���YF���*�8��3g��W*g���V���z�hޢ��������T����wÚ+�Cd�T�2�=�ůZlK���N�]�/���L�DU1p�{)rVs�0�%֜��4��f����2�6 �b'��<�aZR�s���A��XI��"7K -p�w:xX�X��s ~ّ�}�F�ٶ�i��";�-����#Z����{���|�rɺo k�ކ�C7�7�W���!�2��6Ɣ$�(�Zj�v���ku�;�:�AHl�3,q��nKI���5H���Y(,���Fi�ڏ����߯w�ԵC��&��ri�FJq!�s;�݀�!��h -�Ԙt/�t@nVtÉ�"Z猐�j���{�@��� c�4�9u+X�:����m�J���8Q��~��ߧ��m� ��D�(��O @%-9�'!WCMLj���Q!���h�&~w���޴'I��x D�i\��~��PF�y�|w -���ұSC�ؑ���Z:VG -'�ᯂ�m�z��1"=V���꟤}O? �C!)�j�qCs{�Nz�*aG��d?�z*z��� �t'��9���'�4�7l�Ŕ�N�0 ��<��;-�j�<�)K�5"J�m����@H�&��ȉ����Q���,�0&��ŝ�:4�o�x]>�>X�7��*%�d����Dz�軾 C����vk��ޭY�g��ƞ����^i�b -~H�o*}��y/[O���g�I�b����㱖�'R���h)Ze�;���XC)�5a�չE���Z�=�T�):���A�MaNN:��tp�HR,;�Ϊ{Coa�!��\�S�ЏN)򽟎X��NUNP�||�Imf�;� N&��ŏpf���; ��4���Ќ���wqz�g��J�#�Q�z���ς:�� �����?�YM��8��Wt��[pr��RIj'٪�d3L*G�,�AY"�� �~[�a�@������~z�?������+�$oz�@�u!�x�|��z�Go�/�\2k�^Vv�L�����4�M'�B�����@YK9H���)Ef]�P�B;eI�?��WX�h~}���������"3��&`s���I��s2P`�j醊���b�<�<����ax�r#��8��� �/�Bm*#��$�[�=�����p�)1�%i�������t?i4F�gDV�L -�.�13�����"�Zg��ʷ(���Z]����BY4.�s!���Y�)3�z^1M#O/�����kE�����,[�%�r�V�np�R(&7�8��� -�`EoЁs2�� ��.�����]��6�Qf��*mՅ�!��-c@ -u���xB>7X��~�s�z�p]�"��p���h����oͯ������ � T�͜3�[��7K��4 � � �����A�䨐2��� y� �5����-�z��\�4��� �CSۍ�E�y�5� � JР�� q �5�>^�\kف�[�=<�{r��,����b)č`�D�}��)�,,Iێ\�?�Gɺ�Ql$�oN*�����{��1�o��=1a��f����,���Im���jxTd\�#���@� �����L ��2x�&�ُa���\��a@��x]�*���Q�.˰��wg�{��hQп#�Z} �]��S��6y^y�&.,�FW�+yE����Ci�X��A��"*:�ir�Y�R�W|/�X�p5�&LG�̪�jP�ށ1�J��(y<���װy�w�(�|l�J,d��}���w�����?�?�ϼ�<-��S����=U���=I�)(���֐�tm߲gٸ~{i�]�&��T��7���7z˸���'d�Z��/�~��8�ı[H�_���k�ڽ��1����<�� �'W�-��Mj5��VeDH4����o���nU�TgO���t�9��,-����D��a ���?O�0��~��wj��� &e���X8��/���\���T������w~��\,v��-�l�W�n~+� ��%ޗO7��0N� ,�Y��(>H��<6���G�9��9%�}�tv����x�b�ڠcq&)gE�m�� ���d�d�k�2�[�me������5C3�׸֝��3C!մL���A�M��87��2)�2z�e���,O� 9b�s�����o��0�^H�)�pU�ǔ�w=�hM�<��P8 �V�t�_þ�oIR 4�5_P�;�.�_6'�C>�;�%�.y�}d�m��O)O�M��&�SJ��K㍃�g���?���V��(��X˒�0��W�tNn���^R���%K�*����,�}F���`�Tr����h��ְ���-���{��J��}.��/O�>r��|ȥ12*��� ���e-|�[/,`���)�oT9���腄����d��X�B[A������,x-L�m�)�Egpi :φh����8����Q��0�ZZi�lW���Y��G2ޅ��Ʃ�I|D1l!zF#�l����Ѹ!]et�m�K�-��_�c* �]� 7��UΙi5I��= �D7�[��� e��V�eQ�E �o�9s[�WB���!�A�}��&�t���c����/��>�G~3C����?�uABYk����#iLp�a����B�t��=w�^`�6)0��ssRg�F琚�Iӓ3]�ӣI�x��ܑ X���\ [j[��#B�[d�yf}S\ѥxV`i��O[���t;�=r��N��cċX�^gh��FgTyi�-���9m:�13�|�.7h�t��7�����'𪮦�]�u -��9�ɜ��Z~�U�n�0��+F>� �J��JH\8���q&���D�xi�z&qv��v�V*��'oޛyq�����C��k���������Z}��������Ee��$��Z���粔������#�1]r�V��Mö��1(�z�8j����i���ph0��`^+�����.J���6�Y�(5Κ�}Ah����� ���G��Ƒ���%9�`G�`h�\�oR��2xc�,��f.�����qD��\���U�2+:7ꠇ�_�=�"����݈�a%�4gi��n��"�W�9��=Ho�$V<��^���S���(d����I����{��R��� 6`'#��I׽ M�4�`�j0R -��S��o��4`��"��9t��C�!r��l�S?���B���Z�>�O;����@��u� H�\�6�JUV&�)̉� 9�V/��/�|��Y�I��?2"W��X�p����O@�2_�����dz�rM��So�I!!#�\pt�w&h ,� wP����,w~BU�(���VM��0���‰n%]!!�^�ܐV�3mF8��'���L����nP�{�|�{cOR��{ ;�������tƷ䶵���������Me�N �٥Zu��}Y�T�.�~_8�2E�`3X[�����{2w�q�Q��=�� f�$���iuPQ,��8�>c�uQ��>.pҕ9�x�8�}���O:���6\��� $�L�$�(QC�� ه�����G����i�i�7?&qT`2� �`|r�3\`���>u��"��!�A.�; �rp�>�;�2s=����ρ". ��=�V�D1����=���G�� i�э�g�>���!�҆���%C�.[d���m�C��\�NvH?X&ٴ�?0�� -a���N��#��w�l�%�wFr�^�jp/q���E�.����d�3�C�~�T�r�0 ��8.�*�[.'9ץ��S�,��B$+@�ݯ/)َ�^��Ҏ �{�{౹ߏ ;��bh��� .�Z��}|{k�~s�8�"��AZ�U�]]�J> q_�Z&g`;3�&y}�l�w�g4숒�å�֭yln�A��/"���8\ -���7��89�W�����l-K@�*�s�#�����瞩 N�� Q���]_ײ�������1-w|p�f�P@=�Ϧ@��7㬸�e�M�)��>3ؕ��^��c���b������.>�<��g{��$���6P�9�OWR@[ *�ҝ%��Cg��� /9K����QL��@)Ǿ�t\G�ԗ�5����U }���O�����W��,\ۣ��u7���� ��?<�ߘ��6��l~�U�n�0 ��+_�o�aHR�����mY�kn��JT���Gɉ���Ev3E򑏏�緛��C$g��ه -�jג}XT?W_���vy3�F�l����k�{ߺ��"�1� -�d̢�=���0aV ��XNG{Q��� ��op@�q��ɫ>�j�=���7 ����t�YWM�4ˁ2Q�GVLz2��!C�����Uf���u�1N��|�Q�,S�_j�#�`#p��"�Z�$�� ��q��)®�'2n�p] 2p]1J2��\� - �0 �nw�8H'�� X��(�� 9r����0�x����Fca6��G<'��[/�h�W@\������:9������D��0�7"Cb�@i�.j�( �@��w�c���7��id�]2-(��#Jb�e�'�F�&��ArBv�(��Vp �� ��k_"�����x�-�w������;?]��ݩ_q}��ՉJM��  l%E�H ,���q���c[���ߞsZ� ���{��/Nq�n �0D�l)βSh���]��av}r.�rrT(#c>lc)"���|�k��,R�0�)�o�q���+[�^*췷�R ��@�"5� -���Ik&�R��"��si" -�$I��r����Ѵ��]et���4�'�K�NUƩ�v�60��=qlP���F/ �[��|w< u�m<[\9]�֊�}����9Ð��? -p���T��s 1�@��:ي�4Z�k %X" -�h���V���%��A�t� b �Ӭ��6��^���t�Z*@�݋�9x��:�Y�ʐ�^�w�k� -k-�D�O�΀��`�,�]�9-%*��y��ň��=�K���|7V�$��:��ɸ�v�T�����n������E�VV���i=!��d\�-�|G�3*��aŒ?;�q�R2�\I7:AFa���(櫞��JY0.o�ڱ6*�f��\� �P�E�ΰdH�oē��B�&��#� -����]���h34;�����*���c�W �l�!p;Igv%��]���8�wm8 �|ۈS��M��눊K���G�Oô�rKi�$���_�E����1[�aJ\C�z���-'����_;Ƿڧ~� -���|�~�F�2)����_�"�>�o�T4��~l�<8YP�—����!��2� �I��1{���l����k0?ſ�U�{ԿL�����}�>�������/�YɎ�6��W:�ۙ���� �`N��tc�E�lf(R )/��TQKw/���s�D�������}����F/���?F �0���E������}Z~� ŝc�Y�E��>�e6÷�|�'f���ϜK ���x���<1�3p9��ʗ����3�b��wa�������K��R�Dl+��������"VR��x<���/5J0��o�7#be���5lv��ܣ�L� e��yLւ����3������dʴ��َ;�#"�0o���\)�>-� ��ѲWr�Q#r�Ɩ[�9�e��� -�f�ʳ�:�g%���"���{�j�"�c| -xT�Y ��������L�LÎ�N�v�̚�|au�����Zot ��X�ziH�>C��f���c���ޫ:U���I*�iWB�i��v"n�$�e�H��p�( ���UR�Spb|� �3GS�w-���8�1�K��������l� �ß� �snQ��Ü�M�L��*�R�C��Z+���� �cz%%׉�Ծ�w{t�d�"[�]qO��Z���Nfa'�4��[�{�̾O����<`aiPi G��>(F���$p[���x ���^��S����P�=� �(����77��`�l�L��m0^g�e���(���r%�k���m1����B'`?P�{<����r;�n�Î��i�f��U;_O��m���0q0��p<�x�X�)���>�\׹<7h���-�I�l� -�[�/��E�)|^�����l���' "P �x& -�r������p -��9�������&=.!�w־�h�Ʊ��5Az�O��o��]5gKˉ�@� �8�l��Sĺq�F��;���o�*�u�&� �J)\�bw.���4/]MLq/�����ӻ�j�g���t�kHVw�}�=��d���piR -Ѷ,���*�k�M�t�r�4�c�4�F/J"OGй�L���j a�;�O�>��ވ� rԯ�o.Z�Я�R0e��M?�E������>��4�)�Z���Rc��q�cl����L˭��bJM:���M��ɰͤ�)��o�����И�F��+�W��v+'�*�ф6:N߈i(M�jP��M:�9'�-���;�B+�G���AA�c�2JKXKI����\�z�����=˜�*���Vs> �����n�8�����F�]�X.�v{��"M� (jdsK����;���8'� �7��9�񟟤&��+ 4Vh�%/�'���B�Y�|?y��e��O&\2k�V6K��կҔZ���.���B�Z�(��Y�D�L@� -m�8fI�hoL�L*�r4+�������_�h��9���d�b aE.�p�,�}.�=Q_��K7U4�$�Z�=�s�����܈�Q��uE�,����܁6�Â�^J�����[�J��>F��[0nYS�����m`�jd���Ѣqs]�hrP��ގ��0ng�PLvO܋�1:o�ab���n��a��,�4�7Gؓ�(�f��.���&��X�k7𻞙��غ6� �����:ƚ 34��Α{�tA�b� e��50U��AA� /Z��#�*f~�z{Z����(2��] ���׆���8(N���v�u�\�s�Eq}��&������ÿ����C�4 ��A��<쓻�Z/w�L�}:l��YR�s,6v�y�#F�-� ť/�fn���/D6IW�t� �fJ�~60�V�� ��[-��R�����нme4,��{C3��$�Z�*����1l��������60���U y~<��� AI����(E���""��m��k=�%w;�`���nY������Kzh���k�$o��&�U_k���3o��w�= {O_ߨ�oMH�(�]Ԥu:��I| ZJ�(O�ڻ�Hv���|���O�4��x��.q>�fA�<��q�ܨ��8v� �Aކ_���%m)�^���W�����I'L����h=������AK3T��!���#�4N��'͘[O�0���V�@b��6MMQe�� {�r���ؑ}R�߱s!�tSG��Rŷ��;7;�=��l��B�0�2�����Z������׀���FLRk NV6 ���p��A�d�~(CkX@���a"���Dєی2E�18>"d��t�M9��2�E(K*-67Š���a�� )�_��c������]GêU�i�������l� )a:EQQ��Ut���n�r[��$�C -u���̝I|�͍ѹ*�te���cC �m�( N��P\C�hXP�5����mZ{[8����ޡܦM/�c�^u� L+ԥ��>X Ht}�8��y1�g���jv�j�`_#H��7�۔��ˠ@��o�DX����c�p߽����̴�S�;r����3�3#8P���ˑ�B�F�#+% I��� �徨"�ݷ�;3�ړ9�|�_�On��e2����M�"��u�E>}9�����f���SK�|kO*;1<�p�7`���w�;�!����=�E�E�0$��G�����y���'��4��Y -Ee�ڋ|f�S�4�&��TJ���3���A��L��pȍ*a7�xk����!�qɺ�0�Β�&m��w��{��;3��/��� ����t�}˜P{^�S��� gk_�i��Z����������A�Z����Vb:dvb���A��ru>Ofݢ��4=@Z�q^]�����:%��T]�U\�����(��ƺ�Ƈٛ�h�-�,9`:�8?�����JD@=�Xi�R�γ�������%4� ͖M��0���+ ��[ v�RvO-���,Ob��d��c��ʲ�l�~$&Gk�g�pz�UZt^Y��ϫO �H[(���o��� ���]*�����+��u�kU�ua��AJ�� v��#��[�0��Ě�� ����Bb�F���@��)���pze�p:H�{�U�n����'AJfl'�f��ʕV�g�v�P1V����h4mLx4�����V�_�ْ�~{xsIH#��'��MO�0 ��� -+wV�!�nBC��p�������$J�����c���vkb����N�Ѷ��Ƙ�w��\ @��6n^����區��"WV���R!D�6�x5���v�����jem!6�Ֆ�v6����ޱa�d�)H�r�H!�S���.Q\)�Kxy9� �%IE���M( �$�˵I�4�ЮaUZ�h��}F#�2ګ�z�l��&M ���a�1�( A�� p���'#�*:�]�b���>�g�*y�����YB"����pS%�d�.b����k��/_���N!d�r� -����k-��Ĥ���w�5Hm�PqG[~��� ��;��ǟ����C�p��>����4�a}4�?qqi<[_���������7}����m_�����7�|5�v�}R�R�0 �� -�v�8.n��0�ֻ��(�Ա}��6����t`������\�N���l�W��x@ oBm���Ϗ���jyW�s!��p���B�E��:����� 4�s -�����i���޼U�d��rԆn#�p�-�J|��iB�۹��+9i� -�2!d�l�\l��u�{����=[�^� �,"2u0� f?��e�ldy0����K"�<A �䴘�%�.y�>��C��+��e�I�a��UuMC ! ���� "i�����Ϭp;1���z�<ۿ#���B���G����B>B1P�?�]�r9�}ﯨ������蘨�-��jѢEٮ�pbB$F�+Y��� @��{�9@"$v����C�Ó���������~��_���� -�������7��=��۷���c���d����o~��?�T��l�b+����Y�}[��l�ؚ �Xџ�8L6o'�����-Ƭk]���50*�8�w�AV���CFK���l� -PZ�x��)Q�$5.Փ�����wYkL� �.��J����_��A�3@r�^�(u�ӱ.u��2�po%Y�e%kт�&;��V��z��L�?'�_�x��u����8",c%Y���R�o��w�7W��G|��)�u���Zm� '�&��!g~����_���hLx��Um�����T;k[���b�����iq�@�ݽ�֘X�K� -�c�,�f�E����|��`�­����^=ũ7�]��/���,���mN�WG<�3@r?�f��V>��C�����S���^����C -i��ɗ�A$����( �Cx��9Ƽ��g�QU�ב��n9��Ռ\��;�jkJ���XU�1�ck��.Y]��ʙr�Y��G:g�_��leͿ=ԑ�>�4�*�� Φiq��CR��3��c)��6����`)�T�|!�L�?ʍ$��B�e�\��s�_/�H��jy�,���Hj�\,��l�� �q��������k����k�����CR�[,�=�_��K-@�{H�N܃�T ��|�$�Y�p�1��x�j��m�/U�0z��d^[�ƃ��9�[]�$3��J����7=Y?��i�C,%k��� 0�ܵ)�d��N2fr6�q�񐤳1��e�d��L�>v�{Y_�� �|1"�T��[�Eo�C����7 t�\6��I*�1L� S^�HVw�!����;]+Q1B��U$J��|����8=Z(��[y�Ux�!H�Z?l�r�"�Ct}j*��TDU�l���R<52�Y�ۮ4}�F)Iܑω��+��6��;��+Q� #��gӺ2mG��f�B8�?�Yը0V�y��S_U����� |�ۇx>��!�k�kI<$��������?�8� �US�3:>�+��FgF* '��L�d留Ե0�se2��K+ܸ��fN�K،���$#�V�*�nOF�Go�6\ -덾� �U�� �z<8���w��ƃ�t ������bt"�D*�R���g�&N+��ծU%E=> -�z�j��I��Z����U��U-���]£2@���;P�*��቗N�$تt-+�?`I�|����4�����}��Y�/��L`:�^��4>���jZ����x��0�O{zϾ9�M�K�h���dO}ï哌�|(U��ճ,o��L�C����_(I���Tyo@j��k�uY��8�")��>#Nw�&G�H�yA�y���� qvNTqI���|������8�+z�FH��HrJSÏJ�|uGug�U��[�k�,kez͚Y���,�uG���)Tm5�N<�e�tm������쳪ҦG����ߏlZUQ"J�}�:�&N&;�����}�d��`�x$l��V���8qX��npܕ�T���B�[�]��2����*��C�Ը�ۮnԓ�9��� >喣�Q$�ӡ�*�1���Sc�(*�_Z�mqK�NiͻZRE �a�����\W]�ڦ6�PMPʍ�]�v���*�D��A(���L��$JU�gQЋ�p7���ծ�SrJ��ڒ"Hj�t��ùJk(%̅׻�N�r���$�R'�b���\�2cs�U�:�*L�|� -����%���^��Qvd�(7�u� �!)v����=�)��ÑS q�e�jx��h�r��pF6�J ��F3�{Wu`���r�s845X��q 7CMv�y�7]#����$BI�� |����F*� -�|X��n���(�?~���X�O�#�W?�V�y�j_��N?���*�Z���`����)�y"VʹzUO����N6-��%K�C���r1��C���!��u�H�!#^Qw���ӯ,C;h$B�T�#���P�Sۃst���x~0�st������Ё�Z�Y��!,���X�†:����T5�Y��1-�;fj���+aZ��皩�^c���;fj��8Cyv;Yw��\���>=������8=4t�V���P�67 �˄�5- e�$g�/� ��D�H|��F��̚8����:��4�X֞`v� -� �a���`^�o2b=³/��J���H�c�����6�VZ)���:v�מ �����nJ|�b��*�[�� vznI�� *���K)� -�И��N4`=����蒙K)PM����ak�݌�s�N��i&yp��o//j� �O�}o:��J������Ő0�!��[�O%S�� -��9���k��KF8i�/˘�e�y���1��%{2Q�?+`c����@I��R� ��/?E$c�� -z8�Zo%B��s��j��ʱ.�˙';���QJ8�9��L�e�i*�C���Y����k�L*�YO�8юsM�/E��EY�j� �M��Q���x���w�ϤK�a � b?�f�emP�@�dg���ص�z�!ǝ�UbDN⣛b�U§�&����`�� �!���cD���}�H���ʔQFɇYw���2R�+a(X���kZ �Aك�H��%�ި���y�,�3��P>��C���כ`�7ra�Ȇ��e�"N'�`�ֲ~�?��_㕥܉2���s�џ>@_>�*�i�vKQ���Ⲩl�$J�����уh~�)� ��``�A��:�}�W,�v Z���b������j�S�:F"��݇��ɣ3����VR���*�ai�*����P�]V�;f��!�2Tӊ���2t8A����3�����bg�|4��z!�#C m��p��T�R�G�Z��s8dh�I��J��Q�GV�'�AR��vT#��� �nO -.�0^�\&��V�^��ܾ-�p{��^ �����2?Hd=� �\������/����̵�4�f��ofx���]m�:02�OƓ0��%��X�؅Ի�Y��-إ�[9օi �5�l�PVe/�gGb�q�0��Ȩ� p\bH�Cr�W���*�G�R�G(���Υ��[� -|�&{�i*i -������8��. �����8��!v��ŠPV.��)�?�Cx6��S����J����Q�Q7N9'd�r�ߐC�F3�+��o�kl����_.�ͺ��)��:�~"��J����Ov�����?�Hw�,<���,e-� �w��7�z7L�4�dE����T��g�]-w�}m����?Ծ�߉G!V���8��T�Ȋ�V�=���xD|���n?Z��üX��Z����%(��z +b}��-�H�ɔ*F� -�tpۦ'��؋ ŀK"w�G}Z/�����ߨ��f0%����~�1`�Ĵ��ɗ�')ٯNhFNy�Hr�X� ���L���E���|r$�i��N���'x����,�R3@��Oo-��3�����j��QW���m���5�X�U}�%��}_�W�����{g�M"6�8��no&Ivh~J �~��/@�"+*��W���M�� NL#�]bˋ N� ��$����|��l�D&�Ԍ /�Nl}�/��|OftR���F��/+�Aϑ��ū^�/_�`�l4x1o_/�j .FD�Y� ��^�3W1C -�jӊ�˭8��7�j�\U�,����=辯ܪ�(���p"�)��xͶVG*���EW��5ʣH�:e��I��͹��! ��}�RMܽ=*����G���П�.���!ύ�)��r'�H�et"eȻ�G ��������x��t�T�mv���Az�N|'}D�����GRc��=E�!zx����t�ä��~��d窆άn_9�ª,sp`��r��џ���?n��\�țWv`�r��7 �b�6�O����H�ҡ3_�NJ4W��3ׯ�!`��v8����`��B�nC�I�5wC&� ��:'n��+���p��V��!������e�N�n$�U�����O�?�t��y;����cO�I0�O 2S��� b@jF����7�{>��]���O�$�Ve��pd���ǝ�F�y\�p3R>��3~-3����y}��o��4�N]#�;�8��U>L�W�6]��E(����K�G�y.�8*�]P��v�fvc���嬈;^����лR`�����n�E<|@i������>8��;s�ݡBǜ���H���6Cj�u��mVĹ0� �L�"'|�{�c����<��pt,7��Jn��R���K�B���hJ��ě���sDF/����������H"���?��-��&���2 ���y�L�����5��8 �_�R� D���v��*Ϗ�Z�.�G�z?^���Mk�~���n��n�:��^*#����t0�&�p��k,�r�'ߝ��b� vn>���_���H�����Ogb��~T���M����4�R��Ľy�7�*��x'Z��K��ٓ�pu}^<}���[7���0�؋����W�� �c�k}��,G�&��(pugϵH�S���Hj�$ �7n���P�?��C���o��+�wP��Lى�1��䕘mcEN�s��kO_{G�� -�K����p�.�΍��zҦ'�6.=��^V -ķ���|ܣ+�Лٌ�&q;����K,u��7�B)��%Z&W��%;���}t����1v��Ʋ���� -�?�Έ��z��I/�^�ߵ.�pּT�ɦ���A��"�c�n�S�j�\�����}�wi2��n����.t�M��`<�G��8bW���� ��6���1D �M���O ���%wa��e���Y�^��� ��� d��f��L�i��f67\�Up3��Bn���՜y���w�#66���B�X�59ҝ��t��Koc1ϓ��ne|"B,c]�� �ٍ�wvP�U�s�툋<�}��C?�9j�ݺ5���ɏi⃽ӱ���ڂ.�e�8?���-�s�&?ViQ��߂I��t.<��|A6�}���)Ƀ��Fb2 2�0�� /EI���:e7�D��g��+�2�57��UU����1��δ^� ;�7L�WdY+KAڴ�mʞ�&�Ykb�� mDH*�?fX���t^�6$ �!��- s�7v����&�g�:m /����[J]'EL�8��߻0W�~�EߨI�~t[A�Sq8��j o� R�9�� ��$�}�� W�ka���x�0$��t�_5���I��e-��+3��b�7+�=��5Ѹ#�����X�ك|]GPƴ6k�:$�=H����-�T������h~1���mF'v$F�YZ�+[zE�����6T��Z�� -�u�{� c��]t�9it���]J��)���J�'��퓏��ݘ!LQ��P�w� -��+����wz�ڌ��`��1P��\0s1�j�Q0s-~RD�Mx��\��՛���qz/R�?�5���˟�͕MO�0 ��� -+w�pC��h�Ǖ`�4u�I{������.��������Q[�nzf�*s��2��Ŗ�Ke�~�_���/J -���A Wf%�~�F��Jm�,J�����Wf���.�Cf���I�+3���@٣�b�;�]�(wb�r۰d�2���X��;�15�I��I���肂����J��5>��s8&�]�$:=�اI�y�N0� 0��}����!D��¢,�4<�d�u ۤ�51zS�����d�N��O(�S��8�l3v�uUx���c���8oY����Y�,f���9*������DPF�p���������5����:�v�3�ߌ��P?����^�SE>~�u��=�!���O�i�3m�)��w5��.��|�|���ʎL���ߨ_Œ�NC1 ��>���6�nځ -&6x'��5�M��-����1td<�����t��aK�qN����#��vg���~6�F�!��-�W�W�Eߤbg����p��-7�Y�Δ��|�� ��␮�}>�>�ÀZ�\D�!���� 9| Z�*`��?�* ��%�����SK{I�$��@�E1|����u��y��7�bS�+��x���-��^���}Q�N1 ���w���R6`��|4m.�_��=�^{�����߳����ꈥJ���0yjC�4���r�j�Z4!1��zT2���s~�Z�e��N˄�k�!F����^m9l7���|�غ�����f�� O ?fV ��yG��i��̠��\�g��U�+[~.���B |6��h4$o�QZ�[�.�?L�`�%d�)O}����Y������:Rho�F�Vm�-b^r^C�� -(���6�ܹ`�E~�36- �G/�}���nQ�8i�t:�����MPAn�0 ����0��a�^�=�V�ś#�R���ӬA�ERb���\jHb�{F`�4�������p:� �et��R-N��͘��<�!]:a5�¸�h�=-��GF7s�Mlq����ϬS�9Z9�Gjqt�6��N��j�!�Z̋�a���}cun�C"�npp�����Ҝo���#�,Z!����aR4���.E@����:�S�yL���O������W]o�0}߯��O��@c������F�� ��&'�Y�;�N����ꦑmH)o�ѽ>�c�&�ûXB�� -�Ƭ��1@�P��1�Xw�N�FB94�@�1[:�xE�d������y� �T�1�r� �I]�@�mB�cVMY�=�Q��u\�*��b~� �2����T�����~_|8�s��(��P���Mҋ�p��j�3��L�a�ȫQ�(��Of�HȗvN;�PЀE �>�[D�VC=A�?0p�M��h#�eH W��{�5�)֯��W*F+��,� 7�%ժP�#x:��X��S�W+��w%TɕA ��M���G�k�:|��L�0_몽�W��ב61w�r;�J -�<��?��i�_�{����@��Op�!�f���ͣ��Τ�V�}iE'���lU�wӵ�E�|�S�qҶ�ѕ\(qW�P��A�����E�P�oP\��Ǭ����N�W��7�Dm��n� ���m��\�M��q����y���oŔ�N�0E�|��H�H��$*����8ΔX8˞��;}P�V�M�eh��fh5�~��m�r{'`T]�2��Tq��R���>�c���5��,r�0�S���>&cPq�`e��E��Y���TW���-��`�e -'k�[������[j6ʠ�d�Kť�J�p`�Z�ʹ��F�nV�rk+�V���wC�6�>�eZ���v)3(�\��+v4���Ôf�a����3o�W.ƛ�n��"�w/�5�$r���БB�h)��������?o�L`�;��Ƽhc���`�rMd�%ez�^^���z8xH�1����s;`eiA�l�����"��~Ǐ��vNE���T_�S�R�0 �� -�w�8.i�c�`c���XNi�9)):1�d�=�w�7������u��R@ބ���F=?�_�(ج/j�3� � xnT�s��*A���6�V�r��(�F�u�/�=jG -<�Q�K}��Z�� �z�܇�Т��ܿ�p������&�s��z�O+]�L=�$��5,!y%�6�l�� -�VH "i V���}|�&���+B6��J����H���C�ZT[/���\���+*�b�!O�Zg(s�$��� ��y�s�S.�;l]��{ѹ��x�G�b)��� ��p�% L�:��X󵈟�v)�T�@re%U�2�(^G�Q�@΀��$�Q�����l�>g��Ԡ�h�Z׆QA���$=��xx�w�Z��m������07��t�C�=��H�1dx�R�=A��PV��!y�BT�����6Ҡ��>�O֛臀�%��*��^�l���'�S�Q.���}��,�D��yP��y�E";�Y{Y�ʩRb�߬V��Z�q����M�!�s�� �:7��{����@ R"�� �ʞBԻ>Ff�Iv��Y�c�Z�\2Qeov��]F^�#�14��l1k|tJ��^�@@� ���౾�m����� ��@�6�-��F�,<�nEw���QA��eb2 3��DR�$����ٗ����P�F�/�k�J�]q9�9�R�z�:D�0� -T�����`�2��i�Yv��6�n��E����_~V6+�^R+�����0hL��ϸմP���>�v`�ea �v#�qR.����S�M��p�R�}i����>Pue��W#Yf/|P[�r�Q�t��g ������!�҇)�m]{;ۘL��M���vE�q*noG~�B�׳�_z�꽸�1��f�}��/��;-(����;��*<������Q<ӝ[�������-�4\/�跾uc!�2EW�J��o;�u��ĉ�đa�꛽�ș~�u�U�-x���u��k��&��,ʻX">�'[�wM���@C�/!?g��ȝ+ƴCR��]�Tȯ�o��(���Z��ꔙa���\�G�/���cX�K��}H�����~���{�y�&���V+�H��2)�FIw�Y�6��f������T�N�0 ��V�pC�$$\���4u��4�b�lO���ځq�����봼���L�U�zq��� ��J��.o�-/Jr���!W� �E��E�B� �Rp4 -ڍ��zL->*p�G�[�1��%n]Ç�*�,H��^�T���(�o ���(�5K�F*�jˉ΢��1��&K��T�Ԗ�Zr�N��;uo���7�1� d)H�߇��g�Mt �A��>+)��x�B����X�1e1Z��n��t���oO1�F[Ou�����Z�tl@���Ͻ�-/�M� ������� (�T��ʳ/�ߞ�����d�W�lC�<�U�7��Ygmݖi{I�.�� !�>��J�K⫛�DlHZ��g�1x~���R,ewek��o -L�bnH�T�c TW_��V QF�yVas��z7�/���k�gmW�S�#���]v�_&W��֣�s�8��9p�i��}�U@k��51� -(PC��L�q�|� �6�����:h��q��.w�Y��&ܢ�HC���@�3U�6g�|cI���b�y�lIX�eMn-�W&@�0IҨP��x'�� -�b���� �(Q�@�9�EJ��h�A�H?���cSygZw�غ�vj�����;�~N22c�f��2m�V�V�Hݨ� �ㅀ3�Z��5� f=�ty�q_?h��Lv%����p�"�/�J��Zq%��)w�L��o�4P��(�G��!��<���:�v��/ ��cf�<��o��v���o�<���.��À|��ho��`���`������`��?��pY.����/�S�N�0��'/Lİ!d�RT!1-[��q��±-�R5�Ӑ*K7��ݻ��l�:�*D�,'�� e�+��9y�>\�X-L[T�RA*�����S���7�t��*�1HUg 'O��n������.�V��q"!`E��O���1��9~�`��ƕ��Z�Z� ��AH�&&���Ã���Fcω� -����L��;��N���1.T�A{L��Z�� 66 -�D^E(Etf'0`��2 u�2�����mnS�e� �f3=��z���m��\���o����,?mQ�j�0 ��+��םʈ[(���6X�[�8�҈9�������f)�f�=��$��sg�1�wJ̋G茯�����>,����cl�A� .)�2�g)sU�6��\8d�����*����o�����m�AC�����$$��S����P�^�rPvȭ�Y��ǜW��GmX�F۔k&s+O��"K|Q"���!,9m'� � jo*���X�L��yI`|��}t ��l4$��!bB7�f�o2H �M"o��| y�QhBK�7J)���9����S�J1��+�\<��Md�BAA��B�f�n4� �li��w�k��z/�ef޼yo��u�aeSv��^��`��R��׻�+�Ѡp�lZhc�BV�&��Rr4�u�p= �dNF���^�G K���&��ك^�l��t�#tcs��!�/(q�g4(K5Vߨ����M���6��B�̉L��ه+�]鼣��-�딺���q3{���h>��K�^o 6q;��R�B�-z�ʪ�%m�6�E�R^�Zȟ3 ��;juj0�����fk�7�X�[C����_����~i'g��]���}�����Q(K-*��ϳU2�3PRH�K�O��K�U - qӵPR�����+I-JKLNUj�+�U�())����� -2 -R�+��RK􋋒��Jsrl�|����]�b����b|+�S+=a�()�%��� -!@14��v�����Q(K-*��ϳU2�3PRH�K�O��K�U - qӵPR�����+I-JKLNUj�+�U�())����� -2 -R�+��RK􋋒��Jsrl�|����]�b����b|3�S+=a�()�%��� -!@14��v�R�J1��+��m�&�i��Ѓl�J6;�;�MB2-�ߛ�E�A��"�D{������>�ep�g�B6��B~ߡ���k���5��V���#Uh��"��)��=��!���/9��g��R�n�0 ��Q��m��д�~$ƍK���-M�� :�ᗶ���p����q\��a{��az�X�*��W�/w���g�-A����(���?E��~�+w�Z�"�Y�3F�Wg7�y�^,����B$� pfe�'�K�0�;�0Vb� 4`)f���F( � �W��A���ں*3���2c�T���$x-ML�d�P��#*4H��~� v��JsDt -I�rZ����k@�=�m3��k ��y�˨�ɔ A��N}��#�,�9�bHq=�)��D�;��GI��!yOw�3T�$g��>�`� PCHg -co`ϫb�����o��*��g�SKk�0 ��W_vZ���HZ(����ۭPGI��Xji���� k�m��fY���@�t�Z�b$�].n�7�i_W���m~}'`:e�1�Ji�4�( s��2U�Є���YR�����x����l��Z`�����Z���績�� �d��ȍ/��ynUMTA��\T�R�&Vl�Pn ��X��\�MaMg�8eO��<ї^�돾�>�t4��b@�64ț��A87yEPɁ!����'�i��n&��e��m�W�I�������W������f��C���u� ��4t������pT�OmR�n�0 ������V�$E�>��[Y�c��$�t���ʏ1Б��wR�>�NH;����^Z�*m���ܿ�= -X�L[�PK� �r�0��4�U�_�sb�S -J@���Wg��ys����; Z�#K��l�|\�D��\,�� - ӭ7آe� �����U�A:lh�WM쒜��D�,��T��Z� b�Z��I�.��|Ʌ�J�{��JsE� -Q�r�4N}�e�@RA{�قr�4>�;�8iA� /�Dj��v@��@ �[i VPu!�� ��bW7��B5�{J�؛L�1�.؅(q�5�(�қK��'�s1�~G�1҈Hog�(?K:����S�j�0��+���R�B)��������ʒ�6!���vmZ�^��>gg)[�ZG ������VZ�J�u.޷O7���YF�1TJ#�s�0�)S6��/�in�e Z@u0&����q�[m^_v[j1�j�z�`U���������d-r�ʯ�ymu�- PE�4�R&�i�IO�"d�Ϲ���PG��2�Dw �(�.��C�0�@��I�]��oȇ`#p�@#���l�*�(�!rB=��'5�䱕��$29��h��Mx$�����6�{����3��>`L�U?�w��<����U���0Ew������C!!�Mč��4)�M[ ���N�'��[��j�E!� -��@��hxV��.���v��Da4ApT���ORf~��S�1h��i�«�ٝ����ھ{{B�a���ɿ� ��HV�R�j�0 ��+��I ]� ��V(��4f�m,��?�i�� -� v�������ώ��F2��.��N�ʸ]!֫��{��$7�1�J#� � -�0�)S��&T��9dIQ ����Ż�_�7����f�z\(ƕi�y`�T���;�t�W.M'y����c?���8*ͅ���� Vl�X ��XçB�}iMg�8eDG�*�K��G_v $M�h߆��;�>:n1D$t�p�:� ��e���T���6���s��O.��&�ݲ_rLo��I���_��3��B.��9�mR�r�0 ��+4����a��L�p��^dG�8�GV�._��lwف������ݾN $5r�݇�ʁ���{�����G���.f%1�@��۫�OmkUS�e��&��U��qN�w_9?�继�߿==�J1���9�8Q-Fw��L����&�='���/ #D_U0h�FL�UQc8��X��)�we�).{ƌ� �h����'?�riP �Z>x*��=g���V�=� - -���@< ���x�"����[�}�̝�q¸�aR�Dk؞�ʝ�}�4x8K�m��*�(-C��}H&��:��, ��� ��O���������9�*�X���R�=���{/�\@�~��7}�� -�0w�"��D7��)ݜ�-K�����%����7���xpwU��^��� ��,O �l�^��x�ԇ�QF�z���4,9ǫR�d\�vI�Ub bڼ�p4��fZv�o -��|�c�e7 ��~���O�4�t��ՔMN�0���b�= �J�RWH�8�fTglٓ���R!� * /��f�|v�|�7: ��~u�ٸ�x_��ק���MN,v� ����E�c�%��\�b�,�`�Z[��6Ul���� F�j�� @�� Wq1|U�-�:�� J��ŻG�2�&�2J�F -��6��(Z�eG�J�$C�|[ZG'���1���ʙ�:s�币�� �k����� 5��-��.�q۴! K�` ��0� S)���1�f��y6C�_��5�O �X]�p��Z�F[}�#p�~���st�|'��3A�-] @�� ώ���Օ�N�0D�����4pC(iHH\+���4�:�eoB���I( �D.P9L4�[�J���H��f}-�r���OW���UN�1TR!�6�f�wY����^�~m�����5�;Tm���1���+�>�b�\�@�=��q6�'L�'����vz���k2:� ��A*.D%MD�%�:Ɏ"�d��B��4tlOV�7�1?���*�S�I_`T�<���r���!��F�hnUz�FH����r��cP���0fc�}l�g�W�Zƿ����J�L�%�^6��9�ts�o���P�z)��K��4r�gh��������BV�g�N�����<;�a6��Q�N�0���ۉaC(N'x(s�8r±-�R�o��Ш�������w�����D)s��{�6t�?4�^��ͮf/�zc -�g��H|R��*� s�ITN�����J�#+!!x3R���q��fP�$C�g4ǐ��Y����7.B#l7x��-;���8��o��]7�"�ۺ`?W��q6dUǶ�MH���T"���ڳ�3���c��0Dr6w�[h�+ɾ������^����2�Ji��`c.jf� e�������YƠT�1�xF<���SjT���>q�b.�v���� �B0���� r���UDJs.*e" -����Zv� C<�·��Q4YeΈ�8Q�N���\�u �),Ю�ӌ�Cv���H��,mj �m���O�:G��.���3�B��-^V2p�ċ<(����)\�� iOK.r�kF� ���:��L�q#�m'�E�ʨ�[6��kq��*��|'[�g�2t5 -��O2�~‡�R1n�0 �� -�{�v+ -��i Kt�V�������8A�.]:�;,ׇމ=�l���x�^c�{o�ۇg�zUZO�Z�Q0�� -:��"%�"vфC�dND;8W��U�~��!�z̑���f�^ Q�H]0�v>����LIi��U.s!�"����6�Y:V���ф�ʝ'Fu�7A7.����:�H|�С�ӎ%�^$� 3�q�C+B󁚠�uap�hH^P����؜iE)��-�"�8�<ɀ<ٕ�~K9�s���-�M�nД�.�z��C#�0�r���X����CaU��9o�ƀ���6�����q��G��������VMo�0 ��W:w�v�8=t��0�;�L�Beɐ�4ٯe�n�%E�b�ͤ(>��I��j�X�P�ƻ\���U@N�Ҹu�~|��潂����8�P�&� .�fn�Y&֬���og�8�A+�:ks��bJ� ]i)܌�8l(���8��X4ĵ/����(9��Ps�*�����ܘh -c �r�v�5�,�Ў)��.�.�׷��u0-K5�}��� �&�C�*�6�.8�]+���qm�&_dCv$����=}`_ޓ�-iBƵof�keJ�G�/�]����&߀!�����L���O��-G��)��<����TĦ���i�8M�oɝ��gؠ5�^q@�� 7�j��k� �&�h4����`��C��j�\�����P�<�F��t r��0�a[��Y�3�FkI�̺f�*�0�����F��w�����3�"'I�P�9]��PP�C���=�S8�x���g���J^��=#�%�H��� ���z�L��������1yL*(vӄ�VV�s|�p���& -��Xw\�;/�H�.�$���A c� >�2��(�4 ���nR��V�ө�����Y���^n�l�M[�mPMO� �ﯘ�ݢ7cJ��1�Y= -ԝH�0��nm����=^�g_�R����#�h�����������Z���h��*���$�����ɧ�ҥ�^�0�!h|���ߤ�L�s=j\�����N^N���l�F�'�C0K1V4�&p5e1Bv�gb(�\5�y�4�h��X�k�Kv�~�p��l e�m��)�2�o��kK�_P��e.�k�Z�n����}�����>d��S�N�0��'�԰!� Bd�ew� ��ؖ}����Ҵ� - b��w���,����p�\\ �xX� �`�u����ݣ��j�\ ̭6�J%vD�IJ��i�l<.�,�h{�+�e�&� 7��B�K�:X���� �G��������C�E{^�o7�Pֆ*�j_�����\\q��J���n����#��h�~*��]"� &v��1 ���|?#�9 �e61��L�[l�tf/���i���xb�ʂf��b�¹7��a~��+�A�_�)9��=xFu����u��gf���=�k>J�f� UQ�R�0 ��+4�SÍa��ƕ -`; -58��R2���6�q%���j��>�H�}����]j}��������p<��JgA%D�x�/JU���ܦ�>�(.�B�x���2�*�MO�+]� ���@ӓ�S�t��!cY�q��3�k�ňw={냗Icl�WMX'��U�MΆ�~fx-��Ի��>�<�ɑ fw�J��MNPm�B2�2��|��uo�dS�55��U ���oK�S��J͞��l�� �����F��s�?Rۓ�Փ�N�0 ��{ -�w�!�nBB�0x�$uiF�D�;�oO֮�&�H �����I��c��@)��+�[�"�7��������{��f%�gJ�2e�� -[�� DQ���:מX�d�޹ -w��z�'�^u�c��pW�� -@v�m��MŬL��t� W�(� !�bky��j�,�^;{r�^���iuY^�]0��<(�d#����.���+(%ܫΔ -�P"�b�x��{R\ȨR��;��eF�K@�C��R�NKmrgxƩ�G`י���}�W�_�{�_���|`� ���et���MR,�u�͕Mo�0 �����P�)��e�6;�L�leI���_�N��[�!-�)�z�蕠�r�ZXbL�]��N�(@g|En^�����o -.�g99�Xk� .�aY&�$4���C�R4 -ꅵ��5ѯtiQ��-� ͅ���� o�_mss�[LIϥU���6\�Z�$ �5�هKJT�%�*,JK�>9mw��"PySZo���[�d"n0� �� rnڭ~���ȋ�7A&���v�<;�=��?r]�j<(\�媣l�� `�d��E���!ӼA]�&�f�>���ƃ�"�ڗOh�'&'C8���O3z&��A��y�g8��cԛS�����2�� >jx}9?���E\�_� z�O?��� ��38��[�ߒ>>��ѝ}Q`�NQ�|�wXn�n?fq���� U�A� �}�f�zkWo��>qU\P�����d������)� ��z ��0;Y ��p"�ݭuR8-�2�@2�VJ|i]I�-��T�E�d����c2߯�<#��9ǚ�������Oo�0���#�������z������q&���1��j�=���.eUU qJ������V7O��G�ɒ����ZzC���������'7���z��i� >ժg���U���S����nt�V�uw�B#���֫��S��Z-��u�+�j@�]�tJ84N�I��Zu�%�H�ٚ���&�Xgy�Ug�"��*2\�-�ƑyX�y��6�8��0�q�� "�1�:po��"�}����@C1{L��t��q@�`}G;о�CXq�Q��V�:cn)��L�I��x -b�BU�z����VAG�T�^���� |� �)b�Q‧��O�4��8��&F=�N{I�U��9�I����?�s��w6�Ԟ�B���YHb�,ɻi����ی�i��,�4�����8����cq�˔8㴧����N�A��lw�ᢶ��zK�"��Hm��C�f� �x���e ����t��V�n�0 ��+]vI��6I�����]{�)�e:֪H�$'�ߏ���m�E��D�I=>>R�\>-��:i��}f�Z�B�Ŕ��};���rv6�ڣ-�@ �����2Z��.��X�Ϝ �F�){����4��@�%����,.��gg�%.s��8���1p�{)����!��t2�J�͔��x��O -,y��L��l�J6#re�c\�������6�$l~�,.�#DX@О/Y��d�0�,B>�^4֢�dž�K��S������J:�����a�ĀL �J�*f -�Q�Z�I)���A�ǰ1 �a�> a� t��� �ut ;�7Q�+S$�xQ\]��;oC^����lr%[�I����wqwU�hO>'��$@�7����X���75�΍Q�3�u�-��w�)?tp�J�%�O�m��b|��&�0�;['�4�J$��[^[2�R8�ב�gTΒ��f�S���.��>�T �|,N'J����,���\th�V��+�DY� ���������T´��2�����f"��H��I�Fx�e��ޱ��E�P ��,��M�}�3^L�cg�=LS,K*�\aRk�lk�N�����M�����z���C\eU��U���sjӑ��bԍt��X��!��g֒"r���X���77oi��߻�1��I�t��d���9J4 Ǯ�~lNǙ��Ν�����{K����<��=�.��ilmܠ �u{�7ׇ�} 38�'��U?� �UMo�0 ��W�/no�`�إ@o�� �)�%:�&KE{Ϳ/�$��Rd���0e�������< H��P��Ź &Z֕�~s�ᣂ��Y�#5� ȅ�+�2�OE!�"����E@.2M�}�~�f���������;�I^+�-�-����vפsv�@יI�T�}�̚����eW;�xS���ލl\�~�1B ������ޖ�fC.��&vi��y� -��=Gi�:kP�UB�)o��c��oe�xL�4�6��n�� NS�)B�!l���϶貼�2�?ś�>d���,���-�u9y�ym��b���bϩg��� >����)��ՠ)�ĀX�B� -���K̉D�#v���Mj}�ܾ�)7-�#�zJ1?���=������[��hȞ����,�u��ibvH�+�迭�W�,����R�J�0���{�ћH� -~�(^I�m^��춴oj}��A�'3��٤<΃&��+�)�A�7���S���xu �XJ�S� �|����9�I�Q�؄��Ȓ�ю�)�p�s�����^>!��Ax= ���C�1 -.��A�r@�C�Mvȯ:���@�8i� -Z�Ak�f��%[[gyQ���5��ڝ���S�`N\�L���V� C��r���%�H:%���R^��r �k��0z�/eև�0�U�����>uQ;O�0��+�۩aC(n%�XY`C����ۜ����8M ������{/F��bPp��A�D�›���ǫ[�ݦq��hHT@� -�Rҝ��ڦ.�x�*2���+���GK#&����}J�X�н �'{ʩ�^�/�v!��J�yO�Թ0���E�+}.h�rt�i�]��A{7�u���L\�m4�G�s��a�f��>�4@�s�2peJ�A� ~�6�T��k�z�s���m��-�aD?PO>~L =1���o��+Q��) ��\��8-ڍ\�W_+���uQ�N�0��+��S� ��= qB�7$d;����f�)�߳iJ�8�cvfv�����TCNn�נ0�܆�3���pu j�Y5!1Rg=*�j`�\j]����uB֕<�n��@?���vl�`Kx���}*H���1􁭋*�k���yb��f�T�#�s{^�� �u��z6��X���@�RB .����2�&�!���1]��m�.f�1�S��P&�羜8@/sB()˥���������K�H�}vF����'�_�ʍ����Kof�����G[%�)������k�^2�|uQ�N�0��+��� ��= q�ܐ��lZS�6�&J��MS�Z��}���N�;/��Šࡺ���ƅ������G���v�0�ڢ`@( -D�IJ��tHM��$K� ��{�T~���ࠓ�|�}�0kb���KI� ��$�,7pۍu�t��y�� �6�����վ0!Fڵ\q�yG�����s]��oc&f�&Z�=.���b�K���.�4@���� hJ�NnY?cky�U'����X }.�k9��j�]�4���n6��_3�P7��>�����-�4��t�@�ΙJ�ʔ.��� ��o�*��R�N1 ���w��� Bb�ؐP�s{�\b���=9�UP$�ny���^�����”����9(�>���o�.A-���`YY��^�l��WZW4�]n�0�(���ڄ`���{xZ��L��V���������G�u�q�1p�_̔jz�.�_��o�DeK�^ �l ,V��������q�F�m�㒺�Mޅ���p, �BY�)������_P6%*��˥�^���j���o/k��z8 UC���x@st*>~&p���F���]��N�0 D����4pC����VBi�n�j��t�V�=�g�bO}�V�L�x�A�w�#�m�����Ա9��So���gg���uQU<�.̕G�99P��l`X�/�N6��uF7�mAy;`��u�7��T=��C�o��,�fI։��r.�,V��r�L-1�b �-Ӻy����.�.������:��E)�(�x���'�1y%K�?��yd�fk}ˮ�vG)S�m6m��N�0��}��މ�P�^W� m�Mc���J�7I+Z8�w���[oFga�)����n����m�����a�^�� ��4C��s��H|P��*� c�YTN��Y۠;�o�1v��h�_�����Oւ��q��ve�' ^��+�ڸhٱ��X.�$�R�S����O��*3#��J1V4�&ֆ�b$�C.��!� k��C [z`?6n#�d����.����,�5�4�{��S+/e��Z_H#_�8r`��Ձ($sa�5�m�������^��� �q��/�R1N�0��+���!�܉����Ʊ7x�c{�~��p-�����H�N��a�\(�V<4�0�h)����r�{p:��k�P���1�')+j�K6�M@�%��}+�㷋˨-ZAXRկĆ[�����]�ߔ��C�� -gm����Lf���_���yZ�)h�X�����[7� ��L�km0qHkƳ�)cv��8�z��ey����S${������w�1�G���˟Sr��U�A -�0D�9�,�.,n��x)�^���j~~"��4ԅ���f��GOf�LE�F���۲Q��'� �}�9��:!������BJ]U�Q�J��H�$psO���g ��p�SW���c7Y�!:#vvx�,���.�Q_�VIO�@��W����(@{Mڰ�H�"'E{���M3c-���YlO��6��y���m��[��!�AL8���,�S��S����Z��$T�$�p�ȯ ��� �����yo��T�A�rє^e!_����[�H􏨄��w -�Q�4p��ɲ�����<�4�4�v�=6OK�>�E��b��1Mh*���dE�d�D�W=#� FA� -I6�ğH�d��*YX -��poO��=8�H4%`(����T��#Q ���sC(�!�@.�1�ʇA1�YQ��`�N�w�"����C�z�r������/S�/+��l",� x��2����y�����=���A#�BK 0� ,c/�8Dq�B<��GX����H�����Z��n7�xBb��*���L��h71vQ�f�"�����pxppЮ �H(2�3d��� � -�-�s�E�� �\�D@��j;���~����z����4$��eG�!�$�+�y * >��5�YN�& $ �Y�"F��v@M߱������������tp|;������&9KI�n̜�hoI�����r[N���eR�&V�W6*��k�*���{��a.O�X"V�z��p�[3���$|N͞\�f���&��vM���?�O63j�vV�t�N����j3���6k�i}�zio -:�k��vL�>��&.V�H�T�T<�:,��"�{c7�sP.x�)}��e6g��c@s�.m E��s.<�F���Y��2�4<����U�G �z����iC]o4 ��i�P̎�� -����6,׬�m%#��/�~�ӗ�_�S�N�0��+�P�i%@\[ʳ��ʭ ��b)q,��P����ٴ�+>����Ύ77w>�`Pg*`L��w�y���dEN�H^i�U�W*����f�|-i�??~�`Ϙy $h�}��,뢊m���>��o��+Y>����H��ܖ� ؼ�t��}�ͬ��t�WƬ9�xDr���鴾��!�ˌ��bō�������A��vP���CpQU;��28qjƶ� ���p��d�5��I|����n�⡝ 4YUKQ��֩�4�T�vuq{R�y���6��W�k�R�Р�����,-��i���p�O �uY��j$��D�݆�@^'��\�,{���0�.�~@7��)�ߪa=�����\r��O��JI��� ��Z���s�Cθ��[6yO4�?PJ{��t��'S`)��ܢ�9��9g �;b>�:�����6���:�� S �4N��h��B��o�t��6Ǧ(5ܑw� �І���T����A�SA"��q� /�u� !�� -#h���%:=�Ҩ=!�ژy;l?fT+Nl$T;�� 8�v;wO_�t� F˲����8a�cC�Y6[��LH5a�|BKf�ؒ��!|彗�dޢ��b�)�|��0���2���XYx���AM�\M����" ��X8z��Nb��|��6fEQo�gl����İ����Ojnʊ���T�)�f�۸Qo� -�� @�PLdi�,Ӱ�-%��h��"�74ڍ olg�����; ��C����W�@}�xǐ�� �b�`LbJf�e��]���Q%���5ý����ȋ�=�f��ӡ������]o�x�,��}T�n�0 ��+x(;HW�,m��CQt�%A�Xt#Ԗ ���m��Q�;vZ�z|||���:T 1ͅ�ؒQ)��B���,�H�m%R��C�(�E���U.��mm{���c ��y��oJ��b��TS� k�[�SR���&Բ��P/���OeJ”P…��-3ZX�z˼��j:��0�{��Ђ��!�}�������JQ��H?�J��~� ���T��\��n���)��>S�w.���/���6R�UJ'��P��w4;_����k?� ������'4��f��툳�@��Jr��d�2ɇVp7�ث8ӿ��z��.�|�KS�\��^~3Yw�b�3��c���:]����p���n'W�?l�ɍ#N`���k�)��נP�[��� �L��O�M���u/pݹ��)ޯ�� �!:�� &#j���FJ|�Cng� ~�5�}I;���?�U�o�0~�8UL$������I[Um� *d� ��ؑ�����g'$Nh:i�(>����|y�>�s1H�DOi�z��9��[6p���Y�p��L*��OE��()Z(g}�~=m��i�Ru�w"������3M��J)��5ʈ8ly��%�t"����ɥ�h a�-jg�tr���` �Ҍ�p�:�Y�^^����D�Q㐜�¶,~Rm:���� ��|W2#�t� @u`ZR)�i@T��T ��%,�@{.����8QW76����鬀�J����LH���)��~��O&�as�����YӢ�&Q��ta���ac I�ED*J�$l�W�)3ymZ�8��,��si3ͫ�ۑ���i����k.d���7��dV#Uڛǭ�Ϙ�!�¨[��2�� -sY�� �[Ikc ������,�VcZZ��G�:6k�J"(kR�`&�za���\����mm� �%��¿��� ?��%���Ҵ�Y���[�Eh��r�����W7��L^���J�A�_G������=���]�̍=��������x��9�Ix;�h���u1���P/i�����v�*�!󵣉I}������,{οڎT��+�;�G�*�ѫ��s��V-�G�P���3'���_��IɎ� ��n��;n� �Cc�Fﶠ���i� �WQo�0~�WܤJ��� -KW��!A� �K��I.�j�D�Ӗ��ﳝB�ժ��>���w����#�"p�� G#��b!���Z�Ra$�8"6�ċ&����݄�r��ą����<�'��{:xhm>���%�8�1 -/t2'�A�lAC6�wX�[�D<h t�Lqm�]��b0�̩���:\��s�~b��C�Y>K������~�:�8�1Rv˴�/Y@wNP��U���K��0;�Eg ��Omp�2a��C&$�0�yj���3����NoEx�c>&w��;��#�Q$������Y�@%¤ 7��� u�@�J��^-%���f`�wi�3�q�q�P�"7�� P6���I����r8���ɯ�hثeMSO����%w�������������D��%8�<�� w�����u�ީ��;�03�c}��"K� Flq2e:��6���X���Z1F=���0|��(\Q��hc�R�a,"�� -O7d�#��Zko�>W�ՠNwfM;=�V�M"�_�64�b���Ux� ���i�2⟬��vL��W��[��q5!l4}a8P���Ȕ�Z�M!H�PU�(% ͢|y��d����~{$F�¨�Xٝ�� |s{��,���,T���MuL�j�hN �l�K���'[�C�_�~�BYBڹ��Q�5Q����� ؀��VT�����|�r4v#��)�{�)nW�ⵅ����t��&�����^j�Z -׽�c�W����|�9/9���>W�}S�n�0 ��,�� z�kR7��NE�vK�@��F�-�+ɿ���M�������A5�@b� ��%�R��O�6����H�m%R�� -cь��%~uv#�߾�iO�Wh���s)���� -bh&d-<��'�߄ZZx \�RG��TF} -B8�d��!�h�3���v���`�s,X��֤/��6�������;�?��y�7P�2������I�i�RȜN={�L�R3�K)��sJ3e�ot��I�4��mn��KS�\���Ѣ�B]��rO���^�%6�@MG4/��Z; o,vT��r� 9�kA�����M���@�;%9Fh2v�s�:n��$,��-�}���4j��Y 6�2�%��0i��@�G{�5P޿��:���$_����.9�_�ُ�G��.g[&�n�xe���� -[F�VMo1��+���F�BI���rh#�&9!!�w�z�+�K� ����b�W�Jͥ�ϛyo�c>|�D�%���3��Ќ��F��b ��q��"�0mPO�s!#�d�Mk����ݢ�P�]�]!!P\c��X"�$&���G����L����\f��a�ria *���_��p�d?5q��w�_}��,��@�_��/vq�$>h���1&k̤9�E,|�eU|B>id 0ȜhQCXW���^'*&� -�@�]��r��䡤�\q+R�Oa��m��:�ufœ_Ƶ�ΰ���R�j�L�q��|�IĪ��9}� l -�3�nm�U��W�h`��/"�sO��Rx�+������8����5Qp�)��83��eM�^����gn� �+J�����_�"N|��"z�v!m�"��& ����~�=�:��F�f��``�x��_���� q�s�=#0݆a!6)K)�Jٜ�V�Ӡp�}ϭr��̣}���� -l����f�)K鋗#�*��U�O�n� �SHaDI�me���2���4詘niׂ�U�Vgad�H�D�&�Mw��n�A��=sa���|�P�єn�^eZ-䞢��������ti���0 -'����ɣ[�V�U�j�9�� ׭\-�R�Y����i4���V;+�c�/�T]o�0}ϯ�H��T��-c ���&O�Ty�Mc)�}C�m�;vZ'�*x��t}|}��y{� -y�4���䴦_ -�śl�$5��(�n -uôA����R������O�?c�P���u#p���F�+,1c�V3I�-HV�� -k2�&� إtC� �������x� ���l��!�g��f�Q##4����ڃ�j0=��b�U�ѽ�F=9�� ���+hr�F��&?��ڇRr�ۚ;V�^sˍt�)�����t+o,C^@���L���:�B���X-6[����;�%�����d�߿�Ќ�i�Vמ�;OAZ���K�0��V -[#ԹM]vԫHX�<������g&p��]�R�F��К���M^�>R/�咼�|�C?Z[qN�����pv+�H##nǼ�����!}���Ay��q����j���K����O�����Ǫ5���ip��t0� E_�.H����c��Έ�� /���n��z��6H��,���19����t0%g��^�s��W�n�F}�WL�� �i�(WM�A؊`��(���Z�7�.������^DR�#�~($p/�s�̞��.�fb1��T�j���P������(3 ,�ق �b�!�Q��fs�9�9�F -ٜ��!v��nT��rLJX -�խ�I�2�O�qa����N�3z�4� -�,������8��c��9=��Mn�sˋ]�3&X &jsʗo:���o�p����#w�D��YHa50����#v�Yq��w`�'�q{�6Ay�<��iap6g��Jm���W%�Gb�����V_l���˵F����G����Q���"�7j���Q�u|x{E�W�V[!�A� 6+�7��/�����I�!�KkU.��[,�t������f6Q�C}�PlH`��1dҳ~�#�7��Em�����HA���88x�����1j+�$���ez��k��F㽞�$IYP.6�=b���Q�z������je=��ш����'�Edc�'.̘�q��+'y�������������̿^/n?\Φ�gA](��/N<����?]R�?|�@�/ޜw,�����'aH:��b'�R��$�X����̊��H�-��KW�p�r�94Q﫪XU܆ͅ��Q�����{�C�SS�eh ��Y-P�$h�sՑ#����Wj�yS�v։�W�i+�` ^�Q$�#�.We�?�� �[����I�(��V����-���_����>� wܢ���k}�n�6`DM��]���aT�1,f �@�*Jwa�I�I���K���`�ВYJ�]�+g����#��B*w\��QƻIa���f�G-�r�i�L1iM�Lw�'|����i3^�{�WCК�U�s�w��>��R)^��a�mz��ĭ�*f:��O�s�q�/�TM��0��+F+*�v�+����C�Z�� Pd� ��8�=�B[����@Bh�P��x޼y�������Th� iR@����? ���"DxL�G� ���R�j����?��{:� ���F�׿(#�����R I��PE�8N�d����9aH��Қu7���9�6W��H��3�$�NA���7��^� N -�"Ա�c[u��>t:��`�X!ծ� �#��lꄻ�J�Meq�B�/A�+�(C��P>TJ�5�D�ۅ�0o+r�j�SĽ &�md������߫����>,��(E]�TjU1s �H�5�*V͋s qZʨQ�U -.cyy��E���.m6��\g"���:�N�gj����<���� -���$�v��Ѝ�V+�q�=:�����8�����pws� � �{��ɡD�Ϡ���� ��C,�$���S���t�e�1L���<7J^O z]7�e����!k�K����Ȝ�ߪ3�ݻI����s�N�#R䄦 uw�*|n�ԛ�j�2%�G��?Ύ�����&מ�@�����2P -�����|�N.>�N��ay& -��t~l��@�w��4��n�g�mr:=�O/��e� -�>��(I٩hJ�i�f����&�S<>n����MsZ��J�9}$CT�_:�M^^�p�4'or�y��������,�K���W�+�C,�S�Л�!�*�A7l�x�6����m�+b��Y"����˗�r%�܏��^�Q?� �?`��>�wI|�-f+ܪ��5Գ\Cβ��ɬu�F�#l��@!�����KY�e�N}�1�����rl)�@��ڽ�좚��/��1!�ZEG`u�J��|�N@ �� -�S���]^y� �#��Z��q3�1�rD.�,�r݌� 9���T��z�qp����0n�Z�]^�$C��E=�VX�8$�|��$8>��4����,m�IcH����F�l��:�5W\0'N�U����'ҷ��@eqw�RÔ�R[{��@� -�aBN9HꝼW�m���g�i���q��4�G�G�s���Yvd%�@�����v-������Eg�݈k�^�V�`ė)��Z�H(�V�',��h�_RX����p��͐���.�1/�ۢ�M�]�a�t8�e�AX����ٓ �C9P3��&e2�����B��foS���Q� ���K��){���Z��3���Wx�L��ml�[V�K��� 8)e=�ɸZ��/�����V�C�u�i�y��p���1W_A~�5�E���[a�rg��Bl��0�+��c���BO*�Hiڶ��� -w��սg�Sv�����}[m ��V��}PS��� ���'c�_��]�##ϗx��F��@�W�7� !�S-sq9i*\W�W��J��D����;����ƑXgE��v�����ޓ����2�tH�єQ��z���7C�B;{T� G�?**����F��Vz�H�w�D������o�4�r�X���7��`����jTYO-�u]��v'��}�王'��m�m�]~��ya��Y辧\�Sj hr��`� Z������^��JO���?�ZKs�6��W 3�Pv�8�*u��N���j�x�C��@$d��kȎZ�ww� ��8��b>��~�v���l��@���X�<�յ�dB��8|9%<2�`V��K��۵���4�_3����"� -��h�<��Q���gZ|�98��I� -�<>:�#v� -%� J ]���l%�L䒁l�„��`a�E"g� -ӄ�K�X�QÞ��4��f�bA� _E�g���%�G�p<<2�~� -~��F��\�^Gl��1���#��|����V�J�Y���I�R!��B���� ��_}�����o����I�g�2����y�=���H\ABJ0 -�`w9�2 O���j��1H@K.J�����0Aʫ�h�N��r:m���~�����ݪ cۡ a�Ŀ�ݞX� -���B2urC�Pl ��m5b�KI���a�pR�� ��1������^[9�Fvb��K桼ֱ1�{��p�;R�1��L�a-�T��# <E�ک��U9��"�#ZO'P�п'l~��H��xa�-k|�-��sٟ �U��Ո;� "�c�oE`��HU(��!�H)`u"�n�N*F!�UF�!�����4����~–����l��Va$�^������uI���/F�۵ ��Z�뇏tJ+��y�H���B~������������ �`�t��M�b �D�EpXOd�B�џ�z^h2�v2��ɿ��B�~y�G dWS|�$�b �D���g񭅶Ƥ>?�b'''̛Ͻ6�����;حn�XC�u��\/`NE_�V��?'+06Ke1�+7�h|��?E)��]�����՗�F�J��%��V��B���Z>8ة[ƫ ��Z(�UFyҬ�3ÍU �)llI�9�B�>nx�V��yU�1�E�Y���_}1�&@FY�f@Ko>^BđH�S�#/,챎8���Vx|�F0?�9U�p�L���� З9�)x����g@�-��a�'��&@�F����K��Ĩs���J��˪�,F���H�p��=~̚_.�͙s���sݏw[mx3�6�u9��L�����0�Vc�#�����~�`(���{�ѡ��"�6 -��;���:�@��kB�{fI]҂9a�k�<��|�A�𹶈�:�x��,��� �oz�D^��M����R��K� ��K��UJ/LX� �k�j\ M��h෈��*��2�;��c���$v�CwX(��R|\�{x�kF�Z�†��GY� -WNu�u�� Nm<� �[�m�GR7r�'p���7�ͅ8�EN��$fʨ)б�9�,q� M�&⎭_Q&�hp�Ʒ�dK6LC�%��<%�w��&὘�J��} .iu[�s�<�;! ����P�9@�sȜT��WM&-{15z����1��xP�&8��C�� �1�@�ѿ�������A t�$x Wz>HiQ��j�H�wh5\ٮ��:&ڿrjHĥ�Ul����O�4�_���R��ѯ��' �f?>Fy�Cڅ���=ƈE �臯�@��#�k(��!�#���S��'O*��q�͑j���Le @��p]����/���]M�U�i"���9 ��6��&���0�xߎ�e�ϵ6"�c�s�\CC�{om����� ��ñ�ū������y�wl!����1���XV�T+K�[�,M{���ҡ��1ۘSˬYX��*i����\��I�bM�� :�2`+� -�!�{��8 ��[$=1I�������1�|�+a=�R��嫾��V���P�C���0��q��I� ���RD����;\O����Te��TT�.�V�d[=����Xmo�6��_�A-�N�|M�f[��"H C��|��H�JRq�,�}GR�(��ݺ3D"O���sǓ^�WYE��T@ �`���err� � +��Ȫ *$\Ei)��(�+Š*}%I��,�Fs���0x�U�T�(��ꬿ�3k�)�ᢔk몼n6��q��$B�8�P'� %I�/�p�H8�o+*hAt����DV?�Y O�z�Ŝ�9�` -�$9�t ��eܨ;�� -�c3���� Q��) ��Լ�h�/H����Ps݀G��g�9� �g9KHZ�D����4qh����0����E�__��7��<�L����W&9�N�fn͵��$ҕ�/kr���뜕|Z��O�������O��wp]�Ŀ�t��s �U!9ufZ ��{����w[�:���y�E����m.��b����B��Wf�\nȳ��NC���_��<�+{/G:��]�Ox?���ʨ�j�����J#�+��e+8$ P��PE�W��ܒ$�URWF�"��� KM��6�k�i\����ݖ}/nz�n탏���b���v�V��{��9P�� =��Ӝ� �D�؛�*_:�m���#���I la{�l2����&��4 +b� ^:M��1ya��i���_���}n���Ψf� ����d#cE��D�8ҙ�F�9�<��l%�=U��vr��p�������C��s�,5������p�1>~��M+��9M241s��^���OKM<;��Z���:�K}�c�'^np����[=]O��+��5=ۈ�haT�l�%i^��[����5v�S�ϱi��fV�����>������tc˃������kj��5�xBP|�cX�eyg_�J�0ʗ �FW�K�K"�f��b�6���ʄ��Qs;��z��Gg}�?t�1r����56� -h��������"��]�Z_�Y�kެ;&��V��Q�i���/�(PHIM�I,J�(.)�L.�/�,H-�5Դ���K�M-.HLNU�(H,*N-�q���M�+�u+��'+�V���+�`(�����/�(���K�M-.HLNU�(H,*N-���J�I,.Vp��+.q�((r-K�)M,���s�HN-1R+JR�R�b�B\յ\�ZYo7~ׯ�Ց��s*���r�ӗ�5�ݑD�Z.H�d�N{g���=�"o��h���7��YZ��x�z_��y�z_sm@���%�r�Q�pw�㓚7���G��� � �`X�"cyd�Z0F������J��� o�\�*���� �a!,D!�������3�]�`��9��&d[aW*�l�h�M��{�`��d^b���L-�%�.�3B��y��F������� "ڨ�^�[��q��m�®8>��)ĝG��B?�Йå�� -����μ@rc�wt�΢ub,F��l���� JJ��U��\5/������4�w�Roȇ��vY$!*�;�����!���)�h���� d��8�J�!�S��4���2����zĘ_=��]��LX� -r^0U%P���C��uY���B,�IE�n�$�X��"��rw�R�H�$�X ���)����� �b����â DZ��`,]���!��Ю�%�p������/�>�{ä¬:/�=��P)��������P'��`��$�Z��{�����+�2G�x���y��U{Sa�C�����7���)6��H� U���ה>J�Za�D��.u���s��.%��#�sM ��I��E�Y�8 n2�"@V�W:��.r: � �Z�p�:k���է:�Չ֬��,t(�OFW��J�(��F쭡��UbQd���p>[������[]p��a *n����܍��M�(�����nS��8�$� -�و]i�������0�V���9d�J������Z�J����dF%�[�Ǡ�)���"M<�?Y���Z�F�� ��v�fr��-��*�q���G�)t�mY���y�=���6a�H{F��=ɛwϨ�cVi��4��.1�nco74u�7�BG�8�X � MCo�՟rP5�Y�X��&:bk��� �'��}�P����b���#��j�� H�� �|i�& ݎ�ɽC�Owe=(�%�3����/��8v5�ŝ롒ҵ?�� -R��a��80�;�ς��\�� ����a�Qap��9��#[1؃�>�1%<�^[r�Ӆ�����z��0�,L��i�ď�� V��\�IC�!Y����i8�{5� ��V�(�=(��!�E�ihpES�D=_�/��zH}��2��)�� �?���Ջ;�zo�Nf�z(��44 M���U�>��c�������G�$��i�7��?d���t�s�]�vܻ�+��� w?�N���K5�1�q�3TLd���nŵLL�/j3���fx�� �F]��O�k�$�裲 �N��.a��@���}uy)�(~��j&�n+�f]��i���L��ir��V����S�b�N�Ӽ�B��3cPt�������x������|[D�������4e� -��i���~��7�4.f�ָ�֧Oucc��8�}��*)���\����?v|z8�1�8�4jz*\M���t�~q�K��4����۴,k�xZU�)א]�lzN�i��6�=;.�D��=A[�X���y�{k�O����H(�Q��}w{�^�Q}��p���M�%Uل�w�������|��-�X�(�d���D�J8:;��� j�\���r� �JA�o�Q��xz��]�5qu�q8�5Ľ/d� -�BK��f��=�Ag�n��g\��ş�!%<�P�� ����%ƚ����$�Vs\z����y��l��������r0<,Rɞ�>ْ;9d����'�z��z���� �O�S�d����>>v`��uyف�e�u�� �x�����X�i����bͺ���5�k�o��9��c�N^�� ҎE㉥t��J{����Z�P��+���y�� ��Q��뤫�N��뤫�N���E��/��Q����7�dҁ5�������g�vu�k��IWML�kb2��� -�cU&��Aq���&��;��xC��s��.�����~�R5tw>��ڂN�����ŐCr� ��������-� �������8�bt�]^}����X�n�6}�WLX�yݬ�A�.�A�bv���(�X�H*N�Ϳwxх��r[��~�-q�s� ό��2���8#�FJK�G��S�<��O&�l��IL�v����ī�Z)��RH�Ϛ�D�׻�k����1�5|�}�ɥ�4�4����~�J�5=o�"�^�*to��o8�+I �¡r��9K__����s�J��قA�K��~�B0��eA�$/2������O� =����P�CDž�4)=����@i"5d�S��9Y,��\��'3����Xe,������c,8",by�%�y��<���bs�7L-.j^pEiy^�b)DL=ڄD w�����`À�r�+� �o�ejS�ff -ˋ�����6�"O0kS4s+_;�� � -���-���$Յ�e��L?+k���F3�P��a{��lK���&I���;�2���Z�}�ߗ���C�פ4�z��KX��Ŋ�$�p�k���:��H �JCjl�0��!|���T��EemF��q�/�h�g󴾉H�:�=��_bS���=��_o!e�C�S�A����x�P�^�����$c�������$/�)/��7��e�,�.�;{B��ڈU6o�� �јXd��O*䖘�۹�A��*4&+_=�� �ҭP���/sG+����ӍR��ٛ���l$�0+Ƥ� L!�P�}hӇ��8V ��V�L���l�" -�S8�.87=�ʎ&{�X�����8������C��7F5<����E�q�����V��3��H�Nw�9��3��1.4��D{ -i:k�:*���܆��[H툩�.���������1g��ڍ�U�]㈳"1��$�Q"��%|�13#�czS�>�����"s�� u{��>�<�b7^G��^�?c ����*��:���G -R)�p�|8J ���V��F�y�sK81�ۗ�L:�F�yx10�l��J�'j�6q�HST{wt���P���`��m�J�U)�g0��2��I�f��Fg��1�{��dGRSRx�0r�H�����dOX�u�T'��g���_l�&-�}�4|�O$c�}�`�[ fX��%�����+7w�\� ��Sb���e���ڮ|�iEUʔ�70��?���G���T$�jn��6��-�������?i�Z��xm�w�yq[O�݇� c�3����;�b�t~��)�h�7.v��t�ۍ� +�S8鏣���u�:� M��j!E{��)6CȒ: I��r���ջ�0��|Y��{tf���<�+���P��F+�U���K������٘d'�bq �h�R�4&���� ���&?R�͠��0,��l;� �*=�,1]0��J3�}cj�����\:sz܄����i-���� �Z4XE"}�f���<�o��K��4F��\oؘŸ��m��c~��Ak1���S�.ԥ�Z[AJ{�қ��h�f��d���ߛM�V�m{�y��7�>�G�{����E�>����s����S{��/o�Ү��P�}�oh�L��ۼ�� �E�=b�� �|�f��4 Ex��y�n�.�a�w�v�1�L$�T�!�\&�Q]ڬ=�~�1�mIk]t���� ,�>�_��7[.i�W�}�{�m��Ry�cNv:ͭr0�jr��)odW�=d/᱉3�VMW�[��Q:���x|n�{���(ú��f,��z�Hƕk�M2� ���g~��<���'�L&K���x9�3�seӨ�m���\Q&��"�79�������b�Woi�E������mhU�ٿl&���4��rɮ5_�x�9W������t/?/S��D� ����O_n��o��[�(��&��M΢D�1���>8,4�܅t���⨅f�����>�9,��2)����~�I\���P��#�͑�C&����`�9ܷ�{>c�J�����V,��bj�L�Ě Ɂ����x�4 M��p5#�`��j�צ��-/(�+�S/h�����H@��O���E���#O�u��մ��!L��IH'u�y�Ql�$�Q�Be�x9��w����s㩩+�6s����<�ߺu���� u�# 0]�� 0�1�Z+�YSȽ�����k��a�ɢ,Y�G 6鲳Y>�J9p��ӈ���ׯ��YBP��0Ryn����-����֓���Er,�������CƆp\��RW�T��ֿ�U�n�@��s@"�54iP�C/Q�D�!Dh1cX��uw�����fmlC������7o��_�ۂ֜d������ ����Ѭ�3*g_���q[<*��-~��Ψ ۥ�n<�5�.�-~����U���y�=��@�I׌فV -[�Ժ\�I�$;m6��xD0[��2�r[zB޳�F�* # -h�s !�2 �c�i�����\�E���}e�eX�F,�"�o6xI=���LvGX�S��:RnS�lh�_(�y�ą��U�ʬ�f��}���Θx�Fb��-7[�>D6�ѥ�A�j��:DH~]9� N4��A���́��:�4����AL^>ɲ��h���D���*���Ze���p���J��������{�jO�;�(6��m^^i�XN�*� �ᡟ�3e���g����ZEBn��3�Z��!�yx!�y���9�c���Dؠ.r>�<��)��.�����ZZ�D���˦�� ?�:T~�[�Mbt}q��f)�l�T� _�h�bD�M(t*��tut�Y�����NDA@�X��V�i:�V��Ν�c -��퓭�=�z�:��)Űa{���Z�fݯ��ٜ�ŕV��+*��5�$��iJ�����84��/��5�P��U��6���*�R�G[v��g*�q������*��Sb�I�v��|���q�1�����TE^\�)���ȵ��������t��y�Ҷ�c�QUWs���.t��ޯ;���p�7�65�F�J����1�#���.�� � �����O�J��A�TW�S����1ӠvB^[�+fEq����b�D�2a�c��˓X"�YD���?߂�0��m裀$E�d���1��` ��R�a�p���=��T�B�k�t(5oB����m�IB�T�h[���.���2�sv� pB��l�rƢHm�d�U��2��I�-%�U]������^�1�$Y�C�y_W�Yu"2t?��)��)y�Ӣ���~Rޑ+� /t Քӏ&Tk*�JU,� mH��i-���t ��c:����rh$l_)��,~�D�.^^ +��[�����~�B��`U�C��_H��t�����h2><���%S�?:?=�M�o�M�^S��i3�^�s ��*�'Q��}�U���8EX�鷨�/�+�\zd�t���Ј�,�oX��\P��Υz�[#Z��|u�P���GG �+�5���t�*��a(�ih�^�YK�n�c��ۃ�.�'�D�$�]�9c��:�s2��J�ce� xr1���裇���b��$�CW���W����T7�1~.�od��g��eŋQ�t���7�r!!N��s��N+�4�R� &ƿ3���2!p5-tM"*�ӊ�z�Rbg]&t���/��(I ���z���F��Y��ݿ����i�P��B?O��Ӡ^_�;�>I���A'�\���c�H�y�y_0=A���¬Ow�&���r����z���ӌ������i��k'��M����jng�,PZ�k�:ɫ�m�k��y��P3�����T�I��Y,��-%�� r/��n�ԁh���ʄ�*w,L���O��(*��4^���ݦ�d�K�z6~�Z��o�!�޿��ȧ�6�r)���J :$,�� �����m��� -�A�!4ص�X"h��~�İ_`ҷ}Tya�yʌG&g�*�^�N栨�\��L�'�:���e(f�1�����t��#Z�K(f����~�T�q4{�M��g�����p<�&��'g���g���ώ;��u$wJ�֭y���E�n�����;��u��[�Z�4�h2��l��f%%ˌc��}o�n�.�m��àȭ>(�~陻���=ۜ�_lN�e�Q+�6�f���C�5��ڍ�v�w�9�y��W�����z�E��ZP�`���c�mլ.�j}���o��*S��~%U�^��d�2��J��u�����S}!GU�+I�D�>hD1'����{6��|%\ �_���L̨�ͲH��vի�T�p$dp�L`��q�iul�9��.%&�νV��U)p6���ޮl���^�2��P-��A�3<[�T�T�Aj��bԓ�\����(�RR���sֽ�Y8�y�(�c���;�׫����y�ۡ{`Z0�F�`)9���F?�kU�ᨸ�j+�֥�A;�܊�S*EU���3R������D�<���e����������O�Tv�n���R���h� -7g�'A!�s`�G��ҵ����pإ��N3u�d7٭�����[�3�ewG�<_��$�}Y�� �X�6����z���x�j���\Ԣo&���"�7W���X��?�j��O�D�l<{�}+G��ۦx���ks�6�{~�sC�V�7w�:��(��~���sc�4 Y�P�� ������� A����4m,���b��]��f1[�H� �EOy��i!������g)� ��`���%ϥ���RZ�o��Q�I��,)٩�,�g�~��ȳB����f�Eb����[��Y��$N���q".=��x8J2)F����s�{��E @�g��\�3`BtX�&e!��[[��m��\px��bŎ��<�i�s>g<����E���]���;*�\�E�IJ�)f�M�S[Y,�,�,����8��n�ƟM�@�"c<��"<��nM�0ౌ'�XނB�ǂ0���2�@��<���iƤ�^��"��gQJs�/����$ٴLCB>�p�E^�EϹ�vs�gJWH�^�9_�i��a!�'L) �^����ڷY�b��g��� IG��^_K�� $�F�b�R����qz�z( ���D��M�+���`I�}*j1�E �������4�/�T���OS���aQ�D������H�� HƟē�U��us;�z|2^_  >^\�ǣß�������|D��]��w����p �m� ���D�Q3��Z�RR.���� T��7 b #b�:��m����Z���5�Y�nw�=����u���!���LC�� -�זA �� �'<��T���xƓ��|�/�k��SÄȄ��Z��T�s���O����u�M��h�4���#1)�w��T��mtI�!�0ClY��m�<��O�n �;ң[o/W����)�^�)O�+ߡ�*�\)�<�Yo˦%��<~�\뗢�$WGG3�g�W<��MR>� &|���ejgӈ��یQ�>�?$|���R�p�[���f3����Z� &��՟�.L-����,{M�<�0�G�A�B�;����-�=)Z� �*�M�,�o�q���A���% K0��B0�R�{0 S`?�/6y*� -�L �c!�P���L� ����P�B'phf�M� H���Ŵ�Z�/KhĆm|/7X�����}�Q���1�߂�_�̆|5�W��^E�� T��:�8E q{� R��i���0�CZ��ߥ�v7�'�a��+����X��%��8EnZ�y�&Y�4�����`�9RHz*+��=�`I�����ޑ���,)��� -n�W��9�p��+��v쇾R�[���/`5��`�e��5Y ��R�-Ķ����r㨤���ǿq����*��Oǀ�Bc�y؊�D�����^]�\�}�p��N�5k����>�<��H��B��09?���N^VPU3�p��XsA��Z�]Y!��\)�k;үnq�qJ�Q��p���i����NE��~e�c�MFE�g%H[A�2+3zs���&�7N�v�.S��%�߭�LP���,i�S��S�y��i�Z��뺁귱"���5���o*�(§����H���uV��ϋ�s=,��#ԯP��Q���y����U�/S�5���W�_�2b�]�p=�������Y�];Qy/ -�T� }� -��Eh ��D8=/���.T��m7�͸��0�Ta�ư[�U�(�UCk�% �>]�Upϡ��;���C���۹��n���K4�5���+�t�X�B�D2�SV~��*ۛ��Q�T����R.��f�(�!�# -�)�+F�R-S�U5PuvK���O������Ƃ0-����x�{a�lc�Z���b:���T��+�;��ȐV�]�j�lM��Y+�65�7TL� �Ap���B�Xf��5�u1� �GTU T�e�νj�կ�<�I��,��H<�*#�R�����^Y��U� 0��# ���[�1��Yq�9��s�կ����O����a������mtO�W�U��������኷���̝ߋ^nm[?�^��2�mv��v�����Bu�LM_qcٮ��9�n�O\��Ԅ|;���&Z�]5=�v"����M�T�jb�kd�HQ����k�I�i'��L���/,���d�풗?�n6!�����B.p@�`#�$������j�,"��Q �q����MU���&�[���,g\��/L�N/��8=���fC�ͭj���ŪBԌ���E�sr~zr>S�x=�Z�0��T��5Y ��k�aW��/������0#͈3��߄$������$ -�C��"��,"lrMք�\��#�� ~þ�Ҝ�|@v��2�0���5'��IAd��5?'�#���;�>�j|9�|�s�k�_��Q���� .�G�]�t=�}���=<�_��<��<� HNOO�Q;��1���As��8��c���y������d=�O�H��Gc�59���į5�'��u0��S���`l��M��Ss��̶K#b�y�΀K8���?m [G�T0g������z�p1������5Xr��}�k��t���Z�5q�St��� ��q#������a���n��,�_�L���H�}Ŝ�z�y �xj+lY���A(\�Y�Ou�<�1�f3i��Č�MJ&��$� - Ia���,!hi��VG�."2��/X.#�:_Ш{&�~��8"�����)衣�Q2)/Qq�V�q�8�7�����Q]8���Ã��| ?���a�Vֲ�G稩W�H����!�%nS�k��`��؋��V��&b� �C���6x�[���4H[�S��ϑ1�c�gco��@�yā��U�#� 5)�v��Pk)��宺�����J< -n��:�2q؈{:��ģ,pH}SN��ߝ�r����>��yI����<|�^�%���~_�RҬfJ���-E6�$�T���6:I�W_�ҭ��`b�0z�����G��^^�����_�G� �%ZR��g���g��3!!� ;/�t -Vht������mh��9d���H�2�.�Ω�U����]�A)�n ]�F� Q�x���K��lо�u�|.�"FO%��h�0F"[�+�D�VIylӒ4m� �t�&+�,J2�Jrd��li�!�|��9hKa��[�R�M![Eߓe ����c��k"���#~N�#d �45C��a�\:��)�n; �NQ�MD���Fo��i�ʂ詅q��AQ���7"o��|hI�ht$���I �t���N)�sv���mLL�KCI��v�(S������1x��X*����M�����_F�*���I�&��H���~U�u�]�_$"�땻V��{4�r�.��||܄$}&k�2�y�)�i��g���.yA"Kf� ��Ph����Nb��Dn � -��dG'Q��v��2㲢�_J��UA�Td֚WyGQ�%$����r��/z}>�D�Q�t�ds%�|�߅:uO�g`Y�WQF��jA=��@Ag���t�T�-Z;�4���e�V6Q��4,U�֩���H�Z�"�l�����5�e8����2�m* v���q�joΤ�hU�&g+�7D �˒9 ���淊�tke��$W,t��"ݗn.FYa�O%�m��&� -���`�G[ԉ�*T����������ѹ�c3Cw9�geWu�`V ;��5� !�k��-��<��%yғ>-Z��ơv{[��wV�i��)��Kh��,�b� -ɜXI�����z ��=NC/U�x���g��:<!%x�h��b&���e/ -�΋�6��]Y)�9%{'��J��H�U�ι�R�QBk��oo��i��,C���]B�*f}�.�X?#u�~��b�1Nޫ��qj�����ׁ��_����F�����9W#��qC�)�$��"�j�˛���L��'2�Vh�K�=b[�m;�I�!��~_t>�P��xc���z�� �'�5y�>��|�^�u`���\�|O�#���(ݜI�r_*�ޖ�s���t�C���ܺ� -j��}+��}����`�^�ZT������rGN�h�9K�}���Z�S�H篘Pdm�lؼ�:��+�mU�p��l���4:�9�k�U)X"XJN�{ ��f�ٜ|�kR.Ȣ�VT��H�-����or3�VV�cV������j�ɘ���ݠ4ʛ�Bb���.$���M��e�F﫪��E�4g� Y�o�0f-n��� �T� -PVZ�$����Y�ۚVt�n�ȵ����S"JR�uY "�r-M+�yȳ�,6E"�� ���7`s��~�>�������蝣��?..| }#(hgfO ��C߂xBs���Lp"�`'� -�!�k#!��0��gJ�c^>��뛝f���,�K�Zr�2T"V����-s��/Z������ g���\���~������(�;�^}�Nn�4=d������rz믝���2M -�o�H�B�u��_����Ʉ#^��#]�O� DY,�-π��y�]���!����5�UEwD�R����w�3 ��P���˕��i*���3 �4�jK��Z [ d bggd��J /�3LQg�D�9l˪ -vb9����^Q�����5�[�`+ -,��P�_7kE�ց�����> 6e^>��߈Xr�}3��w�e&1����@w\x#���ɏ�XLر�� ����W�����IJ��2٨9M�ãw*I"�#˪����� � ��&�(J!q����Q��K� �RŒ�!��;���34�v��DE����A(ϊ���� ~*&6U�с�1����Ñ�B��'$񋫀3��V=V�"�/U���৴A')=2+�ٱ�w�SX� 5��Vsq�+—J����[QQ g�e������^�}�U�l@� #��H����� J�}f4�q��k>�x;-�^m4�>�dX��z\��@V�t��Oj�ѻ��(a�%� hE���)�cz�p����n9��ݽ�=I`�b ��֙����T���0�Z�o2s�G/J���"��{)R�J��� �]�㺽Xh�Ct��vV��S�~ȯ'��Ħ��WU bxV²M![Nk�U�|u.;qVA��N �`�r#x�Jz�V_�)�. 7͎:��Aϧ�hC�j�}�rQ�Xs�b���h��t(ls� `���U6��qh���]�" 3�����ϡ�Hh]AՏ5ٸ�{RA����+� ->��d~(=7���< �Jb[���]&��NØlB��#l�*���.�Q�!}v�x:$;��_u7/Ѯ`�bU�VH��#_�S&�>$ -õM�p�BDY <�NC����yl6'���Vl� ��lQ�)"���Ȑ�K�>��l�u�d��yi1 -�]|W���"��|1$`(�=Ȍ��Xm�l����� )`�Z?�����g�\U���j��Mj̐���my�%�I��,� ����?�Ĵ���PFdIg."�k���݈�W��w�TW��,��º��.k�5}+۵e@8�G�"aS�ަb�4#�%�L � -eb��!4���h�hIx�bMG�7z��9v�)B+�T+�<} �,��C[�Sζ�#'!� -��T%���dI��&)V�fP�=��� {������R���l�ŋ��?KCBȃ-��絜S5i�P�5A��<�<��~3��Y������^�KG�+X����Uz�El�c4��']��\���A�tN�j۪���d�q��?�u)Y��8�n .y���d�;�Jn��0�G�p���K���7�z���(6�s�ż:�B���@1��J��U�eYA)�@U�f���2���O�����It�4^�d�&�e���]8�s�a�btv����ӫے�w?��r����>������%R�}%�x#�Щ��?)S�B��R~�U���wFg�0$�C���3� {C'R�U��7�5*��o�����"����wic�c�«���5 y�X��d~�� CS��G��Z5|֫9׳ &�H^k�ft��R@̚�Y��@�B�{�� {j)��9�;�<<���)��[� <��wB�u���z+Լ�ҋ:3�����Ն���s����Y{��O�<��ڎ}��ƝB'��EvT�~����Z�7�o����k�fY�*������đ��0*�6�lA!Τ��u�����^����̹|�Sf9"��֠���qƲ�=:NM�q�X)�>�݄��ak.�/�1���V���߂�-�o������ �U-��'�md\�o�)q���HQo#�I��akeu6�Z��_* �C?2�o��%ݲ�X�*'E.1�Rl��a�,ת?��~Ef�\[�䭵^�����j�O���W]O�0}ﯸB�H�6�+���M�B��m���x�vf;�j���I��(��R�\�s��}�fc$���X�#���24'G����d4�.��i���riQ'�깊����wxp�?���5`S�*��K*}SVT�4�\Kp�k�Z+/��g���G��2�\I�F����Y���6y��M�5��y�f^��g�������@5��8WJ -���0����A��gT:�#K�_U J��#� �Z`��  -�qw{x΄�@���.�w�5��J�>0K} -sK( -B����JNQCP] ḡ��>����-x�+&��;X@U�h�Hƻ��:���%%31h�iJ�6��cSf��\K�}��m�R�`��9!1N��ʃX�,�8s���w[�|u��/�P���ï�ӏg�aW�=~1 �)ý�;��b��f�?����2���v��h��C.p�DD)�,���ؒ�����9�'�o��q�������%\{��us� ��LPҗ��FV� �O��ѩq�� �GHF-�B����۱W�-����SQ��G�IJ��@��*��6���JS:/ ^Ĩ�ŌQg�r�m�z���%��C�c�L)�E�5ە�)��%P0��<~�f��� ��j̄;����X�, ,������†4fl����\�� �GS�(߬ȯ�s�-�������w�� ���L����[&��w b{Zm,�*o!z��BI�wc������E�� �J5��򾕯1����ˁ�䢶�����)��mP�/��ď[C���U* ��{��י\��ƇS)�,�X�Ţ�q5��Z2�o��SMO�0 ��W�0��L\7�� ���M���K�| &�'I�ҕ!z�G����k_\�B@F�BIc�e��F���y2�"�;R�m!nQ*�/�:# 5<\�62mA�O)X��C�����5�L`��n>��G� ��A8�d�C�I����݁�CVZ�<� �/݃�퐞��h��p�� �'��͎��iB����?a�5�� � -��3]�V�O*n��B�[ -��k��9�� ������=8N .�#�t��0BmG$5���Ͳ��Dk�; ���u �lXۨ���X>i�G �b��à�;��cϯ 4��I�z6�W��'�.r��R�^6}_ Yne���w��p�Wt�9�{��UX��'0 �~�K�Frx����b_ؽ4�׶���U��.��� �v;�6�W� u�Mo�0 ���<pd+vM�5Ű�.A������L"�������Շ���Cb�������7fg�BY �#b�$�|4H�o�,˴�#!�v�NXB��l*�!G���k�}�z<�` c����� �:���<�B��h�X�R�@6�Xh� � 8Rz�������,�� �%� ���R�� ��r`q~W�Ymڤ�3Bܸu�d�7���b,����|��k�����:�"��A�z�t�"]�O�/��룯-�C.��b�z����! -�����Q�`%��9��8!��� ����1�V�b�hQ�y�% z�qZ�D(Kٵc�N���c���ט�Ӫ�tm��S�t: ���)�w����-�ó�25,.�4��M@�?��{�Z5?]t�g���E~p�0xq�G����Xdg5<�X>� �VlhWX7�}Ҧ]Uih�6D��@�}NڮF�d�~�8���,$䌖D��6�S���d:���A HŴ$���� �4S��.g�׽T���Z��'���Wo����0�kp��;<�8��Q>�Dq�� ��J�����S��e��k| �"g{CΫ�b !OS����Nഖ D�yl<��Qnq[3�D��&��qAsZ�3q��8Z��YU��[���Y��2����.�T�����:!�)m��H<��'񀣦a�Y�u�#yk\a&��eOM��Sp}���&�=]/Uw�ƅ����[f�m�j��p����3V X��'A���uu�k�����-��^Z��ғ[�c� }SMo�@��+��!��W-�M�C�Ę�ij�O!Ņ쇑����b�`� �f޾��/�b�2°�K#�e�<��ƎC�yA"�YR����<��Ӿ` -���9��H�,p �Hcv�Pk��0�Tfُ�K��-d������� �d�ܝ��a�y��g�"A"e+�PK`�F�4:� �W\� ��y��9U���@( R������&O -���z5˚�{c��l��#C�TC:<��*�v�����n$�PgJ���8iNI���ϓ��%�4V��nI�:7��L��u]`M2�h�G�C�v2{"I�՝�� M ��N����������3�ś ��O���{0��4� -�(�]e��k�ꍑw�u��T�]]D#�/&ݜ���ѫ�%skу� u�]O�0���+N�`Q ^2Q��D3�pd)]a��5mg ��n?V@�^�I�{�������CF� - ҐJP�R��Dv��8Z�&0�� I�4)22}�p��<�D)�^�M�la� �ЫՂ�B�P�Oʲ �+���X .�T��O��A�]h�6j� r�ڜjG����_�F;�y���"�}!�*�+��sH�ZV����b�Ss�� <#Qb%1W 0��J�u=�Z��qH�,���8�I�#�U��Ew4˨�C�C�d�����a#�4ž憣V�tus.*�c s��M�g��N�t��s�u�Syy��&� �-nIԨ���Į�O�T�Q�`0 -.�y���A�z�-��4[���fS�=��uRMo�0 ��W��TZ��]aݘ�]Ҧ]Un��Jm��1�&�����)������(��*�4VZ�L��(H%�<8֤2�U!V(��r���� �ō��XE��E�rǁ�x��a�O6�q �o�� >Q��U�ԣ�dU�`d ��;�J��u5��C �{n{3L+@�M��5p���eu�@�uS�;�6:��D򗏳�+*J�G���Ό&;�x���1@�zK=y[X�B?p�F�-�f=jVEV~��������@D�����V4�[��$!��%�ln����=$�N;/�f䎡A�ۀ�h�Nk}3�a�Ɛ}��ueǥ ��� V��Cu�-�8�Q�"�!灜U�Q���7)�w����-�=��r����ZX�95��,�סzl�6��|����@D�����V4�D[��$!��%xlv� ���j���7��SGР�-c&��i�/B٭�˜`(dB�QiY����`u����oÅ�/�6D�蟀�bh�Ҽ �P����=�~Т^ӕ,� ��B ?p�F��W�ET'���2�������@D�����V4��R��$!��%�lv����=0�N;/of䎡A�[�L�x��f��6/�!�Be�ʎK+�R]����R�T}K7N`ԿH�P�����c@j��| �p|���3� ~ h1�ә,��稅8G0D!��;�*���X��L� ��1�0����ۀ�U4��Qep$!�=� ���6���E��[ߗ�݁�fԌ�Dv&v�E(�MU+����Ahj4 r{ ��D�E\ﯔ�$��!�`��� -��z+ȡt��G�&���x�W� -��\�=��%�1��bY�VQ��E�ef5�����@D�����V4��R��$!��%�lv����=4�N;/ofw���E7X�\�x��b���(�!�De���++�R_����RU}GWN`ԿH�P�s���*��yH�x��G$�H�a��+y[X�Q?p�`�B�-+�*j�=+��lf�����0 D�|�7ڑ��+t`�T�Դ��kŎT���$���zO�n䁡C7ڀ�h�N[}1�a[VƐ}��u��� ���L6�Cs�=]9�Q�"iC�1 -�H��ښ�����;xDr�'����%� �~`N@��`�g�U�f���B�Y�����@D�����V4��V),I�y�p .�۽�1�����i�����3��0 �i�/F�o�����깲A0����4q��"�� '0�_$m�@5>'Ej�Ҽ �p� ��=�S?t��t%/` �5��. �1d�J���d��/3��|��1�0F���ۀ�U4���(��$���@��� ���-\���݁:�M�=�,���I��M�+��L� �3��2�X�&��ٶ�J ��f�u��368 ��aE�KA�Go 4������� �����2���dqU�Q�s$���լޅ�1 �0F����lGW����u,��^�@M��*��n�G����SGР�u��%8#�< y�� ��~ �6eG���: VǑBu`v֟)���"�� �εrq��Q�7 �^ -�(�{g��ވ�&�}�QO�0���)�d���Wp�1��� K���蚶S��{�6���Mz���׻T�� y�4��Ղ�����.��d[4�q�y��L��Y���e�4�+s-BDc�������Y����5 `�ɴ���K �k�()�0�<3�% F����Bp������\��1�������O�s)���^6�(-H��Q��b�m���4��qY����� %)噔i���c��%�,R;�LX�N�ͫ_��+ɝ�����}jgqX��&��İXEP���pH;wa}49*{6����!��P'� ��ֻ�!��|���J�H���#7VO�4�JKX��)B��ź���f�#����_f�Z��7l����TMo�0 ��W� �V�,[�avi t��h��8� �C���}��N� �Ų���H>��gW9Фj�4 ��-�͇��(,(8Tw��C�?�4��?;/�m�t����b�૱菷���1��S������O��WEPӎ�B�!M�q�B0���U������I \_ �f_�K��Oz��5Vrh@(S�8�ȍ+���"O�k�YA�y�3�qtVߣ/'����&��˲e -�Ek��5�~�PguصV�@�n�)�Y��rYfiY�r� <<�!�4ъ�,�VC�A�|�GN�2�ݧT�Ͱ���\�M߭x�R���߷et�M��l�N�8O�z �x�t �D;}:#��1�4�7@0{S�NvQ߹?D�[o�� �P&F6�D���ٸ��5��R6�6$^��m�|Ɂ�j4�F�m>q[R���>��JR-� ���]�R j̹,����� �T���wE�2g�u�R���1 �@ �������VWt,���A��\[��n�E��7dx��|�/�BP��4�� [#���~1��J9}EO� �/��쑳m]`�i���u��u���F������z\��tE����+�D�TY�����A��#d-5li��fQ] �F�v�������Vx�^����Y��~�?���1 �@ ����lGW���-�X(�5փz �*��n�E7� //_�k:�hZ͘xak��;�_��L)�/�I��L�f�\��퍸�X���� �od��=l�\�ǜo����Q�([k���9hPrB����6�MRX@$u LQ����9�F�������_�ɪ���W/����0 ��<�7��ʏ��X ��*��D*��8�껓�� ����|�<�ә�DSk�� [#�t�~6N�r����A؜i��#竦�|�� �4wk����F� �aa�j=�l]��gF�DQ8���)8#�qP�� YK�[[�:���๑�(� �����[�.���'�/��C������? �@ ����l]�" -���c���Xj.䮠H��W�N�2<^^~�-��P�i�`�X�pa���8S�� =k�P���Qʍ��\�Yʕ%-��c��#���r�EMK�����3�� -��۷���#�#h0䌢���ml��a -*5��$:!HF�$���}~k�� �� ��`��{u��;�0 ��� -o���CH�Xi%+U!5%Rq,'�Z��wR݀<���ϛ_J4��� ք"t�~�\��"}C�� dWδx���J��-K�X�ҥ����7��Ĺ5���2:+���\[��L���2�N����|+I����(�����g�9<��S|�w�H_|Њ�zj���= �@ ����ZGW?((�VбP�kl�kH�"�w�J�� ›'OVu �s lM�ÍP���R)��(� ¡��fA��}��n �6�k����Q�F� ��ڡ$ }#0%�]A*�gg \�7��Z %!���Ѷ��3X�(�[xo��"{Ȋ"[���W�F<��F�zR�'������? �@ ����l��*��j :�y��A��\ --��n�P7� ���_v* r4�f ��5�IG���0R��'z�!))���K�czn�ӣu���c��Z��évF `+�r�\� ��^Y�����%&d-5_ma� �-����yc�4�`�^ES�������<[Ȳ��ݫ7��1�@ ���݄ɸ����*$:���p M��@ ����������͎j�U##�X+W���nWq"��w�$BVS&�"秶����{m$�)��-ᆵp��Ρ)-�C�D��h7o�ӭ� -]J�ҵ|֕n��02� -���g��"�z�T�^�o�sF�b&+�'w/��1�@ ���݀�U$&&�B�# 9� -��Q{w ���=0��o�о�~���%�Qu�1���r� m�IR!���%��� -��<�5�ǁ��7��A �0 �����^�" -�D=Fmc-�,�8d��m���w�!��|o�� �E��8&�&��a��I�+E����A�_x�%���b���+OZ�w�:��t?b�M��aI�l��G�t����sM&���a�1�N���a��zTr��%�j!i��|ض��y�J�N���[��~Ƿ���A �@���+��f�"�: -���.��0� J��SCo�;�ax��{�5�V2�γV��=��o�T#��H*���\�C..����Gm$�����Z�����Z�6c�Σ�,�0�½� -�(���}F��[��Z�q[�HM ��I�>���r���a��oc��A}���+��>�7��1 �0�����lGW���؂�ӳ �两�"�ﶕꤾ��ݻﭷT���d�|`�B:B�YƉV�ѓTYM�d��]���%�w�J�R���k~xWi%��1`Жf�xD��h�ƪ��� -CJ�28>�J�(�������(�а�E�x�L�~��"�C��I#1��w|/���1 �@ ��������*��-�X(�5փ���,��n[�M}C�����j�W�]m�ĻP��Q׋Yb �*[��]9��(��)1��Y�'+mʽ1�_KC����R�����^ϵwp��o* )�����W���` %U��$�L�d�uc�x���3�o�b�$w� ��1�@ ���] 0�����Q!ё��G�K�hzw ���=0��o�о�~��!�P��1���r��n��B�@KR!� �-rq�*,�=qq�F�Q0z��nX ������2OA�o�Vp�F9���e�,]�]�6N`���1��y6��t�s�x ��Ι�˅� ���Q���1�@ ����7Wј�8*$:���p M�.���7� ��ׯ�=5�V2�ֱV�t�ݭ�T#hI*���\�E.�]�ű'.�H2 -Fo�Z� k��[�ޡ�,,C�D��jwo�ӝ�]F��u|ѵn�601����g�*J��8�o����� �\�ʀ��� ��? �0��|�sҎ��A�ZA�B���jr^.���ݴR7� 7����jC-A��ӌ� l�Tr' �y�T�� �a��^s@.w��2��[�4� J��Zҏ`�%�F��]`t���$���8Gg�z JA�Z<lc�Y �A]�^���t��.��s�������Ѫ��T/�O� -�0��礎�jt,���m ��� -黛*�n8���*��k�IvF -i c6�.� -����A8Tt���]m1�܈� ��=%b�RRF���ek1�3����0��]A5g� \�`��J�=!k���J�'S�C_6��R�`���GY6^<���~�<������Э�l�����1�@ ���݄8�����UHt$!�Q�<��.�����M}C������@5A��ь�x���'��&N�r��B� d5e�9?�%槎8?Z��Oi4�ko�@��<�R`����Qn�5p�x�:�Ч��}�[�&�a ���1��v�Z���7��[�u|�w��],d��� � ��1�@ ����7Wј�� -��$�<*\�wM����া�C���u����B�J��y�ʗ~ t�u� -a�I��7�Kv���VX{�⠍�!�`��_K�����{4��e(�����Z��3�kk�F����Ϻ�m��&FS�{c���@�ZE����-���4q� Zx?��x��1 �@ ���٬��V�MTбP�k��kHrP����n�2����� -t�eLD�;��N(��|eL�7��T�ɲ g���l�g[,ߏ�����?D�\�R�X*`� -��bzQ�6�A�S��P��l�峯|��a i��s1�Q#���l�^>��[⥯�;h��G�|��ҟ���? �@ ���٪����V�c��״=�א�AE��^uRߐ!����bM-A��ӌ/l��r!���4U��3z�a��^�G.v}��v .6�i�d������C��Z�ipt���G]DQ8u�@��;hP2B��sn�M�0��5��(�$�U�>�����x���� ��`��o���A �@���+���]S�� �:�º�+(�O-�U�0��͛�%Gj*TF2�γV��#�;�X+[t$B�P&�!����<'m%�)������?����� m�`3���Y�ߌVp��P�O Y��s]kF������X��{�$A��u~ ���~�,��FV��?ٓx�O� -�0���َ�Z����c�������� (��V*8��p���fk:4h�fL��5Rˍ0���R)��H��D��\m���J\m��|+�'���g���v����� -�&��Sw=(�5p�ވ�<�(!k�xg[�0��-�%��Y��-��c������FÄz�VVG<�����@ ��{�n��*������HBΣ�%x4�]1����M�С�����@A��֌k�l��}G(�&��r��B� \*�h�����Z��h��.�!�od�!ב&�%�֣+�z)�½��o%����o�jK[�#��h�J�l���;X��*���Կ!oÏ�I�~>���B��u��O�0���W��`Q��(�_ ��Y��dM�kzW��w�n�P�]���������TJ�[aqHl��iz;�K-vHFH�Ee��ռ.q��7��]�x"<+�R�����$��;��Y�Oa�YܐH��q�Wl��6��J�B/����dEc�;i�Uu�V����]�H�����)�c$x*K�^(�П��~�pZ!��B�ut�:Þ�r=��-���y29%���l:�+E��]�BᘜʹA~sE�< w8�I��w���jX����;^�/1���������?, ���v�c�u�MO�0 ���>Lꇀ��`0@\�Ipۦ�M�V�K��A���;I�� ��;�������*(IԨ)1�+�9��mzEwd -��V-PҫyS��u���[s)���s�Ԅr�0ОI�|>����8�`��:܅��� �8�-�J��Zd�$|!��F:�V���锤�q�U��3�w�P�A��J�p#dס�2x*ˊ�3�a��g�?���\���U�tʞ�r�Bۣ@rÑ<��N�ݠ~�2���}�|���iC�f ?���t�¤�3�kb�%,cO��=ϟ���n��o����ž��qO=F�uQ�j�0 ��+t(� �ʮٺu�]KaǶ�і@�K-��>�IH7Zl�{z�Yz~5����-MXl����!�=�OI�qOlP�j�B�d7�CE����w| �ҫ���̰@����%�I��i���m�ŋ����lq��]�`D�j�_�`q���)a@P�B�ܳ�-�[�V �3�����x��H�ު�O� ծ�O�O�U BQ�>�$ZG�+�3XoSh���.�������g��n��%~i�)��j�/�W�5-�b')�]��ݒ8�a=j�m�wN~}�� -�@D����LJ[������ �eM���A��߽����������G�X�����B(�}Y���G�{� rs�Zln3qsu� ��Nn�0��Ί�ȝ�H����S���% ? -��F�`�l��M��6b5��}�1 �0F����lGW���tp,���m�&��*��n�����o���G?;�J�'�V_�r�֍1�=Q�y�v�ֱ w��cwY����R��8�E��l�� d��(����#�S 0��򁪆�Ca��Xƨ�lJߖ�-�M�V��}�Ak�0 ���"��������nIa,�et�@p�5�lcɐ2��g'��`}W}zOz�O��E�I�Sb�7|qH��,��/$'���v��zc[�����EG(��q�&�†C��=�i �L| ���am=$m �Ԧ�@r��DYC o���)��U KX�i3k� �bX��u�]�#|�9ٱ������U���أ�~�u1I+8�X['�}�j:�Hՙ�%y�� LRM����rW�}���@D�����V4���PP�\�c\.�{ ����i�o�f�0��ώ��ɫ�W@9��rO��O��N ��ڦ���=�?4���è� ���Yb����|3��}���@D�����V4�V),I�q���c���` ���N;/o�p���C7Y�\�G��� �+Jc�}��u�@�eA��s��e!��V4BA���-���� �(�N ��c �B;��;g=�=^� �C��{��F �!K M�4�9+�jV�}���0 D�|�7ڑ��+�c�(���TR�v�"�'iwn�w�>��'hя���������eeLpor��v,��mj�����ʼn&(��:�E��ܡ���bhri�R(>���+���C}�� � 60�Q#�� 6K�f�U+����}���@D�����V4��PhGr9�H����%ÿ{����˛9�h ��O���ѫ�7�wUmLp/r��q,��u,��ى&(��:�E��.��ȝ�H���F���s�����!? -=��F���6K�&.�X�j�u�Mo�@����9���������bLڛ6d]V�D�;����~@�=ٙ��wf��I��0�'� -%��)g*z -Ǟ'ȁ��P�4_��\/�����\�x��D:� ȷ�u��(M�N���y�2���!M���D��{��5��a�M$��� -�g�mR^l��B�R�m]��<�2�rs�I�^I�o�;�,(* �S�[k[����J2͉$����rV�w]ϕ� J��7X�%�U͕�HINp�z5yS S�$y&��ݒ�jͶ�$BӪ�+c� ���V_!�QZ���G������=L�zxq}�Z�m�m'"Xs�n�J�1�jT��@.^g�;���\W3>�+�.D2,���o}�� �����y���< u�o���1����W��u��j�0��~�=�C�Ы[�)��hoI0��� �,�RI(y�J���4�A�����_e-�B�2�1i�p]�D��� l�$GX�r���,� -7���n�b�D�h;��A����`�,Ma�͔?�5��t�uiʶ�b���/���-�pM���6�a�G�\2��}D��~MyV�;BȴmEi4�UU����0�����ewF( ->��h�|w���z�@�O�S:�� g~;��꺡��K��%'���5�P�� h�F'� �L� -�Q֑�Eۑ��[�O�T<�=���\ -� �x -~���n�0E���Y�d� ���<�@�"�]8�AIT,���jb��� z�v��%r�%9sH��e�+!�1#��R�,V[�/�\} -���INeIb -w��I��m��ǯ/���J{�m.z��[�c��G��NCJ�a����(O$h5dy�hN���y�<���|W���(��Q��� bg�I���,�����U���̶.�� ���h�O)�cF>�����F\/��0�TQ!��R�ȁ�eᗤ�����H��C��o���`��4�¾���� cH��O�3�ە����u�ԲT�pf�>W�Qbs�ӫ���pSp䬊Q@��I��Y1�m��*�^�@ �������>���S1ҜI%2�BUq���7sF�(9������(6�"��ˁ �/�5�f�y!K���,�`�Ώ�&��o"��擈�D9�� -0 ��E�2�3 -7:`vE_)�EU 0A��^0D�+&ZcCb��â`�%U�Z��b���Ղ��(7ڠ�A�8+�d����O"�:����R���8�TV0�Cw�D��")5@C�V?��i\�md��\��O�z�A=]�F�S�i����f'�J3y�h�f�s�����������22�4 <�`ڈ�� T�`�vu.w �eu�-��^�~��r��v���ia�-�۲�_uRKk�@��W�A� m�W[[Ki�"�7��nF�Ͳ3[���>|T����y~Օ��F��Zr�{�4z̞�D�-�aZ�0�f>iJ��quK7*W -��(Ί�E��,+��* <1�M��A���G�����| �.7�����-+�!�c%���`�\��Ձ�Z��^Q�93�@ c�z�]ϖ���,k�%6p�ڲ�2���E!������h�`�� 6'(�q)ON�nt��UM�/�F]�|�\ʹF��K?0��� � �C�ЮllLW���Ui�A%Q�LT�}N�� �T��<�k��Uo4�(��`f�4�Jj5��c��$J��j!�=ƢY��5.�m8�����NS[U�G�%? �L�� waCk���b��U�F�;\�$|�����d�$ Bz�,���$*'Z��\:t�dy!ƈ���#��V� �Pl�tۿ���S� �,e�0 ���� -{ �U]M��h4:'��)Q9�Mc�_�/�c�|��\�i���*ߣ��*�k��sr7H�(X����"�﷘�<-��G���R_�2LB���uRMO1�ﯘ� 1�(Qb�"��Đ�l��n�Nb��N�0h���{o^��ã/=ԕ"�&�y�{�a|׻/ -�j ^i�y���b�\Lw����a�_@�D ��ր�d��Ҿ�\f �n����%�ĵ�L��V�/��˶q�-���5���l��h �-�"�r�k��$ǃ���x�*y��Gt�1�5p��.R���䐚�;��d��p�*V�K��BB �m�;Fg�K,��� �Շ��I�Qs8�L�-��{#�E$q�(��%w�cr���i����Ue5���9��R��v�����у��̖�p<]�.�2* u(�l�A~��4�4N���+�r$']OJ���ڛ hVI+St%�I1/s���uRMo�0 ��W���RmC���1M\���ԃJ�FN2�&�����R�~~�����h�Pnaf,����ь��/I���a��3Ai9m*\N��8�̽ 3���NkB~�ޢ�8ʐ�7>�<�� �FA>y��m-��-kW���g�س�րP�^ LTS��CƂ�$v�XK -�9)_A�8@OX�M�,����2Pl���.L;%=�BF�Y��7�G�X��mM`�(;�3��yDٳ��<��_�~��1��i�v�J?���qևa��Nh)X��-]E>����� \~���?Y�P\�E���u�Mo�0 ���>T*T�Юݺ1M\�vT�ԣ�J��@�}Nڪl�R%~���WU)(I4�id���-�I���OQ$qGF� XTj�ڐ^��%�fG�9�̭�����hI�|�P��I���:܅�����I�+�ij1���I�B -�{ɹ:a �b��$�?t�^8U�qך�0h���G�Z� b�\���d�,k�Bl`��^�J��I�P�Op����v��ڒ�A�,�$\�%�2�Um�_�/�}�|�]�iK��m|�澧\���gp�d���L<-Y�<��b~�����K| -?I�;G?u�_k�0���)."Ԗm�u���0*%����4$7C��ݗ�vN�Вޓ�9��wr#!e4#�u4*N1��d:��d˴$��t#�Di���H[@�Xdv ͅFxOFI�6�A��ia4���[kYrC~�dٷ3,\��<5V�U�)�Y�:W�!�'�V��"lv�����M���CI�6� - ���z"'J�=�Y���5<�)G{�dǦ� -���FP'�$�u�N#LTĻ�\�̗��� ��7A }08(۸�����W]t��l�5�W�r�1q�d�ٯ��C��}G�5�=/1gvL �[���]7�~���~uSKo�0 ��W��TZ��]��@h;"��� -iV*��1�6���4m�t>����;}~{�%�l��,�N�Y05}�� ���� ��j/VD*&7�c�6o'!mݨn%����VE)x7�.HY;i�3X ~�1�c�} ���Xu]9�!�ǮY�]YP`.��e�ނ=́q��0"seMy��B �#�c0����e)�������3A$9t�:KPō�>Xe�Y��vB�$ghb@�]��h�`�e*�ڬ��\��V����C�y��+'SXo�L+����9Z�x��Ln���(i:z_��7���殊^�� ���}���� q�j�ěm�I���Q*A������?�O�8N�-x~O���9�5�%�u�Mo�@����9� ��������Ljm=4jȺ�J� ُƦ�wwE a�y�����l�ADp�is�b,B�����eQ�#�J�@�����ƈ����֥����^��zm���7��j�ͥ����.-�?uSKo�0 ��W��TZ��]aݘ�� ��SRC�A�����ˣ��Ai�{8v�e.!C�e -��U�u�)y��A �I2�0��)B���._�R�xE7"W3�e F��i&8��p�Qd�`�0�a�͔;s��x��Zm =tbg+��`S�dK��9�K t_�aZW�⚀ (N��ȿ@�*���2�L���閳�;�/�[�ފOo�^�3����3mz��4�dY� 7�B{Z#��]W��DHS�T�����61�u� ,>#�=q�fL���L��h|��鼠�'wI�Q���=9�Æ��U�����Α�;�G0�����J X�V,@�h�φ���?��;R���?kh/,m�6lX��u�MO�0 ���>L�V��`q�2Mq٦�M�V�K�8A���;Iڰ�)~�����{�UP��QӐ�����AO�G7I"qG�P,�j��I��MI����.n�\��ᙙL�7$KK>pk�e0�@�kxC]aQC6e��0pv��M8!��F:�V��Z��K� �n�P�.& 0h�w�_Җ4��5��CYV�j$�p���0�n��B�s [`H}����),�#h�P��L&}DO������.�i ���_S2/��M���G0����k2VKX����#��瘯n\ˏ�������y�4��}�KO1���)�@����+�B�CH�dS��,ݦ���ݝvwy��nә����z���dF$���V ۿO�H����q�3c����BL_7ڐ��k"Z ��bb�B�)���N7�`�� $Cc�� ��G��L'��$=Z� -�$��߲n��n^J��A�b�C/��RG J�%)�X?9#�)�k�ۨ�f��/ ������0����aH�;�E!QV��pxm'E.����u%5��uӤ:��a2K�nu`�x�znj#�^#���}����[�M�袮��7���m�^�E��FP�L​g-��A?i ̯%���}��@▷�~uTMO�0 ��W���mL\7�@�&$�m��ԴA]Z%)�;qҮ]���F�߳�KҫE��� ϙ6Jp����r8 ɶ�K����)�j�,\����+}��U��X�#�$�F� $B;����&+�;��_e����O���Fp�����+S��9BV䉐)�;r��+.�8�l�l���uD�)��>�b=����л�Z�G0�j+���/�ஐ���zo��F8�֊���)�)�b[/��!I��� ="�_����;y5QDŽ� >�����=(S�}�;P\�p�$��`9�њf�ٝkQ�7^��c�Ō�~�9�6m��I�!�3�:�; �t�e��g��3� }~M0��N�Y:��Бw}��$~v�^�vOY��VP(J�G�H�8�yo�_�؞ANp�8���%��<2"��q�D0� ,�l�� |=or�~?0�q%��z�(B��|e@��\�A���& L>K���'���q)k��~�&5H���Զ*1<�i�ڔ� �]ՄJq�C�д�g������# -k;E�, -UP-���Z� C��f��J���C�6��ц�F ��r��.[����~��8G�~�אz*�J� -Ye��0p�����;�J�� (g�;д!� ] 7Q�� -5nF �����5�`���QY&Ou�p�Il���*��J�P���2 ���� z��3 � ���)�D��+�n����g'����lA+�7[���R�!�c5GoMl��y�`�����ݽ���oG���R�|�A�#��u��N1���s Yب�+�b�/�D��f�;B��۴S1��m�T衛����ә��k 5� �-)��&;��e�� Y��`��34��b�ִx�j���^�x��0k-�*�eR���̾3�gX0�Bc��Fb�Øծj���xX��/��*_�lA{ �JڐbP���$���7���t:�?B4w�C��������,[� ��_:%��RtU��q�:��|9�CO"�OB�ht -8���t����~��N��>;[Њ��UaD�0��F����!vF�<��|���} ��w%����V_�=�;�>����N�0 ��} -&u����`0\�I .�T���*uY�8hڻ���6`�\"�>��}{o�JR52��p�$��!;��$�� Y��`�63dK��nKZ<� ���e<�Z�1=�� ��B�%� �3�2�| ��!WX��0f�+�JA� <���2x�j��)��`��� i�\��N>1ȸi�D"��+�C�̸���F�,<�e%�Vc �h���w�UB���d?u/Π�0_�iI�9h�N'z?�Nٓue/���m�B���5�"yqE�4��?�Q��X�Ik���.�ý�-���5z���������|u��N�0 ��} -&u����0�8p�*��lS�f��e���Mh�N��ڀ-�T��~�c��Q�5�$d�R���5��vt�$ -7d4 -�b� dC��mkZ��4��3�2�h L�J�,��@�%� �3�2�~!�| K��l��U�0�����  �C�ЮllLW��n�*7�hE Q�L�������i��T�_?~c��UU -J jJ �Zp�GEf��>E����`^�9jCz5ۗ���vqknE�XT�4� ��, �p����Y�/ԀZ��R�2T��!7�6�Ze��0P��8|�o����^�,+���E]AW5)Pl���3��]���p*��m��K�����[Y�\�%6�{��<��Jᅐ�s�\��롏a�N�ܺ��F&y4�f\���:倫�ܿ��7�4чNQ�� ��~�3�I -��跾&�Z�2nq����íV }�G��~䗵�;�)�uT�n�@��s0A���Uj՘z�1�75d]V�V�����wg���̼7���,� b4!�u����P�3��/���I��e���%��������)�&��f&h��-��� ���!�B)Xi�9��$v�LD -��z`ΰ߇�7�`� 3��0�X�C[�廄S�X��\�u�O��c� -�X��z&�-�k�.Մy0MK��C�Ga<Ω�'��PH]o���a~.�K�4#��M��q��s�A��U.M�V[�Ss���S[?Va�|s�%R�3<��f�]���Yq͏�$PE �;G���XaHK�N��)��dcXo�H��=p�d��� =��V���V��1WOo����b����1l� -��];\& �Om�����c5�BE�I��z���^�;|M����Nw�R2�Kk���ǞxGz[6��M��o��;]��>nUX}�~�z��u�MO1���+�@�@P�D0DCH4^�lJw�F(M;����*,,=�nv�ϼ�L{�W+9�5�X7���v -M��M�6h��3mPOG��_J�xa.D*#˪ �1�,��)GIb!Ё� c�� >�[��v�H|�E(s�N�]�fL�+��0���|h�}�*�k������3 Wr��9� Mn��b���僊��7 ���}~�6PL�͑�������\�\p�U�q%w'�5�U��B�*���}� [��p�����($w��e<6�,���־�1��Y˜=̞SI�N r�ot�3k���.��W��Q���Sa�й��Ҁ>H�e/>��#'�Pȇ��?B=��j�MG��ږ��ooT�է�ٙ_�-�0oTt5 TZ0���?���i����ݞ�2��.:f��lK��&�͞�-���u��O�0���W��`Q��Sc|���O@��;�(M�5,��ݶ[3T�C�����ow�T����5 ��Z��4�xr;�K�;b��`^�9j&���KZ��vu˗*�� u�ZK�@C�d���;��Y�/��,l�\�%d�PWv�� �EvW�2x�K� -À`�삓t1n:UOjܵF-�ջ�������k��bx*��8!n�?�n� -�i��B( -C�up�:Þ�b5��-���!M��N�n6Q90U���&�W�pL�fڐy�k?���py��w�d���H=-]E��/1�ݣ ,����?,� (��F�1�u��n�0 ��} -�Z�mhW66�iWTi� PeR�V*!�� 4��KҖ>�R�|�_ۏϪTP��QS�FW��f��'�Ç(��!V(�Re���b�-h�S��-_�8"3|J���� �3$ ��~"p1JS�~�w� �j+!�����0���> _H�u+�_+ ��:���t&�ڢ�v�P�� 8h�׼�D�q�1@�����^��2���߶�3�_V -_y.:�I��U:eO`�BӢ@r��f<>%�Ի1���ۧ����o>q����&�nW~j3?�d��O���X-a{Z��x����p�X~���/,� ȏ�w�C� u�MO�0 ���>Lꇀ�ka0��N�@\��rS�V��(�&�'I�`�b�y�ع���X���e�t�����6��"�R��s���r�U�|� i�F]�X�R�����%��&^)p��3�,��%8Y��r)�YA�ƾ@��m�� Kz�2x�m4L+@�n-� %�r*P��D�3�'����=��j�)�&OU������t@�\��p� -�(X��xW��o���!y�� �y~�|��{�1��F]?��M��\����&�jJ�67���~�꒴���ū�s�K�7��<��X�P���!�u�QO�0���).�d���Wt�1�HH��YJw���5�����m�9T�C���������Qo5�(Kap@l -�4Rz;��"%vHZH��Vτ!4�i���e���[�qD"�(��_M��3�������% L>� o�B��R�|"�}f2 -jmWe!��5��/$�\)��J& -z]z�su��Z�D #vu� ��\�� -c���]�V���)� vBQB��d����Jz!d�lmB�P�� ;��ru�ɍG�x|J8ѻQ��>o �~�����ct���ͮ��~փ!�?]u�l��y�i�������P`��R����7 �ُ���o}S[K�0~�8��e:|ݜN�'a A�(Y{܊Yr+��n�66s�<���r��sz}�7 -�)ؗJ���T�QN��q1�E�I�0��9��lW��Á �kمG)�DZd����`��3s�i -�=�4�_�G� � ���1H������9�>�w)_���voq���7�p�c�e�+ *5�`��ˆ�SN��kk�����^�9�M��!H��e��� -%�E� �Pho�I���Y��V�a� 5�6���+�U'�X&P��0��hz�d���M)/nj�v����AY����Փ^�M����'0j¶�*-,bS+@�\����.�g����� l}n�o������1���MK�0���sl{Y����A��� vIۉ v�0�����MB+���^2O�}f�omc�ƪ��cR�ro��\dWI���B��B�CڭL��Թ�f$��NW��^�w�`Ե�@%� �o���^� �!w����M"��p�&�nB�iǷE޼y;����+�P�`��Q�.�s�4����$Z< UB"��՝0���m�!�j�7�,@H���~?�>��� ��t�/6x��!��8� �&��5)}�� C2�e�|��{�1j�����3��G�?����cEY�#c�ت^JB�q�6�ɨGa��u.�P*QL��0��I �U��+�����SqkڦG堏��FA(�5 ���;�q�S Bg�vN��k.R���n��< q �� ��1x��b���1�Q�hٝNk����r�q:����� y�N3�P��:݄��NL_�3t<���L���E��\��zd4�nk�U����s.����9��v@�=�k�<]�� ɼ)�^R���2���Yc�|�T�qU��e�:1-�>��o�!�&�1�k&� ��=��3'm�6y -.����䨗�Ә��Y� 쑥Q�+����4�s΢�,�v�`� ��6�و��������{D���I�g`�Dx�,�ۂ�k+���y�$�6��Al� Go�vNŞ��=d��k�I���=�#a����@\��xy�y z�sÞ �����mz� �)@D�/|�1wLȊ�X�h�g_�0,z �jQ蠡�00�e9\��0�#�H¨�5�@?Ļ�@�oz��s<�.�0ldP ;gc���,���nnF��FمW�I�&+�'!8��2bc%[��(����i[��x��*+&C���A��^����L�;�y�J3} .���`�3��y&"P���Ԗ������nw[ib�l��^Jj�lX�@d�l�"\W)�����׋ U1� -���\{,7 -���O�鳵�%����Ζ������{������UAД�� �c�-��/9�ɘBMqgF��� -T�%�����d��Os�r�S[g �ܴ`iT�j��Q�l��j?;��jL��L�!n@x��-�� -���$g�~Π"P�X+Ġ=�����gi�ujV -V�TۛA �����@HX�1�g�%��I� -��d[�k^!,�5�V�S �B�P�0�����-L��l�Aڣ����;�_�'�OH�}1�Xy�83,�rx&aaαY�i�O���;��j�d4q��t�'o����+O޴�[��|N_ N~|�I�PI���K�R����|����`n��B�!�@e�M�<9Q�E蜘������5����j�1X:֚vs� B+��} �t ��,�-�#��{�'��c�Մ���W� -zݭ ��D��C��m\7祐�����i��� CN��`�6;.Ym��Bx�s����n������O|2�e�o2u�e��p�6�MȚ���hK�ȭ�]Dw�d�R��DS�#���P������-��6�WH��"�`�W�g��]x��N��lC��L��k=����S�ȗ�| -��U���1aJ'�$�lR����t:.X)�3'�l���ET�P3K��6/DU"�/� Xm ����GPU ��ݗ�`K�ʂ����[�w�lft���c[�UPϑ�����n"�V"�Q���c]s��v��X�(�X�{i���6%�Ӟ9�T��ˌ������游�-�P�o�m����bB|����U��9{~�����OW�����t �m�K�L.�&�������Ҷ�l�� +7bP�٨����9�A�\|���B����lå�\�I?LpS RWb����X��� ���ú6E��T�+�/3:^O��pug��qM7�A�Q�ge��J��P\�#�9�)vA,6=ĨU"t��5z}��n�r�����Q����bw�n���~���[z���ʓ��f5��3��Hc��]�|8*��~v�$ ���l��JJG��������j�0@���9�@z��=�� ��#�cQUr�Q�P��+! Ic���f��I���ʢ� �H5� i�hL���E�*�d���L°(��6����0� 7��G+�|dS�s���Pf�=9�O��u�U]��� |Hjp ��� X��1��i �ѩS�^#Y-a����Ü:�W���� ��W҅��#��\"�K�O](O�q�~d���&i��W��?����w�� m��hm~q�H�&j8+]ENnIEJ� �T¬�( VW#x��m@�s���;�<�1���B>��.��T���V�� �3*�9�%Rݷ{+L�^�o�0���x�([���~��Mk!���+�P�tC����C�E/ ���Y�խ�mB��nbB��i�3��#��7UYA�\2�mKFpѬB{uֹH���V�# �jȌE� t���o�}�g-<�d$>pJ�r َ��;�z�n=B�%�7 _%R�|���>����оK')��25*�$g����ڬ����c��Rp(��$��/��v���#��4*���;�̓���B�+��q -'�l�0� �=X�!��l8��� &�5�:�;cP��"�����=�Y4��yS�t��/t5�ul=�Vj����pB���פcn� �N� ҳ�I�]�}�"X+jΓu�Mo�0 ���> �C�Юl �N� ���rSC#�4J�14��K�v�rH%�~��v�'���+QQ���̤� I�� �#-�, -�@�I��UN�����YfȌ}�<�a^�%f%}X З!��`/3���a�����$ �pRǹU_� }����38wV���_H�Y3�A���r!�+���-��LAʇㇶ�#L%*܁�����xYh�suU�J�n���e�! �<�WK8G[�E��Z0�iʺ�"�����cX�ch��!v�ŒF��|��_s�����76��i�{���{+��nr -���%�^gn�nz:rU���SE�V���������n��8����_.쐧��T]o�0}ϯ�����+��Uׇ��j���R!'�!����+*��vL֢Y�ؾ�����U]ԐaʙđҲL�Z�jT�O�y �EU�ᾨ�T(W�*C -N�$�RMA�S -(ƶ��E���$z����d�OL�0��e(t��(�Kʻ_�$K8�$>���L���ɥ��M��>X��cФ�8�.P(��\PS�T4P -3Hv 1G�� - ��w���я�J��&&7fK5�X���_6FE���N�J6�9�O>:�8P�b�W̙��rs�1k�tD~���n+AGcRm+��th~M��څ�M?l�m�?--@��x�����Q�C���b�Ü��sQw� w\S~t��D�. g\��O -=�7��y/���a���I:�,�Y�bo�x��~�♭J�n\5rd�p�@g�v~��z@m����U< Nj|�$�I�]z~��h���I�mo�Yލ��V��b[O<��1�����ۏO?����5����4_����y�� �� -�P ���ׂ��[u���F�G�>[b)��fN�t�U�N�@}�WL�H�#J�Ц -R_�B�JEk{o1��^Q��;{qb;D"N�;sf朳��_벆�f�4VZ�L/���jz�| 8y��&�벾&RQy?9����`�Q��U)���ǔJ�Ϛ�\���~ƣ�= E%���D*Ca4v!�I+���-"jHs�0�o�c�&� -H@�E�B�����$��D�����v?E����C�$k�d�FS�y�4�T�] Y� -�3�E�t�{8x} -w�<[ ������ъG՚ȡ.����O7�h�^o����ƤVę>N�4t��/�6��]��y����J$�G�s���r�����O��r����5����=zY��% �Λ�6 �M��-�Yۺy7����D��p q��e��m���M'(�&��p|m=tH��Q2��qT�I�ܸ ��ө��S��baz���o��m�����Bc=,W ���lS`%YR{$�8�.q�D%­� �1�͈�#C�ي�&T��iQ5���~ՙ6�D0�Ym�w�y<�U��Β�zne����?��>P�+�α�:=�B�����n��G�w��/)�bfp���Z$S5o,r|b�T�I�}�����v*�!gK�0>!�$٩#�x�v�.\AJ !��Ë����#�n�|O��>�"@��ٵ��{n���n���ε���"����u�u�[k�@���+�A����W[[K�U���ͪ��Y�R�����K�yH`�oNv��]n%T�5�xWU3S���z��{�2�;�%2ӭ���\-&m�3�d!��7��^R�N�d�vZ�X0��W����JC�g?��/ -}���/����v .�؀De4}��v�� :�����P�g+�ePOm#IP��(M��p�Z�/��>�1*��⮬�>��6u+���i�]%_[��ʒ��� �"��>����<�f(�`pɸ��<��c��~| �5<ݛ+���6����Mq�V�ۃALt�J�<��|�����v���0nhyX�2-K����u�Mo�0 ���> �mhW66�iW��n�*�Z��Q>�����&��!��׏_�~~U���D�����J������)I$��(�R�P��iS�r.�SX���S��)P*�L�[Z�/K�0�)���fL���x!tz�c����(��+=�B��C��d'��^@��dH���Z�Pw!�P�8a)�b^Wׇ.��Q��x�e��Y2�V���5�nC�Ek'E+�<���E���?Ǐa�@����v4:���y�Qٳee�_��ƿ/ۆ�ɿƶd�n�Nz�nG���T_�uZ�"��t��y ��k�a��+ZڭK~�Pi���VmO�0��_qCI�n�I�X�l�� ��TmH����%��/����i�t�-:�?~�K?~�� "&DPW*�B5U���7h�8�S����(�FDH*��iD��C�)�#��k�B��� x���}��*��+�# �r�W ��u�k*@�0[7�G[@� rE��3�”K�N�GӃ�!��v�?;����Z����;k 'Ǘz�=�,�t`�����2�%I����A�Bh�`�d�ЁC�,�PI \��kD��4W�꼱 e�~F��S����a�Mg�bjqVr��� �$|�"�X�IRi(mV����z#L�aI�� -�l���$�W��2��~o���V1��?w� -ꥧV#�k���@�i�]�zз���U��0v �3�n,LiF] -��U� ���U�$�P'���o�?�M(�bñ9I���J�bzh��q"�Ɗ�d�ϟ*[fi�h�I��w�uaљ>~�]$@�`�nf� e9KpG'#x���*]Jw�~\��Fe���a�GZ���&JE�r�u) -�%�7��[ܵ�>�H"�6Lɷ� ����8��� �xg���C� -;�x�:y��k9<�B���Ԓ�`�ޕ;]�\��Ss�X�.x�W�ƻ��Z�_���{�*:M�5^J6X'{��dq�?K��oɾj������,7nQ#g�㭒V����Ǜ���X �@��e�;/`��ُ�B|IEI��=�$�H�*NA�D����\)���-��q�T 8Wf��Ө��j�@^�bV�鲴5��^��O��~�?.���6����Ԟ蔠O��u�=O�0�w��� T�) &�J)�.�5��8�}��P�;�$�)-^,�������[(I7�h��՚s>X�ˇd�����EM���5:On��J�n4�E����$�f�x���Z�v�3��)= R��@��w��s�^\;.�h2����J��Y(��˫������;���w� hC�qq}���/i���չ�>ǵ�����G�� n@#܅���}�ہ�_�<׆��v���!r�����>R&B �Sj�1�}�'껶�V �R�##eZ��g;)O�<+�Td��D:�Jg�t���D:o����*1Zo�e�xd�H��P�v�^�������DQgM��[��' ���� @5�ގǮp}F�U焱Y�>���R�C;Al�P�b�Y�X���iNV���:��[ǩfU8nX?��k��!������T�F#ɢ+IGӂV�h�Sq=C�39�����LP�Y)d_Q5S(�?P���w��J]2�, -�#�����̵ql���G�bE9�Z��w7p��R���M�����8����4Ks� ��j���He�c�U�����M��:�Ui� ��Y�n+߽� 0�r�l�|5W��ҥn�F�IbR���0,fn]>=�(r��ɂ^��j}�j�п�����ԛVN=��K��x=\�MZ-U��[�9���^���z����F\?Fݡ�<**�%!���Rɳ�0�>����ck;�ԣh ў\�����a��E��Hz[i��)Q�O���,'I���<-6C��z�?F_�(�,�I2J9*�R���c�:�����7�Xk�[�� '��0�Cb �R��v���^���O�ֻ�WVi��W"�t���덈��g��U��'?���Ɍ���A&�s���\��P�N�;�^L��`I:��c~�o���s�e�D�T`m�}��pF�����#�{��ŵ��n���2F|��!����0/�u�E� �}J�y��i��!���4���Hpl: -��4���nY �xF��͉R<9���ӱ1?oXH�T�sc~��+G���v_���]vV�R���K��B4G�i��q惖��_�����'P�Ǧq����6�]�P80[��r���NSj�쒟�^���7׶a�'C�q�ym$>����/�(PHIM�I,J�(.)�L.�/�,H-�5Դ���K�M-.HLNU�(H,*N-���OIJ�� :& HL.J&B� -@����KrKR+JR�R���rUs�ru�MO1���+�@��Q�WE�WB�7 �nw���m�N D�ﶥ ���d>�y;��G�UP"��ƾ!]q�i�ЌowI"X�F1�0۪��r*K\Ω!��qN3�5��pG(J���3g�,��� l]���Ni�ڦ@ r ���I(< �a�S��+=a�*�� �H��`9`�L8�M��i�4k��=����C`Z�=����M����ʲ�J -V����dm���缕���W��e�1,V8�4@�? -�.a��OObi[��#�8N�>$�� �m�eM�J�0�jν5����������on��o���f�~c�Ji <$�}S�n�0��+怔E��W(-�!�E�q DJ��W����YI�9$�̼7o?>���3�D�Q�c��B�™�I� ]2.`}*�Li��WE"�7�#ōF�IdZÒi�8�����}z@6�"X|0�dٗ���K�B&)����F�P(�w`&���Öf��F�rg�lm��6�P�B�5%hZC�"[�&`8p�����p��(�b�P����À����RV�t���@k#����A��%� -� :o ���H��1o�\㚿�2���u�*Y�./ݏ����gm�O��{r���� 6��M�����������9��!Lk��8%�( [����;Z���_�o��}���շ���� ���}TKk�@��W�A����jjk�^E����ͪ�<��IQ�����lLҴ9ٙ�5;��*�"���P��9�x�BϟF��e,Z2.`}�k��P�U��S�z�����l�~���IEkX2���Ei�鼛�L�cX|1UVd`��c �#����́*O-P�$�0���6�1��d*f�D@�嘫;c��pA�#c�m��HE�����@�j�0�%yFUp��xF|�Uݵ-$S,m��*���w�vj��}��P�*���S�]� �F -$�oQ ����B��w,2n�# ���s��@9�IO���(��a���n̹����i6k -7@����9�/��[+�(�y�{�(9wnYL����I�8��0렇#�U��Ε�Be���>������N�|�%b�ʒ������7��_�����T�n�@}�WL��ƈ6����B%�!A�$��{�+��GEM��{� H��v3;�̙��oy�CDÄp��Y�ns*��N߲R��"'!�I�O�?\e}���y!�O�Q� C��T �_H�H@��~[ �i� g��K�Y�V�r�S}�˄�p�J�Z�w�I���"9L"�4�ӄ�I�TD��̋0ҵ+$Ln� ��t�g�����P�k���U��2I��{�9�6��[���A1�$�XK�NVE*G��J�cxK�=-�����^�΃���BM.g'E�m����~�y�1�V��ֶ��JzM����b&k��b�zq�f��[Jnr�Oan�xvlCl/v�[CqS�)7��:Ʉ��\Ff�l!�#���ISRF]fY�f/��h�����wv -��ܭ�u���wtLn�/���<ٳ���ӫ�;�����gP��x������*i�"�N(�X+bK�Qs�c�kCI��XF_�8&���sD[x��ۤ�KD\o���������ENy����ji8ጎ�f��LOו�t �(Pϣ8P�dg����-��jt�9�����2�YB�^��?����Tp�=�/�k/����&��no�W�)�Z�x4�%I4�FC�\�V2��V�c Z2,,�}���&��g�b@p❹hS:$��|��ߜ��s?�ͨq/裘Y�.^�)����.:�?���x��!W�t^�2����Q���B��7�Xmo�H�ί���G����I :�F��Td-�V1��]'EM������Ɣ#�fg晗}v���p�Cm�D��E�la�}H��O���Ɏ�����8���>��N�<�U�e���8�m�ʞP���8�����b8@� �;2l`�У;� E�Ώ����>=��/`� ��J�k���Ε��e�ux��]�Dq��z�w.���yV`��u+0*ȉ��1�!�=�j' �d�U!�� -�74�JI5r?��׊_�[:O�P��=��탙NMneՁ�Zj5(��\�J�0D${P�ਅ1굈=�pԖ��0[��aK�"��eف��m�\��~Q��L�(���u����:�n�|CEj�(�G��M��o�s�ҰE�B�h�(4�E O��9T�n�����^������a�]��A�P���/`��5����Y��f$j��'�z��U��O�:f}Ed�l��҄�h�V���x^������PU��f�t���������xr;����NF�n;�aTɋ��cv�G��q|�/�MS%�9~�i�c�A �����q0�#��k��ݥyW�@6�c��:t�l`��X���� -(4���O�FlN�~�3�n����i�%�\߸�4߉-��4q����Rw���UYG����hS�LR��E5uL�ȸ�O�N݀k��Kun�B�8� -h�X������IΜ�A�Q�HR��d\�>̡�bև[���#:P;n=ѽE�3.x/aj?�[z+$��+�Os��id���R�'C������C�����T�_:���D���-���T��CO�}�e�RR�5�c��Ͳ�z�A��>�\�)yT)��T�|qZ$�q| � N�H=�&�����J�6V��)1��VZ��W�!��d�4+�ǀ���Y��������|r3�N߬ �n8�ۀ��9�:aj �S���r����̾,nG���Ʃy��ֳ�Po����pq{bd������ ��0��/f����$\�c=��G -c ��'����뷍3�?Wo�|1\`_8-|��vF�wҾ%x�����sM��^�,<����4x��\�|c�� ?u�d|��+C�uW���C7�����WmO�H��_1"���QZt��K��A�("�V�hc��Ugm���EG��;ر��RKy۝y����u��3i�N;B�8�S�̨v�# *2P��gׄ ��Ґ�M�B�~.�;c�S>X[T�(����z��WRڟ�W����).?~�ν������ o�6����"7���P"o��x�\lP�6�L|�/ oq�&��;���Eν+� �;�R���M�3���LK�Kw�7J�9��8�'x��#�u������I�1��7�|�����<�A]��I�s�i��F6�4��5 ��5���^�/��.(����(��0������O؇� -�)�X: -�n� �ӌp�U���x䅈G1�O�Mh���5%�9Y�y�"�)m��]��^�9�(M���!�d�2��**�_��8:��1|o̷>���]Al� �]S �s�+�*�B�!�t�/erWx��_񼄟H"���rB$�u�Kd�alÿZ�����r(A�N���:�z|��!�ޗ�cj� f�hh�xL��5ȕ|wPJ��<��� G��ԭ��{89����i�4@t��.SS�ƍ.����V��f��{���5REMi��j���kV�F+��7Tu��Z�j -�[��@�Ĩ�J�(�L�Vi�T�A�8�s�Y�իx��Ǻ����<���\7��9���ʐZ,�%�f��1�,�Y�&�e,\ �=R�M�:j� �Z1큠I��7N��9��B����_�]?F���-i.^���\��.�F�TD �&�G\��7��hy���]Iڳꔉ# )�������/�U��#�<}ԃC_�:�e��{H�]�E���0ޜ�+��㏆Ne��@l�ܼ��6�ww��o~E{��B���N�f�W�3��-�L��LM���3;:.y�huD��V�z}{��l�V�4ͽ���u��m�����66gc����c��68Eo�u�Mo�0 ���> �mhW66���T�4�J%D�3 M��9,�D�_?~����l �J�ª>�m$U�7 -Ǐ��,�b���`�13aQ��tW�Ŝ��q���d"��4Rꛔ��Y�O��E�/ac ��e���'A _@1 9ƭ�FBOzr� -'1�:I�چ7�%���N ӷ�9Hn:��֊=t�'��r�������m���O��BU��~?r���T�?�r9������4�C����G��i��%=t|j����֊�n�[5����(y:9����P摗/;����~���<����~b��y �V�ЮllLӮ����@�6�gm|�%%��=|h%?~�����*(��L�А�8�Wh&��C ֠Q�#̶jƴA��d��95����#��`���T �9��P�N��g�Fi -��Aغ�j�o;�!�M��j)��� �Q[�lQW�6�a��H}g7��d��+��߅�.s��fM���B��� Lk��� �m���KYVTI�j8{C��I�b[p4��Q��G����t�i�$��x��`� �*�i�}�(�Nك����P�v�UVa=������A2��Q��:U(�)���F�f L���Ef!�aT��k\�j^�q}�+��T�6���Z� /+;jG����'�%���`��c`��>�"7��z�d�\Nؤ���Z�����Ч�2w ��VU"P�����{�=ps��*����C?��iK���沇������uSOo�0��)��!nfW��v\���!!��@!��2����B[� y��{}��ssj �YI8� -�E�)�*��`�y�TT4$��95��u���+T�R wTQ� o�h -� )�8���Z�0��'�i~q�@�6��p�2�/� &����qq��dY� Ҋ2)�Rt�o��k���L�u�d� �W �'w�j�ŀ;JۅjA��᜜��&�����K�XԌ�p�����ei�ٖ����`f�L�tHW�F't3m=��b���ქCN�T��'wf�p�=�MaG��.ޟM)n�^��Z_�i ��S��A�[C~+�'V\�3�Po��o��ʾmjn�o%/�/uR�n�0��+��Ѣ^��T�W�Do�"�$R�X��U�{m�y4�>$�������YdRF "�X��)&xL-�ypR2%e��ĆH��~]�l��M^�a� �R��`gd�&���̼�Μd�%�]��� �V5<4�����9rGj������v�ɪ -�k�u�Mo�0 ���> �mhW66��+Bb7@UH=Z��(v&����(l롑�~��~~��� u���]�9�E?��Ȩ�Ua�۹r�n5�g�Z��E�t�ȥ��`��} -x`4�9(��@�a���K�Z��[�!HT� A2�|�o�BCE"a7�� �����L� ��#��Fom���]媉���{��S��=�ҟ�g$x˲�ůJ��6�W�z���T���kj]�]��K�r=��QK�cx4�2:~T��q^��K���mA:EֵE^�M��,̺?�QS�%�C���2�p����Y� -��l�� iؒ�坢uRMo�0 ��W��TZ��]alL�]��Bj�R�ĝ@�}�j��!�����c?�ʃ�y��5��SFg�z򔌣H� -�daq� �4�����zI�|�o3&hĴ�Y��c��E���E��3LS�~1���T�~yA�Q@:tYo˂C�����ʭ6�$FX� }K����@u/�������qZ�@�ǀh�S��:��Z��������W�7�;�#3�mM��-����k4pu��w?�e�i���LB��h�M�Դ���\��F��r��[d��~xq4�N��lj7���Kt��=Ҳ��&�v � ��ݫ9�T+���;�x���?�O��N����U��3��q�x�~u�QO�@ ���)�@2XT�+�b���ߐ,ǭ’��\{Fb����� �=ܒ��߿i{�hw -ԕr8$v��iz;�K�j$�4�bg�������%�,yO� -�^*���?E�W� fʝ�5,Y1�h� 72�7U�a@" Aw��2x���k)PP���G��*��3����{��9uh50P,��xF���(��U�)�՞���B�s[�Ԯ�)��W�Q�"�G�Ϊ�~ O&}PO/����J�~��8��:&6�E^�M��<�{8�I����!{g`�6�t����*w���]��oZ���CI#�|uR_O�0笠�Q��(�1�"�YJw��5��@ ��k��D����~�z��g�3���K�=G6W��Ѡ���в@g�B���LZ�v9-3\Ω �{w]���` `�h,:����$,��@Ї�}�0p(��9x;��K x ԙ�������a�)��m+���}�� D͋N�Zj��WE� ���֌��V-˨ ���_���������kO��%�rb��CsZ1��x��TՉ{Wn*����>����]5�Fm��߸Fvi��ۧx�qݽP8�?�m��~"MÐ��0��4��[ �$�%�Z/|�����Za���X�!m��Ԫ'� u��n�0 ��} -� -�6�+ӤRwT��@�6D�3 M���6�m=�����-ۏ�z��DY �CK��T�Q��ޏ�D��a�� a,���P�*��8��u�� ��*%��X~���O]��,�ه0��r 9 �Y�ƭL�M]IXX�������$�v���xQ8�ˆ�¬c����U�0F; �6���KYVT���P{���)�P26;쨡�),�7��o��#��yI�&�>���E����}��8<�:%6�C���Og�w>�$tt�7H�(X�-.]G�����1�0\j����e�Z҈<%ߕT]k�@}ϯ��q[�5�ZY�����}��d�@�fn�A�sD�&+����#9gfn�fk �i!�8�y� ~�&?�q(��ΈT���< �}yԙ|��i^�� -��s�[��;J�9�s�����U�j��n�b T��0V:�k�`x���\y -W9�����ZH��r�b����ͻ��QD��@�� -ψ��R������<Ж)��6E��5�A�a�������:���^n%I�~�������\�(�>!GL�*�A��qY�>9���Х�_/�#["�~�cH{U";˪���5�U��T)!IҦ��iMX,G.�Au�&@�q|�u���{�ns��+#��Ӣ*q �)I����6����������}�m|UB��tƷ�8�h�|� "����X��UP��l�zÌ���F��g�L��7���̙Kȇ:� ,l侂o�UQo�0~�W�C$B���+Y�V��XU˴=�(2���A��m��;�@�*<�|�w���ǗU���Ä3�S�e��>������� v@U��)+��T(��>��A�~��;d$2��{!�%��M��)hq�_蹝����I�����`^�T^��%��[�_V1�� 9-�D����/&ss4$�J���G�GEV���E��w�:C�axe�B�� 1E�"$`�;ۅ��0-k&�0��ZSe(��9(R���_���B��UB��1����}�$;t��\h���?8~ϤdGk��*6l���Z�@�P��r���Ԕ �@��4*l�>||���;��vy]���F��PZ��8�v�4m3�5�����:�I�%D������p��F��#tv#v��E�9�Y�>�٦\v�Agөk��D�F� �VV���w�q�Z��t�i|ٿ�'+�9�S�Gx�.J�G�v�G3��u�+�xu%D�)�?o�{�4%-��̺���5��?�[`c�oݷAP� �m=�������UMo�@��W�!�qD�jJ �T�RE�R���^�U��v�IQ��ޙ��'����{3�ޮ���] �s&�Hi��z�%W������sU����|dRq�z(�z�{���LN�[}�D��B���`�w{}��5�KY�\���䂫�11��#� -��*���YYpU��\w�aJA͵�Ws�(h -�l_�|υnè$��ò`��$DE����;.!u� ���P�6�  �RjYEy�UtX�t�G3�?d�� M_�<��T��k0W��T�̒V� WTy�:�}m���0�E9��j��������MQ� -��b}O�i7�A��,�N5W��[!�mU� ��@�O.������)�æ���H�LJv�"�Q�vo^)���y^������YJ���:��о}��A�rt=��kt��ҳ��VO�6m���� @Z�|!;Uב�#]�cSJ+���3�bE�Ʃ�'I��۾ui=�4�l��#����RNa�n�v��}v3h<Ԅ�dL��+���͝Qa��z ���(�p -�Lm�Gm��@������Y��%8K���NJ+s?�#�I"���z���ۅ�=tC��#��b<ԯ�ڪ�͛w� -[��l�� ��̪�j�vY-�|�+]��ƽ�0�m�>�;���5��#֮m>��l�w{�Y�#�E��(8 5�9�a�"����I2��.�ݝ�������Cw��D����~����u��n�0 ��} -� -�6�+�4톐�vT�m�J%M�����¶Z)������g��P��D#����)��v|?x�"�[a5�f=Cc�YN�B,�%�;{�K�Y �e�a��ؓP��SZ��a��d�����Y���Ĭ�aPi��2� -�����^+Ş]N֡ �N�RZ���FߦM4�^�>gT�SV�U"��F�C�e�X�R�d�����ɽh�˩� !M�~M Fn�]��Գ (~1E�Q�����m��} -��Q��1���Z��e~:S����?��F�3 -��ū���1?x�˯�Z���~;�zo�x�~u�Mo�0 ���> �mhW�i�i��n���5��M��ن6���~�e��ۏ_�y���� Ӝi��<�� -��~0� -4����Z0mP��2��� -r~k�<� -�[�z|�]'�����$~�̀w�8F�>���s��r�`$����Ѱ�Q6�y -=��U�1� k`��1twg/��c��T�N�0��+�P)MU�5����!�[�"7qh�Ա<�.h�ߙ��6M ‡d43of������j(d^ #�hM��̾i����,���I�"��Տ 4�M!��vg)��0BN*��e��JU �!��h]N&p�W�{��4��l�(i#ٜ\�t�n�*�QN9�3��j K+��IeqDJ��FI3�����s��.:D�����9x��!��0��mNs -�Y�&~�h�t���An��מ�A�0F�9�� c��[�mJ�[ eS�ͿJ�@�����RyL�j߮ؑ�5�� �I���gE��Q�c!~^���B, -Yi_���������m��$2ހ���-8i�k�ݗqW�#�u�AO1���+�@��Q�W�x�D ܀l��M�ҴS"1�w��P��6�{�i���z��"Q���e#���d����,S�&�Q�Vz�ƒ� 7�Ƽf�;{I�Dk�U�R���� �IU��}e�nQ�`�B�Zz).E7��-j)���ڑ'���B��˜` �@� �Iʷq�l�{��� :0��B�������pL��J��(�ḛ���pJ#��hzl�_%�)��y�a~ �{�Sȉ��q�x%����H��� -�>���%��-´�a���RG�|C쌂iq����%��?�K��C�������>��SMk�@��W�� �8 ��U�z�po�ki/�Wbg�4���ݕ�Ȯ�:�0o޼7�u��(0���)��9g�� ��g_�ȨR�r��}�,��<�nV|`ɷt������a���f��MAp��� F�#��z>�۟ʂ�|�Y�-|��X���׾�iw��a��ɨ����k#;�9(4�@��}�m��:�[�<��� -4�K�&niplNꕵ� `B������|�.��e]U��@Bݰ���@JJz��_�mCzFMg�����\���$qIa�b�x�u��~�E�]˲�]Q�n�!ڵ�<)[��BȲ�7a�\t��l)����a.>�pZn�p�� �z�Ͼr�{MW7޸6���m`0�%|���d?C�6���&ma��gM^�qK0a���S�g�Uhs���q 2 �X��Xǎs��f��}��~/��!o������g�G����_u��n�0 ��} -� -�6�+��i�IBb7@U��TB�8��ƻ/I۵ �C+����v�0Q;�i�҂SFG�&���H�=�8�|��Lԫ١�Ղ����\d^ -�$6��Z�.t���+˱�$�����"p1L��3 - f� $ÐW6/����huI�C��Z�� 0(��t�wu��6UL�=�������P�W�S��fG�F������SQ�JhOk™����_�,��~���%����T� �`�F�����{�_s���1���J�����$~tݜ���z�)�j}����X?'��U��֩F�Z�2��x����?������^�b�+YX����u�oO�0���S\ɀ�_2Q0!� �; K�*,�nioF�|w�necJ_l��s���n�]�#�iC }��P��6�bdOEB| -�]2'\P�r〮�G�OE5#��LpM�� ) d�TZ���i�c�A�� pG����%�&E -��V�1OSw�-&/���y�B�N5��:M�2u#A�-|,��M��4�qO�M�PW�:�U'��H��)CQ-R ���-).����@��Lz^�#$��}2&Ȗ -]��J�/x+U�sr�Ҫ�6\�0B cF�������)�<�7�6 -����ۆ��u;�_n��M�n�n!�#�^�l]�7�����P\���JGXJ���T�h�;˖�"ݨ)��m4���p�b�,m��n��m��1V�K�o�miS��������;=ۘ�_u�� -�0 ��}��������` ����8kW��w��zQ�!�/�|��MC�b�cKf�Ь��IΘ��������.��$����/fi -Ť3��f�� %*�8O6�f��68 �� |i׎���S��IA�T�6��(N`�>��� 9��|�WiY�?�ɿ�R�˪��D!M�3F�ž�U]o�@|�Wl��`D���&�P�jHPpSU!��N2g�n����އm�!)(�6��ٙ���t�BDØڒ(X�.S*��N���dAeJB -�y:&BR1�L":��U<��Ho��ԋ(GvϨ��TYv�8&�����ʦXJ c��T���<�Pj�m����m8}$G%�I,�}l�i6�YG�1y��� tq��+��HU����Z�J+[UU����d �������;�a�pU�,D � iaC�$W|��.6��*�¸��e-�ϥm5�=f��kkhD�܋A՛� U�QĴs$��j ��A�3x������6��~���D8$�[#���;�����G?g���y�i����?�/U`�%S�߰��Fqܚ�%�ou�Mo�0 ���> �"6�+]7�mW�Ĵ EU��B�8���K�~�rh�����q��*w2F9Ql�Q� �mhW66���.�*7 � U⠡��>'M�� �Ti^?~����6PJQ��}KF ���H;~�$��Z���ٶ����,��R.�T�;{�œ���A��ԥ?ה�&�k�e0٣��o4�𮰨$[Z+�H���lWTJ@�c,'���� >YI� ��`��f�R�@������& �/<�Ю[~�B���j�C�O�ht�8�s+;e���>����c��cr��F����S? ����Sv#� ����U���=��X`��ӛ��ԏJ��%���uR�n�0 ��+|@*��Ю�nLhW��n���f4R Q�l��_����-�F�{~~v��*K �Ql�Qq�9�%��S2�"ALKB,K�$J3�Y �Y�-nt�A+�5��9�2vB& - mZ��=�4��Q5�~� -�GQp�G��s��U�€Z`z�5'��뭿���r,�����oH] ۦ����〰�ܰf�(r�X��>�����x`7{l�9�Ѐ��ΠMy+jiR�52o��4��Q�9mZ���B��N��)��z�@� ^ܮ��ɤ+��'Ӗ9���?���B]�O!k��A���.� Wf��/�� ��Ws��Qֱ+� ���v���?�N{Q��b����륍�K� uR�N�@��+�@RhT�E1�+!��fَ� l7;� -1���K+u�dޛ�^w��і -�[�O�*�9,��vp�$F퐬���Δ#t�i]�j�;��%"E#�E��p�h -���39�,�ɻr'�eo� ���@6�$���JC�/~!���Hb��@;0"v��-mb�S��O���9{^Еs�K=��.k�H�T Ym�\m:D~�F"�nc�/"\5&]�1,_pz�('s1<ue:|�Q��qY��C��q�d8&� ��:D��!�0j���w�iPK_[�p�����Za���X� �ۑ���� uSMo�0 ��W��TZ��]al����2m� D�; M��9MJ?`9�����s������L�GԢoH�)%tQ�L^�qH< �0�8�j#�f~��fI'�|a�2�1�Ҙ�'�� ������1L�Q�Ĝ��[We �U�y -=*��vY%0�PR~�\_o�H�N�IOU�!���.R�F(���&�ڳ�TЩB������N����){���B��� tV�Ƕ+H��,�r[�G�Þ�u_�L-�$�Z�;~���˵�M`�����G��Z��u�45��h|C�萛�7n�������0-��V���Ȋ�LInߐ�߳���'��Ê��m�� -�@ ��{� �E�������nI�"=h��%E}w�-8H3���/Yo}����1М%X-Wyz��j�*�!�� ����B�����4y��$.��"��� �V.L{�^Pl�B�0�T� �2I`w�0�,���G��@���+k�a&=I'�CN��R�#I՚�z3�T}�}TMo�0��+��$�[�5Y��z�C�JmOM��6��X��m����� ��ƞy���_W� ���d -��)��:��XG�`j�r����cJ��m�w�T��A1N�7�Ĉ�¤4�4�,|�aSr�� E��8�gf\.�p��)MY~qA��7�ꂿpT��tQ�ٗ<����۰��Hw| -�-��ofk����ް��Z���40 �l�Ra�֘ĺ@�!���L������`�<�����6J��*�ҡ.�ǭ�O@t�N���ǧ0�himt��'�L)�1%�j`d�� �������lм�F���,��nͽ�I�L2�/�&AO�9������јc"h����.rF�/~�v�����Ыv\g��y���+0�������-h��o_:q.���KCa���n�;.�;���+�}��06T�m�7�h�Bj����9'���WA��8�sKj���m�}qc���A����u���?}S]O�0 }��än���A�O<��>mS�5���Q��E��I�.� -���}�}l_\�A� -Yp��l�gt�$ -wd5�����d����x�lP�_K75#�R���;�C�o-� $H98�Ǥ����������p��&�>���Kx|%�f$;`ia|V��j�� ��L(ʵ�o`�7��tw�*\�h�Y���XO���Z��]Ll ��e���(؋�^>k�;��r-��`G�)E���Dw��R�v?}��@�|�� �]n����!���x����TQ ��E��0*D���M����Þ�|9�05��R��i����&6�`#��e�Dְ���3XH��� �Gp���?�zږ�C�=��л|%�j�B�T�<��=|�#o�+�`�ֵ�H��ڳ�˖�?�zv^s���$�_ɼ��y\�%�J�u�MO�@���s )�x�4�L 1��fi�X���4J����n -��6��gޙٽ�ԛ -�Ke�Olu��j��zpEFm�j�#L�V��`q�& ���rS�U��(q�&��N��� rh����7���ZAI�FM}ú��N���D↌BA0[�jC�cڔ�1� ��5�w�`���4�9ЖI�~����fL�P����ҋ%������7`=�a�+[ԕ���0�D$�?d��H�� -6�2&Jǽ��N7Q�qsR2�=���Qk�Eqٵ����OeYq�H��psO�Z)��\t��-5��%��cX,�v-�ܨ$�FLj#�[���2���w>�O�4�"���wg����(�9T��VKX���.;����|u�X~��������¤p��}T�k�0���;�ŵܯ�����wt�m�c�jl%�������Ĩi�H���Ϸ����U��$'��R ��X�+*���|4⤠�" �ծZ!�X/˔�T�p���+���w)�e� -܃���'I���(O%tMGG��t<��7"�q��B���Q�%k��f��W��1$%��p%'���J��!eYFd�,��=-.�� -�NS؊���JO�̪��,� �~���[��l � !�y�X�[�`@�@rF�zkAOz��x�C�L3��4Oí�'�DI �N��SOlY[}SA -���e������ָ��9��tj���Ӱh�N��m�Ӂ�o����\�����h,Nf!�~`QwS+�}��LGr���m�������U4…-�Ȩ� ���� ���'�������x&���ܩN}0�*/Ԏ��+;� ��h܍�:�-�5�q��� �8�ㄴ[�u�ԍ>F��m�z�7Z+)_ok��<U����i�^�������z4� ���TN�.��+��T8�)�l����:Fd���J��ԇ+=��XX=��F�����Y�i� |s��Dё��nIo����;����� -Q�B�~�"��Ї��o"pn��?�q���ڎ�OuTQo�0~�W�C�@�躽����:U[ZA�2�!�۲�R4���ΉKh����ᄏ����EgR����uF$n�����eg�jIVp�Y�a��)3��e�R��s�C{iOY0��0�|��q�Z K�_ p]���7��\��j'�X{�U��;.=����nT�q�L���?c� gm������$���v"ρ�;����u�g�m5�ē�1�2�s�d���F| &��k�&��Z��,�u��Kg+$��$}�f��BYU߃i�o�9�ұ� �⑧��^x�J6愒�Wǿ��w�q�Cx?����R�WZ�F�x��v���u)�=������̈́x?4xɁIw7��w�Q�O��i�ed��j�kr��J������(Eq��Z5� W*ǯjz�uT� 3���I]�Fi*H -����0ԥn�f�U�kW!}�]�P��,�7�~c�5}�{��T'�3�%"]��dh�wϞg.��皬!\��8 ���fA[���5]>1�s��:�Cj���HXDDu!���}�K��b�$>&]�r�:hDgz��G!�S�?uR�k�0�_qBmq����c�"8؃J������ ��%i��syh!���]��e&!E^0�CM*��I��=D� �D-GXfrɔF�YRܬ�$�W�1��Lk����#�H5���'s�q �/�j��(�D�S~�GV�"�0���PY��V�K�����I���-dZ��0��9A�{�7��d����PG���qo�}�:A��b^ �R��M��v�����gt�^{��g%�{�$�Mk�.� =�e|����U�������`�B�d�w��i�P��'�@���z`S������V�H�jg�X�F0�q�p -�R֡-� t��1�����͒;S��b��5�#L����9�u�OK1���s���k�x� H)R< 2ݝ톦I���-���dk-(昼�{o&w��PS�i(ʾ�w=f����ܹ�;�����Kd!~[���i:;� e&��Eڢ�سW�>����Q��^[���� �)X�MA�����{攉���:�V-�2]Ģ���� -�� -�w����ˁd�2P�t���{xT����M�Z�xɗ��*j��PBJ�.��$�1��Y��"?�-=ն;�x����Z�z�>���:X`�ŪԄ ���h8��?�����0i���y�9����XQS�6~�Wlo�áG)�\����t��[.�Q� ѝ"�� �����d;���8�i^�շ��~����y:K!�X0�����vl)��I��ޞds4)�nf� �5�� Y��m3�P �%>O�Q�|�|��(y��3��f�{���R�,��g�O2���ݳ��C��B#��<��r��C�4�Ӛ-B�5�j��s�{�&��0�dl��0�JR -Yl�u�> G=��v�};���fQ��}Y��W$V�v� )0�i��A̸���޺�k2�����fZ� �*G�����Sµ���^k�����zp�� 'k�L������We��՟�W�5�lU��*� �tiyœ��'�����y�2a_��e�^�<�`�@�I���ɻ��n�]��mE\�RWڬ���zijк�rkS��-���Ye uB����a�����,�� �'������9�v���R�wΕ���� voz�Lך]�v;����k�<~��*�}-v�~���_�K1���֒d�m*I�v��U��͸u �\��~�g�]���u��?��+���_�{�x�o�2�x���3�$�oA�K �kR�B7b5�/� 1[��{.Ƙpy�>���"NHFU��F������Mi��b��ċ�t8z���0g'��]�)C����c��2�R�M|��3��ª�[<��s�.����!KJd&ĿE�/��e��� Hd��9�hg�� ��엜�~��$G�-�C��vI�����q<��U�3�UF!�Gp2Z�n����d�T���m�)e 7��vk=P�qc]wհ4��Ə�!xԠ�H� o��,��euk!<����05i4�n���Tf��z���*׌��Gx�6�I���HG�A�ӿ���m��~HG�(@ap�� M�Q ��O���9�T�A9΢j���UF��*]�wO���ϸ �&2l�d0t�>Xo���FGn\O�����|L&c_$q�Fў�}s��N�f���}Ôi`�u�uNh�Ӯ������t�!�k���|nö�n��-R<��Y0y�R�����q~(���*:��d����Y�O�F�X:;���Phs!TQ9���S(2ΆXulkw � -�{g��^xi!%ޝ���xv���t��% "�Q� b!�S�O��wvbCy��\��K�q�`5���,���G�n'qeK�8n��q;�|�;�eI�^s�N���+�wTDp�,ۤ����C�������/au�l64���u��P�I\� @ Hǵ�ϟ�7�LF@(XN|K��7dWY�&L�%IR)|`v��>I"b[5 ��T�)#�^!|�dђ�SIH��M��酀(��Q�Zq*H���.����(�\|���'��`tHL�����J�۫F��g������d_G�L���)XFy�B���lG�L��( �*���b�cp+O��]�(�g_�C~pjGhv�&Ϯ�q�}�w�ğG�M@N�Kx���Љ1~ S��� � � ��eb]��̎^�,���K<0� ��x ��lx5,��$���# Wo��HD�C�C@,������2�C����S����ȅ�qFE�b�� 1�{���v�HU�oxU�NF�Gf��'M�����vX��� c<|�JO)K �H�L��X��Q���\�q@!�^�H���:�<8}�be޵P��jB��O�}�{"R�S�ҦU�jՇ�X��Z5h���|��P?Xk'��W�=:����������s����ȡ$�/g@�z$�H&�>�Qa�d4N0��I��R;uA��F����?��'�;B!|�<���|�\-�M ;����R��~y�Чo�2\��;mR -�,���T��nl�`��p-IL��.+4�̞��Ҫ ?`g=���� 6m�_�i�ZO��;`�ֳ��J-"Z�� S(� F��QRz��'v���j�6 �R���`�# ���a(ꑡp;��65��;�F����W�**%�l -Z -�|8���� -KNN;��;k��U�=7���M��[�ڣ��i��Qu��vgN�i�Q�Y�����&.��!CU��ݥ��X���&����3%���c+)Pil��a�z{�te��$_w� �_]; -]2P��|"�<|��M�'�������dT�P�ss�iZ�tl'�T1��ǣ��l+-��C�& ����H�(~��φ��6Z ��\͇���EQ~@���^l�A:����%��MA"��N�O4)�fhN�P+��缑�&O$G���l)uN��Rf��⦤�4h������b�.���j^E�����`]L�Ѹ�*W��g�߯'��j��kE��j���u7��p1:72fw�wF~���&���B�u��l�{� ���V��]������7��Y��+��Y,�t�mxaG[��s�_{��d�f��g4� q�y�cV�{�R��II�/�}� xxіّ�YC#x���um����:�)j�t�愖 M��bi�'���~��<�i�[>X( �>vu3���P�u���*�aL�ލ��h�T$-g��4�})�t�2 -�f4���J�M��O�.��� �����DNTr�4�)�S9M� ��6v�@(yD2J�l���~�qm�[�e�+q�{T�WK��m��sWn؎�� MCDK6d��S�S@N �s�Vi{��!��SL��j�v�u��"�P�3�=kcv����8��A����3`7f��^vP1��1�fdK������Kg������)����UMk�0��WLa�m�5�l ��^�@K/I���:��䄥��F���� �>�C�y�yz~���Z(�Q f���^�u+��|�e -o�i� 8��3�Fh7ڙ��ŗ�?����K� -����|o�6v3Y�t��V��s����L?^v�*����}-{�ENa��Rl%�l1�m���"oa j,��w-��t���%yT����gTWמ���3� P�r^Cs��-}�ϼ��l�V��9�19z��P��~�˼���R��U�lQ����^�σ��~Y#����Z[OG~�W�"vD���I(�*R -�q�A�xw�GY�lgf!(���m�W_��e�ޝs?�;sf�˻t�BL��H�QZ�H��mJU�M���'3�RQ�8M?��Ļ�Z)81JrM�]`�4�3�u��1�TN���� �����?���]z��B��Q����tAR�I����d8���������G���.DS���q� -����B�I���3�aI��4�)h�R !��[�v��+�"p� ��l��ߙ��AQA�Yi�yG$I���E;� �1$5(ғD C��������{m�G�+������^Fq�gE}����� ���n�&I�) c!5��~�)d��B6 ��SW���o2�,M�q�� f�����ytrz�w�wa�S�AD���R�1X@�$����S��t&�M����(�%��N-�U�C�� �-�����R^0��������%�̊ȭ �-��q��U��_�ibU�wKO�z�\����޶�0�5�l��1zl}k��jxU �ݔDS�TL1پ�xL�@o�U���"����K+���5� W��U�N�e�mm���\�}�Q�T�t�*�V"T S�_Y@��)��y$j�Q�x�q8(1�R8� $��HUs �u�ۅ=ϣ�����D�`������%V&���fщ �S�8����-��Ao<�]Wȍ�OL���O�̜cK��:VY8l(�&1�j��r��o@���z�vP�#*ܞ�ъ����O� ���K�Ҷ����� ͵J��cA��Jn�OZ����j� -���Wv���r�I:�i��=�(��8�+���Ҭuβ�cM�Q���W4����Mc���T� �2�Q���*dɚ2�����[ʯ���l��}�m��k�t��K��7�?}W,��hj����\;�1��х$ wM$ɨm�)�x��s(��B ���I��]�W��Z�~UZ�͵ �V2��!x>����'���Wj�V�A��a��1 -��U�|�BlXΉ�s�ɚ�wH��G~���7�ce��_�v�jh��J�|@�j��2�7���d*#I2��-�Qq��|a8j���JÑ�%DS¯������,�N��8�f~ng����p �o�y��І�o��d��N�����=�d�����p��/���yp�Ogjxd�]/��}�t��r�.�J/t����v������FG�W�Q�>�i��:�=������p��i�~�ڸ5���翟)�)7J�硫j�S�������h�αw���|8߃C�5�LA*��{N�Sq_��g����R�*y\�iu�-�B'aJw<Z�be[Q�T����r�T��(��C>��M�ݽ�٥�]M��–#������4�'���L�4lo[������֣�(�͡��o�x?`d��1t�}�˸����]6�e��)PԄ*e�GB�^"|�#7�[���)��4�2֎�s^7��)��6KK Ѫ�S� �V��²+��a��U.���{����OK�0���s�C�����ы,�x��L��m&��"~w��A,R�\��{���ܹ΁��, +Ϥ~�C{�� ad����й�$�Tm���G��Hrĩ�Pd�! �M]� -5�� a�^���,�$���&`�G�lA*��X��~� ��iX[3��t�[Դ� � {;b �fT�\$���G�rτE$/`[0��a�ڜ�; �'�ý�����+�R��G��A��b)$j���3gS�<��5lS�N|�/�UM��0��ẂC���X�j#���U����M&K����*�3v���ka��Q���̼�{��7M�@����u$s�t��������5�F���� �H<+�C*�lj -\H+�����u5�' a,��������5��r��f�ZҰ�E�X �dn��3ߺU*Cx�ւubcA�����W!�K�q'98� hi���� Q�E}��g���L a��������;�p�M�R2��չ�FoY�w�`$Bf���?��}��K ���4:��u���>A�_^�*��d�Η�l�H��d9~��f�%�a�q%UA�23ҕg�qӮ7���{;��z���!޼���]� ��s���h��_�y��R� +�(�Q5 ���s�+م�8�� _0���#VR(��IY�a�H��t��Q��6k�$S�kOV�4�.�.���iبGj�ė���{��t�pݤ�_�����q'�?f��?����?��p���M��,���@ʩ����mP�N1 ��+|�ԇ(祠�;�qBB٬ۍ�:!v**���vEA���왉�����Р�l� K�N����w���;�hº�k���sh�ճ��������;�W�~a�(o1���y� �i �뀔�`����G�JiB�[Ov@d>1���u�Uɢ��z��YZ˄��'l�PĴ i%Y�DŽ��(i)r�Ǔp�Z� 3<�� �� 5 W��o1םw����$�T���`T��j -=�Ĩ������/`��(���d<��7��u��Ծ{4G�}R�O�0��x��WŘ��x0^���{���.���w_��1�@��{߯���z]C�R ��'�$�hW��]�Yf�}-$�˺~Σ�x�%�)��:F4�7M�����%�$���F��u��� �t��)k8 �l�a�b�h>cv� !���t�qb�a�L���&4�����'~�6̷"��B#S�����%��6Q��L�y�����1\�!����wM����1�B��JZ�FI��YOx1BxĽ���0K{�gG�q~OHPuw�AkL~;N�p��V� �űu%�>|Q��Z�$Y�'��T[>�Vtb�)��=v�鸓�O[`e�:�E����98�I|"�rߗ��:�����20��/*� ��aj��Ӏ� � 呵K����Ϣ�?���H����O��0���s�����M��J/U�X��J�1��j�#{�m��� $��*�=�{o���};����:�JҖN����,I�8���DX�������f���)2�+j7��"g+��?�.[�"g}!����,�l���1܂rn�(���=�u�9�)W��~g�*B��� ��4��'��W�c�2��>�'��s���% 0�5�J�w"����Ŧ��(R�%2g �|ae �$�2 &>�ِ�����`ij���1��!�pU祒P�Z��}��F��ZRz�6R�B���&ı�� �.n��t�C� �"Aq���I��8���k^#�V�ftP#��� �����K���[�Ӷu �i�e���ju���{�%3��X{�=��Zq����bK蘜�wݮ�'N�D�Y5�N#5P�{ѱ/@vi���QS�}�?�h -�AU;��o���i�c�ݬ�_�O�o׎� ��?�Ymo�6��_��*N�}u�A�tV'K� CS�D�ɣ������%�$Z~i��@-�x��+��_N糹�TK����;�4W������^"T6��W���4�2��4R�L���"�k�Iͯ2�b����:O��������}�m�� �l����"��~_�XO���t7�� �iv6c�H��6Y&�����Q*�$��� {�� �;>8��Lk���{�ǃc"��ԪЪH�'�Du>�4�ş3eg ߦ�K�G%R��u"cA��0-(/!"�ֱ7j��M�r$P5:��b�LFQI���B M֋�1EB��.B� I��V��x̭|䈋=�s\��3��+�HM�"�b*�L��,�SF�.�:��D�vۦ%w!d����Τ�$Rf��1D\� -��8�KNHÂ>kү#q�~Y� /���¯���r� f(�$��Wf��!��T�רPi'ξ�����E�u�k8��\� xxN žb��u1˟{�1�I짹k ;"S�I�C1]$!E��]XO�E7�B�7% ŗ�=��-��ۙ�߲dĝj�s9�s>=%.�v<3���h��?D�%�/o�o�� -��Z�qN|!g����i��e[���U3��e&�F��ѽ��~=1������o� 5�M�5M� �9S�s1ei�o�B:�F�09(5��!��ATI,���"���� - U:xݖb�8��H�eN�@��Z��- m'Ù(X¹�a��Ï&-U�,�2 ��/�f��d4�*�ަ?�t1�ܖ��t�qS�mg���$�̊�T�^ �� �;x��(0���"M2��A�Ut�����Pp�GCv��k��#��hmËlK3WnY��wû2�\��Iy���Qp�fcx��;W1�֦�Zy٥���o�e��j]ϡw������ ʆ���‹l��֑Z�mC��|�[��u�t7eLF��)�_{���Q$�Hk��� yy]��hghw v25�0 ��R+��u�� �� �U6���zw�yt>�x9�Y�*4[컀�_�n�g������E������\� ںU�##9�r�7?,aճB�DW�N�Bޢ�Ң���ŵ�U���]鋳�N"��G*�w2usqT�Z�8�~�=��B6�6u�E��ι��oX�TZyo3�QZU]�ʋA��O�KB~=���~~;p���8�"���x��./&ե��?GvJ\8 -Ci��O���x��N�8<�q�)h(t�"�4��&O�~��BƐ����n9Kc��ܤ��ʮ���y����[m_CK�5j �"'�=���9m�4i�P �^u�5g�\��w>7�g�ҹӵA6ܾ��c~mV �� ��*����?c���(!Iy���yY�;�[{�&�9:��Ĵoũ�K/Z w������9v�S"#�5sA{ 憲"�h���proS6�n�֚�����Q�-�#u�2q�6���n�1�y�(��7�|>h�e#��`����/b E���Gw�ȾEd�J�>�t�[>�k�+�|�N�1��[r�Z\3����u��#����n�p�6c�(x�7��7�4� j�6e\�������H�7~���e�p�ި���&���Q;qةJ��1�n�� Y�S=����_a��u��)a)�SH��a���ɟ{^E`@n���9��u��]���n�R��� ��S~���@��\���j�`�_��QU� -��Sɚ�+xʤY6VY~�h��Qގ��\U�3��s�Wރ��@�J�.�?���<�[�ۣ��vcc����rk�V][�����޿���nA D�� -)�kPjJ�&�"�E�eV��]�{H���*R*� 3��ۺ� -�# �|��K���.V�%��=�;��E!�.��QK���g�yLY`��L���� 5˶72�Ja,#R�c��#�2�1x��kȉz F��E�"|��dw����<�:I�4Ÿ�o�_��B��f^w�#�+����n��R�N�0��7tH�B� 0 uB��Fr� �����L����(��8����s��}�Bma�Z��|�V��,�����F(_* -H�����&({�@ֲ(�ŀ(�%hW!Á�3!����o�"�Ao���,q6 Bp��M�E���'�;"Gke+��a�10� 4�C3V"B-��7�e�$H���Q�*�&�\";���4���WNB�d�^m��ǧ��-�:��e��C�r1���g<*N���S��Zf�w��g�Τ�j�*hM������}�vw��PG��uvʧ��1��WX�$�K}f_�T�n�0 ��+x0P'����f+0 ؀ ��ڢPm��H%' ���(Yre7��C��{|䳯>�u %� ̍%Y�{{l�l.���L��Vۺ� -2H�����Iß�4GO�U6�*7�0�1�k�l���*k�/�=̀������B���IcAW������q R����+$T,aq�qZ�{a�p�;�� �D� -D�C-��i>�b�����I�U@���p�C=�T�׿�t�ƀ���g����䬓��B+^PWXM+�Z�V��8�c?�)�ak�YΓ>H��c�g8�rTrڎ�+��t��ǃ -l�Ԯ'aS�J< ���tX�DM�^s�ؽ�+N<��:��FPu������\��pl�ܕ�Z�w��M�M�~����5����B���Ԣ�a���5MJ++�] 6�͸8�� N��0���������հ�������Y����#̝�^?��O7�����r����;_��$�^�T��z�@��Ɏ+��3ɭ]:.��Sv��| \s�k8?�䔬�m�d�O�����A2��6Ul�b{�'�{� {0|n*��":�-'F9� � }i2ʛ��gk��x>Їi��{;�8���0�MW��2�0�����L�g�}�w�6����+xsfG�\k�I�/�ffe��hG�4����dV�-�voZݺݭI�w�����$|6�/�sr`� �B�P(>�ǟ><|��tt/Fw�u���r4_����W_|�_��Y�V~y>{���q��Ci�ϓK��MF��\/�I}��;���xa%�����q9�-��g���7��������z7��Ϭ�?��>X����[k:[Zob�~4}�����2~��/��Y���2��W�l[�w��Cl��M&�������\���������hNE,����0���Gk6���(|��)���ٔ�,�Q���&�e,�����]�X$5�w���2�l�yOނA�ߧ�h��_�MF�E�9ϊY�ӷ �G�S��,��������J�K�X�w +��,g?�ӛ����7�ɫ�������ז�_�Y��dz����$���?-���r&f�Y����i<��h2~�bSJ^��S�+rh���ߍ'�#�V���u�ײ=N�_��&m�����-����;7/ήo�J2�����>ޟϦg����2�E�€�T�Y*�I��|>���2��}9�x���Oj����������ɰ���������3����/�O���/5��gߜ��^\���[�G�'��W��7��O�gJ����/Ά�GWW-�|����Tᯕ -_����MZ�����y����^��~W��8���M)����� �^_\���[����^�潼�ky��Ά��C���?I>������뷧7CQ��L�L�]V���� Uz��j����}�*�$��.y1<:��,��>���#uM2��?�V�ᯧ7%��Nv�������*�I��1�&�ᔯ���Q���������yI����+%����j}�&�<=OT����[m:��4�Eã� e |sv�Bӟ�av�h���Tߔ�F�C�Kt����&Q0C�fI��ZP2tJt�:h���.o�|{t����e"� -�D���U�i��<�c^jn���T����.φ��YPy�5��e�~Ҍ��R� -�ٙ�K���%mP_��|��N��TP�w�R�W-7�����ë�Ʌ�y”�N.�\�<8�=~}u�}2�R�@i��fR������������ϯO.n�gE�&��D1�k�99�Ҳ\/�J�fxvvzy}*���+y��o��_�?��>��������]���/��?��+ ii�/���Ϭt��9������X��@x�轛��_Fġ����Ӕ)+���(uL5�r�#�bﰔ~��ʥj;T��7��ޱ|1�Om��7�~5GNI*\�I�W�S{Ӝ�D>�o"ɿ��"�H�[LұiW���OH��4�m�܆��m�ö/�ij��s�>w�s�>w�s�>w�ƩPG&������K����W�R��`������Q�Q�$\��N���Шu���(�(�) (���P�;�u����8v��1�!f;�l���$���b��0{*��i����2 r�����Z���Iy�6l���/��O�ܧA�'�\������O��Oy2�R3`�Yc)�H��cij� ��t�O:�'��d��HG�#���rPRJ�l�������1 y H��h�`��)��hR4�[Z�d�d��n �����jk{2*��g@! � \�!"jv�R�J��H��d- 0�k-,̨�y��:�C���R( 3�0���&�A��[�B[!�Ŭ#MQ����$$�=!��8m�h:�[|JR%�u�wPߕ�:���h\9�<��O` �љE ���i�b�L��ppip6�95��t0�_�`Gڊ�yi3*Z�$�$�����j��+�K�[�rlA��qPT�m�VU*iim#��z�$�h�b$K.��F=K�jbQ�-g4T͐�_0y�a \ MH" !}H��N��Y����Dl��l��l�[2�=�$��TCz/���^��! �! ��.0�{PRnʕ���0R,�LJ��&���l��#���*.<�ts[a> 9ik��:�Y�����Ox���T�G�i�b(KX 0�L��ZK��Ҍs���JtIR\���,�'�P3���5BWq�Cjg�"6Jk��ur.���^̍�)UoM�35q����,�� -� -3� .6X iN�AEA.��\d� �,L�u�-�eSHBQ�����Q$B� �� PD��4��i|R%1�S���"�h��G�ځ�=@s�*��S�`�<��ї�ֆ� �$�!���! .r�A-��cc�%[<��S��iI2����]h���B�z:1M���S E�i(אFi�C'Y1m�f�ջSx�ws�(�e}?W�[Hv5F~D|t����*�Jx-�T3%/uR._��璐_����=�VN��e�43٥�����6������V�� @��L��LaٸNKdr�����}�x����Cp���A� � m�� ����U{�s�p��@�%��4N�#,F�f(�^%I\Rŀ�����N���J� F3��E�T[�d�g�(��J�ū�dX2�����A��������ż%�' A��$^>-� ��Y�)�e�:&�;�H��ܞq"��|K㇆��^�ΐ�ԝh���4�0���.-���|Zm�4eF4�}��T�adPY��#�����i�c��8��\8pID���._6�q�q��o���+�t�8u�ޥ"|W����5�QT��{ׇp���`�k({0�9���{CZ{���b��˂~�� s:z� �$S�`Lj�޻A��*��0����Ť����B���I$ -�T{צŪmG��S�n������ O�YA⨈��'��>�I�����w큇�Cv?�YP\�#U� -I��yB�� 6m�H�(�_�{ >-�� H9|T����� u�E�@��^�]BcڅbvU]��B݁��ܤ��E�]hl5�\y ���)١e{�m�4E�D�9c -�gPL^ڸ�V$6�|�5XR7�>�F|ZZP��?�;h��*��1.��/}؍��&��E}R�Y�������>࢘�$h%�Ҵ�P�#�Ɛ��>=�&#�Ƃ������O��� �I(��<�����,����ߓ>���T\)G���l���BN$ ��up �������&]��@]`Hq"ũ�"ڊ%MI�K�)����)n-�%"ނ�H)�������;�^$?z�b�/�М��U���9ȥ}� ²z��CU;Ř-X����B��/�a�>~��$��z�6�k��<��&��n�Y�l{� ���)�2O0̅����4K�G��[�Vٖ���D+�Jұ��UR��i�Zv��<<�)X��"��rd�a�ֲdiA�����JtU� ���S>�c�� -�—����u+r2�G����x>f�<�4T�Q\��c�X���XVs��0f�&�����ؖ%6 N�� �U��O�6�%�&��c�`F�b���U.OV��� ���6�d�2U�`�����c�<ӕ���(��e�7ޔ�.��K`�y����D�D�8��(��Q�s�o�I$�X�E�b���Z"PA��@ �Q ] � {@~E �.����C�0v��i���'�d�j�%� ��>������+�� -Re]lA��q��j 3�.j���%A�b�^����bE)�.��Y-L��.�M�<���A!l\Iv�b��|��n�Ű� �^��$iu�Kf�7����t1�&��.F��[vq6��G1��.�3F��W�b�_ۚ�Gr�Ŭ�Oc��zGK]���i�T\c�֬+t����,i�s(d�V�����b+6��z�����7��N�tm ����1�ư���*(�|��r3�4v!w=� ���1Y�,�+��-���<����zL�и�"�-�|��� ��>A�ºX��w����r� z�/^`Me©�H��gRt.�>x��U�X�\_�vֹ�\V\RMą���C�bxF��f��zs�^�[�����9W\��坛�@�\����gI؄�R�C��� -� ��h�~����8�M�~��Cœc�9�7~��Wa�/���@Q&��] ��R�C�ÏsEn��(O:�������a�\�� O9{ݢGt����zG0���䪤�\b„�)���jP�X��e+/�˧�Qѹ^�7U�8�>-|�2߇eD�O:�\>3^ =%o�� ��2���G�����^�&0���@��oD�%�@I3w��^o����e����"<�,�Քv�_�cu�c���H6 -y >����T���Wi�fg�y��EYM*SgP��-#����+p�)�X�ڢg�{��B�rW�T�]L %��b��N(!Ҝ����D29�>���`��6�F�I��ϖUzn�>+�u��JD�2�I;��CM�������xn?� =SH�����WՄ��� � �Fi�\�)�t�$�O�&�Ѣ�4�/)��1���z��q����)p����)v��CGI}ijP�â��1%�)�1iB - A���#-�aW���Q�Rl�#�ã���TG(���F�W��*Ո��~&�H��!�s9� �"����`�����;�H!�$�GD�"�<��yi�U� ��В�ǭS}<����/����F�'�a8r�`-���jRQ ���V�T ���i�I5���p.ƣΥ�1-��gh�~BD�WH�R���i����8������%�ʬA5�`��|9�3$#�B���x\9SJV -�[B8 e�DS����9NM�0ZN\�89/� -=H&U���T���>#T�48�˾5���X]~hf���-�,���)^f'���"��� �ߑ5 v�j#��z�i �4�y*��I�x�y�����@Ȝ<�e}�������!Z�唉(�̞�rQ+R!߲ ���(��Q�"奋X3y�"���Kg��4��x����b���1�'���ì�⯲�aW˂��l��'��[�C���d^z�Y� VB�IZ| 4p��F��'Wp<)ͷ�yR�6�)j=�����0�}.���)9[��<��*iJ�ΆPM�p~f��@=D���� .�vPp#�AHC��q(��'��4e �E�Ba�ȅ�@@���De���i܈j��}s���1N1�xvr.��$#�H�q˰H���@�q$&brȱ�0P�@9� -��y�iu��qn�lE~�ǂi ��.��HY񆘜��Q|���n ���>z -���m.��>�x����ď�����L2R��>�w�8 43A�*�&�X��0�h�|�ȃ�A{P�b�#; H�C�Q�P�8��đ+�r���C0�BuV�0u� ��� ��{vؠ!���Rw�����79q�f+/�9�,�:��as>�/"F�0�&bP)����S�� -_AՇ�䉘V��BW�cd�����Og�����꣖�Ƨ ��������“RK�(�w�L�V� -Z_\�+�<`�))�w�ODRb k�D�V���)�����,u=�詭��!�M�|�jz���A� -����H�:;j'�����7�D�ק:)_v`�����n���V�Tx��K��� n�,�Bu�\�k�Y���X$MK��>�v�Z94k���$s�6����,CS�$�����.Iu_ۮI^z�g�k00@Ux� � \���a�w��g&�.-�oE�<��al��޴�Eq���"���>��7A܅ -��̈'�������A.�g`_�L>5쒛�f0Y�7-C�bB��.�M")��O�6�@���@��b<�s���ʗ~��CrN��Ur�1�;���(�c� -6 (#�:�t�cIB��Hd�3�Ti�t���U�Y�HSt�b�Ϟ���X�L�v�Z]ϖr��mAĢ[��^`��4e���KZ6;L�<�I�[i���a�fK�k��,o��ʙ\� -�C*����;~ԍHS�����������Ȇg�J�Ҳ r�@t�k~iʰ�O|�ʹ�~�����l�-���H�mf��^Ģ�}z,�Ô�����$"���]S��t�-Uѿ��0�ڡ�)W6r<Į�}�Ѣ�x6�Ӈ�u���G} -ڋӎ8���S'� 1/#F�aX/��<ܑ�*5��!V��$>8���Α�T�~!RP�!&/Z��$���pȻ8�� �A8a�n!Vc��v��㓈�ġc�����������11IN��<6�/?��O�PG~�^8'vN��ss*��'�i�g[>�*��~�D�yӐ���g�w�ڊ��J� ���#��g��6n.b�C9N ���F5����9�u\d���Ɖ��q&�4� M@��ޓ����y�}�Tb6�7���6�P_�=79�")�asB�p!��g!G2�-1Ѕ�: '�A�����\3wrd#�V]Cڰ��"%�Lv�M���kac~���С�� ���i����d�if���ey�DuX����;�� �;�i��_����Oc'��GH���+��G"�C��]��a��C)���Q���##��B�y����f���$m�1�X�Grjۀ�B)���A� -�l2��f:T��rF�g�'�蓴!b*u:�:��+��*rbp�``r��XR_��_"0f�t�{�o��DZ~� �k��W�:q]�[�f���@jVX"j*�D,K��h�Z��9X��NN]f�JV���S� ��w�.�&O�<�6D�TR��h�X֠x�g�H�g]Ǯ��k��~i��],�dq�f�ST��������c䝂����(�8� -в�� ��I�!@�F������-j�������8�TX�D��A����w��9@��X'A���XT��Ff&V8PR��3�z�hiv_tT��:+ڞC_���І�����)��s�W�O��^WHq]����G���T�+���9ٲT"R���� )����N)�e �6�T��nI��0Y�'`�@}�5�x~�Z�H%�b�����3CPį���"U���e�=a����J���YK��t���[�p&�Z\�'�t訟��W���;�r(�n.�C� ��$��C�e��kܴ�/�!G�_���NT��ˮw�&�-�����R�A�'�#[To�2��E��-)\O���.I,NД��K,�s�Mm�ι~ushw�$ᐜۇ$����aM𰳯�/�p�r�b.~T���u(b�S��2��Y�n�0l��와d$�0iŗ��0�M:�e#x*$+.�V<?�8 W�#�C���h�A�glO����"�ޓj��eژri���Q�^�:�k��"I�B���Ұ�� -Ӯ#�i�A�B 'ɱ����A�r�f��7�~���2<'�S8��q��z�c�� -���v �ɽH5>��~��~G5$�'{ +���-H�$"�:4n"�!#�q5"%۷�p�`�/�daǹ����+ K#ew˦�@ -���� �p��æ�$��D���}N�=� ?��M�"�cl�2ySRA���舞����u��hwWM��8 -�p�18���@E��kG0 A2�Zk��F�Dp�6�m��*�?N���lz����Ѥ�<��t���U���i��3���[��+L�¢=�&���e��V)flc*��w������/RHD��+��H����0�X�"E��/�%��X��WHw��⏀k�S8�X�����Q�R�ow���)��XL��G�p��R� �/z˼����GK�"�c������p}ZVv 2R�� -�r]5VI�_��4 �k< �T�%|������k��Ґ���_�,�|��'���͒��+��r=,v5���<����H���h��z�n�E�Ԧp�)~p� �/�a��k"�����kV��Kf����R�e I��tO��?�)"��ci��RV�*;7p���R�"��H�5bR*����A�N�wt���D�<����߮���ᥧ��-�z":m'�5�T���;K���5�:u-��" �4�TD�`-� ��%G:�H$Y�N��{lN�Sn�KA�M�����T��U�Da���~+��)ׄߓUl��T<��fʩvP��� +���T-�<�_0������ke���W��suZI�K���'B��N�`�΄7LE����j��7nE�J7:�l��\CD/��*!� ��D�칄��AO�F9��wH$��kD��k�X��B� �86(#"0�G�B}���(�V��Vy�3��&LjiyĴ�E�i�3�t������f��G+K��2�Ս]�w�.�5P�D��XusꇞX���HKE���դJ� -�i"�^��(%��2Y-�I�&�NKj��NEډ���4��@Mj��>�����N��A�DnR*:��y�hM�%�A5��i�� -�y�+�� ���5��hP�Uo��lr�U�}�12vV �4�;�DWԝ ?]��H c�R�d8�H���U��|͚��kd�""Uӄn�4�1 ��~V �h�*��)���!U(M���n����U���.>M&oFw?-�<��;/�7�����r�du���T��?D��f�L�]�")��"��n4yL�ފ7��w?���+��_�N�Phu�Fӷ��|t/Fw��Iem�؇�ӿ?���ӥR�w֓�����r�C')7!Q� ���>����I$UV�꬐��_�x����ʩ���������ߜpR�Z��:��F֩�����h����<.�E���l���CQ����nvO������CBr�l]/�?�����ϊ��o��Rwr2�����6���}�O���.�?�*-�S/������P�ml��p�F[�$�35u��̕�e�1Wjul��.�\�[�K�V���doE5�[�M�Vt����lE79[�M�Vt����lE79[�M�Vt����nE7�[�M�Vt����nE7�[�M�Vt����nE7�[�Ml+��mE7���&��Ķ���Vtۊnb[�Ml+��mE7y[�M�Vt����mE7y[�M�Vt����mE7y[�M�Vt����oE7�[�M�Vt����og�n+��ߊn򷢛���`+�)؊n -������`+�)؊n -�@��lE7[�Ma�TRl�s>{�x�6�.����1ʬՒ}�3WF�Զ�g�܎��S�޴�g�ێ��[��֪Ƹ��a���Z�>qۄD��n�H�1X.�2�TI��6~tS"�e2�h����ߍ��ћI�d��f�l#��� 4�ˬ�ML�c*�.���£�sh�� Luz��h�<��?�'��Ŀ��,�\ʰ�� Lw*��o놊��t?��ms۷M�^�T~r��x����Z����|z~r{=|uz|qvq^N�.��(��RR� L�x�ǵ9X{]�m��/����'k`~O������0���t[�=g�����׋V�#����������WGgk�&j; I�کOig-⥶��-U��y��i���>�M�6��v�i>����������ym����ݤ�z|q~}st~�P�)�����!)�I�5F�E�R��,���vL�~è ��Y����S&]'އ]l -�ܒ������a���� ���7e�u�ߥu��%�)o=��C�lV~}��󋿞����,7e�훪���>��yك��� �=X���-N�4��prE�j�8���8���i�B'U�͞S��{�z����*FUf)Ԛ�d��j����b>�����MW�uj�aO{��W��t���i,��㤱���h��lMJCm{N�����m/7������ �0�v:�\����#�R߭�gcw:b��������ħ��OF|:�o���|����|�|7ue��z��ɴ�� *�|��� iS�6?�N���|�3"i���������Gg7���.OφWO�Zw��5�N>Zob�q� -�g�ɇ�5Kdt~?[,����!>X�] U�g��а�����D�8J�-�6��W���_.��Hh����ɔB�Ue�O�]�Nγ=�g��3K���IiO%5��!�g���'�h��5�� -U�X�[�����z�fZ�Up�z�6��9}�&������66�G~n�Ou#��Xϛ�O�;��n�C-j��feo��=��:�e�3�Oj�P����Zp6�vP+Ά�>�&v�:!��q��ƓVA*;����R��Y��'i7�c�i;�����KSN/g��'?���MC#��$��ms^ �da�М�v�� ��0�0>�6Zu��xy�a_��Puj��y<�I�J���ӣڦ�x�����z€g3�5[���Z���:��Q���@tBJ�k�U�|�O7��,��޻��"�&�7��n�ɔi�Tj9�ۍ6��R�w� ���ȔUp:����7��v���� B���~y�NjEB���}�ϔY�c�2e��.��]+����c=� a�a=�����0C�._~�G���w�9�K0e��x�kZ����za���M�bw�U��ꨑմ��Y�bm٪@�}7ll}� g���MF��wM������L�����Q����Aoj�/7�4�ߞ1�!��k�U+���mv'ȾZ��b;aSu����g�ݰ;a ֵ�l�&���� �p,�N@�u;`��u�����tf%D�q��g����o������_[�Z�<��x�w��)�����a��x��F��hʽ}Ҹ�6�xR6\�Vb'�̪�P_�����t4�|�r�|]'��N���ïI7U�S��YR��Q����9U.� %t�\���9Wk��U�KN�v�� �ڭœ�d<���ƥx� �9�<�||h��H>鰗����O���k:��N`��b����4� `hĿ,��pR!`|�0�a�5~��wH�)���L\xkψ�l�OS����2��k�O�>m�O��ю�~�`���E�����߂�L���h�����F1G�p�uB�-����|ϥ����?uqr��txu{��������o�_��7�$���� �o�q�U_�&��}aj{6q7��}�]{6���3�bY�u�V,}!Y�D������سoYڪg�w��Ξ*t'Y�F��,� �,��Dm�j�b����W��)%m ݒo������.�B�Ω�m,}}�b���NY�Flj������ -�Xb�nmS�1�kxxi��n�PJ��b��y�������&S�����Pv��SU2�o�=����Y���0~�<���4��U€������6����=i�q��x6�ċ��j�)�/�dy:�ہ�#���^y��qc�6�"5&�w�I��d���$��&S�ԋ�t4��Z��$M�Ŝ�KVɀ�������8��ߍ&�"}1�*�?"`ʜR9�7k���s%j`���}��*�?����r`�)�-��>�M��ɭߟ=��hwz��h�؞��}�C1��'7}o�Pzc�齾/�}�����7�a9�ȁ���d��j_pȼ�Ӗ7��r��E��[�8{o�Yƌ2xe�g�tc�2go�cW�eܒB���hO�)0�2���77�����7�7nZ%�O�7oY=���Ň�Þ0��au}?�L�^�P�;�x�/��4 -�;Ҿ�ǣ���qOo�~ �)�t�|9��gM7��L˷������M��]����7�<�F ��G�&��n�������n︋�\�l_e�B�:��h�ڵ�ƍ�&�T�I������h�)��tz7y|��`������/�����g�O�pW�A5��ۋ��4۔a2��h�����y��ȫ�_^�^m����n�D�/�iqts�X&m��L6%�aTG ] �G��j��?��o`�����d6Z�<��>��)Nhꆓ�c� ?��M�4elQݮVM�����aG)��n�˔)E��x��֦ *jX����2e5Q�^O�L�����Y�1-�XΗ�����{O�����:�/�?q#-�#���|z~r;����\r:l���;c�ڔ�6��g��LJ�y�؉��`��|`}�O{����Mݾب�L]�'��/�u ^aR�*c���f7n_�׼Iߏ���[��9�/��\���Y� ��cs�d�x��O~8X�`tq�;\-��^{�6���%�N�܃�E�&s�k`�O���=0I�.�$�W�2Sޜ�.�5�-{��tO��k�c6g����ܓ�]����X��X�����=K7���J�Yiv�z�ξ����[���0_��3舫�m�TW���u@ ȭ��5g�ߴ^�4;D���6 �����SgF�b���W79�vX��E���u2�/�>��=��[G0��6״�;�˃/���E~����߶��� ��:����O�?Y�w��h�9h3�>=��lx���7��yy����/�L�}ղ� �D��ZXb�_�G�z����Qcc{8<�a��aۍ\�(_ �-Ʌ[?�R��2�Y�E�����a����/X{�?H���9�q���tnl��S�Ǔ��J���U��5y=�n5����L(Q7���8�p�.�pR���l~4�d]�I�]*� �169�r��YC�=��&���U���%};�$�Ɛ�c��v_�QGl -����˄��hRo��Ϋ�7۔�rM�w6]���o�#k�F����v��Wf � ��߼���p�����;q�f�0c��{�0�9���[�0�������2߭�����75��t�����)mK�&��yM�K�&74�M\a�6�*�$� GO6�d}��{J�6����(�=K�f�� �ݳ�m�����_�{ g�9H� �d�_ۍ�z�֚1��}l{�e}�U��7vc }!��i̾��m���/����m���n����b;�2�� ��"�_���O����gnF���3�♹R��3���w�y<��P�}�ڌ�oR��]J��0 �k����7�IH���^�����+��_Y�Z�v�߶[#����9��lb�9����:;�SF��2��E6����ť�M��E���E�{��@�[dK]�:F��#[쎑-���9�"[����!��&-"�� �e^�W����Y��<O���M�M&�u9�l����z�O�� T݀�Z�:RY� 8�����G�I���ހ�A���x�A��^_� � J,�� �X�� H]��Yq-�i�V����S_������p�hTU�/?�o�Eޞ�|��`3{��i8���+׊�[�07L3!i�=�n��MmB�n�e�>Lc��폢�]����nfw��md�v���Ffdw��md��u���F�gw�md}�~���Ff nx_�mdƀ����F�xό��޿k�1�=����+޻k�1�⽻��/޻k�1��=���=��k�1�㽻��uI�n�.]�Ȍ��Ե��V�N]�ȌA�Ե��P�^�ȌA�﵍�#�q ��]]��~^���������/\�'qk!� ۼ߷2c���|k!3�3�i�_nj�U���u����ꋈ];���r�9p� -q� ���qKk`n:���g���#.n� -�-m��ј��Ѵ/�JU'�J���e!����s��Z�G�:�Nk�oud�/�s�ëY�t�E:�Tl3�`�+���2� BfZ�i�i�ZcX�dÞ.���Z��u�+�ۀ�� 7�o�mqө�=�ʾ�� �5���)}��ȫ¥*}�S�ў�x%z�7�:�Õ�1��=2h�c��֫��t?�<=c��� �a S�/�i���[#�h�w�� =ڳ����F��� �kW#�sw�a�6�<��x� -Jxk���2��ܹULQ�#��?F����Ų ���̳��xq��.Ƅ` -�CH���5�g�b�����I#+Y�9տ������3f��iuU�����3������1��}l���/����E{�9���ژ��bi_��Fx[�/�l�N�<۳���?[��b��l�� j8��̠��S��Փ��44��\s6^,wo�[z��v�ζ��RU+�Ķ�g�s�� =����ľ���)�/@g���/�f�#���皝R*}�0�(����������c�&��&����/ �ۀ���[���:�K {l� -�o����b�X���>��i�W�yX+V�wA^�O��s�4 {����v<�s�zX����smt?Yj���3m���?�W�����ppy8k�&��������^[�ϟ�w�l�����x�D����*����s�d�\�;���@���V���n>����޿�Z���*�/��u����eHH���|�׸_|�>\�L��|F_���/V���t� -�����q1� ��p��v1y\%�-��O����"������N��eD9W ~_�������÷��b|��䟟h���>V�mH -�����`6�_}xx;����&�7о�L��Jn~?��F�Ӏ��m=���|5��غ�Pj4��:�N��k�H�� � ���E������i�:�Q�-z��:N���,����E�^>M ����׏?�W�W�P��F��7{z�g�����*��2�г���4��<���x�xF���}:8�����+_1�9�^r+o·G�/��OŚ��߯O.��/Ć��ߜ�ޜ_������������p$V�x28�Z�_�:�^^����������0௥_�^_݄��d8;�p}�W}|�}^�QH��Q.����y��y��ȯ���Unۋ���}x:�:d;��@����_�����O�Ì\����%v���E��$�Sa��<���/'ǃ�(���o -~��8<\}{r!��?���:;<=\������\Bݑ�>��I޿�����B����?���?���<�a��B j����iv$M����"��+i~�ɜ��j*�or~���9�����(��H�r���W�l��)+ Q���~$%ttz>�5�?N��O^K-N�yuR��Xnt:�>{5�f�q^%�K��/��gRga��a� ��\H'�ӓ�����٩<�Ë��1i��= hFeTf$e\�>Gߞ��#�;�/8�YN�kY)��2ua����ۼ���P�O�54l�[x%�ᇓQNGyա��_^��B� �G� -'}���H"�������|��"�B]_J�F�?��͠��d�J�����r~-c����4�9=�џ�av"i��W�T�rF��C�u�\y> -�@�YB�%wN�]#������H�ʷ����.B �`�buu%�0�h���:3��E�%�=�8����T�4��E�}�s'� -Z��Ӭ.! n�rp�+��oC�I��U�����_��P6.C�.����!Q�8>1��h0�9��<�1�a�a aD�D�$!9<<;���,\-��>>EKz5Կ9�q�����e�����0�5��ӓ��a�_���?��!������?�S����?�T��g�ҿ�gw@��2�����E[��%d�ϷD�-����]�7��t�TI l=*-�ч�O�}* U�h&1"�sNJ��-��͠���sGs���is5�~�#�$&P ɱ�8�7�|��~ �K%)R�T�f :V#�9T�ұ:�e�>ס��s����@tZt�\�� �ܠ� �ܠ� �q���� &}h҇&�*��įԅE]X��dPc K!ea����K qD ��(����}�Ї.}��8]j��>t�C�F���.����xċ����H<�@�z�C�@_Dǧ���' >� �O@|�G@�>��}�ާ��M*I��i�꓆�T��� ɏ��@H�t$�I��]� I�NR���$E�!!q�I�t'��I7I�NR����ô0E $Z:��� �0,"6�l>I2u ƌN%uDb�� ����!)�IJu�+2e��CƆ���6q��u* *�>��lb�Ml���NH��I���iW�ႌ���2�H���[,�8c��M����d�h�x4Y<�DM(�f\~��}�f>�� -g2�H��K����lo���MS� �$ϭ�N���f�Ej�3@ -y,�"��i��x1�!&;�d��z��g�CLv���j�f�c���(�QM�X�Ќuh�:4cB�!���jp0B�SQ58����w���ClsH�;��%�ְE �#Y�MK���B ��l` �>K�M~�6Nm�2�2���*K�Ol�b@�Z��$�u�ۏA]F{�r�����M0���fV�KϢ� ��r�WђGs )*�M�q��ufE�>�U�6��7��-y�m��a��4P�ʦ�Cπ]P4 `M���&M��~T_-̽��'��"���ҍ�[�� �{���S3�ȅ�d�y�l4ò8��U$N�aD�`=А��2��lb]D =�$��K�}�c��d�N�iB�Q�Ȋb�M2�,� �$z�`���'�\jL5�F,�xh]#E��@]��7ҶaIl� �KH�Qs�� ��0������� ��1�pm+ʠ� 2L�H]�Ț�ˈ �, ����*�9�.�5I1�Ƭp&�:ˍĊ�֗���V�MF�EcfL7 l1�?��MR�&�-��=X��@ٴE��!9���4`U��hA�t"�g�<"}�k*��d�$3.-.� xg����߰�^t��g��c�`��0��=_O�]�86�#I�ิ �ьc쀌Y�9��=Q�~�$���^�~F�D��(0ғ(��S���4?M�Id� �4P|.���[D�`���*��AJ� �o -� �>� ^�cþčE�G�V�C۸�L�P�_B-v��OWh�x�bz���ji'��f�B�) e� -m�,p��S�籣�9����js.놞p�t�,��i��~��ARΕ�\fz�����R\Ng`��$��C>��єfv:�M"�a��'�3�&��2I�"a �M>���mP��ʧ�ceY j���(i�@ܱ�/�l�%[d0�O���F3�� ��}s��L���r{tŲ���vSV�؝3� $'�"��&�g1Vz*��=�+��k����KwZ=����#��B�AZX�h�Ms�&�.7��-�-�"�͂����&0�� 2��� {��)��ď��դ�z��m}�� ��p�`F�n�2��j61Ɇ�O���Ē���ra4�'�= ���K����6POk5I�-�)�\�ԯA���[�� -���y�Lj�UX������h ���0�� � |��l��I}����5XM�0��}R]ɖ�n��Y�~�Ɖ4#�Vs{CA4K��i���\l������M�� ,6�Ԓ2Ir�K|��L4�b�m�*̥)m{� -��Q,�m���/�㑀�����"!s# �H���CG���N��G@RԲ������ݞ̘�ݘ����I�A7�l'6�ys ~u'�`Z� gK�8"h�4ta�C��l�dۅ�y�nS���kcO.�ǵ�Ҁ(�n���O -ԅA����r�'�)"����D>,3:�"R�^J^�Pv���%����������� ���H��0c��pj�B���9�ө�(N%;BvX�%SO˞ -��v�^�k�%�gy�� �i$"dP3��rH��|o$K�!&K~�P/��Z�;�6p$�l�a��O���s�榬�q�;`�g�v�uG�_w|�9�U�uG�zLM�'C�w�U�*��,�ԅ��̶��?v2�J����x&m�s�&��k�� i��� ���;k�B�GH�pӗ�E�=?]e4���L{��j����?���l�����j��򃡣�����'��XF%g�� -8�6�jչ�<�3��xv�K-��2�L�m����,"8�v�}m�ȼ6��1�%{ƌp%-��5:[�5�~�B���I<�yO���F�A�Sg��R KAN��>�x�\��Yc��V'�5�4�Sd�3�c5X�$;;\�Ԃ�� �P�ztd�@�9�� �3��#�$z�VH���7�1 ���Ajq���c����j?#��h�N�Z�,�������('z?�X秼��?�����z�c�_���4����Ii:���Xټ��ďrȕ1�2:�㔶+�X+�ډR�]�l⻜yL 2#��NF0=?e.����988 {��H?��@`q�=��,>��X ����n�󊂉A�MA���C|J�%�W��Ra�9�����H9����h��+�U��b ����,�9���6>�F^�SM�/3��X�$!e1�˙|��˲���b6�0��T�`�Y1�5X��>��F�f�x��jd�*��f����u� oW��7�,f��S�0��}����&��,F|$�b���Y�˰�b&f<��Ƽl)ae[�b;5x��b��%lK���d��7͡癢N��ǒ�R*��M������5d��y;q�`�aW�2f'�Gv�C�>�;{�Ķ��')���HH��#�HXN�+�ǰ�e :J�R��tar�&l�Z,�V7�X��6�`%�aʰc�c�N��3~em{��zz:�8!��9C�'�=�s�b����HN �f�%���n�bK)��5SDJ�%���6��Lq�вb�CP�n"��tYs��T4G ^:C�\�+uC�Iʌ����8K�S;�=Ϧ^F�_�< ��~:;xK ���K�u�@@N� �[_��l ������ZBiH@�=�kH�3��y�$�)����d�k�b�����6eP~�I�_���i�SO$/�N?g$�ԍ� �h?�BԌ��~�F�6�C���G-!������& /TF_n ^��x�a �Q��Iw�)��#�nЇ�-�;���O�tf;���QV2��*�%��J-Ϣ^Ǽ��s/�7B��qō���G'nl%e\c ��.?�y5^�$��Gq�&{���5�n���78���JH����b�Q�d��~n��p���A=�����,9��O"���]��`{ľ��G���G �l?�Mq� � j�EIz ��.M -�N�]��b�1�����T�K=��� H4�\�U�҇�mV�q�?d~ ��bl��ѼT �|�������H�9�n�Ѻ_T��,2[Fw�x����f��2���%/lu>a�����1��e���(�~D�@T AN��>���$3%���a�1a�q�)^��IΖ�1��Fw�=y�M�S��q�fj��g:���_!�IR��_b(�ٵGj#�,p���E5�;�Z�Y�W��� †;;��r�N�7�2�+m� -� z�I x#��t����$� �C�ՠF���,���]���O"� -�sJ)�} N���v�/����g� -��0*�#�d@LOȳ�,����=��!� ��" ��4�h걐�~��r$t�)H{���6)xD=�u��X�4�X`��h˨��3�� >�*Q��8�K�h �DKMUW�|�&�5p��(��\���Ǎ3�����*.�.nK@�<,���4x� 4-\a��[D#saW1���ؤ�G'��tݨ;[��Coaqm�I����י]ړJ�v?�QT(��� R�F��T[Ա�w�������J") �� �d���r6T;��C�̴�pY0+1�E� -bϮ��7.سXj� -R��y�x��)��a���Ipl!"!���E{vW�xD�8\�t�Ÿ)�ꘪ�Ē��0��#�b��xň[��o�ܡ1������$%N@C u'z>�y��]��O���5��\ -����a�h �S��8�h~�@�%@�`��v���\���A Q �}U��|����}��@���v�&9T�E@ҟꕺ��ٮf3C�pN���p����� ��F��@�s�� �0]]������g7qٱ�N�[.l��.��%���v�n� �=��.�a��f1?���p�J[��w[�$�m��B�B�T�㽕�3�n��/5��B��������]��;ɯ�hT�?�@L\^Mʸނ�Jt+��ٲ&:�c�!�|� @0 va$�\h.r� �ߛ.�&w1������b`a���)�B�Sl�i1�r0�m��bYi.� �M�;T��8���� ~T������e�>��'���Cp�1��;���h��ex:�1�w�9Ա8��e��`�"�5`y5��܉>$��۝`��mi�h���� ���x���s�2�0���D�;��[��9iL+gG���q����z�H"���#+��#��U�O-8��P�ua�<:��+&����qt�d��1x��ݔ�h,yG|&eȔA~���별���-�[�܉�$ea+����9���D�x`A��/�2m���t�!!l$�,M�#H��q:_,�:ʌ���y�� @���b�Г5�!O���H���_K�M�`� K��˪��mI�zl6gSQǑ:�N�#B���5�� &y�M:���Inag4Dw�i�l"����P�k�s��W� -�l�r�m��� atZ�;��"%)�t��f�B����^Q��t~�fې���7��t/�twc#a7��t.^��[{$>LM��:Mҿ��Oԧ�6��"%!�UH��"�q2��h�=ᚷ�ׁ)Jv�V ��k����M\:p��), �Oo�����Yh~�t�4�%e�3S��2Q��Ev���} �'8��i��}�#4��s��k��cPI �f�`)a�%�٢AG��#��@Ȋ���Ql��� �(�0t�E�>BB,$���(` ��F���� ����Ex�� :��0@$6� ``�'Z�"�9B$6����C������]�[i�E4�(��B����q�i=\�������9���$��ÃD℄�D% k��B�{���p�����h���"_V"�4V�|F����D� T�j �H�����mL1 ��K�~�j�+ /�^���'uKH}��eɍ�z�,RP��HnQ�d�Gw�rGG���:"�@G��G�q.M��0e���Q$#�}�n . ��t�O��D�~�=��BFa/�="�\��[ 0p�9$�>��3<+"*jAA~ZzH��$�����P -�P�)j ��w�j|Hb}I�{��z�%ʹ2���Aɢ�(�a�qY)l������)����|勗4ٰӒ}����]�!"K#�P����ay��d:WN�I1���WA�V��F��rM����i~��a6!5'Vϫij�̰��d�"(��PҪB�6:k��=ЩǣґX�T��/��sP�2�s�xp��!���9l�b�%�r �������[��ˏ@>��Av:�`I�<�F�V^"�_������ y5X2Q�����������+�����?��肓))�����KKM�W\�),��Q -I�&�i��J����ʹd� /)5_J8DZ�L���)`��T��p����=���\���R~� �cҬ�̜%��L�T�)*a.Yq)Є*��?, -�����"0]�;ꔦP� �{�l+,_� ����i�ٚDX` -K\��ccK��~a3�dI�L֝ �� ��C��g�?�R�~N�:��� -�!k,��)Q���Ϧ@��Ɉl˥c�m�<��O�s#�{�W0��/(<�|QY"���g�� �E�_$9g��4�l�ʑ�Ci�)���/U�\�O2I����Ͳ=��x8Yԣ���i�x�X�g�P&[#�ͨ5�JDꣳ6�Dۏ����'S��d{W2��R�h��UD7ʶ��yQ��n�TA��,�7�'�)�[����,�T3����� f-�f;G�r��0��t�a �!1��H�����J�#E/S��!.z ����`5/'қ5f� i�8���Ó=x�{��~�K)�c��#�� ;H�����i����H��AY�X´��ٚĽ�2 Y��(���t�$�Ͻ��3#���B��8UL+�,�V��,�A�67���=�b$r�C�M/}z8zL���[=�hd|�����!f/q��@�4H�Ez��Ҹ#�6P����PO����Kd��������8,�qϘ<Ф�p��ʼn�h��̹����p��F n?㐁��jKA���R\sǶ),ܒǦ�t���/m�0����M���q><�ޖ�68��4ķ��o�8 -�O���n�%��+�2]r�#;�}�#. � ����8�������/Ƒ�G]E��JЄ�L|��7���k��q���㨍F�!�F����#zx�>D���`j�Ct�b��8�C H�<���<=�����$G>�W:t�R�f:��v:t�+c 2xśp>ҁP�&;hB3.o,N����Ǒ�i��u��4��Cx5 s�B�!$��Iq��0C�f$`� Pu��ٳ�& ��ҙ_����i����l,&��� �Rbr%��- ��>�E N�R»Ya��~̸�6��ak�%{���FRژ��RJ��E% i;-ٳ/.WZ�l��ւ/�KK>�6\,-$J�}%�{��p/�\ ��'��dɱ���������]~x�F�%��A �F?-��/�:�wF| P�c7q�;��C�d��HOޠR�J�+ ��k�e�@�D�^�B�eJ|�R �<����ӱ'iP�† &K��ROK�jW"$XL��]���.(���R�;aʞ��Jᮄ.���(�70S�N|�� J���� F�6WB>ѻǕ>+�=l�EH � Y�ۢ�a������aE%3�gD��˴�e���VZ���P��$�Rɿ�Jꈽt �%7v�2=c�v�0�#����)(���d�⡄���J��X�1��!7�g��\��Q1f�����/��Ҳ���]ʞ����J��f ��'���c\�^��J�>�_"�/s��}�YH|If���H�C��sKv���)HW�uJ��� "�٭�NY)e8��%�G���su�ɾM`����< f�W��Jl%Q�^1��,Ҥ��OĬ��:b�����~�8�͎����V���$ۘ�; P\Ns���T* ���@��j̼��/�K�,@����"��"�g����XP�@� -W��t�q��9�鿨�[U:֋���B� -���Q��H� R2��̇YP���<�EŸ���cq�X�������%Pg:E:��>w�"��ȯ$��"�����j�9��ԫ��Y:����yXU����xՔ-Q2���lj$��euKv6�̝&e���.��H8O���s΢Y�X��$��˕@�����O�Jm�Q����I��-��8(ZM��Ba�k��%b_o�2�\�fG��g����b#�&Y���!*cìbXt{��ӌ'j��du�=�G����������?����'��_b�p �F\���}��r6��/yP�e���x�l�5W�wo���J�� ���z?��M���!X>�o�������=����Ke4���l���v��_�g��� rР�o����D8�Ť�;�J�C ��@�+ڛ��x�~Մ� p8�R��r���j�E��bu�Z-&o�V�2Eq:��2��wWb������������C0[-�g���,�M�Z=�~�3i�w���j�� ��t�!p4��߾F�����S��_E�4�2}^J� ��tѩ�E�^���,�,MM�&u�6Y|���D�뵉�V�k'zI�D1�h&�դw���Nt�щn2:�MF'���D7��&��dt���Nt�щn2;�Mf'���D7���&��dv���Nt�ىn2;�Mf'���D7Y��&��du���Nt�Չn�:�MV'���D7Y��&��dw���Nt�݉n�;�Mv'���D7ٝ�&��dw���Nt�Ӊnr:�MN'���D79ݜ�u���Nt�Ӊnr:�Mn'���D7���&���v���Nt��MA'���D7���&��n��6���N��j�n,*���P-!�'�qa�O)V-��v�Fx{�Uː�ŪY�]�ʸ��^���X����uB"͞�8$Ҭ ��;E�1~(�ݖH4Y� -P�������b2~; �-��.Wu�]�����ڗ�k�y�������`�,�����ֽ�`�Yx���j�X�ψ������Z�'Ө�(fv�M����t:�-���Ep����l�;1��N z��Ү��坯���t�RͶ�\�b���o������q2Mm�i�{���B ���F:����M��7�[ /(Ƚ.nf}ܤ�E����2�݅ -I�˗ߝ �o�g'G���|���c7�GϤ�*h�h�hMc4.ma�7'���������X=������L}Q~s(lE�f] -[�Na{Y�����Hھ|9��bp3<�<;<݀>� �`{[ QI|r[� �-��%(�ˮ�oi�$?�ϖ�:��yjiD����zx4:9V���z�{=:^����^U�M�,�O�a���l��&���s2����i������6�3����2���kbYP㚴����zn��&��Z�^��׈�N�qM�S�r�;=;oe�~7<�aXPk���M�?��iI���5K�A��;�:���ܐ^ot�S=�Gz�;��cKk�%״7v��-n%|lt1�b�-dՇ/pg�\I�}���!30� M�VE'�n� -:���[LX-iX��lJ�hLU4l��h�U�]n�����7}���֭><��������_�S%�m�.4���|UjtUyWW�F7�7�*)��RLW\Mm�qg]�{�ʅ��}�]�F׻7%��n{�i�����1^� �Ȇ�7�\_(�U�l��.?f�Sm�6�+����@��[.��zo�o@[�/�i\���ͷ�����󳋓������x��g���@{ZF�X���@��2�x�/W��v��(x���5>iJ�lE�ԥD�� 9��87�/�,N_E��a�:�.�U�-xx\�K -��� >}���'E2$�^��J� ���ƛ��炚���H�B�Y�M�h�?b]���X�[�����p�dYu�R�r�V(q���b�/oH�N��0ntZ�\�[��J�F{Ym� � b��FQ}nCͫۺ���iq��#-���+o��m��nm↍�� ��O^Uq,?�O�U�U�C6�j�a� �/�tK�8A�x^��zߨ1�_<���TpÄ�z��y�� J;_59|�Q�j������cu[L�Z���� &�߂ª��~��n��e�� -z�ƿ�Dt]��Ce(�g���V<�Ʊj�֨ ��`����N�Nル�ߎ����*�7�jr�8�� ������0Re��̦�Y����t�nꍲU�2���E�\�@8q��2Z���������&+����F��*�`\an�����o��+�%���xAv�B�~� �U�S#zm�nk��F �ڒگ$5Y�YRװ�k���ak�u��km��� �et8����4IwXHzM_��2�g�M�F��J����t��wC�Lweh����.������+[8� S.q5E�匿�-�F9�J��섥�(!`�c�� ۵Q��2�N�o��.�w��N|����g˗�l��@VAt�R� Co��P��f�̆�N��u:�i��g��к*_��ɟf� u)E�Q��BJUM� -J6�Y~��]D\���>�q��٫4S� ���*7vi�Q��"uRރ����d6�N?4�¿� ���m�� -�p�QΝ� --W�w�p����.|�x��m��H�M�Y��݉1�o��M6�� -�2%�@�Y����_��������ҡ�1�؂�F+#>�&��^�� ߺI0���t�\V8� �cU�Z.v����>]D L���_X���6r�5p��`靺��U�Tubq2[�wU龜��5��\Wu���f��<����h��;_�LS]�i�j�v�S0c��s8W�wߚ����5�Q -:�ݶ��;iUn0�Z ��6�Z-��n&��R�m���'�ˠ�4���#�v���vݻ�6���i?��6�[=�Su�ߩةZ�j�]��ee1��X��P��n��M��ͳ���ʧ�Q�v���v���ƺY��BU�X�fY�|r� ����˦��4`�G�3UCQ�GSD��D��K������j��ms�)1��� -�Q�^iF��LrU^DQT��w+�_n�*�_G��pX!vDF��M'CEv�zv*������J�;��w�P��U����*+:_�g���ߌ(�w�O�*�@��!O�'��8������I�X ^}}�4��&úl�*p�ʯ���|����D��ߎ!;[A`�;۶ـ�M��y{���2�h��?%� -1l� ��׹m�%��uӬ��n@E҇���uw��6�,U�����h�\ AE��1�������?c�\;5��f�P�$U$�������$��v ��6�DK�TDҩp��E�NUgcW�O1�ڏ�m^FU�Ī�����5�#Q��N�j>��b��n�`ֲ���X���N̺��Y,���Q��U�/.��`�*U�u��P�@�'T�gǐ�� �J�U]f�{���W��ՄSv̑;��H����Y���ߕ'Ŵ�_9�ٕ��.��|��D�l~���T�)Ȑ<+~ 1PuI��JW>�[u�5룭s�V�/?�~�L��R�_&���]E�1�mv�z�����q5�m�Z(���e��`9�2m��wx7~�v�Y����]0�-����P��F�n�b�M�SF���d\z�f�!�]�\u��6�1�"BWY�u�\��l��H�*�i D�İ������oA�M��jz�����äF�t4 R%��*�f��>6n����7���N�-W��FF��4U�7wQ�v]f�6�JKC�ݳ��w����ąͱAy��0�U��i�RY�g���~uzrT��F�v]\��G��qi�fی6E��|8Tt�Rg�w}5:UP�l�s�E�4�mj�u3��m�´�V�mӷ�8�4���o�us�k�M�D�|?^�N~ �X������1�^������fۜ4�C�:�+[=g6��x� -ŝ9n6���i���MV�Sڠm�%z�m�:�es�n�Me�m����+��P ��r9y_�t�i ��!�`��?�,�җ����[I�d6��\��M�P�h:��_��ҽ/�m:I����>���wg�M&���l2��UEJ��?����C�+8�Q?���'��5�Bԏ��qi��a��>�d����ؼ�_��~�:����s�f۴`)N�����H�f�t��TY_��~'ؤʜz5��"kb>��پ�S�fg�$��X�*R��8���܎�{#��)��#��)�{��h��,G �׶REJ�\��$Pe��؟I��fK(�OnEI�D��� �u~� �{t��6u]��r��:o^��9Ci��/���r��:�`�=s��MvX@�=r���Ę����m����5~[��u�J�cU�zo -eF�2�y��Z��-�b�Z*�K�ZA���=���&�� ���@�a5����A^�i -�>�޲z�����@�au�0�N�o���ʝh ���>�厴o�x�7B�ܓ���/!Pe���/g��������Y6�,�(T]h7��4M�> -��U�`j�[��Fn%�a�Vt��n�vx�a�Z'�4�"%)tQ�e������"2U� �]�����*��dv;}��u5Ih����Nj�������x��A�̗j1�9mmU����t7DW��ӊ����_�\n��m3�*�x[��n������r�R��l�q�0��x�$Azٸ�a�_&��.���`�z:�"�}7)�1r�T��x�T�HJ�`1U[4���T���!%��(B�0J��x�2����'�ݑi�ʠ"Ģ#��@K��Dh]ϪL�����,ښ^���t�[��ϵ���Z;~����1#-�G�Zw2<���d����� j�l�N��Ve��K�WO���`�G�m����P#�D�Ĩ ]���t�{�u� M[��&\�n�Lv��k��5A��I0��a�{��G�m�Xy���h�X��2I��|��-�b�Mtۤ�"ү󇝘`ʬ��|����9X��r���;<0Z�q(�|�ǃ`<-�m�>���\���C>^��Omfy-���3��,<�����ڼ����ѥ ��$��Ί�/�e���Ȟ]�#�D�e<�<����؝VN��kucڳ��휡ׇaԦ.\�6�&6�_��Ɉ��q�4��q�i��8����O�6��Gݵ��j����mn�v�b���-{�\4d��Ŷy}[�ީ=�6nKʫ���]�Y����d\� `[�+e�dɕ}�u�1f��u���r��i��Y��v%: ������t�K�d��&��g�D�����AWAU�+�ֱ��JY��]�(K󹇶��|����c�CYz��Ue��{K���E��m�|�s�5�ƹ Y۬�-کWZY�ɝb��չ۫fʒI�N�;��j�F�b5�(Cۼ�J�Z�M�(�'��4!��H@��zm|+��X E!��am�ɡ,g�3Q�޿�f�O�����oa+*�Y �:��8a �^Y��=e~[3�2��m�������iƿcJ����!%ֿ�>�͔��X�n��51`W��7�������\����f�^O{\���v�?MW�`���>�:3&��/�� �>�m� ��/�Ff���n��'�Yֹ��Ov��o����i�v�������n�{o�g{��~�WW{ݬ{�XVi��L�U�+�P�����r�m��U�f"��a��v��Y�{��6}�.�,��Y�Z��my���8�V�hnO�3����M�-��b�Ms��H�:x�=C��K7N��N�� ��>!�ۦ��I���M�T~��ny ��oym 15�$�z����v=mS�m'�_���O�?m�Mo�6���:-9��- -Z�l(R��$ -n��.�/�{Y�牢r,�R. ����pf8��׷�o�����xz;<���[qx?���>O&W��-i��Q}��t��<��E�/�����������/��~D��&_����ǧ��9��;�������{�ߓ���F�D����q�ԩ�2b�E��i��>�;�x|���{%-/��E�����2[� -U�t6NZ�c�3�IVj�`�{I*�7��֊ja��g�YXo�ə��BUe��$݀�\�.^���R�2yׁ )�n��p������Wׁ���U=���^�R�]� -s��kܧPT�ԙ1�� ,��Q$th�x����K�sp���i�ѿ�|+k��vM�2Ǥ ��s�Y!R*w�`�<�̅Rd{��L�Z���M��xt��ƣ`M�� ��f��锇�WFaxP������u�M��v�O�����b��5h1��(u<���J��Y���A��0 ZRe411� ��1n6<�%Z�ԒՐ4�S@: �X�u���2��g��d����"�<�Ÿ43����}���RRS�s�b Vҝ�"u������fC�(JʸX��#�G�:�@��5�o��DJw��c�v{��A�l>8� i6�,9��`�t���i �|V+�-@O �,��G1��k_� �ց�D"�x��:mD�8�bp��[�e�1c|=�Ux��LJ���M�S'�f6 ��%Wٰ,8���w�n?��o���@X�.�4q����d� U:W�8��+���#����c�k� >Ř�F���P+����h��� 7e AD���3��/1<��J�=�g���Mcǻ�9��9��–קB�A�ݮ�J=���n$�iKv�����`��I^(�}_(�ho>S4�u�r����!d���+�� -CS�s���]�`�;oH���|xiҭ% ~x�L��ݴ��Y^�'F��#�aP���� ���ΞY� ����ZeJ:?[��u�M�O7�#�,��� `�GF'�7\6|gZY����4y.�V��T� �"{����ʌk��y`���/��� �p3�щ��^R!>�[YRJ���4 ����=ks�8���+���2������9q���l|�8�X��9��$��Y"�$�3����n<�);��\�r�&�4�~�=�[Ζl���8�QQ�ɸ�(o���}�}��A/x��ǜ��-��y�s�v���� fI����O�����,f���>^�IY�n��,On�b�������"��Y2��$E`�*O-(�Y~E��".3�2)�-h��`UȜ�d~v�y h^��Ey�*[�������}�����8����x �6�9>Y-F܏�lr -dO//�M�Eyv0�����A�eK����et��QZ�| -��0؉b��6��l���&�b����xn��������8��G���4!��G���dc�(��՗�b9� h��<���g�Dc���}�������!�e��_"�レ��4�ϳkX(�ย�N�s6�7�/0�uR��%Oy��'J���e�#m+��ٟ?� 6) ���Y6e�ev��Avz�es�����/��c�i�y/���0������=����m�t�����K���TJ����2M�7H��>�ϓ ��y����E 6���<�Y���'���R� �}A�:�>�W�I������а���%�c���KJ`~���l�� ��G�� � �h��/?^����N4��,��y��;��++� - -2�'9#��*2��-�q��7���8�q� �5 e����|̋"���M�huy �t=�U��#� ��l��9hVTT؊�d ��q�砄�tB2$�#J%�d��̇F�4͔�Ŗ���ߗ`z������(v�Ŏ�"���'=���?'h#ŁOz,��%�W�k ��e���� -=��2�'[A������Jz)+��:&��@7�P��\ �Ã5 �(����e�*�7x�x3>�j!6{��>����O|w���i��V�]�Z��r �4Gj�S����� |5��W>�m��բQ�P���Y$�N��VY:���[�|����m��Z�Đ�+��dS� bɢU -��Xa��QP�E�Z,B?�j��'��+! -v�|`�;Hv����d�R��x�(!�:��_k���%%4�V���"��D�e/�#�(�q��4 -,����*1̜O��,N'��L�$J�9�@���G�  ��½�2x�#��'��M��&5��%8��X#Ւ˙5ZbN�g��r�f�'��77X�e�\��<[�鍯���D\cZDŽ⚑�/���Q5Q4;�c%�KӁْn�FP�G=r�J/�_,��p�W(�iW� Jy}:��Y�(�Hŧ������zi�/�%J�H_�o�k&�HZggx�]�A��F�S"��<�ѪT�{�`�}լ �!�����vh6�BS�q1K@�&�����ݞ�<� y�eW� ��Z���C ���b͹����_�hL6��'�; DO�9�Oč"ܛV�� �<�X᧤�(M��K�q]g�--z���0T���T:b� ���;�"�c�7?,N�\���[���V�|�%�YZk��%������E��4+�� � -��ڞ�r5��X�]\P�$َ́z.�]6<��t4���Q���jW+�>��() -^F -İS����]J��9�����]����2~s��b�Ά k�c��y�^�b]-Aӑ;�n�d� �w��4ԯ�iV�hl�;� -���{P�.����e�k� -�Q,L1�0��G���i,(�:3IW\�a��TP���jB�(�j�J��!�D�`���ˉ8�m�tШ�*s1=�DH9}L}�%]���;*�r>Co�yR���|)����X����pm�9���NH�����ʩ�DH���5C���l.k �Rr�u��^�Z�n���\�����Uإ�x�ie���!����YMxhA�/Ȣ�N�6�D�3�!����U�&��ֵ$w��`��nd+�R�W^���ʌ����~���. �D8b0\�kon���+�`���Yv=��7���k^�gBS��J$����J�OM�Ʉ1I����s>� -��`*3������F|�'d�qDRK��p��_l��MhT�(�J6Y�}�ȭ�����:B�d+vM-4�e����D����B{T�8�)�’"4���&ʳ46�plp!K����A?��6�Ѥ��v'�J���Vd�z5e�st�˴ָ�Z-�f�OR�R���]%?�ں���g�C��s5%�~�*>{�^C�<�q� -J��RoW�|]C����}�!��b�b ,XN뱘�tz�gB.�� %��Gސ��}Tt;�F��D=f�K�G�����k�{��#H���]���0<�<�t57u4�~�0�����T,hg�p{h��v����5X��O���V���n�3+mKJ�v���)) Y���;?<=Gn/�lO��#P��IO`��M^��4M�Ufn .�L&�]#�����Cq�"��������'�1����#�����_�0iʫ������Q���P8��CK.F E����"�����m� U����y�Dd�04L�����fNq�l+F ��\��М��!A<��* -��l��,y��F�G��� -��)&$��8I17 V^�9t�)T3��Qm>ɀ� �Nr���")i��&o�y�]x+я V���� Ɂ"| T����e}i��,�e �����kC� U���Y���bVRH. t6*x��ݨ/i�a9�-�H�͉<��[Uӆ�k�DU^��F[WnNK���%��N݊�Z}�������Ɛ�֢� m�V��Cj>���a@��넴�|�1��=�i�z���XO.��[Ll\r��$�t�I+���&`���Sv(AvήeЈ/s_�h|�pb�.��@jT�i����By�l�p%��%[�s��}a�u!J��T;�4��%�D��i�Dmq�y�g�u�����>z��w�d؈cR��ٺ�h���p���$��bI��b�s�p��snxDjY��� ^����C��u`�>�����>�S]ܧ2|�)�x���۳2�o� �ZJ����C��:�4��V2�[$T��_��).��(����`#N%3�+W�9�]�����6�����I���e:&k������O[�&����#�� �D����;ZK�aE>�6B߽���(R�q�o!�k ����UR��iW�|�_Mi ؏k'S�SP�4�릱$�1ʶ7�}�4���T��NU0p2�ѫZ��W(�X"J��Y��ص��J��gU���k�{��|*��.����Y�m'�B��ྎ�+:bc�,��X���cu4�>#f�%�3ᰢ�MnޠV�(����Y�v+�5%��@�g��e;j ��jF�<�L�Sq������I�B�U��=�{���;l�N�guL�ӫ���%�:�7it�gv� -3�#�C}�o��*�>�7O�K�q�����Lp�(��D���l��Ă�2��"N�:c�)�i��6�S;�[밊�r6��p �6񢈦��Z�i���Б��,�yhu��m�����F ���dz��Yӫ���&$��{��e��T~=�iI�{���R�aH�y��j�B&'F�+-'�m�cHa����=#)���e�4bC�rS}|������858G�)U4w�\B�1dѼu��(��� ǣx�� b��,c�G�~�6/*�)�-�Ǫ.�Z~q�-d{���� �~�S�ky D�m��m�K���3 |�,]�UZ{L{��G:�A��!ծ�;��#&^��Y�db,�Ӌ\�� ����k��(�n��������^�"�)Ê o3��x�Mn�X��}��KL�h�M�O��&HN�`�p�Vy /̳5X���8�C�^�7xy�������)U[,JQ�=F3SP�-�� _v���E��G2� ���"�Q�6?GD ���"�g���љ�9���L�ya��M1m��U��]N�e$A۾3�I��K�)ߦ�9���3y��Y���q �XoewF�t��� �`�nb��5U��D�XAq� ��y�4�U�{�9T�!��*�Ɠ zE�DƧr%7b�A�_�C����k�B�<�V�7W 17�$�j��H���,�Sˇ -z�b��i���I�G����PCf��8��‰y1�J��9�\renj3��C�����z ?�Ay��/J�����S�\E�ĒC�]1���U��Fm���KK���| ���-CT��ݫ� ���f�o��/F��J^�Tq�(MAU���ӻ�hIJl�-�s��u�%�QA|���R2z؍۠a7��R!��� -�m���"#;�2B.U�� -z�'Y��w4a�^c'X�94 e�}n�7�4�cqP���V�X����:���a��X]���d��P�X��~׭�|:'^Wsh�EQ7��.���������$N~�������8���J�eAvW]�9⨜����AТ�^��/j����;�!���dK'�K���0W0B�*��b`�9V]c�Ua n7''C�:t'?(2��?TA]>;.�����Z��/��Qw#��fՏ!��2����^�/�[���U��e��K/���6!z,����|���|A%.�T��5J �\�OI:�nl�/�;H(�j�,��Se^f���hl����ū�Eq e5|%~1mg秣�W�����L�o��������j���ǗLJm��L��*��2w��Gb��ݸ���5��7"H�$�T�R���2�Ε�Z�� Ʊ����l�d�X���C^�"ɓ��H>� b=�8����t����/�*=�Wy��:�6���=ۙ��&���U���@��x�4���:�o�3唿�_�cG���q���m �7�����ho�ɗ���i��χO�?���2?��T=���B�������5���k��"��1hq�z G�� �.Rv�?J��,N��6hN����jy�7��⫃D��/:1@~�ٺ}q�G���0�x�`�+Z��? Weʪc�� �뢅�9�!���ݳ���l�K�:�o#Ì��Vw���ywx������W�_}e���xF����L�-h=dg%�(�Ł�vo��+�H��s�5� ��M����v��zG�$(4x�{d����!�҃"i�M\�5h�ç�=��� -�goה�?�.e�H Z� ���8+7���G�;m�H���JX�J�?,J��b�f�E�M�WϹ9�oO�J����x骘sOvT<�E~�EUT�1|��7�z�Mn����e��pߨ:�E�팿W�g������![7mO݅��o�+�1����2��{6f��U����^eci�<�T��=�y�^��4������iG�0U[�^�_ ��i�����,yM�n^%�˔��xmt6���O�{�BI�����W�@(��O���6��Uz�l8zy����s��g���h�����������>O�����Ɨ�����*�/�����=����\`�R':K�Ta�9���H�:<5a�8�{Ti;{���i�4Wr:�"�exS� $�XHT�2��Bw�� ?=�7�d�c�WG޶�- _A�7p2�_���S�@%CY -���#�^�*��&n� ����j(�):l��ߠI�j7ꖴ.�V��{�^|�gc��|����z�'M�\�JJ�ȂG��s��M�i௵�$׋�)�it�/�0�u�#K�Ck夂��E.dd�c��Ѣ��$���(z\�N'�P;ӥiTcTɈ��\��cA���a�!S�f�b�~Q���IَaW��+ ?PΫ��EQ��a\N��V<Ń7���3Ë́��BY�ϠK=*K5E�J�g5�<�-@����_��A��-��<�ʓ4�w��@.�+�\RHD�?-{��YZ/G^��!fh�Dҩ���k��� ~}Pq�[Y_mh�� -�����M ��im��8�{�/f"d=�m4��xi��e�LOM�� ��>��9��Zܚ��2�a� ��ZE��t���ևG:�� ��޿ƫ�}W��fh��8 �C����17%�G&��ҋ�x����&r�"8#�K�7x��]����R ��K� � �H���8ƴ����h�t�tM&��Pa_c�����#:���������"��xZ;��]kܚ ^���1���� ���K��N�z��s�J��~R|'/O�&�g��X���U*����(�T�|W �fMԽ-���EPꃟV�0��d��[�b�Ŧ ]d�M�պ��ȳ�-��x�Q��:I z���ؑy������Þ�O�n=]�q�I�R}��6DQ?Fc��8�m7 -|�SE�r�w���x��]Q��G���D�Fw8.�~��NGq��U4��iH�A�;f�֯v ~$���Ė{܎��-��5�N�ؒ��Rܯ��'A�fF X5�u�U�"N�E�7�O�H������ �������,`!�L����z��-�� �h b5��o΢5�s���)ҟ�)�w�e�i�@��c�c�6o��?2>k� �/�����KP�:�x������x��Q- ��7��)0�/����W�;�;_��!T+������m��&���4��@x��TQ`¿^������,��e��:���]��x9�>~?�� �7 �ّD%��Q��I�6ַ,!�~����_�>� �mv1��X�����룓��{����yfm�y�%R����9~b�P�����>��]W�e"b��A������H���BKB��$6�>�����^�m:���a}T�L.x�?n���\�CX�v���09=knug�^�q�߯��T�n�0}�W܇IK��ӀjRJ:!Q�j��֩r���"u,�YWA�;qJ��c�c��s�9�����$Hs"�WZ2��z#Pŗ���8Y��"L31%R�4����kBu!7�LЂ+ �����v>�8��.�GS�z�NMn�ߚ=/�%�W���\�z�=x/�hT@\G��&�)�@(-d��t:C�x` &������H�2�5�8�0� G(R����po:��� -ٚ:�c|D���yg���Wl���>|����VvWeN4{@��� 9�B�HY�0�)��5/*;&B3cccNQO��ѭ�[��7�E�y�,��õ,W�u�&����!�jm�7�!/�Q���QHKNme��q�������w^�`� ����������?g���W��q옂V��cǏ��lԸ��v��Rk�i~�f����0O���hcÙe��]�>��e"G��l��4w�ʯ�;Va88<(r�{R��+9�`W�I��dw��㩢��,��ҿ_!�o�!��d��8g�b���b��5���}�V�y?�����������z[��]}s�6��?�ɸ��Xrr7�>����J�yٵ��7���d� E�$���>��.@$�Hv���IM�X���b���`9_�)�]+��0 -;�DwK���=z�Y .-������ -B��Ewg��E<�8�P�:��|O6�Y���0tn�ӥ9�dz���GVi�!��/�4�`�8���R����^�P-"=�!��,ojSƿDܛ�L��_��vv�Œێ�2؅�(t��#>e��gG��1�m-ڄ��t�H�fx�M�����ۊ�N�}�T�;��Z��.k�: =�z ���9�A��?[�cM��z=C�<�2�8�3k�F� ۇEj�#x�QB���; n���Rp��+�VԕOٜj3�x5,R;�'Fl2]��Tvz��d;\��u����ஆ1�t9�1inxtA��U���}Ȧ�������b0|�����Q�XZ ���Ndϡ�Fb��l �7mr��_�O�v�~�֐�&�S�>��-@�sfBP?}�������r���M�2�`oKv�<�L� -���'�C])�'�^�j��[cO<��� ���BZ�.�g;5� �UEm���\��^A%�J}��_�\�������'�w���m-9���Д�6��K�ˀ�0���ؓ������ӏo�5��D��K�����OqG���e�e�+�������bg�BJ���v+e���K^��\�^l9mԴRβږW��ꚾ�q���P���-�o�����(C"r�R��^�z�� -T�f��3����D��V���L6��꿦��4�"�'N�dN&�Fm� ^�*�Q��UW���jq ][Y�򵴊�����?� ��ɇ�ߺ/�%.^�}0C�y~Į9dj�L �Bf1׉x`��y�kn[8L�9Ñ>� ���2PE ���Xj�.�ME�����)[���R�QV�h$7�C [�a���hI"����{�I]1���D��[�zm�7��7����!� ��Xא�|��<�4���m��'���hT�W �����}������z�c�yc�*ج�;����z�� '3ǃz��1��������D'�B𽕳k��!7X������0 �y]  3 -�X�V|��s � �z,��(��ZY(�L����� �8?k�~���]:�w� bx bhO�յ �&Z�*� *a����І�>� gS���c2 ��4���f����ɤ�\�� ��د���"vky��c�Ð�%�K{���H,d�c���:�Z`��p�@�nA��� �]�=�U=��r�Ce1��YX. 94V�얳�u��]BW��^�XTQ0�Buq��� �k�P|���2B�^� -$JY;$���NS1��l��V* ���<7��rh�v~�\������8� -�^�#$2���W��i�o��=�ܱ�9�/����Φࠋj[Q�J�7�(� ({��?U��d��\����M��q�['䇞�1 k5�5 ~zO�O��Ӓi� -�����{���_� akZ��7E|1wf� �)+ j�7!5�}��^�;7�<` -[1� �~�)�3�Vi���i�f��&����?��s_U1�"d�*( `���'T1�n>�����X�7�ߚ�&������V��r@'D�#�d�R��r(���� \��|�ԃ�9�lI�ħ�|c��ɶg��)�Ǣ�E�8��¶���ˌdHwwG�<�Oã���}���]�tr:<ꫤ�D ����� -���r��)kI�g}�lΈMH�R�t%Ϣ2Q�¡���J��ň� -�D|��Hh�Nq�M���ҷ+�޷��cú��sx1�tDr�ZX��[�v�F�鬫adk��V�xS:��D�dS���G����y����۔�=�l�T4�e�v�Q��T� -�g|�����4 �/'��w��W� -�4��L���nUH��8d�<��y�5��<�b�V� ����X�۸���1%�O�N��C�8�-T����?]-�ұ�x�q3Y��8h���Z�D2u�@h'�`� � �蕬����؃���],�����=Mv�����T���� h��k0k>܃_�u����u�_�����ܰ�Q�Ѕ?���QW�]���*H/f�$yI��|2؞V¬����V �_�`��ހ�� -yء�6�F�">�l��yS$�3���"��:�|��������Q��l�U]����1�4�W�u�v��;��4��ΨP�:���z�gV��-�h�Y0�U45ё��Ҳ������ݕ����*_�,��a�k�NO��L�w����#\�C�#IK'�\��>���������3X��`�q~O�Y��'�.s� Q^��s���v���C��i��1�(�L�Gf��n<���TYؾ7�5'Z�ơ��-g�9٘Z&u �݄_ݙ�/��Y�ӚG����@��IlJ2�Q ��6a�9�Q��ayi6?_�5C 5�QN��9�rC��"�`��5hf��c�詨��n����� -P �;y2�=�n-:0d�1#X)�c�ֱ�2x]�y�Zjѵ��g��+����c��u�""5}�C���1�q�tJّ��d(��R Rv.)����۱�K[E�q�W��[݌0��2U3/�S=�\�"G[e�L��3� E�v:S1V69����~L�.k��N���!5j4a��6B$��4K�V�Z6�a��Ɗ����At��;�*!+� AT��א��I�JϠzj5�o�&�I���ç�yձ�~�*��"1B�������ҫ�/,�茚YSk)�-��d8y�kwK�5��<��"��0I &�|�CAY)�Q�i���t$#u�N/h��8 ���B��ɜ8n�vK��C���L���C�B�5��d�7�lHVX�)�4���?�`ĞM=�E��-���Ҋ��C��`�t��2 �����|���C?u -r�B��̅�B��)��\K��|�j��r���,̴+���;�#z��f�W�{�L*eS���,�{��'m�� iSVqeCEɭ9[.���efܴ�,5��&f�V�<�5*eqRZ��~oe{�UeD�U����^㶔�f}�>���*�7g=d��k��|)A21X���QkI�J���UT�K]��s�MGʀ�~� �p ��E��♭�U�™���.l�@�v��Ǔ�Q� EӰ��0��b{XYț����\4�e��Z�>IJ�e�������i}�)� -�1�(?�m��5~Rk6���K#U��>0_�Xi�ƴ]�_n�6�ڰ�C��yR �R^C���L �UVE�p�X�~����>�$�i~�*��/�#��c�To���a8�ec嗵�~#},��7V+�u�� :�]��%�� !�G�x���[a|2�.TJC�V��*9� Pї���M4����ݑ�H?~+ļ[Ö�꾍�x�� ��: -q��WJ)��z_AFl�IO%E�e�U��\��vj��ז.1�m��lY��d�"^�Z)�����`� -]5�v9�բZ! ��f���B���V<��[y?��|'|��V��%��_�Lq��¼&�9�n����NhA��R�������cUG.��z��L��6�J�t���A��T��=�/�/�_��מ�h,ʾ=�Yŧ�^|�5�� � M�U��u��2�!^�Z�#٨U{����-�T⹬�)�C�?Y��u5@{[��|��y��m�m�߮�.�|?�pO���q�ů�Eqs+<����n�"b+�=r���=�-5�3w ��lj'��s|ϫ��|�jf��F ���G�Q����'�9ě�1igO1T_X�?�n�a�K���7���*���]򲻘�Y���?�E<�0��p��uV�tH�d�H�FS,�0-��XDQ����9�W$F(��:�� -q��Cf�vJhWR�ݍ�ٌ�hQ4X��l�� cl�'!�j�-�� ��g�x2��P�X�%��-�E~�u�i>J�p��L?�'Yd&��x���z�P�L��%�"t�l��j�R��Z�ʻ�R43����S;�* �ʶ�����c���[�:�b�FQ� �.�|�3�� ?`Sߖ� -C�P�c'�0�dg3�����y2�Ƴ����I�b�ꤼq ��{h) ��&��%��&���ٖ���H2�E��[��%xm.�bC/!��������*}������� ��z�����d�i�4�#�^�a~��}�T�b|A"�N����h��ʥ��b䦋�e���8[�Y�nPj2Z���F��,0d��^>�(�Y^�-/�� -!�֬s�6^�-���SM��.�J�-]E�匪fnftJQ���v��E�z/���� ��8/"<Íҍ*\j��M��� �7ߟ�5T�?[��O��γg��3�Z�0;�W"Ca�M2��d؎���T,Ѓ�~u�9���:�U�R�WQ��tk$cY -O7����I�!*"�X�s���+T��/nͨ;�B�;1�NVRJj�뭄5������,�%Ӄ)*ӷG��}ks�F��w� -X�R %[���Ρ)*a�,�D9�-IW�C k��e����t�<0OT��V�˪�X�LOOOwOwOO�o߭�Wќ�Ҹ`��*�YuS=�X�z�ճgY�d�*����~u%+������d+�8�:L�qʖ�B Ί$��۔���a�g�4.˓|΂=.�,�V����j�iU_\ �2��NW�o�,.�Z����b ��U��>��[ h<���3:+XU= X1���,��,��*:��������xts<>����u��U��Et��4�W���y/lV�yt�� -6cs�͘����.$�}u�9�����QPX}[c������p|>>�:��������-��,��8>U�g��������d�n,�����s�iT�q-�Ǩ`�^'�n��ׁ���܀�:�Sh�?B���g��aM�?�q6�W%P� -f�����^yʼn�]��]�����$�pY�D�C�,}L��k���=P1Z䅶���~����I٢D/�� r$�)��Erw_)XR���ζ�����2�^�h�z�: A�����ݏ:�{;�DL��g��N�l�NyY��R�v���C5tz�����v��b��u�ֹ���`=U��$:m6���$�_����=�Z:M`����ի}$O��NS�~��d��Hb7fs�#�+���?�������iᠷ��Ou'�\<�����7��0NY9c����ѫ,/��/�eߑ\�~VdyG`� s�,��l�%,w9�n��t �9������� �mm ��"�;������N�7iЈ ����Spkh��:�uoz����SX��;�X���O<Ý��I['�~��FA��q������&,��2�{�[�I�T���e��;�վ��KP�N�u�k['G8 -[u�t=g�Ya�o �}��{}/��:��q9�ѺϷ��D�o���,݋�~a;���1�~e��� {H��4D ��\�>�Y ^�U -��<_��Lj.;8��E���r �gR�+���4�q�8�8B�!w(�ٜ��0���&�鞁�t�2��)G���hg��Q�ϣ~��dyt��qZ7C��Ŏ�dS �D��P�ч��O��(_!1KX��^n-���~L���aI[F�� ����]�-X��������6"r������s�IY��Ǘ�����,��u�Q�9��$C�B�ьU ���قg�q�Ʒ,���R�4�jP.n��\^���}8�x\ ������F���c3_�HM f�^tq �����|M��[ @���HD�|��Z�B��+��C0p�և��W��΄ɧ���d�~���}sǪ����|�}��6�������Z>=�>�� �Y��,��&ˣF�)�oDm�f�� %A_ �PX�؜�b��Z�<@�2�(�b�q����i����oL �p6D��SU�����]�zϋ,u"�̈́�K1��r��}2�'�$0��|�X���ymQZ#�(�`���d�n��/Q��e�t�I�x\S �u������}�NG�P@n�A���P,edr�^A���F�fp&�h⛍&�q_6NZmݖ�9��f|rxӋ��eu�G���C����g/)aZ��W��5n -�ol���g���as� C"UAASd��bӾ�$��I�O�� o]e[�|�E-�u����2���g�}��}�Q혭��h�-"n���f;�'կA�Ξ7 �� -��T�|��th��:�~ ��6����{���7x@��x8��B��'c��+54Ί���� `�㠨�� �AQ�!�#��=O�=��M(��t�0�H��j ]�n?� �.2�������Wj���X*5':��~�%ܡ� -7��T�O��4�&�N�R. �w�6<𔹌�+<�P\���t'?�D�)����7�N蟼-m6�[��Jh�_��9�&���s�]�-���,.0F�<_1r��F�U|��3w��a�|�?���`n���?����ѷh�W��E}ֶm�`� �lWV�������3��B��J�A -�f�&��+�ɢׯ_G…B�mX%5���y 0���e�H=�&�P�pQ�|�o��C}��] F��Y�Q��)����[\}���j%̪���4zH�{t��ɗ)�i�� +,Π��m��5��!�^=Ԁ,*��sx��!Ź�_o;��|9It�Z$�����]�%g8��r��OP������=ǂtf��Lx˹On�=y���� �����ŠOO���3Cu^���5~Z߈�%k��m�XJ�@��0�0+��#� �/�f���D� �VO�2�0�H��W,9�l'�򝏇�J��(�F:ɢ�:��-�ǔԾJ��Ƹ���:i��P�^"�I��g����!r��ە��<<�v%"~ⷁ֐a���q�w|=�L�8��9�~=Kx�!������Cc:Mg�ux�%(� �d5�c��8O���7�;�i;��M5�WomhTp����DM�ъ v�םA��R�j�&3�EBk@f�XŖ�ko��\�*��'IL�l�W�SU�%�k���`�J�� &4��g|�g�P�)�y�'sj�Q�Зz���5!�c�:�h��q�Q�d���E"�M�f�o>�U���� ����`�:&f�pz��Vx_���ܡ[+��ڋ��"*s�>f�qYJ� @����$�Cf���>���yԇ��w�ɉi�% ��h�^1�7 �b]������N%�\J��PZ�,�F��E��z�&N��⢢�6ph�d�������n��Ix��#�� �f��,��4G��GD"�[k�C�hː�tx����@�ֽ ��c�@OYe�8ݑ��5�;%���O���d䁚�@ ���W@�dʁ�j�E(�NI�Ţ�LC�#u���=կRD�?2�&!�ڂc�nq�\0���8Srb�� T����cռ�˔���'��nz�]�hlx$�,46:W�����T�f쬼�����l� -�gf����N__n{F��*/�lZe�I d�<:���+o��%�h\�{+�B~#���8&H=3�S����@� -�P�?`��,>c�ŋ���m<�`?�8=<����P��X�>z��Ӵ�$-_�a�Nז�(e�u>�@NI ����{+��ؗ6���g�S۔�2�*��{�����?�� ���F��%^�f�I�K�q �=z�E�h�5ך��Y%#��P�/�RyN��vCg%7�&-5��v�v6�n�`0���#|�d�M��bu�����i�� �=����=����lь-; w� _ɴr����Zac(_2��r~�B���Nu����>�u�f[߇�� ({� @����BD��Ic'����������`��Pi�{I���u1��2x����<{\��Rެ4ur� Vq;8��Y��D��,�X綮`Ԃ�Y�ET'�Z��0�.��9ٮ���g?�ant�M��+��� ����d�������"��d/��*#�)��k�:py׬y魼^d��~��Yu �#�' ��w`ɓ�|�5�Ĥ����x�x�6�3}��6M�����y�V��D�}'�����%�P ���~� -)�_�� O<���]=��G�u�Z˘}�x&��n�ߨ�;�s"Cx����G��:��^���,�Lã&]h����N��j.T���Sؔ�>1�4�>X�3؁��f��[˂a����� ��^��ơ�sG�a�����X'��//�6�����+f(� -�ی�3"V��*����x-��(�.w��_qPk��gh�y�io�7&�+�]�jx? ��p�����ݽ'��g�sh�x��Sa���{N��p���J6x�ϵr����y7H�A P}j��_� �ވ�=���_�hX(ߤ�,��^�S,g�M��os��B+y���0��/�b -Y� ��˺l�Ɠ���Oц�&L���Q >Uz���"�Z�Kaȿ�uQvt��F�?�:�X���%���� �~̟�3��(��>�V�O�@e�����g�\=� -�d!y��J �?��AT~HV+^m ���a���嫊O$8�%�~R` =�e6�0�v��4R <44�W�}���K�>!�~?�K�~�!�s�^9��>�0�U"�ɋ�˓�T�ɥ����l�s��E�E� �R�iҍIQ��6o "�H����ma^9���������EW�㊉�o�U��#�tq�e�5X�`�(���eW2Z&fa'��1|x�zK�X��ɰ�N۴�'�q�㉈��F���灟a|L�u�>�zp�� �΂(��1 6�o -���Mp�d���b4�s��4�1���b -�� ދ7�P"�v�׌F+�D�%�����*��.Qg���@8��k���g(͂u@��O�u0Hvസ -�b�� �0���bɉ�_�Rn�(�a��Ht��[ޙ`�x������<�$E�C) .����ޑcR`���Jm�����7�VLW����;�m��D�rc�21-3I~'��{7��%�A�T�+��t3E���5dy���BQ����.� +�{�EBv���?�v���F�r0�< -��7UD�9� ��;�/!�ڏD�1�8@0Z�^�H���-�iUH�"�6W�n�� ����hs�`6����l0�� -n�'�Y�L?8r��ç�V �L �2�N��+.?t]� +H�����NTԢ�F��y�X�Z-3��v��۟��^T����"�X?��>�QR���[����]e{�����1mļRA)��թv���')�&;�L�}~�6�P��}ڣ5qH�������x"���+-�H�C-��w"y��}�P�s���ڣO�Cpձ!���0\��O��|���-׭�=�k�c��Nh�������5x�^�q��"Ƶ�dW�0ᓧI�(q�̓�hf;�����U�B��-O���t����o"�� ���z���O�k]��[J����ktI`.> ��$��Ǿ���t�M$��_H�n�����X�|n���C;D�a��΀��s�ߤ� �Q~����g㛿��g�����||v<���V��:��5&!��Hl=lR��X��f��3,�kI�b�]�๞�D��P8*�?׻�_��yhGQ�>j=7P�t�ܜ�r��0��z�����l���[Le���M�~��S&��Ɠ},!���X!��z���j��`ۉS�r���fΩa=�����(M �}������n:�*�-�j��Hlh�a N��J��E[l���xX�� ��u���G�k>�=�jK��Q�H��O��4�e��s)=ћ������J�5Fn��F��n�8R��Y�hK��������j��L�o3{��T)2�r4�kʨ��D��a4�"]��>K��Eh��ʚOwO����p��{�"�o����m������[�mg��?���&����M�� ��5M��K�9d�P88�P�!d�̍����]`_ȴ~[�� �AC0O MJ�Xa�/}6[#�,���i��F� 0��o'�A��r�H�i�E���ni�@��6bm���$rNץ�x�@κ��u(F�m������D���Y�j���8�sI�܆�.�;��<+ �U!h��-V��HQpk3BW��p�U(�Նp�Ym�1^�վ6����Kx�{K܂��h���%?8�&��܉�꾄Ql�#ޜ�CZɜ�/fc�*�����$�X ѦAh�/P�� � �^g�ݰ�=I]��ߝ�� ����>/D��W��*FHy�_0T��hEBJ�q#�������_&:���'�ZSo -�_���/��W�Ɵf��e�����*� �!�e!J�9zx����.�-'��pp��t]�!�*;v�"�&�S�Su5��T����to�֓t�S XG>QK�u�������0:y� Ŧyq�������aZq��RG�P����P�ױ�# ��s:wQ_�,�h#�����8�X�?�e�����'(A��b賽�=~ /�wWV��� �C�$a�:y�M,>o��E�F�Ŭ�v�\�i�ơ]����������}"�[�����Y'4ݦ�F���v� K=�N�%��(27~ �c�D���܀B��P��*>�h}�_��]��$ @���#: :��P��V���H�O����/A:(�B#�Lg#%K�s�g��������C����?X ��tPzF#��B�����j$ �`y+�jk.�6E6�Q^�F�E���\�^��~��d�� ������S�r�ϥ���}y.w�� ��;�����t�$��DUb�����)u8>��j�Tf���{{>�oN���-j���y�����v0ؔ�*�� ��ZkR ���o�����9�4���ҌOFó��0��=������q6�W%���g!��w��oB1�R�>^��7��p�w�K��%N�-1��,]`�o�9u�͋��1�M~~CBl���z�N������r������D[lv�oQe���O[�A.��G<�Ho�6�>m��ȟDvR����@�ǃ�D1�C��hYռP���7�D����HV� Z�v�Dǯ�������H�s�����DG���҇��x����R���_ -��/����_��%~6eY�0 ��w��g�^X�ފ���'��� x�dXFY��~���s#�Yԥ��"�/��o#���C�(Z���zQfa(�U��k�6��Uc�6: T�N ��-�����x�1+�nIuw~���HwkM黇��Э��r[�S����W��X�.�>M�&�S���6������S�������h�>�7c� � ,xF���� �p�zB��Dvom+�;nյ7'�#,��aș�;5T -��,hQ��U��ڸi�Rm+�$k-��o -�j�Dq>�52S�0��S�k�}��6;8xwz89���o�޿=������*}���D�u�x~z1]� ����-� -q���b,p,��q��'C�^ /ԬKb�_9����8b1����a>���dx  $9�ֽEm-��!�uIE�e��:�=��}��S_�tv?o�5|�ai��u�����AX[�֥��E�J�Y�V�/\�Y�nK-��sR���#��,�Š:˼�J܃2��W���eb �ox�p��6�c yH�*�K���º͇��n�7���|�'V�Slʝ��}d���t_Ml��!���M���2^t��N�:�+1E� G�����X�Q/���*Yg��D�C~�#~���t����.�-k��h?ö�5��N����,Y���3���fC�5��D‘d�lvp �4��.{㢇��s^�@ٯ�cQʎP��0�3*! %V7: G�z�z���X]�.>�.P{��x�A1�/ cn���� ��� V���fW 8~��L$#������F3�{�v��� ��N c4�+��I`;����R�:l� Km�ē'��q��B - J��t��[�T؃�cP?��}x�|����4���t'��x�i�f���dQaA)�;��5�wK�(V���N��ױ�߃�\�@�VG� �'yx3���d�8-��a���OVI���l��,�h���C����*�Sg��y;�Q���1��q~��m.�yq�N]��~��*��ȿ�S<��lx��"�x��M6Q^(.��� -���&w�O?�]�^+�i���B= - ������c�M4u���F=4�!��T�ǝw�ٯ4��w����TJ5�4���`�ff�W�Q��ʌ�a�mX�A�L}] 4Y0�/ȋb%@ə�(����=J�����0K��?�Qݨ^��0_ߦ�|&�ż��( ���_�{Y�p -֣�Yɴf���(�b�^Q��wj�τ��"_���4Ƃ8��u���Z�@�j�VV� ���8��I1� ï�|u�u�[DŽ�:jy/sk�� ��#/����O5|(��ʬa�a�[�P���� ������!��4��y�?�[��B�1_i�5�>޼����7ٺeŨ1Ur|��]��f��������C����~�� �8&��뤏�����1.:�0�(tئ��,`j2T+_S*z�� LF���=��߉��u�M�뜦�E�9���7墈��=X��x�K�� �$.���I7�\���������}^�����A9��D��:n�l:r&�u;�o9��V��W�=�^� �T~������GR����5Hf�4A�6�7j����Z�V�g�V5��QMn�Ұ�<���d4u�]���⃽��#n��]�.{⋩�^&��WSy��K���ϷZ/���v�۩����4�����ޙ� �����KP������O���\���V秝3�g�3{��b�wH�.��=�ގq��u݂��5�q��엒�-�[&Rhtzr19y?�H$�>�`��N�ռ�nDec�����ѷ��0�-��Np<�0��qx>�U��f7�{�}y3�wviD�||�����wW4^��d#�4�FCLn�����;���DD�|�J�� ���q�rǿ�ȕH����b}��m`7lf,�|9k�ns��`} �jא d�t�{���%e3!-����������ϙz=�]3)������� ǜ>+KW�0ѣ��t�u#O�,�>����h����Th�B4���j�����&���wK^�&�$-=; �;`f`V�6p�6��-��3V[ʓ��7qq��t���@��hn���eB�It��a��*�m���O����I �U��Ҵi^;];%xՏ�=e����Y�qJ��?�[�g�9W�����A�"��[�,��Vi���[)���R|��q���a��W����S�d��d�ym��&��V� Z1��F�W���nv��'�c�����@�̡f>2:{��tR�J�/(L���e�v��;4�2�ذIx�GИ��1�^g���jon�F�M�.�ư�Fܭe�vG�P��T:W��vj�TM��4m�{jo<�1h�HYjd�X x[������}�6 �������r��u���t��nd���i,L��O/Z���r��dt19=i�����ys0�+/xsQ�&�� ^�!�cq= -o�݁#�����&C��<��r��s ]km��׍o̗��}~��=�AK1���sl�v�ܪE�BA��GA�ɤ�&!�u-�7)���7��|o��\C�c�Y��Z>在<>�W�[(X��q˞�΄Y Z��1�tu� �����3=�=�c�ʧC�u�i�kti���� ���=�ΙON�ϚB����y@�� yª}1M�������ԅS -LX�p�t� L,�nj�Ǭ�>2Ԩ;p���j��7��C+�R��P���kJ D��_�e�OkA ���)rTi���h�P(=T�B�3Y70�&٪�~�Έz�)��^�{���&�#f�e��e�Dz7������-+4 �L� �Jn�Y����i�V�ȏaE[Tcx���Pzў��i��s��`-A3��O�̻�%@dO��4}�и�H��h�L�S���e��Z�S'Nk�`� -�k��``�֖M���=�G�*�� v� �v%��oֵ�ܹ� \DU��v^<�Jt0�������v$�pݏ�u�Vmo�:��_q�T�t*���������h���N���4�l�����c'�Kֲ]�[�yy�<�8}�"Or�0L��@�C33���}��<8�I�5����� ��bU�;����'@+�5;U�Q�,��mWl޻�RًT)�O"��0Z���J�{ئ���.J�Eu���e�������*+Uʴ����Ca%o4� -a�� 1��l?�u�c.BҌ:�c<¯j�@��rʵF�f������ ?}�'��ۋ�����M��v�법W ��X3t �j�/�/������ ���M��5}��=���uMx������;8m�W��w�ӎ�q����忁Ϛ�qo]ԏz��m���[����fR-���lٔ��[����_�i�B��hf9�/��vT�W�+�n���Y�s����b��k CL^�_j?�[!QI��4F�����6�ۻ{:��&��)�nwo�{�{�_~[<,N��ޝ�;�ċU�RhL���Ç?�����?��D�B/z�I*��?���3��J�Z���p��'��{����4 ��4�#�"��C���Ʉ�7� �L�d.Z��'��La��4�x$�E2��Â'� M��$~ -|�^�8� ��9�f0�#? >!��<=Wz��n�i��$��z)Rēz�,����M喊�4��V& �$�@�S\8�KZ᪓� �<9;� �Z�K� ������(�~�;7��]����U{�kҊ=\R��f�zghh6I ��0 �]����g�3T�v�L��0*#�"\x�PY��`�r��۬��ͨ�vD%�8��i��0D���!�d��e�t8]��]#�F�@%됓:C��Iq�G�������epmY]iy�ٷz�9`X�4��a-\��Z4�R�j8�㫡���H�n�̶�W��&�-��j�ݕ��L��e��$��!]т������L�d8���;n�WE{��`�kC�ff�ѬE��t�5ehٺC4z����+ �t� �ܒ��Z�V'�3bh]��u��e�|g|Zx�Go�i[u��OpӮ��E<����Z"�]���i��jp��U"�bf�����邑=�����C�.�������l�ۘ��I�N��Df)��}��}-��?NNH�����d��J  &x�!%�f8e�+��9,>��H�~5��� L��$kr}_��i�*M M(��P��W��_�����bM�m/~��-��j�����Ά{e1�i�Fܱv@m�D��,���Æ9�,34����hM�s-�{ Ձ�T�5+�*CP¶�TB�0����e��ȅ�[/ �q��~�C?˝�^&|AK4N��X���~���� ����!�͋-�ߪ��oڭ�f�y �^���� ��_���a)l���1�; ��@��ԑ�WπOØ2���(�A{cȬY��[�y�>?�Y�aݍ���������޽-�%U�,���%`��U~Tvͣ����藡���M��~�-�³')`�B�ה u�g�po�Vn�(��@���'G5Jq[ 5��a��-�y�rD1G�߯��U�q�=5K��s|8���z�#�9�^�_̕�Ŝ���o�Ϻf�,��痽��*�kX���R�6�h�B,B�3�����]Tv̓g���N(�1�bT�Ю ~kK�)[�4�m�'��է�Ԡd}8�s��nע8�c�d=�Ơp���L/ �~��Pu�@Q<���PŽ�C��qS kW|<�1�ɪ��2jwɡz�� r� ��7C�S�Hת��V���ci9��UP�c���7�.AZ -T�(��C��K)��QbS��ϟ��"}�,,#�l_+��Y�Qf�ڑ���OGK�ٶ����ꧥ�#C��E1?|_R�Q⯺H6�b�]��:`� ���;�/�HUV�j��z+�ނs�>A�e�m����e�<8���q*� ���������%G���'��yv�k���ʞ�������x�y�U`��e�9ا���L�:���ݓSka�B�+ -W�I�J����/O�� f�e�6���M��|Wq� -��wo�Į;ɭ��Đ睌���`}kVh��g��tD�LUI�Rz������ m���HsFͽ1�>k��� ��=�xN��#��V�����&�gv��S\���ґ鈵�Ee���ڎfw�2jr:����k�u���i=g���[ ��|ٹ�_����_�����oůU�K�#_��ͫ�_&X� ��yxʡf�4R��t��~_�U�|ƫK� yi��������.ٚ�3I�֌� d��EI{s�t����g�� U�x��ɷ��Vao�6��_q(V�.ܤ+�ei�E�蘀,y��4@���蘈,��,��wGɋ�KlK��w��G�e�ٍ�߾�[��ݽ�כ��>����� �2�I?��Qʒ$[�<�&(�*�L=���q�jF���pI �S�X0\�m��(&�<�C��~:�Ĭ�g:("v�3���1]p�&��.ȇw�/<̜Q�nӐ[�8h��Z�s��sN�;g����WE�/z��'�t'�y��Xڨ���eU)��ųfܱ{��r�8�^1��M�%N��:f3@ؽ��x4r��+H7J(e���?F# -u�%�Y6�%@����U��s��a[�Z�8��ȑ8 {,нs�G����Q��2=֭24��-Q��V���~�u�u�񞰖�W6�����82 _�{��&�2.J�z��/ọrW��z#���-��.��5/,y�>�H�t� <�A�x��/ٺƊ������]{���ʻ�/n��,�wH��@�˻�=�����kդ�;5�F8)QfxC���� 3}z��i��o��X���9Dm��c�d���w���B��O^��Pkٖ�3������r������џ�V]o�H}����Zɮhҭ�e�� �x$ ^�I#�e ��k��Ѫ�}� PS'm�Uk)�~�{����z78~�r@/�)vw*�^W4LF����^�y�ǟd�\�x#�ORU%��>[v�G�|���͆�uIJ�R����Z�4++�-�*+ryJu))˩,j�H���r��hU�mi�mV��P�Y�m�4[e��,�S(I;��YUɔv���R�TkQោ�ͦ���kJ�<ʹ]i���z��{�J*V��H�]���j�bY�hQ�T^TY"��A%+i��O?p��B�d#��TG?@��=^:4�6���7�6״H��+Cz��ǨKE[QI��M�/�)���%�Oї���:��J ���b/.[���B������ԍ�L -�y -��=,ۢ���)@�iA�HY��[tC�k�r'�g��t�)�^y�he��$��`_�!#������̥����`~��iL��sY���X�㐏q��gv�gf�@f�W�>�CE��gs����sY�}�[��?�> �AL���q`5���Lh�Bg�O{�=_���:�!m��a̝�g���|΃��N����|��#�@\b̏)�ڞ�`�H���v8fk�=քC�.����oxHϢh��_������d[x��? �AN�=�ϑ����vt��E�fx0Ѯ��8�y����k��Xx���D��E�,�mK���A���"�?��1 ��<�? �`HmX����79�� ��~5�]N�Qv�Ś8[��@'�k"*��{ɒ��=~�|�ii�]�Lk�<�:� ~i#�B�o�l�k��-SZ��� ��7ʦ%�o���™��w;�l'�O�Z�m�fJ&شwQ"�\*#5��2��q7���t{�������68�� G.y�'Dǃ�9k]R������!x�w0Ц�$s�M� @�?Q�3� �$rW��%-�>�p�ey��Sy,?�g� n=u�JNk�Kj}�H�:�ʍD -o�E����:6O\eI�ɍ5�0"?��LK�x��R� �M|���V�@_{{T�%���d�1�c��/HP⮷z��wB���,�[�'O���Ci�~J$�fH�k&��J_N� ��!���JlT���iv^ڬH_���w��PTC��҇ӫ��o����L}���V�}���p4BOP��\�ӯ�w�N���W�/�����O� -�{�:/���v;uT��?��uj�C����I�=f�J1���Z�Q 2��ZЖ��D���JaP$�����;Bɲ(p��b�9Z���G�f�<�h� G��_�j�ʇ����?lh)����?Q�ŝ�q�/[�p�c��Vб���U�����0%˓{_�~�`�4�,�)�,�J�OE�pb�_��Vmo�6��_q(Z�������"K�M@�~w��{��RҴH�/�jj��`�>�O2y���� c]S%kY����Z�,��*_�M�JJʌ�ZR^R��*��d��I�HU�j��fK�2_�6�SY���DX3�$�e�˛Ff���}�a�l�$p�B=�����rmW��l>�q�_b�ImA�*�v[7ȧI��M��^�L���Siuh����g�̞E�i��;Y��C4�:�� ��ZD�?D}��J۝,C������vI#�<)� 0g�� S�enl�N���Y���I\��wX����GZK]H�D�,3��IJS���&�e�H:Fj�iP �C��{��:�m�����Uv�V׃L�$�Ytm���^��w�K�9��&��ED��sY(��]��Qȧq���-`��<�l����UȄ� $�\yxp�~ę�����ܟ[ �f�Ǘ<�fX]h�c -f�d���֞r�G7��G��8�K�Vvq'��P#��pF:E� dz���'~�]1?"��=�Ō��0] 8e֞z�s��]2'ҙ=��� =�Ċ9\/�g�����d[ �k =�ɵ��9���}IN��<�i(OEģ8b4�0/Xx�&>�C\,�'�mi9P��XOc�5�� �x�����k0�HmX����79�� �Ѹ� s]/�q��iŚ8[�!@� 5�|F�d�gs�ϙ�0- 4�5lbJ+�B����� ϱN�\b떃r������^q|�lJ��|p*bgѳx��$���I��l��B%�9OZ���<��(�O]�,���@tY�#���h*\�-D����1YR��B����+���<�4)Kt��G#mj�$�6� 4>�ߤѽJQ��r�&[���z�!��@['�Ia��>������-3���N�u/+���.��D��M���:5_̈���b����e5�>;�����I\J�`Zy������fP@���y�m&� zL�mU�U�����z͡�%z[a�*�\ˁ�����]�lhӖi�O���� ����&/7�Ω�tk6���g�oK������{��{U�;�� ]&��z2_�ٌ'�w�Y%�V��x2��m�O����N����ΞпuD��m��Xmo�F��_1=E ���K�\�;��*��m.���� �g�t�D���άmb�|�N:�|fwv�g^v���,�ˣ�˗G��b� ��<�V؆_������L9�� ��e�����T�;?��f��>�@r��=�:�N[.�b��x�g�H!H#��8%rr�2��@>�Tȅ2`gsR�<����i�� ����rg�`)�}�C62��QO��U�� i�9�-xvV�'��i*J�*C����`"�i�b*Yr��(+HP!�N�Th5L�x�e�4h��K���rD��A�k$�|��L�^)ă]��@ � �2�8}��H�E���,ɤ���� K��*���T�E�N����i���r�,Dơ� �E���Q0��4[a6Tʪ4SKR��٘�ORz�E�)U�Ŀ�<� ��t���u�Y6��-n2�;�[׺������ip��]�7�\xazx�.�3�[`�G.�� �x#ַ聽g���j�,j��oc��}���%���!h� -R�k�\�*o��|�� .g�������3�5 O7���F|Ӡ}Ԃ��6>�ƞE��.����G���m$�B�&�h�[��d9�-�%2t( ��b��a�u+&�L��C�~]�"�~�Y���кdv�ѮC�n,��uj��G2Va��D�cr_ ���t6th��s��"���N d�*�W�q��d�� z�}��v-]���2?3���Ϲǹ�08�z+ȱ�$Ua����c����нo쓩"H�,[�u��ժ#����G�Y��Q݉�N��2z���B�-��[I�m�-%�tB��b/S�rw�E�T ��GG��!=|���舔�t��'�R�~d��r.��j{C�6�z������-;���@��tV[�n��=�c���/R����_���}����%�a09^=zM�$�� ��](�_ -�V�0 �F�ҡ�~��� �< ��rw�W�k�Uk#kP��}rz�x�9��^o�>D�� ���!"�.)��ޅ���C��(n�HM�R��^���.H��I�u���z�nwO��~k���*�)���sx����b;�w�8I�>�ժ�������h�Ԁa�g��i��$Yj��n')f<�����TWTzP��QN��:h�A% s%��� -)��R��}��C�,f�l�|�ȾQ�٥��� )w|����Nl�/I4:H�j�֋�sBS/v�,8����{�^��o� �z�S�D���Y��<��o����g����4���*O�o(�%��d�:�7T�D_1�żk���t��/��@��5��ҹ�I(�M�b�[8q�,HC.�ڙA��o��3�uz.Vx�V�{e�X�r�%�o.�B��t2T�i�S4NmҦ� ��2EjC�h�,r�3���D��og�FS�NC�YP��-���� ���-���:Vw�+kUs����D�=�xg¢�A8�� P8��/�m���U�{1�S��}>=�4Ǜ�D�#�F�a۴�����N �������j�U�@�)����UMo�8��W zj�n��@�݆�h�XYRE*��^d���X�ARq���}���x���esp��x��q4���;�&��#��P�t��Yx]]�/o߽}�?�ѝ�����>��|�>?�k���&�=�lZ�e=F�s�n��ͺ����j荄��z]IoY7]��`�tk86vJ����Ъ��4U��Yj ���ZY�A�Ǧƃݕ$�����t[�TW7.���V��'^�7js&U��{c�["Y[�գs���m* h�!��=:���]��V��e�J=�6X�B�3����DN�֪�[�Y/�'x/ -4����)�������F.[Ld�s]LW��{1a�zv��Kis&ҖO��n�����)�� �VY �L�W#IGؠcPĨ�=�4���cf�rs����>�ƫ͘�NĂq��Lܑ���<�e�`�B'�0�V9�/,�8�9�DhMDΦ�H���p�|�_��d�>�)����Y� �$��X�EĒy���L�l�F�4�џ$C:�%��>�)��X��3&Wq�% d$,,b�;��ȳ�Sp-F��1aK���zK|A���c��:�)E�dӡ6����u�| -QG$�32w��;"��w��������Y�9����@/�q�9]:���A�b���0O��+�i~�B�?@�r/\�i�E �QP5t�yZp��sX,4ϋL�4�B�P!dJ0;�Z����J��ub���nAюמ�U�#N���2����YH�%��T���7x�����F�G��MO�@���s �)��d����D<�V��Sڸ�.�a����j-N����;�N�^^�\z���%e�9NU�M����=��Zä✪�����r���KFC�� �*�c��ZQ���Ї������ȑ�Juf[6��*ڨB,:������Af3E%�J�?�68pG@GM�T��)�ܝ9c��9e������؏i�u����, ӣ ���5I�2��`g�*E� &/��o< v�{8m�:���� ��'d�*���娇�Xn��o�^c�wU|P��[��/�$�v������?>I]G�Y�Q,L����G�ӹ�vz��Y����,��˷���~��MO�@���s ���Zo~k��AMlO�H��iK\����D���ŴB�ƹ0�;3�>;'�j���h��N5]\��ҫ��p�3� -�P -�,Ws��@�~#-�EU�QW1�:�.�L�L3i��n�׹�ٚY�ֺ�� �,o�l���/�"�(%�Y!!My!��%�A����*3�ٗN�f����l{�ӖL�-�g/��_�)%^���wJ�2ը����3!�?-e�8�Y�z=�G�4� �!9���A?Q������שdZ3g�"��v�O��.�(~�/g�����f�j�5����>�r�єºEh�����ɐ���◌B���Mz�v��+�`�0>H�.�UlX��I#70v�.m�h>I��m�����TMs�0��Wlg���C�}+��v�Kmg� -�#@�Bh$�6�����q�6� �j����.o.e!G�VLK�2�V�c*�?|�]-p?���S��)���g_Π�$gFC��( Sy���Қ��\x'��8�q��Z���3���*�����ֲIx�BވԔ�*%��0�$����R��R1�-�eJ9Oh��"[C>�w���>�^G�ȋ�xLa -/mӏ0�#�$">:ܧJQK��>�{�a� %���n�~N��[�TԤ��!�*o�a{E;y�/+��֏w��,&��4S� �����ׁL�A��TvÞFn(k]�yaU��l>*2ػ��'�oˑ��q���r����u# ������ӥ�l� -���;��� �����1NsxUj�L�d������4A��sH��=�.��k��C"����ڱ�ș���]8 y�����,����2��(�����㶺�۴N� mm�Fx�0ަM�m!��i�l\'Z�w���>�9��z����N�0 ��} -z誢=%�p@H+�IU��5R�ZN:��ޝ�����\�%���g����2#z�$$�K�5"oۇ��}]�G��������mF��z�أq>�ߏ�!� �X�e�C w{�`+�K��x'V{�r��\���i�r�F�Ex��b=2盏�ZIhG#� 4���:��#��a�({�l(��4c �x��n��/����X%�°�6Ϝ+��������]8��I:���2�/�0{]ݲA�wq��X���1q��٨Y���5� ,ݶ����� �C��w �V��wzî�?W1��=��pP�1��5� e0��R�:���=ùV� ��-��I ���aO�8�`h L[bװ;=͸Ի-t��E�;`_h�ޛc�p9੎�j�==7�w K�8<��SyD'{ -��c��7#Ҭk!�"���:D9���v��c��AK��E� -��;n�q({xj;�3tt87ͮ`�֭/FG�?CϴqC[WЈ�)|Q�5���ӡmp�8��wt����H�2��j��\�}3�eZ���!�B�� �q���s�4N��v��$ZE>�J����{ƹ���|��@W���"�,��2Fn�JC�C�X4�-���"��3к_ �|.,R1���Q{ع(�/v:�:M�1V-\ �HB��dLq�9jS����y)�� �^�w�v��dQ��WP��%8 �NF��4ɲ�HU[q�c����t�2L�g~�xia�8���� -��(%%�K����SkÐՑOˢ�S���"���W̸̼�vv8�h� �g'$�������a1^N#9��$�-�x����H������N(y�'�aI�����h Әe8L�8Ɋ�YZ�e�c��[B�I�w�wI��"?��2��ԛ Dp��8��!l$� �*����B���A��M��x��A43A:!"�h#��kQ%D��i%���ͽ8y@�H���ʨ��]�S��,���z���d -�bg�3R�ր/�]����Wx꘥ѪPŝ�=��Y䉃UV�H��2 �#(�&R��B�xL3��"�@XֲF^N 6"l�#��B��P�YD�΅��$I§�_�_)}�VD���{�Z�-T��g��} _X�p�:�`%Ob�a� �a��C�٪�r��^_�sw��U�U����os^&V�#��G�1����T�4�Ԛ��������t���;kT�S`�~pT?ܓ�7�߇�/@2~�b�X���ts��>�u��޹sw Im>�?��j �ޜ�ꇷ��Nga$�V���F��� -� ��:T����������X.1�k@����*���c�1��D�dn�h|T,�?����o�7�*n��lo0�!۰K�"�<�`ig�l4��m��kc���s�Vd\C.�~Ю�kkS����^!H����_*��Ҷ�����n%`釹ے����.'�Q?��k;~�cw�ҳ�I~�����Uu��rپ�����ʺ���Q���� <|���*K@ت&պ�Gأ?du�_�r�7a���&�Q�Oqb�_Dν���6�i�=�3�B�vC�8��w�) �?iS2%yC�(���Xtȼ�U�&��g��8� �5��aT*;>��my/J�Ļ���}��np�%�d�V?��'`�.z�V%gyo��T��J�k�EE��H��w G� /IR�P�5k֤�1�+.��½�����!��� �נ�,''��k5Y�q�`Gw��n�����DS}��.����H�)�w?��J|�s2y���n{É+���5i��k���I���ݪ���l�(��"WjP?� ��Eyq�!^b�8\����;�����ɯm-]h6������� �\ms�H��_1�Ex����ݖw� �r�* ,�����%`0: ��$;�K��u��IHB؎��l�fz�{���i�������~�c?������"d��>���g?������8;qm0`�����D�n��s��v]F�&x�� ���:���™D��{��f, -8s<���r�2q<[ܱ�/�A��:�����QȖ�̙;S 4��-8[q�t��J�7� �� ;�?踮�xWl�{3��i��#���-`�\35�g�: -B�'��Y$kO���5���3�MI�A'`.D:Ɂ�Y�+u��Β�V 70jB/��v���!�d���hɽ��� B�C���C.� �@G��$E�q��b�^rd,ca���(�%-_���}�& $��fp���/K?�L� �̀I0G6�R#�?o�41mf��O�Π���'мb���1�X�w -W{��y2����m z~O��ڽ��x3���Cf^ �&Ѓ����4�&3{�����l2�A��X׼0G�r�oJ֌�ά�.�a�~�O̮9zK�����xC�٠=��q�=DJ��pз �"��V��6/����2��1����J B$�E�'0�>�r8���J���=��&�F��/�$jߒ��([�ch��i���dld��NRg<4.������'���G{��-c����o�۷Hqc�h� �v��܆�'c�D�!-�72���`d�{���נ!� �OI��� ���"]TME��>7�:L{���FuX���(�F}�²��k�4z����k�2�ɴ���mL9��6���1덻�� -��TCO�f|�|�q�����H8��B�w��4�e��~1K)I*l[�/�����DD���pA�$?o.�야�d�����g�KH�#���1ԯ&�'s;rCr�(���C��d�����T�J�����lh0E��Hx)�W6�38�7����2VZ#V�1����fB:�DR��4��x���3g���ҋ\��m��fp�k,jہ�$���T�O{���3��t/O.�-@��\� ����t��)��::��CB��%&���,4�M���p��?���#�J_�^���S����`R6��m��v��q�PuΑV#;�B�$�@Vb�iНLkX�p{��<_CG�}�?g5@����Jp�����7e�ulW������5�P��j�q��Ͻ�⊹�`ʥ�Ӣ�^;}9�I��i�؍�bv��.�!��a�DD�����+s�q#�+)g��,��ԏ�WB���JCI��[*���J�_�%s���nY�Y�.��R?�?��.�� -���5��E�t%��F˴W�5�� �vp�/Γ�%��]V�M���VE�����C���|p��Zf��d�_Z?Ӳ��{=�If�����U��Z�&$����^f���M E�M%HE�Sf���d�y��� -���gž8%Y;g&/�����W����w�Kv@�/�~�K!�}3���ao=�Ƽ�����8ٓ��-f|GG�}S��UM�t�!�����JH�1I�0� ��z�lوފM)˕R��.Z��}-��םt~(ï�8C� -f�.W5���5�p��B�yX\�v>����<�1�EY:�9mPV<���64���@�-4���W؟����ŀ��o��D+`��0P�drl_b6�_��p��7��F�U�_���%��J��T��9����Ă�P[F�1!� ���j{�%���>��0�EȂ� �a�E��O��]Q<�T���x���3�����M`o��P�mK_���8Xh��)5J�"2��0�g3�ϵIjʦv8]�ƻ�6����w5��b8�Ү����s��/`�m_��~��XaPIJ9T��~� �0SW -�d��^W�}�����L\7E]j�[�2v|�EyyH\K��ee$ rc��.q�N�Ѵc��� u��i�=_7�����*��� -?@B�]9X�������|��΄}��*A[�\���т3�D?��W�����Y���,���r� �j�Y�4&�����o�Xòo�/�0��)G�b��\?�^�Mi���c�<���(��Yb�e%�H��Z��%=�(~�R3V�P��g����s �ެ�p�YC*���� b�)ˡv0<���G(Ҡ+� ��G�>��-�G\ְI���"������'. l�ïDn��l~H,��k�2��%7 �V����ꬥ~V���)gx0�� 2�����=�4"��l���W|�!� v�K$��]�+�5�r~�F�hJB��ȼ�j�x�jǟ r����E�;���܂����q�H�.#��ʺ��@S����5�rN������'� �`����d�L� -�х�0de|�}�x�'�B�a�t���m����[VJ�v���zSJX�W||�yl8�|�9��Ǩ�I�9>���<�5�l��_=�A�K�� l�ۛܩݗr��E\�b,��:����-�JȨ�}I��H4�U�E�k���Q���X��f#XQDW��}mH)�-���zf$��.3� -��Gt0j�$�9��k��Y�A%G��T�MN�Չ��8�fJ9f�{I�y9�E�1�Hws�5���O���9�{tCʱC���B�*d��m�U?��2�I�R~�r���&v�����=��y@��y֎����.�����Kp�ܓ��FSJ�d��V4)ӃŤ� <��<����mEu�x�Ţf��2��I��4�����1�E�q��bC�DP�,�X(["gP�悡��+��z�����<��B�id>�%g�y��9�'��$C��Ռ��V�9�L��=��H�@��(��Yv^gE�y���2�g����W�����9xP�S -�y����ӂ�暾X'��Wԏҥ�8�G�@���S����_p�S�S��W ��M��&�j��O�q�4M�4���7��!�͗��^�8��V֌��m���k�lO��@ry�f���|J�w< $�j�R�>�!��J�����j��ǣs���^2�ٮ�S/�}�� $�&��&�Az^7�B�(z�M��)<�F���,���9 �E�q�k���9E��FR��=����m����(' -�s=�� -8x�����|�ѭ/�m��P��/7�z�&¿�3��Q�� |7_�~9�, ��� :A���ٯ?���� �L�n�?��f�̦c�=�P\0��#˽� ��~6S( e����\�"\ǭ&˼p��W�&Q[Qtޱ]W��5�7D�&�ӆ9U6�_~~��~39�|7!�He���A}��Fd�D�i��j���,]ŗq5ꉙ�7�n�rJz��w 6�K�RB� �I��RZ}J��r��{���k.�F�&���_���i��Xmo�H��_Q�������}8);o����p2�VZ��m��� q�����j�/$$Nt��]>��]/O=U�]���j�:�yso`$V�2Y, �E'��_��-�� �s�,��e����O���4�H�HS�� -$W\����i��q� -���"9�<�RqHrP���#�$g��Bf��X���_�d"N�I��@�l2�a�e��a%�m�M�d�p���b�� �D'���RƋ�<��@�P��Q�T�S0Kf�L��T�T.�$����H� E�dg�q�P��(eI���h��/ �6.�wu���ʌ�&�1��̋@ +�LX�� Љ��;����D�L�2N�Z������W��T ���ÌS!a$x�$��A,�(8T4��Ab9�'*F��k���XSfj�#�3�M��$�W^�R;��v�7� ����ޕmZ& op҂�7����.<Ǵ� ��Q7���4�p�g#@͟���9ý��ķ�<�ˉc�=t�nh[Alw�LM�=���lz!8���d��+h�#������G�h m�o�ױ��q�. �~h���ᓥ�ԟx��i#ǰ/-�a�_��,7���p�G#� v�%�C �CǪ�a���[��"�ލ�G��!�X#�n��Fd�7:B�Eˁ��)��<�ƥq�1�Z�ء$���uI��1� -�� ��ih��癚������h⦁�G'�ѧy����4���M��-� -ߟNB�sO��kd��mj�=Wnjdy� �%2t*�p}a�8���[1g8 -w%�+�� �u���;�h�#C�v`�����d��������Nb�nwʹ�S � ��&�. dĮ�G���f�^$� �i��k��b�GV,���Z�����]�)V�ړ� -;h>m7���]�|Jq��AZ��l0X�קb�cz�?r1�e�`����)�������_�V�X[��R�N#� p/S�`���T+�GGz�Cz�3�#2��f�' ���X�EeE�5"m���.���%�Q�"�=��2�u&�CY �]�p,�9��&�]����|,J�w� � -�xl�fj�� ܥ�.�|�33��W�a^��M����eعjc�t���������ϗNOϞ��Q�� �Z�Պ�A���Te2��d)�3��E|�1�r���Q� r�$�����dk�< -���"�y�e�XJ��2`5λdk2fB�P��]©k� �+$�1ڦ:�C�'��Ԟ��D۲ti���>��/��%�dΒ�ǿ�����&ē%��; ���HPԇ�y$9.J`O���m2 90��W�˭�&��6�0ˑ��j�H</K�Am��DR����=�|�1?ԅ6��]�TA���!��@�C^�i;��9���;�a���b+��� -�h��qOo�5랦^�pl�؃���_S��q7Vt!��� -�ۗ�`���^I�����|���T=������h��瘩���f��e - -�riJ��3'k�%�+�����Dޫs�U���@���p��}E?�u��(s�[U���9��*$Ǽ��S�I�/HeX�Yo�3�3�z:3�<�;������}ɣ/�r�'�zO!��|R���ֽ+ĝ^oU)��"2j�tg{@K�Y��*3VDK��XJ� 5�{��9.��-u�*��������E�~�R����.��B���GR�ȶ&��\��N�p����}�0��&�ؓy���=�� �[.�W���um��8�|���K�v���z�y��ցosؘ�Ϯ6��u�bE���~+��>���^�ҋһu�-���*�z�{:�^�@�{�>mO~xd��޾�L����_���_ -���8�*yl� 8����O��_<-�����Vx�/Z�u��@�c*�z]>��`��G��oG��[mO�H�ί�CH$�@v�N:�Y�1�K��� �Y���I,;�v`������~I��00�Zm> ������nϯ���N�ݻx�h����4��ۄ_~�����ߠ��i��w�'1�������N�Z �1p3~ϼC|NCC��q���E�G!8����B-��ē[?t�#�#>�[��'S���-�E�?�]��������I�<������K2u��!� ��pnz>�łhƒ�T/X�-�h��r#g/��IT��:��= )O�Q⻬%�N�c�!�) -�%�P�8���� ڠԂ_�6h��@ �H!Hm�"w1ca"��"a��3'a�w�8���.R4�d���9�3c����(�S�%���J��������D�Bau�E �&�硒G��H��D�b�`ϙK8CZ��� ^�Z,�/ ���}� u��a����]8��A:����8������C 4��OM{h���>>��,����4��O��nY��q9�� 5�6t���鍺�y��!�ٷ�g\6δ�-�����gp�;�S;5z�}#���I�P�mh�QO��h8�[:��]���4�R��(�+ݴ���z���Es�ᩎ�j�=]�C���P��dY���~D%{-�zǠ/�'-҆7�B�EΖ����q�j��9��XrВw(H��P�$��g���Z�a�l��������n��^��Yz ��Z�Ƒ z �����2���0m}8 l�o6� ��!�TC��u�6�����K��h�����1�H��8��a�;vq&JE�c���{ƹnvt��k�қZCâ9�~����/���ɯ8�Dh�8�{e��r��z�H�O�Q�"�~�2h�G��laւ��@<�;�3a���)������r��8��z��c��'hU�%�`� cRi�$�v����0��3��#>i�s��m��?�d�Z]��)��N��m�)eq{�F�6�Mn��h���tU{gG$9t+y���C�Di�8��h�L�Gjd����*�����]�.�c�1_ܢCra]�T��tS+a/z�fF�n��Xt�!����9׋�s�YꎒKR�X�[ ��0�ξKUa�]р��GYbz�����I��Q�88���/��|��x���,�XOD%�8)�N�,l4 sK����R����(> ��չ5��"���=7�Z"�F�;N���B��]��6�愨g,݌�i�$�7�gs�V2�*�R~F/��� -�t�ޗ֒Q�.�����1$|�6ǯ> !8���� ��~�~@�ќ<�������y��O�z{()o���M�C@�Ǥ�A;��g�,�x%ǭCE\œ%�]Ē��"� cjqf��rUIxY�hS�̱���ed�����is ӹ{!O�âH��/1sƝ�(xC6f���X��r9sh��f�ZU�4�A�-�wo������+.+V,����%AKs$9�3��!Ȗ��h�P��u���@����B!�~Rk��](}�t;�{�z_:b�Oq.��� � R�5fU�����TLS�(Z3,����g�%�$$�h��`Q�� ^yUB�BTa��J)�}�� � -/Xv gZ�#��K7�BL� �����FC/���ĿǨ�m�K�#f�E��c���7��������]p\!���9gc�k ��161��{�z �Hc����� s���� P��5�|��[+�[��9��j-ō�5L�G��D^<\�!&���4ǿ0�������[��|c<��K�0_u���(�+��v"Uw���$�����xA3�7�PQm�#�ɓ=���Z �z��Ly���u�\^ξLEǪ���P�Y�� ��T�80�D98��DK��2�ט͜E��=E�M�5���&� ��� 'c�E,7��`2A����O\�K9��O,i ��1��)ݒI�8|�F���������,E�6��L������:�q>�(J�*�&��_ ��*�xL��ҁA&�/�l� �*��Ҥ�#,˿GG�J��݃��Oq~E�ċ�<����U~r���ɪ��\FS}�Tl �N&����4?��!��'h�@Gn�4Z��p�b�������� -B�z���l&<�*{�x���U��Qv����K�������$�~���G�n�,�����y���صҮ$����c��w��*�!ə�9��i�Tm�����q��U�u��̸��j��)�O�r����޶�(4���Y%U���|��'�OA~�|%��7�P�Kk��f��Ѷ�-��ra�Ӥ�r!�;˔?��e�F�V��`A�k0w� �{,�Bux�9�8�;������9�_�������y��]�<0�ߍ�Ʋ������+2=ruV�W�zK.n쭵�h���X��|#>�P%w*C��g�'*���C����|A��O�^��5����w���l>�s�Yg����X[�yd� 5n�o���ȯ�zT �Y�P�x[Bx{������G���a�T�3�l$^�­���q�Ӂ��1��$�D���߼sq�;%_Ԩ�[�ɈjvR-Om)��TؼC�o(��-�\'8 ]��}E����6�`���j���v 6_�t^�Թg�]ئ~���!C�����ƒ��p� Tg��;u��fSj��X�'����ޟ� -�z�6��U�}�ь� ���q�z���F@Μĝb�E9$��<�x=��`������Q�u���%N˗�u���{��Ko�5eas�z���8^��7e��y�1%��l����Mni����� ����������Xmo�H�ί�H$�C��ң&�dl�6M�� -��/� mt����;���g)��;��3��3���]o[/^�� ��.V�N�3��嫗���'P�� d�G� ��p|�(�]����j�ܝ@����yZ��$i,�4�ȇ,�D��,���Y��`)�M��� ]����R�?X#�0Y�a��M��܇m,v��/隥��#N�OA�OD~@��i��^� ��% �%)O�(�%)ړ2$K�l!v�Tz*i�q%G �p�#�� -�z! 6<�>����R�Ak� �O����^��Q*�^�� <�1lX�々Iu����!uMȽ$� 'b��D���s,'%� ���@BK���EN1�\6"吻 �|$��K\�=��e� ��+�,�r�� �}1�W�Z��,q�uk�ި��>��w�H��5Z�[[��v��2F��j�p�tm}0s-�h��l�˃k�y ����9X6蓩�#*�U��5G���n^)�қ� �>�]�t-%��� �&�=�ơ:� ݽ�ZǺk��1�Ta�ڮ>��MHә=� �đ� U�h�.�@����L�k�0Z�F��%���dՁ������nkC�,�ކ�G$i(�L��N/�{ -R�[i!�EdG�}�r�#u�^��� 5�C�4��ڄ�[c�rf��ݙ���e����~�5� �#7s4���B눂^�e|���GX��j�=���e��n�C�T��#�k˔6��,��p��(���p�ݔ����;t�ЭK�V��[3L��Я4s�ѪE@7����вu�d�\����gd�<4䖿��Y�G ���;����2$�#z>8�ׅ̆�˛q�e�G��t��#k"�X��+�W�y���h�ء�>pb6��f���ȇ�t�j���t9�k�p ��w�E�*��Y4�}��6τr�\�Ģ�WS4L��������˕C��r���$��6[�a�\�O��(�0�Ϙ��>TS��rփ �|-��C�O^�iHM�؇b}��)���zݔ�=��]�����WTHd � �4އ�R�@��O֞ -�Ǧ�,Z�,�1_�K�����4WTq���{1v�h����4��r�}��g58 �7!p�-q,P5������e��]m���K�CN$��c��^�� ��#�1�T����Z��8ɡ��G��w0��B����O�8�R.��Ż��������?~\C:��#�e1�TN -�Bu��]n -"L����j��Q��'ޚ�Q!G,9�k�&�&� e�eyp>�JkOK3��[^*�������='�[��m�L ���}�`~5�+���trz�pO!����GD�[رB���UJa�Y��k�Fv����}�ڍ-ؖs��t��7��%z..`�B��c��6�.s�M�� 1˄��n��"��=�`�z���[�������םC�6:���I��u�H�ݫXbR<;�}�w�>t�x?1Ω�1������C��_����)u;���M:�g���A��ہs�a���K��)T�)H�{� ���xx�4n�����9�^&��wP�8�� v ���Ѓ��G�g�!���r��Y�W3�����g�=���|�,ee�����V�z��U=���SG[�NG�W_$�3�*͜Bݾ���^�Q����*o��zҘ/� �¨���� �a+�1O�8z��7���q�{ڴ�|̺���z���ܝ�����>w}Ug��<�B�"�M�J�'���Χɻb� -9|�k,m����! ����~�QxW�4Ou�y��{�(��`�U��[��!?H�\.����m�>����g�/�v�#8'�7�G:�ڭ�¿�)�7�B���8}jXʞ���4�]���p����_�9z�������X[o�H~��8Z���h�Rz �I�D� 8i�J����:@�h���� �K�I۴~�af����77�����N�ŋ�C�2��J�F���ׯ^��?�ˌC? �/\�� ��dܾ��=Y�i -ʺ� .�y|�����8)J���2�Y U�!ɠ���j'Y(oa"��`��3R�������I��@#���s9OʒǐKq���P���8�IS�H�)D"��+�ќ��M^��[b�&�GWE�x��%��X\SW�T&�$�Z� pHR@���z�,��F��0�sy�@6u��6DW��3% �XD՜g�"�u��=����a�e�Ū�p�z �:D�'ʖ�d�Sbw��Uw�$^��h���0�$$D"�g1vr� �2%��&�c�(G�`G�H!&���:keV�<"��mB�$��ZQ�! ��{\�|z�e2�W���p�W�u6`��&�|�[������ņ?t-�P��t� -�ǡ�|\��m�? ��N`1_�1�i9g�Ŧ�m�[� \�N��c �)�3��޷l+�RQO�����R����1�u�< G���D�� [�Ιy�i`\`� ��}/b���&��mV�C���1# d�'y�$m �!3,z`"ҽ+�mѳ���8�S?��c�Awء"#��S��)��G}?��Q���uMżϼ �`��]_7�A]�~�a7>�G�E��/� �獆��:�H�%2���hm*�]GaF�\���\�c��q:��#�F�>�"��Xpؙm�1�`�뒣K�g�JZ����~�c��WE����59k��`��n^X�|=XI��`�?2 ��̠A'�N��U ���j���K8�8Ղ�9��&�{��� -瞤Y����d���7�v�Kp��:��Ҭ,��^o�X����$�׌)z�"~ټ� ��M���[�E-%��).)��(��eB�Mb1o՚�^��9��o0�u:�Lm͠Ή)��I�{�|��u�M��Ho�������X�ț,1 -b6�@,2.�%���{�wtHF� � ��q�چ�� &���)WO�1dӵ���N&U�s��ϥ����!z��O��6����;��7��vfaH��C_(,�+�kx��QG���<�ἁ -���i���Վ ��AX�s�T�v�������wJW��-Ӂw��l\���c۩n�:�`56D����Q6��P����1�$� Ʒx2���,��A��:nz8Ϭ�q�A��Y8?E{�nD�1����2���^�xݤ�BA-A������a{���qI�RjaZ�^9�bQ� �&�y}y>�:~wU�_�҇c>]��B�F݆謰�����{�(|����aHw:Aw�{ ���AT�l.{�o���S�Fnv�K<�0ɧx}䒬�^\��$ ��ӡR�Qħ��d���$Qu�U�Ꝛ*�V^�V!����q=z+�O�����P�����U0Onx���_ϯ�6���)���K�����~m~�S� ��y��}�v���o�$��nM�el/�8p�D\��%�"�beWh����_����IJu*���D�\�y�d�Φ�kھ�<�,{<�O,�� `h6��֩�C�w��5�Ø�#:e����ȚZ������m���4if��5�OMW!������'�7���)� ��%���>y'�t�3b��18k����O,��}���<�ɩAތ�-��>0Dd��:B��c?͡9M�S�1���bG%i}��h�E�����ȟRX]����2��]�������Ar\a[&��cM��Z��=AX���v�OpR�e�?Hу���&[�����/��vr�K�2~�Y��.W}�_胺;� ��w�2���H����O*h�v{V_��3W��M��7;��F��-�3��W��8M�����4�#p#�A���cr�!�\��I�� OA:����x��<��I�D�I4]� a|�)�!����LJt���!�0���h -^��rӜ�'�\P�M@<�By���"E}R�%��C���4RQ�k*j�K!$:EƑ_� -�z��?�! r-ࢥAm�J�?2]��[�Y�J�5A��F�ĸ���M�P� 'w)��g��Kk"w�H���E�jZd�+Z1Z����F�����"'� �2�S -&�磐�0� ���'�z�&��L$�#?ýy'���� Q�Ĺ�l��έ12����'�kv��'M� �w#��ځ�A�k�l0�]�;#�b� p�аq� �3�w`~�Lۆ���a�Bz�`d�˴�`�;�q��_5iH4�����Π�D37l��%ܘ��5�V�r�$�K���Kdi��9Vg�3FDi8 � �bײ;=ú1��(���w��6z���Eu�����=S�C�����8��ꩃ8���&�C�cу��D��ѝ��"e������$R���v,g�p5t%�9�duL���nl�Md�M�G*�N���ض?�e�s4k�o ��Jj���zЗ:#X���%0�)�p{m�8��/S1g6�q�+�+�����yճ��~Ǥ���l�!]kdٴ�R�o �<&���P6�Xp�4-X�`t?Y$�Z,]�2��Q{ܹ���"�}�<ͦ1f-^�H�z��a�93f3��'�힜r{��p�B�q��^���V��c�y2$�,M��v����8NpL���O���~~+{9ƒ paw��2jѣL%��S���؋�m�eUnO�E� j�䆰�5D��DD�d� ��;n> �٘�r(�់��M0K�pAu.^`��~g��dQ�ڇyR�L�'�eT<,%g�c� SQZ�d�0e�hX��Hfy���Q�#�(y�� %}��]��o*�ω�"���bCB�N͊p��"�}Y��v��ʛϨ,"5Z'�e�Č��$o���mn�M�+�MAF/��}$�h�oe�� ��׬���Vؖ�E�j���+l/R����@�W�ZIJ����y�;%J8��+A��F��e����@��n�8�bs���� R7�dA�0��DQ��[�f�(���B����B��]�b_Yq�sw��}NΠ?��^�i��� ��}Q�zdʱ��6����y� ��T=Aiu��%��{�1WmcFh�f2NW��勧�g"�A����[ -�#j$[�z��� Kr"�s�߶"i3l<9�(+�r>�T�?�~����w�}Tq��Ex�Y��Ɓ�^��J�zFx 6j�ס���WJqTn��I�[p���p��=��`�*���4�U*��ZS�;,8/[�]�)Ŭ��j��FdSJ���"[�]�� G�}�w�e����b�b -�K �Uv��b�O@\h9�������[��J���A$Y���{W5�zw_�{��Kil�F�,L�F��SJ1F� ����� 9��.um�,zߍ����V�(���`���6qY[U�̮�l�/�t;1sŬ���]��J�J�!vP��6ݛR�w�8�.�Z�:��7f��P��eot�����L�%�Fyτ��2�3l�������խ�s�N������W�bN�#��$�[x�5��j��G�Q�$FI"��#6 �DR��b�. ��͙��:��PeV� ���ɉe�/zV~��l�G1�SbL���͓tY�SJ�j3�Ҧ%�p)[7W9i�ŷ ��N��~w�oš4��,}%Rc��`E�I��Y�#[Kn����z/�'��B�S�x)9 n2φ���j[L���B�b4(������z�pw��7yg��۷o�2�����ɧ�Oo�r� ���$W+[���%���ߎ#�����eS���.��VG*A�X�qǞ��r\ ��VL �k�9eݷ���IK�gSYESV�/�(�c�T)�2g|��4�T��Z��F�B� -�NI������� �TAV�jI@���^��auձKc���|���u[� �f�6hϓ5�ߕ t�ְ��b�a�q��4�bG^���P�W!��Ed��&B��������l"�� u�_WH�'R� � ى�ik8���^i� Rv��sEl�ː�R�P]��ݍ3�n���q�姈qM�u���"Qk6e�w�߿oc�j;܍�V+�2כA^k�i�� -.�����s�PF��祼t -h�/� �_T��eHS�]o4+G���Y!O����%ɿz��ju�B|��Ԃh�>TfKM�����ˍ -r;�/�i��~#�2��ku�N�#v��2ή?���K�]�&#Y՘4��TW���>ÃI���<�����Z�5��T�\]O�U�ꊬu�2N�#e���@C�9��D�Rt��g�H4�f1��ٕ�/�U�P&��Ap+��8��Rbג�+��DR���\��i2�-�m8��+yԃ���w�Tgڥ4�����o�^�V���;lx���P��e���BN�L��� -}���ؑ�~;�ZP���1�l$]Io��v �:���\��H�٘Hi� MW��.����H ���;e(k���L��5� &�ԥqPj�狹�~DK�겪ίue6�� I%� �gS�g�5S?]C���n.e<�ڧ"p������ ��S�m�M��<�4:#B7�&w�i��Fs�����sھPd�>�̹+T˔1�F�>�,L�(���6^��ǞA�� N󵇨Ů/�œ�a�kW�7\����z�MĐ������%�7Y�{|A�P^bo�I7 �6a������=o�g"��<ݏ��e`��_zH��]h#6����[Jjk����gz�>bY�-�W_'����n��a�xADDJf"`�c���L�?Mu�f��A���*+��Î�7VXgIqp�w�tj8v���C$5(#0{C(�V~�f��J ��Ϳ�|����qGia� -��>��w�T�B�6|K��/�za[S��A�9J�$7�"����/[<�{h��x�J�㧇l1~�=&�L�����KJ<�#�����S�q��� D<4J�&�xM�UT��#�% -b��Y/�4O�͝����~�y������i���A��#D�c�?�QzoL %�KY��ur��N�^������[�#��+q�{��/������n@��ַ����~N��qnk[��@�!=���a�}�Z�ӯ-T:{��tk�:ht7��S�+k���B�R�)�eO�ĺz�I��]�Hm[T��ҦC-�#� �&9%]zޭ�wk�rⅎ����2�Z���)�� -9�g;XD13�]_��hE'�l�c�x��Աn�07,'7��)��nzB�;�O0kG�M����%���M-`*�K*��n��ICL�z�0^Y¨��')?�֧|��Ӫd�y�j5�O�ϸvf�3�T8�!'`�� � "���!���ou�0�T���[p��ϳ'��S׳*��0�$9� ���HҒ��]���8��&PM�S+��[}�9�D{���� 3�(B|�}�v� F4���3���1G�dv�)Άeȓ}F6SV+�u��;�=Z� X��:�9,������Z�>y"��|k�[9��^/ƛJ�N��J\!���-��n�53���n/rYȿ�]�p�1������M��|>�����8�P{�d�ҏv ��ea�i�٦�dB iG�€�l���-!Юg|�Wm?���]%7�O}�/����m�qmZ����K�c -��k�{z�hw�x:x�I��q /yܟ�t�e�f������a¼8@��Y{q7"b,�w ���<�96��!�7�4�� g�̢Ew̲kP]�E�f� \��D�5������S�B�h=� -٣L=�!�S�A#�+%�A���$�Ћ�K��_����xbJ�w㋩ԅ�w[����Mį�0�BM�� �ƛ�,�w&tX�� q�E@�m�LDx�_���UiY�g�:��B���->�A�L�n��:CI�E7��qџ��Ȣ�vG�"�V�6;0�ױ�KQN��ջ�� N��)��G�f�0B?�o����b^z�m ~t�5��β�M.I��fە�H7aSs��Zi��b�W ��}]*$���%7��ܞ,b�v�+�hePť��*�M 嗈b����~�����*�GR�U�e�5e�_�����["q�����9ET|�� j�Ǩ�S�_��S}� ����]�'��r���1�)b��7S�\U:��Z�VIE�9?~.�=��q�.b����m˩�>���e��W�#�:�R[��2�fO����SI��E$���è����c't�0`'�Q`�K `�0z-�����Ep�|rO��&c�܎A�&h����{W�I�k�v8��#Vl>&p ��� -dZ�.�����D�S�.�p�I�?"]�� T��;�Ѷ"�S�*��Em���5���B���;C[���\����xQ�⧯��і��x8�����V*s�W�(� -�x׃�[��/-���y��X�u�1��< '/finder-facade/Configuration.php', - 'sebastianbergmann\\finderfacade\\finderfacade' => '/finder-facade/FinderFacade.php', - 'sebastianbergmann\\phploc\\analyser' => '/src/Analyser.php', - 'sebastianbergmann\\phploc\\cli\\application' => '/src/CLI/Application.php', - 'sebastianbergmann\\phploc\\cli\\command' => '/src/CLI/Command.php', - 'sebastianbergmann\\phploc\\collector' => '/src/Collector.php', - 'sebastianbergmann\\phploc\\exception' => '/src/Exception/Exception.php', - 'sebastianbergmann\\phploc\\log\\csv' => '/src/Log/Csv.php', - 'sebastianbergmann\\phploc\\log\\text' => '/src/Log/Text.php', - 'sebastianbergmann\\phploc\\log\\xml' => '/src/Log/Xml.php', - 'sebastianbergmann\\phploc\\publisher' => '/src/Publisher.php', - 'sebastianbergmann\\phploc\\runtimeexception' => '/src/Exception/RuntimeException.php', - 'sebastianbergmann\\version' => '/version/Version.php', - 'symfony\\component\\console\\application' => '/symfony/console/Application.php', - 'symfony\\component\\console\\command\\command' => '/symfony/console/Command/Command.php', - 'symfony\\component\\console\\command\\helpcommand' => '/symfony/console/Command/HelpCommand.php', - 'symfony\\component\\console\\command\\listcommand' => '/symfony/console/Command/ListCommand.php', - 'symfony\\component\\console\\command\\lockabletrait' => '/symfony/console/Command/LockableTrait.php', - 'symfony\\component\\console\\consoleevents' => '/symfony/console/ConsoleEvents.php', - 'symfony\\component\\console\\dependencyinjection\\addconsolecommandpass' => '/symfony/console/DependencyInjection/AddConsoleCommandPass.php', - 'symfony\\component\\console\\descriptor\\applicationdescription' => '/symfony/console/Descriptor/ApplicationDescription.php', - 'symfony\\component\\console\\descriptor\\descriptor' => '/symfony/console/Descriptor/Descriptor.php', - 'symfony\\component\\console\\descriptor\\descriptorinterface' => '/symfony/console/Descriptor/DescriptorInterface.php', - 'symfony\\component\\console\\descriptor\\jsondescriptor' => '/symfony/console/Descriptor/JsonDescriptor.php', - 'symfony\\component\\console\\descriptor\\markdowndescriptor' => '/symfony/console/Descriptor/MarkdownDescriptor.php', - 'symfony\\component\\console\\descriptor\\textdescriptor' => '/symfony/console/Descriptor/TextDescriptor.php', - 'symfony\\component\\console\\descriptor\\xmldescriptor' => '/symfony/console/Descriptor/XmlDescriptor.php', - 'symfony\\component\\console\\event\\consolecommandevent' => '/symfony/console/Event/ConsoleCommandEvent.php', - 'symfony\\component\\console\\event\\consoleerrorevent' => '/symfony/console/Event/ConsoleErrorEvent.php', - 'symfony\\component\\console\\event\\consoleevent' => '/symfony/console/Event/ConsoleEvent.php', - 'symfony\\component\\console\\event\\consoleexceptionevent' => '/symfony/console/Event/ConsoleExceptionEvent.php', - 'symfony\\component\\console\\event\\consoleterminateevent' => '/symfony/console/Event/ConsoleTerminateEvent.php', - 'symfony\\component\\console\\eventlistener\\errorlistener' => '/symfony/console/EventListener/ErrorListener.php', - 'symfony\\component\\console\\exception\\commandnotfoundexception' => '/symfony/console/Exception/CommandNotFoundException.php', - 'symfony\\component\\console\\exception\\exceptioninterface' => '/symfony/console/Exception/ExceptionInterface.php', - 'symfony\\component\\console\\exception\\invalidargumentexception' => '/symfony/console/Exception/InvalidArgumentException.php', - 'symfony\\component\\console\\exception\\invalidoptionexception' => '/symfony/console/Exception/InvalidOptionException.php', - 'symfony\\component\\console\\exception\\logicexception' => '/symfony/console/Exception/LogicException.php', - 'symfony\\component\\console\\exception\\runtimeexception' => '/symfony/console/Exception/RuntimeException.php', - 'symfony\\component\\console\\formatter\\outputformatter' => '/symfony/console/Formatter/OutputFormatter.php', - 'symfony\\component\\console\\formatter\\outputformatterinterface' => '/symfony/console/Formatter/OutputFormatterInterface.php', - 'symfony\\component\\console\\formatter\\outputformatterstyle' => '/symfony/console/Formatter/OutputFormatterStyle.php', - 'symfony\\component\\console\\formatter\\outputformatterstyleinterface' => '/symfony/console/Formatter/OutputFormatterStyleInterface.php', - 'symfony\\component\\console\\formatter\\outputformatterstylestack' => '/symfony/console/Formatter/OutputFormatterStyleStack.php', - 'symfony\\component\\console\\helper\\debugformatterhelper' => '/symfony/console/Helper/DebugFormatterHelper.php', - 'symfony\\component\\console\\helper\\descriptorhelper' => '/symfony/console/Helper/DescriptorHelper.php', - 'symfony\\component\\console\\helper\\formatterhelper' => '/symfony/console/Helper/FormatterHelper.php', - 'symfony\\component\\console\\helper\\helper' => '/symfony/console/Helper/Helper.php', - 'symfony\\component\\console\\helper\\helperinterface' => '/symfony/console/Helper/HelperInterface.php', - 'symfony\\component\\console\\helper\\helperset' => '/symfony/console/Helper/HelperSet.php', - 'symfony\\component\\console\\helper\\inputawarehelper' => '/symfony/console/Helper/InputAwareHelper.php', - 'symfony\\component\\console\\helper\\processhelper' => '/symfony/console/Helper/ProcessHelper.php', - 'symfony\\component\\console\\helper\\progressbar' => '/symfony/console/Helper/ProgressBar.php', - 'symfony\\component\\console\\helper\\progressindicator' => '/symfony/console/Helper/ProgressIndicator.php', - 'symfony\\component\\console\\helper\\questionhelper' => '/symfony/console/Helper/QuestionHelper.php', - 'symfony\\component\\console\\helper\\symfonyquestionhelper' => '/symfony/console/Helper/SymfonyQuestionHelper.php', - 'symfony\\component\\console\\helper\\table' => '/symfony/console/Helper/Table.php', - 'symfony\\component\\console\\helper\\tablecell' => '/symfony/console/Helper/TableCell.php', - 'symfony\\component\\console\\helper\\tableseparator' => '/symfony/console/Helper/TableSeparator.php', - 'symfony\\component\\console\\helper\\tablestyle' => '/symfony/console/Helper/TableStyle.php', - 'symfony\\component\\console\\input\\argvinput' => '/symfony/console/Input/ArgvInput.php', - 'symfony\\component\\console\\input\\arrayinput' => '/symfony/console/Input/ArrayInput.php', - 'symfony\\component\\console\\input\\input' => '/symfony/console/Input/Input.php', - 'symfony\\component\\console\\input\\inputargument' => '/symfony/console/Input/InputArgument.php', - 'symfony\\component\\console\\input\\inputawareinterface' => '/symfony/console/Input/InputAwareInterface.php', - 'symfony\\component\\console\\input\\inputdefinition' => '/symfony/console/Input/InputDefinition.php', - 'symfony\\component\\console\\input\\inputinterface' => '/symfony/console/Input/InputInterface.php', - 'symfony\\component\\console\\input\\inputoption' => '/symfony/console/Input/InputOption.php', - 'symfony\\component\\console\\input\\streamableinputinterface' => '/symfony/console/Input/StreamableInputInterface.php', - 'symfony\\component\\console\\input\\stringinput' => '/symfony/console/Input/StringInput.php', - 'symfony\\component\\console\\logger\\consolelogger' => '/symfony/console/Logger/ConsoleLogger.php', - 'symfony\\component\\console\\output\\bufferedoutput' => '/symfony/console/Output/BufferedOutput.php', - 'symfony\\component\\console\\output\\consoleoutput' => '/symfony/console/Output/ConsoleOutput.php', - 'symfony\\component\\console\\output\\consoleoutputinterface' => '/symfony/console/Output/ConsoleOutputInterface.php', - 'symfony\\component\\console\\output\\nulloutput' => '/symfony/console/Output/NullOutput.php', - 'symfony\\component\\console\\output\\output' => '/symfony/console/Output/Output.php', - 'symfony\\component\\console\\output\\outputinterface' => '/symfony/console/Output/OutputInterface.php', - 'symfony\\component\\console\\output\\streamoutput' => '/symfony/console/Output/StreamOutput.php', - 'symfony\\component\\console\\question\\choicequestion' => '/symfony/console/Question/ChoiceQuestion.php', - 'symfony\\component\\console\\question\\confirmationquestion' => '/symfony/console/Question/ConfirmationQuestion.php', - 'symfony\\component\\console\\question\\question' => '/symfony/console/Question/Question.php', - 'symfony\\component\\console\\style\\outputstyle' => '/symfony/console/Style/OutputStyle.php', - 'symfony\\component\\console\\style\\styleinterface' => '/symfony/console/Style/StyleInterface.php', - 'symfony\\component\\console\\style\\symfonystyle' => '/symfony/console/Style/SymfonyStyle.php', - 'symfony\\component\\console\\terminal' => '/symfony/console/Terminal.php', - 'symfony\\component\\console\\tester\\applicationtester' => '/symfony/console/Tester/ApplicationTester.php', - 'symfony\\component\\console\\tester\\commandtester' => '/symfony/console/Tester/CommandTester.php', - 'symfony\\component\\debug\\bufferinglogger' => '/symfony/debug/BufferingLogger.php', - 'symfony\\component\\debug\\debug' => '/symfony/debug/Debug.php', - 'symfony\\component\\debug\\debugclassloader' => '/symfony/debug/DebugClassLoader.php', - 'symfony\\component\\debug\\errorhandler' => '/symfony/debug/ErrorHandler.php', - 'symfony\\component\\debug\\exception\\classnotfoundexception' => '/symfony/debug/Exception/ClassNotFoundException.php', - 'symfony\\component\\debug\\exception\\contexterrorexception' => '/symfony/debug/Exception/ContextErrorException.php', - 'symfony\\component\\debug\\exception\\fatalerrorexception' => '/symfony/debug/Exception/FatalErrorException.php', - 'symfony\\component\\debug\\exception\\fatalthrowableerror' => '/symfony/debug/Exception/FatalThrowableError.php', - 'symfony\\component\\debug\\exception\\flattenexception' => '/symfony/debug/Exception/FlattenException.php', - 'symfony\\component\\debug\\exception\\outofmemoryexception' => '/symfony/debug/Exception/OutOfMemoryException.php', - 'symfony\\component\\debug\\exception\\silencederrorcontext' => '/symfony/debug/Exception/SilencedErrorContext.php', - 'symfony\\component\\debug\\exception\\undefinedfunctionexception' => '/symfony/debug/Exception/UndefinedFunctionException.php', - 'symfony\\component\\debug\\exception\\undefinedmethodexception' => '/symfony/debug/Exception/UndefinedMethodException.php', - 'symfony\\component\\debug\\exceptionhandler' => '/symfony/debug/ExceptionHandler.php', - 'symfony\\component\\debug\\fatalerrorhandler\\classnotfoundfatalerrorhandler' => '/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php', - 'symfony\\component\\debug\\fatalerrorhandler\\fatalerrorhandlerinterface' => '/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php', - 'symfony\\component\\debug\\fatalerrorhandler\\undefinedfunctionfatalerrorhandler' => '/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php', - 'symfony\\component\\debug\\fatalerrorhandler\\undefinedmethodfatalerrorhandler' => '/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php', - 'symfony\\component\\finder\\comparator\\comparator' => '/symfony/finder/Comparator/Comparator.php', - 'symfony\\component\\finder\\comparator\\datecomparator' => '/symfony/finder/Comparator/DateComparator.php', - 'symfony\\component\\finder\\comparator\\numbercomparator' => '/symfony/finder/Comparator/NumberComparator.php', - 'symfony\\component\\finder\\exception\\accessdeniedexception' => '/symfony/finder/Exception/AccessDeniedException.php', - 'symfony\\component\\finder\\exception\\exceptioninterface' => '/symfony/finder/Exception/ExceptionInterface.php', - 'symfony\\component\\finder\\finder' => '/symfony/finder/Finder.php', - 'symfony\\component\\finder\\glob' => '/symfony/finder/Glob.php', - 'symfony\\component\\finder\\iterator\\customfilteriterator' => '/symfony/finder/Iterator/CustomFilterIterator.php', - 'symfony\\component\\finder\\iterator\\daterangefilteriterator' => '/symfony/finder/Iterator/DateRangeFilterIterator.php', - 'symfony\\component\\finder\\iterator\\depthrangefilteriterator' => '/symfony/finder/Iterator/DepthRangeFilterIterator.php', - 'symfony\\component\\finder\\iterator\\excludedirectoryfilteriterator' => '/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php', - 'symfony\\component\\finder\\iterator\\filecontentfilteriterator' => '/symfony/finder/Iterator/FilecontentFilterIterator.php', - 'symfony\\component\\finder\\iterator\\filenamefilteriterator' => '/symfony/finder/Iterator/FilenameFilterIterator.php', - 'symfony\\component\\finder\\iterator\\filetypefilteriterator' => '/symfony/finder/Iterator/FileTypeFilterIterator.php', - 'symfony\\component\\finder\\iterator\\filteriterator' => '/symfony/finder/Iterator/FilterIterator.php', - 'symfony\\component\\finder\\iterator\\multiplepcrefilteriterator' => '/symfony/finder/Iterator/MultiplePcreFilterIterator.php', - 'symfony\\component\\finder\\iterator\\pathfilteriterator' => '/symfony/finder/Iterator/PathFilterIterator.php', - 'symfony\\component\\finder\\iterator\\recursivedirectoryiterator' => '/symfony/finder/Iterator/RecursiveDirectoryIterator.php', - 'symfony\\component\\finder\\iterator\\sizerangefilteriterator' => '/symfony/finder/Iterator/SizeRangeFilterIterator.php', - 'symfony\\component\\finder\\iterator\\sortableiterator' => '/symfony/finder/Iterator/SortableIterator.php', - 'symfony\\component\\finder\\splfileinfo' => '/symfony/finder/SplFileInfo.php', - 'symfony\\polyfill\\mbstring\\mbstring' => '/symfony/polyfill-mbstring/Mbstring.php', - 'theseer\\fdom\\css\\dollarequalrule' => '/fdomdocument/css/DollarEqualRule.php', - 'theseer\\fdom\\css\\notrule' => '/fdomdocument/css/NotRule.php', - 'theseer\\fdom\\css\\nthchildrule' => '/fdomdocument/css/NthChildRule.php', - 'theseer\\fdom\\css\\regexrule' => '/fdomdocument/css/RegexRule.php', - 'theseer\\fdom\\css\\ruleinterface' => '/fdomdocument/css/RuleInterface.php', - 'theseer\\fdom\\css\\translator' => '/fdomdocument/css/Translator.php', - 'theseer\\fdom\\fdomdocument' => '/fdomdocument/fDOMDocument.php', - 'theseer\\fdom\\fdomdocumentfragment' => '/fdomdocument/fDOMDocumentFragment.php', - 'theseer\\fdom\\fdomelement' => '/fdomdocument/fDOMElement.php', - 'theseer\\fdom\\fdomexception' => '/fdomdocument/fDOMException.php', - 'theseer\\fdom\\fdomnode' => '/fdomdocument/fDOMNode.php', - 'theseer\\fdom\\fdomxpath' => '/fdomdocument/fDOMXPath.php', - 'theseer\\fdom\\xpathquery' => '/fdomdocument/XPathQuery.php', - 'theseer\\fdom\\xpathqueryexception' => '/fdomdocument/XPathQueryException.php' - ); - } - - $class = strtolower($class); - - if (isset($classes[$class])) { - require 'phar://phploc-4.0.1.phar' . $classes[$class]; - } - } -); - -Phar::mapPhar('phploc-4.0.1.phar'); - -if (isset($_SERVER['argv'][1]) && $_SERVER['argv'][1] == '--manifest') { - print file_get_contents(__PHPLOC_PHAR_ROOT__ . '/manifest.txt'); - exit; -} - -$application = new SebastianBergmann\PHPLOC\CLI\Application; -$application->run(); - -__HALT_COMPILER(); ?> -&'�phploc-4.0.1.pharsrc/Analyser.phpvO�oZvO�XA�src/CLI/Application.phpy �oZy �.���src/CLI/Command.php|�oZ|L��j�src/Collector.phpO�oZO� ���src/Exception/Exception.php!�oZ!�_�ö"src/Exception/RuntimeException.phpS�oZS F���src/Log/Csv.php.�oZ."���src/Log/Text.php5!�oZ5!�F�X�src/Log/Xml.php�oZ����src/Publisher.php&-�oZ&-C���finder-facade/Configuration.php� �oZ� Xt&�finder-facade/FinderFacade.php �oZ O�z��version/Version.php��oZ�N\Ƕsymfony/console/Application.phpP��oZP����B�#symfony/console/Command/Command.php�H�oZ�H%?8$�'symfony/console/Command/HelpCommand.phpI �oZI �)~��'symfony/console/Command/ListCommand.php -�oZ -�� 4�)symfony/console/Command/LockableTrait.php��oZ�����!symfony/console/ConsoleEvents.php!�oZ!��Ω�=symfony/console/DependencyInjection/AddConsoleCommandPass.php��oZ���R��5symfony/console/Descriptor/ApplicationDescription.php�oZ����)symfony/console/Descriptor/Descriptor.php� �oZ� �j�2symfony/console/Descriptor/DescriptorInterface.php��oZ�JZ0<�-symfony/console/Descriptor/JsonDescriptor.phpd�oZd�m�h�1symfony/console/Descriptor/MarkdownDescriptor.php��oZ���`�-symfony/console/Descriptor/TextDescriptor.php�0�oZ�04�t�,symfony/console/Descriptor/XmlDescriptor.php$�oZ${g�L�-symfony/console/Event/ConsoleCommandEvent.php%�oZ%{˾�+symfony/console/Event/ConsoleErrorEvent.php�oZ�.�&symfony/console/Event/ConsoleEvent.php��oZ�xS*�/symfony/console/Event/ConsoleExceptionEvent.phpS�oZS���^�/symfony/console/Event/ConsoleTerminateEvent.php�oZ{e��/symfony/console/EventListener/ErrorListener.php� -�oZ� -�/ű�6symfony/console/Exception/CommandNotFoundException.php��oZ���Ͷ0symfony/console/Exception/ExceptionInterface.php��oZ����U�6symfony/console/Exception/InvalidArgumentException.php��oZ��u i�4symfony/console/Exception/InvalidOptionException.php��oZ��;�,symfony/console/Exception/LogicException.php��oZ�SML��.symfony/console/Exception/RuntimeException.php��oZ��*b�-symfony/console/Formatter/OutputFormatter.php_�oZ_�j7�6symfony/console/Formatter/OutputFormatterInterface.php��oZ�#7�u�2symfony/console/Formatter/OutputFormatterStyle.php��oZ����ʶ;symfony/console/Formatter/OutputFormatterStyleInterface.php7�oZ7�.@Y�7symfony/console/Formatter/OutputFormatterStyleStack.php� �oZ� \g�V�/symfony/console/Helper/DebugFormatterHelper.phpV�oZV�ݔ��+symfony/console/Helper/DescriptorHelper.php1 -�oZ1 -i��*symfony/console/Helper/FormatterHelper.php� �oZ� �����!symfony/console/Helper/Helper.php��oZ� -FY>�*symfony/console/Helper/HelperInterface.phpp�oZp���n�$symfony/console/Helper/HelperSet.php� �oZ� .��ض+symfony/console/Helper/InputAwareHelper.php��oZ���˶(symfony/console/Helper/ProcessHelper.php��oZ���A�&symfony/console/Helper/ProgressBar.php�C�oZ�C�Vde�,symfony/console/Helper/ProgressIndicator.php �oZ fG.�)symfony/console/Helper/QuestionHelper.php[8�oZ[8��̶0symfony/console/Helper/SymfonyQuestionHelper.php��oZ���歶 symfony/console/Helper/Table.php�L�oZ�Lr�:�$symfony/console/Helper/TableCell.phpy�oZyp/#6�)symfony/console/Helper/TableSeparator.php�oZ�=_�%symfony/console/Helper/TableStyle.php��oZ�w�U\�#symfony/console/Input/ArgvInput.php�(�oZ�(��ӊ�$symfony/console/Input/ArrayInput.php��oZ��pGW�symfony/console/Input/Input.php��oZ����Ŷ'symfony/console/Input/InputArgument.php� �oZ�  ��-symfony/console/Input/InputAwareInterface.php^�oZ^9K�h�)symfony/console/Input/InputDefinition.php,�oZ,Y��Z�(symfony/console/Input/InputInterface.php"�oZ"�SO��%symfony/console/Input/InputOption.php!�oZ!�y�2symfony/console/Input/StreamableInputInterface.phpi�oZi����%symfony/console/Input/StringInput.phpW �oZW ؙ��(symfony/console/Logger/ConsoleLogger.php��oZ�S"M��)symfony/console/Output/BufferedOutput.phpH�oZH�9�Զ(symfony/console/Output/ConsoleOutput.php��oZ���U�1symfony/console/Output/ConsoleOutputInterface.php��oZ�����%symfony/console/Output/NullOutput.phpn�oZnt�D��!symfony/console/Output/Output.php{�oZ{�m�n�*symfony/console/Output/OutputInterface.php �oZ ���9�'symfony/console/Output/StreamOutput.php� �oZ� ����+symfony/console/Question/ChoiceQuestion.php��oZ���ֶ1symfony/console/Question/ConfirmationQuestion.php�oZ�����%symfony/console/Question/Question.php��oZ��� ��%symfony/console/Style/OutputStyle.php� �oZ� �gݶ(symfony/console/Style/StyleInterface.phpR �oZR ����&symfony/console/Style/SymfonyStyle.php -/�oZ -/���ֶsymfony/console/Terminal.php��oZ�h�7�,symfony/console/Tester/ApplicationTester.php �oZ e5�(symfony/console/Tester/CommandTester.php��oZ� A��!symfony/debug/BufferingLogger.php��oZ�M0 =�symfony/debug/Debug.php��oZ���f�"symfony/debug/DebugClassLoader.php6�oZ6V��y�symfony/debug/ErrorHandler.phpj�oZj66�2symfony/debug/Exception/ClassNotFoundException.php<�oZ<'D���1symfony/debug/Exception/ContextErrorException.phpS�oZS;Iҫ�/symfony/debug/Exception/FatalErrorException.php �oZ U��l�/symfony/debug/Exception/FatalThrowableError.php4�oZ4��,symfony/debug/Exception/FlattenException.php(�oZ(�D�D�0symfony/debug/Exception/OutOfMemoryException.php��oZ��� h�0symfony/debug/Exception/SilencedErrorContext.phph�oZhz=��6symfony/debug/Exception/UndefinedFunctionException.php+�oZ+�O��4symfony/debug/Exception/UndefinedMethodException.php&�oZ&ïy߶"symfony/debug/ExceptionHandler.php��oZ�G]3�Bsymfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php��oZ���(�>symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php��oZ��iA��Fsymfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php� �oZ� �g���Dsymfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.phpZ�oZZ�2�(symfony/finder/Comparator/Comparator.php�oZ���,symfony/finder/Comparator/DateComparator.php��oZ���Gö.symfony/finder/Comparator/NumberComparator.php -�oZ -����2symfony/finder/Exception/AccessDeniedException.php��oZ��cW޶/symfony/finder/Exception/ExceptionInterface.php"�oZ"ڀfW�symfony/finder/Finder.php�I�oZ�I׷��symfony/finder/Glob.php��oZ� J�@�0symfony/finder/Iterator/CustomFilterIterator.php��oZ�cˀS�3symfony/finder/Iterator/DateRangeFilterIterator.php��oZ���@��4symfony/finder/Iterator/DepthRangeFilterIterator.php��oZ�����:symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php� �oZ� ,��2symfony/finder/Iterator/FileTypeFilterIterator.php>�oZ>p��Y�5symfony/finder/Iterator/FilecontentFilterIterator.php��oZ�r�~�2symfony/finder/Iterator/FilenameFilterIterator.php��oZ� �p��*symfony/finder/Iterator/FilterIterator.php��oZ�8=/��6symfony/finder/Iterator/MultiplePcreFilterIterator.php� �oZ� 5���.symfony/finder/Iterator/PathFilterIterator.php��oZ������6symfony/finder/Iterator/RecursiveDirectoryIterator.php��oZ����3symfony/finder/Iterator/SizeRangeFilterIterator.php��oZ��:~ж,symfony/finder/Iterator/SortableIterator.php -�oZ -!ư�symfony/finder/SplFileInfo.phpZ�oZZiq�p�&symfony/polyfill-mbstring/Mbstring.phpjV�oZjV�v���9symfony/polyfill-mbstring/Resources/unidata/lowerCase.php�I�oZ�I�҈�9symfony/polyfill-mbstring/Resources/unidata/upperCase.php9J�oZ9J|Q��'symfony/polyfill-mbstring/bootstrap.php'�oZ'p#Di�fdomdocument/XPathQuery.php��oZ���߶$fdomdocument/XPathQueryException.php��oZ��*��fdomdocument/autoload.php��oZ�!�H�$fdomdocument/css/DollarEqualRule.php�oZp���fdomdocument/css/NotRule.php7�oZ7��ꙶ!fdomdocument/css/NthChildRule.php��oZ�[4���fdomdocument/css/RegexRule.php��oZ����s�"fdomdocument/css/RuleInterface.php��oZ��,��fdomdocument/css/Translator.php��oZ�F]hX�fdomdocument/fDOMDocument.php"[�oZ"[7h&�%fdomdocument/fDOMDocumentFragment.phpI�oZI]�~�fdomdocument/fDOMElement.php�8�oZ�8��o�fdomdocument/fDOMException.php��oZ��d�O�fdomdocument/fDOMNode.php��oZ�VGQ�fdomdocument/fDOMXPath.php��oZ��!X̶ manifest.txt��oZ��S�� - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SebastianBergmann\PHPLOC; - -/** - * PHPLOC code analyser. - */ -class Analyser -{ - /** - * @var Collector - */ - private $collector; - - /** - * @var array - */ - private $classes = []; - - /** - * @var array - */ - private $superGlobals = [ - '$_ENV' => true, - '$_POST' => true, - '$_GET' => true, - '$_COOKIE' => true, - '$_SERVER' => true, - '$_FILES' => true, - '$_REQUEST' => true, - '$HTTP_ENV_VARS' => true, - '$HTTP_POST_VARS' => true, - '$HTTP_GET_VARS' => true, - '$HTTP_COOKIE_VARS' => true, - '$HTTP_SERVER_VARS' => true, - '$HTTP_POST_FILES' => true - ]; - - public function __construct() - { - $this->collector = new Collector(); - } - - /** - * Processes a set of files. - * - * @param array $files - * @param bool $countTests - * - * @return array - */ - public function countFiles(array $files, $countTests) - { - foreach ($files as $file) { - $this->countFile($file, $countTests); - } - - return $this->collector->getPublisher()->toArray(); - } - - /** - * Pre-processes a single file. - * - * @param string $filename - */ - public function preProcessFile($filename) - { - $tokens = \token_get_all(\file_get_contents($filename)); - $numTokens = \count($tokens); - $namespace = false; - - for ($i = 0; $i < $numTokens; $i++) { - if (\is_string($tokens[$i])) { - continue; - } - - switch ($tokens[$i][0]) { - case T_NAMESPACE: - $namespace = $this->getNamespaceName($tokens, $i); - break; - - case T_CLASS: - if (!$this->isClassDeclaration($tokens, $i)) { - continue; - } - - $className = $this->getClassName($namespace, $tokens, $i); - - if (isset($tokens[$i + 4]) && \is_array($tokens[$i + 4]) && - $tokens[$i + 4][0] == T_EXTENDS) { - $parent = $this->getClassName($namespace, $tokens, $i + 4); - } else { - $parent = null; - } - - $this->classes[$className] = $parent; - break; - } - } - } - - /** - * Processes a single file. - * - * @param string $filename - * @param bool $countTests - */ - public function countFile($filename, $countTests) - { - if ($countTests) { - $this->preProcessFile($filename); - } - - $buffer = \file_get_contents($filename); - $this->collector->incrementLines(\substr_count($buffer, "\n")); - $tokens = \token_get_all($buffer); - $numTokens = \count($tokens); - - unset($buffer); - - $this->collector->addFile($filename); - - $blocks = []; - $currentBlock = false; - $namespace = false; - $className = null; - $functionName = null; - $testClass = false; - $this->collector->currentClassReset(); - $isInMethod = false; - - for ($i = 0; $i < $numTokens; $i++) { - if (\is_string($tokens[$i])) { - $token = \trim($tokens[$i]); - - if ($token == ';') { - if ($className !== null && !$testClass) { - $this->collector->currentClassIncrementLines(); - - if ($functionName !== null) { - $this->collector->currentMethodIncrementLines(); - } - } elseif ($functionName !== null) { - $this->collector->incrementFunctionLines(); - } - - $this->collector->incrementLogicalLines(); - } elseif ($token == '?' && !$testClass) { - if ($className !== null) { - $this->collector->currentClassIncrementComplexity(); - $this->collector->currentMethodIncrementComplexity(); - } - - $this->collector->incrementComplexity(); - } elseif ($token == '{') { - if ($currentBlock == T_CLASS) { - $block = $className; - } elseif ($currentBlock == T_FUNCTION) { - $block = $functionName; - } else { - $block = false; - } - - \array_push($blocks, $block); - - $currentBlock = false; - } elseif ($token == '}') { - $block = \array_pop($blocks); - - if ($block !== false && $block !== null) { - if ($block == $functionName) { - $functionName = null; - - if ($isInMethod) { - $this->collector->currentMethodStop(); - $isInMethod = false; - } - } elseif ($block == $className) { - $className = null; - $testClass = false; - $this->collector->currentClassReset(); - } - } - } - - continue; - } - - list($token, $value) = $tokens[$i]; - - switch ($token) { - case T_NAMESPACE: - $namespace = $this->getNamespaceName($tokens, $i); - $this->collector->addNamespace($namespace); - break; - - case T_CLASS: - case T_INTERFACE: - case T_TRAIT: - if (!$this->isClassDeclaration($tokens, $i)) { - continue; - } - - $this->collector->currentClassReset(); - $this->collector->currentClassIncrementComplexity(); - $className = $this->getClassName($namespace, $tokens, $i); - $currentBlock = T_CLASS; - - if ($token == T_TRAIT) { - $this->collector->incrementTraits(); - } elseif ($token == T_INTERFACE) { - $this->collector->incrementInterfaces(); - } else { - if ($countTests && $this->isTestClass($className)) { - $testClass = true; - $this->collector->incrementTestClasses(); - } else { - if (isset($tokens[$i - 2]) && - \is_array($tokens[$i - 2]) && - $tokens[$i - 2][0] == T_ABSTRACT) { - $this->collector->incrementAbstractClasses(); - } else { - $this->collector->incrementConcreteClasses(); - } - } - } - break; - - case T_FUNCTION: - $prev = $this->getPreviousNonWhitespaceTokenPos($tokens, $i); - - if ($tokens[$prev][0] === T_USE) { - continue; - } - - $currentBlock = T_FUNCTION; - - $next = $this->getNextNonWhitespaceTokenPos($tokens, $i); - - if (!\is_array($tokens[$next]) && $tokens[$next] == '&') { - $next = $this->getNextNonWhitespaceTokenPos($tokens, $next); - } - - if (\is_array($tokens[$next]) && - $tokens[$next][0] == T_STRING) { - $functionName = $tokens[$next][1]; - } else { - $currentBlock = 'anonymous function'; - $functionName = 'anonymous function'; - $this->collector->incrementAnonymousFunctions(); - } - - if ($currentBlock == T_FUNCTION) { - if ($className === null && - $functionName != 'anonymous function') { - $this->collector->incrementNamedFunctions(); - } else { - $static = false; - $visibility = T_PUBLIC; - - for ($j = $i; $j > 0; $j--) { - if (\is_string($tokens[$j])) { - if ($tokens[$j] == '{' || - $tokens[$j] == '}' || - $tokens[$j] == ';') { - break; - } - - continue; - } - - if (isset($tokens[$j][0])) { - switch ($tokens[$j][0]) { - case T_PRIVATE: - $visibility = T_PRIVATE; - break; - - case T_PROTECTED: - $visibility = T_PROTECTED; - break; - - case T_STATIC: - $static = true; - break; - } - } - } - - if ($testClass && - $this->isTestMethod($functionName, $visibility, $static, $tokens, $i)) { - $this->collector->incrementTestMethods(); - } elseif (!$testClass) { - $isInMethod = true; - $this->collector->currentMethodStart(); - - if (!$static) { - $this->collector->incrementNonStaticMethods(); - } else { - $this->collector->incrementStaticMethods(); - } - - if ($visibility == T_PUBLIC) { - $this->collector->incrementPublicMethods(); - } else { - $this->collector->incrementNonPublicMethods(); - } - } - } - } - break; - - case T_CURLY_OPEN: - $currentBlock = T_CURLY_OPEN; - \array_push($blocks, $currentBlock); - break; - - case T_DOLLAR_OPEN_CURLY_BRACES: - $currentBlock = T_DOLLAR_OPEN_CURLY_BRACES; - \array_push($blocks, $currentBlock); - break; - - case T_IF: - case T_ELSEIF: - case T_FOR: - case T_FOREACH: - case T_WHILE: - case T_CASE: - case T_CATCH: - case T_BOOLEAN_AND: - case T_LOGICAL_AND: - case T_BOOLEAN_OR: - case T_LOGICAL_OR: - if (!$testClass) { - if ($isInMethod) { - $this->collector->currentClassIncrementComplexity(); - $this->collector->currentMethodIncrementComplexity(); - } - - $this->collector->incrementComplexity(); - } - break; - - case T_COMMENT: - case T_DOC_COMMENT: - // We want to count all intermediate lines before the token ends - // But sometimes a new token starts after a newline, we don't want to count that. - // That happened with /* */ and /** */, but not with // since it'll end at the end - $this->collector->incrementCommentLines(\substr_count(\rtrim($value, "\n"), "\n") + 1); - break; - case T_CONST: - $this->collector->incrementClassConstants(); - break; - - case T_STRING: - if ($value == 'define') { - $this->collector->incrementGlobalConstants(); - - $j = $i + 1; - - while (isset($tokens[$j]) && $tokens[$j] != ';') { - if (\is_array($tokens[$j]) && - $tokens[$j][0] == T_CONSTANT_ENCAPSED_STRING) { - $this->collector->addConstant(\str_replace('\'', '', $tokens[$j][1])); - - break; - } - - $j++; - } - } else { - $this->collector->addPossibleConstantAccesses($value); - } - break; - - case T_DOUBLE_COLON: - case T_OBJECT_OPERATOR: - $n = $this->getNextNonWhitespaceTokenPos($tokens, $i); - $nn = $this->getNextNonWhitespaceTokenPos($tokens, $n); - - if ($n && $nn && - isset($tokens[$n][0]) && - ($tokens[$n][0] == T_STRING || - $tokens[$n][0] == T_VARIABLE) && - $tokens[$nn] == '(') { - if ($token == T_DOUBLE_COLON) { - $this->collector->incrementStaticMethodCalls(); - } else { - $this->collector->incrementNonStaticMethodCalls(); - } - } else { - if ($token == T_DOUBLE_COLON && - $tokens[$n][0] == T_VARIABLE) { - $this->collector->incrementStaticAttributeAccesses(); - } elseif ($token == T_OBJECT_OPERATOR) { - $this->collector->incrementNonStaticAttributeAccesses(); - } - } - break; - - case T_GLOBAL: - $this->collector->incrementGlobalVariableAccesses(); - break; - - case T_VARIABLE: - if ($value == '$GLOBALS') { - $this->collector->incrementGlobalVariableAccesses(); - } elseif (isset($this->superGlobals[$value])) { - $this->collector->incrementSuperGlobalVariableAccesses(); - } - break; - } - } - } - - /** - * @param array $tokens - * @param int $i - * - * @return string - */ - private function getNamespaceName(array $tokens, $i) - { - if (isset($tokens[$i + 2][1])) { - $namespace = $tokens[$i + 2][1]; - - for ($j = $i + 3;; $j += 2) { - if (isset($tokens[$j]) && $tokens[$j][0] == T_NS_SEPARATOR) { - $namespace .= '\\' . $tokens[$j + 1][1]; - } else { - break; - } - } - - return $namespace; - } - - return false; - } - - /** - * @param string $namespace - * @param array $tokens - * @param int $i - * - * @return string - */ - private function getClassName($namespace, array $tokens, $i) - { - $i += 2; - if (!isset($tokens[$i][1])) { - return 'invalid class name'; - } - $className = $tokens[$i][1]; - - $namespaced = $className === '\\'; - - while (\is_array($tokens[$i + 1]) && $tokens[$i + 1][0] !== T_WHITESPACE) { - $className .= $tokens[++$i][1]; - } - - if (!$namespaced && $namespace !== false) { - $className = $namespace . '\\' . $className; - } - - return \strtolower($className); - } - - /** - * @param string $className - * - * @return bool - */ - private function isTestClass($className) - { - $parent = $this->classes[$className]; - $count = 0; - - // Check ancestry for PHPUnit_Framework_TestCase. - while ($parent !== null) { - $count++; - - if ($count > 100) { - // Prevent infinite loops and just bail - break; - } - - if ($parent == 'phpunit_framework_testcase' || - $parent == '\\phpunit_framework_testcase' || - // TODO: Recognize PHPUnit\Framework\TestCase when it is imported - $parent == 'phpunit\\framework\\testcase' || - $parent == '\\phpunit\\framework\\testcase') { - return true; - } - - if (isset($this->classes[$parent]) && $parent !== $this->classes[$parent]) { - $parent = $this->classes[$parent]; - } else { - // Class has a parent that is declared in a file - // that was not pre-processed. - break; - } - } - - // Fallback: Treat the class as a test case class if the name - // of the parent class ends with "TestCase". - return \substr($this->classes[$className], -8) === 'testcase'; - } - - /** - * @param string $functionName - * @param int $visibility - * @param bool $static - * @param array $tokens - * @param int $currentToken - * - * @return bool - */ - private function isTestMethod($functionName, $visibility, $static, array $tokens, $currentToken) - { - if ($static || $visibility != T_PUBLIC) { - return false; - } - - if (\strpos($functionName, 'test') === 0) { - return true; - } - - while ($tokens[$currentToken][0] != T_DOC_COMMENT) { - if ($tokens[$currentToken] == '{' || $tokens[$currentToken] == '}') { - return false; - } - - --$currentToken; - } - - return \strpos($tokens[$currentToken][1], '@test') !== false || - \strpos($tokens[$currentToken][1], '@scenario') !== false; - } - - /** - * @param array $tokens - * @param int $start - * - * @return bool - */ - private function getNextNonWhitespaceTokenPos(array $tokens, $start) - { - if (isset($tokens[$start + 1])) { - if (isset($tokens[$start + 1][0]) && - $tokens[$start + 1][0] == T_WHITESPACE && - isset($tokens[$start + 2])) { - return $start + 2; - } else { - return $start + 1; - } - } - - return false; - } - - /** - * @param array $tokens - * @param int $start - * - * @return bool - */ - private function getPreviousNonWhitespaceTokenPos(array $tokens, $start) - { - if (isset($tokens[$start - 1])) { - if (isset($tokens[$start - 1][0]) && - $tokens[$start - 1][0] == T_WHITESPACE && - isset($tokens[$start - 2])) { - return $start - 2; - } else { - return $start - 1; - } - } - - return false; - } - - /** - * @param array $tokens - * @param int $i - * - * @return bool - */ - private function isClassDeclaration(array $tokens, $i) - { - $n = $this->getPreviousNonWhitespaceTokenPos($tokens, $i); - - return !isset($tokens[$n]) - || !\is_array($tokens[$n]) - || !\in_array($tokens[$n][0], [T_DOUBLE_COLON, T_NEW], true); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SebastianBergmann\PHPLOC\CLI; - -use SebastianBergmann\Version; -use Symfony\Component\Console\Application as AbstractApplication; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Input\ArrayInput; - -/** - * TextUI frontend for PHPLOC. - */ -class Application extends AbstractApplication -{ - public function __construct() - { - $version = new Version('4.0.1', \dirname(\dirname(__DIR__))); - parent::__construct('phploc', $version->getVersion()); - } - - /** - * Gets the name of the command based on input. - * - * @param InputInterface $input The input interface - * - * @return string The command name - */ - protected function getCommandName(InputInterface $input) - { - return 'phploc'; - } - - /** - * Gets the default commands that should always be available. - * - * @return array An array of default Command instances - */ - protected function getDefaultCommands() - { - $defaultCommands = parent::getDefaultCommands(); - - $defaultCommands[] = new Command; - - return $defaultCommands; - } - - /** - * Overridden so that the application doesn't expect the command - * name to be the first argument. - */ - public function getDefinition() - { - $inputDefinition = parent::getDefinition(); - $inputDefinition->setArguments(); - - return $inputDefinition; - } - - /** - * Runs the current application. - * - * @param InputInterface $input An Input instance - * @param OutputInterface $output An Output instance - * - * @return int 0 if everything went fine, or an error code - */ - public function doRun(InputInterface $input, OutputInterface $output) - { - $this->disableXdebug(); - - if (!$input->hasParameterOption('--quiet')) { - $output->write( - \sprintf( - "phploc %s by Sebastian Bergmann.\n\n", - $this->getVersion() - ) - ); - } - - if ($input->hasParameterOption('--version') || - $input->hasParameterOption('-V')) { - exit; - } - - if (!$input->getFirstArgument()) { - $input = new ArrayInput(['--help']); - } - - parent::doRun($input, $output); - } - - private function disableXdebug() - { - if (!\extension_loaded('xdebug')) { - return; - } - - \ini_set('xdebug.scream', 0); - \ini_set('xdebug.max_nesting_level', 8192); - \ini_set('xdebug.show_exception_trace', 0); - \ini_set('xdebug.show_error_trace', 0); - - \xdebug_disable(); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SebastianBergmann\PHPLOC\CLI; - -use SebastianBergmann\FinderFacade\FinderFacade; -use SebastianBergmann\PHPLOC\Analyser; -use SebastianBergmann\PHPLOC\Log\Csv; -use SebastianBergmann\PHPLOC\Log\Text; -use SebastianBergmann\PHPLOC\Log\Xml; -use Symfony\Component\Console\Command\Command as AbstractCommand; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Output\OutputInterface; - -class Command extends AbstractCommand -{ - /** - * Configures the current command. - */ - protected function configure() - { - $this->setName('phploc') - ->setDefinition( - [ - new InputArgument( - 'values', - InputArgument::IS_ARRAY - ) - ] - ) - ->addOption( - 'names', - null, - InputOption::VALUE_REQUIRED, - 'A comma-separated list of file names to check', - ['*.php'] - ) - ->addOption( - 'names-exclude', - null, - InputOption::VALUE_REQUIRED, - 'A comma-separated list of file names to exclude', - [] - ) - ->addOption( - 'count-tests', - null, - InputOption::VALUE_NONE, - 'Count PHPUnit test case classes and test methods' - ) - ->addOption( - 'exclude', - null, - InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, - 'Exclude a directory from code analysis' - ) - ->addOption( - 'log-csv', - null, - InputOption::VALUE_REQUIRED, - 'Write result in CSV format to file' - ) - ->addOption( - 'log-xml', - null, - InputOption::VALUE_REQUIRED, - 'Write result in XML format to file' - ); - } - - /** - * Executes the current command. - * - * @param InputInterface $input An InputInterface instance - * @param OutputInterface $output An OutputInterface instance - * - * @return null|int null or 0 if everything went fine, or an error code - */ - protected function execute(InputInterface $input, OutputInterface $output) - { - $count = $this->count( - $input->getArgument('values'), - $input->getOption('exclude'), - $this->handleCSVOption($input, 'names'), - $this->handleCSVOption($input, 'names-exclude'), - $input->getOption('count-tests') - ); - - if (!$count) { - $output->writeln('No files found to scan'); - exit(1); - } - - $printer = new Text; - - $printer->printResult( - $output, - $count, - $input->getOption('count-tests') - ); - - if ($input->getOption('log-csv')) { - $printer = new Csv; - $printer->printResult($input->getOption('log-csv'), $count); - } - - if ($input->getOption('log-xml')) { - $printer = new Xml; - $printer->printResult($input->getOption('log-xml'), $count); - } - } - - private function count(array $arguments, $excludes, $names, $namesExclude, $countTests) - { - try { - $finder = new FinderFacade($arguments, $excludes, $names, $namesExclude); - $files = $finder->findFiles(); - } catch (\InvalidArgumentException $ex) { - return false; - } - - if (empty($files)) { - return false; - } - - $analyser = new Analyser; - - return $analyser->countFiles($files, $countTests); - } - - /** - * @param InputInterface $input - * @param string $option - * - * @return array - */ - private function handleCSVOption(InputInterface $input, $option) - { - $result = $input->getOption($option); - - return \is_array($result) ? $result : \explode(',', $result); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\PHPLOC; - -class Collector -{ - private $counts = []; - - private $currentClassComplexity = 0; - - private $currentClassLines = 0; - - private $currentMethodComplexity = 0; - - private $currentMethodLines = 0; - - public function getPublisher() - { - return new Publisher($this->counts); - } - - public function addFile($filename) - { - $this->increment('files'); - $this->addUnique('directories', \dirname($filename)); - } - - public function incrementLines($number) - { - $this->increment('lines', $number); - } - - public function incrementCommentLines($number) - { - $this->increment('comment lines', $number); - } - - public function incrementLogicalLines() - { - $this->increment('logical lines'); - } - - public function currentClassReset() - { - if ($this->currentClassComplexity > 0) { - $this->addToArray('class complexity', $this->currentClassComplexity); - $this->addToArray('class lines', $this->currentClassLines); - } - $this->currentClassComplexity = 0; - $this->currentClassLines = 0; - } - - public function currentClassIncrementComplexity() - { - $this->currentClassComplexity++; - } - - public function currentClassIncrementLines() - { - $this->currentClassLines++; - } - - public function currentMethodStart() - { - $this->currentMethodComplexity = 1; - $this->currentMethodLines = 0; - } - - public function currentMethodIncrementComplexity() - { - $this->currentMethodComplexity++; - $this->increment('total method complexity'); - } - - public function currentMethodIncrementLines() - { - $this->currentMethodLines++; - } - - public function currentMethodStop() - { - $this->addToArray('method complexity', $this->currentMethodComplexity); - $this->addToArray('method lines', $this->currentMethodLines); - } - - public function incrementFunctionLines() - { - $this->increment('function lines'); - } - - public function incrementComplexity() - { - $this->increment('complexity'); - } - - public function addPossibleConstantAccesses($name) - { - $this->addToArray('possible constant accesses', $name); - } - - public function addConstant($name) - { - $this->addToArray('constant', $name); - } - - public function incrementGlobalVariableAccesses() - { - $this->increment('global variable accesses'); - } - - public function incrementSuperGlobalVariableAccesses() - { - $this->increment('super global variable accesses'); - } - - public function incrementNonStaticAttributeAccesses() - { - $this->increment('non-static attribute accesses'); - } - - public function incrementStaticAttributeAccesses() - { - $this->increment('static attribute accesses'); - } - - public function incrementNonStaticMethodCalls() - { - $this->increment('non-static method calls'); - } - - public function incrementStaticMethodCalls() - { - $this->increment('static method calls'); - } - - public function addNamespace($namespace) - { - $this->addUnique('namespaces', $namespace); - } - - public function incrementInterfaces() - { - $this->increment('interfaces'); - } - - public function incrementTraits() - { - $this->increment('traits'); - } - - public function incrementAbstractClasses() - { - $this->increment('abstract classes'); - } - - public function incrementConcreteClasses() - { - $this->increment('concrete classes'); - } - - public function incrementNonStaticMethods() - { - $this->increment('non-static methods'); - } - - public function incrementStaticMethods() - { - $this->increment('static methods'); - } - - public function incrementPublicMethods() - { - $this->increment('public methods'); - } - - public function incrementNonPublicMethods() - { - $this->increment('non-public methods'); - } - - public function incrementNamedFunctions() - { - $this->increment('named functions'); - } - - public function incrementAnonymousFunctions() - { - $this->increment('anonymous functions'); - } - - public function incrementGlobalConstants() - { - $this->increment('global constants'); - } - - public function incrementClassConstants() - { - $this->increment('class constants'); - } - - public function incrementTestClasses() - { - $this->increment('test classes'); - } - - public function incrementTestMethods() - { - $this->increment('test methods'); - } - - private function addUnique($key, $name) - { - $this->check($key, []); - $this->counts[$key][$name] = true; - } - - private function addToArray($key, $value) - { - $this->check($key, []); - $this->counts[$key][] = $value; - } - - private function increment($key, $number = 1) - { - $this->check($key, 0); - $this->counts[$key] += $number; - } - - private function check($key, $default) - { - if (!isset($this->counts[$key])) { - $this->counts[$key] = $default; - } - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SebastianBergmann\PHPLOC; - -interface Exception -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SebastianBergmann\PHPLOC; - -class RuntimeException extends \RuntimeException implements Exception -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SebastianBergmann\PHPLOC\Log; - -/** - * A CSV ResultPrinter for the TextUI. - */ -class Csv -{ - /** - * Mapping between internal and human-readable metric names - * - * @var array - */ - private $colmap = [ - 'directories' => 'Directories', - 'files' => 'Files', - 'loc' => 'Lines of Code (LOC)', - 'ccnByLloc' => 'Cyclomatic Complexity / Lines of Code', - 'cloc' => 'Comment Lines of Code (CLOC)', - 'ncloc' => 'Non-Comment Lines of Code (NCLOC)', - 'lloc' => 'Logical Lines of Code (LLOC)', - 'llocGlobal' => 'LLOC outside functions or classes', - 'namespaces' => 'Namespaces', - 'interfaces' => 'Interfaces', - 'traits' => 'Traits', - 'classes' => 'Classes', - 'abstractClasses' => 'Abstract Classes', - 'concreteClasses' => 'Concrete Classes', - 'llocClasses' => 'Classes Length (LLOC)', - 'methods' => 'Methods', - 'nonStaticMethods' => 'Non-Static Methods', - 'staticMethods' => 'Static Methods', - 'publicMethods' => 'Public Methods', - 'nonPublicMethods' => 'Non-Public Methods', - 'methodCcnAvg' => 'Cyclomatic Complexity / Number of Methods', - 'functions' => 'Functions', - 'namedFunctions' => 'Named Functions', - 'anonymousFunctions' => 'Anonymous Functions', - 'llocFunctions' => 'Functions Length (LLOC)', - 'llocByNof' => 'Average Function Length (LLOC)', - 'constants' => 'Constants', - 'globalConstants' => 'Global Constants', - 'classConstants' => 'Class Constants', - 'attributeAccesses' => 'Attribute Accesses', - 'instanceAttributeAccesses' => 'Non-Static Attribute Accesses', - 'staticAttributeAccesses' => 'Static Attribute Accesses', - 'methodCalls' => 'Method Calls', - 'instanceMethodCalls' => 'Non-Static Method Calls', - 'staticMethodCalls' => 'Static Method Calls', - 'globalAccesses' => 'Global Accesses', - 'globalVariableAccesses' => 'Global Variable Accesses', - 'superGlobalVariableAccesses' => 'Super-Global Variable Accesses', - 'globalConstantAccesses' => 'Global Constant Accesses', - 'testClasses' => 'Test Classes', - 'testMethods' => 'Test Methods' - ]; - - /** - * Prints a result set. - * - * @param string $filename - * @param array $count - */ - public function printResult($filename, array $count) - { - \file_put_contents( - $filename, - $this->getKeysLine($count) . $this->getValuesLine($count) - ); - } - - /** - * @param array $count - * - * @return string - */ - protected function getKeysLine(array $count) - { - return \implode(',', \array_values($this->colmap)) . PHP_EOL; - } - - /** - * @param array $count - * - * @throws \InvalidArgumentException - * - * @return string - */ - protected function getValuesLine(array $count) - { - $values = []; - - foreach ($this->colmap as $key => $name) { - if (isset($count[$key])) { - $values[] = $count[$key]; - } else { - throw new \InvalidArgumentException('Attempted to print row with missing keys'); - } - } - - return '"' . \implode('","', $values) . '"' . PHP_EOL; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SebastianBergmann\PHPLOC\Log; - -use Symfony\Component\Console\Output\OutputInterface; - -/** - * A ResultPrinter for the TextUI. - */ -class Text -{ - /** - * Prints a result set. - * - * @param OutputInterface $output - * @param array $count - * @param bool $printTests - */ - public function printResult(OutputInterface $output, array $count, $printTests) - { - if ($count['directories'] > 0) { - $output->write( - \sprintf( - "Directories %10d\n" . - "Files %10d\n\n", - $count['directories'], - $count['files'] - ) - ); - } - - $format = <<write( - \sprintf( - $format, - $count['loc'], - $count['cloc'], - $count['loc'] > 0 ? ($count['cloc'] / $count['loc']) * 100 : 0, - $count['ncloc'], - $count['loc'] > 0 ? ($count['ncloc'] / $count['loc']) * 100 : 0, - $count['lloc'], - $count['loc'] > 0 ? ($count['lloc'] / $count['loc']) * 100 : 0, - $count['llocClasses'], - $count['lloc'] > 0 ? ($count['llocClasses'] / $count['lloc']) * 100 : 0, - $count['classLlocAvg'], - $count['classLlocMin'], - $count['classLlocMax'], - $count['methodLlocAvg'], - $count['methodLlocMin'], - $count['methodLlocMax'], - $count['llocFunctions'], - $count['lloc'] > 0 ? ($count['llocFunctions'] / $count['lloc']) * 100 : 0, - $count['llocByNof'], - $count['llocGlobal'], - $count['lloc'] > 0 ? ($count['llocGlobal'] / $count['lloc']) * 100 : 0, - $count['ccnByLloc'], - $count['classCcnAvg'], - $count['classCcnMin'], - $count['classCcnMax'], - $count['methodCcnAvg'], - $count['methodCcnMin'], - $count['methodCcnMax'], - $count['globalAccesses'], - $count['globalConstantAccesses'], - $count['globalAccesses'] > 0 ? ($count['globalConstantAccesses'] / $count['globalAccesses']) * 100 : 0, - $count['globalVariableAccesses'], - $count['globalAccesses'] > 0 ? ($count['globalVariableAccesses'] / $count['globalAccesses']) * 100 : 0, - $count['superGlobalVariableAccesses'], - $count['globalAccesses'] > 0 ? ($count['superGlobalVariableAccesses'] / $count['globalAccesses']) * 100 : 0, - $count['attributeAccesses'], - $count['instanceAttributeAccesses'], - $count['attributeAccesses'] > 0 ? ($count['instanceAttributeAccesses'] / $count['attributeAccesses']) * 100 : 0, - $count['staticAttributeAccesses'], - $count['attributeAccesses'] > 0 ? ($count['staticAttributeAccesses'] / $count['attributeAccesses']) * 100 : 0, - $count['methodCalls'], - $count['instanceMethodCalls'], - $count['methodCalls'] > 0 ? ($count['instanceMethodCalls'] / $count['methodCalls']) * 100 : 0, - $count['staticMethodCalls'], - $count['methodCalls'] > 0 ? ($count['staticMethodCalls'] / $count['methodCalls']) * 100 : 0, - $count['namespaces'], - $count['interfaces'], - $count['traits'], - $count['classes'], - $count['abstractClasses'], - $count['classes'] > 0 ? ($count['abstractClasses'] / $count['classes']) * 100 : 0, - $count['concreteClasses'], - $count['classes'] > 0 ? ($count['concreteClasses'] / $count['classes']) * 100 : 0, - $count['methods'], - $count['nonStaticMethods'], - $count['methods'] > 0 ? ($count['nonStaticMethods'] / $count['methods']) * 100 : 0, - $count['staticMethods'], - $count['methods'] > 0 ? ($count['staticMethods'] / $count['methods']) * 100 : 0, - $count['publicMethods'], - $count['methods'] > 0 ? ($count['publicMethods'] / $count['methods']) * 100 : 0, - $count['nonPublicMethods'], - $count['methods'] > 0 ? ($count['nonPublicMethods'] / $count['methods']) * 100 : 0, - $count['functions'], - $count['namedFunctions'], - $count['functions'] > 0 ? ($count['namedFunctions'] / $count['functions']) * 100 : 0, - $count['anonymousFunctions'], - $count['functions'] > 0 ? ($count['anonymousFunctions'] / $count['functions']) * 100 : 0, - $count['constants'], - $count['globalConstants'], - $count['constants'] > 0 ? ($count['globalConstants'] / $count['constants']) * 100 : 0, - $count['classConstants'], - $count['constants'] > 0 ? ($count['classConstants'] / $count['constants']) * 100 : 0 - ) - ); - - if ($printTests) { - $output->write( - \sprintf( - "\nTests\n" . - " Classes %10d\n" . - " Methods %10d\n", - $count['testClasses'], - $count['testMethods'] - ) - ); - } - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SebastianBergmann\PHPLOC\Log; - -/** - * An XML ResultPrinter for the TextUI. - */ -class Xml -{ - /** - * Prints a result set. - * - * @param string $filename - * @param array $count - */ - public function printResult($filename, array $count) - { - $document = new \DOMDocument('1.0', 'UTF-8'); - $document->formatOutput = true; - - $root = $document->createElement('phploc'); - $document->appendChild($root); - - if ($count['directories'] > 0) { - $root->appendChild( - $document->createElement('directories', $count['directories']) - ); - - $root->appendChild( - $document->createElement('files', $count['files']) - ); - } - - unset($count['directories']); - unset($count['files']); - - foreach ($count as $k => $v) { - $root->appendChild( - $document->createElement($k, $v) - ); - } - - \file_put_contents($filename, $document->saveXML()); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\PHPLOC; - -class Publisher -{ - private $counts; - - public function __construct(array $counts) - { - $this->counts = $counts; - } - - public function getDirectories() - { - return $this->getCount('directories') - 1; - } - - public function getFiles() - { - return $this->getValue('files'); - } - - public function getLines() - { - return $this->getValue('lines'); - } - - public function getCommentLines() - { - return $this->getValue('comment lines'); - } - - public function getNonCommentLines() - { - return $this->getLines() - $this->getCommentLines(); - } - - public function getLogicalLines() - { - return $this->getValue('logical lines'); - } - - public function getClassLines() - { - return $this->getSum('class lines'); - } - - public function getAverageClassLength() - { - return $this->getAverage('class lines'); - } - - public function getMinimumClassLength() - { - return $this->getMinimum('class lines'); - } - - public function getMaximumClassLength() - { - return $this->getMaximum('class lines'); - } - - public function getAverageMethodLength() - { - return $this->getAverage('method lines'); - } - - public function getMinimumMethodLength() - { - return $this->getMinimum('method lines'); - } - - public function getMaximumMethodLength() - { - return $this->getMaximum('method lines'); - } - - public function getFunctionLines() - { - return $this->getValue('function lines'); - } - - public function getAverageFunctionLength() - { - return $this->divide($this->getFunctionLines(), $this->getFunctions()); - } - - public function getNotInClassesOrFunctions() - { - return $this->getLogicalLines() - $this->getClassLines() - $this->getFunctionLines(); - } - - public function getComplexity() - { - return $this->getValue('complexity'); - } - - public function getMethodComplexity() - { - return $this->getValue('total method complexity'); - } - - public function getAverageComplexityPerLogicalLine() - { - return $this->divide($this->getComplexity(), $this->getLogicalLines()); - } - - public function getAverageComplexityPerClass() - { - return $this->getAverage('class complexity'); - } - - public function getMinimumClassComplexity() - { - return $this->getMinimum('class complexity'); - } - - public function getMaximumClassComplexity() - { - return $this->getMaximum('class complexity'); - } - - public function getAverageComplexityPerMethod() - { - return $this->getAverage('method complexity'); - } - - public function getMinimumMethodComplexity() - { - return $this->getMinimum('method complexity'); - } - - public function getMaximumMethodComplexity() - { - return $this->getMaximum('method complexity'); - } - - public function getGlobalAccesses() - { - return $this->getGlobalConstantAccesses() + $this->getGlobalVariableAccesses() + $this->getSuperGlobalVariableAccesses(); - } - - public function getGlobalConstantAccesses() - { - return \count(\array_intersect($this->getValue('possible constant accesses', []), $this->getValue('constant', []))); - } - - public function getGlobalVariableAccesses() - { - return $this->getValue('global variable accesses'); - } - - public function getSuperGlobalVariableAccesses() - { - return $this->getValue('super global variable accesses'); - } - - public function getAttributeAccesses() - { - return $this->getNonStaticAttributeAccesses() + $this->getStaticAttributeAccesses(); - } - - public function getNonStaticAttributeAccesses() - { - return $this->getValue('non-static attribute accesses'); - } - - public function getStaticAttributeAccesses() - { - return $this->getValue('static attribute accesses'); - } - - public function getMethodCalls() - { - return $this->getNonStaticMethodCalls() + $this->getStaticMethodCalls(); - } - - public function getNonStaticMethodCalls() - { - return $this->getValue('non-static method calls'); - } - - public function getStaticMethodCalls() - { - return $this->getValue('static method calls'); - } - - public function getNamespaces() - { - return $this->getCount('namespaces'); - } - - public function getInterfaces() - { - return $this->getValue('interfaces'); - } - - public function getTraits() - { - return $this->getValue('traits'); - } - - public function getClasses() - { - return $this->getAbstractClasses() + $this->getConcreteClasses(); - } - - public function getAbstractClasses() - { - return $this->getValue('abstract classes'); - } - - public function getConcreteClasses() - { - return $this->getValue('concrete classes'); - } - - public function getMethods() - { - return $this->getNonStaticMethods() + $this->getStaticMethods(); - } - - public function getNonStaticMethods() - { - return $this->getValue('non-static methods'); - } - - public function getStaticMethods() - { - return $this->getValue('static methods'); - } - - public function getPublicMethods() - { - return $this->getValue('public methods'); - } - - public function getNonPublicMethods() - { - return $this->getValue('non-public methods'); - } - - public function getFunctions() - { - return $this->getNamedFunctions() + $this->getAnonymousFunctions(); - } - - public function getNamedFunctions() - { - return $this->getValue('named functions'); - } - - public function getAnonymousFunctions() - { - return $this->getValue('anonymous functions'); - } - - public function getConstants() - { - return $this->getGlobalConstants() + $this->getClassConstants(); - } - - public function getGlobalConstants() - { - return $this->getValue('global constants'); - } - - public function getClassConstants() - { - return $this->getValue('class constants'); - } - - public function getTestClasses() - { - return $this->getValue('test classes'); - } - - public function getTestMethods() - { - return $this->getValue('test methods'); - } - - public function toArray() - { - return [ - 'files' => $this->getFiles(), - 'loc' => $this->getLines(), - 'lloc' => $this->getLogicalLines(), - 'llocClasses' => $this->getClassLines(), - 'llocFunctions' => $this->getFunctionLines(), - 'llocGlobal' => $this->getNotInClassesOrFunctions(), - 'cloc' => $this->getCommentLines(), - 'ccn' => $this->getComplexity(), - 'ccnMethods' => $this->getMethodComplexity(), - 'interfaces' => $this->getInterfaces(), - 'traits' => $this->getTraits(), - 'classes' => $this->getClasses(), - 'abstractClasses' => $this->getAbstractClasses(), - 'concreteClasses' => $this->getConcreteClasses(), - 'functions' => $this->getFunctions(), - 'namedFunctions' => $this->getNamedFunctions(), - 'anonymousFunctions' => $this->getAnonymousFunctions(), - 'methods' => $this->getMethods(), - 'publicMethods' => $this->getPublicMethods(), - 'nonPublicMethods' => $this->getNonPublicMethods(), - 'nonStaticMethods' => $this->getNonStaticMethods(), - 'staticMethods' => $this->getStaticMethods(), - 'constants' => $this->getConstants(), - 'classConstants' => $this->getClassConstants(), - 'globalConstants' => $this->getGlobalConstants(), - 'testClasses' => $this->getTestClasses(), - 'testMethods' => $this->getTestMethods(), - 'ccnByLloc' => $this->getAverageComplexityPerLogicalLine(), - 'llocByNof' => $this->getAverageFunctionLength(), - 'methodCalls' => $this->getMethodCalls(), - 'staticMethodCalls' => $this->getStaticMethodCalls(), - 'instanceMethodCalls' => $this->getNonStaticMethodCalls(), - 'attributeAccesses' => $this->getAttributeAccesses(), - 'staticAttributeAccesses' => $this->getStaticAttributeAccesses(), - 'instanceAttributeAccesses' => $this->getNonStaticAttributeAccesses(), - 'globalAccesses' => $this->getGlobalAccesses(), - 'globalVariableAccesses' => $this->getGlobalVariableAccesses(), - 'superGlobalVariableAccesses' => $this->getSuperGlobalVariableAccesses(), - 'globalConstantAccesses' => $this->getGlobalConstantAccesses(), - 'directories' => $this->getDirectories(), - 'classCcnMin' => $this->getMinimumClassComplexity(), - 'classCcnAvg' => $this->getAverageComplexityPerClass(), - 'classCcnMax' => $this->getMaximumClassComplexity(), - 'classLlocMin' => $this->getMinimumClassLength(), - 'classLlocAvg' => $this->getAverageClassLength(), - 'classLlocMax' => $this->getMaximumClassLength(), - 'methodCcnMin' => $this->getMinimumMethodComplexity(), - 'methodCcnAvg' => $this->getAverageComplexityPerMethod(), - 'methodCcnMax' => $this->getMaximumMethodComplexity(), - 'methodLlocMin' => $this->getMinimumMethodLength(), - 'methodLlocAvg' => $this->getAverageMethodLength(), - 'methodLlocMax' => $this->getMaximumMethodLength(), - 'namespaces' => $this->getNamespaces(), - 'ncloc' => $this->getNonCommentLines(), - ]; - } - - private function getAverage($key) - { - return $this->divide($this->getSum($key), $this->getCount($key)); - } - - private function getCount($key) - { - return isset($this->counts[$key]) ? \count($this->counts[$key]) : 0; - } - - private function getSum($key) - { - return isset($this->counts[$key]) ? \array_sum($this->counts[$key]) : 0; - } - - private function getMaximum($key) - { - return isset($this->counts[$key]) ? \max($this->counts[$key]) : 0; - } - - private function getMinimum($key) - { - return isset($this->counts[$key]) ? \min($this->counts[$key]) : 0; - } - - private function getValue($key, $default = 0) - { - return isset($this->counts[$key]) ? $this->counts[$key] : $default; - } - - private function divide($x, $y) - { - return $y != 0 ? $x / $y : 0; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SebastianBergmann\FinderFacade; - -use TheSeer\fDOM\fDOMDocument; - -/** - * - * - * - * /path/to/directory - * /path/to/file - * - * /path/to/directory - * *.php - * - * - * - * @since Class available since Release 1.0.0 - */ -class Configuration -{ - /** - * @var string - */ - protected $basePath; - - /** - * @var fDOMDocument - */ - protected $xml; - - /** - * @param string $file - */ - public function __construct($file) - { - $this->basePath = dirname($file); - - $this->xml = new fDOMDocument; - $this->xml->load($file); - } - - /** - * @param string $xpath - * - * @return array - */ - public function parse($xpath = '') - { - $result = array( - 'items' => array(), - 'excludes' => array(), - 'names' => array(), - 'notNames' => array(), - 'regularExpressionExcludes' => array() - ); - - foreach ($this->xml->getDOMXPath()->query($xpath . 'include/directory') as $item) { - $result['items'][] = $this->toAbsolutePath($item->nodeValue); - } - - foreach ($this->xml->getDOMXPath()->query($xpath . 'include/file') as $item) { - $result['items'][] = $this->toAbsolutePath($item->nodeValue); - } - - foreach ($this->xml->getDOMXPath()->query($xpath . 'exclude') as $exclude) { - $result['excludes'][] = $exclude->nodeValue; - } - - foreach ($this->xml->getDOMXPath()->query($xpath . 'name') as $name) { - $result['names'][] = $name->nodeValue; - } - - foreach ($this->xml->getDOMXPath()->query($xpath . 'notName') as $notName) { - $result['notNames'][] = $notName->nodeValue; - } - - foreach ($this->xml->getDOMXPath()->query($xpath . 'regularExpressionExcludes') as $regularExpressionExclude) { - $result['regularExpressionExcludes'][] = $regularExpressionExclude->nodeValue; - } - - return $result; - } - - /** - * @param string $path - * - * @return string - */ - protected function toAbsolutePath($path) - { - // Check whether the path is already absolute. - if ($path[0] === '/' || $path[0] === '\\' || (strlen($path) > 3 && ctype_alpha($path[0]) && - $path[1] === ':' && ($path[2] === '\\' || $path[2] === '/'))) { - return $path; - } - - // Check whether a stream is used. - if (strpos($path, '://') !== false) { - return $path; - } - - return $this->basePath . DIRECTORY_SEPARATOR . $path; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SebastianBergmann\FinderFacade; - -use Symfony\Component\Finder\Finder; - -/** - * Convenience wrapper for Symfony's Finder component. - * - * @since Class available since Release 1.0.0 - */ -class FinderFacade -{ - /** - * @var array - */ - protected $items = array(); - - /** - * @var array - */ - protected $excludes = array(); - - /** - * @var array - */ - protected $names = array(); - - /** - * @var array - */ - protected $notNames = array(); - - /** - * @var array - */ - protected $regularExpressionsExcludes = array(); - - /** - * @param array $items - * @param array $excludes - * @param array $names - * @param array $notNames - * @param array $regularExpressionsExcludes - */ - public function __construct(array $items = array(), array $excludes = array(), array $names = array(), array $notNames = array(), $regularExpressionsExcludes = array()) - { - $this->items = $items; - $this->excludes = $excludes; - $this->names = $names; - $this->notNames = $notNames; - $this->regularExpressionsExcludes = $regularExpressionsExcludes; - } - - /** - * @return array - */ - public function findFiles() - { - $files = array(); - $finder = new Finder; - $iterate = false; - - $finder->ignoreUnreadableDirs(); - - foreach ($this->items as $item) { - if (!is_file($item)) { - $finder->in($item); - $iterate = true; - } else { - $files[] = realpath($item); - } - } - - foreach ($this->excludes as $exclude) { - $finder->exclude($exclude); - } - - foreach ($this->names as $name) { - $finder->name($name); - } - - foreach ($this->notNames as $notName) { - $finder->notName($notName); - } - - foreach ($this->regularExpressionsExcludes as $regularExpressionExclude) { - $finder->notPath($regularExpressionExclude); - } - - if ($iterate) { - foreach ($finder as $file) { - $files[] = $file->getRealpath(); - } - } - - return $files; - } - - /** - * @param string $file - */ - public function loadConfiguration($file) - { - $configuration = new Configuration($file); - $configuration = $configuration->parse(); - - $this->items = $configuration['items']; - $this->excludes = $configuration['excludes']; - $this->names = $configuration['names']; - $this->notNames = $configuration['notNames']; - $this->regularExpressionsExcludes = $configuration['regularExpressionExcludes']; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace SebastianBergmann; - -/** - * @since Class available since Release 1.0.0 - */ -class Version -{ - /** - * @var string - */ - private $path; - - /** - * @var string - */ - private $release; - - /** - * @var string - */ - private $version; - - /** - * @param string $release - * @param string $path - */ - public function __construct($release, $path) - { - $this->release = $release; - $this->path = $path; - } - - /** - * @return string - */ - public function getVersion() - { - if ($this->version === null) { - if (count(explode('.', $this->release)) == 3) { - $this->version = $this->release; - } else { - $this->version = $this->release . '-dev'; - } - - $git = $this->getGitInformation($this->path); - - if ($git) { - if (count(explode('.', $this->release)) == 3) { - $this->version = $git; - } else { - $git = explode('-', $git); - - $this->version = $this->release . '-' . end($git); - } - } - } - - return $this->version; - } - - /** - * @param string $path - * - * @return bool|string - */ - private function getGitInformation($path) - { - if (!is_dir($path . DIRECTORY_SEPARATOR . '.git')) { - return false; - } - - $process = proc_open( - 'git describe --tags', - [ - 1 => ['pipe', 'w'], - 2 => ['pipe', 'w'], - ], - $pipes, - $path - ); - - if (!is_resource($process)) { - return false; - } - - $result = trim(stream_get_contents($pipes[1])); - - fclose($pipes[1]); - fclose($pipes[2]); - - $returnCode = proc_close($process); - - if ($returnCode !== 0) { - return false; - } - - return $result; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console; - -use Symfony\Component\Console\Exception\ExceptionInterface; -use Symfony\Component\Console\Formatter\OutputFormatter; -use Symfony\Component\Console\Helper\DebugFormatterHelper; -use Symfony\Component\Console\Helper\Helper; -use Symfony\Component\Console\Helper\ProcessHelper; -use Symfony\Component\Console\Helper\QuestionHelper; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\StreamableInputInterface; -use Symfony\Component\Console\Input\ArgvInput; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Input\InputDefinition; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputAwareInterface; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Output\ConsoleOutput; -use Symfony\Component\Console\Output\ConsoleOutputInterface; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Command\HelpCommand; -use Symfony\Component\Console\Command\ListCommand; -use Symfony\Component\Console\Helper\HelperSet; -use Symfony\Component\Console\Helper\FormatterHelper; -use Symfony\Component\Console\Event\ConsoleCommandEvent; -use Symfony\Component\Console\Event\ConsoleErrorEvent; -use Symfony\Component\Console\Event\ConsoleExceptionEvent; -use Symfony\Component\Console\Event\ConsoleTerminateEvent; -use Symfony\Component\Console\Exception\CommandNotFoundException; -use Symfony\Component\Console\Exception\LogicException; -use Symfony\Component\Debug\Exception\FatalThrowableError; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; - -/** - * An Application is the container for a collection of commands. - * - * It is the main entry point of a Console application. - * - * This class is optimized for a standard CLI environment. - * - * Usage: - * - * $app = new Application('myapp', '1.0 (stable)'); - * $app->add(new SimpleCommand()); - * $app->run(); - * - * @author Fabien Potencier - */ -class Application -{ - private $commands = array(); - private $wantHelps = false; - private $runningCommand; - private $name; - private $version; - private $catchExceptions = true; - private $autoExit = true; - private $definition; - private $helperSet; - private $dispatcher; - private $terminal; - private $defaultCommand; - private $singleCommand; - private $initialized; - - /** - * @param string $name The name of the application - * @param string $version The version of the application - */ - public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN') - { - $this->name = $name; - $this->version = $version; - $this->terminal = new Terminal(); - $this->defaultCommand = 'list'; - } - - public function setDispatcher(EventDispatcherInterface $dispatcher) - { - $this->dispatcher = $dispatcher; - } - - /** - * Runs the current application. - * - * @return int 0 if everything went fine, or an error code - * - * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}. - */ - public function run(InputInterface $input = null, OutputInterface $output = null) - { - putenv('LINES='.$this->terminal->getHeight()); - putenv('COLUMNS='.$this->terminal->getWidth()); - - if (null === $input) { - $input = new ArgvInput(); - } - - if (null === $output) { - $output = new ConsoleOutput(); - } - - if (null !== $this->dispatcher && $this->dispatcher->hasListeners(ConsoleEvents::EXCEPTION)) { - @trigger_error(sprintf('The "ConsoleEvents::EXCEPTION" event is deprecated since Symfony 3.3 and will be removed in 4.0. Listen to the "ConsoleEvents::ERROR" event instead.'), E_USER_DEPRECATED); - } - - $this->configureIO($input, $output); - - try { - $e = null; - $exitCode = $this->doRun($input, $output); - } catch (\Exception $e) { - } catch (\Throwable $e) { - } - - if (null !== $e) { - if (!$this->catchExceptions || !$e instanceof \Exception) { - throw $e; - } - - if ($output instanceof ConsoleOutputInterface) { - $this->renderException($e, $output->getErrorOutput()); - } else { - $this->renderException($e, $output); - } - - $exitCode = $e->getCode(); - if (is_numeric($exitCode)) { - $exitCode = (int) $exitCode; - if (0 === $exitCode) { - $exitCode = 1; - } - } else { - $exitCode = 1; - } - } - - if ($this->autoExit) { - if ($exitCode > 255) { - $exitCode = 255; - } - - exit($exitCode); - } - - return $exitCode; - } - - /** - * Runs the current application. - * - * @return int 0 if everything went fine, or an error code - */ - public function doRun(InputInterface $input, OutputInterface $output) - { - if (true === $input->hasParameterOption(array('--version', '-V'), true)) { - $output->writeln($this->getLongVersion()); - - return 0; - } - - $name = $this->getCommandName($input); - if (true === $input->hasParameterOption(array('--help', '-h'), true)) { - if (!$name) { - $name = 'help'; - $input = new ArrayInput(array('command_name' => $this->defaultCommand)); - } else { - $this->wantHelps = true; - } - } - - if (!$name) { - $name = $this->defaultCommand; - $definition = $this->getDefinition(); - $definition->setArguments(array_merge( - $definition->getArguments(), - array( - 'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name), - ) - )); - } - - try { - $e = $this->runningCommand = null; - // the command name MUST be the first element of the input - $command = $this->find($name); - } catch (\Exception $e) { - } catch (\Throwable $e) { - } - if (null !== $e) { - if (null !== $this->dispatcher) { - $event = new ConsoleErrorEvent($input, $output, $e); - $this->dispatcher->dispatch(ConsoleEvents::ERROR, $event); - $e = $event->getError(); - - if (0 === $event->getExitCode()) { - return 0; - } - } - - throw $e; - } - - $this->runningCommand = $command; - $exitCode = $this->doRunCommand($command, $input, $output); - $this->runningCommand = null; - - return $exitCode; - } - - public function setHelperSet(HelperSet $helperSet) - { - $this->helperSet = $helperSet; - } - - /** - * Get the helper set associated with the command. - * - * @return HelperSet The HelperSet instance associated with this command - */ - public function getHelperSet() - { - if (!$this->helperSet) { - $this->helperSet = $this->getDefaultHelperSet(); - } - - return $this->helperSet; - } - - public function setDefinition(InputDefinition $definition) - { - $this->definition = $definition; - } - - /** - * Gets the InputDefinition related to this Application. - * - * @return InputDefinition The InputDefinition instance - */ - public function getDefinition() - { - if (!$this->definition) { - $this->definition = $this->getDefaultInputDefinition(); - } - - if ($this->singleCommand) { - $inputDefinition = $this->definition; - $inputDefinition->setArguments(); - - return $inputDefinition; - } - - return $this->definition; - } - - /** - * Gets the help message. - * - * @return string A help message - */ - public function getHelp() - { - return $this->getLongVersion(); - } - - /** - * Gets whether to catch exceptions or not during commands execution. - * - * @return bool Whether to catch exceptions or not during commands execution - */ - public function areExceptionsCaught() - { - return $this->catchExceptions; - } - - /** - * Sets whether to catch exceptions or not during commands execution. - * - * @param bool $boolean Whether to catch exceptions or not during commands execution - */ - public function setCatchExceptions($boolean) - { - $this->catchExceptions = (bool) $boolean; - } - - /** - * Gets whether to automatically exit after a command execution or not. - * - * @return bool Whether to automatically exit after a command execution or not - */ - public function isAutoExitEnabled() - { - return $this->autoExit; - } - - /** - * Sets whether to automatically exit after a command execution or not. - * - * @param bool $boolean Whether to automatically exit after a command execution or not - */ - public function setAutoExit($boolean) - { - $this->autoExit = (bool) $boolean; - } - - /** - * Gets the name of the application. - * - * @return string The application name - */ - public function getName() - { - return $this->name; - } - - /** - * Sets the application name. - * - * @param string $name The application name - */ - public function setName($name) - { - $this->name = $name; - } - - /** - * Gets the application version. - * - * @return string The application version - */ - public function getVersion() - { - return $this->version; - } - - /** - * Sets the application version. - * - * @param string $version The application version - */ - public function setVersion($version) - { - $this->version = $version; - } - - /** - * Returns the long version of the application. - * - * @return string The long application version - */ - public function getLongVersion() - { - if ('UNKNOWN' !== $this->getName()) { - if ('UNKNOWN' !== $this->getVersion()) { - return sprintf('%s %s', $this->getName(), $this->getVersion()); - } - - return $this->getName(); - } - - return 'Console Tool'; - } - - /** - * Registers a new command. - * - * @param string $name The command name - * - * @return Command The newly created command - */ - public function register($name) - { - return $this->add(new Command($name)); - } - - /** - * Adds an array of command objects. - * - * If a Command is not enabled it will not be added. - * - * @param Command[] $commands An array of commands - */ - public function addCommands(array $commands) - { - foreach ($commands as $command) { - $this->add($command); - } - } - - /** - * Adds a command object. - * - * If a command with the same name already exists, it will be overridden. - * If the command is not enabled it will not be added. - * - * @return Command|null The registered command if enabled or null - */ - public function add(Command $command) - { - $this->init(); - - $command->setApplication($this); - - if (!$command->isEnabled()) { - $command->setApplication(null); - - return; - } - - if (null === $command->getDefinition()) { - throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command))); - } - - $this->commands[$command->getName()] = $command; - - foreach ($command->getAliases() as $alias) { - $this->commands[$alias] = $command; - } - - return $command; - } - - /** - * Returns a registered command by name or alias. - * - * @param string $name The command name or alias - * - * @return Command A Command object - * - * @throws CommandNotFoundException When given command name does not exist - */ - public function get($name) - { - $this->init(); - - if (!isset($this->commands[$name])) { - throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name)); - } - - $command = $this->commands[$name]; - - if ($this->wantHelps) { - $this->wantHelps = false; - - $helpCommand = $this->get('help'); - $helpCommand->setCommand($command); - - return $helpCommand; - } - - return $command; - } - - /** - * Returns true if the command exists, false otherwise. - * - * @param string $name The command name or alias - * - * @return bool true if the command exists, false otherwise - */ - public function has($name) - { - $this->init(); - - return isset($this->commands[$name]); - } - - /** - * Returns an array of all unique namespaces used by currently registered commands. - * - * It does not return the global namespace which always exists. - * - * @return string[] An array of namespaces - */ - public function getNamespaces() - { - $namespaces = array(); - foreach ($this->all() as $command) { - $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName())); - - foreach ($command->getAliases() as $alias) { - $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias)); - } - } - - return array_values(array_unique(array_filter($namespaces))); - } - - /** - * Finds a registered namespace by a name or an abbreviation. - * - * @param string $namespace A namespace or abbreviation to search for - * - * @return string A registered namespace - * - * @throws CommandNotFoundException When namespace is incorrect or ambiguous - */ - public function findNamespace($namespace) - { - $allNamespaces = $this->getNamespaces(); - $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $namespace); - $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces); - - if (empty($namespaces)) { - $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace); - - if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { - if (1 == count($alternatives)) { - $message .= "\n\nDid you mean this?\n "; - } else { - $message .= "\n\nDid you mean one of these?\n "; - } - - $message .= implode("\n ", $alternatives); - } - - throw new CommandNotFoundException($message, $alternatives); - } - - $exact = in_array($namespace, $namespaces, true); - if (count($namespaces) > 1 && !$exact) { - throw new CommandNotFoundException(sprintf("The namespace \"%s\" is ambiguous.\nDid you mean one of these?\n%s", $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces)); - } - - return $exact ? $namespace : reset($namespaces); - } - - /** - * Finds a command by name or alias. - * - * Contrary to get, this command tries to find the best - * match if you give it an abbreviation of a name or alias. - * - * @param string $name A command name or a command alias - * - * @return Command A Command instance - * - * @throws CommandNotFoundException When command name is incorrect or ambiguous - */ - public function find($name) - { - $this->init(); - - $allCommands = array_keys($this->commands); - $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name); - $commands = preg_grep('{^'.$expr.'}', $allCommands); - - if (empty($commands) || count(preg_grep('{^'.$expr.'$}', $commands)) < 1) { - if (false !== $pos = strrpos($name, ':')) { - // check if a namespace exists and contains commands - $this->findNamespace(substr($name, 0, $pos)); - } - - $message = sprintf('Command "%s" is not defined.', $name); - - if ($alternatives = $this->findAlternatives($name, $allCommands)) { - if (1 == count($alternatives)) { - $message .= "\n\nDid you mean this?\n "; - } else { - $message .= "\n\nDid you mean one of these?\n "; - } - $message .= implode("\n ", $alternatives); - } - - throw new CommandNotFoundException($message, $alternatives); - } - - // filter out aliases for commands which are already on the list - if (count($commands) > 1) { - $commandList = $this->commands; - $commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) { - $commandName = $commandList[$nameOrAlias]->getName(); - - return $commandName === $nameOrAlias || !in_array($commandName, $commands); - }); - } - - $exact = in_array($name, $commands, true); - if (count($commands) > 1 && !$exact) { - $usableWidth = $this->terminal->getWidth() - 10; - $abbrevs = array_values($commands); - $maxLen = 0; - foreach ($abbrevs as $abbrev) { - $maxLen = max(Helper::strlen($abbrev), $maxLen); - } - $abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen) { - $abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription(); - - return Helper::strlen($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev; - }, array_values($commands)); - $suggestions = $this->getAbbreviationSuggestions($abbrevs); - - throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s", $name, $suggestions), array_values($commands)); - } - - return $this->get($exact ? $name : reset($commands)); - } - - /** - * Gets the commands (registered in the given namespace if provided). - * - * The array keys are the full names and the values the command instances. - * - * @param string $namespace A namespace name - * - * @return Command[] An array of Command instances - */ - public function all($namespace = null) - { - $this->init(); - - if (null === $namespace) { - return $this->commands; - } - - $commands = array(); - foreach ($this->commands as $name => $command) { - if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) { - $commands[$name] = $command; - } - } - - return $commands; - } - - /** - * Returns an array of possible abbreviations given a set of names. - * - * @param array $names An array of names - * - * @return array An array of abbreviations - */ - public static function getAbbreviations($names) - { - $abbrevs = array(); - foreach ($names as $name) { - for ($len = strlen($name); $len > 0; --$len) { - $abbrev = substr($name, 0, $len); - $abbrevs[$abbrev][] = $name; - } - } - - return $abbrevs; - } - - /** - * Renders a caught exception. - */ - public function renderException(\Exception $e, OutputInterface $output) - { - $output->writeln('', OutputInterface::VERBOSITY_QUIET); - - do { - $title = sprintf( - ' [%s%s] ', - get_class($e), - $output->isVerbose() && 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : '' - ); - - $len = Helper::strlen($title); - - $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : PHP_INT_MAX; - // HHVM only accepts 32 bits integer in str_split, even when PHP_INT_MAX is a 64 bit integer: https://github.com/facebook/hhvm/issues/1327 - if (defined('HHVM_VERSION') && $width > 1 << 31) { - $width = 1 << 31; - } - $lines = array(); - foreach (preg_split('/\r?\n/', trim($e->getMessage())) as $line) { - foreach ($this->splitStringByWidth($line, $width - 4) as $line) { - // pre-format lines to get the right string length - $lineLength = Helper::strlen($line) + 4; - $lines[] = array($line, $lineLength); - - $len = max($lineLength, $len); - } - } - - $messages = array(); - $messages[] = $emptyLine = sprintf('%s', str_repeat(' ', $len)); - $messages[] = sprintf('%s%s', $title, str_repeat(' ', max(0, $len - Helper::strlen($title)))); - foreach ($lines as $line) { - $messages[] = sprintf(' %s %s', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1])); - } - $messages[] = $emptyLine; - $messages[] = ''; - - $output->writeln($messages, OutputInterface::VERBOSITY_QUIET); - - if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { - $output->writeln('Exception trace:', OutputInterface::VERBOSITY_QUIET); - - // exception related properties - $trace = $e->getTrace(); - array_unshift($trace, array( - 'function' => '', - 'file' => null !== $e->getFile() ? $e->getFile() : 'n/a', - 'line' => null !== $e->getLine() ? $e->getLine() : 'n/a', - 'args' => array(), - )); - - for ($i = 0, $count = count($trace); $i < $count; ++$i) { - $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; - $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; - $function = $trace[$i]['function']; - $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a'; - $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a'; - - $output->writeln(sprintf(' %s%s%s() at %s:%s', $class, $type, $function, $file, $line), OutputInterface::VERBOSITY_QUIET); - } - - $output->writeln('', OutputInterface::VERBOSITY_QUIET); - } - } while ($e = $e->getPrevious()); - - if (null !== $this->runningCommand) { - $output->writeln(sprintf('%s', sprintf($this->runningCommand->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET); - $output->writeln('', OutputInterface::VERBOSITY_QUIET); - } - } - - /** - * Tries to figure out the terminal width in which this application runs. - * - * @return int|null - * - * @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead. - */ - protected function getTerminalWidth() - { - @trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), E_USER_DEPRECATED); - - return $this->terminal->getWidth(); - } - - /** - * Tries to figure out the terminal height in which this application runs. - * - * @return int|null - * - * @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead. - */ - protected function getTerminalHeight() - { - @trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), E_USER_DEPRECATED); - - return $this->terminal->getHeight(); - } - - /** - * Tries to figure out the terminal dimensions based on the current environment. - * - * @return array Array containing width and height - * - * @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead. - */ - public function getTerminalDimensions() - { - @trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), E_USER_DEPRECATED); - - return array($this->terminal->getWidth(), $this->terminal->getHeight()); - } - - /** - * Sets terminal dimensions. - * - * Can be useful to force terminal dimensions for functional tests. - * - * @param int $width The width - * @param int $height The height - * - * @return $this - * - * @deprecated since version 3.2, to be removed in 4.0. Set the COLUMNS and LINES env vars instead. - */ - public function setTerminalDimensions($width, $height) - { - @trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Set the COLUMNS and LINES env vars instead.', __METHOD__), E_USER_DEPRECATED); - - putenv('COLUMNS='.$width); - putenv('LINES='.$height); - - return $this; - } - - /** - * Configures the input and output instances based on the user arguments and options. - */ - protected function configureIO(InputInterface $input, OutputInterface $output) - { - if (true === $input->hasParameterOption(array('--ansi'), true)) { - $output->setDecorated(true); - } elseif (true === $input->hasParameterOption(array('--no-ansi'), true)) { - $output->setDecorated(false); - } - - if (true === $input->hasParameterOption(array('--no-interaction', '-n'), true)) { - $input->setInteractive(false); - } elseif (function_exists('posix_isatty')) { - $inputStream = null; - - if ($input instanceof StreamableInputInterface) { - $inputStream = $input->getStream(); - } - - // This check ensures that calling QuestionHelper::setInputStream() works - // To be removed in 4.0 (in the same time as QuestionHelper::setInputStream) - if (!$inputStream && $this->getHelperSet()->has('question')) { - $inputStream = $this->getHelperSet()->get('question')->getInputStream(false); - } - - if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) { - $input->setInteractive(false); - } - } - - if (true === $input->hasParameterOption(array('--quiet', '-q'), true)) { - $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); - $input->setInteractive(false); - } else { - if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || 3 === $input->getParameterOption('--verbose', false, true)) { - $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); - } elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || 2 === $input->getParameterOption('--verbose', false, true)) { - $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); - } elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) { - $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); - } - } - } - - /** - * Runs the current command. - * - * If an event dispatcher has been attached to the application, - * events are also dispatched during the life-cycle of the command. - * - * @return int 0 if everything went fine, or an error code - */ - protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) - { - foreach ($command->getHelperSet() as $helper) { - if ($helper instanceof InputAwareInterface) { - $helper->setInput($input); - } - } - - if (null === $this->dispatcher) { - return $command->run($input, $output); - } - - // bind before the console.command event, so the listeners have access to input options/arguments - try { - $command->mergeApplicationDefinition(); - $input->bind($command->getDefinition()); - } catch (ExceptionInterface $e) { - // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition - } - - $event = new ConsoleCommandEvent($command, $input, $output); - $e = null; - - try { - $this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event); - - if ($event->commandShouldRun()) { - $exitCode = $command->run($input, $output); - } else { - $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED; - } - } catch (\Exception $e) { - } catch (\Throwable $e) { - } - if (null !== $e) { - if ($this->dispatcher->hasListeners(ConsoleEvents::EXCEPTION)) { - $x = $e instanceof \Exception ? $e : new FatalThrowableError($e); - $event = new ConsoleExceptionEvent($command, $input, $output, $x, $x->getCode()); - $this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event); - - if ($x !== $event->getException()) { - $e = $event->getException(); - } - } - $event = new ConsoleErrorEvent($input, $output, $e, $command); - $this->dispatcher->dispatch(ConsoleEvents::ERROR, $event); - $e = $event->getError(); - - if (0 === $exitCode = $event->getExitCode()) { - $e = null; - } - } - - $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode); - $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event); - - if (null !== $e) { - throw $e; - } - - return $event->getExitCode(); - } - - /** - * Gets the name of the command based on input. - * - * @return string The command name - */ - protected function getCommandName(InputInterface $input) - { - return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument(); - } - - /** - * Gets the default input definition. - * - * @return InputDefinition An InputDefinition instance - */ - protected function getDefaultInputDefinition() - { - return new InputDefinition(array( - new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), - - new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'), - new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'), - new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'), - new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'), - new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'), - new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'), - new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'), - )); - } - - /** - * Gets the default commands that should always be available. - * - * @return Command[] An array of default Command instances - */ - protected function getDefaultCommands() - { - return array(new HelpCommand(), new ListCommand()); - } - - /** - * Gets the default helper set with the helpers that should always be available. - * - * @return HelperSet A HelperSet instance - */ - protected function getDefaultHelperSet() - { - return new HelperSet(array( - new FormatterHelper(), - new DebugFormatterHelper(), - new ProcessHelper(), - new QuestionHelper(), - )); - } - - /** - * Returns abbreviated suggestions in string format. - * - * @param array $abbrevs Abbreviated suggestions to convert - * - * @return string A formatted string of abbreviated suggestions - */ - private function getAbbreviationSuggestions($abbrevs) - { - return ' '.implode("\n ", $abbrevs); - } - - /** - * Returns the namespace part of the command name. - * - * This method is not part of public API and should not be used directly. - * - * @param string $name The full name of the command - * @param string $limit The maximum number of parts of the namespace - * - * @return string The namespace of the command - */ - public function extractNamespace($name, $limit = null) - { - $parts = explode(':', $name); - array_pop($parts); - - return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit)); - } - - /** - * Finds alternative of $name among $collection, - * if nothing is found in $collection, try in $abbrevs. - * - * @param string $name The string - * @param array|\Traversable $collection The collection - * - * @return string[] A sorted array of similar string - */ - private function findAlternatives($name, $collection) - { - $threshold = 1e3; - $alternatives = array(); - - $collectionParts = array(); - foreach ($collection as $item) { - $collectionParts[$item] = explode(':', $item); - } - - foreach (explode(':', $name) as $i => $subname) { - foreach ($collectionParts as $collectionName => $parts) { - $exists = isset($alternatives[$collectionName]); - if (!isset($parts[$i]) && $exists) { - $alternatives[$collectionName] += $threshold; - continue; - } elseif (!isset($parts[$i])) { - continue; - } - - $lev = levenshtein($subname, $parts[$i]); - if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) { - $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev; - } elseif ($exists) { - $alternatives[$collectionName] += $threshold; - } - } - } - - foreach ($collection as $item) { - $lev = levenshtein($name, $item); - if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) { - $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; - } - } - - $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; }); - ksort($alternatives, SORT_NATURAL | SORT_FLAG_CASE); - - return array_keys($alternatives); - } - - /** - * Sets the default Command name. - * - * @param string $commandName The Command name - * @param bool $isSingleCommand Set to true if there is only one command in this application - * - * @return self - */ - public function setDefaultCommand($commandName, $isSingleCommand = false) - { - $this->defaultCommand = $commandName; - - if ($isSingleCommand) { - // Ensure the command exist - $this->find($commandName); - - $this->singleCommand = true; - } - - return $this; - } - - private function splitStringByWidth($string, $width) - { - // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly. - // additionally, array_slice() is not enough as some character has doubled width. - // we need a function to split string not by character count but by string width - if (false === $encoding = mb_detect_encoding($string, null, true)) { - return str_split($string, $width); - } - - $utf8String = mb_convert_encoding($string, 'utf8', $encoding); - $lines = array(); - $line = ''; - foreach (preg_split('//u', $utf8String) as $char) { - // test if $char could be appended to current line - if (mb_strwidth($line.$char, 'utf8') <= $width) { - $line .= $char; - continue; - } - // if not, push current line to array and make new line - $lines[] = str_pad($line, $width); - $line = $char; - } - - $lines[] = count($lines) ? str_pad($line, $width) : $line; - - mb_convert_variables($encoding, 'utf8', $lines); - - return $lines; - } - - /** - * Returns all namespaces of the command name. - * - * @param string $name The full name of the command - * - * @return string[] The namespaces of the command - */ - private function extractAllNamespaces($name) - { - // -1 as third argument is needed to skip the command short name when exploding - $parts = explode(':', $name, -1); - $namespaces = array(); - - foreach ($parts as $part) { - if (count($namespaces)) { - $namespaces[] = end($namespaces).':'.$part; - } else { - $namespaces[] = $part; - } - } - - return $namespaces; - } - - private function init() - { - if ($this->initialized) { - return; - } - $this->initialized = true; - - foreach ($this->getDefaultCommands() as $command) { - $this->add($command); - } - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Command; - -use Symfony\Component\Console\Exception\ExceptionInterface; -use Symfony\Component\Console\Input\InputDefinition; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Application; -use Symfony\Component\Console\Helper\HelperSet; -use Symfony\Component\Console\Exception\InvalidArgumentException; -use Symfony\Component\Console\Exception\LogicException; - -/** - * Base class for all commands. - * - * @author Fabien Potencier - */ -class Command -{ - private $application; - private $name; - private $processTitle; - private $aliases = array(); - private $definition; - private $hidden = false; - private $help; - private $description; - private $ignoreValidationErrors = false; - private $applicationDefinitionMerged = false; - private $applicationDefinitionMergedWithArgs = false; - private $code; - private $synopsis = array(); - private $usages = array(); - private $helperSet; - - /** - * @param string|null $name The name of the command; passing null means it must be set in configure() - * - * @throws LogicException When the command name is empty - */ - public function __construct($name = null) - { - $this->definition = new InputDefinition(); - - if (null !== $name) { - $this->setName($name); - } - - $this->configure(); - - if (!$this->name) { - throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this))); - } - } - - /** - * Ignores validation errors. - * - * This is mainly useful for the help command. - */ - public function ignoreValidationErrors() - { - $this->ignoreValidationErrors = true; - } - - public function setApplication(Application $application = null) - { - $this->application = $application; - if ($application) { - $this->setHelperSet($application->getHelperSet()); - } else { - $this->helperSet = null; - } - } - - public function setHelperSet(HelperSet $helperSet) - { - $this->helperSet = $helperSet; - } - - /** - * Gets the helper set. - * - * @return HelperSet A HelperSet instance - */ - public function getHelperSet() - { - return $this->helperSet; - } - - /** - * Gets the application instance for this command. - * - * @return Application An Application instance - */ - public function getApplication() - { - return $this->application; - } - - /** - * Checks whether the command is enabled or not in the current environment. - * - * Override this to check for x or y and return false if the command can not - * run properly under the current conditions. - * - * @return bool - */ - public function isEnabled() - { - return true; - } - - /** - * Configures the current command. - */ - protected function configure() - { - } - - /** - * Executes the current command. - * - * This method is not abstract because you can use this class - * as a concrete class. In this case, instead of defining the - * execute() method, you set the code to execute by passing - * a Closure to the setCode() method. - * - * @return null|int null or 0 if everything went fine, or an error code - * - * @throws LogicException When this abstract method is not implemented - * - * @see setCode() - */ - protected function execute(InputInterface $input, OutputInterface $output) - { - throw new LogicException('You must override the execute() method in the concrete command class.'); - } - - /** - * Interacts with the user. - * - * This method is executed before the InputDefinition is validated. - * This means that this is the only place where the command can - * interactively ask for values of missing required arguments. - */ - protected function interact(InputInterface $input, OutputInterface $output) - { - } - - /** - * Initializes the command just after the input has been validated. - * - * This is mainly useful when a lot of commands extends one main command - * where some things need to be initialized based on the input arguments and options. - */ - protected function initialize(InputInterface $input, OutputInterface $output) - { - } - - /** - * Runs the command. - * - * The code to execute is either defined directly with the - * setCode() method or by overriding the execute() method - * in a sub-class. - * - * @return int The command exit code - * - * @throws \Exception When binding input fails. Bypass this by calling {@link ignoreValidationErrors()}. - * - * @see setCode() - * @see execute() - */ - public function run(InputInterface $input, OutputInterface $output) - { - // force the creation of the synopsis before the merge with the app definition - $this->getSynopsis(true); - $this->getSynopsis(false); - - // add the application arguments and options - $this->mergeApplicationDefinition(); - - // bind the input against the command specific arguments/options - try { - $input->bind($this->definition); - } catch (ExceptionInterface $e) { - if (!$this->ignoreValidationErrors) { - throw $e; - } - } - - $this->initialize($input, $output); - - if (null !== $this->processTitle) { - if (function_exists('cli_set_process_title')) { - if (false === @cli_set_process_title($this->processTitle)) { - if ('Darwin' === PHP_OS) { - $output->writeln('Running "cli_get_process_title" as an unprivileged user is not supported on MacOS.'); - } else { - $error = error_get_last(); - trigger_error($error['message'], E_USER_WARNING); - } - } - } elseif (function_exists('setproctitle')) { - setproctitle($this->processTitle); - } elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) { - $output->writeln('Install the proctitle PECL to be able to change the process title.'); - } - } - - if ($input->isInteractive()) { - $this->interact($input, $output); - } - - // The command name argument is often omitted when a command is executed directly with its run() method. - // It would fail the validation if we didn't make sure the command argument is present, - // since it's required by the application. - if ($input->hasArgument('command') && null === $input->getArgument('command')) { - $input->setArgument('command', $this->getName()); - } - - $input->validate(); - - if ($this->code) { - $statusCode = call_user_func($this->code, $input, $output); - } else { - $statusCode = $this->execute($input, $output); - } - - return is_numeric($statusCode) ? (int) $statusCode : 0; - } - - /** - * Sets the code to execute when running this command. - * - * If this method is used, it overrides the code defined - * in the execute() method. - * - * @param callable $code A callable(InputInterface $input, OutputInterface $output) - * - * @return $this - * - * @throws InvalidArgumentException - * - * @see execute() - */ - public function setCode(callable $code) - { - if ($code instanceof \Closure) { - $r = new \ReflectionFunction($code); - if (null === $r->getClosureThis()) { - if (\PHP_VERSION_ID < 70000) { - // Bug in PHP5: https://bugs.php.net/bug.php?id=64761 - // This means that we cannot bind static closures and therefore we must - // ignore any errors here. There is no way to test if the closure is - // bindable. - $code = @\Closure::bind($code, $this); - } else { - $code = \Closure::bind($code, $this); - } - } - } - - $this->code = $code; - - return $this; - } - - /** - * Merges the application definition with the command definition. - * - * This method is not part of public API and should not be used directly. - * - * @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments - */ - public function mergeApplicationDefinition($mergeArgs = true) - { - if (null === $this->application || (true === $this->applicationDefinitionMerged && ($this->applicationDefinitionMergedWithArgs || !$mergeArgs))) { - return; - } - - $this->definition->addOptions($this->application->getDefinition()->getOptions()); - - if ($mergeArgs) { - $currentArguments = $this->definition->getArguments(); - $this->definition->setArguments($this->application->getDefinition()->getArguments()); - $this->definition->addArguments($currentArguments); - } - - $this->applicationDefinitionMerged = true; - if ($mergeArgs) { - $this->applicationDefinitionMergedWithArgs = true; - } - } - - /** - * Sets an array of argument and option instances. - * - * @param array|InputDefinition $definition An array of argument and option instances or a definition instance - * - * @return $this - */ - public function setDefinition($definition) - { - if ($definition instanceof InputDefinition) { - $this->definition = $definition; - } else { - $this->definition->setDefinition($definition); - } - - $this->applicationDefinitionMerged = false; - - return $this; - } - - /** - * Gets the InputDefinition attached to this Command. - * - * @return InputDefinition An InputDefinition instance - */ - public function getDefinition() - { - return $this->definition; - } - - /** - * Gets the InputDefinition to be used to create representations of this Command. - * - * Can be overridden to provide the original command representation when it would otherwise - * be changed by merging with the application InputDefinition. - * - * This method is not part of public API and should not be used directly. - * - * @return InputDefinition An InputDefinition instance - */ - public function getNativeDefinition() - { - return $this->getDefinition(); - } - - /** - * Adds an argument. - * - * @param string $name The argument name - * @param int $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL - * @param string $description A description text - * @param mixed $default The default value (for InputArgument::OPTIONAL mode only) - * - * @return $this - */ - public function addArgument($name, $mode = null, $description = '', $default = null) - { - $this->definition->addArgument(new InputArgument($name, $mode, $description, $default)); - - return $this; - } - - /** - * Adds an option. - * - * @param string $name The option name - * @param string $shortcut The shortcut (can be null) - * @param int $mode The option mode: One of the InputOption::VALUE_* constants - * @param string $description A description text - * @param mixed $default The default value (must be null for InputOption::VALUE_NONE) - * - * @return $this - */ - public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null) - { - $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); - - return $this; - } - - /** - * Sets the name of the command. - * - * This method can set both the namespace and the name if - * you separate them by a colon (:) - * - * $command->setName('foo:bar'); - * - * @param string $name The command name - * - * @return $this - * - * @throws InvalidArgumentException When the name is invalid - */ - public function setName($name) - { - $this->validateName($name); - - $this->name = $name; - - return $this; - } - - /** - * Sets the process title of the command. - * - * This feature should be used only when creating a long process command, - * like a daemon. - * - * PHP 5.5+ or the proctitle PECL library is required - * - * @param string $title The process title - * - * @return $this - */ - public function setProcessTitle($title) - { - $this->processTitle = $title; - - return $this; - } - - /** - * Returns the command name. - * - * @return string The command name - */ - public function getName() - { - return $this->name; - } - - /** - * @param bool $hidden Whether or not the command should be hidden from the list of commands - * - * @return Command The current instance - */ - public function setHidden($hidden) - { - $this->hidden = (bool) $hidden; - - return $this; - } - - /** - * @return bool whether the command should be publicly shown or not - */ - public function isHidden() - { - return $this->hidden; - } - - /** - * Sets the description for the command. - * - * @param string $description The description for the command - * - * @return $this - */ - public function setDescription($description) - { - $this->description = $description; - - return $this; - } - - /** - * Returns the description for the command. - * - * @return string The description for the command - */ - public function getDescription() - { - return $this->description; - } - - /** - * Sets the help for the command. - * - * @param string $help The help for the command - * - * @return $this - */ - public function setHelp($help) - { - $this->help = $help; - - return $this; - } - - /** - * Returns the help for the command. - * - * @return string The help for the command - */ - public function getHelp() - { - return $this->help; - } - - /** - * Returns the processed help for the command replacing the %command.name% and - * %command.full_name% patterns with the real values dynamically. - * - * @return string The processed help for the command - */ - public function getProcessedHelp() - { - $name = $this->name; - - $placeholders = array( - '%command.name%', - '%command.full_name%', - ); - $replacements = array( - $name, - $_SERVER['PHP_SELF'].' '.$name, - ); - - return str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription()); - } - - /** - * Sets the aliases for the command. - * - * @param string[] $aliases An array of aliases for the command - * - * @return $this - * - * @throws InvalidArgumentException When an alias is invalid - */ - public function setAliases($aliases) - { - if (!is_array($aliases) && !$aliases instanceof \Traversable) { - throw new InvalidArgumentException('$aliases must be an array or an instance of \Traversable'); - } - - foreach ($aliases as $alias) { - $this->validateName($alias); - } - - $this->aliases = $aliases; - - return $this; - } - - /** - * Returns the aliases for the command. - * - * @return array An array of aliases for the command - */ - public function getAliases() - { - return $this->aliases; - } - - /** - * Returns the synopsis for the command. - * - * @param bool $short Whether to show the short version of the synopsis (with options folded) or not - * - * @return string The synopsis - */ - public function getSynopsis($short = false) - { - $key = $short ? 'short' : 'long'; - - if (!isset($this->synopsis[$key])) { - $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short))); - } - - return $this->synopsis[$key]; - } - - /** - * Add a command usage example. - * - * @param string $usage The usage, it'll be prefixed with the command name - * - * @return $this - */ - public function addUsage($usage) - { - if (0 !== strpos($usage, $this->name)) { - $usage = sprintf('%s %s', $this->name, $usage); - } - - $this->usages[] = $usage; - - return $this; - } - - /** - * Returns alternative usages of the command. - * - * @return array - */ - public function getUsages() - { - return $this->usages; - } - - /** - * Gets a helper instance by name. - * - * @param string $name The helper name - * - * @return mixed The helper value - * - * @throws LogicException if no HelperSet is defined - * @throws InvalidArgumentException if the helper is not defined - */ - public function getHelper($name) - { - if (null === $this->helperSet) { - throw new LogicException(sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name)); - } - - return $this->helperSet->get($name); - } - - /** - * Validates a command name. - * - * It must be non-empty and parts can optionally be separated by ":". - * - * @param string $name - * - * @throws InvalidArgumentException When the name is invalid - */ - private function validateName($name) - { - if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { - throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); - } - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Command; - -use Symfony\Component\Console\Helper\DescriptorHelper; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; - -/** - * HelpCommand displays the help for a given command. - * - * @author Fabien Potencier - */ -class HelpCommand extends Command -{ - private $command; - - /** - * {@inheritdoc} - */ - protected function configure() - { - $this->ignoreValidationErrors(); - - $this - ->setName('help') - ->setDefinition(array( - new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), - new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), - new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), - )) - ->setDescription('Displays help for a command') - ->setHelp(<<<'EOF' -The %command.name% command displays help for a given command: - - php %command.full_name% list - -You can also output the help in other formats by using the --format option: - - php %command.full_name% --format=xml list - -To display the list of available commands, please use the list command. -EOF - ) - ; - } - - public function setCommand(Command $command) - { - $this->command = $command; - } - - /** - * {@inheritdoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) - { - if (null === $this->command) { - $this->command = $this->getApplication()->find($input->getArgument('command_name')); - } - - $helper = new DescriptorHelper(); - $helper->describe($output, $this->command, array( - 'format' => $input->getOption('format'), - 'raw_text' => $input->getOption('raw'), - )); - - $this->command = null; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Command; - -use Symfony\Component\Console\Helper\DescriptorHelper; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Input\InputDefinition; - -/** - * ListCommand displays the list of all available commands for the application. - * - * @author Fabien Potencier - */ -class ListCommand extends Command -{ - /** - * {@inheritdoc} - */ - protected function configure() - { - $this - ->setName('list') - ->setDefinition($this->createDefinition()) - ->setDescription('Lists commands') - ->setHelp(<<<'EOF' -The %command.name% command lists all commands: - - php %command.full_name% - -You can also display the commands for a specific namespace: - - php %command.full_name% test - -You can also output the information in other formats by using the --format option: - - php %command.full_name% --format=xml - -It's also possible to get raw list of commands (useful for embedding command runner): - - php %command.full_name% --raw -EOF - ) - ; - } - - /** - * {@inheritdoc} - */ - public function getNativeDefinition() - { - return $this->createDefinition(); - } - - /** - * {@inheritdoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) - { - $helper = new DescriptorHelper(); - $helper->describe($output, $this->getApplication(), array( - 'format' => $input->getOption('format'), - 'raw_text' => $input->getOption('raw'), - 'namespace' => $input->getArgument('namespace'), - )); - } - - /** - * {@inheritdoc} - */ - private function createDefinition() - { - return new InputDefinition(array( - new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), - new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'), - new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), - )); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Command; - -use Symfony\Component\Console\Exception\LogicException; -use Symfony\Component\Console\Exception\RuntimeException; -use Symfony\Component\Filesystem\LockHandler; - -/** - * Basic lock feature for commands. - * - * @author Geoffrey Brier - */ -trait LockableTrait -{ - private $lockHandler; - - /** - * Locks a command. - * - * @return bool - */ - private function lock($name = null, $blocking = false) - { - if (!class_exists(LockHandler::class)) { - throw new RuntimeException('To enable the locking feature you must install the symfony/filesystem component.'); - } - - if (null !== $this->lockHandler) { - throw new LogicException('A lock is already in place.'); - } - - $this->lockHandler = new LockHandler($name ?: $this->getName()); - - if (!$this->lockHandler->lock($blocking)) { - $this->lockHandler = null; - - return false; - } - - return true; - } - - /** - * Releases the command lock if there is one. - */ - private function release() - { - if ($this->lockHandler) { - $this->lockHandler->release(); - $this->lockHandler = null; - } - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console; - -/** - * Contains all events dispatched by an Application. - * - * @author Francesco Levorato - */ -final class ConsoleEvents -{ - /** - * The COMMAND event allows you to attach listeners before any command is - * executed by the console. It also allows you to modify the command, input and output - * before they are handled to the command. - * - * @Event("Symfony\Component\Console\Event\ConsoleCommandEvent") - */ - const COMMAND = 'console.command'; - - /** - * The TERMINATE event allows you to attach listeners after a command is - * executed by the console. - * - * @Event("Symfony\Component\Console\Event\ConsoleTerminateEvent") - */ - const TERMINATE = 'console.terminate'; - - /** - * The EXCEPTION event occurs when an uncaught exception appears - * while executing Command#run(). - * - * This event allows you to deal with the exception or - * to modify the thrown exception. - * - * @Event("Symfony\Component\Console\Event\ConsoleExceptionEvent") - * - * @deprecated The console.exception event is deprecated since version 3.3 and will be removed in 4.0. Use the console.error event instead. - */ - const EXCEPTION = 'console.exception'; - - /** - * The ERROR event occurs when an uncaught exception or error appears. - * - * This event allows you to deal with the exception/error or - * to modify the thrown exception. - * - * @Event("Symfony\Component\Console\Event\ConsoleErrorEvent") - */ - const ERROR = 'console.error'; -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\DependencyInjection; - -use Symfony\Component\Console\Command\Command; -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; - -/** - * Registers console commands. - * - * @author Grégoire Pineau - */ -class AddConsoleCommandPass implements CompilerPassInterface -{ - public function process(ContainerBuilder $container) - { - $commandServices = $container->findTaggedServiceIds('console.command', true); - $serviceIds = array(); - - foreach ($commandServices as $id => $tags) { - $definition = $container->getDefinition($id); - $class = $container->getParameterBag()->resolveValue($definition->getClass()); - - if (!$r = $container->getReflectionClass($class)) { - throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); - } - if (!$r->isSubclassOf(Command::class)) { - throw new InvalidArgumentException(sprintf('The service "%s" tagged "console.command" must be a subclass of "%s".', $id, Command::class)); - } - - $commandId = 'console.command.'.strtolower(str_replace('\\', '_', $class)); - if ($container->hasAlias($commandId) || isset($serviceIds[$commandId])) { - $commandId = $commandId.'_'.$id; - } - if (!$definition->isPublic()) { - $container->setAlias($commandId, $id); - $id = $commandId; - } - - $serviceIds[$commandId] = $id; - } - - $container->setParameter('console.command.ids', $serviceIds); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Descriptor; - -use Symfony\Component\Console\Application; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Exception\CommandNotFoundException; - -/** - * @author Jean-François Simon - * - * @internal - */ -class ApplicationDescription -{ - const GLOBAL_NAMESPACE = '_global'; - - private $application; - private $namespace; - private $showHidden; - - /** - * @var array - */ - private $namespaces; - - /** - * @var Command[] - */ - private $commands; - - /** - * @var Command[] - */ - private $aliases; - - /** - * @param Application $application - * @param string|null $namespace - * @param bool $showHidden - */ - public function __construct(Application $application, $namespace = null, $showHidden = false) - { - $this->application = $application; - $this->namespace = $namespace; - $this->showHidden = $showHidden; - } - - /** - * @return array - */ - public function getNamespaces() - { - if (null === $this->namespaces) { - $this->inspectApplication(); - } - - return $this->namespaces; - } - - /** - * @return Command[] - */ - public function getCommands() - { - if (null === $this->commands) { - $this->inspectApplication(); - } - - return $this->commands; - } - - /** - * @param string $name - * - * @return Command - * - * @throws CommandNotFoundException - */ - public function getCommand($name) - { - if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { - throw new CommandNotFoundException(sprintf('Command %s does not exist.', $name)); - } - - return isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name]; - } - - private function inspectApplication() - { - $this->commands = array(); - $this->namespaces = array(); - - $all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null); - foreach ($this->sortCommands($all) as $namespace => $commands) { - $names = array(); - - /** @var Command $command */ - foreach ($commands as $name => $command) { - if (!$command->getName() || (!$this->showHidden && $command->isHidden())) { - continue; - } - - if ($command->getName() === $name) { - $this->commands[$name] = $command; - } else { - $this->aliases[$name] = $command; - } - - $names[] = $name; - } - - $this->namespaces[$namespace] = array('id' => $namespace, 'commands' => $names); - } - } - - /** - * @return array - */ - private function sortCommands(array $commands) - { - $namespacedCommands = array(); - $globalCommands = array(); - foreach ($commands as $name => $command) { - $key = $this->application->extractNamespace($name, 1); - if (!$key) { - $globalCommands['_global'][$name] = $command; - } else { - $namespacedCommands[$key][$name] = $command; - } - } - ksort($namespacedCommands); - $namespacedCommands = array_merge($globalCommands, $namespacedCommands); - - foreach ($namespacedCommands as &$commandsSet) { - ksort($commandsSet); - } - // unset reference to keep scope clear - unset($commandsSet); - - return $namespacedCommands; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Descriptor; - -use Symfony\Component\Console\Application; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputDefinition; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Exception\InvalidArgumentException; - -/** - * @author Jean-François Simon - * - * @internal - */ -abstract class Descriptor implements DescriptorInterface -{ - /** - * @var OutputInterface - */ - protected $output; - - /** - * {@inheritdoc} - */ - public function describe(OutputInterface $output, $object, array $options = array()) - { - $this->output = $output; - - switch (true) { - case $object instanceof InputArgument: - $this->describeInputArgument($object, $options); - break; - case $object instanceof InputOption: - $this->describeInputOption($object, $options); - break; - case $object instanceof InputDefinition: - $this->describeInputDefinition($object, $options); - break; - case $object instanceof Command: - $this->describeCommand($object, $options); - break; - case $object instanceof Application: - $this->describeApplication($object, $options); - break; - default: - throw new InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object))); - } - } - - /** - * Writes content to output. - * - * @param string $content - * @param bool $decorated - */ - protected function write($content, $decorated = false) - { - $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); - } - - /** - * Describes an InputArgument instance. - * - * @return string|mixed - */ - abstract protected function describeInputArgument(InputArgument $argument, array $options = array()); - - /** - * Describes an InputOption instance. - * - * @return string|mixed - */ - abstract protected function describeInputOption(InputOption $option, array $options = array()); - - /** - * Describes an InputDefinition instance. - * - * @return string|mixed - */ - abstract protected function describeInputDefinition(InputDefinition $definition, array $options = array()); - - /** - * Describes a Command instance. - * - * @return string|mixed - */ - abstract protected function describeCommand(Command $command, array $options = array()); - - /** - * Describes an Application instance. - * - * @return string|mixed - */ - abstract protected function describeApplication(Application $application, array $options = array()); -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Descriptor; - -use Symfony\Component\Console\Output\OutputInterface; - -/** - * Descriptor interface. - * - * @author Jean-François Simon - */ -interface DescriptorInterface -{ - /** - * Describes an InputArgument instance. - * - * @param OutputInterface $output - * @param object $object - * @param array $options - */ - public function describe(OutputInterface $output, $object, array $options = array()); -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Descriptor; - -use Symfony\Component\Console\Application; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputDefinition; -use Symfony\Component\Console\Input\InputOption; - -/** - * JSON descriptor. - * - * @author Jean-François Simon - * - * @internal - */ -class JsonDescriptor extends Descriptor -{ - /** - * {@inheritdoc} - */ - protected function describeInputArgument(InputArgument $argument, array $options = array()) - { - $this->writeData($this->getInputArgumentData($argument), $options); - } - - /** - * {@inheritdoc} - */ - protected function describeInputOption(InputOption $option, array $options = array()) - { - $this->writeData($this->getInputOptionData($option), $options); - } - - /** - * {@inheritdoc} - */ - protected function describeInputDefinition(InputDefinition $definition, array $options = array()) - { - $this->writeData($this->getInputDefinitionData($definition), $options); - } - - /** - * {@inheritdoc} - */ - protected function describeCommand(Command $command, array $options = array()) - { - $this->writeData($this->getCommandData($command), $options); - } - - /** - * {@inheritdoc} - */ - protected function describeApplication(Application $application, array $options = array()) - { - $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; - $description = new ApplicationDescription($application, $describedNamespace, true); - $commands = array(); - - foreach ($description->getCommands() as $command) { - $commands[] = $this->getCommandData($command); - } - - $data = array(); - if ('UNKNOWN' !== $application->getName()) { - $data['application']['name'] = $application->getName(); - if ('UNKNOWN' !== $application->getVersion()) { - $data['application']['version'] = $application->getVersion(); - } - } - - $data['commands'] = $commands; - - if ($describedNamespace) { - $data['namespace'] = $describedNamespace; - } else { - $data['namespaces'] = array_values($description->getNamespaces()); - } - - $this->writeData($data, $options); - } - - /** - * Writes data as json. - * - * @return array|string - */ - private function writeData(array $data, array $options) - { - $this->write(json_encode($data, isset($options['json_encoding']) ? $options['json_encoding'] : 0)); - } - - /** - * @return array - */ - private function getInputArgumentData(InputArgument $argument) - { - return array( - 'name' => $argument->getName(), - 'is_required' => $argument->isRequired(), - 'is_array' => $argument->isArray(), - 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()), - 'default' => INF === $argument->getDefault() ? 'INF' : $argument->getDefault(), - ); - } - - /** - * @return array - */ - private function getInputOptionData(InputOption $option) - { - return array( - 'name' => '--'.$option->getName(), - 'shortcut' => $option->getShortcut() ? '-'.implode('|-', explode('|', $option->getShortcut())) : '', - 'accept_value' => $option->acceptValue(), - 'is_value_required' => $option->isValueRequired(), - 'is_multiple' => $option->isArray(), - 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()), - 'default' => INF === $option->getDefault() ? 'INF' : $option->getDefault(), - ); - } - - /** - * @return array - */ - private function getInputDefinitionData(InputDefinition $definition) - { - $inputArguments = array(); - foreach ($definition->getArguments() as $name => $argument) { - $inputArguments[$name] = $this->getInputArgumentData($argument); - } - - $inputOptions = array(); - foreach ($definition->getOptions() as $name => $option) { - $inputOptions[$name] = $this->getInputOptionData($option); - } - - return array('arguments' => $inputArguments, 'options' => $inputOptions); - } - - /** - * @return array - */ - private function getCommandData(Command $command) - { - $command->getSynopsis(); - $command->mergeApplicationDefinition(false); - - return array( - 'name' => $command->getName(), - 'usage' => array_merge(array($command->getSynopsis()), $command->getUsages(), $command->getAliases()), - 'description' => $command->getDescription(), - 'help' => $command->getProcessedHelp(), - 'definition' => $this->getInputDefinitionData($command->getNativeDefinition()), - 'hidden' => $command->isHidden(), - ); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Descriptor; - -use Symfony\Component\Console\Application; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Helper\Helper; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputDefinition; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Output\OutputInterface; - -/** - * Markdown descriptor. - * - * @author Jean-François Simon - * - * @internal - */ -class MarkdownDescriptor extends Descriptor -{ - /** - * {@inheritdoc} - */ - public function describe(OutputInterface $output, $object, array $options = array()) - { - $decorated = $output->isDecorated(); - $output->setDecorated(false); - - parent::describe($output, $object, $options); - - $output->setDecorated($decorated); - } - - /** - * {@inheritdoc} - */ - protected function write($content, $decorated = true) - { - parent::write($content, $decorated); - } - - /** - * {@inheritdoc} - */ - protected function describeInputArgument(InputArgument $argument, array $options = array()) - { - $this->write( - '#### `'.($argument->getName() ?: '')."`\n\n" - .($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '') - .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n" - .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n" - .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`' - ); - } - - /** - * {@inheritdoc} - */ - protected function describeInputOption(InputOption $option, array $options = array()) - { - $name = '--'.$option->getName(); - if ($option->getShortcut()) { - $name .= '|-'.implode('|-', explode('|', $option->getShortcut())).''; - } - - $this->write( - '#### `'.$name.'`'."\n\n" - .($option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $option->getDescription())."\n\n" : '') - .'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n" - .'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n" - .'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n" - .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`' - ); - } - - /** - * {@inheritdoc} - */ - protected function describeInputDefinition(InputDefinition $definition, array $options = array()) - { - if ($showArguments = count($definition->getArguments()) > 0) { - $this->write('### Arguments'); - foreach ($definition->getArguments() as $argument) { - $this->write("\n\n"); - $this->write($this->describeInputArgument($argument)); - } - } - - if (count($definition->getOptions()) > 0) { - if ($showArguments) { - $this->write("\n\n"); - } - - $this->write('### Options'); - foreach ($definition->getOptions() as $option) { - $this->write("\n\n"); - $this->write($this->describeInputOption($option)); - } - } - } - - /** - * {@inheritdoc} - */ - protected function describeCommand(Command $command, array $options = array()) - { - $command->getSynopsis(); - $command->mergeApplicationDefinition(false); - - $this->write( - '`'.$command->getName()."`\n" - .str_repeat('-', Helper::strlen($command->getName()) + 2)."\n\n" - .($command->getDescription() ? $command->getDescription()."\n\n" : '') - .'### Usage'."\n\n" - .array_reduce(array_merge(array($command->getSynopsis()), $command->getAliases(), $command->getUsages()), function ($carry, $usage) { - return $carry.'* `'.$usage.'`'."\n"; - }) - ); - - if ($help = $command->getProcessedHelp()) { - $this->write("\n"); - $this->write($help); - } - - if ($command->getNativeDefinition()) { - $this->write("\n\n"); - $this->describeInputDefinition($command->getNativeDefinition()); - } - } - - /** - * {@inheritdoc} - */ - protected function describeApplication(Application $application, array $options = array()) - { - $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; - $description = new ApplicationDescription($application, $describedNamespace); - $title = $this->getApplicationTitle($application); - - $this->write($title."\n".str_repeat('=', Helper::strlen($title))); - - foreach ($description->getNamespaces() as $namespace) { - if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { - $this->write("\n\n"); - $this->write('**'.$namespace['id'].':**'); - } - - $this->write("\n\n"); - $this->write(implode("\n", array_map(function ($commandName) use ($description) { - return sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName())); - }, $namespace['commands']))); - } - - foreach ($description->getCommands() as $command) { - $this->write("\n\n"); - $this->write($this->describeCommand($command)); - } - } - - private function getApplicationTitle(Application $application) - { - if ('UNKNOWN' !== $application->getName()) { - if ('UNKNOWN' !== $application->getVersion()) { - return sprintf('%s %s', $application->getName(), $application->getVersion()); - } - - return $application->getName(); - } - - return 'Console Tool'; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Descriptor; - -use Symfony\Component\Console\Application; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Formatter\OutputFormatter; -use Symfony\Component\Console\Helper\Helper; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputDefinition; -use Symfony\Component\Console\Input\InputOption; - -/** - * Text descriptor. - * - * @author Jean-François Simon - * - * @internal - */ -class TextDescriptor extends Descriptor -{ - /** - * {@inheritdoc} - */ - protected function describeInputArgument(InputArgument $argument, array $options = array()) - { - if (null !== $argument->getDefault() && (!is_array($argument->getDefault()) || count($argument->getDefault()))) { - $default = sprintf(' [default: %s]', $this->formatDefaultValue($argument->getDefault())); - } else { - $default = ''; - } - - $totalWidth = isset($options['total_width']) ? $options['total_width'] : Helper::strlen($argument->getName()); - $spacingWidth = $totalWidth - strlen($argument->getName()); - - $this->writeText(sprintf(' %s %s%s%s', - $argument->getName(), - str_repeat(' ', $spacingWidth), - // + 4 = 2 spaces before , 2 spaces after - preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()), - $default - ), $options); - } - - /** - * {@inheritdoc} - */ - protected function describeInputOption(InputOption $option, array $options = array()) - { - if ($option->acceptValue() && null !== $option->getDefault() && (!is_array($option->getDefault()) || count($option->getDefault()))) { - $default = sprintf(' [default: %s]', $this->formatDefaultValue($option->getDefault())); - } else { - $default = ''; - } - - $value = ''; - if ($option->acceptValue()) { - $value = '='.strtoupper($option->getName()); - - if ($option->isValueOptional()) { - $value = '['.$value.']'; - } - } - - $totalWidth = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions(array($option)); - $synopsis = sprintf('%s%s', - $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', - sprintf('--%s%s', $option->getName(), $value) - ); - - $spacingWidth = $totalWidth - Helper::strlen($synopsis); - - $this->writeText(sprintf(' %s %s%s%s%s', - $synopsis, - str_repeat(' ', $spacingWidth), - // + 4 = 2 spaces before , 2 spaces after - preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()), - $default, - $option->isArray() ? ' (multiple values allowed)' : '' - ), $options); - } - - /** - * {@inheritdoc} - */ - protected function describeInputDefinition(InputDefinition $definition, array $options = array()) - { - $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); - foreach ($definition->getArguments() as $argument) { - $totalWidth = max($totalWidth, Helper::strlen($argument->getName())); - } - - if ($definition->getArguments()) { - $this->writeText('Arguments:', $options); - $this->writeText("\n"); - foreach ($definition->getArguments() as $argument) { - $this->describeInputArgument($argument, array_merge($options, array('total_width' => $totalWidth))); - $this->writeText("\n"); - } - } - - if ($definition->getArguments() && $definition->getOptions()) { - $this->writeText("\n"); - } - - if ($definition->getOptions()) { - $laterOptions = array(); - - $this->writeText('Options:', $options); - foreach ($definition->getOptions() as $option) { - if (strlen($option->getShortcut()) > 1) { - $laterOptions[] = $option; - continue; - } - $this->writeText("\n"); - $this->describeInputOption($option, array_merge($options, array('total_width' => $totalWidth))); - } - foreach ($laterOptions as $option) { - $this->writeText("\n"); - $this->describeInputOption($option, array_merge($options, array('total_width' => $totalWidth))); - } - } - } - - /** - * {@inheritdoc} - */ - protected function describeCommand(Command $command, array $options = array()) - { - $command->getSynopsis(true); - $command->getSynopsis(false); - $command->mergeApplicationDefinition(false); - - $this->writeText('Usage:', $options); - foreach (array_merge(array($command->getSynopsis(true)), $command->getAliases(), $command->getUsages()) as $usage) { - $this->writeText("\n"); - $this->writeText(' '.OutputFormatter::escape($usage), $options); - } - $this->writeText("\n"); - - $definition = $command->getNativeDefinition(); - if ($definition->getOptions() || $definition->getArguments()) { - $this->writeText("\n"); - $this->describeInputDefinition($definition, $options); - $this->writeText("\n"); - } - - if ($help = $command->getProcessedHelp()) { - $this->writeText("\n"); - $this->writeText('Help:', $options); - $this->writeText("\n"); - $this->writeText(' '.str_replace("\n", "\n ", $help), $options); - $this->writeText("\n"); - } - } - - /** - * {@inheritdoc} - */ - protected function describeApplication(Application $application, array $options = array()) - { - $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; - $description = new ApplicationDescription($application, $describedNamespace); - - if (isset($options['raw_text']) && $options['raw_text']) { - $width = $this->getColumnWidth($description->getCommands()); - - foreach ($description->getCommands() as $command) { - $this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options); - $this->writeText("\n"); - } - } else { - if ('' != $help = $application->getHelp()) { - $this->writeText("$help\n\n", $options); - } - - $this->writeText("Usage:\n", $options); - $this->writeText(" command [options] [arguments]\n\n", $options); - - $this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options); - - $this->writeText("\n"); - $this->writeText("\n"); - - $commands = $description->getCommands(); - $namespaces = $description->getNamespaces(); - if ($describedNamespace && $namespaces) { - // make sure all alias commands are included when describing a specific namespace - $describedNamespaceInfo = reset($namespaces); - foreach ($describedNamespaceInfo['commands'] as $name) { - $commands[$name] = $description->getCommand($name); - } - } - - // calculate max. width based on available commands per namespace - $width = $this->getColumnWidth(call_user_func_array('array_merge', array_map(function ($namespace) use ($commands) { - return array_intersect($namespace['commands'], array_keys($commands)); - }, $namespaces))); - - if ($describedNamespace) { - $this->writeText(sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options); - } else { - $this->writeText('Available commands:', $options); - } - - foreach ($namespaces as $namespace) { - $namespace['commands'] = array_filter($namespace['commands'], function ($name) use ($commands) { - return isset($commands[$name]); - }); - - if (!$namespace['commands']) { - continue; - } - - if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { - $this->writeText("\n"); - $this->writeText(' '.$namespace['id'].'', $options); - } - - foreach ($namespace['commands'] as $name) { - $this->writeText("\n"); - $spacingWidth = $width - Helper::strlen($name); - $command = $commands[$name]; - $commandAliases = $name === $command->getName() ? $this->getCommandAliasesText($command) : ''; - $this->writeText(sprintf(' %s%s%s', $name, str_repeat(' ', $spacingWidth), $commandAliases.$command->getDescription()), $options); - } - } - - $this->writeText("\n"); - } - } - - /** - * {@inheritdoc} - */ - private function writeText($content, array $options = array()) - { - $this->write( - isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content, - isset($options['raw_output']) ? !$options['raw_output'] : true - ); - } - - /** - * Formats command aliases to show them in the command description. - * - * @return string - */ - private function getCommandAliasesText(Command $command) - { - $text = ''; - $aliases = $command->getAliases(); - - if ($aliases) { - $text = '['.implode('|', $aliases).'] '; - } - - return $text; - } - - /** - * Formats input option/argument default value. - * - * @param mixed $default - * - * @return string - */ - private function formatDefaultValue($default) - { - if (INF === $default) { - return 'INF'; - } - - if (is_string($default)) { - $default = OutputFormatter::escape($default); - } elseif (is_array($default)) { - foreach ($default as $key => $value) { - if (is_string($value)) { - $default[$key] = OutputFormatter::escape($value); - } - } - } - - return str_replace('\\\\', '\\', json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); - } - - /** - * @param (Command|string)[] $commands - * - * @return int - */ - private function getColumnWidth(array $commands) - { - $widths = array(); - - foreach ($commands as $command) { - if ($command instanceof Command) { - $widths[] = Helper::strlen($command->getName()); - foreach ($command->getAliases() as $alias) { - $widths[] = Helper::strlen($alias); - } - } else { - $widths[] = Helper::strlen($command); - } - } - - return $widths ? max($widths) + 2 : 0; - } - - /** - * @param InputOption[] $options - * - * @return int - */ - private function calculateTotalWidthForOptions(array $options) - { - $totalWidth = 0; - foreach ($options as $option) { - // "-" + shortcut + ", --" + name - $nameLength = 1 + max(Helper::strlen($option->getShortcut()), 1) + 4 + Helper::strlen($option->getName()); - - if ($option->acceptValue()) { - $valueLength = 1 + Helper::strlen($option->getName()); // = + value - $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ] - - $nameLength += $valueLength; - } - $totalWidth = max($totalWidth, $nameLength); - } - - return $totalWidth; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Descriptor; - -use Symfony\Component\Console\Application; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputDefinition; -use Symfony\Component\Console\Input\InputOption; - -/** - * XML descriptor. - * - * @author Jean-François Simon - * - * @internal - */ -class XmlDescriptor extends Descriptor -{ - /** - * @return \DOMDocument - */ - public function getInputDefinitionDocument(InputDefinition $definition) - { - $dom = new \DOMDocument('1.0', 'UTF-8'); - $dom->appendChild($definitionXML = $dom->createElement('definition')); - - $definitionXML->appendChild($argumentsXML = $dom->createElement('arguments')); - foreach ($definition->getArguments() as $argument) { - $this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument)); - } - - $definitionXML->appendChild($optionsXML = $dom->createElement('options')); - foreach ($definition->getOptions() as $option) { - $this->appendDocument($optionsXML, $this->getInputOptionDocument($option)); - } - - return $dom; - } - - /** - * @return \DOMDocument - */ - public function getCommandDocument(Command $command) - { - $dom = new \DOMDocument('1.0', 'UTF-8'); - $dom->appendChild($commandXML = $dom->createElement('command')); - - $command->getSynopsis(); - $command->mergeApplicationDefinition(false); - - $commandXML->setAttribute('id', $command->getName()); - $commandXML->setAttribute('name', $command->getName()); - $commandXML->setAttribute('hidden', $command->isHidden() ? 1 : 0); - - $commandXML->appendChild($usagesXML = $dom->createElement('usages')); - - foreach (array_merge(array($command->getSynopsis()), $command->getAliases(), $command->getUsages()) as $usage) { - $usagesXML->appendChild($dom->createElement('usage', $usage)); - } - - $commandXML->appendChild($descriptionXML = $dom->createElement('description')); - $descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription()))); - - $commandXML->appendChild($helpXML = $dom->createElement('help')); - $helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp()))); - - $definitionXML = $this->getInputDefinitionDocument($command->getNativeDefinition()); - $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0)); - - return $dom; - } - - /** - * @param Application $application - * @param string|null $namespace - * - * @return \DOMDocument - */ - public function getApplicationDocument(Application $application, $namespace = null) - { - $dom = new \DOMDocument('1.0', 'UTF-8'); - $dom->appendChild($rootXml = $dom->createElement('symfony')); - - if ('UNKNOWN' !== $application->getName()) { - $rootXml->setAttribute('name', $application->getName()); - if ('UNKNOWN' !== $application->getVersion()) { - $rootXml->setAttribute('version', $application->getVersion()); - } - } - - $rootXml->appendChild($commandsXML = $dom->createElement('commands')); - - $description = new ApplicationDescription($application, $namespace, true); - - if ($namespace) { - $commandsXML->setAttribute('namespace', $namespace); - } - - foreach ($description->getCommands() as $command) { - $this->appendDocument($commandsXML, $this->getCommandDocument($command)); - } - - if (!$namespace) { - $rootXml->appendChild($namespacesXML = $dom->createElement('namespaces')); - - foreach ($description->getNamespaces() as $namespaceDescription) { - $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace')); - $namespaceArrayXML->setAttribute('id', $namespaceDescription['id']); - - foreach ($namespaceDescription['commands'] as $name) { - $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command')); - $commandXML->appendChild($dom->createTextNode($name)); - } - } - } - - return $dom; - } - - /** - * {@inheritdoc} - */ - protected function describeInputArgument(InputArgument $argument, array $options = array()) - { - $this->writeDocument($this->getInputArgumentDocument($argument)); - } - - /** - * {@inheritdoc} - */ - protected function describeInputOption(InputOption $option, array $options = array()) - { - $this->writeDocument($this->getInputOptionDocument($option)); - } - - /** - * {@inheritdoc} - */ - protected function describeInputDefinition(InputDefinition $definition, array $options = array()) - { - $this->writeDocument($this->getInputDefinitionDocument($definition)); - } - - /** - * {@inheritdoc} - */ - protected function describeCommand(Command $command, array $options = array()) - { - $this->writeDocument($this->getCommandDocument($command)); - } - - /** - * {@inheritdoc} - */ - protected function describeApplication(Application $application, array $options = array()) - { - $this->writeDocument($this->getApplicationDocument($application, isset($options['namespace']) ? $options['namespace'] : null)); - } - - /** - * Appends document children to parent node. - */ - private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent) - { - foreach ($importedParent->childNodes as $childNode) { - $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true)); - } - } - - /** - * Writes DOM document. - * - * @return \DOMDocument|string - */ - private function writeDocument(\DOMDocument $dom) - { - $dom->formatOutput = true; - $this->write($dom->saveXML()); - } - - /** - * @return \DOMDocument - */ - private function getInputArgumentDocument(InputArgument $argument) - { - $dom = new \DOMDocument('1.0', 'UTF-8'); - - $dom->appendChild($objectXML = $dom->createElement('argument')); - $objectXML->setAttribute('name', $argument->getName()); - $objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0); - $objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0); - $objectXML->appendChild($descriptionXML = $dom->createElement('description')); - $descriptionXML->appendChild($dom->createTextNode($argument->getDescription())); - - $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); - $defaults = is_array($argument->getDefault()) ? $argument->getDefault() : (is_bool($argument->getDefault()) ? array(var_export($argument->getDefault(), true)) : ($argument->getDefault() ? array($argument->getDefault()) : array())); - foreach ($defaults as $default) { - $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); - $defaultXML->appendChild($dom->createTextNode($default)); - } - - return $dom; - } - - /** - * @return \DOMDocument - */ - private function getInputOptionDocument(InputOption $option) - { - $dom = new \DOMDocument('1.0', 'UTF-8'); - - $dom->appendChild($objectXML = $dom->createElement('option')); - $objectXML->setAttribute('name', '--'.$option->getName()); - $pos = strpos($option->getShortcut(), '|'); - if (false !== $pos) { - $objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos)); - $objectXML->setAttribute('shortcuts', '-'.implode('|-', explode('|', $option->getShortcut()))); - } else { - $objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : ''); - } - $objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0); - $objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0); - $objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0); - $objectXML->appendChild($descriptionXML = $dom->createElement('description')); - $descriptionXML->appendChild($dom->createTextNode($option->getDescription())); - - if ($option->acceptValue()) { - $defaults = is_array($option->getDefault()) ? $option->getDefault() : (is_bool($option->getDefault()) ? array(var_export($option->getDefault(), true)) : ($option->getDefault() ? array($option->getDefault()) : array())); - $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); - - if (!empty($defaults)) { - foreach ($defaults as $default) { - $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); - $defaultXML->appendChild($dom->createTextNode($default)); - } - } - } - - return $dom; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Event; - -/** - * Allows to do things before the command is executed, like skipping the command or changing the input. - * - * @author Fabien Potencier - */ -class ConsoleCommandEvent extends ConsoleEvent -{ - /** - * The return code for skipped commands, this will also be passed into the terminate event. - */ - const RETURN_CODE_DISABLED = 113; - - /** - * Indicates if the command should be run or skipped. - */ - private $commandShouldRun = true; - - /** - * Disables the command, so it won't be run. - * - * @return bool - */ - public function disableCommand() - { - return $this->commandShouldRun = false; - } - - /** - * Enables the command. - * - * @return bool - */ - public function enableCommand() - { - return $this->commandShouldRun = true; - } - - /** - * Returns true if the command is runnable, false otherwise. - * - * @return bool - */ - public function commandShouldRun() - { - return $this->commandShouldRun; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Event; - -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Exception\InvalidArgumentException; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; - -/** - * Allows to handle throwables thrown while running a command. - * - * @author Wouter de Jong - */ -final class ConsoleErrorEvent extends ConsoleEvent -{ - private $error; - private $exitCode; - - public function __construct(InputInterface $input, OutputInterface $output, $error, Command $command = null) - { - parent::__construct($command, $input, $output); - - $this->setError($error); - } - - /** - * Returns the thrown error/exception. - * - * @return \Throwable - */ - public function getError() - { - return $this->error; - } - - /** - * Replaces the thrown error/exception. - * - * @param \Throwable $error - */ - public function setError($error) - { - if (!$error instanceof \Throwable && !$error instanceof \Exception) { - throw new InvalidArgumentException(sprintf('The error passed to ConsoleErrorEvent must be an instance of \Throwable or \Exception, "%s" was passed instead.', is_object($error) ? get_class($error) : gettype($error))); - } - - $this->error = $error; - } - - /** - * Sets the exit code. - * - * @param int $exitCode The command exit code - */ - public function setExitCode($exitCode) - { - $this->exitCode = (int) $exitCode; - - $r = new \ReflectionProperty($this->error, 'code'); - $r->setAccessible(true); - $r->setValue($this->error, $this->exitCode); - } - - /** - * Gets the exit code. - * - * @return int The command exit code - */ - public function getExitCode() - { - return null !== $this->exitCode ? $this->exitCode : ($this->error->getCode() ?: 1); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Event; - -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\EventDispatcher\Event; - -/** - * Allows to inspect input and output of a command. - * - * @author Francesco Levorato - */ -class ConsoleEvent extends Event -{ - protected $command; - - private $input; - private $output; - - public function __construct(Command $command = null, InputInterface $input, OutputInterface $output) - { - $this->command = $command; - $this->input = $input; - $this->output = $output; - } - - /** - * Gets the command that is executed. - * - * @return Command|null A Command instance - */ - public function getCommand() - { - return $this->command; - } - - /** - * Gets the input instance. - * - * @return InputInterface An InputInterface instance - */ - public function getInput() - { - return $this->input; - } - - /** - * Gets the output instance. - * - * @return OutputInterface An OutputInterface instance - */ - public function getOutput() - { - return $this->output; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Event; - -@trigger_error(sprintf('The "%s" class is deprecated since version 3.3 and will be removed in 4.0. Use the ConsoleErrorEvent instead.', ConsoleExceptionEvent::class), E_USER_DEPRECATED); - -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; - -/** - * Allows to handle exception thrown in a command. - * - * @author Fabien Potencier - * - * @deprecated since version 3.3, to be removed in 4.0. Use ConsoleErrorEvent instead. - */ -class ConsoleExceptionEvent extends ConsoleEvent -{ - private $exception; - private $exitCode; - - public function __construct(Command $command, InputInterface $input, OutputInterface $output, \Exception $exception, $exitCode) - { - parent::__construct($command, $input, $output); - - $this->setException($exception); - $this->exitCode = (int) $exitCode; - } - - /** - * Returns the thrown exception. - * - * @return \Exception The thrown exception - */ - public function getException() - { - return $this->exception; - } - - /** - * Replaces the thrown exception. - * - * This exception will be thrown if no response is set in the event. - * - * @param \Exception $exception The thrown exception - */ - public function setException(\Exception $exception) - { - $this->exception = $exception; - } - - /** - * Gets the exit code. - * - * @return int The command exit code - */ - public function getExitCode() - { - return $this->exitCode; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Event; - -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; - -/** - * Allows to manipulate the exit code of a command after its execution. - * - * @author Francesco Levorato - */ -class ConsoleTerminateEvent extends ConsoleEvent -{ - /** - * The exit code of the command. - * - * @var int - */ - private $exitCode; - - public function __construct(Command $command, InputInterface $input, OutputInterface $output, $exitCode) - { - parent::__construct($command, $input, $output); - - $this->setExitCode($exitCode); - } - - /** - * Sets the exit code. - * - * @param int $exitCode The command exit code - */ - public function setExitCode($exitCode) - { - $this->exitCode = (int) $exitCode; - } - - /** - * Gets the exit code. - * - * @return int The command exit code - */ - public function getExitCode() - { - return $this->exitCode; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\EventListener; - -use Psr\Log\LoggerInterface; -use Symfony\Component\Console\ConsoleEvents; -use Symfony\Component\Console\Event\ConsoleErrorEvent; -use Symfony\Component\Console\Event\ConsoleEvent; -use Symfony\Component\Console\Event\ConsoleTerminateEvent; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; - -/** - * @author James Halsall - * @author Robin Chalas - */ -class ErrorListener implements EventSubscriberInterface -{ - private $logger; - - public function __construct(LoggerInterface $logger = null) - { - $this->logger = $logger; - } - - public function onConsoleError(ConsoleErrorEvent $event) - { - if (null === $this->logger) { - return; - } - - $error = $event->getError(); - - if (!$inputString = $this->getInputString($event)) { - return $this->logger->error('An error occurred while using the console. Message: "{message}"', array('error' => $error, 'message' => $error->getMessage())); - } - - $this->logger->error('Error thrown while running command "{command}". Message: "{message}"', array('error' => $error, 'command' => $inputString, 'message' => $error->getMessage())); - } - - public function onConsoleTerminate(ConsoleTerminateEvent $event) - { - if (null === $this->logger) { - return; - } - - $exitCode = $event->getExitCode(); - - if (0 === $exitCode) { - return; - } - - if (!$inputString = $this->getInputString($event)) { - return $this->logger->debug('The console exited with code "{code}"', array('code' => $exitCode)); - } - - $this->logger->debug('Command "{command}" exited with code "{code}"', array('command' => $inputString, 'code' => $exitCode)); - } - - public static function getSubscribedEvents() - { - return array( - ConsoleEvents::ERROR => array('onConsoleError', -128), - ConsoleEvents::TERMINATE => array('onConsoleTerminate', -128), - ); - } - - private static function getInputString(ConsoleEvent $event) - { - $commandName = $event->getCommand() ? $event->getCommand()->getName() : null; - $input = $event->getInput(); - - if (method_exists($input, '__toString')) { - if ($commandName) { - return str_replace(array("'$commandName'", "\"$commandName\""), $commandName, (string) $input); - } - - return (string) $input; - } - - return $commandName; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Exception; - -/** - * Represents an incorrect command name typed in the console. - * - * @author Jérôme Tamarelle - */ -class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface -{ - private $alternatives; - - /** - * @param string $message Exception message to throw - * @param array $alternatives List of similar defined names - * @param int $code Exception code - * @param \Exception $previous Previous exception used for the exception chaining - */ - public function __construct($message, array $alternatives = array(), $code = 0, \Exception $previous = null) - { - parent::__construct($message, $code, $previous); - - $this->alternatives = $alternatives; - } - - /** - * @return array A list of similar defined names - */ - public function getAlternatives() - { - return $this->alternatives; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Exception; - -/** - * ExceptionInterface. - * - * @author Jérôme Tamarelle - */ -interface ExceptionInterface -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Exception; - -/** - * @author Jérôme Tamarelle - */ -class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Exception; - -/** - * Represents an incorrect option name typed in the console. - * - * @author Jérôme Tamarelle - */ -class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Exception; - -/** - * @author Jérôme Tamarelle - */ -class LogicException extends \LogicException implements ExceptionInterface -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Exception; - -/** - * @author Jérôme Tamarelle - */ -class RuntimeException extends \RuntimeException implements ExceptionInterface -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Formatter; - -use Symfony\Component\Console\Exception\InvalidArgumentException; - -/** - * Formatter class for console output. - * - * @author Konstantin Kudryashov - */ -class OutputFormatter implements OutputFormatterInterface -{ - private $decorated; - private $styles = array(); - private $styleStack; - - /** - * Escapes "<" special char in given text. - * - * @param string $text Text to escape - * - * @return string Escaped text - */ - public static function escape($text) - { - $text = preg_replace('/([^\\\\]?) FormatterStyle" instances - */ - public function __construct($decorated = false, array $styles = array()) - { - $this->decorated = (bool) $decorated; - - $this->setStyle('error', new OutputFormatterStyle('white', 'red')); - $this->setStyle('info', new OutputFormatterStyle('green')); - $this->setStyle('comment', new OutputFormatterStyle('yellow')); - $this->setStyle('question', new OutputFormatterStyle('black', 'cyan')); - - foreach ($styles as $name => $style) { - $this->setStyle($name, $style); - } - - $this->styleStack = new OutputFormatterStyleStack(); - } - - /** - * {@inheritdoc} - */ - public function setDecorated($decorated) - { - $this->decorated = (bool) $decorated; - } - - /** - * {@inheritdoc} - */ - public function isDecorated() - { - return $this->decorated; - } - - /** - * {@inheritdoc} - */ - public function setStyle($name, OutputFormatterStyleInterface $style) - { - $this->styles[strtolower($name)] = $style; - } - - /** - * {@inheritdoc} - */ - public function hasStyle($name) - { - return isset($this->styles[strtolower($name)]); - } - - /** - * {@inheritdoc} - */ - public function getStyle($name) - { - if (!$this->hasStyle($name)) { - throw new InvalidArgumentException(sprintf('Undefined style: %s', $name)); - } - - return $this->styles[strtolower($name)]; - } - - /** - * {@inheritdoc} - */ - public function format($message) - { - $message = (string) $message; - $offset = 0; - $output = ''; - $tagRegex = '[a-z][a-z0-9,_=;-]*+'; - preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#ix", $message, $matches, PREG_OFFSET_CAPTURE); - foreach ($matches[0] as $i => $match) { - $pos = $match[1]; - $text = $match[0]; - - if (0 != $pos && '\\' == $message[$pos - 1]) { - continue; - } - - // add the text up to the next tag - $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset)); - $offset = $pos + strlen($text); - - // opening tag? - if ($open = '/' != $text[1]) { - $tag = $matches[1][$i][0]; - } else { - $tag = isset($matches[3][$i][0]) ? $matches[3][$i][0] : ''; - } - - if (!$open && !$tag) { - // - $this->styleStack->pop(); - } elseif (false === $style = $this->createStyleFromString(strtolower($tag))) { - $output .= $this->applyCurrentStyle($text); - } elseif ($open) { - $this->styleStack->push($style); - } else { - $this->styleStack->pop($style); - } - } - - $output .= $this->applyCurrentStyle(substr($message, $offset)); - - if (false !== strpos($output, "\0")) { - return strtr($output, array("\0" => '\\', '\\<' => '<')); - } - - return str_replace('\\<', '<', $output); - } - - /** - * @return OutputFormatterStyleStack - */ - public function getStyleStack() - { - return $this->styleStack; - } - - /** - * Tries to create new style instance from string. - * - * @param string $string - * - * @return OutputFormatterStyle|false false if string is not format string - */ - private function createStyleFromString($string) - { - if (isset($this->styles[$string])) { - return $this->styles[$string]; - } - - if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, PREG_SET_ORDER)) { - return false; - } - - $style = new OutputFormatterStyle(); - foreach ($matches as $match) { - array_shift($match); - - if ('fg' == $match[0]) { - $style->setForeground($match[1]); - } elseif ('bg' == $match[0]) { - $style->setBackground($match[1]); - } elseif ('options' === $match[0]) { - preg_match_all('([^,;]+)', $match[1], $options); - $options = array_shift($options); - foreach ($options as $option) { - try { - $style->setOption($option); - } catch (\InvalidArgumentException $e) { - @trigger_error(sprintf('Unknown style options are deprecated since version 3.2 and will be removed in 4.0. Exception "%s".', $e->getMessage()), E_USER_DEPRECATED); - - return false; - } - } - } else { - return false; - } - } - - return $style; - } - - /** - * Applies current style from stack to text, if must be applied. - * - * @param string $text Input text - * - * @return string Styled text - */ - private function applyCurrentStyle($text) - { - return $this->isDecorated() && strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Formatter; - -/** - * Formatter interface for console output. - * - * @author Konstantin Kudryashov - */ -interface OutputFormatterInterface -{ - /** - * Sets the decorated flag. - * - * @param bool $decorated Whether to decorate the messages or not - */ - public function setDecorated($decorated); - - /** - * Gets the decorated flag. - * - * @return bool true if the output will decorate messages, false otherwise - */ - public function isDecorated(); - - /** - * Sets a new style. - * - * @param string $name The style name - * @param OutputFormatterStyleInterface $style The style instance - */ - public function setStyle($name, OutputFormatterStyleInterface $style); - - /** - * Checks if output formatter has style with specified name. - * - * @param string $name - * - * @return bool - */ - public function hasStyle($name); - - /** - * Gets style options from style with specified name. - * - * @param string $name - * - * @return OutputFormatterStyleInterface - * - * @throws \InvalidArgumentException When style isn't defined - */ - public function getStyle($name); - - /** - * Formats a message according to the given styles. - * - * @param string $message The message to style - * - * @return string The styled message - */ - public function format($message); -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Formatter; - -use Symfony\Component\Console\Exception\InvalidArgumentException; - -/** - * Formatter style class for defining styles. - * - * @author Konstantin Kudryashov - */ -class OutputFormatterStyle implements OutputFormatterStyleInterface -{ - private static $availableForegroundColors = array( - 'black' => array('set' => 30, 'unset' => 39), - 'red' => array('set' => 31, 'unset' => 39), - 'green' => array('set' => 32, 'unset' => 39), - 'yellow' => array('set' => 33, 'unset' => 39), - 'blue' => array('set' => 34, 'unset' => 39), - 'magenta' => array('set' => 35, 'unset' => 39), - 'cyan' => array('set' => 36, 'unset' => 39), - 'white' => array('set' => 37, 'unset' => 39), - 'default' => array('set' => 39, 'unset' => 39), - ); - private static $availableBackgroundColors = array( - 'black' => array('set' => 40, 'unset' => 49), - 'red' => array('set' => 41, 'unset' => 49), - 'green' => array('set' => 42, 'unset' => 49), - 'yellow' => array('set' => 43, 'unset' => 49), - 'blue' => array('set' => 44, 'unset' => 49), - 'magenta' => array('set' => 45, 'unset' => 49), - 'cyan' => array('set' => 46, 'unset' => 49), - 'white' => array('set' => 47, 'unset' => 49), - 'default' => array('set' => 49, 'unset' => 49), - ); - private static $availableOptions = array( - 'bold' => array('set' => 1, 'unset' => 22), - 'underscore' => array('set' => 4, 'unset' => 24), - 'blink' => array('set' => 5, 'unset' => 25), - 'reverse' => array('set' => 7, 'unset' => 27), - 'conceal' => array('set' => 8, 'unset' => 28), - ); - - private $foreground; - private $background; - private $options = array(); - - /** - * Initializes output formatter style. - * - * @param string|null $foreground The style foreground color name - * @param string|null $background The style background color name - * @param array $options The style options - */ - public function __construct($foreground = null, $background = null, array $options = array()) - { - if (null !== $foreground) { - $this->setForeground($foreground); - } - if (null !== $background) { - $this->setBackground($background); - } - if (count($options)) { - $this->setOptions($options); - } - } - - /** - * Sets style foreground color. - * - * @param string|null $color The color name - * - * @throws InvalidArgumentException When the color name isn't defined - */ - public function setForeground($color = null) - { - if (null === $color) { - $this->foreground = null; - - return; - } - - if (!isset(static::$availableForegroundColors[$color])) { - throw new InvalidArgumentException(sprintf( - 'Invalid foreground color specified: "%s". Expected one of (%s)', - $color, - implode(', ', array_keys(static::$availableForegroundColors)) - )); - } - - $this->foreground = static::$availableForegroundColors[$color]; - } - - /** - * Sets style background color. - * - * @param string|null $color The color name - * - * @throws InvalidArgumentException When the color name isn't defined - */ - public function setBackground($color = null) - { - if (null === $color) { - $this->background = null; - - return; - } - - if (!isset(static::$availableBackgroundColors[$color])) { - throw new InvalidArgumentException(sprintf( - 'Invalid background color specified: "%s". Expected one of (%s)', - $color, - implode(', ', array_keys(static::$availableBackgroundColors)) - )); - } - - $this->background = static::$availableBackgroundColors[$color]; - } - - /** - * Sets some specific style option. - * - * @param string $option The option name - * - * @throws InvalidArgumentException When the option name isn't defined - */ - public function setOption($option) - { - if (!isset(static::$availableOptions[$option])) { - throw new InvalidArgumentException(sprintf( - 'Invalid option specified: "%s". Expected one of (%s)', - $option, - implode(', ', array_keys(static::$availableOptions)) - )); - } - - if (!in_array(static::$availableOptions[$option], $this->options)) { - $this->options[] = static::$availableOptions[$option]; - } - } - - /** - * Unsets some specific style option. - * - * @param string $option The option name - * - * @throws InvalidArgumentException When the option name isn't defined - */ - public function unsetOption($option) - { - if (!isset(static::$availableOptions[$option])) { - throw new InvalidArgumentException(sprintf( - 'Invalid option specified: "%s". Expected one of (%s)', - $option, - implode(', ', array_keys(static::$availableOptions)) - )); - } - - $pos = array_search(static::$availableOptions[$option], $this->options); - if (false !== $pos) { - unset($this->options[$pos]); - } - } - - /** - * {@inheritdoc} - */ - public function setOptions(array $options) - { - $this->options = array(); - - foreach ($options as $option) { - $this->setOption($option); - } - } - - /** - * Applies the style to a given text. - * - * @param string $text The text to style - * - * @return string - */ - public function apply($text) - { - $setCodes = array(); - $unsetCodes = array(); - - if (null !== $this->foreground) { - $setCodes[] = $this->foreground['set']; - $unsetCodes[] = $this->foreground['unset']; - } - if (null !== $this->background) { - $setCodes[] = $this->background['set']; - $unsetCodes[] = $this->background['unset']; - } - if (count($this->options)) { - foreach ($this->options as $option) { - $setCodes[] = $option['set']; - $unsetCodes[] = $option['unset']; - } - } - - if (0 === count($setCodes)) { - return $text; - } - - return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes)); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Formatter; - -/** - * Formatter style interface for defining styles. - * - * @author Konstantin Kudryashov - */ -interface OutputFormatterStyleInterface -{ - /** - * Sets style foreground color. - * - * @param string $color The color name - */ - public function setForeground($color = null); - - /** - * Sets style background color. - * - * @param string $color The color name - */ - public function setBackground($color = null); - - /** - * Sets some specific style option. - * - * @param string $option The option name - */ - public function setOption($option); - - /** - * Unsets some specific style option. - * - * @param string $option The option name - */ - public function unsetOption($option); - - /** - * Sets multiple style options at once. - */ - public function setOptions(array $options); - - /** - * Applies the style to a given text. - * - * @param string $text The text to style - * - * @return string - */ - public function apply($text); -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Formatter; - -use Symfony\Component\Console\Exception\InvalidArgumentException; - -/** - * @author Jean-François Simon - */ -class OutputFormatterStyleStack -{ - /** - * @var OutputFormatterStyleInterface[] - */ - private $styles; - - private $emptyStyle; - - public function __construct(OutputFormatterStyleInterface $emptyStyle = null) - { - $this->emptyStyle = $emptyStyle ?: new OutputFormatterStyle(); - $this->reset(); - } - - /** - * Resets stack (ie. empty internal arrays). - */ - public function reset() - { - $this->styles = array(); - } - - /** - * Pushes a style in the stack. - */ - public function push(OutputFormatterStyleInterface $style) - { - $this->styles[] = $style; - } - - /** - * Pops a style from the stack. - * - * @return OutputFormatterStyleInterface - * - * @throws InvalidArgumentException When style tags incorrectly nested - */ - public function pop(OutputFormatterStyleInterface $style = null) - { - if (empty($this->styles)) { - return $this->emptyStyle; - } - - if (null === $style) { - return array_pop($this->styles); - } - - foreach (array_reverse($this->styles, true) as $index => $stackedStyle) { - if ($style->apply('') === $stackedStyle->apply('')) { - $this->styles = array_slice($this->styles, 0, $index); - - return $stackedStyle; - } - } - - throw new InvalidArgumentException('Incorrectly nested style tag found.'); - } - - /** - * Computes current style with stacks top codes. - * - * @return OutputFormatterStyle - */ - public function getCurrent() - { - if (empty($this->styles)) { - return $this->emptyStyle; - } - - return $this->styles[count($this->styles) - 1]; - } - - /** - * @return $this - */ - public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle) - { - $this->emptyStyle = $emptyStyle; - - return $this; - } - - /** - * @return OutputFormatterStyleInterface - */ - public function getEmptyStyle() - { - return $this->emptyStyle; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Helper; - -/** - * Helps outputting debug information when running an external program from a command. - * - * An external program can be a Process, an HTTP request, or anything else. - * - * @author Fabien Potencier - */ -class DebugFormatterHelper extends Helper -{ - private $colors = array('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default'); - private $started = array(); - private $count = -1; - - /** - * Starts a debug formatting session. - * - * @param string $id The id of the formatting session - * @param string $message The message to display - * @param string $prefix The prefix to use - * - * @return string - */ - public function start($id, $message, $prefix = 'RUN') - { - $this->started[$id] = array('border' => ++$this->count % count($this->colors)); - - return sprintf("%s %s %s\n", $this->getBorder($id), $prefix, $message); - } - - /** - * Adds progress to a formatting session. - * - * @param string $id The id of the formatting session - * @param string $buffer The message to display - * @param bool $error Whether to consider the buffer as error - * @param string $prefix The prefix for output - * @param string $errorPrefix The prefix for error output - * - * @return string - */ - public function progress($id, $buffer, $error = false, $prefix = 'OUT', $errorPrefix = 'ERR') - { - $message = ''; - - if ($error) { - if (isset($this->started[$id]['out'])) { - $message .= "\n"; - unset($this->started[$id]['out']); - } - if (!isset($this->started[$id]['err'])) { - $message .= sprintf('%s %s ', $this->getBorder($id), $errorPrefix); - $this->started[$id]['err'] = true; - } - - $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $errorPrefix), $buffer); - } else { - if (isset($this->started[$id]['err'])) { - $message .= "\n"; - unset($this->started[$id]['err']); - } - if (!isset($this->started[$id]['out'])) { - $message .= sprintf('%s %s ', $this->getBorder($id), $prefix); - $this->started[$id]['out'] = true; - } - - $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $prefix), $buffer); - } - - return $message; - } - - /** - * Stops a formatting session. - * - * @param string $id The id of the formatting session - * @param string $message The message to display - * @param bool $successful Whether to consider the result as success - * @param string $prefix The prefix for the end output - * - * @return string - */ - public function stop($id, $message, $successful, $prefix = 'RES') - { - $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : ''; - - if ($successful) { - return sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); - } - - $message = sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); - - unset($this->started[$id]['out'], $this->started[$id]['err']); - - return $message; - } - - /** - * @param string $id The id of the formatting session - * - * @return string - */ - private function getBorder($id) - { - return sprintf(' ', $this->colors[$this->started[$id]['border']]); - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'debug_formatter'; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Helper; - -use Symfony\Component\Console\Descriptor\DescriptorInterface; -use Symfony\Component\Console\Descriptor\JsonDescriptor; -use Symfony\Component\Console\Descriptor\MarkdownDescriptor; -use Symfony\Component\Console\Descriptor\TextDescriptor; -use Symfony\Component\Console\Descriptor\XmlDescriptor; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Exception\InvalidArgumentException; - -/** - * This class adds helper method to describe objects in various formats. - * - * @author Jean-François Simon - */ -class DescriptorHelper extends Helper -{ - /** - * @var DescriptorInterface[] - */ - private $descriptors = array(); - - public function __construct() - { - $this - ->register('txt', new TextDescriptor()) - ->register('xml', new XmlDescriptor()) - ->register('json', new JsonDescriptor()) - ->register('md', new MarkdownDescriptor()) - ; - } - - /** - * Describes an object if supported. - * - * Available options are: - * * format: string, the output format name - * * raw_text: boolean, sets output type as raw - * - * @param OutputInterface $output - * @param object $object - * @param array $options - * - * @throws InvalidArgumentException when the given format is not supported - */ - public function describe(OutputInterface $output, $object, array $options = array()) - { - $options = array_merge(array( - 'raw_text' => false, - 'format' => 'txt', - ), $options); - - if (!isset($this->descriptors[$options['format']])) { - throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format'])); - } - - $descriptor = $this->descriptors[$options['format']]; - $descriptor->describe($output, $object, $options); - } - - /** - * Registers a descriptor. - * - * @param string $format - * @param DescriptorInterface $descriptor - * - * @return $this - */ - public function register($format, DescriptorInterface $descriptor) - { - $this->descriptors[$format] = $descriptor; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'descriptor'; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Helper; - -use Symfony\Component\Console\Formatter\OutputFormatter; - -/** - * The Formatter class provides helpers to format messages. - * - * @author Fabien Potencier - */ -class FormatterHelper extends Helper -{ - /** - * Formats a message within a section. - * - * @param string $section The section name - * @param string $message The message - * @param string $style The style to apply to the section - * - * @return string The format section - */ - public function formatSection($section, $message, $style = 'info') - { - return sprintf('<%s>[%s] %s', $style, $section, $style, $message); - } - - /** - * Formats a message as a block of text. - * - * @param string|array $messages The message to write in the block - * @param string $style The style to apply to the whole block - * @param bool $large Whether to return a large block - * - * @return string The formatter message - */ - public function formatBlock($messages, $style, $large = false) - { - if (!is_array($messages)) { - $messages = array($messages); - } - - $len = 0; - $lines = array(); - foreach ($messages as $message) { - $message = OutputFormatter::escape($message); - $lines[] = sprintf($large ? ' %s ' : ' %s ', $message); - $len = max($this->strlen($message) + ($large ? 4 : 2), $len); - } - - $messages = $large ? array(str_repeat(' ', $len)) : array(); - for ($i = 0; isset($lines[$i]); ++$i) { - $messages[] = $lines[$i].str_repeat(' ', $len - $this->strlen($lines[$i])); - } - if ($large) { - $messages[] = str_repeat(' ', $len); - } - - for ($i = 0; isset($messages[$i]); ++$i) { - $messages[$i] = sprintf('<%s>%s', $style, $messages[$i], $style); - } - - return implode("\n", $messages); - } - - /** - * Truncates a message to the given length. - * - * @param string $message - * @param int $length - * @param string $suffix - * - * @return string - */ - public function truncate($message, $length, $suffix = '...') - { - $computedLength = $length - $this->strlen($suffix); - - if ($computedLength > $this->strlen($message)) { - return $message; - } - - if (false === $encoding = mb_detect_encoding($message, null, true)) { - return substr($message, 0, $length).$suffix; - } - - return mb_substr($message, 0, $length, $encoding).$suffix; - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'formatter'; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Helper; - -use Symfony\Component\Console\Formatter\OutputFormatterInterface; - -/** - * Helper is the base class for all helper classes. - * - * @author Fabien Potencier - */ -abstract class Helper implements HelperInterface -{ - protected $helperSet = null; - - /** - * {@inheritdoc} - */ - public function setHelperSet(HelperSet $helperSet = null) - { - $this->helperSet = $helperSet; - } - - /** - * {@inheritdoc} - */ - public function getHelperSet() - { - return $this->helperSet; - } - - /** - * Returns the length of a string, using mb_strwidth if it is available. - * - * @param string $string The string to check its length - * - * @return int The length of the string - */ - public static function strlen($string) - { - if (false === $encoding = mb_detect_encoding($string, null, true)) { - return strlen($string); - } - - return mb_strwidth($string, $encoding); - } - - /** - * Returns the subset of a string, using mb_substr if it is available. - * - * @param string $string String to subset - * @param int $from Start offset - * @param int|null $length Length to read - * - * @return string The string subset - */ - public static function substr($string, $from, $length = null) - { - if (false === $encoding = mb_detect_encoding($string, null, true)) { - return substr($string, $from, $length); - } - - return mb_substr($string, $from, $length, $encoding); - } - - public static function formatTime($secs) - { - static $timeFormats = array( - array(0, '< 1 sec'), - array(1, '1 sec'), - array(2, 'secs', 1), - array(60, '1 min'), - array(120, 'mins', 60), - array(3600, '1 hr'), - array(7200, 'hrs', 3600), - array(86400, '1 day'), - array(172800, 'days', 86400), - ); - - foreach ($timeFormats as $index => $format) { - if ($secs >= $format[0]) { - if ((isset($timeFormats[$index + 1]) && $secs < $timeFormats[$index + 1][0]) - || $index == count($timeFormats) - 1 - ) { - if (2 == count($format)) { - return $format[1]; - } - - return floor($secs / $format[2]).' '.$format[1]; - } - } - } - } - - public static function formatMemory($memory) - { - if ($memory >= 1024 * 1024 * 1024) { - return sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024); - } - - if ($memory >= 1024 * 1024) { - return sprintf('%.1f MiB', $memory / 1024 / 1024); - } - - if ($memory >= 1024) { - return sprintf('%d KiB', $memory / 1024); - } - - return sprintf('%d B', $memory); - } - - public static function strlenWithoutDecoration(OutputFormatterInterface $formatter, $string) - { - return self::strlen(self::removeDecoration($formatter, $string)); - } - - public static function removeDecoration(OutputFormatterInterface $formatter, $string) - { - $isDecorated = $formatter->isDecorated(); - $formatter->setDecorated(false); - // remove <...> formatting - $string = $formatter->format($string); - // remove already formatted characters - $string = preg_replace("/\033\[[^m]*m/", '', $string); - $formatter->setDecorated($isDecorated); - - return $string; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Helper; - -/** - * HelperInterface is the interface all helpers must implement. - * - * @author Fabien Potencier - */ -interface HelperInterface -{ - /** - * Sets the helper set associated with this helper. - */ - public function setHelperSet(HelperSet $helperSet = null); - - /** - * Gets the helper set associated with this helper. - * - * @return HelperSet A HelperSet instance - */ - public function getHelperSet(); - - /** - * Returns the canonical name of this helper. - * - * @return string The canonical name - */ - public function getName(); -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Helper; - -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Exception\InvalidArgumentException; - -/** - * HelperSet represents a set of helpers to be used with a command. - * - * @author Fabien Potencier - */ -class HelperSet implements \IteratorAggregate -{ - /** - * @var Helper[] - */ - private $helpers = array(); - private $command; - - /** - * @param Helper[] $helpers An array of helper - */ - public function __construct(array $helpers = array()) - { - foreach ($helpers as $alias => $helper) { - $this->set($helper, is_int($alias) ? null : $alias); - } - } - - /** - * Sets a helper. - * - * @param HelperInterface $helper The helper instance - * @param string $alias An alias - */ - public function set(HelperInterface $helper, $alias = null) - { - $this->helpers[$helper->getName()] = $helper; - if (null !== $alias) { - $this->helpers[$alias] = $helper; - } - - $helper->setHelperSet($this); - } - - /** - * Returns true if the helper if defined. - * - * @param string $name The helper name - * - * @return bool true if the helper is defined, false otherwise - */ - public function has($name) - { - return isset($this->helpers[$name]); - } - - /** - * Gets a helper value. - * - * @param string $name The helper name - * - * @return HelperInterface The helper instance - * - * @throws InvalidArgumentException if the helper is not defined - */ - public function get($name) - { - if (!$this->has($name)) { - throw new InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name)); - } - - return $this->helpers[$name]; - } - - public function setCommand(Command $command = null) - { - $this->command = $command; - } - - /** - * Gets the command associated with this helper set. - * - * @return Command A Command instance - */ - public function getCommand() - { - return $this->command; - } - - /** - * @return Helper[] - */ - public function getIterator() - { - return new \ArrayIterator($this->helpers); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Helper; - -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputAwareInterface; - -/** - * An implementation of InputAwareInterface for Helpers. - * - * @author Wouter J - */ -abstract class InputAwareHelper extends Helper implements InputAwareInterface -{ - protected $input; - - /** - * {@inheritdoc} - */ - public function setInput(InputInterface $input) - { - $this->input = $input; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Helper; - -use Symfony\Component\Console\Output\ConsoleOutputInterface; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Process\Exception\ProcessFailedException; -use Symfony\Component\Process\Process; -use Symfony\Component\Process\ProcessBuilder; - -/** - * The ProcessHelper class provides helpers to run external processes. - * - * @author Fabien Potencier - */ -class ProcessHelper extends Helper -{ - /** - * Runs an external process. - * - * @param OutputInterface $output An OutputInterface instance - * @param string|array|Process $cmd An instance of Process or an array of arguments to escape and run or a command to run - * @param string|null $error An error message that must be displayed if something went wrong - * @param callable|null $callback A PHP callback to run whenever there is some - * output available on STDOUT or STDERR - * @param int $verbosity The threshold for verbosity - * - * @return Process The process that ran - */ - public function run(OutputInterface $output, $cmd, $error = null, callable $callback = null, $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE) - { - if ($output instanceof ConsoleOutputInterface) { - $output = $output->getErrorOutput(); - } - - $formatter = $this->getHelperSet()->get('debug_formatter'); - - if (is_array($cmd)) { - $process = ProcessBuilder::create($cmd)->getProcess(); - } elseif ($cmd instanceof Process) { - $process = $cmd; - } else { - $process = new Process($cmd); - } - - if ($verbosity <= $output->getVerbosity()) { - $output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine()))); - } - - if ($output->isDebug()) { - $callback = $this->wrapCallback($output, $process, $callback); - } - - $process->run($callback); - - if ($verbosity <= $output->getVerbosity()) { - $message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode()); - $output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful())); - } - - if (!$process->isSuccessful() && null !== $error) { - $output->writeln(sprintf('%s', $this->escapeString($error))); - } - - return $process; - } - - /** - * Runs the process. - * - * This is identical to run() except that an exception is thrown if the process - * exits with a non-zero exit code. - * - * @param OutputInterface $output An OutputInterface instance - * @param string|Process $cmd An instance of Process or a command to run - * @param string|null $error An error message that must be displayed if something went wrong - * @param callable|null $callback A PHP callback to run whenever there is some - * output available on STDOUT or STDERR - * - * @return Process The process that ran - * - * @throws ProcessFailedException - * - * @see run() - */ - public function mustRun(OutputInterface $output, $cmd, $error = null, callable $callback = null) - { - $process = $this->run($output, $cmd, $error, $callback); - - if (!$process->isSuccessful()) { - throw new ProcessFailedException($process); - } - - return $process; - } - - /** - * Wraps a Process callback to add debugging output. - * - * @param OutputInterface $output An OutputInterface interface - * @param Process $process The Process - * @param callable|null $callback A PHP callable - * - * @return callable - */ - public function wrapCallback(OutputInterface $output, Process $process, callable $callback = null) - { - if ($output instanceof ConsoleOutputInterface) { - $output = $output->getErrorOutput(); - } - - $formatter = $this->getHelperSet()->get('debug_formatter'); - - return function ($type, $buffer) use ($output, $process, $callback, $formatter) { - $output->write($formatter->progress(spl_object_hash($process), $this->escapeString($buffer), Process::ERR === $type)); - - if (null !== $callback) { - call_user_func($callback, $type, $buffer); - } - }; - } - - private function escapeString($str) - { - return str_replace('<', '\\<', $str); - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'process'; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Helper; - -use Symfony\Component\Console\Output\ConsoleOutputInterface; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Exception\LogicException; -use Symfony\Component\Console\Terminal; - -/** - * The ProgressBar provides helpers to display progress output. - * - * @author Fabien Potencier - * @author Chris Jones - */ -final class ProgressBar -{ - private $barWidth = 28; - private $barChar; - private $emptyBarChar = '-'; - private $progressChar = '>'; - private $format; - private $internalFormat; - private $redrawFreq = 1; - private $output; - private $step = 0; - private $max; - private $startTime; - private $stepWidth; - private $percent = 0.0; - private $formatLineCount; - private $messages = array(); - private $overwrite = true; - private $terminal; - private $firstRun = true; - - private static $formatters; - private static $formats; - - /** - * @param OutputInterface $output An OutputInterface instance - * @param int $max Maximum steps (0 if unknown) - */ - public function __construct(OutputInterface $output, $max = 0) - { - if ($output instanceof ConsoleOutputInterface) { - $output = $output->getErrorOutput(); - } - - $this->output = $output; - $this->setMaxSteps($max); - $this->terminal = new Terminal(); - - if (!$this->output->isDecorated()) { - // disable overwrite when output does not support ANSI codes. - $this->overwrite = false; - - // set a reasonable redraw frequency so output isn't flooded - $this->setRedrawFrequency($max / 10); - } - - $this->startTime = time(); - } - - /** - * Sets a placeholder formatter for a given name. - * - * This method also allow you to override an existing placeholder. - * - * @param string $name The placeholder name (including the delimiter char like %) - * @param callable $callable A PHP callable - */ - public static function setPlaceholderFormatterDefinition($name, callable $callable) - { - if (!self::$formatters) { - self::$formatters = self::initPlaceholderFormatters(); - } - - self::$formatters[$name] = $callable; - } - - /** - * Gets the placeholder formatter for a given name. - * - * @param string $name The placeholder name (including the delimiter char like %) - * - * @return callable|null A PHP callable - */ - public static function getPlaceholderFormatterDefinition($name) - { - if (!self::$formatters) { - self::$formatters = self::initPlaceholderFormatters(); - } - - return isset(self::$formatters[$name]) ? self::$formatters[$name] : null; - } - - /** - * Sets a format for a given name. - * - * This method also allow you to override an existing format. - * - * @param string $name The format name - * @param string $format A format string - */ - public static function setFormatDefinition($name, $format) - { - if (!self::$formats) { - self::$formats = self::initFormats(); - } - - self::$formats[$name] = $format; - } - - /** - * Gets the format for a given name. - * - * @param string $name The format name - * - * @return string|null A format string - */ - public static function getFormatDefinition($name) - { - if (!self::$formats) { - self::$formats = self::initFormats(); - } - - return isset(self::$formats[$name]) ? self::$formats[$name] : null; - } - - /** - * Associates a text with a named placeholder. - * - * The text is displayed when the progress bar is rendered but only - * when the corresponding placeholder is part of the custom format line - * (by wrapping the name with %). - * - * @param string $message The text to associate with the placeholder - * @param string $name The name of the placeholder - */ - public function setMessage($message, $name = 'message') - { - $this->messages[$name] = $message; - } - - public function getMessage($name = 'message') - { - return $this->messages[$name]; - } - - /** - * Gets the progress bar start time. - * - * @return int The progress bar start time - */ - public function getStartTime() - { - return $this->startTime; - } - - /** - * Gets the progress bar maximal steps. - * - * @return int The progress bar max steps - */ - public function getMaxSteps() - { - return $this->max; - } - - /** - * Gets the current step position. - * - * @return int The progress bar step - */ - public function getProgress() - { - return $this->step; - } - - /** - * Gets the progress bar step width. - * - * @return int The progress bar step width - */ - private function getStepWidth() - { - return $this->stepWidth; - } - - /** - * Gets the current progress bar percent. - * - * @return float The current progress bar percent - */ - public function getProgressPercent() - { - return $this->percent; - } - - /** - * Sets the progress bar width. - * - * @param int $size The progress bar size - */ - public function setBarWidth($size) - { - $this->barWidth = max(1, (int) $size); - } - - /** - * Gets the progress bar width. - * - * @return int The progress bar size - */ - public function getBarWidth() - { - return $this->barWidth; - } - - /** - * Sets the bar character. - * - * @param string $char A character - */ - public function setBarCharacter($char) - { - $this->barChar = $char; - } - - /** - * Gets the bar character. - * - * @return string A character - */ - public function getBarCharacter() - { - if (null === $this->barChar) { - return $this->max ? '=' : $this->emptyBarChar; - } - - return $this->barChar; - } - - /** - * Sets the empty bar character. - * - * @param string $char A character - */ - public function setEmptyBarCharacter($char) - { - $this->emptyBarChar = $char; - } - - /** - * Gets the empty bar character. - * - * @return string A character - */ - public function getEmptyBarCharacter() - { - return $this->emptyBarChar; - } - - /** - * Sets the progress bar character. - * - * @param string $char A character - */ - public function setProgressCharacter($char) - { - $this->progressChar = $char; - } - - /** - * Gets the progress bar character. - * - * @return string A character - */ - public function getProgressCharacter() - { - return $this->progressChar; - } - - /** - * Sets the progress bar format. - * - * @param string $format The format - */ - public function setFormat($format) - { - $this->format = null; - $this->internalFormat = $format; - } - - /** - * Sets the redraw frequency. - * - * @param int|float $freq The frequency in steps - */ - public function setRedrawFrequency($freq) - { - $this->redrawFreq = max((int) $freq, 1); - } - - /** - * Starts the progress output. - * - * @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged - */ - public function start($max = null) - { - $this->startTime = time(); - $this->step = 0; - $this->percent = 0.0; - - if (null !== $max) { - $this->setMaxSteps($max); - } - - $this->display(); - } - - /** - * Advances the progress output X steps. - * - * @param int $step Number of steps to advance - */ - public function advance($step = 1) - { - $this->setProgress($this->step + $step); - } - - /** - * Sets whether to overwrite the progressbar, false for new line. - * - * @param bool $overwrite - */ - public function setOverwrite($overwrite) - { - $this->overwrite = (bool) $overwrite; - } - - /** - * Sets the current progress. - * - * @param int $step The current progress - */ - public function setProgress($step) - { - $step = (int) $step; - - if ($this->max && $step > $this->max) { - $this->max = $step; - } elseif ($step < 0) { - $step = 0; - } - - $prevPeriod = (int) ($this->step / $this->redrawFreq); - $currPeriod = (int) ($step / $this->redrawFreq); - $this->step = $step; - $this->percent = $this->max ? (float) $this->step / $this->max : 0; - if ($prevPeriod !== $currPeriod || $this->max === $step) { - $this->display(); - } - } - - /** - * Finishes the progress output. - */ - public function finish() - { - if (!$this->max) { - $this->max = $this->step; - } - - if ($this->step === $this->max && !$this->overwrite) { - // prevent double 100% output - return; - } - - $this->setProgress($this->max); - } - - /** - * Outputs the current progress string. - */ - public function display() - { - if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { - return; - } - - if (null === $this->format) { - $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); - } - - $this->overwrite($this->buildLine()); - } - - /** - * Removes the progress bar from the current line. - * - * This is useful if you wish to write some output - * while a progress bar is running. - * Call display() to show the progress bar again. - */ - public function clear() - { - if (!$this->overwrite) { - return; - } - - if (null === $this->format) { - $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); - } - - $this->overwrite(''); - } - - /** - * Sets the progress bar format. - * - * @param string $format The format - */ - private function setRealFormat($format) - { - // try to use the _nomax variant if available - if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) { - $this->format = self::getFormatDefinition($format.'_nomax'); - } elseif (null !== self::getFormatDefinition($format)) { - $this->format = self::getFormatDefinition($format); - } else { - $this->format = $format; - } - - $this->formatLineCount = substr_count($this->format, "\n"); - } - - /** - * Sets the progress bar maximal steps. - * - * @param int $max The progress bar max steps - */ - private function setMaxSteps($max) - { - $this->max = max(0, (int) $max); - $this->stepWidth = $this->max ? Helper::strlen($this->max) : 4; - } - - /** - * Overwrites a previous message to the output. - * - * @param string $message The message - */ - private function overwrite($message) - { - if ($this->overwrite) { - if (!$this->firstRun) { - // Move the cursor to the beginning of the line - $this->output->write("\x0D"); - - // Erase the line - $this->output->write("\x1B[2K"); - - // Erase previous lines - if ($this->formatLineCount > 0) { - $this->output->write(str_repeat("\x1B[1A\x1B[2K", $this->formatLineCount)); - } - } - } elseif ($this->step > 0) { - $this->output->writeln(''); - } - - $this->firstRun = false; - - $this->output->write($message); - } - - private function determineBestFormat() - { - switch ($this->output->getVerbosity()) { - // OutputInterface::VERBOSITY_QUIET: display is disabled anyway - case OutputInterface::VERBOSITY_VERBOSE: - return $this->max ? 'verbose' : 'verbose_nomax'; - case OutputInterface::VERBOSITY_VERY_VERBOSE: - return $this->max ? 'very_verbose' : 'very_verbose_nomax'; - case OutputInterface::VERBOSITY_DEBUG: - return $this->max ? 'debug' : 'debug_nomax'; - default: - return $this->max ? 'normal' : 'normal_nomax'; - } - } - - private static function initPlaceholderFormatters() - { - return array( - 'bar' => function (ProgressBar $bar, OutputInterface $output) { - $completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getProgress() % $bar->getBarWidth()); - $display = str_repeat($bar->getBarCharacter(), $completeBars); - if ($completeBars < $bar->getBarWidth()) { - $emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter()); - $display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars); - } - - return $display; - }, - 'elapsed' => function (ProgressBar $bar) { - return Helper::formatTime(time() - $bar->getStartTime()); - }, - 'remaining' => function (ProgressBar $bar) { - if (!$bar->getMaxSteps()) { - throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.'); - } - - if (!$bar->getProgress()) { - $remaining = 0; - } else { - $remaining = round((time() - $bar->getStartTime()) / $bar->getProgress() * ($bar->getMaxSteps() - $bar->getProgress())); - } - - return Helper::formatTime($remaining); - }, - 'estimated' => function (ProgressBar $bar) { - if (!$bar->getMaxSteps()) { - throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.'); - } - - if (!$bar->getProgress()) { - $estimated = 0; - } else { - $estimated = round((time() - $bar->getStartTime()) / $bar->getProgress() * $bar->getMaxSteps()); - } - - return Helper::formatTime($estimated); - }, - 'memory' => function (ProgressBar $bar) { - return Helper::formatMemory(memory_get_usage(true)); - }, - 'current' => function (ProgressBar $bar) { - return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', STR_PAD_LEFT); - }, - 'max' => function (ProgressBar $bar) { - return $bar->getMaxSteps(); - }, - 'percent' => function (ProgressBar $bar) { - return floor($bar->getProgressPercent() * 100); - }, - ); - } - - private static function initFormats() - { - return array( - 'normal' => ' %current%/%max% [%bar%] %percent:3s%%', - 'normal_nomax' => ' %current% [%bar%]', - - 'verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%', - 'verbose_nomax' => ' %current% [%bar%] %elapsed:6s%', - - 'very_verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%', - 'very_verbose_nomax' => ' %current% [%bar%] %elapsed:6s%', - - 'debug' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%', - 'debug_nomax' => ' %current% [%bar%] %elapsed:6s% %memory:6s%', - ); - } - - /** - * @return string - */ - private function buildLine() - { - $regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i"; - $callback = function ($matches) { - if ($formatter = $this::getPlaceholderFormatterDefinition($matches[1])) { - $text = call_user_func($formatter, $this, $this->output); - } elseif (isset($this->messages[$matches[1]])) { - $text = $this->messages[$matches[1]]; - } else { - return $matches[0]; - } - - if (isset($matches[2])) { - $text = sprintf('%'.$matches[2], $text); - } - - return $text; - }; - $line = preg_replace_callback($regex, $callback, $this->format); - - // gets string length for each sub line with multiline format - $linesLength = array_map(function ($subLine) { - return Helper::strlenWithoutDecoration($this->output->getFormatter(), rtrim($subLine, "\r")); - }, explode("\n", $line)); - - $linesWidth = max($linesLength); - - $terminalWidth = $this->terminal->getWidth(); - if ($linesWidth <= $terminalWidth) { - return $line; - } - - $this->setBarWidth($this->barWidth - $linesWidth + $terminalWidth); - - return preg_replace_callback($regex, $callback, $this->format); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Helper; - -use Symfony\Component\Console\Exception\InvalidArgumentException; -use Symfony\Component\Console\Exception\LogicException; -use Symfony\Component\Console\Output\OutputInterface; - -/** - * @author Kevin Bond - */ -class ProgressIndicator -{ - private $output; - private $startTime; - private $format; - private $message; - private $indicatorValues; - private $indicatorCurrent; - private $indicatorChangeInterval; - private $indicatorUpdateTime; - private $started = false; - - private static $formatters; - private static $formats; - - /** - * @param OutputInterface $output - * @param string|null $format Indicator format - * @param int $indicatorChangeInterval Change interval in milliseconds - * @param array|null $indicatorValues Animated indicator characters - */ - public function __construct(OutputInterface $output, $format = null, $indicatorChangeInterval = 100, $indicatorValues = null) - { - $this->output = $output; - - if (null === $format) { - $format = $this->determineBestFormat(); - } - - if (null === $indicatorValues) { - $indicatorValues = array('-', '\\', '|', '/'); - } - - $indicatorValues = array_values($indicatorValues); - - if (2 > count($indicatorValues)) { - throw new InvalidArgumentException('Must have at least 2 indicator value characters.'); - } - - $this->format = self::getFormatDefinition($format); - $this->indicatorChangeInterval = $indicatorChangeInterval; - $this->indicatorValues = $indicatorValues; - $this->startTime = time(); - } - - /** - * Sets the current indicator message. - * - * @param string|null $message - */ - public function setMessage($message) - { - $this->message = $message; - - $this->display(); - } - - /** - * Starts the indicator output. - * - * @param $message - */ - public function start($message) - { - if ($this->started) { - throw new LogicException('Progress indicator already started.'); - } - - $this->message = $message; - $this->started = true; - $this->startTime = time(); - $this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval; - $this->indicatorCurrent = 0; - - $this->display(); - } - - /** - * Advances the indicator. - */ - public function advance() - { - if (!$this->started) { - throw new LogicException('Progress indicator has not yet been started.'); - } - - if (!$this->output->isDecorated()) { - return; - } - - $currentTime = $this->getCurrentTimeInMilliseconds(); - - if ($currentTime < $this->indicatorUpdateTime) { - return; - } - - $this->indicatorUpdateTime = $currentTime + $this->indicatorChangeInterval; - ++$this->indicatorCurrent; - - $this->display(); - } - - /** - * Finish the indicator with message. - * - * @param $message - */ - public function finish($message) - { - if (!$this->started) { - throw new LogicException('Progress indicator has not yet been started.'); - } - - $this->message = $message; - $this->display(); - $this->output->writeln(''); - $this->started = false; - } - - /** - * Gets the format for a given name. - * - * @param string $name The format name - * - * @return string|null A format string - */ - public static function getFormatDefinition($name) - { - if (!self::$formats) { - self::$formats = self::initFormats(); - } - - return isset(self::$formats[$name]) ? self::$formats[$name] : null; - } - - /** - * Sets a placeholder formatter for a given name. - * - * This method also allow you to override an existing placeholder. - * - * @param string $name The placeholder name (including the delimiter char like %) - * @param callable $callable A PHP callable - */ - public static function setPlaceholderFormatterDefinition($name, $callable) - { - if (!self::$formatters) { - self::$formatters = self::initPlaceholderFormatters(); - } - - self::$formatters[$name] = $callable; - } - - /** - * Gets the placeholder formatter for a given name. - * - * @param string $name The placeholder name (including the delimiter char like %) - * - * @return callable|null A PHP callable - */ - public static function getPlaceholderFormatterDefinition($name) - { - if (!self::$formatters) { - self::$formatters = self::initPlaceholderFormatters(); - } - - return isset(self::$formatters[$name]) ? self::$formatters[$name] : null; - } - - private function display() - { - if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { - return; - } - - $self = $this; - - $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) use ($self) { - if ($formatter = $self::getPlaceholderFormatterDefinition($matches[1])) { - return call_user_func($formatter, $self); - } - - return $matches[0]; - }, $this->format)); - } - - private function determineBestFormat() - { - switch ($this->output->getVerbosity()) { - // OutputInterface::VERBOSITY_QUIET: display is disabled anyway - case OutputInterface::VERBOSITY_VERBOSE: - return $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi'; - case OutputInterface::VERBOSITY_VERY_VERBOSE: - case OutputInterface::VERBOSITY_DEBUG: - return $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi'; - default: - return $this->output->isDecorated() ? 'normal' : 'normal_no_ansi'; - } - } - - /** - * Overwrites a previous message to the output. - * - * @param string $message The message - */ - private function overwrite($message) - { - if ($this->output->isDecorated()) { - $this->output->write("\x0D\x1B[2K"); - $this->output->write($message); - } else { - $this->output->writeln($message); - } - } - - private function getCurrentTimeInMilliseconds() - { - return round(microtime(true) * 1000); - } - - private static function initPlaceholderFormatters() - { - return array( - 'indicator' => function (ProgressIndicator $indicator) { - return $indicator->indicatorValues[$indicator->indicatorCurrent % count($indicator->indicatorValues)]; - }, - 'message' => function (ProgressIndicator $indicator) { - return $indicator->message; - }, - 'elapsed' => function (ProgressIndicator $indicator) { - return Helper::formatTime(time() - $indicator->startTime); - }, - 'memory' => function () { - return Helper::formatMemory(memory_get_usage(true)); - }, - ); - } - - private static function initFormats() - { - return array( - 'normal' => ' %indicator% %message%', - 'normal_no_ansi' => ' %message%', - - 'verbose' => ' %indicator% %message% (%elapsed:6s%)', - 'verbose_no_ansi' => ' %message% (%elapsed:6s%)', - - 'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)', - 'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)', - ); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Helper; - -use Symfony\Component\Console\Exception\InvalidArgumentException; -use Symfony\Component\Console\Exception\RuntimeException; -use Symfony\Component\Console\Formatter\OutputFormatter; -use Symfony\Component\Console\Formatter\OutputFormatterStyle; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\StreamableInputInterface; -use Symfony\Component\Console\Output\ConsoleOutputInterface; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Question\Question; -use Symfony\Component\Console\Question\ChoiceQuestion; - -/** - * The QuestionHelper class provides helpers to interact with the user. - * - * @author Fabien Potencier - */ -class QuestionHelper extends Helper -{ - private $inputStream; - private static $shell; - private static $stty; - - /** - * Asks a question to the user. - * - * @return mixed The user answer - * - * @throws RuntimeException If there is no data to read in the input stream - */ - public function ask(InputInterface $input, OutputInterface $output, Question $question) - { - if ($output instanceof ConsoleOutputInterface) { - $output = $output->getErrorOutput(); - } - - if (!$input->isInteractive()) { - return $question->getDefault(); - } - - if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) { - $this->inputStream = $stream; - } - - if (!$question->getValidator()) { - return $this->doAsk($output, $question); - } - - $interviewer = function () use ($output, $question) { - return $this->doAsk($output, $question); - }; - - return $this->validateAttempts($interviewer, $output, $question); - } - - /** - * Sets the input stream to read from when interacting with the user. - * - * This is mainly useful for testing purpose. - * - * @deprecated since version 3.2, to be removed in 4.0. Use - * StreamableInputInterface::setStream() instead. - * - * @param resource $stream The input stream - * - * @throws InvalidArgumentException In case the stream is not a resource - */ - public function setInputStream($stream) - { - @trigger_error(sprintf('The %s() method is deprecated since version 3.2 and will be removed in 4.0. Use %s::setStream() instead.', __METHOD__, StreamableInputInterface::class), E_USER_DEPRECATED); - - if (!is_resource($stream)) { - throw new InvalidArgumentException('Input stream must be a valid resource.'); - } - - $this->inputStream = $stream; - } - - /** - * Returns the helper's input stream. - * - * @deprecated since version 3.2, to be removed in 4.0. Use - * StreamableInputInterface::getStream() instead. - * - * @return resource - */ - public function getInputStream() - { - if (0 === func_num_args() || func_get_arg(0)) { - @trigger_error(sprintf('The %s() method is deprecated since version 3.2 and will be removed in 4.0. Use %s::getStream() instead.', __METHOD__, StreamableInputInterface::class), E_USER_DEPRECATED); - } - - return $this->inputStream; - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'question'; - } - - /** - * Prevents usage of stty. - */ - public static function disableStty() - { - self::$stty = false; - } - - /** - * Asks the question to the user. - * - * @return bool|mixed|null|string - * - * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden - */ - private function doAsk(OutputInterface $output, Question $question) - { - $this->writePrompt($output, $question); - - $inputStream = $this->inputStream ?: STDIN; - $autocomplete = $question->getAutocompleterValues(); - - if (null === $autocomplete || !$this->hasSttyAvailable()) { - $ret = false; - if ($question->isHidden()) { - try { - $ret = trim($this->getHiddenResponse($output, $inputStream)); - } catch (RuntimeException $e) { - if (!$question->isHiddenFallback()) { - throw $e; - } - } - } - - if (false === $ret) { - $ret = fgets($inputStream, 4096); - if (false === $ret) { - throw new RuntimeException('Aborted'); - } - $ret = trim($ret); - } - } else { - $ret = trim($this->autocomplete($output, $question, $inputStream, is_array($autocomplete) ? $autocomplete : iterator_to_array($autocomplete, false))); - } - - $ret = strlen($ret) > 0 ? $ret : $question->getDefault(); - - if ($normalizer = $question->getNormalizer()) { - return $normalizer($ret); - } - - return $ret; - } - - /** - * Outputs the question prompt. - */ - protected function writePrompt(OutputInterface $output, Question $question) - { - $message = $question->getQuestion(); - - if ($question instanceof ChoiceQuestion) { - $maxWidth = max(array_map(array($this, 'strlen'), array_keys($question->getChoices()))); - - $messages = (array) $question->getQuestion(); - foreach ($question->getChoices() as $key => $value) { - $width = $maxWidth - $this->strlen($key); - $messages[] = ' ['.$key.str_repeat(' ', $width).'] '.$value; - } - - $output->writeln($messages); - - $message = $question->getPrompt(); - } - - $output->write($message); - } - - /** - * Outputs an error message. - */ - protected function writeError(OutputInterface $output, \Exception $error) - { - if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) { - $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'); - } else { - $message = ''.$error->getMessage().''; - } - - $output->writeln($message); - } - - /** - * Autocompletes a question. - * - * @param OutputInterface $output - * @param Question $question - * @param resource $inputStream - * @param array $autocomplete - * - * @return string - */ - private function autocomplete(OutputInterface $output, Question $question, $inputStream, array $autocomplete) - { - $ret = ''; - - $i = 0; - $ofs = -1; - $matches = $autocomplete; - $numMatches = count($matches); - - $sttyMode = shell_exec('stty -g'); - - // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead) - shell_exec('stty -icanon -echo'); - - // Add highlighted text style - $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white')); - - // Read a keypress - while (!feof($inputStream)) { - $c = fread($inputStream, 1); - - // Backspace Character - if ("\177" === $c) { - if (0 === $numMatches && 0 !== $i) { - --$i; - // Move cursor backwards - $output->write("\033[1D"); - } - - if (0 === $i) { - $ofs = -1; - $matches = $autocomplete; - $numMatches = count($matches); - } else { - $numMatches = 0; - } - - // Pop the last character off the end of our string - $ret = substr($ret, 0, $i); - } elseif ("\033" === $c) { - // Did we read an escape sequence? - $c .= fread($inputStream, 2); - - // A = Up Arrow. B = Down Arrow - if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) { - if ('A' === $c[2] && -1 === $ofs) { - $ofs = 0; - } - - if (0 === $numMatches) { - continue; - } - - $ofs += ('A' === $c[2]) ? -1 : 1; - $ofs = ($numMatches + $ofs) % $numMatches; - } - } elseif (ord($c) < 32) { - if ("\t" === $c || "\n" === $c) { - if ($numMatches > 0 && -1 !== $ofs) { - $ret = $matches[$ofs]; - // Echo out remaining chars for current match - $output->write(substr($ret, $i)); - $i = strlen($ret); - } - - if ("\n" === $c) { - $output->write($c); - break; - } - - $numMatches = 0; - } - - continue; - } else { - $output->write($c); - $ret .= $c; - ++$i; - - $numMatches = 0; - $ofs = 0; - - foreach ($autocomplete as $value) { - // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle) - if (0 === strpos($value, $ret) && $i !== strlen($value)) { - $matches[$numMatches++] = $value; - } - } - } - - // Erase characters from cursor to end of line - $output->write("\033[K"); - - if ($numMatches > 0 && -1 !== $ofs) { - // Save cursor position - $output->write("\0337"); - // Write highlighted text - $output->write(''.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $i)).''); - // Restore cursor position - $output->write("\0338"); - } - } - - // Reset stty so it behaves normally again - shell_exec(sprintf('stty %s', $sttyMode)); - - return $ret; - } - - /** - * Gets a hidden response from user. - * - * @param OutputInterface $output An Output instance - * @param resource $inputStream The handler resource - * - * @return string The answer - * - * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden - */ - private function getHiddenResponse(OutputInterface $output, $inputStream) - { - if ('\\' === DIRECTORY_SEPARATOR) { - $exe = __DIR__.'/../Resources/bin/hiddeninput.exe'; - - // handle code running from a phar - if ('phar:' === substr(__FILE__, 0, 5)) { - $tmpExe = sys_get_temp_dir().'/hiddeninput.exe'; - copy($exe, $tmpExe); - $exe = $tmpExe; - } - - $value = rtrim(shell_exec($exe)); - $output->writeln(''); - - if (isset($tmpExe)) { - unlink($tmpExe); - } - - return $value; - } - - if ($this->hasSttyAvailable()) { - $sttyMode = shell_exec('stty -g'); - - shell_exec('stty -echo'); - $value = fgets($inputStream, 4096); - shell_exec(sprintf('stty %s', $sttyMode)); - - if (false === $value) { - throw new RuntimeException('Aborted'); - } - - $value = trim($value); - $output->writeln(''); - - return $value; - } - - if (false !== $shell = $this->getShell()) { - $readCmd = 'csh' === $shell ? 'set mypassword = $<' : 'read -r mypassword'; - $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd); - $value = rtrim(shell_exec($command)); - $output->writeln(''); - - return $value; - } - - throw new RuntimeException('Unable to hide the response.'); - } - - /** - * Validates an attempt. - * - * @param callable $interviewer A callable that will ask for a question and return the result - * @param OutputInterface $output An Output instance - * @param Question $question A Question instance - * - * @return mixed The validated response - * - * @throws \Exception In case the max number of attempts has been reached and no valid response has been given - */ - private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question) - { - $error = null; - $attempts = $question->getMaxAttempts(); - while (null === $attempts || $attempts--) { - if (null !== $error) { - $this->writeError($output, $error); - } - - try { - return call_user_func($question->getValidator(), $interviewer()); - } catch (RuntimeException $e) { - throw $e; - } catch (\Exception $error) { - } - } - - throw $error; - } - - /** - * Returns a valid unix shell. - * - * @return string|bool The valid shell name, false in case no valid shell is found - */ - private function getShell() - { - if (null !== self::$shell) { - return self::$shell; - } - - self::$shell = false; - - if (file_exists('/usr/bin/env')) { - // handle other OSs with bash/zsh/ksh/csh if available to hide the answer - $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null"; - foreach (array('bash', 'zsh', 'ksh', 'csh') as $sh) { - if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) { - self::$shell = $sh; - break; - } - } - } - - return self::$shell; - } - - /** - * Returns whether Stty is available or not. - * - * @return bool - */ - private function hasSttyAvailable() - { - if (null !== self::$stty) { - return self::$stty; - } - - exec('stty 2>&1', $output, $exitcode); - - return self::$stty = 0 === $exitcode; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Helper; - -use Symfony\Component\Console\Exception\LogicException; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Question\ChoiceQuestion; -use Symfony\Component\Console\Question\ConfirmationQuestion; -use Symfony\Component\Console\Question\Question; -use Symfony\Component\Console\Style\SymfonyStyle; -use Symfony\Component\Console\Formatter\OutputFormatter; - -/** - * Symfony Style Guide compliant question helper. - * - * @author Kevin Bond - */ -class SymfonyQuestionHelper extends QuestionHelper -{ - /** - * {@inheritdoc} - */ - public function ask(InputInterface $input, OutputInterface $output, Question $question) - { - $validator = $question->getValidator(); - $question->setValidator(function ($value) use ($validator) { - if (null !== $validator) { - $value = $validator($value); - } else { - // make required - if (!is_array($value) && !is_bool($value) && 0 === strlen($value)) { - throw new LogicException('A value is required.'); - } - } - - return $value; - }); - - return parent::ask($input, $output, $question); - } - - /** - * {@inheritdoc} - */ - protected function writePrompt(OutputInterface $output, Question $question) - { - $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion()); - $default = $question->getDefault(); - - switch (true) { - case null === $default: - $text = sprintf(' %s:', $text); - - break; - - case $question instanceof ConfirmationQuestion: - $text = sprintf(' %s (yes/no) [%s]:', $text, $default ? 'yes' : 'no'); - - break; - - case $question instanceof ChoiceQuestion && $question->isMultiselect(): - $choices = $question->getChoices(); - $default = explode(',', $default); - - foreach ($default as $key => $value) { - $default[$key] = $choices[trim($value)]; - } - - $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape(implode(', ', $default))); - - break; - - case $question instanceof ChoiceQuestion: - $choices = $question->getChoices(); - $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape($choices[$default])); - - break; - - default: - $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape($default)); - } - - $output->writeln($text); - - if ($question instanceof ChoiceQuestion) { - $width = max(array_map('strlen', array_keys($question->getChoices()))); - - foreach ($question->getChoices() as $key => $value) { - $output->writeln(sprintf(" [%-${width}s] %s", $key, $value)); - } - } - - $output->write(' > '); - } - - /** - * {@inheritdoc} - */ - protected function writeError(OutputInterface $output, \Exception $error) - { - if ($output instanceof SymfonyStyle) { - $output->newLine(); - $output->error($error->getMessage()); - - return; - } - - parent::writeError($output, $error); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Helper; - -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Exception\InvalidArgumentException; - -/** - * Provides helpers to display a table. - * - * @author Fabien Potencier - * @author Саша Стаменковић - * @author Abdellatif Ait boudad - * @author Max Grigorian - */ -class Table -{ - /** - * Table headers. - */ - private $headers = array(); - - /** - * Table rows. - */ - private $rows = array(); - - /** - * Column widths cache. - */ - private $effectiveColumnWidths = array(); - - /** - * Number of columns cache. - * - * @var int - */ - private $numberOfColumns; - - /** - * @var OutputInterface - */ - private $output; - - /** - * @var TableStyle - */ - private $style; - - /** - * @var array - */ - private $columnStyles = array(); - - /** - * User set column widths. - * - * @var array - */ - private $columnWidths = array(); - - private static $styles; - - public function __construct(OutputInterface $output) - { - $this->output = $output; - - if (!self::$styles) { - self::$styles = self::initStyles(); - } - - $this->setStyle('default'); - } - - /** - * Sets a style definition. - * - * @param string $name The style name - * @param TableStyle $style A TableStyle instance - */ - public static function setStyleDefinition($name, TableStyle $style) - { - if (!self::$styles) { - self::$styles = self::initStyles(); - } - - self::$styles[$name] = $style; - } - - /** - * Gets a style definition by name. - * - * @param string $name The style name - * - * @return TableStyle - */ - public static function getStyleDefinition($name) - { - if (!self::$styles) { - self::$styles = self::initStyles(); - } - - if (isset(self::$styles[$name])) { - return self::$styles[$name]; - } - - throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); - } - - /** - * Sets table style. - * - * @param TableStyle|string $name The style name or a TableStyle instance - * - * @return $this - */ - public function setStyle($name) - { - $this->style = $this->resolveStyle($name); - - return $this; - } - - /** - * Gets the current table style. - * - * @return TableStyle - */ - public function getStyle() - { - return $this->style; - } - - /** - * Sets table column style. - * - * @param int $columnIndex Column index - * @param TableStyle|string $name The style name or a TableStyle instance - * - * @return $this - */ - public function setColumnStyle($columnIndex, $name) - { - $columnIndex = (int) $columnIndex; - - $this->columnStyles[$columnIndex] = $this->resolveStyle($name); - - return $this; - } - - /** - * Gets the current style for a column. - * - * If style was not set, it returns the global table style. - * - * @param int $columnIndex Column index - * - * @return TableStyle - */ - public function getColumnStyle($columnIndex) - { - if (isset($this->columnStyles[$columnIndex])) { - return $this->columnStyles[$columnIndex]; - } - - return $this->getStyle(); - } - - /** - * Sets the minimum width of a column. - * - * @param int $columnIndex Column index - * @param int $width Minimum column width in characters - * - * @return $this - */ - public function setColumnWidth($columnIndex, $width) - { - $this->columnWidths[(int) $columnIndex] = (int) $width; - - return $this; - } - - /** - * Sets the minimum width of all columns. - * - * @param array $widths - * - * @return $this - */ - public function setColumnWidths(array $widths) - { - $this->columnWidths = array(); - foreach ($widths as $index => $width) { - $this->setColumnWidth($index, $width); - } - - return $this; - } - - public function setHeaders(array $headers) - { - $headers = array_values($headers); - if (!empty($headers) && !is_array($headers[0])) { - $headers = array($headers); - } - - $this->headers = $headers; - - return $this; - } - - public function setRows(array $rows) - { - $this->rows = array(); - - return $this->addRows($rows); - } - - public function addRows(array $rows) - { - foreach ($rows as $row) { - $this->addRow($row); - } - - return $this; - } - - public function addRow($row) - { - if ($row instanceof TableSeparator) { - $this->rows[] = $row; - - return $this; - } - - if (!is_array($row)) { - throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.'); - } - - $this->rows[] = array_values($row); - - return $this; - } - - public function setRow($column, array $row) - { - $this->rows[$column] = $row; - - return $this; - } - - /** - * Renders table to output. - * - * Example: - * +---------------+-----------------------+------------------+ - * | ISBN | Title | Author | - * +---------------+-----------------------+------------------+ - * | 99921-58-10-7 | Divine Comedy | Dante Alighieri | - * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | - * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | - * +---------------+-----------------------+------------------+ - */ - public function render() - { - $this->calculateNumberOfColumns(); - $rows = $this->buildTableRows($this->rows); - $headers = $this->buildTableRows($this->headers); - - $this->calculateColumnsWidth(array_merge($headers, $rows)); - - $this->renderRowSeparator(); - if (!empty($headers)) { - foreach ($headers as $header) { - $this->renderRow($header, $this->style->getCellHeaderFormat()); - $this->renderRowSeparator(); - } - } - foreach ($rows as $row) { - if ($row instanceof TableSeparator) { - $this->renderRowSeparator(); - } else { - $this->renderRow($row, $this->style->getCellRowFormat()); - } - } - if (!empty($rows)) { - $this->renderRowSeparator(); - } - - $this->cleanup(); - } - - /** - * Renders horizontal header separator. - * - * Example: +-----+-----------+-------+ - */ - private function renderRowSeparator() - { - if (0 === $count = $this->numberOfColumns) { - return; - } - - if (!$this->style->getHorizontalBorderChar() && !$this->style->getCrossingChar()) { - return; - } - - $markup = $this->style->getCrossingChar(); - for ($column = 0; $column < $count; ++$column) { - $markup .= str_repeat($this->style->getHorizontalBorderChar(), $this->effectiveColumnWidths[$column]).$this->style->getCrossingChar(); - } - - $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup)); - } - - /** - * Renders vertical column separator. - */ - private function renderColumnSeparator() - { - return sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar()); - } - - /** - * Renders table row. - * - * Example: | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | - * - * @param array $row - * @param string $cellFormat - */ - private function renderRow(array $row, $cellFormat) - { - if (empty($row)) { - return; - } - - $rowContent = $this->renderColumnSeparator(); - foreach ($this->getRowColumns($row) as $column) { - $rowContent .= $this->renderCell($row, $column, $cellFormat); - $rowContent .= $this->renderColumnSeparator(); - } - $this->output->writeln($rowContent); - } - - /** - * Renders table cell with padding. - * - * @param array $row - * @param int $column - * @param string $cellFormat - */ - private function renderCell(array $row, $column, $cellFormat) - { - $cell = isset($row[$column]) ? $row[$column] : ''; - $width = $this->effectiveColumnWidths[$column]; - if ($cell instanceof TableCell && $cell->getColspan() > 1) { - // add the width of the following columns(numbers of colspan). - foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) { - $width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn]; - } - } - - // str_pad won't work properly with multi-byte strings, we need to fix the padding - if (false !== $encoding = mb_detect_encoding($cell, null, true)) { - $width += strlen($cell) - mb_strwidth($cell, $encoding); - } - - $style = $this->getColumnStyle($column); - - if ($cell instanceof TableSeparator) { - return sprintf($style->getBorderFormat(), str_repeat($style->getHorizontalBorderChar(), $width)); - } - - $width += Helper::strlen($cell) - Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); - $content = sprintf($style->getCellRowContentFormat(), $cell); - - return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $style->getPadType())); - } - - /** - * Calculate number of columns for this table. - */ - private function calculateNumberOfColumns() - { - if (null !== $this->numberOfColumns) { - return; - } - - $columns = array(0); - foreach (array_merge($this->headers, $this->rows) as $row) { - if ($row instanceof TableSeparator) { - continue; - } - - $columns[] = $this->getNumberOfColumns($row); - } - - $this->numberOfColumns = max($columns); - } - - private function buildTableRows($rows) - { - $unmergedRows = array(); - for ($rowKey = 0; $rowKey < count($rows); ++$rowKey) { - $rows = $this->fillNextRows($rows, $rowKey); - - // Remove any new line breaks and replace it with a new line - foreach ($rows[$rowKey] as $column => $cell) { - if (!strstr($cell, "\n")) { - continue; - } - $lines = explode("\n", str_replace("\n", "\n", $cell)); - foreach ($lines as $lineKey => $line) { - if ($cell instanceof TableCell) { - $line = new TableCell($line, array('colspan' => $cell->getColspan())); - } - if (0 === $lineKey) { - $rows[$rowKey][$column] = $line; - } else { - $unmergedRows[$rowKey][$lineKey][$column] = $line; - } - } - } - } - - $tableRows = array(); - foreach ($rows as $rowKey => $row) { - $tableRows[] = $this->fillCells($row); - if (isset($unmergedRows[$rowKey])) { - $tableRows = array_merge($tableRows, $unmergedRows[$rowKey]); - } - } - - return $tableRows; - } - - /** - * fill rows that contains rowspan > 1. - * - * @param array $rows - * @param int $line - * - * @return array - */ - private function fillNextRows(array $rows, $line) - { - $unmergedRows = array(); - foreach ($rows[$line] as $column => $cell) { - if ($cell instanceof TableCell && $cell->getRowspan() > 1) { - $nbLines = $cell->getRowspan() - 1; - $lines = array($cell); - if (strstr($cell, "\n")) { - $lines = explode("\n", str_replace("\n", "\n", $cell)); - $nbLines = count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines; - - $rows[$line][$column] = new TableCell($lines[0], array('colspan' => $cell->getColspan())); - unset($lines[0]); - } - - // create a two dimensional array (rowspan x colspan) - $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, array()), $unmergedRows); - foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { - $value = isset($lines[$unmergedRowKey - $line]) ? $lines[$unmergedRowKey - $line] : ''; - $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, array('colspan' => $cell->getColspan())); - if ($nbLines === $unmergedRowKey - $line) { - break; - } - } - } - } - - foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { - // we need to know if $unmergedRow will be merged or inserted into $rows - if (isset($rows[$unmergedRowKey]) && is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) { - foreach ($unmergedRow as $cellKey => $cell) { - // insert cell into row at cellKey position - array_splice($rows[$unmergedRowKey], $cellKey, 0, array($cell)); - } - } else { - $row = $this->copyRow($rows, $unmergedRowKey - 1); - foreach ($unmergedRow as $column => $cell) { - if (!empty($cell)) { - $row[$column] = $unmergedRow[$column]; - } - } - array_splice($rows, $unmergedRowKey, 0, array($row)); - } - } - - return $rows; - } - - /** - * fill cells for a row that contains colspan > 1. - * - * @return array - */ - private function fillCells($row) - { - $newRow = array(); - foreach ($row as $column => $cell) { - $newRow[] = $cell; - if ($cell instanceof TableCell && $cell->getColspan() > 1) { - foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) { - // insert empty value at column position - $newRow[] = ''; - } - } - } - - return $newRow ?: $row; - } - - /** - * @param array $rows - * @param int $line - * - * @return array - */ - private function copyRow(array $rows, $line) - { - $row = $rows[$line]; - foreach ($row as $cellKey => $cellValue) { - $row[$cellKey] = ''; - if ($cellValue instanceof TableCell) { - $row[$cellKey] = new TableCell('', array('colspan' => $cellValue->getColspan())); - } - } - - return $row; - } - - /** - * Gets number of columns by row. - * - * @return int - */ - private function getNumberOfColumns(array $row) - { - $columns = count($row); - foreach ($row as $column) { - $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0; - } - - return $columns; - } - - /** - * Gets list of columns for the given row. - * - * @return array - */ - private function getRowColumns(array $row) - { - $columns = range(0, $this->numberOfColumns - 1); - foreach ($row as $cellKey => $cell) { - if ($cell instanceof TableCell && $cell->getColspan() > 1) { - // exclude grouped columns. - $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1)); - } - } - - return $columns; - } - - /** - * Calculates columns widths. - */ - private function calculateColumnsWidth(array $rows) - { - for ($column = 0; $column < $this->numberOfColumns; ++$column) { - $lengths = array(); - foreach ($rows as $row) { - if ($row instanceof TableSeparator) { - continue; - } - - foreach ($row as $i => $cell) { - if ($cell instanceof TableCell) { - $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell); - $textLength = Helper::strlen($textContent); - if ($textLength > 0) { - $contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan())); - foreach ($contentColumns as $position => $content) { - $row[$i + $position] = $content; - } - } - } - } - - $lengths[] = $this->getCellWidth($row, $column); - } - - $this->effectiveColumnWidths[$column] = max($lengths) + strlen($this->style->getCellRowContentFormat()) - 2; - } - } - - /** - * Gets column width. - * - * @return int - */ - private function getColumnSeparatorWidth() - { - return strlen(sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar())); - } - - /** - * Gets cell width. - * - * @param array $row - * @param int $column - * - * @return int - */ - private function getCellWidth(array $row, $column) - { - $cellWidth = 0; - - if (isset($row[$column])) { - $cell = $row[$column]; - $cellWidth = Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); - } - - $columnWidth = isset($this->columnWidths[$column]) ? $this->columnWidths[$column] : 0; - - return max($cellWidth, $columnWidth); - } - - /** - * Called after rendering to cleanup cache data. - */ - private function cleanup() - { - $this->effectiveColumnWidths = array(); - $this->numberOfColumns = null; - } - - private static function initStyles() - { - $borderless = new TableStyle(); - $borderless - ->setHorizontalBorderChar('=') - ->setVerticalBorderChar(' ') - ->setCrossingChar(' ') - ; - - $compact = new TableStyle(); - $compact - ->setHorizontalBorderChar('') - ->setVerticalBorderChar(' ') - ->setCrossingChar('') - ->setCellRowContentFormat('%s') - ; - - $styleGuide = new TableStyle(); - $styleGuide - ->setHorizontalBorderChar('-') - ->setVerticalBorderChar(' ') - ->setCrossingChar(' ') - ->setCellHeaderFormat('%s') - ; - - return array( - 'default' => new TableStyle(), - 'borderless' => $borderless, - 'compact' => $compact, - 'symfony-style-guide' => $styleGuide, - ); - } - - private function resolveStyle($name) - { - if ($name instanceof TableStyle) { - return $name; - } - - if (isset(self::$styles[$name])) { - return self::$styles[$name]; - } - - throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Helper; - -use Symfony\Component\Console\Exception\InvalidArgumentException; - -/** - * @author Abdellatif Ait boudad - */ -class TableCell -{ - private $value; - private $options = array( - 'rowspan' => 1, - 'colspan' => 1, - ); - - /** - * @param string $value - * @param array $options - */ - public function __construct($value = '', array $options = array()) - { - if (is_numeric($value) && !is_string($value)) { - $value = (string) $value; - } - - $this->value = $value; - - // check option names - if ($diff = array_diff(array_keys($options), array_keys($this->options))) { - throw new InvalidArgumentException(sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff))); - } - - $this->options = array_merge($this->options, $options); - } - - /** - * Returns the cell value. - * - * @return string - */ - public function __toString() - { - return $this->value; - } - - /** - * Gets number of colspan. - * - * @return int - */ - public function getColspan() - { - return (int) $this->options['colspan']; - } - - /** - * Gets number of rowspan. - * - * @return int - */ - public function getRowspan() - { - return (int) $this->options['rowspan']; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Helper; - -/** - * Marks a row as being a separator. - * - * @author Fabien Potencier - */ -class TableSeparator extends TableCell -{ - public function __construct(array $options = array()) - { - parent::__construct('', $options); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Helper; - -use Symfony\Component\Console\Exception\InvalidArgumentException; -use Symfony\Component\Console\Exception\LogicException; - -/** - * Defines the styles for a Table. - * - * @author Fabien Potencier - * @author Саша Стаменковић - */ -class TableStyle -{ - private $paddingChar = ' '; - private $horizontalBorderChar = '-'; - private $verticalBorderChar = '|'; - private $crossingChar = '+'; - private $cellHeaderFormat = '%s'; - private $cellRowFormat = '%s'; - private $cellRowContentFormat = ' %s '; - private $borderFormat = '%s'; - private $padType = STR_PAD_RIGHT; - - /** - * Sets padding character, used for cell padding. - * - * @param string $paddingChar - * - * @return $this - */ - public function setPaddingChar($paddingChar) - { - if (!$paddingChar) { - throw new LogicException('The padding char must not be empty'); - } - - $this->paddingChar = $paddingChar; - - return $this; - } - - /** - * Gets padding character, used for cell padding. - * - * @return string - */ - public function getPaddingChar() - { - return $this->paddingChar; - } - - /** - * Sets horizontal border character. - * - * @param string $horizontalBorderChar - * - * @return $this - */ - public function setHorizontalBorderChar($horizontalBorderChar) - { - $this->horizontalBorderChar = $horizontalBorderChar; - - return $this; - } - - /** - * Gets horizontal border character. - * - * @return string - */ - public function getHorizontalBorderChar() - { - return $this->horizontalBorderChar; - } - - /** - * Sets vertical border character. - * - * @param string $verticalBorderChar - * - * @return $this - */ - public function setVerticalBorderChar($verticalBorderChar) - { - $this->verticalBorderChar = $verticalBorderChar; - - return $this; - } - - /** - * Gets vertical border character. - * - * @return string - */ - public function getVerticalBorderChar() - { - return $this->verticalBorderChar; - } - - /** - * Sets crossing character. - * - * @param string $crossingChar - * - * @return $this - */ - public function setCrossingChar($crossingChar) - { - $this->crossingChar = $crossingChar; - - return $this; - } - - /** - * Gets crossing character. - * - * @return string $crossingChar - */ - public function getCrossingChar() - { - return $this->crossingChar; - } - - /** - * Sets header cell format. - * - * @param string $cellHeaderFormat - * - * @return $this - */ - public function setCellHeaderFormat($cellHeaderFormat) - { - $this->cellHeaderFormat = $cellHeaderFormat; - - return $this; - } - - /** - * Gets header cell format. - * - * @return string - */ - public function getCellHeaderFormat() - { - return $this->cellHeaderFormat; - } - - /** - * Sets row cell format. - * - * @param string $cellRowFormat - * - * @return $this - */ - public function setCellRowFormat($cellRowFormat) - { - $this->cellRowFormat = $cellRowFormat; - - return $this; - } - - /** - * Gets row cell format. - * - * @return string - */ - public function getCellRowFormat() - { - return $this->cellRowFormat; - } - - /** - * Sets row cell content format. - * - * @param string $cellRowContentFormat - * - * @return $this - */ - public function setCellRowContentFormat($cellRowContentFormat) - { - $this->cellRowContentFormat = $cellRowContentFormat; - - return $this; - } - - /** - * Gets row cell content format. - * - * @return string - */ - public function getCellRowContentFormat() - { - return $this->cellRowContentFormat; - } - - /** - * Sets table border format. - * - * @param string $borderFormat - * - * @return $this - */ - public function setBorderFormat($borderFormat) - { - $this->borderFormat = $borderFormat; - - return $this; - } - - /** - * Gets table border format. - * - * @return string - */ - public function getBorderFormat() - { - return $this->borderFormat; - } - - /** - * Sets cell padding type. - * - * @param int $padType STR_PAD_* - * - * @return $this - */ - public function setPadType($padType) - { - if (!in_array($padType, array(STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH), true)) { - throw new InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).'); - } - - $this->padType = $padType; - - return $this; - } - - /** - * Gets cell padding type. - * - * @return int - */ - public function getPadType() - { - return $this->padType; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Input; - -use Symfony\Component\Console\Exception\RuntimeException; - -/** - * ArgvInput represents an input coming from the CLI arguments. - * - * Usage: - * - * $input = new ArgvInput(); - * - * By default, the `$_SERVER['argv']` array is used for the input values. - * - * This can be overridden by explicitly passing the input values in the constructor: - * - * $input = new ArgvInput($_SERVER['argv']); - * - * If you pass it yourself, don't forget that the first element of the array - * is the name of the running application. - * - * When passing an argument to the constructor, be sure that it respects - * the same rules as the argv one. It's almost always better to use the - * `StringInput` when you want to provide your own input. - * - * @author Fabien Potencier - * - * @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html - * @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02 - */ -class ArgvInput extends Input -{ - private $tokens; - private $parsed; - - /** - * @param array|null $argv An array of parameters from the CLI (in the argv format) - * @param InputDefinition|null $definition A InputDefinition instance - */ - public function __construct(array $argv = null, InputDefinition $definition = null) - { - if (null === $argv) { - $argv = $_SERVER['argv']; - } - - // strip the application name - array_shift($argv); - - $this->tokens = $argv; - - parent::__construct($definition); - } - - protected function setTokens(array $tokens) - { - $this->tokens = $tokens; - } - - /** - * {@inheritdoc} - */ - protected function parse() - { - $parseOptions = true; - $this->parsed = $this->tokens; - while (null !== $token = array_shift($this->parsed)) { - if ($parseOptions && '' == $token) { - $this->parseArgument($token); - } elseif ($parseOptions && '--' == $token) { - $parseOptions = false; - } elseif ($parseOptions && 0 === strpos($token, '--')) { - $this->parseLongOption($token); - } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) { - $this->parseShortOption($token); - } else { - $this->parseArgument($token); - } - } - } - - /** - * Parses a short option. - * - * @param string $token The current token - */ - private function parseShortOption($token) - { - $name = substr($token, 1); - - if (strlen($name) > 1) { - if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) { - // an option with a value (with no space) - $this->addShortOption($name[0], substr($name, 1)); - } else { - $this->parseShortOptionSet($name); - } - } else { - $this->addShortOption($name, null); - } - } - - /** - * Parses a short option set. - * - * @param string $name The current token - * - * @throws RuntimeException When option given doesn't exist - */ - private function parseShortOptionSet($name) - { - $len = strlen($name); - for ($i = 0; $i < $len; ++$i) { - if (!$this->definition->hasShortcut($name[$i])) { - throw new RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i])); - } - - $option = $this->definition->getOptionForShortcut($name[$i]); - if ($option->acceptValue()) { - $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1)); - - break; - } else { - $this->addLongOption($option->getName(), null); - } - } - } - - /** - * Parses a long option. - * - * @param string $token The current token - */ - private function parseLongOption($token) - { - $name = substr($token, 2); - - if (false !== $pos = strpos($name, '=')) { - if (0 === strlen($value = substr($name, $pos + 1))) { - // if no value after "=" then substr() returns "" since php7 only, false before - // see http://php.net/manual/fr/migration70.incompatible.php#119151 - if (\PHP_VERSION_ID < 70000 && false === $value) { - $value = ''; - } - array_unshift($this->parsed, $value); - } - $this->addLongOption(substr($name, 0, $pos), $value); - } else { - $this->addLongOption($name, null); - } - } - - /** - * Parses an argument. - * - * @param string $token The current token - * - * @throws RuntimeException When too many arguments are given - */ - private function parseArgument($token) - { - $c = count($this->arguments); - - // if input is expecting another argument, add it - if ($this->definition->hasArgument($c)) { - $arg = $this->definition->getArgument($c); - $this->arguments[$arg->getName()] = $arg->isArray() ? array($token) : $token; - - // if last argument isArray(), append token to last argument - } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { - $arg = $this->definition->getArgument($c - 1); - $this->arguments[$arg->getName()][] = $token; - - // unexpected argument - } else { - $all = $this->definition->getArguments(); - if (count($all)) { - throw new RuntimeException(sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all)))); - } - - throw new RuntimeException(sprintf('No arguments expected, got "%s".', $token)); - } - } - - /** - * Adds a short option value. - * - * @param string $shortcut The short option key - * @param mixed $value The value for the option - * - * @throws RuntimeException When option given doesn't exist - */ - private function addShortOption($shortcut, $value) - { - if (!$this->definition->hasShortcut($shortcut)) { - throw new RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); - } - - $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); - } - - /** - * Adds a long option value. - * - * @param string $name The long option key - * @param mixed $value The value for the option - * - * @throws RuntimeException When option given doesn't exist - */ - private function addLongOption($name, $value) - { - if (!$this->definition->hasOption($name)) { - throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name)); - } - - $option = $this->definition->getOption($name); - - if (null !== $value && !$option->acceptValue()) { - throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name)); - } - - if (in_array($value, array('', null), true) && $option->acceptValue() && count($this->parsed)) { - // if option accepts an optional or mandatory argument - // let's see if there is one provided - $next = array_shift($this->parsed); - if ((isset($next[0]) && '-' !== $next[0]) || in_array($next, array('', null), true)) { - $value = $next; - } else { - array_unshift($this->parsed, $next); - } - } - - if (null === $value) { - if ($option->isValueRequired()) { - throw new RuntimeException(sprintf('The "--%s" option requires a value.', $name)); - } - - if (!$option->isArray() && !$option->isValueOptional()) { - $value = true; - } - } - - if ($option->isArray()) { - $this->options[$name][] = $value; - } else { - $this->options[$name] = $value; - } - } - - /** - * {@inheritdoc} - */ - public function getFirstArgument() - { - foreach ($this->tokens as $token) { - if ($token && '-' === $token[0]) { - continue; - } - - return $token; - } - } - - /** - * {@inheritdoc} - */ - public function hasParameterOption($values, $onlyParams = false) - { - $values = (array) $values; - - foreach ($this->tokens as $token) { - if ($onlyParams && '--' === $token) { - return false; - } - foreach ($values as $value) { - if ($token === $value || 0 === strpos($token, $value.'=')) { - return true; - } - } - } - - return false; - } - - /** - * {@inheritdoc} - */ - public function getParameterOption($values, $default = false, $onlyParams = false) - { - $values = (array) $values; - $tokens = $this->tokens; - - while (0 < count($tokens)) { - $token = array_shift($tokens); - if ($onlyParams && '--' === $token) { - return false; - } - - foreach ($values as $value) { - if ($token === $value || 0 === strpos($token, $value.'=')) { - if (false !== $pos = strpos($token, '=')) { - return substr($token, $pos + 1); - } - - return array_shift($tokens); - } - } - } - - return $default; - } - - /** - * Returns a stringified representation of the args passed to the command. - * - * @return string - */ - public function __toString() - { - $tokens = array_map(function ($token) { - if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { - return $match[1].$this->escapeToken($match[2]); - } - - if ($token && '-' !== $token[0]) { - return $this->escapeToken($token); - } - - return $token; - }, $this->tokens); - - return implode(' ', $tokens); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Input; - -use Symfony\Component\Console\Exception\InvalidArgumentException; -use Symfony\Component\Console\Exception\InvalidOptionException; - -/** - * ArrayInput represents an input provided as an array. - * - * Usage: - * - * $input = new ArrayInput(array('name' => 'foo', '--bar' => 'foobar')); - * - * @author Fabien Potencier - */ -class ArrayInput extends Input -{ - private $parameters; - - public function __construct(array $parameters, InputDefinition $definition = null) - { - $this->parameters = $parameters; - - parent::__construct($definition); - } - - /** - * {@inheritdoc} - */ - public function getFirstArgument() - { - foreach ($this->parameters as $key => $value) { - if ($key && '-' === $key[0]) { - continue; - } - - return $value; - } - } - - /** - * {@inheritdoc} - */ - public function hasParameterOption($values, $onlyParams = false) - { - $values = (array) $values; - - foreach ($this->parameters as $k => $v) { - if (!is_int($k)) { - $v = $k; - } - - if ($onlyParams && '--' === $v) { - return false; - } - - if (in_array($v, $values)) { - return true; - } - } - - return false; - } - - /** - * {@inheritdoc} - */ - public function getParameterOption($values, $default = false, $onlyParams = false) - { - $values = (array) $values; - - foreach ($this->parameters as $k => $v) { - if ($onlyParams && ('--' === $k || (is_int($k) && '--' === $v))) { - return false; - } - - if (is_int($k)) { - if (in_array($v, $values)) { - return true; - } - } elseif (in_array($k, $values)) { - return $v; - } - } - - return $default; - } - - /** - * Returns a stringified representation of the args passed to the command. - * - * @return string - */ - public function __toString() - { - $params = array(); - foreach ($this->parameters as $param => $val) { - if ($param && '-' === $param[0]) { - if (is_array($val)) { - foreach ($val as $v) { - $params[] = $param.('' != $v ? '='.$this->escapeToken($v) : ''); - } - } else { - $params[] = $param.('' != $val ? '='.$this->escapeToken($val) : ''); - } - } else { - $params[] = is_array($val) ? array_map(array($this, 'escapeToken'), $val) : $this->escapeToken($val); - } - } - - return implode(' ', $params); - } - - /** - * {@inheritdoc} - */ - protected function parse() - { - foreach ($this->parameters as $key => $value) { - if ('--' === $key) { - return; - } - if (0 === strpos($key, '--')) { - $this->addLongOption(substr($key, 2), $value); - } elseif ('-' === $key[0]) { - $this->addShortOption(substr($key, 1), $value); - } else { - $this->addArgument($key, $value); - } - } - } - - /** - * Adds a short option value. - * - * @param string $shortcut The short option key - * @param mixed $value The value for the option - * - * @throws InvalidOptionException When option given doesn't exist - */ - private function addShortOption($shortcut, $value) - { - if (!$this->definition->hasShortcut($shortcut)) { - throw new InvalidOptionException(sprintf('The "-%s" option does not exist.', $shortcut)); - } - - $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); - } - - /** - * Adds a long option value. - * - * @param string $name The long option key - * @param mixed $value The value for the option - * - * @throws InvalidOptionException When option given doesn't exist - * @throws InvalidOptionException When a required value is missing - */ - private function addLongOption($name, $value) - { - if (!$this->definition->hasOption($name)) { - throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name)); - } - - $option = $this->definition->getOption($name); - - if (null === $value) { - if ($option->isValueRequired()) { - throw new InvalidOptionException(sprintf('The "--%s" option requires a value.', $name)); - } - - if (!$option->isValueOptional()) { - $value = true; - } - } - - $this->options[$name] = $value; - } - - /** - * Adds an argument value. - * - * @param string $name The argument name - * @param mixed $value The value for the argument - * - * @throws InvalidArgumentException When argument given doesn't exist - */ - private function addArgument($name, $value) - { - if (!$this->definition->hasArgument($name)) { - throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); - } - - $this->arguments[$name] = $value; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Input; - -use Symfony\Component\Console\Exception\InvalidArgumentException; -use Symfony\Component\Console\Exception\RuntimeException; - -/** - * Input is the base class for all concrete Input classes. - * - * Three concrete classes are provided by default: - * - * * `ArgvInput`: The input comes from the CLI arguments (argv) - * * `StringInput`: The input is provided as a string - * * `ArrayInput`: The input is provided as an array - * - * @author Fabien Potencier - */ -abstract class Input implements InputInterface, StreamableInputInterface -{ - protected $definition; - protected $stream; - protected $options = array(); - protected $arguments = array(); - protected $interactive = true; - - public function __construct(InputDefinition $definition = null) - { - if (null === $definition) { - $this->definition = new InputDefinition(); - } else { - $this->bind($definition); - $this->validate(); - } - } - - /** - * {@inheritdoc} - */ - public function bind(InputDefinition $definition) - { - $this->arguments = array(); - $this->options = array(); - $this->definition = $definition; - - $this->parse(); - } - - /** - * Processes command line arguments. - */ - abstract protected function parse(); - - /** - * {@inheritdoc} - */ - public function validate() - { - $definition = $this->definition; - $givenArguments = $this->arguments; - - $missingArguments = array_filter(array_keys($definition->getArguments()), function ($argument) use ($definition, $givenArguments) { - return !array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired(); - }); - - if (count($missingArguments) > 0) { - throw new RuntimeException(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments))); - } - } - - /** - * {@inheritdoc} - */ - public function isInteractive() - { - return $this->interactive; - } - - /** - * {@inheritdoc} - */ - public function setInteractive($interactive) - { - $this->interactive = (bool) $interactive; - } - - /** - * {@inheritdoc} - */ - public function getArguments() - { - return array_merge($this->definition->getArgumentDefaults(), $this->arguments); - } - - /** - * {@inheritdoc} - */ - public function getArgument($name) - { - if (!$this->definition->hasArgument($name)) { - throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); - } - - return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)->getDefault(); - } - - /** - * {@inheritdoc} - */ - public function setArgument($name, $value) - { - if (!$this->definition->hasArgument($name)) { - throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); - } - - $this->arguments[$name] = $value; - } - - /** - * {@inheritdoc} - */ - public function hasArgument($name) - { - return $this->definition->hasArgument($name); - } - - /** - * {@inheritdoc} - */ - public function getOptions() - { - return array_merge($this->definition->getOptionDefaults(), $this->options); - } - - /** - * {@inheritdoc} - */ - public function getOption($name) - { - if (!$this->definition->hasOption($name)) { - throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); - } - - return array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); - } - - /** - * {@inheritdoc} - */ - public function setOption($name, $value) - { - if (!$this->definition->hasOption($name)) { - throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); - } - - $this->options[$name] = $value; - } - - /** - * {@inheritdoc} - */ - public function hasOption($name) - { - return $this->definition->hasOption($name); - } - - /** - * Escapes a token through escapeshellarg if it contains unsafe chars. - * - * @param string $token - * - * @return string - */ - public function escapeToken($token) - { - return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); - } - - /** - * {@inheritdoc} - */ - public function setStream($stream) - { - $this->stream = $stream; - } - - /** - * {@inheritdoc} - */ - public function getStream() - { - return $this->stream; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Input; - -use Symfony\Component\Console\Exception\InvalidArgumentException; -use Symfony\Component\Console\Exception\LogicException; - -/** - * Represents a command line argument. - * - * @author Fabien Potencier - */ -class InputArgument -{ - const REQUIRED = 1; - const OPTIONAL = 2; - const IS_ARRAY = 4; - - private $name; - private $mode; - private $default; - private $description; - - /** - * @param string $name The argument name - * @param int $mode The argument mode: self::REQUIRED or self::OPTIONAL - * @param string $description A description text - * @param mixed $default The default value (for self::OPTIONAL mode only) - * - * @throws InvalidArgumentException When argument mode is not valid - */ - public function __construct($name, $mode = null, $description = '', $default = null) - { - if (null === $mode) { - $mode = self::OPTIONAL; - } elseif (!is_int($mode) || $mode > 7 || $mode < 1) { - throw new InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); - } - - $this->name = $name; - $this->mode = $mode; - $this->description = $description; - - $this->setDefault($default); - } - - /** - * Returns the argument name. - * - * @return string The argument name - */ - public function getName() - { - return $this->name; - } - - /** - * Returns true if the argument is required. - * - * @return bool true if parameter mode is self::REQUIRED, false otherwise - */ - public function isRequired() - { - return self::REQUIRED === (self::REQUIRED & $this->mode); - } - - /** - * Returns true if the argument can take multiple values. - * - * @return bool true if mode is self::IS_ARRAY, false otherwise - */ - public function isArray() - { - return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); - } - - /** - * Sets the default value. - * - * @param mixed $default The default value - * - * @throws LogicException When incorrect default value is given - */ - public function setDefault($default = null) - { - if (self::REQUIRED === $this->mode && null !== $default) { - throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.'); - } - - if ($this->isArray()) { - if (null === $default) { - $default = array(); - } elseif (!is_array($default)) { - throw new LogicException('A default value for an array argument must be an array.'); - } - } - - $this->default = $default; - } - - /** - * Returns the default value. - * - * @return mixed The default value - */ - public function getDefault() - { - return $this->default; - } - - /** - * Returns the description text. - * - * @return string The description text - */ - public function getDescription() - { - return $this->description; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Input; - -/** - * InputAwareInterface should be implemented by classes that depends on the - * Console Input. - * - * @author Wouter J - */ -interface InputAwareInterface -{ - /** - * Sets the Console Input. - * - * @param InputInterface - */ - public function setInput(InputInterface $input); -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Input; - -use Symfony\Component\Console\Exception\InvalidArgumentException; -use Symfony\Component\Console\Exception\LogicException; - -/** - * A InputDefinition represents a set of valid command line arguments and options. - * - * Usage: - * - * $definition = new InputDefinition(array( - * new InputArgument('name', InputArgument::REQUIRED), - * new InputOption('foo', 'f', InputOption::VALUE_REQUIRED), - * )); - * - * @author Fabien Potencier - */ -class InputDefinition -{ - private $arguments; - private $requiredCount; - private $hasAnArrayArgument = false; - private $hasOptional; - private $options; - private $shortcuts; - - /** - * @param array $definition An array of InputArgument and InputOption instance - */ - public function __construct(array $definition = array()) - { - $this->setDefinition($definition); - } - - /** - * Sets the definition of the input. - */ - public function setDefinition(array $definition) - { - $arguments = array(); - $options = array(); - foreach ($definition as $item) { - if ($item instanceof InputOption) { - $options[] = $item; - } else { - $arguments[] = $item; - } - } - - $this->setArguments($arguments); - $this->setOptions($options); - } - - /** - * Sets the InputArgument objects. - * - * @param InputArgument[] $arguments An array of InputArgument objects - */ - public function setArguments($arguments = array()) - { - $this->arguments = array(); - $this->requiredCount = 0; - $this->hasOptional = false; - $this->hasAnArrayArgument = false; - $this->addArguments($arguments); - } - - /** - * Adds an array of InputArgument objects. - * - * @param InputArgument[] $arguments An array of InputArgument objects - */ - public function addArguments($arguments = array()) - { - if (null !== $arguments) { - foreach ($arguments as $argument) { - $this->addArgument($argument); - } - } - } - - /** - * @throws LogicException When incorrect argument is given - */ - public function addArgument(InputArgument $argument) - { - if (isset($this->arguments[$argument->getName()])) { - throw new LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName())); - } - - if ($this->hasAnArrayArgument) { - throw new LogicException('Cannot add an argument after an array argument.'); - } - - if ($argument->isRequired() && $this->hasOptional) { - throw new LogicException('Cannot add a required argument after an optional one.'); - } - - if ($argument->isArray()) { - $this->hasAnArrayArgument = true; - } - - if ($argument->isRequired()) { - ++$this->requiredCount; - } else { - $this->hasOptional = true; - } - - $this->arguments[$argument->getName()] = $argument; - } - - /** - * Returns an InputArgument by name or by position. - * - * @param string|int $name The InputArgument name or position - * - * @return InputArgument An InputArgument object - * - * @throws InvalidArgumentException When argument given doesn't exist - */ - public function getArgument($name) - { - if (!$this->hasArgument($name)) { - throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); - } - - $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; - - return $arguments[$name]; - } - - /** - * Returns true if an InputArgument object exists by name or position. - * - * @param string|int $name The InputArgument name or position - * - * @return bool true if the InputArgument object exists, false otherwise - */ - public function hasArgument($name) - { - $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; - - return isset($arguments[$name]); - } - - /** - * Gets the array of InputArgument objects. - * - * @return InputArgument[] An array of InputArgument objects - */ - public function getArguments() - { - return $this->arguments; - } - - /** - * Returns the number of InputArguments. - * - * @return int The number of InputArguments - */ - public function getArgumentCount() - { - return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments); - } - - /** - * Returns the number of required InputArguments. - * - * @return int The number of required InputArguments - */ - public function getArgumentRequiredCount() - { - return $this->requiredCount; - } - - /** - * Gets the default values. - * - * @return array An array of default values - */ - public function getArgumentDefaults() - { - $values = array(); - foreach ($this->arguments as $argument) { - $values[$argument->getName()] = $argument->getDefault(); - } - - return $values; - } - - /** - * Sets the InputOption objects. - * - * @param InputOption[] $options An array of InputOption objects - */ - public function setOptions($options = array()) - { - $this->options = array(); - $this->shortcuts = array(); - $this->addOptions($options); - } - - /** - * Adds an array of InputOption objects. - * - * @param InputOption[] $options An array of InputOption objects - */ - public function addOptions($options = array()) - { - foreach ($options as $option) { - $this->addOption($option); - } - } - - /** - * @throws LogicException When option given already exist - */ - public function addOption(InputOption $option) - { - if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { - throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName())); - } - - if ($option->getShortcut()) { - foreach (explode('|', $option->getShortcut()) as $shortcut) { - if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) { - throw new LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut)); - } - } - } - - $this->options[$option->getName()] = $option; - if ($option->getShortcut()) { - foreach (explode('|', $option->getShortcut()) as $shortcut) { - $this->shortcuts[$shortcut] = $option->getName(); - } - } - } - - /** - * Returns an InputOption by name. - * - * @param string $name The InputOption name - * - * @return InputOption A InputOption object - * - * @throws InvalidArgumentException When option given doesn't exist - */ - public function getOption($name) - { - if (!$this->hasOption($name)) { - throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); - } - - return $this->options[$name]; - } - - /** - * Returns true if an InputOption object exists by name. - * - * This method can't be used to check if the user included the option when - * executing the command (use getOption() instead). - * - * @param string $name The InputOption name - * - * @return bool true if the InputOption object exists, false otherwise - */ - public function hasOption($name) - { - return isset($this->options[$name]); - } - - /** - * Gets the array of InputOption objects. - * - * @return InputOption[] An array of InputOption objects - */ - public function getOptions() - { - return $this->options; - } - - /** - * Returns true if an InputOption object exists by shortcut. - * - * @param string $name The InputOption shortcut - * - * @return bool true if the InputOption object exists, false otherwise - */ - public function hasShortcut($name) - { - return isset($this->shortcuts[$name]); - } - - /** - * Gets an InputOption by shortcut. - * - * @param string $shortcut The Shortcut name - * - * @return InputOption An InputOption object - */ - public function getOptionForShortcut($shortcut) - { - return $this->getOption($this->shortcutToName($shortcut)); - } - - /** - * Gets an array of default values. - * - * @return array An array of all default values - */ - public function getOptionDefaults() - { - $values = array(); - foreach ($this->options as $option) { - $values[$option->getName()] = $option->getDefault(); - } - - return $values; - } - - /** - * Returns the InputOption name given a shortcut. - * - * @param string $shortcut The shortcut - * - * @return string The InputOption name - * - * @throws InvalidArgumentException When option given does not exist - */ - private function shortcutToName($shortcut) - { - if (!isset($this->shortcuts[$shortcut])) { - throw new InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); - } - - return $this->shortcuts[$shortcut]; - } - - /** - * Gets the synopsis. - * - * @param bool $short Whether to return the short version (with options folded) or not - * - * @return string The synopsis - */ - public function getSynopsis($short = false) - { - $elements = array(); - - if ($short && $this->getOptions()) { - $elements[] = '[options]'; - } elseif (!$short) { - foreach ($this->getOptions() as $option) { - $value = ''; - if ($option->acceptValue()) { - $value = sprintf( - ' %s%s%s', - $option->isValueOptional() ? '[' : '', - strtoupper($option->getName()), - $option->isValueOptional() ? ']' : '' - ); - } - - $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; - $elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value); - } - } - - if (count($elements) && $this->getArguments()) { - $elements[] = '[--]'; - } - - foreach ($this->getArguments() as $argument) { - $element = '<'.$argument->getName().'>'; - if (!$argument->isRequired()) { - $element = '['.$element.']'; - } elseif ($argument->isArray()) { - $element = $element.' ('.$element.')'; - } - - if ($argument->isArray()) { - $element .= '...'; - } - - $elements[] = $element; - } - - return implode(' ', $elements); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Input; - -use Symfony\Component\Console\Exception\InvalidArgumentException; -use Symfony\Component\Console\Exception\RuntimeException; - -/** - * InputInterface is the interface implemented by all input classes. - * - * @author Fabien Potencier - */ -interface InputInterface -{ - /** - * Returns the first argument from the raw parameters (not parsed). - * - * @return string The value of the first argument or null otherwise - */ - public function getFirstArgument(); - - /** - * Returns true if the raw parameters (not parsed) contain a value. - * - * This method is to be used to introspect the input parameters - * before they have been validated. It must be used carefully. - * - * @param string|array $values The values to look for in the raw parameters (can be an array) - * @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal - * - * @return bool true if the value is contained in the raw parameters - */ - public function hasParameterOption($values, $onlyParams = false); - - /** - * Returns the value of a raw option (not parsed). - * - * This method is to be used to introspect the input parameters - * before they have been validated. It must be used carefully. - * - * @param string|array $values The value(s) to look for in the raw parameters (can be an array) - * @param mixed $default The default value to return if no result is found - * @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal - * - * @return mixed The option value - */ - public function getParameterOption($values, $default = false, $onlyParams = false); - - /** - * Binds the current Input instance with the given arguments and options. - */ - public function bind(InputDefinition $definition); - - /** - * Validates the input. - * - * @throws RuntimeException When not enough arguments are given - */ - public function validate(); - - /** - * Returns all the given arguments merged with the default values. - * - * @return array - */ - public function getArguments(); - - /** - * Returns the argument value for a given argument name. - * - * @param string $name The argument name - * - * @return mixed The argument value - * - * @throws InvalidArgumentException When argument given doesn't exist - */ - public function getArgument($name); - - /** - * Sets an argument value by name. - * - * @param string $name The argument name - * @param string $value The argument value - * - * @throws InvalidArgumentException When argument given doesn't exist - */ - public function setArgument($name, $value); - - /** - * Returns true if an InputArgument object exists by name or position. - * - * @param string|int $name The InputArgument name or position - * - * @return bool true if the InputArgument object exists, false otherwise - */ - public function hasArgument($name); - - /** - * Returns all the given options merged with the default values. - * - * @return array - */ - public function getOptions(); - - /** - * Returns the option value for a given option name. - * - * @param string $name The option name - * - * @return mixed The option value - * - * @throws InvalidArgumentException When option given doesn't exist - */ - public function getOption($name); - - /** - * Sets an option value by name. - * - * @param string $name The option name - * @param string|bool $value The option value - * - * @throws InvalidArgumentException When option given doesn't exist - */ - public function setOption($name, $value); - - /** - * Returns true if an InputOption object exists by name. - * - * @param string $name The InputOption name - * - * @return bool true if the InputOption object exists, false otherwise - */ - public function hasOption($name); - - /** - * Is this input means interactive? - * - * @return bool - */ - public function isInteractive(); - - /** - * Sets the input interactivity. - * - * @param bool $interactive If the input should be interactive - */ - public function setInteractive($interactive); -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Input; - -use Symfony\Component\Console\Exception\InvalidArgumentException; -use Symfony\Component\Console\Exception\LogicException; - -/** - * Represents a command line option. - * - * @author Fabien Potencier - */ -class InputOption -{ - const VALUE_NONE = 1; - const VALUE_REQUIRED = 2; - const VALUE_OPTIONAL = 4; - const VALUE_IS_ARRAY = 8; - - private $name; - private $shortcut; - private $mode; - private $default; - private $description; - - /** - * @param string $name The option name - * @param string|array $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts - * @param int $mode The option mode: One of the VALUE_* constants - * @param string $description A description text - * @param mixed $default The default value (must be null for self::VALUE_NONE) - * - * @throws InvalidArgumentException If option mode is invalid or incompatible - */ - public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null) - { - if (0 === strpos($name, '--')) { - $name = substr($name, 2); - } - - if (empty($name)) { - throw new InvalidArgumentException('An option name cannot be empty.'); - } - - if (empty($shortcut)) { - $shortcut = null; - } - - if (null !== $shortcut) { - if (is_array($shortcut)) { - $shortcut = implode('|', $shortcut); - } - $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); - $shortcuts = array_filter($shortcuts); - $shortcut = implode('|', $shortcuts); - - if (empty($shortcut)) { - throw new InvalidArgumentException('An option shortcut cannot be empty.'); - } - } - - if (null === $mode) { - $mode = self::VALUE_NONE; - } elseif (!is_int($mode) || $mode > 15 || $mode < 1) { - throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); - } - - $this->name = $name; - $this->shortcut = $shortcut; - $this->mode = $mode; - $this->description = $description; - - if ($this->isArray() && !$this->acceptValue()) { - throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); - } - - $this->setDefault($default); - } - - /** - * Returns the option shortcut. - * - * @return string The shortcut - */ - public function getShortcut() - { - return $this->shortcut; - } - - /** - * Returns the option name. - * - * @return string The name - */ - public function getName() - { - return $this->name; - } - - /** - * Returns true if the option accepts a value. - * - * @return bool true if value mode is not self::VALUE_NONE, false otherwise - */ - public function acceptValue() - { - return $this->isValueRequired() || $this->isValueOptional(); - } - - /** - * Returns true if the option requires a value. - * - * @return bool true if value mode is self::VALUE_REQUIRED, false otherwise - */ - public function isValueRequired() - { - return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); - } - - /** - * Returns true if the option takes an optional value. - * - * @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise - */ - public function isValueOptional() - { - return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); - } - - /** - * Returns true if the option can take multiple values. - * - * @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise - */ - public function isArray() - { - return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); - } - - /** - * Sets the default value. - * - * @param mixed $default The default value - * - * @throws LogicException When incorrect default value is given - */ - public function setDefault($default = null) - { - if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { - throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); - } - - if ($this->isArray()) { - if (null === $default) { - $default = array(); - } elseif (!is_array($default)) { - throw new LogicException('A default value for an array option must be an array.'); - } - } - - $this->default = $this->acceptValue() ? $default : false; - } - - /** - * Returns the default value. - * - * @return mixed The default value - */ - public function getDefault() - { - return $this->default; - } - - /** - * Returns the description text. - * - * @return string The description text - */ - public function getDescription() - { - return $this->description; - } - - /** - * Checks whether the given option equals this one. - * - * @return bool - */ - public function equals(InputOption $option) - { - return $option->getName() === $this->getName() - && $option->getShortcut() === $this->getShortcut() - && $option->getDefault() === $this->getDefault() - && $option->isArray() === $this->isArray() - && $option->isValueRequired() === $this->isValueRequired() - && $option->isValueOptional() === $this->isValueOptional() - ; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Input; - -/** - * StreamableInputInterface is the interface implemented by all input classes - * that have an input stream. - * - * @author Robin Chalas - */ -interface StreamableInputInterface extends InputInterface -{ - /** - * Sets the input stream to read from when interacting with the user. - * - * This is mainly useful for testing purpose. - * - * @param resource $stream The input stream - */ - public function setStream($stream); - - /** - * Returns the input stream. - * - * @return resource|null - */ - public function getStream(); -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Input; - -use Symfony\Component\Console\Exception\InvalidArgumentException; - -/** - * StringInput represents an input provided as a string. - * - * Usage: - * - * $input = new StringInput('foo --bar="foobar"'); - * - * @author Fabien Potencier - */ -class StringInput extends ArgvInput -{ - const REGEX_STRING = '([^\s]+?)(?:\s|(?setTokens($this->tokenize($input)); - } - - /** - * Tokenizes a string. - * - * @param string $input The input to tokenize - * - * @return array An array of tokens - * - * @throws InvalidArgumentException When unable to parse input (should never happen) - */ - private function tokenize($input) - { - $tokens = array(); - $length = strlen($input); - $cursor = 0; - while ($cursor < $length) { - if (preg_match('/\s+/A', $input, $match, null, $cursor)) { - } elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, null, $cursor)) { - $tokens[] = $match[1].$match[2].stripcslashes(str_replace(array('"\'', '\'"', '\'\'', '""'), '', substr($match[3], 1, strlen($match[3]) - 2))); - } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, null, $cursor)) { - $tokens[] = stripcslashes(substr($match[0], 1, strlen($match[0]) - 2)); - } elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, null, $cursor)) { - $tokens[] = stripcslashes($match[1]); - } else { - // should never happen - throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10))); - } - - $cursor += strlen($match[0]); - } - - return $tokens; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Logger; - -use Psr\Log\AbstractLogger; -use Psr\Log\InvalidArgumentException; -use Psr\Log\LogLevel; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Output\ConsoleOutputInterface; - -/** - * PSR-3 compliant console logger. - * - * @author Kévin Dunglas - * - * @see http://www.php-fig.org/psr/psr-3/ - */ -class ConsoleLogger extends AbstractLogger -{ - const INFO = 'info'; - const ERROR = 'error'; - - private $output; - private $verbosityLevelMap = array( - LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL, - LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL, - LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL, - LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL, - LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL, - LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE, - LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE, - LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG, - ); - private $formatLevelMap = array( - LogLevel::EMERGENCY => self::ERROR, - LogLevel::ALERT => self::ERROR, - LogLevel::CRITICAL => self::ERROR, - LogLevel::ERROR => self::ERROR, - LogLevel::WARNING => self::INFO, - LogLevel::NOTICE => self::INFO, - LogLevel::INFO => self::INFO, - LogLevel::DEBUG => self::INFO, - ); - private $errored = false; - - public function __construct(OutputInterface $output, array $verbosityLevelMap = array(), array $formatLevelMap = array()) - { - $this->output = $output; - $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap; - $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap; - } - - /** - * {@inheritdoc} - */ - public function log($level, $message, array $context = array()) - { - if (!isset($this->verbosityLevelMap[$level])) { - throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level)); - } - - $output = $this->output; - - // Write to the error output if necessary and available - if (self::ERROR === $this->formatLevelMap[$level]) { - if ($this->output instanceof ConsoleOutputInterface) { - $output = $output->getErrorOutput(); - } - $this->errored = true; - } - - // the if condition check isn't necessary -- it's the same one that $output will do internally anyway. - // We only do it for efficiency here as the message formatting is relatively expensive. - if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) { - $output->writeln(sprintf('<%1$s>[%2$s] %3$s', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context)), $this->verbosityLevelMap[$level]); - } - } - - /** - * Returns true when any messages have been logged at error levels. - */ - public function hasErrored() - { - return $this->errored; - } - - /** - * Interpolates context values into the message placeholders. - * - * @author PHP Framework Interoperability Group - * - * @param string $message - * @param array $context - * - * @return string - */ - private function interpolate($message, array $context) - { - // build a replacement array with braces around the context keys - $replace = array(); - foreach ($context as $key => $val) { - if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) { - $replace[sprintf('{%s}', $key)] = $val; - } - } - - // interpolate replacement values into the message and return - return strtr($message, $replace); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Output; - -/** - * @author Jean-François Simon - */ -class BufferedOutput extends Output -{ - private $buffer = ''; - - /** - * Empties buffer and returns its content. - * - * @return string - */ - public function fetch() - { - $content = $this->buffer; - $this->buffer = ''; - - return $content; - } - - /** - * {@inheritdoc} - */ - protected function doWrite($message, $newline) - { - $this->buffer .= $message; - - if ($newline) { - $this->buffer .= PHP_EOL; - } - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Output; - -use Symfony\Component\Console\Formatter\OutputFormatterInterface; - -/** - * ConsoleOutput is the default class for all CLI output. It uses STDOUT and STDERR. - * - * This class is a convenient wrapper around `StreamOutput` for both STDOUT and STDERR. - * - * $output = new ConsoleOutput(); - * - * This is equivalent to: - * - * $output = new StreamOutput(fopen('php://stdout', 'w')); - * $stdErr = new StreamOutput(fopen('php://stderr', 'w')); - * - * @author Fabien Potencier - */ -class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface -{ - private $stderr; - - /** - * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) - * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) - * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) - */ - public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null) - { - parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter); - - $actualDecorated = $this->isDecorated(); - $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter()); - - if (null === $decorated) { - $this->setDecorated($actualDecorated && $this->stderr->isDecorated()); - } - } - - /** - * {@inheritdoc} - */ - public function setDecorated($decorated) - { - parent::setDecorated($decorated); - $this->stderr->setDecorated($decorated); - } - - /** - * {@inheritdoc} - */ - public function setFormatter(OutputFormatterInterface $formatter) - { - parent::setFormatter($formatter); - $this->stderr->setFormatter($formatter); - } - - /** - * {@inheritdoc} - */ - public function setVerbosity($level) - { - parent::setVerbosity($level); - $this->stderr->setVerbosity($level); - } - - /** - * {@inheritdoc} - */ - public function getErrorOutput() - { - return $this->stderr; - } - - /** - * {@inheritdoc} - */ - public function setErrorOutput(OutputInterface $error) - { - $this->stderr = $error; - } - - /** - * Returns true if current environment supports writing console output to - * STDOUT. - * - * @return bool - */ - protected function hasStdoutSupport() - { - return false === $this->isRunningOS400(); - } - - /** - * Returns true if current environment supports writing console output to - * STDERR. - * - * @return bool - */ - protected function hasStderrSupport() - { - return false === $this->isRunningOS400(); - } - - /** - * Checks if current executing environment is IBM iSeries (OS400), which - * doesn't properly convert character-encodings between ASCII to EBCDIC. - * - * @return bool - */ - private function isRunningOS400() - { - $checks = array( - function_exists('php_uname') ? php_uname('s') : '', - getenv('OSTYPE'), - PHP_OS, - ); - - return false !== stripos(implode(';', $checks), 'OS400'); - } - - /** - * @return resource - */ - private function openOutputStream() - { - if (!$this->hasStdoutSupport()) { - return fopen('php://output', 'w'); - } - - return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w'); - } - - /** - * @return resource - */ - private function openErrorStream() - { - return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w'); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Output; - -/** - * ConsoleOutputInterface is the interface implemented by ConsoleOutput class. - * This adds information about stderr output stream. - * - * @author Dariusz Górecki - */ -interface ConsoleOutputInterface extends OutputInterface -{ - /** - * Gets the OutputInterface for errors. - * - * @return OutputInterface - */ - public function getErrorOutput(); - - public function setErrorOutput(OutputInterface $error); -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Output; - -use Symfony\Component\Console\Formatter\OutputFormatter; -use Symfony\Component\Console\Formatter\OutputFormatterInterface; - -/** - * NullOutput suppresses all output. - * - * $output = new NullOutput(); - * - * @author Fabien Potencier - * @author Tobias Schultze - */ -class NullOutput implements OutputInterface -{ - /** - * {@inheritdoc} - */ - public function setFormatter(OutputFormatterInterface $formatter) - { - // do nothing - } - - /** - * {@inheritdoc} - */ - public function getFormatter() - { - // to comply with the interface we must return a OutputFormatterInterface - return new OutputFormatter(); - } - - /** - * {@inheritdoc} - */ - public function setDecorated($decorated) - { - // do nothing - } - - /** - * {@inheritdoc} - */ - public function isDecorated() - { - return false; - } - - /** - * {@inheritdoc} - */ - public function setVerbosity($level) - { - // do nothing - } - - /** - * {@inheritdoc} - */ - public function getVerbosity() - { - return self::VERBOSITY_QUIET; - } - - /** - * {@inheritdoc} - */ - public function isQuiet() - { - return true; - } - - /** - * {@inheritdoc} - */ - public function isVerbose() - { - return false; - } - - /** - * {@inheritdoc} - */ - public function isVeryVerbose() - { - return false; - } - - /** - * {@inheritdoc} - */ - public function isDebug() - { - return false; - } - - /** - * {@inheritdoc} - */ - public function writeln($messages, $options = self::OUTPUT_NORMAL) - { - // do nothing - } - - /** - * {@inheritdoc} - */ - public function write($messages, $newline = false, $options = self::OUTPUT_NORMAL) - { - // do nothing - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Output; - -use Symfony\Component\Console\Formatter\OutputFormatterInterface; -use Symfony\Component\Console\Formatter\OutputFormatter; - -/** - * Base class for output classes. - * - * There are five levels of verbosity: - * - * * normal: no option passed (normal output) - * * verbose: -v (more output) - * * very verbose: -vv (highly extended output) - * * debug: -vvv (all debug output) - * * quiet: -q (no output) - * - * @author Fabien Potencier - */ -abstract class Output implements OutputInterface -{ - private $verbosity; - private $formatter; - - /** - * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) - * @param bool $decorated Whether to decorate messages - * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) - */ - public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = false, OutputFormatterInterface $formatter = null) - { - $this->verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity; - $this->formatter = $formatter ?: new OutputFormatter(); - $this->formatter->setDecorated($decorated); - } - - /** - * {@inheritdoc} - */ - public function setFormatter(OutputFormatterInterface $formatter) - { - $this->formatter = $formatter; - } - - /** - * {@inheritdoc} - */ - public function getFormatter() - { - return $this->formatter; - } - - /** - * {@inheritdoc} - */ - public function setDecorated($decorated) - { - $this->formatter->setDecorated($decorated); - } - - /** - * {@inheritdoc} - */ - public function isDecorated() - { - return $this->formatter->isDecorated(); - } - - /** - * {@inheritdoc} - */ - public function setVerbosity($level) - { - $this->verbosity = (int) $level; - } - - /** - * {@inheritdoc} - */ - public function getVerbosity() - { - return $this->verbosity; - } - - /** - * {@inheritdoc} - */ - public function isQuiet() - { - return self::VERBOSITY_QUIET === $this->verbosity; - } - - /** - * {@inheritdoc} - */ - public function isVerbose() - { - return self::VERBOSITY_VERBOSE <= $this->verbosity; - } - - /** - * {@inheritdoc} - */ - public function isVeryVerbose() - { - return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; - } - - /** - * {@inheritdoc} - */ - public function isDebug() - { - return self::VERBOSITY_DEBUG <= $this->verbosity; - } - - /** - * {@inheritdoc} - */ - public function writeln($messages, $options = self::OUTPUT_NORMAL) - { - $this->write($messages, true, $options); - } - - /** - * {@inheritdoc} - */ - public function write($messages, $newline = false, $options = self::OUTPUT_NORMAL) - { - $messages = (array) $messages; - - $types = self::OUTPUT_NORMAL | self::OUTPUT_RAW | self::OUTPUT_PLAIN; - $type = $types & $options ?: self::OUTPUT_NORMAL; - - $verbosities = self::VERBOSITY_QUIET | self::VERBOSITY_NORMAL | self::VERBOSITY_VERBOSE | self::VERBOSITY_VERY_VERBOSE | self::VERBOSITY_DEBUG; - $verbosity = $verbosities & $options ?: self::VERBOSITY_NORMAL; - - if ($verbosity > $this->getVerbosity()) { - return; - } - - foreach ($messages as $message) { - switch ($type) { - case OutputInterface::OUTPUT_NORMAL: - $message = $this->formatter->format($message); - break; - case OutputInterface::OUTPUT_RAW: - break; - case OutputInterface::OUTPUT_PLAIN: - $message = strip_tags($this->formatter->format($message)); - break; - } - - $this->doWrite($message, $newline); - } - } - - /** - * Writes a message to the output. - * - * @param string $message A message to write to the output - * @param bool $newline Whether to add a newline or not - */ - abstract protected function doWrite($message, $newline); -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Output; - -use Symfony\Component\Console\Formatter\OutputFormatterInterface; - -/** - * OutputInterface is the interface implemented by all Output classes. - * - * @author Fabien Potencier - */ -interface OutputInterface -{ - const VERBOSITY_QUIET = 16; - const VERBOSITY_NORMAL = 32; - const VERBOSITY_VERBOSE = 64; - const VERBOSITY_VERY_VERBOSE = 128; - const VERBOSITY_DEBUG = 256; - - const OUTPUT_NORMAL = 1; - const OUTPUT_RAW = 2; - const OUTPUT_PLAIN = 4; - - /** - * Writes a message to the output. - * - * @param string|array $messages The message as an array of lines or a single string - * @param bool $newline Whether to add a newline - * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL - */ - public function write($messages, $newline = false, $options = 0); - - /** - * Writes a message to the output and adds a newline at the end. - * - * @param string|array $messages The message as an array of lines of a single string - * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL - */ - public function writeln($messages, $options = 0); - - /** - * Sets the verbosity of the output. - * - * @param int $level The level of verbosity (one of the VERBOSITY constants) - */ - public function setVerbosity($level); - - /** - * Gets the current verbosity of the output. - * - * @return int The current level of verbosity (one of the VERBOSITY constants) - */ - public function getVerbosity(); - - /** - * Returns whether verbosity is quiet (-q). - * - * @return bool true if verbosity is set to VERBOSITY_QUIET, false otherwise - */ - public function isQuiet(); - - /** - * Returns whether verbosity is verbose (-v). - * - * @return bool true if verbosity is set to VERBOSITY_VERBOSE, false otherwise - */ - public function isVerbose(); - - /** - * Returns whether verbosity is very verbose (-vv). - * - * @return bool true if verbosity is set to VERBOSITY_VERY_VERBOSE, false otherwise - */ - public function isVeryVerbose(); - - /** - * Returns whether verbosity is debug (-vvv). - * - * @return bool true if verbosity is set to VERBOSITY_DEBUG, false otherwise - */ - public function isDebug(); - - /** - * Sets the decorated flag. - * - * @param bool $decorated Whether to decorate the messages - */ - public function setDecorated($decorated); - - /** - * Gets the decorated flag. - * - * @return bool true if the output will decorate messages, false otherwise - */ - public function isDecorated(); - - public function setFormatter(OutputFormatterInterface $formatter); - - /** - * Returns current output formatter instance. - * - * @return OutputFormatterInterface - */ - public function getFormatter(); -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Output; - -use Symfony\Component\Console\Exception\InvalidArgumentException; -use Symfony\Component\Console\Exception\RuntimeException; -use Symfony\Component\Console\Formatter\OutputFormatterInterface; - -/** - * StreamOutput writes the output to a given stream. - * - * Usage: - * - * $output = new StreamOutput(fopen('php://stdout', 'w')); - * - * As `StreamOutput` can use any stream, you can also use a file: - * - * $output = new StreamOutput(fopen('/path/to/output.log', 'a', false)); - * - * @author Fabien Potencier - */ -class StreamOutput extends Output -{ - private $stream; - - /** - * @param resource $stream A stream resource - * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) - * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) - * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) - * - * @throws InvalidArgumentException When first argument is not a real stream - */ - public function __construct($stream, $verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null) - { - if (!is_resource($stream) || 'stream' !== get_resource_type($stream)) { - throw new InvalidArgumentException('The StreamOutput class needs a stream as its first argument.'); - } - - $this->stream = $stream; - - if (null === $decorated) { - $decorated = $this->hasColorSupport(); - } - - parent::__construct($verbosity, $decorated, $formatter); - } - - /** - * Gets the stream attached to this StreamOutput instance. - * - * @return resource A stream resource - */ - public function getStream() - { - return $this->stream; - } - - /** - * {@inheritdoc} - */ - protected function doWrite($message, $newline) - { - if (false === @fwrite($this->stream, $message) || ($newline && (false === @fwrite($this->stream, PHP_EOL)))) { - // should never happen - throw new RuntimeException('Unable to write output.'); - } - - fflush($this->stream); - } - - /** - * Returns true if the stream supports colorization. - * - * Colorization is disabled if not supported by the stream: - * - * - Windows != 10.0.10586 without Ansicon, ConEmu or Mintty - * - non tty consoles - * - * @return bool true if the stream supports colorization, false otherwise - */ - protected function hasColorSupport() - { - if (DIRECTORY_SEPARATOR === '\\') { - return - '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR.'.'.PHP_WINDOWS_VERSION_MINOR.'.'.PHP_WINDOWS_VERSION_BUILD - || false !== getenv('ANSICON') - || 'ON' === getenv('ConEmuANSI') - || 'xterm' === getenv('TERM'); - } - - return function_exists('posix_isatty') && @posix_isatty($this->stream); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Question; - -use Symfony\Component\Console\Exception\InvalidArgumentException; - -/** - * Represents a choice question. - * - * @author Fabien Potencier - */ -class ChoiceQuestion extends Question -{ - private $choices; - private $multiselect = false; - private $prompt = ' > '; - private $errorMessage = 'Value "%s" is invalid'; - - /** - * @param string $question The question to ask to the user - * @param array $choices The list of available choices - * @param mixed $default The default answer to return - */ - public function __construct($question, array $choices, $default = null) - { - if (!$choices) { - throw new \LogicException('Choice question must have at least 1 choice available.'); - } - - parent::__construct($question, $default); - - $this->choices = $choices; - $this->setValidator($this->getDefaultValidator()); - $this->setAutocompleterValues($choices); - } - - /** - * Returns available choices. - * - * @return array - */ - public function getChoices() - { - return $this->choices; - } - - /** - * Sets multiselect option. - * - * When multiselect is set to true, multiple choices can be answered. - * - * @param bool $multiselect - * - * @return $this - */ - public function setMultiselect($multiselect) - { - $this->multiselect = $multiselect; - $this->setValidator($this->getDefaultValidator()); - - return $this; - } - - /** - * Returns whether the choices are multiselect. - * - * @return bool - */ - public function isMultiselect() - { - return $this->multiselect; - } - - /** - * Gets the prompt for choices. - * - * @return string - */ - public function getPrompt() - { - return $this->prompt; - } - - /** - * Sets the prompt for choices. - * - * @param string $prompt - * - * @return $this - */ - public function setPrompt($prompt) - { - $this->prompt = $prompt; - - return $this; - } - - /** - * Sets the error message for invalid values. - * - * The error message has a string placeholder (%s) for the invalid value. - * - * @param string $errorMessage - * - * @return $this - */ - public function setErrorMessage($errorMessage) - { - $this->errorMessage = $errorMessage; - $this->setValidator($this->getDefaultValidator()); - - return $this; - } - - /** - * Returns the default answer validator. - * - * @return callable - */ - private function getDefaultValidator() - { - $choices = $this->choices; - $errorMessage = $this->errorMessage; - $multiselect = $this->multiselect; - $isAssoc = $this->isAssoc($choices); - - return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) { - // Collapse all spaces. - $selectedChoices = str_replace(' ', '', $selected); - - if ($multiselect) { - // Check for a separated comma values - if (!preg_match('/^[^,]+(?:,[^,]+)*$/', $selectedChoices, $matches)) { - throw new InvalidArgumentException(sprintf($errorMessage, $selected)); - } - $selectedChoices = explode(',', $selectedChoices); - } else { - $selectedChoices = array($selected); - } - - $multiselectChoices = array(); - foreach ($selectedChoices as $value) { - $results = array(); - foreach ($choices as $key => $choice) { - if ($choice === $value) { - $results[] = $key; - } - } - - if (count($results) > 1) { - throw new InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of %s.', implode(' or ', $results))); - } - - $result = array_search($value, $choices); - - if (!$isAssoc) { - if (false !== $result) { - $result = $choices[$result]; - } elseif (isset($choices[$value])) { - $result = $choices[$value]; - } - } elseif (false === $result && isset($choices[$value])) { - $result = $value; - } - - if (false === $result) { - throw new InvalidArgumentException(sprintf($errorMessage, $value)); - } - - $multiselectChoices[] = (string) $result; - } - - if ($multiselect) { - return $multiselectChoices; - } - - return current($multiselectChoices); - }; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Question; - -/** - * Represents a yes/no question. - * - * @author Fabien Potencier - */ -class ConfirmationQuestion extends Question -{ - private $trueAnswerRegex; - - /** - * @param string $question The question to ask to the user - * @param bool $default The default answer to return, true or false - * @param string $trueAnswerRegex A regex to match the "yes" answer - */ - public function __construct($question, $default = true, $trueAnswerRegex = '/^y/i') - { - parent::__construct($question, (bool) $default); - - $this->trueAnswerRegex = $trueAnswerRegex; - $this->setNormalizer($this->getDefaultNormalizer()); - } - - /** - * Returns the default answer normalizer. - * - * @return callable - */ - private function getDefaultNormalizer() - { - $default = $this->getDefault(); - $regex = $this->trueAnswerRegex; - - return function ($answer) use ($default, $regex) { - if (is_bool($answer)) { - return $answer; - } - - $answerIsTrue = (bool) preg_match($regex, $answer); - if (false === $default) { - return $answer && $answerIsTrue; - } - - return !$answer || $answerIsTrue; - }; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Question; - -use Symfony\Component\Console\Exception\InvalidArgumentException; -use Symfony\Component\Console\Exception\LogicException; - -/** - * Represents a Question. - * - * @author Fabien Potencier - */ -class Question -{ - private $question; - private $attempts; - private $hidden = false; - private $hiddenFallback = true; - private $autocompleterValues; - private $validator; - private $default; - private $normalizer; - - /** - * @param string $question The question to ask to the user - * @param mixed $default The default answer to return if the user enters nothing - */ - public function __construct($question, $default = null) - { - $this->question = $question; - $this->default = $default; - } - - /** - * Returns the question. - * - * @return string - */ - public function getQuestion() - { - return $this->question; - } - - /** - * Returns the default answer. - * - * @return mixed - */ - public function getDefault() - { - return $this->default; - } - - /** - * Returns whether the user response must be hidden. - * - * @return bool - */ - public function isHidden() - { - return $this->hidden; - } - - /** - * Sets whether the user response must be hidden or not. - * - * @param bool $hidden - * - * @return $this - * - * @throws LogicException In case the autocompleter is also used - */ - public function setHidden($hidden) - { - if ($this->autocompleterValues) { - throw new LogicException('A hidden question cannot use the autocompleter.'); - } - - $this->hidden = (bool) $hidden; - - return $this; - } - - /** - * In case the response can not be hidden, whether to fallback on non-hidden question or not. - * - * @return bool - */ - public function isHiddenFallback() - { - return $this->hiddenFallback; - } - - /** - * Sets whether to fallback on non-hidden question if the response can not be hidden. - * - * @param bool $fallback - * - * @return $this - */ - public function setHiddenFallback($fallback) - { - $this->hiddenFallback = (bool) $fallback; - - return $this; - } - - /** - * Gets values for the autocompleter. - * - * @return null|array|\Traversable - */ - public function getAutocompleterValues() - { - return $this->autocompleterValues; - } - - /** - * Sets values for the autocompleter. - * - * @param null|array|\Traversable $values - * - * @return $this - * - * @throws InvalidArgumentException - * @throws LogicException - */ - public function setAutocompleterValues($values) - { - if (is_array($values)) { - $values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values); - } - - if (null !== $values && !is_array($values) && !$values instanceof \Traversable) { - throw new InvalidArgumentException('Autocompleter values can be either an array, `null` or a `Traversable` object.'); - } - - if ($this->hidden) { - throw new LogicException('A hidden question cannot use the autocompleter.'); - } - - $this->autocompleterValues = $values; - - return $this; - } - - /** - * Sets a validator for the question. - * - * @param null|callable $validator - * - * @return $this - */ - public function setValidator(callable $validator = null) - { - $this->validator = $validator; - - return $this; - } - - /** - * Gets the validator for the question. - * - * @return null|callable - */ - public function getValidator() - { - return $this->validator; - } - - /** - * Sets the maximum number of attempts. - * - * Null means an unlimited number of attempts. - * - * @param null|int $attempts - * - * @return $this - * - * @throws InvalidArgumentException in case the number of attempts is invalid - */ - public function setMaxAttempts($attempts) - { - if (null !== $attempts && $attempts < 1) { - throw new InvalidArgumentException('Maximum number of attempts must be a positive value.'); - } - - $this->attempts = $attempts; - - return $this; - } - - /** - * Gets the maximum number of attempts. - * - * Null means an unlimited number of attempts. - * - * @return null|int - */ - public function getMaxAttempts() - { - return $this->attempts; - } - - /** - * Sets a normalizer for the response. - * - * The normalizer can be a callable (a string), a closure or a class implementing __invoke. - * - * @param callable $normalizer - * - * @return $this - */ - public function setNormalizer(callable $normalizer) - { - $this->normalizer = $normalizer; - - return $this; - } - - /** - * Gets the normalizer for the response. - * - * The normalizer can ba a callable (a string), a closure or a class implementing __invoke. - * - * @return callable - */ - public function getNormalizer() - { - return $this->normalizer; - } - - protected function isAssoc($array) - { - return (bool) count(array_filter(array_keys($array), 'is_string')); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Style; - -use Symfony\Component\Console\Formatter\OutputFormatterInterface; -use Symfony\Component\Console\Helper\ProgressBar; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Output\ConsoleOutputInterface; - -/** - * Decorates output to add console style guide helpers. - * - * @author Kevin Bond - */ -abstract class OutputStyle implements OutputInterface, StyleInterface -{ - private $output; - - public function __construct(OutputInterface $output) - { - $this->output = $output; - } - - /** - * {@inheritdoc} - */ - public function newLine($count = 1) - { - $this->output->write(str_repeat(PHP_EOL, $count)); - } - - /** - * @param int $max - * - * @return ProgressBar - */ - public function createProgressBar($max = 0) - { - return new ProgressBar($this->output, $max); - } - - /** - * {@inheritdoc} - */ - public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) - { - $this->output->write($messages, $newline, $type); - } - - /** - * {@inheritdoc} - */ - public function writeln($messages, $type = self::OUTPUT_NORMAL) - { - $this->output->writeln($messages, $type); - } - - /** - * {@inheritdoc} - */ - public function setVerbosity($level) - { - $this->output->setVerbosity($level); - } - - /** - * {@inheritdoc} - */ - public function getVerbosity() - { - return $this->output->getVerbosity(); - } - - /** - * {@inheritdoc} - */ - public function setDecorated($decorated) - { - $this->output->setDecorated($decorated); - } - - /** - * {@inheritdoc} - */ - public function isDecorated() - { - return $this->output->isDecorated(); - } - - /** - * {@inheritdoc} - */ - public function setFormatter(OutputFormatterInterface $formatter) - { - $this->output->setFormatter($formatter); - } - - /** - * {@inheritdoc} - */ - public function getFormatter() - { - return $this->output->getFormatter(); - } - - /** - * {@inheritdoc} - */ - public function isQuiet() - { - return $this->output->isQuiet(); - } - - /** - * {@inheritdoc} - */ - public function isVerbose() - { - return $this->output->isVerbose(); - } - - /** - * {@inheritdoc} - */ - public function isVeryVerbose() - { - return $this->output->isVeryVerbose(); - } - - /** - * {@inheritdoc} - */ - public function isDebug() - { - return $this->output->isDebug(); - } - - protected function getErrorOutput() - { - if (!$this->output instanceof ConsoleOutputInterface) { - return $this->output; - } - - return $this->output->getErrorOutput(); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Style; - -/** - * Output style helpers. - * - * @author Kevin Bond - */ -interface StyleInterface -{ - /** - * Formats a command title. - * - * @param string $message - */ - public function title($message); - - /** - * Formats a section title. - * - * @param string $message - */ - public function section($message); - - /** - * Formats a list. - */ - public function listing(array $elements); - - /** - * Formats informational text. - * - * @param string|array $message - */ - public function text($message); - - /** - * Formats a success result bar. - * - * @param string|array $message - */ - public function success($message); - - /** - * Formats an error result bar. - * - * @param string|array $message - */ - public function error($message); - - /** - * Formats an warning result bar. - * - * @param string|array $message - */ - public function warning($message); - - /** - * Formats a note admonition. - * - * @param string|array $message - */ - public function note($message); - - /** - * Formats a caution admonition. - * - * @param string|array $message - */ - public function caution($message); - - /** - * Formats a table. - */ - public function table(array $headers, array $rows); - - /** - * Asks a question. - * - * @param string $question - * @param string|null $default - * @param callable|null $validator - * - * @return string - */ - public function ask($question, $default = null, $validator = null); - - /** - * Asks a question with the user input hidden. - * - * @param string $question - * @param callable|null $validator - * - * @return string - */ - public function askHidden($question, $validator = null); - - /** - * Asks for confirmation. - * - * @param string $question - * @param bool $default - * - * @return bool - */ - public function confirm($question, $default = true); - - /** - * Asks a choice question. - * - * @param string $question - * @param array $choices - * @param string|int|null $default - * - * @return string - */ - public function choice($question, array $choices, $default = null); - - /** - * Add newline(s). - * - * @param int $count The number of newlines - */ - public function newLine($count = 1); - - /** - * Starts the progress output. - * - * @param int $max Maximum steps (0 if unknown) - */ - public function progressStart($max = 0); - - /** - * Advances the progress output X steps. - * - * @param int $step Number of steps to advance - */ - public function progressAdvance($step = 1); - - /** - * Finishes the progress output. - */ - public function progressFinish(); -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Style; - -use Symfony\Component\Console\Exception\RuntimeException; -use Symfony\Component\Console\Formatter\OutputFormatter; -use Symfony\Component\Console\Helper\Helper; -use Symfony\Component\Console\Helper\ProgressBar; -use Symfony\Component\Console\Helper\SymfonyQuestionHelper; -use Symfony\Component\Console\Helper\Table; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\BufferedOutput; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Question\ChoiceQuestion; -use Symfony\Component\Console\Question\ConfirmationQuestion; -use Symfony\Component\Console\Question\Question; -use Symfony\Component\Console\Terminal; - -/** - * Output decorator helpers for the Symfony Style Guide. - * - * @author Kevin Bond - */ -class SymfonyStyle extends OutputStyle -{ - const MAX_LINE_LENGTH = 120; - - private $input; - private $questionHelper; - private $progressBar; - private $lineLength; - private $bufferedOutput; - - public function __construct(InputInterface $input, OutputInterface $output) - { - $this->input = $input; - $this->bufferedOutput = new BufferedOutput($output->getVerbosity(), false, clone $output->getFormatter()); - // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not. - $width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH; - $this->lineLength = min($width - (int) (DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH); - - parent::__construct($output); - } - - /** - * Formats a message as a block of text. - * - * @param string|array $messages The message to write in the block - * @param string|null $type The block type (added in [] on first line) - * @param string|null $style The style to apply to the whole block - * @param string $prefix The prefix for the block - * @param bool $padding Whether to add vertical padding - * @param bool $escape Whether to escape the message - */ - public function block($messages, $type = null, $style = null, $prefix = ' ', $padding = false, $escape = true) - { - $messages = is_array($messages) ? array_values($messages) : array($messages); - - $this->autoPrependBlock(); - $this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, $escape)); - $this->newLine(); - } - - /** - * {@inheritdoc} - */ - public function title($message) - { - $this->autoPrependBlock(); - $this->writeln(array( - sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), - sprintf('%s', str_repeat('=', Helper::strlenWithoutDecoration($this->getFormatter(), $message))), - )); - $this->newLine(); - } - - /** - * {@inheritdoc} - */ - public function section($message) - { - $this->autoPrependBlock(); - $this->writeln(array( - sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), - sprintf('%s', str_repeat('-', Helper::strlenWithoutDecoration($this->getFormatter(), $message))), - )); - $this->newLine(); - } - - /** - * {@inheritdoc} - */ - public function listing(array $elements) - { - $this->autoPrependText(); - $elements = array_map(function ($element) { - return sprintf(' * %s', $element); - }, $elements); - - $this->writeln($elements); - $this->newLine(); - } - - /** - * {@inheritdoc} - */ - public function text($message) - { - $this->autoPrependText(); - - $messages = is_array($message) ? array_values($message) : array($message); - foreach ($messages as $message) { - $this->writeln(sprintf(' %s', $message)); - } - } - - /** - * Formats a command comment. - * - * @param string|array $message - */ - public function comment($message) - { - $this->block($message, null, null, ' // ', false, false); - } - - /** - * {@inheritdoc} - */ - public function success($message) - { - $this->block($message, 'OK', 'fg=black;bg=green', ' ', true); - } - - /** - * {@inheritdoc} - */ - public function error($message) - { - $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true); - } - - /** - * {@inheritdoc} - */ - public function warning($message) - { - $this->block($message, 'WARNING', 'fg=white;bg=red', ' ', true); - } - - /** - * {@inheritdoc} - */ - public function note($message) - { - $this->block($message, 'NOTE', 'fg=yellow', ' ! '); - } - - /** - * {@inheritdoc} - */ - public function caution($message) - { - $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true); - } - - /** - * {@inheritdoc} - */ - public function table(array $headers, array $rows) - { - $style = clone Table::getStyleDefinition('symfony-style-guide'); - $style->setCellHeaderFormat('%s'); - - $table = new Table($this); - $table->setHeaders($headers); - $table->setRows($rows); - $table->setStyle($style); - - $table->render(); - $this->newLine(); - } - - /** - * {@inheritdoc} - */ - public function ask($question, $default = null, $validator = null) - { - $question = new Question($question, $default); - $question->setValidator($validator); - - return $this->askQuestion($question); - } - - /** - * {@inheritdoc} - */ - public function askHidden($question, $validator = null) - { - $question = new Question($question); - - $question->setHidden(true); - $question->setValidator($validator); - - return $this->askQuestion($question); - } - - /** - * {@inheritdoc} - */ - public function confirm($question, $default = true) - { - return $this->askQuestion(new ConfirmationQuestion($question, $default)); - } - - /** - * {@inheritdoc} - */ - public function choice($question, array $choices, $default = null) - { - if (null !== $default) { - $values = array_flip($choices); - $default = $values[$default]; - } - - return $this->askQuestion(new ChoiceQuestion($question, $choices, $default)); - } - - /** - * {@inheritdoc} - */ - public function progressStart($max = 0) - { - $this->progressBar = $this->createProgressBar($max); - $this->progressBar->start(); - } - - /** - * {@inheritdoc} - */ - public function progressAdvance($step = 1) - { - $this->getProgressBar()->advance($step); - } - - /** - * {@inheritdoc} - */ - public function progressFinish() - { - $this->getProgressBar()->finish(); - $this->newLine(2); - $this->progressBar = null; - } - - /** - * {@inheritdoc} - */ - public function createProgressBar($max = 0) - { - $progressBar = parent::createProgressBar($max); - - if ('\\' !== DIRECTORY_SEPARATOR) { - $progressBar->setEmptyBarCharacter('░'); // light shade character \u2591 - $progressBar->setProgressCharacter(''); - $progressBar->setBarCharacter('▓'); // dark shade character \u2593 - } - - return $progressBar; - } - - /** - * @return string - */ - public function askQuestion(Question $question) - { - if ($this->input->isInteractive()) { - $this->autoPrependBlock(); - } - - if (!$this->questionHelper) { - $this->questionHelper = new SymfonyQuestionHelper(); - } - - $answer = $this->questionHelper->ask($this->input, $this, $question); - - if ($this->input->isInteractive()) { - $this->newLine(); - $this->bufferedOutput->write("\n"); - } - - return $answer; - } - - /** - * {@inheritdoc} - */ - public function writeln($messages, $type = self::OUTPUT_NORMAL) - { - parent::writeln($messages, $type); - $this->bufferedOutput->writeln($this->reduceBuffer($messages), $type); - } - - /** - * {@inheritdoc} - */ - public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) - { - parent::write($messages, $newline, $type); - $this->bufferedOutput->write($this->reduceBuffer($messages), $newline, $type); - } - - /** - * {@inheritdoc} - */ - public function newLine($count = 1) - { - parent::newLine($count); - $this->bufferedOutput->write(str_repeat("\n", $count)); - } - - /** - * Returns a new instance which makes use of stderr if available. - * - * @return self - */ - public function getErrorStyle() - { - return new self($this->input, $this->getErrorOutput()); - } - - /** - * @return ProgressBar - */ - private function getProgressBar() - { - if (!$this->progressBar) { - throw new RuntimeException('The ProgressBar is not started.'); - } - - return $this->progressBar; - } - - private function autoPrependBlock() - { - $chars = substr(str_replace(PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2); - - if (!isset($chars[0])) { - return $this->newLine(); //empty history, so we should start with a new line. - } - //Prepend new line for each non LF chars (This means no blank line was output before) - $this->newLine(2 - substr_count($chars, "\n")); - } - - private function autoPrependText() - { - $fetched = $this->bufferedOutput->fetch(); - //Prepend new line if last char isn't EOL: - if ("\n" !== substr($fetched, -1)) { - $this->newLine(); - } - } - - private function reduceBuffer($messages) - { - // We need to know if the two last chars are PHP_EOL - // Preserve the last 4 chars inserted (PHP_EOL on windows is two chars) in the history buffer - return array_map(function ($value) { - return substr($value, -4); - }, array_merge(array($this->bufferedOutput->fetch()), (array) $messages)); - } - - private function createBlock($messages, $type = null, $style = null, $prefix = ' ', $padding = false, $escape = false) - { - $indentLength = 0; - $prefixLength = Helper::strlenWithoutDecoration($this->getFormatter(), $prefix); - $lines = array(); - - if (null !== $type) { - $type = sprintf('[%s] ', $type); - $indentLength = strlen($type); - $lineIndentation = str_repeat(' ', $indentLength); - } - - // wrap and add newlines for each element - foreach ($messages as $key => $message) { - if ($escape) { - $message = OutputFormatter::escape($message); - } - - $lines = array_merge($lines, explode(PHP_EOL, wordwrap($message, $this->lineLength - $prefixLength - $indentLength, PHP_EOL, true))); - - if (count($messages) > 1 && $key < count($messages) - 1) { - $lines[] = ''; - } - } - - $firstLineIndex = 0; - if ($padding && $this->isDecorated()) { - $firstLineIndex = 1; - array_unshift($lines, ''); - $lines[] = ''; - } - - foreach ($lines as $i => &$line) { - if (null !== $type) { - $line = $firstLineIndex === $i ? $type.$line : $lineIndentation.$line; - } - - $line = $prefix.$line; - $line .= str_repeat(' ', $this->lineLength - Helper::strlenWithoutDecoration($this->getFormatter(), $line)); - - if ($style) { - $line = sprintf('<%s>%s', $style, $line); - } - } - - return $lines; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console; - -class Terminal -{ - private static $width; - private static $height; - - /** - * Gets the terminal width. - * - * @return int - */ - public function getWidth() - { - $width = getenv('COLUMNS'); - if (false !== $width) { - return (int) trim($width); - } - - if (null === self::$width) { - self::initDimensions(); - } - - return self::$width ?: 80; - } - - /** - * Gets the terminal height. - * - * @return int - */ - public function getHeight() - { - $height = getenv('LINES'); - if (false !== $height) { - return (int) trim($height); - } - - if (null === self::$height) { - self::initDimensions(); - } - - return self::$height ?: 50; - } - - private static function initDimensions() - { - if ('\\' === DIRECTORY_SEPARATOR) { - if (preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', trim(getenv('ANSICON')), $matches)) { - // extract [w, H] from "wxh (WxH)" - // or [w, h] from "wxh" - self::$width = (int) $matches[1]; - self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2]; - } elseif (null !== $dimensions = self::getConsoleMode()) { - // extract [w, h] from "wxh" - self::$width = (int) $dimensions[0]; - self::$height = (int) $dimensions[1]; - } - } elseif ($sttyString = self::getSttyColumns()) { - if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) { - // extract [w, h] from "rows h; columns w;" - self::$width = (int) $matches[2]; - self::$height = (int) $matches[1]; - } elseif (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) { - // extract [w, h] from "; h rows; w columns" - self::$width = (int) $matches[2]; - self::$height = (int) $matches[1]; - } - } - } - - /** - * Runs and parses mode CON if it's available, suppressing any error output. - * - * @return int[]|null An array composed of the width and the height or null if it could not be parsed - */ - private static function getConsoleMode() - { - if (!function_exists('proc_open')) { - return; - } - - $descriptorspec = array( - 1 => array('pipe', 'w'), - 2 => array('pipe', 'w'), - ); - $process = proc_open('mode CON', $descriptorspec, $pipes, null, null, array('suppress_errors' => true)); - if (is_resource($process)) { - $info = stream_get_contents($pipes[1]); - fclose($pipes[1]); - fclose($pipes[2]); - proc_close($process); - - if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { - return array((int) $matches[2], (int) $matches[1]); - } - } - } - - /** - * Runs and parses stty -a if it's available, suppressing any error output. - * - * @return string|null - */ - private static function getSttyColumns() - { - if (!function_exists('proc_open')) { - return; - } - - $descriptorspec = array( - 1 => array('pipe', 'w'), - 2 => array('pipe', 'w'), - ); - - $process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, array('suppress_errors' => true)); - if (is_resource($process)) { - $info = stream_get_contents($pipes[1]); - fclose($pipes[1]); - fclose($pipes[2]); - proc_close($process); - - return $info; - } - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Tester; - -use Symfony\Component\Console\Application; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\ConsoleOutput; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Output\StreamOutput; - -/** - * Eases the testing of console applications. - * - * When testing an application, don't forget to disable the auto exit flag: - * - * $application = new Application(); - * $application->setAutoExit(false); - * - * @author Fabien Potencier - */ -class ApplicationTester -{ - private $application; - private $input; - private $statusCode; - /** - * @var OutputInterface - */ - private $output; - private $captureStreamsIndependently = false; - - public function __construct(Application $application) - { - $this->application = $application; - } - - /** - * Executes the application. - * - * Available options: - * - * * interactive: Sets the input interactive flag - * * decorated: Sets the output decorated flag - * * verbosity: Sets the output verbosity flag - * * capture_stderr_separately: Make output of stdOut and stdErr separately available - * - * @param array $input An array of arguments and options - * @param array $options An array of options - * - * @return int The command exit code - */ - public function run(array $input, $options = array()) - { - $this->input = new ArrayInput($input); - if (isset($options['interactive'])) { - $this->input->setInteractive($options['interactive']); - } - - $this->captureStreamsIndependently = array_key_exists('capture_stderr_separately', $options) && $options['capture_stderr_separately']; - if (!$this->captureStreamsIndependently) { - $this->output = new StreamOutput(fopen('php://memory', 'w', false)); - if (isset($options['decorated'])) { - $this->output->setDecorated($options['decorated']); - } - if (isset($options['verbosity'])) { - $this->output->setVerbosity($options['verbosity']); - } - } else { - $this->output = new ConsoleOutput( - isset($options['verbosity']) ? $options['verbosity'] : ConsoleOutput::VERBOSITY_NORMAL, - isset($options['decorated']) ? $options['decorated'] : null - ); - - $errorOutput = new StreamOutput(fopen('php://memory', 'w', false)); - $errorOutput->setFormatter($this->output->getFormatter()); - $errorOutput->setVerbosity($this->output->getVerbosity()); - $errorOutput->setDecorated($this->output->isDecorated()); - - $reflectedOutput = new \ReflectionObject($this->output); - $strErrProperty = $reflectedOutput->getProperty('stderr'); - $strErrProperty->setAccessible(true); - $strErrProperty->setValue($this->output, $errorOutput); - - $reflectedParent = $reflectedOutput->getParentClass(); - $streamProperty = $reflectedParent->getProperty('stream'); - $streamProperty->setAccessible(true); - $streamProperty->setValue($this->output, fopen('php://memory', 'w', false)); - } - - return $this->statusCode = $this->application->run($this->input, $this->output); - } - - /** - * Gets the display returned by the last execution of the application. - * - * @param bool $normalize Whether to normalize end of lines to \n or not - * - * @return string The display - */ - public function getDisplay($normalize = false) - { - rewind($this->output->getStream()); - - $display = stream_get_contents($this->output->getStream()); - - if ($normalize) { - $display = str_replace(PHP_EOL, "\n", $display); - } - - return $display; - } - - /** - * Gets the output written to STDERR by the application. - * - * @param bool $normalize Whether to normalize end of lines to \n or not - * - * @return string - */ - public function getErrorOutput($normalize = false) - { - if (!$this->captureStreamsIndependently) { - throw new \LogicException('The error output is not available when the tester is run without "capture_stderr_separately" option set.'); - } - - rewind($this->output->getErrorOutput()->getStream()); - - $display = stream_get_contents($this->output->getErrorOutput()->getStream()); - - if ($normalize) { - $display = str_replace(PHP_EOL, "\n", $display); - } - - return $display; - } - - /** - * Gets the input instance used by the last execution of the application. - * - * @return InputInterface The current input instance - */ - public function getInput() - { - return $this->input; - } - - /** - * Gets the output instance used by the last execution of the application. - * - * @return OutputInterface The current output instance - */ - public function getOutput() - { - return $this->output; - } - - /** - * Gets the status code returned by the last execution of the application. - * - * @return int The status code - */ - public function getStatusCode() - { - return $this->statusCode; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Console\Tester; - -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Output\StreamOutput; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; - -/** - * Eases the testing of console commands. - * - * @author Fabien Potencier - * @author Robin Chalas - */ -class CommandTester -{ - private $command; - private $input; - private $output; - private $inputs = array(); - private $statusCode; - - public function __construct(Command $command) - { - $this->command = $command; - } - - /** - * Executes the command. - * - * Available execution options: - * - * * interactive: Sets the input interactive flag - * * decorated: Sets the output decorated flag - * * verbosity: Sets the output verbosity flag - * - * @param array $input An array of command arguments and options - * @param array $options An array of execution options - * - * @return int The command exit code - */ - public function execute(array $input, array $options = array()) - { - // set the command name automatically if the application requires - // this argument and no command name was passed - if (!isset($input['command']) - && (null !== $application = $this->command->getApplication()) - && $application->getDefinition()->hasArgument('command') - ) { - $input = array_merge(array('command' => $this->command->getName()), $input); - } - - $this->input = new ArrayInput($input); - if ($this->inputs) { - $this->input->setStream(self::createStream($this->inputs)); - } - - if (isset($options['interactive'])) { - $this->input->setInteractive($options['interactive']); - } - - $this->output = new StreamOutput(fopen('php://memory', 'w', false)); - $this->output->setDecorated(isset($options['decorated']) ? $options['decorated'] : false); - if (isset($options['verbosity'])) { - $this->output->setVerbosity($options['verbosity']); - } - - return $this->statusCode = $this->command->run($this->input, $this->output); - } - - /** - * Gets the display returned by the last execution of the command. - * - * @param bool $normalize Whether to normalize end of lines to \n or not - * - * @return string The display - */ - public function getDisplay($normalize = false) - { - rewind($this->output->getStream()); - - $display = stream_get_contents($this->output->getStream()); - - if ($normalize) { - $display = str_replace(PHP_EOL, "\n", $display); - } - - return $display; - } - - /** - * Gets the input instance used by the last execution of the command. - * - * @return InputInterface The current input instance - */ - public function getInput() - { - return $this->input; - } - - /** - * Gets the output instance used by the last execution of the command. - * - * @return OutputInterface The current output instance - */ - public function getOutput() - { - return $this->output; - } - - /** - * Gets the status code returned by the last execution of the application. - * - * @return int The status code - */ - public function getStatusCode() - { - return $this->statusCode; - } - - /** - * Sets the user inputs. - * - * @param array $inputs An array of strings representing each input - * passed to the command input stream - * - * @return CommandTester - */ - public function setInputs(array $inputs) - { - $this->inputs = $inputs; - - return $this; - } - - private static function createStream(array $inputs) - { - $stream = fopen('php://memory', 'r+', false); - - fwrite($stream, implode(PHP_EOL, $inputs)); - rewind($stream); - - return $stream; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Debug; - -use Psr\Log\AbstractLogger; - -/** - * A buffering logger that stacks logs for later. - * - * @author Nicolas Grekas - */ -class BufferingLogger extends AbstractLogger -{ - private $logs = array(); - - public function log($level, $message, array $context = array()) - { - $this->logs[] = array($level, $message, $context); - } - - public function cleanLogs() - { - $logs = $this->logs; - $this->logs = array(); - - return $logs; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Debug; - -/** - * Registers all the debug tools. - * - * @author Fabien Potencier - */ -class Debug -{ - private static $enabled = false; - - /** - * Enables the debug tools. - * - * This method registers an error handler and an exception handler. - * - * If the Symfony ClassLoader component is available, a special - * class loader is also registered. - * - * @param int $errorReportingLevel The level of error reporting you want - * @param bool $displayErrors Whether to display errors (for development) or just log them (for production) - */ - public static function enable($errorReportingLevel = E_ALL, $displayErrors = true) - { - if (static::$enabled) { - return; - } - - static::$enabled = true; - - if (null !== $errorReportingLevel) { - error_reporting($errorReportingLevel); - } else { - error_reporting(E_ALL); - } - - if ('cli' !== PHP_SAPI) { - ini_set('display_errors', 0); - ExceptionHandler::register(); - } elseif ($displayErrors && (!ini_get('log_errors') || ini_get('error_log'))) { - // CLI - display errors only if they're not already logged to STDERR - ini_set('display_errors', 1); - } - if ($displayErrors) { - ErrorHandler::register(new ErrorHandler(new BufferingLogger())); - } else { - ErrorHandler::register()->throwAt(0, true); - } - - DebugClassLoader::enable(); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Debug; - -/** - * Autoloader checking if the class is really defined in the file found. - * - * The ClassLoader will wrap all registered autoloaders - * and will throw an exception if a file is found but does - * not declare the class. - * - * @author Fabien Potencier - * @author Christophe Coevoet - * @author Nicolas Grekas - */ -class DebugClassLoader -{ - private $classLoader; - private $isFinder; - private $loaded = array(); - private static $caseCheck; - private static $final = array(); - private static $finalMethods = array(); - private static $deprecated = array(); - private static $php7Reserved = array('int', 'float', 'bool', 'string', 'true', 'false', 'null'); - private static $darwinCache = array('/' => array('/', array())); - - public function __construct(callable $classLoader) - { - $this->classLoader = $classLoader; - $this->isFinder = is_array($classLoader) && method_exists($classLoader[0], 'findFile'); - - if (!isset(self::$caseCheck)) { - $file = file_exists(__FILE__) ? __FILE__ : rtrim(realpath('.'), DIRECTORY_SEPARATOR); - $i = strrpos($file, DIRECTORY_SEPARATOR); - $dir = substr($file, 0, 1 + $i); - $file = substr($file, 1 + $i); - $test = strtoupper($file) === $file ? strtolower($file) : strtoupper($file); - $test = realpath($dir.$test); - - if (false === $test || false === $i) { - // filesystem is case sensitive - self::$caseCheck = 0; - } elseif (substr($test, -strlen($file)) === $file) { - // filesystem is case insensitive and realpath() normalizes the case of characters - self::$caseCheck = 1; - } elseif (false !== stripos(PHP_OS, 'darwin')) { - // on MacOSX, HFS+ is case insensitive but realpath() doesn't normalize the case of characters - self::$caseCheck = 2; - } else { - // filesystem case checks failed, fallback to disabling them - self::$caseCheck = 0; - } - } - } - - /** - * Gets the wrapped class loader. - * - * @return callable The wrapped class loader - */ - public function getClassLoader() - { - return $this->classLoader; - } - - /** - * Wraps all autoloaders. - */ - public static function enable() - { - // Ensures we don't hit https://bugs.php.net/42098 - class_exists('Symfony\Component\Debug\ErrorHandler'); - class_exists('Psr\Log\LogLevel'); - - if (!is_array($functions = spl_autoload_functions())) { - return; - } - - foreach ($functions as $function) { - spl_autoload_unregister($function); - } - - foreach ($functions as $function) { - if (!is_array($function) || !$function[0] instanceof self) { - $function = array(new static($function), 'loadClass'); - } - - spl_autoload_register($function); - } - } - - /** - * Disables the wrapping. - */ - public static function disable() - { - if (!is_array($functions = spl_autoload_functions())) { - return; - } - - foreach ($functions as $function) { - spl_autoload_unregister($function); - } - - foreach ($functions as $function) { - if (is_array($function) && $function[0] instanceof self) { - $function = $function[0]->getClassLoader(); - } - - spl_autoload_register($function); - } - } - - /** - * Loads the given class or interface. - * - * @param string $class The name of the class - * - * @return bool|null True, if loaded - * - * @throws \RuntimeException - */ - public function loadClass($class) - { - ErrorHandler::stackErrors(); - - try { - if ($this->isFinder && !isset($this->loaded[$class])) { - $this->loaded[$class] = true; - if ($file = $this->classLoader[0]->findFile($class)) { - require $file; - } - } else { - call_user_func($this->classLoader, $class); - $file = false; - } - } finally { - ErrorHandler::unstackErrors(); - } - - $exists = class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false); - - if ($class && '\\' === $class[0]) { - $class = substr($class, 1); - } - - if ($exists) { - $refl = new \ReflectionClass($class); - $name = $refl->getName(); - - if ($name !== $class && 0 === strcasecmp($name, $class)) { - throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: "%s" vs "%s".', $class, $name)); - } - - $parent = get_parent_class($class); - - // Not an interface nor a trait - if (class_exists($name, false)) { - if (preg_match('#\n \* @final(?:( .+?)\.?)?\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) { - self::$final[$name] = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : ''; - } - - if ($parent && isset(self::$final[$parent])) { - @trigger_error(sprintf('The "%s" class is considered final%s. It may change without further notice as of its next major version. You should not extend it from "%s".', $parent, self::$final[$parent], $name), E_USER_DEPRECATED); - } - - // Inherit @final annotations - self::$finalMethods[$name] = $parent && isset(self::$finalMethods[$parent]) ? self::$finalMethods[$parent] : array(); - - foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) { - if ($method->class !== $name) { - continue; - } - - if ($parent && isset(self::$finalMethods[$parent][$method->name])) { - @trigger_error(sprintf('%s It may change without further notice as of its next major version. You should not extend it from "%s".', self::$finalMethods[$parent][$method->name], $name), E_USER_DEPRECATED); - } - - $doc = $method->getDocComment(); - if (false === $doc || false === strpos($doc, '@final')) { - continue; - } - - if (preg_match('#\n\s+\* @final(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$)#s', $doc, $notice)) { - $message = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : ''; - self::$finalMethods[$name][$method->name] = sprintf('The "%s::%s()" method is considered final%s.', $name, $method->name, $message); - } - } - } - - if (in_array(strtolower($refl->getShortName()), self::$php7Reserved)) { - @trigger_error(sprintf('The "%s" class uses the reserved name "%s", it will break on PHP 7 and higher', $name, $refl->getShortName()), E_USER_DEPRECATED); - } elseif (preg_match('#\n \* @deprecated (.*?)\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) { - self::$deprecated[$name] = preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]); - } else { - // Don't trigger deprecations for classes in the same vendor - if (2 > $len = 1 + (strpos($name, '\\') ?: strpos($name, '_'))) { - $len = 0; - $ns = ''; - } else { - $ns = substr($name, 0, $len); - } - - if (!$parent || strncmp($ns, $parent, $len)) { - if ($parent && isset(self::$deprecated[$parent]) && strncmp($ns, $parent, $len)) { - @trigger_error(sprintf('The "%s" class extends "%s" that is deprecated %s', $name, $parent, self::$deprecated[$parent]), E_USER_DEPRECATED); - } - - $parentInterfaces = array(); - $deprecatedInterfaces = array(); - if ($parent) { - foreach (class_implements($parent) as $interface) { - $parentInterfaces[$interface] = 1; - } - } - - foreach ($refl->getInterfaceNames() as $interface) { - if (isset(self::$deprecated[$interface]) && strncmp($ns, $interface, $len)) { - $deprecatedInterfaces[] = $interface; - } - foreach (class_implements($interface) as $interface) { - $parentInterfaces[$interface] = 1; - } - } - - foreach ($deprecatedInterfaces as $interface) { - if (!isset($parentInterfaces[$interface])) { - @trigger_error(sprintf('The "%s" %s "%s" that is deprecated %s', $name, $refl->isInterface() ? 'interface extends' : 'class implements', $interface, self::$deprecated[$interface]), E_USER_DEPRECATED); - } - } - } - } - } - - if ($file) { - if (!$exists) { - if (false !== strpos($class, '/')) { - throw new \RuntimeException(sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\" in PHP, not "/".', $class)); - } - - throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file)); - } - if (self::$caseCheck) { - $real = explode('\\', $class.strrchr($file, '.')); - $tail = explode(DIRECTORY_SEPARATOR, str_replace('/', DIRECTORY_SEPARATOR, $file)); - - $i = count($tail) - 1; - $j = count($real) - 1; - - while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) { - --$i; - --$j; - } - - array_splice($tail, 0, $i + 1); - } - if (self::$caseCheck && $tail) { - $tail = DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $tail); - $tailLen = strlen($tail); - $real = $refl->getFileName(); - - if (2 === self::$caseCheck) { - // realpath() on MacOSX doesn't normalize the case of characters - - $i = 1 + strrpos($real, '/'); - $file = substr($real, $i); - $real = substr($real, 0, $i); - - if (isset(self::$darwinCache[$real])) { - $kDir = $real; - } else { - $kDir = strtolower($real); - - if (isset(self::$darwinCache[$kDir])) { - $real = self::$darwinCache[$kDir][0]; - } else { - $dir = getcwd(); - chdir($real); - $real = getcwd().'/'; - chdir($dir); - - $dir = $real; - $k = $kDir; - $i = strlen($dir) - 1; - while (!isset(self::$darwinCache[$k])) { - self::$darwinCache[$k] = array($dir, array()); - self::$darwinCache[$dir] = &self::$darwinCache[$k]; - - while ('/' !== $dir[--$i]) { - } - $k = substr($k, 0, ++$i); - $dir = substr($dir, 0, $i--); - } - } - } - - $dirFiles = self::$darwinCache[$kDir][1]; - - if (isset($dirFiles[$file])) { - $kFile = $file; - } else { - $kFile = strtolower($file); - - if (!isset($dirFiles[$kFile])) { - foreach (scandir($real, 2) as $f) { - if ('.' !== $f[0]) { - $dirFiles[$f] = $f; - if ($f === $file) { - $kFile = $k = $file; - } elseif ($f !== $k = strtolower($f)) { - $dirFiles[$k] = $f; - } - } - } - self::$darwinCache[$kDir][1] = $dirFiles; - } - } - - $real .= $dirFiles[$kFile]; - } - - if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true) - && 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false) - ) { - throw new \RuntimeException(sprintf('Case mismatch between class and real file names: "%s" vs "%s" in "%s".', substr($tail, -$tailLen + 1), substr($real, -$tailLen + 1), substr($real, 0, -$tailLen + 1))); - } - } - - return true; - } - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Debug; - -use Psr\Log\LogLevel; -use Psr\Log\LoggerInterface; -use Symfony\Component\Debug\Exception\ContextErrorException; -use Symfony\Component\Debug\Exception\FatalErrorException; -use Symfony\Component\Debug\Exception\FatalThrowableError; -use Symfony\Component\Debug\Exception\OutOfMemoryException; -use Symfony\Component\Debug\Exception\SilencedErrorContext; -use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler; -use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler; -use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler; -use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface; - -/** - * A generic ErrorHandler for the PHP engine. - * - * Provides five bit fields that control how errors are handled: - * - thrownErrors: errors thrown as \ErrorException - * - loggedErrors: logged errors, when not @-silenced - * - scopedErrors: errors thrown or logged with their local context - * - tracedErrors: errors logged with their stack trace - * - screamedErrors: never @-silenced errors - * - * Each error level can be logged by a dedicated PSR-3 logger object. - * Screaming only applies to logging. - * Throwing takes precedence over logging. - * Uncaught exceptions are logged as E_ERROR. - * E_DEPRECATED and E_USER_DEPRECATED levels never throw. - * E_RECOVERABLE_ERROR and E_USER_ERROR levels always throw. - * Non catchable errors that can be detected at shutdown time are logged when the scream bit field allows so. - * As errors have a performance cost, repeated errors are all logged, so that the developer - * can see them and weight them as more important to fix than others of the same level. - * - * @author Nicolas Grekas - * @author Grégoire Pineau - */ -class ErrorHandler -{ - private $levels = array( - E_DEPRECATED => 'Deprecated', - E_USER_DEPRECATED => 'User Deprecated', - E_NOTICE => 'Notice', - E_USER_NOTICE => 'User Notice', - E_STRICT => 'Runtime Notice', - E_WARNING => 'Warning', - E_USER_WARNING => 'User Warning', - E_COMPILE_WARNING => 'Compile Warning', - E_CORE_WARNING => 'Core Warning', - E_USER_ERROR => 'User Error', - E_RECOVERABLE_ERROR => 'Catchable Fatal Error', - E_COMPILE_ERROR => 'Compile Error', - E_PARSE => 'Parse Error', - E_ERROR => 'Error', - E_CORE_ERROR => 'Core Error', - ); - - private $loggers = array( - E_DEPRECATED => array(null, LogLevel::INFO), - E_USER_DEPRECATED => array(null, LogLevel::INFO), - E_NOTICE => array(null, LogLevel::WARNING), - E_USER_NOTICE => array(null, LogLevel::WARNING), - E_STRICT => array(null, LogLevel::WARNING), - E_WARNING => array(null, LogLevel::WARNING), - E_USER_WARNING => array(null, LogLevel::WARNING), - E_COMPILE_WARNING => array(null, LogLevel::WARNING), - E_CORE_WARNING => array(null, LogLevel::WARNING), - E_USER_ERROR => array(null, LogLevel::CRITICAL), - E_RECOVERABLE_ERROR => array(null, LogLevel::CRITICAL), - E_COMPILE_ERROR => array(null, LogLevel::CRITICAL), - E_PARSE => array(null, LogLevel::CRITICAL), - E_ERROR => array(null, LogLevel::CRITICAL), - E_CORE_ERROR => array(null, LogLevel::CRITICAL), - ); - - private $thrownErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED - private $scopedErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED - private $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE - private $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE - private $loggedErrors = 0; - private $traceReflector; - - private $isRecursive = 0; - private $isRoot = false; - private $exceptionHandler; - private $bootstrappingLogger; - - private static $reservedMemory; - private static $stackedErrors = array(); - private static $stackedErrorLevels = array(); - private static $toStringException = null; - private static $silencedErrorCache = array(); - private static $silencedErrorCount = 0; - private static $exitCode = 0; - - /** - * Registers the error handler. - * - * @param self|null $handler The handler to register - * @param bool $replace Whether to replace or not any existing handler - * - * @return self The registered error handler - */ - public static function register(self $handler = null, $replace = true) - { - if (null === self::$reservedMemory) { - self::$reservedMemory = str_repeat('x', 10240); - register_shutdown_function(__CLASS__.'::handleFatalError'); - } - - if ($handlerIsNew = null === $handler) { - $handler = new static(); - } - - if (null === $prev = set_error_handler(array($handler, 'handleError'))) { - restore_error_handler(); - // Specifying the error types earlier would expose us to https://bugs.php.net/63206 - set_error_handler(array($handler, 'handleError'), $handler->thrownErrors | $handler->loggedErrors); - $handler->isRoot = true; - } - - if ($handlerIsNew && is_array($prev) && $prev[0] instanceof self) { - $handler = $prev[0]; - $replace = false; - } - if ($replace || !$prev) { - $handler->setExceptionHandler(set_exception_handler(array($handler, 'handleException'))); - } else { - restore_error_handler(); - } - - $handler->throwAt(E_ALL & $handler->thrownErrors, true); - - return $handler; - } - - public function __construct(BufferingLogger $bootstrappingLogger = null) - { - if ($bootstrappingLogger) { - $this->bootstrappingLogger = $bootstrappingLogger; - $this->setDefaultLogger($bootstrappingLogger); - } - $this->traceReflector = new \ReflectionProperty('Exception', 'trace'); - $this->traceReflector->setAccessible(true); - } - - /** - * Sets a logger to non assigned errors levels. - * - * @param LoggerInterface $logger A PSR-3 logger to put as default for the given levels - * @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants - * @param bool $replace Whether to replace or not any existing logger - */ - public function setDefaultLogger(LoggerInterface $logger, $levels = E_ALL, $replace = false) - { - $loggers = array(); - - if (is_array($levels)) { - foreach ($levels as $type => $logLevel) { - if (empty($this->loggers[$type][0]) || $replace || $this->loggers[$type][0] === $this->bootstrappingLogger) { - $loggers[$type] = array($logger, $logLevel); - } - } - } else { - if (null === $levels) { - $levels = E_ALL; - } - foreach ($this->loggers as $type => $log) { - if (($type & $levels) && (empty($log[0]) || $replace || $log[0] === $this->bootstrappingLogger)) { - $log[0] = $logger; - $loggers[$type] = $log; - } - } - } - - $this->setLoggers($loggers); - } - - /** - * Sets a logger for each error level. - * - * @param array $loggers Error levels to [LoggerInterface|null, LogLevel::*] map - * - * @return array The previous map - * - * @throws \InvalidArgumentException - */ - public function setLoggers(array $loggers) - { - $prevLogged = $this->loggedErrors; - $prev = $this->loggers; - $flush = array(); - - foreach ($loggers as $type => $log) { - if (!isset($prev[$type])) { - throw new \InvalidArgumentException('Unknown error type: '.$type); - } - if (!is_array($log)) { - $log = array($log); - } elseif (!array_key_exists(0, $log)) { - throw new \InvalidArgumentException('No logger provided'); - } - if (null === $log[0]) { - $this->loggedErrors &= ~$type; - } elseif ($log[0] instanceof LoggerInterface) { - $this->loggedErrors |= $type; - } else { - throw new \InvalidArgumentException('Invalid logger provided'); - } - $this->loggers[$type] = $log + $prev[$type]; - - if ($this->bootstrappingLogger && $prev[$type][0] === $this->bootstrappingLogger) { - $flush[$type] = $type; - } - } - $this->reRegister($prevLogged | $this->thrownErrors); - - if ($flush) { - foreach ($this->bootstrappingLogger->cleanLogs() as $log) { - $type = $log[2]['exception'] instanceof \ErrorException ? $log[2]['exception']->getSeverity() : E_ERROR; - if (!isset($flush[$type])) { - $this->bootstrappingLogger->log($log[0], $log[1], $log[2]); - } elseif ($this->loggers[$type][0]) { - $this->loggers[$type][0]->log($this->loggers[$type][1], $log[1], $log[2]); - } - } - } - - return $prev; - } - - /** - * Sets a user exception handler. - * - * @param callable $handler A handler that will be called on Exception - * - * @return callable|null The previous exception handler - */ - public function setExceptionHandler(callable $handler = null) - { - $prev = $this->exceptionHandler; - $this->exceptionHandler = $handler; - - return $prev; - } - - /** - * Sets the PHP error levels that throw an exception when a PHP error occurs. - * - * @param int $levels A bit field of E_* constants for thrown errors - * @param bool $replace Replace or amend the previous value - * - * @return int The previous value - */ - public function throwAt($levels, $replace = false) - { - $prev = $this->thrownErrors; - $this->thrownErrors = ($levels | E_RECOVERABLE_ERROR | E_USER_ERROR) & ~E_USER_DEPRECATED & ~E_DEPRECATED; - if (!$replace) { - $this->thrownErrors |= $prev; - } - $this->reRegister($prev | $this->loggedErrors); - - return $prev; - } - - /** - * Sets the PHP error levels for which local variables are preserved. - * - * @param int $levels A bit field of E_* constants for scoped errors - * @param bool $replace Replace or amend the previous value - * - * @return int The previous value - */ - public function scopeAt($levels, $replace = false) - { - $prev = $this->scopedErrors; - $this->scopedErrors = (int) $levels; - if (!$replace) { - $this->scopedErrors |= $prev; - } - - return $prev; - } - - /** - * Sets the PHP error levels for which the stack trace is preserved. - * - * @param int $levels A bit field of E_* constants for traced errors - * @param bool $replace Replace or amend the previous value - * - * @return int The previous value - */ - public function traceAt($levels, $replace = false) - { - $prev = $this->tracedErrors; - $this->tracedErrors = (int) $levels; - if (!$replace) { - $this->tracedErrors |= $prev; - } - - return $prev; - } - - /** - * Sets the error levels where the @-operator is ignored. - * - * @param int $levels A bit field of E_* constants for screamed errors - * @param bool $replace Replace or amend the previous value - * - * @return int The previous value - */ - public function screamAt($levels, $replace = false) - { - $prev = $this->screamedErrors; - $this->screamedErrors = (int) $levels; - if (!$replace) { - $this->screamedErrors |= $prev; - } - - return $prev; - } - - /** - * Re-registers as a PHP error handler if levels changed. - */ - private function reRegister($prev) - { - if ($prev !== $this->thrownErrors | $this->loggedErrors) { - $handler = set_error_handler('var_dump'); - $handler = is_array($handler) ? $handler[0] : null; - restore_error_handler(); - if ($handler === $this) { - restore_error_handler(); - if ($this->isRoot) { - set_error_handler(array($this, 'handleError'), $this->thrownErrors | $this->loggedErrors); - } else { - set_error_handler(array($this, 'handleError')); - } - } - } - } - - /** - * Handles errors by filtering then logging them according to the configured bit fields. - * - * @param int $type One of the E_* constants - * @param string $message - * @param string $file - * @param int $line - * - * @return bool Returns false when no handling happens so that the PHP engine can handle the error itself - * - * @throws \ErrorException When $this->thrownErrors requests so - * - * @internal - */ - public function handleError($type, $message, $file, $line) - { - // Level is the current error reporting level to manage silent error. - // Strong errors are not authorized to be silenced. - $level = error_reporting() | E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED; - $log = $this->loggedErrors & $type; - $throw = $this->thrownErrors & $type & $level; - $type &= $level | $this->screamedErrors; - - if (!$type || (!$log && !$throw)) { - return $type && $log; - } - $scope = $this->scopedErrors & $type; - - if (4 < $numArgs = func_num_args()) { - $context = $scope ? (func_get_arg(4) ?: array()) : array(); - $backtrace = 5 < $numArgs ? func_get_arg(5) : null; // defined on HHVM - } else { - $context = array(); - $backtrace = null; - } - - if (isset($context['GLOBALS']) && $scope) { - $e = $context; // Whatever the signature of the method, - unset($e['GLOBALS'], $context); // $context is always a reference in 5.3 - $context = $e; - } - - if (null !== $backtrace && $type & E_ERROR) { - // E_ERROR fatal errors are triggered on HHVM when - // hhvm.error_handling.call_user_handler_on_fatals=1 - // which is the way to get their backtrace. - $this->handleFatalError(compact('type', 'message', 'file', 'line', 'backtrace')); - - return true; - } - - $logMessage = $this->levels[$type].': '.$message; - - if (null !== self::$toStringException) { - $errorAsException = self::$toStringException; - self::$toStringException = null; - } elseif (!$throw && !($type & $level)) { - if (isset(self::$silencedErrorCache[$message])) { - $lightTrace = null; - $errorAsException = self::$silencedErrorCache[$message]; - ++$errorAsException->count; - } else { - $lightTrace = $this->tracedErrors & $type ? $this->cleanTrace(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3), $type, $file, $line, false) : array(); - $errorAsException = new SilencedErrorContext($type, $file, $line, $lightTrace); - } - - if (100 < ++self::$silencedErrorCount) { - self::$silencedErrorCache = $lightTrace = array(); - self::$silencedErrorCount = 1; - } - self::$silencedErrorCache[$message] = $errorAsException; - - if (null === $lightTrace) { - return; - } - } else { - if ($scope) { - $errorAsException = new ContextErrorException($logMessage, 0, $type, $file, $line, $context); - } else { - $errorAsException = new \ErrorException($logMessage, 0, $type, $file, $line); - } - - // Clean the trace by removing function arguments and the first frames added by the error handler itself. - if ($throw || $this->tracedErrors & $type) { - $backtrace = $backtrace ?: $errorAsException->getTrace(); - $lightTrace = $this->cleanTrace($backtrace, $type, $file, $line, $throw); - $this->traceReflector->setValue($errorAsException, $lightTrace); - } else { - $this->traceReflector->setValue($errorAsException, array()); - } - } - - if ($throw) { - if (E_USER_ERROR & $type) { - for ($i = 1; isset($backtrace[$i]); ++$i) { - if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function']) - && '__toString' === $backtrace[$i]['function'] - && '->' === $backtrace[$i]['type'] - && !isset($backtrace[$i - 1]['class']) - && ('trigger_error' === $backtrace[$i - 1]['function'] || 'user_error' === $backtrace[$i - 1]['function']) - ) { - // Here, we know trigger_error() has been called from __toString(). - // HHVM is fine with throwing from __toString() but PHP triggers a fatal error instead. - // A small convention allows working around the limitation: - // given a caught $e exception in __toString(), quitting the method with - // `return trigger_error($e, E_USER_ERROR);` allows this error handler - // to make $e get through the __toString() barrier. - - foreach ($context as $e) { - if (($e instanceof \Exception || $e instanceof \Throwable) && $e->__toString() === $message) { - if (1 === $i) { - // On HHVM - $errorAsException = $e; - break; - } - self::$toStringException = $e; - - return true; - } - } - - if (1 < $i) { - // On PHP (not on HHVM), display the original error message instead of the default one. - $this->handleException($errorAsException); - - // Stop the process by giving back the error to the native handler. - return false; - } - } - } - } - - throw $errorAsException; - } - - if ($this->isRecursive) { - $log = 0; - } elseif (self::$stackedErrorLevels) { - self::$stackedErrors[] = array( - $this->loggers[$type][0], - ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG, - $logMessage, - array('exception' => $errorAsException), - ); - } else { - try { - $this->isRecursive = true; - $level = ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG; - $this->loggers[$type][0]->log($level, $logMessage, array('exception' => $errorAsException)); - } finally { - $this->isRecursive = false; - } - } - - return $type && $log; - } - - /** - * Handles an exception by logging then forwarding it to another handler. - * - * @param \Exception|\Throwable $exception An exception to handle - * @param array $error An array as returned by error_get_last() - * - * @internal - */ - public function handleException($exception, array $error = null) - { - if (null === $error) { - self::$exitCode = 255; - } - if (!$exception instanceof \Exception) { - $exception = new FatalThrowableError($exception); - } - $type = $exception instanceof FatalErrorException ? $exception->getSeverity() : E_ERROR; - - if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) { - if ($exception instanceof FatalErrorException) { - if ($exception instanceof FatalThrowableError) { - $error = array( - 'type' => $type, - 'message' => $message = $exception->getMessage(), - 'file' => $exception->getFile(), - 'line' => $exception->getLine(), - ); - } else { - $message = 'Fatal '.$exception->getMessage(); - } - } elseif ($exception instanceof \ErrorException) { - $message = 'Uncaught '.$exception->getMessage(); - } else { - $message = 'Uncaught Exception: '.$exception->getMessage(); - } - } - if ($this->loggedErrors & $type) { - try { - $this->loggers[$type][0]->log($this->loggers[$type][1], $message, array('exception' => $exception)); - } catch (\Exception $handlerException) { - } catch (\Throwable $handlerException) { - } - } - if ($exception instanceof FatalErrorException && !$exception instanceof OutOfMemoryException && $error) { - foreach ($this->getFatalErrorHandlers() as $handler) { - if ($e = $handler->handleError($error, $exception)) { - $exception = $e; - break; - } - } - } - if (empty($this->exceptionHandler)) { - throw $exception; // Give back $exception to the native handler - } - try { - call_user_func($this->exceptionHandler, $exception); - } catch (\Exception $handlerException) { - } catch (\Throwable $handlerException) { - } - if (isset($handlerException)) { - $this->exceptionHandler = null; - $this->handleException($handlerException); - } - } - - /** - * Shutdown registered function for handling PHP fatal errors. - * - * @param array $error An array as returned by error_get_last() - * - * @internal - */ - public static function handleFatalError(array $error = null) - { - if (null === self::$reservedMemory) { - return; - } - - self::$reservedMemory = null; - - $handler = set_error_handler('var_dump'); - $handler = is_array($handler) ? $handler[0] : null; - restore_error_handler(); - - if (!$handler instanceof self) { - return; - } - - if ($exit = null === $error) { - $error = error_get_last(); - } - - try { - while (self::$stackedErrorLevels) { - static::unstackErrors(); - } - } catch (\Exception $exception) { - // Handled below - } catch (\Throwable $exception) { - // Handled below - } - - if ($error && $error['type'] &= E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR) { - // Let's not throw anymore but keep logging - $handler->throwAt(0, true); - $trace = isset($error['backtrace']) ? $error['backtrace'] : null; - - if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) { - $exception = new OutOfMemoryException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, false, $trace); - } else { - $exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true, $trace); - } - } - - try { - if (isset($exception)) { - self::$exitCode = 255; - $handler->handleException($exception, $error); - } - } catch (FatalErrorException $e) { - // Ignore this re-throw - } - - if ($exit && self::$exitCode) { - $exitCode = self::$exitCode; - register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); }); - } - } - - /** - * Configures the error handler for delayed handling. - * Ensures also that non-catchable fatal errors are never silenced. - * - * As shown by http://bugs.php.net/42098 and http://bugs.php.net/60724 - * PHP has a compile stage where it behaves unusually. To workaround it, - * we plug an error handler that only stacks errors for later. - * - * The most important feature of this is to prevent - * autoloading until unstackErrors() is called. - */ - public static function stackErrors() - { - self::$stackedErrorLevels[] = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR); - } - - /** - * Unstacks stacked errors and forwards to the logger. - */ - public static function unstackErrors() - { - $level = array_pop(self::$stackedErrorLevels); - - if (null !== $level) { - $errorReportingLevel = error_reporting($level); - if ($errorReportingLevel !== ($level | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR)) { - // If the user changed the error level, do not overwrite it - error_reporting($errorReportingLevel); - } - } - - if (empty(self::$stackedErrorLevels)) { - $errors = self::$stackedErrors; - self::$stackedErrors = array(); - - foreach ($errors as $error) { - $error[0]->log($error[1], $error[2], $error[3]); - } - } - } - - /** - * Gets the fatal error handlers. - * - * Override this method if you want to define more fatal error handlers. - * - * @return FatalErrorHandlerInterface[] An array of FatalErrorHandlerInterface - */ - protected function getFatalErrorHandlers() - { - return array( - new UndefinedFunctionFatalErrorHandler(), - new UndefinedMethodFatalErrorHandler(), - new ClassNotFoundFatalErrorHandler(), - ); - } - - private function cleanTrace($backtrace, $type, $file, $line, $throw) - { - $lightTrace = $backtrace; - - for ($i = 0; isset($backtrace[$i]); ++$i) { - if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) { - $lightTrace = array_slice($lightTrace, 1 + $i); - break; - } - } - if (!($throw || $this->scopedErrors & $type)) { - for ($i = 0; isset($lightTrace[$i]); ++$i) { - unset($lightTrace[$i]['args'], $lightTrace[$i]['object']); - } - } - - return $lightTrace; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Debug\Exception; - -/** - * Class (or Trait or Interface) Not Found Exception. - * - * @author Konstanton Myakshin - */ -class ClassNotFoundException extends FatalErrorException -{ - public function __construct($message, \ErrorException $previous) - { - parent::__construct( - $message, - $previous->getCode(), - $previous->getSeverity(), - $previous->getFile(), - $previous->getLine(), - $previous->getPrevious() - ); - $this->setTrace($previous->getTrace()); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Debug\Exception; - -/** - * Error Exception with Variable Context. - * - * @author Christian Sciberras - * - * @deprecated since version 3.3. Instead, \ErrorException will be used directly in 4.0. - */ -class ContextErrorException extends \ErrorException -{ - private $context = array(); - - public function __construct($message, $code, $severity, $filename, $lineno, $context = array()) - { - parent::__construct($message, $code, $severity, $filename, $lineno); - $this->context = $context; - } - - /** - * @return array Array of variables that existed when the exception occurred - */ - public function getContext() - { - @trigger_error(sprintf('The %s class is deprecated since version 3.3 and will be removed in 4.0.', __CLASS__), E_USER_DEPRECATED); - - return $this->context; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Debug\Exception; - -/** - * Fatal Error Exception. - * - * @author Konstanton Myakshin - */ -class FatalErrorException extends \ErrorException -{ - public function __construct($message, $code, $severity, $filename, $lineno, $traceOffset = null, $traceArgs = true, array $trace = null) - { - parent::__construct($message, $code, $severity, $filename, $lineno); - - if (null !== $trace) { - if (!$traceArgs) { - foreach ($trace as &$frame) { - unset($frame['args'], $frame['this'], $frame); - } - } - - $this->setTrace($trace); - } elseif (null !== $traceOffset) { - if (function_exists('xdebug_get_function_stack')) { - $trace = xdebug_get_function_stack(); - if (0 < $traceOffset) { - array_splice($trace, -$traceOffset); - } - - foreach ($trace as &$frame) { - if (!isset($frame['type'])) { - // XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695 - if (isset($frame['class'])) { - $frame['type'] = '::'; - } - } elseif ('dynamic' === $frame['type']) { - $frame['type'] = '->'; - } elseif ('static' === $frame['type']) { - $frame['type'] = '::'; - } - - // XDebug also has a different name for the parameters array - if (!$traceArgs) { - unset($frame['params'], $frame['args']); - } elseif (isset($frame['params']) && !isset($frame['args'])) { - $frame['args'] = $frame['params']; - unset($frame['params']); - } - } - - unset($frame); - $trace = array_reverse($trace); - } elseif (function_exists('symfony_debug_backtrace')) { - $trace = symfony_debug_backtrace(); - if (0 < $traceOffset) { - array_splice($trace, 0, $traceOffset); - } - } else { - $trace = array(); - } - - $this->setTrace($trace); - } - } - - protected function setTrace($trace) - { - $traceReflector = new \ReflectionProperty('Exception', 'trace'); - $traceReflector->setAccessible(true); - $traceReflector->setValue($this, $trace); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Debug\Exception; - -/** - * Fatal Throwable Error. - * - * @author Nicolas Grekas - */ -class FatalThrowableError extends FatalErrorException -{ - public function __construct(\Throwable $e) - { - if ($e instanceof \ParseError) { - $message = 'Parse error: '.$e->getMessage(); - $severity = E_PARSE; - } elseif ($e instanceof \TypeError) { - $message = 'Type error: '.$e->getMessage(); - $severity = E_RECOVERABLE_ERROR; - } else { - $message = $e->getMessage(); - $severity = E_ERROR; - } - - \ErrorException::__construct( - $message, - $e->getCode(), - $severity, - $e->getFile(), - $e->getLine() - ); - - $this->setTrace($e->getTrace()); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Debug\Exception; - -use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface; -use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; - -/** - * FlattenException wraps a PHP Exception to be able to serialize it. - * - * Basically, this class removes all objects from the trace. - * - * @author Fabien Potencier - */ -class FlattenException -{ - private $message; - private $code; - private $previous; - private $trace; - private $class; - private $statusCode; - private $headers; - private $file; - private $line; - - public static function create(\Exception $exception, $statusCode = null, array $headers = array()) - { - $e = new static(); - $e->setMessage($exception->getMessage()); - $e->setCode($exception->getCode()); - - if ($exception instanceof HttpExceptionInterface) { - $statusCode = $exception->getStatusCode(); - $headers = array_merge($headers, $exception->getHeaders()); - } elseif ($exception instanceof RequestExceptionInterface) { - $statusCode = 400; - } - - if (null === $statusCode) { - $statusCode = 500; - } - - $e->setStatusCode($statusCode); - $e->setHeaders($headers); - $e->setTraceFromException($exception); - $e->setClass(get_class($exception)); - $e->setFile($exception->getFile()); - $e->setLine($exception->getLine()); - - $previous = $exception->getPrevious(); - - if ($previous instanceof \Exception) { - $e->setPrevious(static::create($previous)); - } elseif ($previous instanceof \Throwable) { - $e->setPrevious(static::create(new FatalThrowableError($previous))); - } - - return $e; - } - - public function toArray() - { - $exceptions = array(); - foreach (array_merge(array($this), $this->getAllPrevious()) as $exception) { - $exceptions[] = array( - 'message' => $exception->getMessage(), - 'class' => $exception->getClass(), - 'trace' => $exception->getTrace(), - ); - } - - return $exceptions; - } - - public function getStatusCode() - { - return $this->statusCode; - } - - public function setStatusCode($code) - { - $this->statusCode = $code; - } - - public function getHeaders() - { - return $this->headers; - } - - public function setHeaders(array $headers) - { - $this->headers = $headers; - } - - public function getClass() - { - return $this->class; - } - - public function setClass($class) - { - $this->class = $class; - } - - public function getFile() - { - return $this->file; - } - - public function setFile($file) - { - $this->file = $file; - } - - public function getLine() - { - return $this->line; - } - - public function setLine($line) - { - $this->line = $line; - } - - public function getMessage() - { - return $this->message; - } - - public function setMessage($message) - { - $this->message = $message; - } - - public function getCode() - { - return $this->code; - } - - public function setCode($code) - { - $this->code = $code; - } - - public function getPrevious() - { - return $this->previous; - } - - public function setPrevious(FlattenException $previous) - { - $this->previous = $previous; - } - - public function getAllPrevious() - { - $exceptions = array(); - $e = $this; - while ($e = $e->getPrevious()) { - $exceptions[] = $e; - } - - return $exceptions; - } - - public function getTrace() - { - return $this->trace; - } - - public function setTraceFromException(\Exception $exception) - { - $this->setTrace($exception->getTrace(), $exception->getFile(), $exception->getLine()); - } - - public function setTrace($trace, $file, $line) - { - $this->trace = array(); - $this->trace[] = array( - 'namespace' => '', - 'short_class' => '', - 'class' => '', - 'type' => '', - 'function' => '', - 'file' => $file, - 'line' => $line, - 'args' => array(), - ); - foreach ($trace as $entry) { - $class = ''; - $namespace = ''; - if (isset($entry['class'])) { - $parts = explode('\\', $entry['class']); - $class = array_pop($parts); - $namespace = implode('\\', $parts); - } - - $this->trace[] = array( - 'namespace' => $namespace, - 'short_class' => $class, - 'class' => isset($entry['class']) ? $entry['class'] : '', - 'type' => isset($entry['type']) ? $entry['type'] : '', - 'function' => isset($entry['function']) ? $entry['function'] : null, - 'file' => isset($entry['file']) ? $entry['file'] : null, - 'line' => isset($entry['line']) ? $entry['line'] : null, - 'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : array(), - ); - } - } - - private function flattenArgs($args, $level = 0, &$count = 0) - { - $result = array(); - foreach ($args as $key => $value) { - if (++$count > 1e4) { - return array('array', '*SKIPPED over 10000 entries*'); - } - if ($value instanceof \__PHP_Incomplete_Class) { - // is_object() returns false on PHP<=7.1 - $result[$key] = array('incomplete-object', $this->getClassNameFromIncomplete($value)); - } elseif (is_object($value)) { - $result[$key] = array('object', get_class($value)); - } elseif (is_array($value)) { - if ($level > 10) { - $result[$key] = array('array', '*DEEP NESTED ARRAY*'); - } else { - $result[$key] = array('array', $this->flattenArgs($value, $level + 1, $count)); - } - } elseif (null === $value) { - $result[$key] = array('null', null); - } elseif (is_bool($value)) { - $result[$key] = array('boolean', $value); - } elseif (is_int($value)) { - $result[$key] = array('integer', $value); - } elseif (is_float($value)) { - $result[$key] = array('float', $value); - } elseif (is_resource($value)) { - $result[$key] = array('resource', get_resource_type($value)); - } else { - $result[$key] = array('string', (string) $value); - } - } - - return $result; - } - - private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value) - { - $array = new \ArrayObject($value); - - return $array['__PHP_Incomplete_Class_Name']; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Debug\Exception; - -/** - * Out of memory exception. - * - * @author Nicolas Grekas - */ -class OutOfMemoryException extends FatalErrorException -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Debug\Exception; - -/** - * Data Object that represents a Silenced Error. - * - * @author Grégoire Pineau - */ -class SilencedErrorContext implements \JsonSerializable -{ - public $count = 1; - - private $severity; - private $file; - private $line; - private $trace; - - public function __construct($severity, $file, $line, array $trace = array(), $count = 1) - { - $this->severity = $severity; - $this->file = $file; - $this->line = $line; - $this->trace = $trace; - $this->count = $count; - } - - public function getSeverity() - { - return $this->severity; - } - - public function getFile() - { - return $this->file; - } - - public function getLine() - { - return $this->line; - } - - public function getTrace() - { - return $this->trace; - } - - public function JsonSerialize() - { - return array( - 'severity' => $this->severity, - 'file' => $this->file, - 'line' => $this->line, - 'trace' => $this->trace, - 'count' => $this->count, - ); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Debug\Exception; - -/** - * Undefined Function Exception. - * - * @author Konstanton Myakshin - */ -class UndefinedFunctionException extends FatalErrorException -{ - public function __construct($message, \ErrorException $previous) - { - parent::__construct( - $message, - $previous->getCode(), - $previous->getSeverity(), - $previous->getFile(), - $previous->getLine(), - $previous->getPrevious() - ); - $this->setTrace($previous->getTrace()); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Debug\Exception; - -/** - * Undefined Method Exception. - * - * @author Grégoire Pineau - */ -class UndefinedMethodException extends FatalErrorException -{ - public function __construct($message, \ErrorException $previous) - { - parent::__construct( - $message, - $previous->getCode(), - $previous->getSeverity(), - $previous->getFile(), - $previous->getLine(), - $previous->getPrevious() - ); - $this->setTrace($previous->getTrace()); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Debug; - -use Symfony\Component\Debug\Exception\FlattenException; -use Symfony\Component\Debug\Exception\OutOfMemoryException; -use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; - -/** - * ExceptionHandler converts an exception to a Response object. - * - * It is mostly useful in debug mode to replace the default PHP/XDebug - * output with something prettier and more useful. - * - * As this class is mainly used during Kernel boot, where nothing is yet - * available, the Response content is always HTML. - * - * @author Fabien Potencier - * @author Nicolas Grekas - */ -class ExceptionHandler -{ - private $debug; - private $charset; - private $handler; - private $caughtBuffer; - private $caughtLength; - private $fileLinkFormat; - - public function __construct($debug = true, $charset = null, $fileLinkFormat = null) - { - $this->debug = $debug; - $this->charset = $charset ?: ini_get('default_charset') ?: 'UTF-8'; - $this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); - } - - /** - * Registers the exception handler. - * - * @param bool $debug Enable/disable debug mode, where the stack trace is displayed - * @param string|null $charset The charset used by exception messages - * @param string|null $fileLinkFormat The IDE link template - * - * @return static - */ - public static function register($debug = true, $charset = null, $fileLinkFormat = null) - { - $handler = new static($debug, $charset, $fileLinkFormat); - - $prev = set_exception_handler(array($handler, 'handle')); - if (is_array($prev) && $prev[0] instanceof ErrorHandler) { - restore_exception_handler(); - $prev[0]->setExceptionHandler(array($handler, 'handle')); - } - - return $handler; - } - - /** - * Sets a user exception handler. - * - * @param callable $handler An handler that will be called on Exception - * - * @return callable|null The previous exception handler if any - */ - public function setHandler(callable $handler = null) - { - $old = $this->handler; - $this->handler = $handler; - - return $old; - } - - /** - * Sets the format for links to source files. - * - * @param string|FileLinkFormatter $fileLinkFormat The format for links to source files - * - * @return string The previous file link format - */ - public function setFileLinkFormat($fileLinkFormat) - { - $old = $this->fileLinkFormat; - $this->fileLinkFormat = $fileLinkFormat; - - return $old; - } - - /** - * Sends a response for the given Exception. - * - * To be as fail-safe as possible, the exception is first handled - * by our simple exception handler, then by the user exception handler. - * The latter takes precedence and any output from the former is cancelled, - * if and only if nothing bad happens in this handling path. - */ - public function handle(\Exception $exception) - { - if (null === $this->handler || $exception instanceof OutOfMemoryException) { - $this->sendPhpResponse($exception); - - return; - } - - $caughtLength = $this->caughtLength = 0; - - ob_start(function ($buffer) { - $this->caughtBuffer = $buffer; - - return ''; - }); - - $this->sendPhpResponse($exception); - while (null === $this->caughtBuffer && ob_end_flush()) { - // Empty loop, everything is in the condition - } - if (isset($this->caughtBuffer[0])) { - ob_start(function ($buffer) { - if ($this->caughtLength) { - // use substr_replace() instead of substr() for mbstring overloading resistance - $cleanBuffer = substr_replace($buffer, '', 0, $this->caughtLength); - if (isset($cleanBuffer[0])) { - $buffer = $cleanBuffer; - } - } - - return $buffer; - }); - - echo $this->caughtBuffer; - $caughtLength = ob_get_length(); - } - $this->caughtBuffer = null; - - try { - call_user_func($this->handler, $exception); - $this->caughtLength = $caughtLength; - } catch (\Exception $e) { - if (!$caughtLength) { - // All handlers failed. Let PHP handle that now. - throw $exception; - } - } - } - - /** - * Sends the error associated with the given Exception as a plain PHP response. - * - * This method uses plain PHP functions like header() and echo to output - * the response. - * - * @param \Exception|FlattenException $exception An \Exception or FlattenException instance - */ - public function sendPhpResponse($exception) - { - if (!$exception instanceof FlattenException) { - $exception = FlattenException::create($exception); - } - - if (!headers_sent()) { - header(sprintf('HTTP/1.0 %s', $exception->getStatusCode())); - foreach ($exception->getHeaders() as $name => $value) { - header($name.': '.$value, false); - } - header('Content-Type: text/html; charset='.$this->charset); - } - - echo $this->decorate($this->getContent($exception), $this->getStylesheet($exception)); - } - - /** - * Gets the full HTML content associated with the given exception. - * - * @param \Exception|FlattenException $exception An \Exception or FlattenException instance - * - * @return string The HTML content as a string - */ - public function getHtml($exception) - { - if (!$exception instanceof FlattenException) { - $exception = FlattenException::create($exception); - } - - return $this->decorate($this->getContent($exception), $this->getStylesheet($exception)); - } - - /** - * Gets the HTML content associated with the given exception. - * - * @return string The content as a string - */ - public function getContent(FlattenException $exception) - { - switch ($exception->getStatusCode()) { - case 404: - $title = 'Sorry, the page you are looking for could not be found.'; - break; - default: - $title = 'Whoops, looks like something went wrong.'; - } - - $content = ''; - if ($this->debug) { - try { - $count = count($exception->getAllPrevious()); - $total = $count + 1; - foreach ($exception->toArray() as $position => $e) { - $ind = $count - $position + 1; - $class = $this->formatClass($e['class']); - $message = nl2br($this->escapeHtml($e['message'])); - $content .= sprintf(<<<'EOF' -

    \n"; - } - } catch (\Exception $e) { - // something nasty happened and we cannot throw an exception anymore - if ($this->debug) { - $title = sprintf('Exception thrown when handling an exception (%s: %s)', get_class($e), $this->escapeHtml($e->getMessage())); - } else { - $title = 'Whoops, looks like something went wrong.'; - } - } - } - - $symfonyGhostImageContents = $this->getSymfonyGhostAsSvg(); - - return << -
    -
    -

    $title

    -
    $symfonyGhostImageContents
    -
    -
    - - -
    - $content -
    -EOF; - } - - /** - * Gets the stylesheet associated with the given exception. - * - * @return string The stylesheet as a string - */ - public function getStylesheet(FlattenException $exception) - { - return <<<'EOF' - body { background-color: #F9F9F9; color: #222; font: 14px/1.4 Helvetica, Arial, sans-serif; margin: 0; padding-bottom: 45px; } - - a { cursor: pointer; text-decoration: none; } - a:hover { text-decoration: underline; } - abbr[title] { border-bottom: none; cursor: help; text-decoration: none; } - - code, pre { font: 13px/1.5 Consolas, Monaco, Menlo, "Ubuntu Mono", "Liberation Mono", monospace; } - - table, tr, th, td { background: #FFF; border-collapse: collapse; vertical-align: top; } - table { background: #FFF; border: 1px solid #E0E0E0; box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; width: 100%; } - table th, table td { border: solid #E0E0E0; border-width: 1px 0; padding: 8px 10px; } - table th { background-color: #E0E0E0; font-weight: bold; text-align: left; } - - .hidden-xs-down { display: none; } - .block { display: block; } - .break-long-words { -ms-word-break: break-all; word-break: break-all; word-break: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; } - .text-muted { color: #999; } - - .container { max-width: 1024px; margin: 0 auto; padding: 0 15px; } - .container::after { content: ""; display: table; clear: both; } - - .exception-summary { background: #B0413E; border-bottom: 2px solid rgba(0, 0, 0, 0.1); border-top: 1px solid rgba(0, 0, 0, .3); flex: 0 0 auto; margin-bottom: 30px; } - - .exception-message-wrapper { display: flex; align-items: center; min-height: 70px; } - .exception-message { flex-grow: 1; padding: 30px 0; } - .exception-message, .exception-message a { color: #FFF; font-size: 21px; font-weight: 400; margin: 0; } - .exception-message.long { font-size: 18px; } - .exception-message a { border-bottom: 1px solid rgba(255, 255, 255, 0.5); font-size: inherit; text-decoration: none; } - .exception-message a:hover { border-bottom-color: #ffffff; } - - .exception-illustration { flex-basis: 111px; flex-shrink: 0; height: 66px; margin-left: 15px; opacity: .7; } - - .trace + .trace { margin-top: 30px; } - .trace-head .trace-class { color: #222; font-size: 18px; font-weight: bold; line-height: 1.3; margin: 0; position: relative; } - - .trace-message { font-size: 14px; font-weight: normal; margin: .5em 0 0; } - - .trace-file-path, .trace-file-path a { color: #222; margin-top: 3px; font-size: 13px; } - .trace-class { color: #B0413E; } - .trace-type { padding: 0 2px; } - .trace-method { color: #B0413E; font-weight: bold; } - .trace-arguments { color: #777; font-weight: normal; padding-left: 2px; } - - @media (min-width: 575px) { - .hidden-xs-down { display: initial; } - } -EOF; - } - - private function decorate($content, $css) - { - return << - - - - - - - - $content - - -EOF; - } - - private function formatClass($class) - { - $parts = explode('\\', $class); - - return sprintf('%s', $class, array_pop($parts)); - } - - private function formatPath($path, $line) - { - $file = $this->escapeHtml(preg_match('#[^/\\\\]*+$#', $path, $file) ? $file[0] : $path); - $fmt = $this->fileLinkFormat; - - if ($fmt && $link = is_string($fmt) ? strtr($fmt, array('%f' => $path, '%l' => $line)) : $fmt->format($path, $line)) { - return sprintf('in
    %s (line %d)', $this->escapeHtml($link), $file, $line); - } - - return sprintf('in %s (line %d)', $this->escapeHtml($path), $file, $line); - } - - /** - * Formats an array as a string. - * - * @param array $args The argument array - * - * @return string - */ - private function formatArgs(array $args) - { - $result = array(); - foreach ($args as $key => $item) { - if ('object' === $item[0]) { - $formattedValue = sprintf('object(%s)', $this->formatClass($item[1])); - } elseif ('array' === $item[0]) { - $formattedValue = sprintf('array(%s)', is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); - } elseif ('null' === $item[0]) { - $formattedValue = 'null'; - } elseif ('boolean' === $item[0]) { - $formattedValue = ''.strtolower(var_export($item[1], true)).''; - } elseif ('resource' === $item[0]) { - $formattedValue = 'resource'; - } else { - $formattedValue = str_replace("\n", '', $this->escapeHtml(var_export($item[1], true))); - } - - $result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $this->escapeHtml($key), $formattedValue); - } - - return implode(', ', $result); - } - - /** - * HTML-encodes a string. - */ - private function escapeHtml($str) - { - return htmlspecialchars($str, ENT_COMPAT | ENT_SUBSTITUTE, $this->charset); - } - - private function getSymfonyGhostAsSvg() - { - return ''; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Debug\FatalErrorHandler; - -use Symfony\Component\Debug\Exception\ClassNotFoundException; -use Symfony\Component\Debug\Exception\FatalErrorException; -use Symfony\Component\Debug\DebugClassLoader; -use Composer\Autoload\ClassLoader as ComposerClassLoader; -use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader; - -/** - * ErrorHandler for classes that do not exist. - * - * @author Fabien Potencier - */ -class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface -{ - /** - * {@inheritdoc} - */ - public function handleError(array $error, FatalErrorException $exception) - { - $messageLen = strlen($error['message']); - $notFoundSuffix = '\' not found'; - $notFoundSuffixLen = strlen($notFoundSuffix); - if ($notFoundSuffixLen > $messageLen) { - return; - } - - if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) { - return; - } - - foreach (array('class', 'interface', 'trait') as $typeName) { - $prefix = ucfirst($typeName).' \''; - $prefixLen = strlen($prefix); - if (0 !== strpos($error['message'], $prefix)) { - continue; - } - - $fullyQualifiedClassName = substr($error['message'], $prefixLen, -$notFoundSuffixLen); - if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) { - $className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1); - $namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex); - $message = sprintf('Attempted to load %s "%s" from namespace "%s".', $typeName, $className, $namespacePrefix); - $tail = ' for another namespace?'; - } else { - $className = $fullyQualifiedClassName; - $message = sprintf('Attempted to load %s "%s" from the global namespace.', $typeName, $className); - $tail = '?'; - } - - if ($candidates = $this->getClassCandidates($className)) { - $tail = array_pop($candidates).'"?'; - if ($candidates) { - $tail = ' for e.g. "'.implode('", "', $candidates).'" or "'.$tail; - } else { - $tail = ' for "'.$tail; - } - } - $message .= "\nDid you forget a \"use\" statement".$tail; - - return new ClassNotFoundException($message, $exception); - } - } - - /** - * Tries to guess the full namespace for a given class name. - * - * By default, it looks for PSR-0 and PSR-4 classes registered via a Symfony or a Composer - * autoloader (that should cover all common cases). - * - * @param string $class A class name (without its namespace) - * - * @return array An array of possible fully qualified class names - */ - private function getClassCandidates($class) - { - if (!is_array($functions = spl_autoload_functions())) { - return array(); - } - - // find Symfony and Composer autoloaders - $classes = array(); - - foreach ($functions as $function) { - if (!is_array($function)) { - continue; - } - // get class loaders wrapped by DebugClassLoader - if ($function[0] instanceof DebugClassLoader) { - $function = $function[0]->getClassLoader(); - - if (!is_array($function)) { - continue; - } - } - - if ($function[0] instanceof ComposerClassLoader || $function[0] instanceof SymfonyClassLoader) { - foreach ($function[0]->getPrefixes() as $prefix => $paths) { - foreach ($paths as $path) { - $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix)); - } - } - } - if ($function[0] instanceof ComposerClassLoader) { - foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) { - foreach ($paths as $path) { - $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix)); - } - } - } - } - - return array_unique($classes); - } - - /** - * @param string $path - * @param string $class - * @param string $prefix - * - * @return array - */ - private function findClassInPath($path, $class, $prefix) - { - if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) { - return array(); - } - - $classes = array(); - $filename = $class.'.php'; - foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { - if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) { - $classes[] = $class; - } - } - - return $classes; - } - - /** - * @param string $path - * @param string $file - * @param string $prefix - * - * @return string|null - */ - private function convertFileToClass($path, $file, $prefix) - { - $candidates = array( - // namespaced class - $namespacedClass = str_replace(array($path.DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file), - // namespaced class (with target dir) - $prefix.$namespacedClass, - // namespaced class (with target dir and separator) - $prefix.'\\'.$namespacedClass, - // PEAR class - str_replace('\\', '_', $namespacedClass), - // PEAR class (with target dir) - str_replace('\\', '_', $prefix.$namespacedClass), - // PEAR class (with target dir and separator) - str_replace('\\', '_', $prefix.'\\'.$namespacedClass), - ); - - if ($prefix) { - $candidates = array_filter($candidates, function ($candidate) use ($prefix) { return 0 === strpos($candidate, $prefix); }); - } - - // We cannot use the autoloader here as most of them use require; but if the class - // is not found, the new autoloader call will require the file again leading to a - // "cannot redeclare class" error. - foreach ($candidates as $candidate) { - if ($this->classExists($candidate)) { - return $candidate; - } - } - - require_once $file; - - foreach ($candidates as $candidate) { - if ($this->classExists($candidate)) { - return $candidate; - } - } - } - - /** - * @param string $class - * - * @return bool - */ - private function classExists($class) - { - return class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Debug\FatalErrorHandler; - -use Symfony\Component\Debug\Exception\FatalErrorException; - -/** - * Attempts to convert fatal errors to exceptions. - * - * @author Fabien Potencier - */ -interface FatalErrorHandlerInterface -{ - /** - * Attempts to convert an error into an exception. - * - * @param array $error An array as returned by error_get_last() - * @param FatalErrorException $exception A FatalErrorException instance - * - * @return FatalErrorException|null A FatalErrorException instance if the class is able to convert the error, null otherwise - */ - public function handleError(array $error, FatalErrorException $exception); -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Debug\FatalErrorHandler; - -use Symfony\Component\Debug\Exception\UndefinedFunctionException; -use Symfony\Component\Debug\Exception\FatalErrorException; - -/** - * ErrorHandler for undefined functions. - * - * @author Fabien Potencier - */ -class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface -{ - /** - * {@inheritdoc} - */ - public function handleError(array $error, FatalErrorException $exception) - { - $messageLen = strlen($error['message']); - $notFoundSuffix = '()'; - $notFoundSuffixLen = strlen($notFoundSuffix); - if ($notFoundSuffixLen > $messageLen) { - return; - } - - if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) { - return; - } - - $prefix = 'Call to undefined function '; - $prefixLen = strlen($prefix); - if (0 !== strpos($error['message'], $prefix)) { - return; - } - - $fullyQualifiedFunctionName = substr($error['message'], $prefixLen, -$notFoundSuffixLen); - if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedFunctionName, '\\')) { - $functionName = substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1); - $namespacePrefix = substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex); - $message = sprintf('Attempted to call function "%s" from namespace "%s".', $functionName, $namespacePrefix); - } else { - $functionName = $fullyQualifiedFunctionName; - $message = sprintf('Attempted to call function "%s" from the global namespace.', $functionName); - } - - $candidates = array(); - foreach (get_defined_functions() as $type => $definedFunctionNames) { - foreach ($definedFunctionNames as $definedFunctionName) { - if (false !== $namespaceSeparatorIndex = strrpos($definedFunctionName, '\\')) { - $definedFunctionNameBasename = substr($definedFunctionName, $namespaceSeparatorIndex + 1); - } else { - $definedFunctionNameBasename = $definedFunctionName; - } - - if ($definedFunctionNameBasename === $functionName) { - $candidates[] = '\\'.$definedFunctionName; - } - } - } - - if ($candidates) { - sort($candidates); - $last = array_pop($candidates).'"?'; - if ($candidates) { - $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last; - } else { - $candidates = '"'.$last; - } - $message .= "\nDid you mean to call ".$candidates; - } - - return new UndefinedFunctionException($message, $exception); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Debug\FatalErrorHandler; - -use Symfony\Component\Debug\Exception\FatalErrorException; -use Symfony\Component\Debug\Exception\UndefinedMethodException; - -/** - * ErrorHandler for undefined methods. - * - * @author Grégoire Pineau - */ -class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface -{ - /** - * {@inheritdoc} - */ - public function handleError(array $error, FatalErrorException $exception) - { - preg_match('/^Call to undefined method (.*)::(.*)\(\)$/', $error['message'], $matches); - if (!$matches) { - return; - } - - $className = $matches[1]; - $methodName = $matches[2]; - - $message = sprintf('Attempted to call an undefined method named "%s" of class "%s".', $methodName, $className); - - if (!class_exists($className) || null === $methods = get_class_methods($className)) { - // failed to get the class or its methods on which an unknown method was called (for example on an anonymous class) - return new UndefinedMethodException($message, $exception); - } - - $candidates = array(); - foreach ($methods as $definedMethodName) { - $lev = levenshtein($methodName, $definedMethodName); - if ($lev <= strlen($methodName) / 3 || false !== strpos($definedMethodName, $methodName)) { - $candidates[] = $definedMethodName; - } - } - - if ($candidates) { - sort($candidates); - $last = array_pop($candidates).'"?'; - if ($candidates) { - $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last; - } else { - $candidates = '"'.$last; - } - - $message .= "\nDid you mean to call ".$candidates; - } - - return new UndefinedMethodException($message, $exception); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Finder\Comparator; - -/** - * Comparator. - * - * @author Fabien Potencier - */ -class Comparator -{ - private $target; - private $operator = '=='; - - /** - * Gets the target value. - * - * @return string The target value - */ - public function getTarget() - { - return $this->target; - } - - /** - * Sets the target value. - * - * @param string $target The target value - */ - public function setTarget($target) - { - $this->target = $target; - } - - /** - * Gets the comparison operator. - * - * @return string The operator - */ - public function getOperator() - { - return $this->operator; - } - - /** - * Sets the comparison operator. - * - * @param string $operator A valid operator - * - * @throws \InvalidArgumentException - */ - public function setOperator($operator) - { - if (!$operator) { - $operator = '=='; - } - - if (!in_array($operator, array('>', '<', '>=', '<=', '==', '!='))) { - throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); - } - - $this->operator = $operator; - } - - /** - * Tests against the target. - * - * @param mixed $test A test value - * - * @return bool - */ - public function test($test) - { - switch ($this->operator) { - case '>': - return $test > $this->target; - case '>=': - return $test >= $this->target; - case '<': - return $test < $this->target; - case '<=': - return $test <= $this->target; - case '!=': - return $test != $this->target; - } - - return $test == $this->target; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Finder\Comparator; - -/** - * DateCompare compiles date comparisons. - * - * @author Fabien Potencier - */ -class DateComparator extends Comparator -{ - /** - * @param string $test A comparison string - * - * @throws \InvalidArgumentException If the test is not understood - */ - public function __construct($test) - { - if (!preg_match('#^\s*(==|!=|[<>]=?|after|since|before|until)?\s*(.+?)\s*$#i', $test, $matches)) { - throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a date test.', $test)); - } - - try { - $date = new \DateTime($matches[2]); - $target = $date->format('U'); - } catch (\Exception $e) { - throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2])); - } - - $operator = isset($matches[1]) ? $matches[1] : '=='; - if ('since' === $operator || 'after' === $operator) { - $operator = '>'; - } - - if ('until' === $operator || 'before' === $operator) { - $operator = '<'; - } - - $this->setOperator($operator); - $this->setTarget($target); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Finder\Comparator; - -/** - * NumberComparator compiles a simple comparison to an anonymous - * subroutine, which you can call with a value to be tested again. - * - * Now this would be very pointless, if NumberCompare didn't understand - * magnitudes. - * - * The target value may use magnitudes of kilobytes (k, ki), - * megabytes (m, mi), or gigabytes (g, gi). Those suffixed - * with an i use the appropriate 2**n version in accordance with the - * IEC standard: http://physics.nist.gov/cuu/Units/binary.html - * - * Based on the Perl Number::Compare module. - * - * @author Fabien Potencier PHP port - * @author Richard Clamp Perl version - * @copyright 2004-2005 Fabien Potencier - * @copyright 2002 Richard Clamp - * - * @see http://physics.nist.gov/cuu/Units/binary.html - */ -class NumberComparator extends Comparator -{ - /** - * @param string|int $test A comparison string or an integer - * - * @throws \InvalidArgumentException If the test is not understood - */ - public function __construct($test) - { - if (!preg_match('#^\s*(==|!=|[<>]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) { - throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a number test.', $test)); - } - - $target = $matches[2]; - if (!is_numeric($target)) { - throw new \InvalidArgumentException(sprintf('Invalid number "%s".', $target)); - } - if (isset($matches[3])) { - // magnitude - switch (strtolower($matches[3])) { - case 'k': - $target *= 1000; - break; - case 'ki': - $target *= 1024; - break; - case 'm': - $target *= 1000000; - break; - case 'mi': - $target *= 1024 * 1024; - break; - case 'g': - $target *= 1000000000; - break; - case 'gi': - $target *= 1024 * 1024 * 1024; - break; - } - } - - $this->setTarget($target); - $this->setOperator(isset($matches[1]) ? $matches[1] : '=='); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Finder\Exception; - -/** - * @author Jean-François Simon - */ -class AccessDeniedException extends \UnexpectedValueException -{ -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Finder\Exception; - -/** - * @author Jean-François Simon - * - * @deprecated since 3.3, to be removed in 4.0. - */ -interface ExceptionInterface -{ - /** - * @return \Symfony\Component\Finder\Adapter\AdapterInterface - */ - public function getAdapter(); -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Finder; - -use Symfony\Component\Finder\Comparator\DateComparator; -use Symfony\Component\Finder\Comparator\NumberComparator; -use Symfony\Component\Finder\Iterator\CustomFilterIterator; -use Symfony\Component\Finder\Iterator\DateRangeFilterIterator; -use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator; -use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator; -use Symfony\Component\Finder\Iterator\FilecontentFilterIterator; -use Symfony\Component\Finder\Iterator\FilenameFilterIterator; -use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator; -use Symfony\Component\Finder\Iterator\SortableIterator; - -/** - * Finder allows to build rules to find files and directories. - * - * It is a thin wrapper around several specialized iterator classes. - * - * All rules may be invoked several times. - * - * All methods return the current Finder object to allow easy chaining: - * - * $finder = Finder::create()->files()->name('*.php')->in(__DIR__); - * - * @author Fabien Potencier - */ -class Finder implements \IteratorAggregate, \Countable -{ - const IGNORE_VCS_FILES = 1; - const IGNORE_DOT_FILES = 2; - - private $mode = 0; - private $names = array(); - private $notNames = array(); - private $exclude = array(); - private $filters = array(); - private $depths = array(); - private $sizes = array(); - private $followLinks = false; - private $sort = false; - private $ignore = 0; - private $dirs = array(); - private $dates = array(); - private $iterators = array(); - private $contains = array(); - private $notContains = array(); - private $paths = array(); - private $notPaths = array(); - private $ignoreUnreadableDirs = false; - - private static $vcsPatterns = array('.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg'); - - public function __construct() - { - $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES; - } - - /** - * Creates a new Finder. - * - * @return static - */ - public static function create() - { - return new static(); - } - - /** - * Restricts the matching to directories only. - * - * @return $this - */ - public function directories() - { - $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES; - - return $this; - } - - /** - * Restricts the matching to files only. - * - * @return $this - */ - public function files() - { - $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES; - - return $this; - } - - /** - * Adds tests for the directory depth. - * - * Usage: - * - * $finder->depth('> 1') // the Finder will start matching at level 1. - * $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point. - * - * @param string|int $level The depth level expression - * - * @return $this - * - * @see DepthRangeFilterIterator - * @see NumberComparator - */ - public function depth($level) - { - $this->depths[] = new Comparator\NumberComparator($level); - - return $this; - } - - /** - * Adds tests for file dates (last modified). - * - * The date must be something that strtotime() is able to parse: - * - * $finder->date('since yesterday'); - * $finder->date('until 2 days ago'); - * $finder->date('> now - 2 hours'); - * $finder->date('>= 2005-10-15'); - * - * @param string $date A date range string - * - * @return $this - * - * @see strtotime - * @see DateRangeFilterIterator - * @see DateComparator - */ - public function date($date) - { - $this->dates[] = new Comparator\DateComparator($date); - - return $this; - } - - /** - * Adds rules that files must match. - * - * You can use patterns (delimited with / sign), globs or simple strings. - * - * $finder->name('*.php') - * $finder->name('/\.php$/') // same as above - * $finder->name('test.php') - * - * @param string $pattern A pattern (a regexp, a glob, or a string) - * - * @return $this - * - * @see FilenameFilterIterator - */ - public function name($pattern) - { - $this->names[] = $pattern; - - return $this; - } - - /** - * Adds rules that files must not match. - * - * @param string $pattern A pattern (a regexp, a glob, or a string) - * - * @return $this - * - * @see FilenameFilterIterator - */ - public function notName($pattern) - { - $this->notNames[] = $pattern; - - return $this; - } - - /** - * Adds tests that file contents must match. - * - * Strings or PCRE patterns can be used: - * - * $finder->contains('Lorem ipsum') - * $finder->contains('/Lorem ipsum/i') - * - * @param string $pattern A pattern (string or regexp) - * - * @return $this - * - * @see FilecontentFilterIterator - */ - public function contains($pattern) - { - $this->contains[] = $pattern; - - return $this; - } - - /** - * Adds tests that file contents must not match. - * - * Strings or PCRE patterns can be used: - * - * $finder->notContains('Lorem ipsum') - * $finder->notContains('/Lorem ipsum/i') - * - * @param string $pattern A pattern (string or regexp) - * - * @return $this - * - * @see FilecontentFilterIterator - */ - public function notContains($pattern) - { - $this->notContains[] = $pattern; - - return $this; - } - - /** - * Adds rules that filenames must match. - * - * You can use patterns (delimited with / sign) or simple strings. - * - * $finder->path('some/special/dir') - * $finder->path('/some\/special\/dir/') // same as above - * - * Use only / as dirname separator. - * - * @param string $pattern A pattern (a regexp or a string) - * - * @return $this - * - * @see FilenameFilterIterator - */ - public function path($pattern) - { - $this->paths[] = $pattern; - - return $this; - } - - /** - * Adds rules that filenames must not match. - * - * You can use patterns (delimited with / sign) or simple strings. - * - * $finder->notPath('some/special/dir') - * $finder->notPath('/some\/special\/dir/') // same as above - * - * Use only / as dirname separator. - * - * @param string $pattern A pattern (a regexp or a string) - * - * @return $this - * - * @see FilenameFilterIterator - */ - public function notPath($pattern) - { - $this->notPaths[] = $pattern; - - return $this; - } - - /** - * Adds tests for file sizes. - * - * $finder->size('> 10K'); - * $finder->size('<= 1Ki'); - * $finder->size(4); - * - * @param string|int $size A size range string or an integer - * - * @return $this - * - * @see SizeRangeFilterIterator - * @see NumberComparator - */ - public function size($size) - { - $this->sizes[] = new Comparator\NumberComparator($size); - - return $this; - } - - /** - * Excludes directories. - * - * @param string|array $dirs A directory path or an array of directories - * - * @return $this - * - * @see ExcludeDirectoryFilterIterator - */ - public function exclude($dirs) - { - $this->exclude = array_merge($this->exclude, (array) $dirs); - - return $this; - } - - /** - * Excludes "hidden" directories and files (starting with a dot). - * - * @param bool $ignoreDotFiles Whether to exclude "hidden" files or not - * - * @return $this - * - * @see ExcludeDirectoryFilterIterator - */ - public function ignoreDotFiles($ignoreDotFiles) - { - if ($ignoreDotFiles) { - $this->ignore |= static::IGNORE_DOT_FILES; - } else { - $this->ignore &= ~static::IGNORE_DOT_FILES; - } - - return $this; - } - - /** - * Forces the finder to ignore version control directories. - * - * @param bool $ignoreVCS Whether to exclude VCS files or not - * - * @return $this - * - * @see ExcludeDirectoryFilterIterator - */ - public function ignoreVCS($ignoreVCS) - { - if ($ignoreVCS) { - $this->ignore |= static::IGNORE_VCS_FILES; - } else { - $this->ignore &= ~static::IGNORE_VCS_FILES; - } - - return $this; - } - - /** - * Adds VCS patterns. - * - * @see ignoreVCS() - * - * @param string|string[] $pattern VCS patterns to ignore - */ - public static function addVCSPattern($pattern) - { - foreach ((array) $pattern as $p) { - self::$vcsPatterns[] = $p; - } - - self::$vcsPatterns = array_unique(self::$vcsPatterns); - } - - /** - * Sorts files and directories by an anonymous function. - * - * The anonymous function receives two \SplFileInfo instances to compare. - * - * This can be slow as all the matching files and directories must be retrieved for comparison. - * - * @return $this - * - * @see SortableIterator - */ - public function sort(\Closure $closure) - { - $this->sort = $closure; - - return $this; - } - - /** - * Sorts files and directories by name. - * - * This can be slow as all the matching files and directories must be retrieved for comparison. - * - * @return $this - * - * @see SortableIterator - */ - public function sortByName() - { - $this->sort = Iterator\SortableIterator::SORT_BY_NAME; - - return $this; - } - - /** - * Sorts files and directories by type (directories before files), then by name. - * - * This can be slow as all the matching files and directories must be retrieved for comparison. - * - * @return $this - * - * @see SortableIterator - */ - public function sortByType() - { - $this->sort = Iterator\SortableIterator::SORT_BY_TYPE; - - return $this; - } - - /** - * Sorts files and directories by the last accessed time. - * - * This is the time that the file was last accessed, read or written to. - * - * This can be slow as all the matching files and directories must be retrieved for comparison. - * - * @return $this - * - * @see SortableIterator - */ - public function sortByAccessedTime() - { - $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME; - - return $this; - } - - /** - * Sorts files and directories by the last inode changed time. - * - * This is the time that the inode information was last modified (permissions, owner, group or other metadata). - * - * On Windows, since inode is not available, changed time is actually the file creation time. - * - * This can be slow as all the matching files and directories must be retrieved for comparison. - * - * @return $this - * - * @see SortableIterator - */ - public function sortByChangedTime() - { - $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME; - - return $this; - } - - /** - * Sorts files and directories by the last modified time. - * - * This is the last time the actual contents of the file were last modified. - * - * This can be slow as all the matching files and directories must be retrieved for comparison. - * - * @return $this - * - * @see SortableIterator - */ - public function sortByModifiedTime() - { - $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME; - - return $this; - } - - /** - * Filters the iterator with an anonymous function. - * - * The anonymous function receives a \SplFileInfo and must return false - * to remove files. - * - * @return $this - * - * @see CustomFilterIterator - */ - public function filter(\Closure $closure) - { - $this->filters[] = $closure; - - return $this; - } - - /** - * Forces the following of symlinks. - * - * @return $this - */ - public function followLinks() - { - $this->followLinks = true; - - return $this; - } - - /** - * Tells finder to ignore unreadable directories. - * - * By default, scanning unreadable directories content throws an AccessDeniedException. - * - * @param bool $ignore - * - * @return $this - */ - public function ignoreUnreadableDirs($ignore = true) - { - $this->ignoreUnreadableDirs = (bool) $ignore; - - return $this; - } - - /** - * Searches files and directories which match defined rules. - * - * @param string|array $dirs A directory path or an array of directories - * - * @return $this - * - * @throws \InvalidArgumentException if one of the directories does not exist - */ - public function in($dirs) - { - $resolvedDirs = array(); - - foreach ((array) $dirs as $dir) { - if (is_dir($dir)) { - $resolvedDirs[] = $dir; - } elseif ($glob = glob($dir, (defined('GLOB_BRACE') ? GLOB_BRACE : 0) | GLOB_ONLYDIR)) { - $resolvedDirs = array_merge($resolvedDirs, $glob); - } else { - throw new \InvalidArgumentException(sprintf('The "%s" directory does not exist.', $dir)); - } - } - - $this->dirs = array_merge($this->dirs, $resolvedDirs); - - return $this; - } - - /** - * Returns an Iterator for the current Finder configuration. - * - * This method implements the IteratorAggregate interface. - * - * @return \Iterator|SplFileInfo[] An iterator - * - * @throws \LogicException if the in() method has not been called - */ - public function getIterator() - { - if (0 === count($this->dirs) && 0 === count($this->iterators)) { - throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.'); - } - - if (1 === count($this->dirs) && 0 === count($this->iterators)) { - return $this->searchInDirectory($this->dirs[0]); - } - - $iterator = new \AppendIterator(); - foreach ($this->dirs as $dir) { - $iterator->append($this->searchInDirectory($dir)); - } - - foreach ($this->iterators as $it) { - $iterator->append($it); - } - - return $iterator; - } - - /** - * Appends an existing set of files/directories to the finder. - * - * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array. - * - * @param mixed $iterator - * - * @return $this - * - * @throws \InvalidArgumentException when the given argument is not iterable - */ - public function append($iterator) - { - if ($iterator instanceof \IteratorAggregate) { - $this->iterators[] = $iterator->getIterator(); - } elseif ($iterator instanceof \Iterator) { - $this->iterators[] = $iterator; - } elseif ($iterator instanceof \Traversable || is_array($iterator)) { - $it = new \ArrayIterator(); - foreach ($iterator as $file) { - $it->append($file instanceof \SplFileInfo ? $file : new \SplFileInfo($file)); - } - $this->iterators[] = $it; - } else { - throw new \InvalidArgumentException('Finder::append() method wrong argument type.'); - } - - return $this; - } - - /** - * Counts all the results collected by the iterators. - * - * @return int - */ - public function count() - { - return iterator_count($this->getIterator()); - } - - /** - * @param $dir - * - * @return \Iterator - */ - private function searchInDirectory($dir) - { - if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) { - $this->exclude = array_merge($this->exclude, self::$vcsPatterns); - } - - if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) { - $this->notPaths[] = '#(^|/)\..+(/|$)#'; - } - - $minDepth = 0; - $maxDepth = PHP_INT_MAX; - - foreach ($this->depths as $comparator) { - switch ($comparator->getOperator()) { - case '>': - $minDepth = $comparator->getTarget() + 1; - break; - case '>=': - $minDepth = $comparator->getTarget(); - break; - case '<': - $maxDepth = $comparator->getTarget() - 1; - break; - case '<=': - $maxDepth = $comparator->getTarget(); - break; - default: - $minDepth = $maxDepth = $comparator->getTarget(); - } - } - - $flags = \RecursiveDirectoryIterator::SKIP_DOTS; - - if ($this->followLinks) { - $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS; - } - - $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs); - - if ($this->exclude) { - $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude); - } - - $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST); - - if ($minDepth > 0 || $maxDepth < PHP_INT_MAX) { - $iterator = new Iterator\DepthRangeFilterIterator($iterator, $minDepth, $maxDepth); - } - - if ($this->mode) { - $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode); - } - - if ($this->names || $this->notNames) { - $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames); - } - - if ($this->contains || $this->notContains) { - $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); - } - - if ($this->sizes) { - $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes); - } - - if ($this->dates) { - $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates); - } - - if ($this->filters) { - $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); - } - - if ($this->paths || $this->notPaths) { - $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $this->notPaths); - } - - if ($this->sort) { - $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort); - $iterator = $iteratorAggregate->getIterator(); - } - - return $iterator; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Finder; - -/** - * Glob matches globbing patterns against text. - * - * if match_glob("foo.*", "foo.bar") echo "matched\n"; - * - * // prints foo.bar and foo.baz - * $regex = glob_to_regex("foo.*"); - * for (array('foo.bar', 'foo.baz', 'foo', 'bar') as $t) - * { - * if (/$regex/) echo "matched: $car\n"; - * } - * - * Glob implements glob(3) style matching that can be used to match - * against text, rather than fetching names from a filesystem. - * - * Based on the Perl Text::Glob module. - * - * @author Fabien Potencier PHP port - * @author Richard Clamp Perl version - * @copyright 2004-2005 Fabien Potencier - * @copyright 2002 Richard Clamp - */ -class Glob -{ - /** - * Returns a regexp which is the equivalent of the glob pattern. - * - * @param string $glob The glob pattern - * @param bool $strictLeadingDot - * @param bool $strictWildcardSlash - * @param string $delimiter Optional delimiter - * - * @return string regex The regexp - */ - public static function toRegex($glob, $strictLeadingDot = true, $strictWildcardSlash = true, $delimiter = '#') - { - $firstByte = true; - $escaping = false; - $inCurlies = 0; - $regex = ''; - $sizeGlob = strlen($glob); - for ($i = 0; $i < $sizeGlob; ++$i) { - $car = $glob[$i]; - if ($firstByte && $strictLeadingDot && '.' !== $car) { - $regex .= '(?=[^\.])'; - } - - $firstByte = '/' === $car; - - if ($firstByte && $strictWildcardSlash && isset($glob[$i + 2]) && '**' === $glob[$i + 1].$glob[$i + 2] && (!isset($glob[$i + 3]) || '/' === $glob[$i + 3])) { - $car = '[^/]++/'; - if (!isset($glob[$i + 3])) { - $car .= '?'; - } - - if ($strictLeadingDot) { - $car = '(?=[^\.])'.$car; - } - - $car = '/(?:'.$car.')*'; - $i += 2 + isset($glob[$i + 3]); - - if ('/' === $delimiter) { - $car = str_replace('/', '\\/', $car); - } - } - - if ($delimiter === $car || '.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) { - $regex .= "\\$car"; - } elseif ('*' === $car) { - $regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*'); - } elseif ('?' === $car) { - $regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.'); - } elseif ('{' === $car) { - $regex .= $escaping ? '\\{' : '('; - if (!$escaping) { - ++$inCurlies; - } - } elseif ('}' === $car && $inCurlies) { - $regex .= $escaping ? '}' : ')'; - if (!$escaping) { - --$inCurlies; - } - } elseif (',' === $car && $inCurlies) { - $regex .= $escaping ? ',' : '|'; - } elseif ('\\' === $car) { - if ($escaping) { - $regex .= '\\\\'; - $escaping = false; - } else { - $escaping = true; - } - - continue; - } else { - $regex .= $car; - } - $escaping = false; - } - - return $delimiter.'^'.$regex.'$'.$delimiter; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Finder\Iterator; - -/** - * CustomFilterIterator filters files by applying anonymous functions. - * - * The anonymous function receives a \SplFileInfo and must return false - * to remove files. - * - * @author Fabien Potencier - */ -class CustomFilterIterator extends FilterIterator -{ - private $filters = array(); - - /** - * @param \Iterator $iterator The Iterator to filter - * @param callable[] $filters An array of PHP callbacks - * - * @throws \InvalidArgumentException - */ - public function __construct(\Iterator $iterator, array $filters) - { - foreach ($filters as $filter) { - if (!is_callable($filter)) { - throw new \InvalidArgumentException('Invalid PHP callback.'); - } - } - $this->filters = $filters; - - parent::__construct($iterator); - } - - /** - * Filters the iterator values. - * - * @return bool true if the value should be kept, false otherwise - */ - public function accept() - { - $fileinfo = $this->current(); - - foreach ($this->filters as $filter) { - if (false === call_user_func($filter, $fileinfo)) { - return false; - } - } - - return true; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Finder\Iterator; - -use Symfony\Component\Finder\Comparator\DateComparator; - -/** - * DateRangeFilterIterator filters out files that are not in the given date range (last modified dates). - * - * @author Fabien Potencier - */ -class DateRangeFilterIterator extends FilterIterator -{ - private $comparators = array(); - - /** - * @param \Iterator $iterator The Iterator to filter - * @param DateComparator[] $comparators An array of DateComparator instances - */ - public function __construct(\Iterator $iterator, array $comparators) - { - $this->comparators = $comparators; - - parent::__construct($iterator); - } - - /** - * Filters the iterator values. - * - * @return bool true if the value should be kept, false otherwise - */ - public function accept() - { - $fileinfo = $this->current(); - - if (!file_exists($fileinfo->getPathname())) { - return false; - } - - $filedate = $fileinfo->getMTime(); - foreach ($this->comparators as $compare) { - if (!$compare->test($filedate)) { - return false; - } - } - - return true; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Finder\Iterator; - -/** - * DepthRangeFilterIterator limits the directory depth. - * - * @author Fabien Potencier - */ -class DepthRangeFilterIterator extends FilterIterator -{ - private $minDepth = 0; - - /** - * @param \RecursiveIteratorIterator $iterator The Iterator to filter - * @param int $minDepth The min depth - * @param int $maxDepth The max depth - */ - public function __construct(\RecursiveIteratorIterator $iterator, $minDepth = 0, $maxDepth = PHP_INT_MAX) - { - $this->minDepth = $minDepth; - $iterator->setMaxDepth(PHP_INT_MAX === $maxDepth ? -1 : $maxDepth); - - parent::__construct($iterator); - } - - /** - * Filters the iterator values. - * - * @return bool true if the value should be kept, false otherwise - */ - public function accept() - { - return $this->getInnerIterator()->getDepth() >= $this->minDepth; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Finder\Iterator; - -/** - * ExcludeDirectoryFilterIterator filters out directories. - * - * @author Fabien Potencier - */ -class ExcludeDirectoryFilterIterator extends FilterIterator implements \RecursiveIterator -{ - private $iterator; - private $isRecursive; - private $excludedDirs = array(); - private $excludedPattern; - - /** - * @param \Iterator $iterator The Iterator to filter - * @param array $directories An array of directories to exclude - */ - public function __construct(\Iterator $iterator, array $directories) - { - $this->iterator = $iterator; - $this->isRecursive = $iterator instanceof \RecursiveIterator; - $patterns = array(); - foreach ($directories as $directory) { - $directory = rtrim($directory, '/'); - if (!$this->isRecursive || false !== strpos($directory, '/')) { - $patterns[] = preg_quote($directory, '#'); - } else { - $this->excludedDirs[$directory] = true; - } - } - if ($patterns) { - $this->excludedPattern = '#(?:^|/)(?:'.implode('|', $patterns).')(?:/|$)#'; - } - - parent::__construct($iterator); - } - - /** - * Filters the iterator values. - * - * @return bool True if the value should be kept, false otherwise - */ - public function accept() - { - if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) { - return false; - } - - if ($this->excludedPattern) { - $path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath(); - $path = str_replace('\\', '/', $path); - - return !preg_match($this->excludedPattern, $path); - } - - return true; - } - - public function hasChildren() - { - return $this->isRecursive && $this->iterator->hasChildren(); - } - - public function getChildren() - { - $children = new self($this->iterator->getChildren(), array()); - $children->excludedDirs = $this->excludedDirs; - $children->excludedPattern = $this->excludedPattern; - - return $children; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Finder\Iterator; - -/** - * FileTypeFilterIterator only keeps files, directories, or both. - * - * @author Fabien Potencier - */ -class FileTypeFilterIterator extends FilterIterator -{ - const ONLY_FILES = 1; - const ONLY_DIRECTORIES = 2; - - private $mode; - - /** - * @param \Iterator $iterator The Iterator to filter - * @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) - */ - public function __construct(\Iterator $iterator, $mode) - { - $this->mode = $mode; - - parent::__construct($iterator); - } - - /** - * Filters the iterator values. - * - * @return bool true if the value should be kept, false otherwise - */ - public function accept() - { - $fileinfo = $this->current(); - if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) { - return false; - } elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) { - return false; - } - - return true; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Finder\Iterator; - -/** - * FilecontentFilterIterator filters files by their contents using patterns (regexps or strings). - * - * @author Fabien Potencier - * @author Włodzimierz Gajda - */ -class FilecontentFilterIterator extends MultiplePcreFilterIterator -{ - /** - * Filters the iterator values. - * - * @return bool true if the value should be kept, false otherwise - */ - public function accept() - { - if (!$this->matchRegexps && !$this->noMatchRegexps) { - return true; - } - - $fileinfo = $this->current(); - - if ($fileinfo->isDir() || !$fileinfo->isReadable()) { - return false; - } - - $content = $fileinfo->getContents(); - if (!$content) { - return false; - } - - return $this->isAccepted($content); - } - - /** - * Converts string to regexp if necessary. - * - * @param string $str Pattern: string or regexp - * - * @return string regexp corresponding to a given string or regexp - */ - protected function toRegex($str) - { - return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Finder\Iterator; - -use Symfony\Component\Finder\Glob; - -/** - * FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string). - * - * @author Fabien Potencier - */ -class FilenameFilterIterator extends MultiplePcreFilterIterator -{ - /** - * Filters the iterator values. - * - * @return bool true if the value should be kept, false otherwise - */ - public function accept() - { - return $this->isAccepted($this->current()->getFilename()); - } - - /** - * Converts glob to regexp. - * - * PCRE patterns are left unchanged. - * Glob strings are transformed with Glob::toRegex(). - * - * @param string $str Pattern: glob or regexp - * - * @return string regexp corresponding to a given glob or regexp - */ - protected function toRegex($str) - { - return $this->isRegex($str) ? $str : Glob::toRegex($str); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Finder\Iterator; - -/** - * This iterator just overrides the rewind method in order to correct a PHP bug, - * which existed before version 5.5.23/5.6.7. - * - * @see https://bugs.php.net/68557 - * - * @author Alex Bogomazov - */ -abstract class FilterIterator extends \FilterIterator -{ - /** - * This is a workaround for the problem with \FilterIterator leaving inner \FilesystemIterator in wrong state after - * rewind in some cases. - * - * @see FilterIterator::rewind() - */ - public function rewind() - { - if (\PHP_VERSION_ID > 50607 || (\PHP_VERSION_ID > 50523 && \PHP_VERSION_ID < 50600)) { - parent::rewind(); - - return; - } - - $iterator = $this; - while ($iterator instanceof \OuterIterator) { - $innerIterator = $iterator->getInnerIterator(); - - if ($innerIterator instanceof RecursiveDirectoryIterator) { - // this condition is necessary for iterators to work properly with non-local filesystems like ftp - if ($innerIterator->isRewindable()) { - $innerIterator->next(); - $innerIterator->rewind(); - } - } elseif ($innerIterator instanceof \FilesystemIterator) { - $innerIterator->next(); - $innerIterator->rewind(); - } - - $iterator = $innerIterator; - } - - parent::rewind(); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Finder\Iterator; - -/** - * MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings). - * - * @author Fabien Potencier - */ -abstract class MultiplePcreFilterIterator extends FilterIterator -{ - protected $matchRegexps = array(); - protected $noMatchRegexps = array(); - - /** - * @param \Iterator $iterator The Iterator to filter - * @param array $matchPatterns An array of patterns that need to match - * @param array $noMatchPatterns An array of patterns that need to not match - */ - public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns) - { - foreach ($matchPatterns as $pattern) { - $this->matchRegexps[] = $this->toRegex($pattern); - } - - foreach ($noMatchPatterns as $pattern) { - $this->noMatchRegexps[] = $this->toRegex($pattern); - } - - parent::__construct($iterator); - } - - /** - * Checks whether the string is accepted by the regex filters. - * - * If there is no regexps defined in the class, this method will accept the string. - * Such case can be handled by child classes before calling the method if they want to - * apply a different behavior. - * - * @param string $string The string to be matched against filters - * - * @return bool - */ - protected function isAccepted($string) - { - // should at least not match one rule to exclude - foreach ($this->noMatchRegexps as $regex) { - if (preg_match($regex, $string)) { - return false; - } - } - - // should at least match one rule - if ($this->matchRegexps) { - foreach ($this->matchRegexps as $regex) { - if (preg_match($regex, $string)) { - return true; - } - } - - return false; - } - - // If there is no match rules, the file is accepted - return true; - } - - /** - * Checks whether the string is a regex. - * - * @param string $str - * - * @return bool Whether the given string is a regex - */ - protected function isRegex($str) - { - if (preg_match('/^(.{3,}?)[imsxuADU]*$/', $str, $m)) { - $start = substr($m[1], 0, 1); - $end = substr($m[1], -1); - - if ($start === $end) { - return !preg_match('/[*?[:alnum:] \\\\]/', $start); - } - - foreach (array(array('{', '}'), array('(', ')'), array('[', ']'), array('<', '>')) as $delimiters) { - if ($start === $delimiters[0] && $end === $delimiters[1]) { - return true; - } - } - } - - return false; - } - - /** - * Converts string into regexp. - * - * @param string $str Pattern - * - * @return string regexp corresponding to a given string - */ - abstract protected function toRegex($str); -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Finder\Iterator; - -/** - * PathFilterIterator filters files by path patterns (e.g. some/special/dir). - * - * @author Fabien Potencier - * @author Włodzimierz Gajda - */ -class PathFilterIterator extends MultiplePcreFilterIterator -{ - /** - * Filters the iterator values. - * - * @return bool true if the value should be kept, false otherwise - */ - public function accept() - { - $filename = $this->current()->getRelativePathname(); - - if ('\\' === DIRECTORY_SEPARATOR) { - $filename = str_replace('\\', '/', $filename); - } - - return $this->isAccepted($filename); - } - - /** - * Converts strings to regexp. - * - * PCRE patterns are left unchanged. - * - * Default conversion: - * 'lorem/ipsum/dolor' ==> 'lorem\/ipsum\/dolor/' - * - * Use only / as directory separator (on Windows also). - * - * @param string $str Pattern: regexp or dirname - * - * @return string regexp corresponding to a given string or regexp - */ - protected function toRegex($str) - { - return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Finder\Iterator; - -use Symfony\Component\Finder\Exception\AccessDeniedException; -use Symfony\Component\Finder\SplFileInfo; - -/** - * Extends the \RecursiveDirectoryIterator to support relative paths. - * - * @author Victor Berchet - */ -class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator -{ - /** - * @var bool - */ - private $ignoreUnreadableDirs; - - /** - * @var bool - */ - private $rewindable; - - // these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations - private $rootPath; - private $subPath; - private $directorySeparator = '/'; - - /** - * @param string $path - * @param int $flags - * @param bool $ignoreUnreadableDirs - * - * @throws \RuntimeException - */ - public function __construct($path, $flags, $ignoreUnreadableDirs = false) - { - if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) { - throw new \RuntimeException('This iterator only support returning current as fileinfo.'); - } - - parent::__construct($path, $flags); - $this->ignoreUnreadableDirs = $ignoreUnreadableDirs; - $this->rootPath = $path; - if ('/' !== DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) { - $this->directorySeparator = DIRECTORY_SEPARATOR; - } - } - - /** - * Return an instance of SplFileInfo with support for relative paths. - * - * @return SplFileInfo File information - */ - public function current() - { - // the logic here avoids redoing the same work in all iterations - - if (null === $subPathname = $this->subPath) { - $subPathname = $this->subPath = (string) $this->getSubPath(); - } - if ('' !== $subPathname) { - $subPathname .= $this->directorySeparator; - } - $subPathname .= $this->getFilename(); - - return new SplFileInfo($this->rootPath.$this->directorySeparator.$subPathname, $this->subPath, $subPathname); - } - - /** - * @return \RecursiveIterator - * - * @throws AccessDeniedException - */ - public function getChildren() - { - try { - $children = parent::getChildren(); - - if ($children instanceof self) { - // parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore - $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs; - - // performance optimization to avoid redoing the same work in all children - $children->rewindable = &$this->rewindable; - $children->rootPath = $this->rootPath; - } - - return $children; - } catch (\UnexpectedValueException $e) { - if ($this->ignoreUnreadableDirs) { - // If directory is unreadable and finder is set to ignore it, a fake empty content is returned. - return new \RecursiveArrayIterator(array()); - } else { - throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e); - } - } - } - - /** - * Do nothing for non rewindable stream. - */ - public function rewind() - { - if (false === $this->isRewindable()) { - return; - } - - // @see https://bugs.php.net/68557 - if (\PHP_VERSION_ID < 50523 || \PHP_VERSION_ID >= 50600 && \PHP_VERSION_ID < 50607) { - parent::next(); - } - - parent::rewind(); - } - - /** - * Checks if the stream is rewindable. - * - * @return bool true when the stream is rewindable, false otherwise - */ - public function isRewindable() - { - if (null !== $this->rewindable) { - return $this->rewindable; - } - - // workaround for an HHVM bug, should be removed when https://github.com/facebook/hhvm/issues/7281 is fixed - if ('' === $this->getPath()) { - return $this->rewindable = false; - } - - if (false !== $stream = @opendir($this->getPath())) { - $infos = stream_get_meta_data($stream); - closedir($stream); - - if ($infos['seekable']) { - return $this->rewindable = true; - } - } - - return $this->rewindable = false; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Finder\Iterator; - -use Symfony\Component\Finder\Comparator\NumberComparator; - -/** - * SizeRangeFilterIterator filters out files that are not in the given size range. - * - * @author Fabien Potencier - */ -class SizeRangeFilterIterator extends FilterIterator -{ - private $comparators = array(); - - /** - * @param \Iterator $iterator The Iterator to filter - * @param NumberComparator[] $comparators An array of NumberComparator instances - */ - public function __construct(\Iterator $iterator, array $comparators) - { - $this->comparators = $comparators; - - parent::__construct($iterator); - } - - /** - * Filters the iterator values. - * - * @return bool true if the value should be kept, false otherwise - */ - public function accept() - { - $fileinfo = $this->current(); - if (!$fileinfo->isFile()) { - return true; - } - - $filesize = $fileinfo->getSize(); - foreach ($this->comparators as $compare) { - if (!$compare->test($filesize)) { - return false; - } - } - - return true; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Finder\Iterator; - -/** - * SortableIterator applies a sort on a given Iterator. - * - * @author Fabien Potencier - */ -class SortableIterator implements \IteratorAggregate -{ - const SORT_BY_NAME = 1; - const SORT_BY_TYPE = 2; - const SORT_BY_ACCESSED_TIME = 3; - const SORT_BY_CHANGED_TIME = 4; - const SORT_BY_MODIFIED_TIME = 5; - - private $iterator; - private $sort; - - /** - * @param \Traversable $iterator The Iterator to filter - * @param int|callable $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback) - * - * @throws \InvalidArgumentException - */ - public function __construct(\Traversable $iterator, $sort) - { - $this->iterator = $iterator; - - if (self::SORT_BY_NAME === $sort) { - $this->sort = function ($a, $b) { - return strcmp($a->getRealpath() ?: $a->getPathname(), $b->getRealpath() ?: $b->getPathname()); - }; - } elseif (self::SORT_BY_TYPE === $sort) { - $this->sort = function ($a, $b) { - if ($a->isDir() && $b->isFile()) { - return -1; - } elseif ($a->isFile() && $b->isDir()) { - return 1; - } - - return strcmp($a->getRealpath() ?: $a->getPathname(), $b->getRealpath() ?: $b->getPathname()); - }; - } elseif (self::SORT_BY_ACCESSED_TIME === $sort) { - $this->sort = function ($a, $b) { - return $a->getATime() - $b->getATime(); - }; - } elseif (self::SORT_BY_CHANGED_TIME === $sort) { - $this->sort = function ($a, $b) { - return $a->getCTime() - $b->getCTime(); - }; - } elseif (self::SORT_BY_MODIFIED_TIME === $sort) { - $this->sort = function ($a, $b) { - return $a->getMTime() - $b->getMTime(); - }; - } elseif (is_callable($sort)) { - $this->sort = $sort; - } else { - throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.'); - } - } - - public function getIterator() - { - $array = iterator_to_array($this->iterator, true); - uasort($array, $this->sort); - - return new \ArrayIterator($array); - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Finder; - -/** - * Extends \SplFileInfo to support relative paths. - * - * @author Fabien Potencier - */ -class SplFileInfo extends \SplFileInfo -{ - private $relativePath; - private $relativePathname; - - /** - * @param string $file The file name - * @param string $relativePath The relative path - * @param string $relativePathname The relative path name - */ - public function __construct($file, $relativePath, $relativePathname) - { - parent::__construct($file); - $this->relativePath = $relativePath; - $this->relativePathname = $relativePathname; - } - - /** - * Returns the relative path. - * - * This path does not contain the file name. - * - * @return string the relative path - */ - public function getRelativePath() - { - return $this->relativePath; - } - - /** - * Returns the relative path name. - * - * This path contains the file name. - * - * @return string the relative path name - */ - public function getRelativePathname() - { - return $this->relativePathname; - } - - /** - * Returns the contents of the file. - * - * @return string the contents of the file - * - * @throws \RuntimeException - */ - public function getContents() - { - $level = error_reporting(0); - $content = file_get_contents($this->getPathname()); - error_reporting($level); - if (false === $content) { - $error = error_get_last(); - throw new \RuntimeException($error['message']); - } - - return $content; - } -} - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Polyfill\Mbstring; - -/** - * Partial mbstring implementation in PHP, iconv based, UTF-8 centric. - * - * Implemented: - * - mb_chr - Returns a specific character from its Unicode code point - * - mb_convert_encoding - Convert character encoding - * - mb_convert_variables - Convert character code in variable(s) - * - mb_decode_mimeheader - Decode string in MIME header field - * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED - * - mb_convert_case - Perform case folding on a string - * - mb_get_info - Get internal settings of mbstring - * - mb_http_input - Detect HTTP input character encoding - * - mb_http_output - Set/Get HTTP output character encoding - * - mb_internal_encoding - Set/Get internal character encoding - * - mb_list_encodings - Returns an array of all supported encodings - * - mb_ord - Returns the Unicode code point of a character - * - mb_output_handler - Callback function converts character encoding in output buffer - * - mb_scrub - Replaces ill-formed byte sequences with substitute characters - * - mb_strlen - Get string length - * - mb_strpos - Find position of first occurrence of string in a string - * - mb_strrpos - Find position of last occurrence of a string in a string - * - mb_strtolower - Make a string lowercase - * - mb_strtoupper - Make a string uppercase - * - mb_substitute_character - Set/Get substitution character - * - mb_substr - Get part of string - * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive - * - mb_stristr - Finds first occurrence of a string within another, case insensitive - * - mb_strrchr - Finds the last occurrence of a character in a string within another - * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive - * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive - * - mb_strstr - Finds first occurrence of a string within anothers - * - mb_strwidth - Return width of string - * - mb_substr_count - Count the number of substring occurrences - * - * Not implemented: - * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) - * - mb_decode_numericentity - Decode HTML numeric string reference to character - * - mb_encode_numericentity - Encode character to HTML numeric string reference - * - mb_ereg_* - Regular expression with multibyte support - * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable - * - mb_preferred_mime_name - Get MIME charset string - * - mb_regex_encoding - Returns current encoding for multibyte regex as string - * - mb_regex_set_options - Set/Get the default options for mbregex functions - * - mb_send_mail - Send encoded mail - * - mb_split - Split multibyte string using regular expression - * - mb_strcut - Get part of string - * - mb_strimwidth - Get truncated string with specified width - * - * @author Nicolas Grekas - * - * @internal - */ -final class Mbstring -{ - const MB_CASE_FOLD = PHP_INT_MAX; - - private static $encodingList = array('ASCII', 'UTF-8'); - private static $language = 'neutral'; - private static $internalEncoding = 'UTF-8'; - private static $caseFold = array( - array('µ','ſ',"\xCD\x85",'ς',"\xCF\x90","\xCF\x91","\xCF\x95","\xCF\x96","\xCF\xB0","\xCF\xB1","\xCF\xB5","\xE1\xBA\x9B","\xE1\xBE\xBE"), - array('μ','s','ι', 'σ','β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1",'ι'), - ); - - public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) - { - if (is_array($fromEncoding) || false !== strpos($fromEncoding, ',')) { - $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); - } else { - $fromEncoding = self::getEncoding($fromEncoding); - } - - $toEncoding = self::getEncoding($toEncoding); - - if ('BASE64' === $fromEncoding) { - $s = base64_decode($s); - $fromEncoding = $toEncoding; - } - - if ('BASE64' === $toEncoding) { - return base64_encode($s); - } - - if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) { - if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) { - $fromEncoding = 'Windows-1252'; - } - if ('UTF-8' !== $fromEncoding) { - $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s); - } - - return preg_replace_callback('/[\x80-\xFF]+/', array(__CLASS__, 'html_encoding_callback'), $s); - } - - if ('HTML-ENTITIES' === $fromEncoding) { - $s = html_entity_decode($s, ENT_COMPAT, 'UTF-8'); - $fromEncoding = 'UTF-8'; - } - - return iconv($fromEncoding, $toEncoding.'//IGNORE', $s); - } - - public static function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) - { - $vars = array(&$a, &$b, &$c, &$d, &$e, &$f); - - $ok = true; - array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { - if (false === $v = Mbstring::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { - $ok = false; - } - }); - - return $ok ? $fromEncoding : false; - } - - public static function mb_decode_mimeheader($s) - { - return iconv_mime_decode($s, 2, self::$internalEncoding); - } - - public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) - { - trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', E_USER_WARNING); - } - - public static function mb_convert_case($s, $mode, $encoding = null) - { - if ('' === $s .= '') { - return ''; - } - - $encoding = self::getEncoding($encoding); - - if ('UTF-8' === $encoding) { - $encoding = null; - if (!preg_match('//u', $s)) { - $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); - } - } else { - $s = iconv($encoding, 'UTF-8//IGNORE', $s); - } - - if (MB_CASE_TITLE == $mode) { - $s = preg_replace_callback('/\b\p{Ll}/u', array(__CLASS__, 'title_case_upper'), $s); - $s = preg_replace_callback('/\B[\p{Lu}\p{Lt}]+/u', array(__CLASS__, 'title_case_lower'), $s); - } else { - if (MB_CASE_UPPER == $mode) { - static $upper = null; - if (null === $upper) { - $upper = self::getData('upperCase'); - } - $map = $upper; - } else { - if (self::MB_CASE_FOLD === $mode) { - $s = str_replace(self::$caseFold[0], self::$caseFold[1], $s); - } - - static $lower = null; - if (null === $lower) { - $lower = self::getData('lowerCase'); - } - $map = $lower; - } - - static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4); - - $i = 0; - $len = strlen($s); - - while ($i < $len) { - $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; - $uchr = substr($s, $i, $ulen); - $i += $ulen; - - if (isset($map[$uchr])) { - $uchr = $map[$uchr]; - $nlen = strlen($uchr); - - if ($nlen == $ulen) { - $nlen = $i; - do { - $s[--$nlen] = $uchr[--$ulen]; - } while ($ulen); - } else { - $s = substr_replace($s, $uchr, $i - $ulen, $ulen); - $len += $nlen - $ulen; - $i += $nlen - $ulen; - } - } - } - } - - if (null === $encoding) { - return $s; - } - - return iconv('UTF-8', $encoding.'//IGNORE', $s); - } - - public static function mb_internal_encoding($encoding = null) - { - if (null === $encoding) { - return self::$internalEncoding; - } - - $encoding = self::getEncoding($encoding); - - if ('UTF-8' === $encoding || false !== @iconv($encoding, $encoding, ' ')) { - self::$internalEncoding = $encoding; - - return true; - } - - return false; - } - - public static function mb_language($lang = null) - { - if (null === $lang) { - return self::$language; - } - - switch ($lang = strtolower($lang)) { - case 'uni': - case 'neutral': - self::$language = $lang; - - return true; - } - - return false; - } - - public static function mb_list_encodings() - { - return array('UTF-8'); - } - - public static function mb_encoding_aliases($encoding) - { - switch (strtoupper($encoding)) { - case 'UTF8': - case 'UTF-8': - return array('utf8'); - } - - return false; - } - - public static function mb_check_encoding($var = null, $encoding = null) - { - if (null === $encoding) { - if (null === $var) { - return false; - } - $encoding = self::$internalEncoding; - } - - return self::mb_detect_encoding($var, array($encoding)) || false !== @iconv($encoding, $encoding, $var); - } - - public static function mb_detect_encoding($str, $encodingList = null, $strict = false) - { - if (null === $encodingList) { - $encodingList = self::$encodingList; - } else { - if (!is_array($encodingList)) { - $encodingList = array_map('trim', explode(',', $encodingList)); - } - $encodingList = array_map('strtoupper', $encodingList); - } - - foreach ($encodingList as $enc) { - switch ($enc) { - case 'ASCII': - if (!preg_match('/[\x80-\xFF]/', $str)) { - return $enc; - } - break; - - case 'UTF8': - case 'UTF-8': - if (preg_match('//u', $str)) { - return 'UTF-8'; - } - break; - - default: - if (0 === strncmp($enc, 'ISO-8859-', 9)) { - return $enc; - } - } - } - - return false; - } - - public static function mb_detect_order($encodingList = null) - { - if (null === $encodingList) { - return self::$encodingList; - } - - if (!is_array($encodingList)) { - $encodingList = array_map('trim', explode(',', $encodingList)); - } - $encodingList = array_map('strtoupper', $encodingList); - - foreach ($encodingList as $enc) { - switch ($enc) { - default: - if (strncmp($enc, 'ISO-8859-', 9)) { - return false; - } - case 'ASCII': - case 'UTF8': - case 'UTF-8': - } - } - - self::$encodingList = $encodingList; - - return true; - } - - public static function mb_strlen($s, $encoding = null) - { - $encoding = self::getEncoding($encoding); - if ('CP850' === $encoding || 'ASCII' === $encoding) { - return strlen($s); - } - - return @iconv_strlen($s, $encoding); - } - - public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) - { - $encoding = self::getEncoding($encoding); - if ('CP850' === $encoding || 'ASCII' === $encoding) { - return strpos($haystack, $needle, $offset); - } - - if ('' === $needle .= '') { - trigger_error(__METHOD__.': Empty delimiter', E_USER_WARNING); - - return false; - } - - return iconv_strpos($haystack, $needle, $offset, $encoding); - } - - public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) - { - $encoding = self::getEncoding($encoding); - if ('CP850' === $encoding || 'ASCII' === $encoding) { - return strrpos($haystack, $needle, $offset); - } - - if ($offset != (int) $offset) { - $offset = 0; - } elseif ($offset = (int) $offset) { - if ($offset < 0) { - $haystack = self::mb_substr($haystack, 0, $offset, $encoding); - $offset = 0; - } else { - $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); - } - } - - $pos = iconv_strrpos($haystack, $needle, $encoding); - - return false !== $pos ? $offset + $pos : false; - } - - public static function mb_strtolower($s, $encoding = null) - { - return self::mb_convert_case($s, MB_CASE_LOWER, $encoding); - } - - public static function mb_strtoupper($s, $encoding = null) - { - return self::mb_convert_case($s, MB_CASE_UPPER, $encoding); - } - - public static function mb_substitute_character($c = null) - { - if (0 === strcasecmp($c, 'none')) { - return true; - } - - return null !== $c ? false : 'none'; - } - - public static function mb_substr($s, $start, $length = null, $encoding = null) - { - $encoding = self::getEncoding($encoding); - if ('CP850' === $encoding || 'ASCII' === $encoding) { - return substr($s, $start, null === $length ? 2147483647 : $length); - } - - if ($start < 0) { - $start = iconv_strlen($s, $encoding) + $start; - if ($start < 0) { - $start = 0; - } - } - - if (null === $length) { - $length = 2147483647; - } elseif ($length < 0) { - $length = iconv_strlen($s, $encoding) + $length - $start; - if ($length < 0) { - return ''; - } - } - - return iconv_substr($s, $start, $length, $encoding).''; - } - - public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) - { - $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); - $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); - - return self::mb_strpos($haystack, $needle, $offset, $encoding); - } - - public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) - { - $pos = self::mb_stripos($haystack, $needle, 0, $encoding); - - return self::getSubpart($pos, $part, $haystack, $encoding); - } - - public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) - { - $encoding = self::getEncoding($encoding); - if ('CP850' === $encoding || 'ASCII' === $encoding) { - return strrchr($haystack, $needle, $part); - } - $needle = self::mb_substr($needle, 0, 1, $encoding); - $pos = iconv_strrpos($haystack, $needle, $encoding); - - return self::getSubpart($pos, $part, $haystack, $encoding); - } - - public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) - { - $needle = self::mb_substr($needle, 0, 1, $encoding); - $pos = self::mb_strripos($haystack, $needle, $encoding); - - return self::getSubpart($pos, $part, $haystack, $encoding); - } - - public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) - { - $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); - $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); - - return self::mb_strrpos($haystack, $needle, $offset, $encoding); - } - - public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) - { - $pos = strpos($haystack, $needle); - if (false === $pos) { - return false; - } - if ($part) { - return substr($haystack, 0, $pos); - } - - return substr($haystack, $pos); - } - - public static function mb_get_info($type = 'all') - { - $info = array( - 'internal_encoding' => self::$internalEncoding, - 'http_output' => 'pass', - 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', - 'func_overload' => 0, - 'func_overload_list' => 'no overload', - 'mail_charset' => 'UTF-8', - 'mail_header_encoding' => 'BASE64', - 'mail_body_encoding' => 'BASE64', - 'illegal_chars' => 0, - 'encoding_translation' => 'Off', - 'language' => self::$language, - 'detect_order' => self::$encodingList, - 'substitute_character' => 'none', - 'strict_detection' => 'Off', - ); - - if ('all' === $type) { - return $info; - } - if (isset($info[$type])) { - return $info[$type]; - } - - return false; - } - - public static function mb_http_input($type = '') - { - return false; - } - - public static function mb_http_output($encoding = null) - { - return null !== $encoding ? 'pass' === $encoding : 'pass'; - } - - public static function mb_strwidth($s, $encoding = null) - { - $encoding = self::getEncoding($encoding); - - if ('UTF-8' !== $encoding) { - $s = iconv($encoding, 'UTF-8//IGNORE', $s); - } - - $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); - - return ($wide << 1) + iconv_strlen($s, 'UTF-8'); - } - - public static function mb_substr_count($haystack, $needle, $encoding = null) - { - return substr_count($haystack, $needle); - } - - public static function mb_output_handler($contents, $status) - { - return $contents; - } - - public static function mb_chr($code, $encoding = null) - { - if (0x80 > $code %= 0x200000) { - $s = chr($code); - } elseif (0x800 > $code) { - $s = chr(0xC0 | $code >> 6).chr(0x80 | $code & 0x3F); - } elseif (0x10000 > $code) { - $s = chr(0xE0 | $code >> 12).chr(0x80 | $code >> 6 & 0x3F).chr(0x80 | $code & 0x3F); - } else { - $s = chr(0xF0 | $code >> 18).chr(0x80 | $code >> 12 & 0x3F).chr(0x80 | $code >> 6 & 0x3F).chr(0x80 | $code & 0x3F); - } - - if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { - $s = mb_convert_encoding($s, $encoding, 'UTF-8'); - } - - return $s; - } - - public static function mb_ord($s, $encoding = null) - { - if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { - $s = mb_convert_encoding($s, 'UTF-8', $encoding); - } - - $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; - if (0xF0 <= $code) { - return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; - } - if (0xE0 <= $code) { - return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; - } - if (0xC0 <= $code) { - return (($code - 0xC0) << 6) + $s[2] - 0x80; - } - - return $code; - } - - private static function getSubpart($pos, $part, $haystack, $encoding) - { - if (false === $pos) { - return false; - } - if ($part) { - return self::mb_substr($haystack, 0, $pos, $encoding); - } - - return self::mb_substr($haystack, $pos, null, $encoding); - } - - private static function html_encoding_callback($m) - { - $i = 1; - $entities = ''; - $m = unpack('C*', htmlentities($m[0], ENT_COMPAT, 'UTF-8')); - - while (isset($m[$i])) { - if (0x80 > $m[$i]) { - $entities .= chr($m[$i++]); - continue; - } - if (0xF0 <= $m[$i]) { - $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; - } elseif (0xE0 <= $m[$i]) { - $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; - } else { - $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80; - } - - $entities .= '&#'.$c.';'; - } - - return $entities; - } - - private static function title_case_lower($s) - { - return self::mb_convert_case($s[0], MB_CASE_LOWER, 'UTF-8'); - } - - private static function title_case_upper($s) - { - return self::mb_convert_case($s[0], MB_CASE_UPPER, 'UTF-8'); - } - - private static function getData($file) - { - if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { - return require $file; - } - - return false; - } - - private static function getEncoding($encoding) - { - if (null === $encoding) { - return self::$internalEncoding; - } - - $encoding = strtoupper($encoding); - - if ('8BIT' === $encoding || 'BINARY' === $encoding) { - return 'CP850'; - } - if ('UTF8' === $encoding) { - return 'UTF-8'; - } - - return $encoding; - } -} - 'a', - 'B' => 'b', - 'C' => 'c', - 'D' => 'd', - 'E' => 'e', - 'F' => 'f', - 'G' => 'g', - 'H' => 'h', - 'I' => 'i', - 'J' => 'j', - 'K' => 'k', - 'L' => 'l', - 'M' => 'm', - 'N' => 'n', - 'O' => 'o', - 'P' => 'p', - 'Q' => 'q', - 'R' => 'r', - 'S' => 's', - 'T' => 't', - 'U' => 'u', - 'V' => 'v', - 'W' => 'w', - 'X' => 'x', - 'Y' => 'y', - 'Z' => 'z', - 'À' => 'à', - 'Á' => 'á', - 'Â' => 'â', - 'Ã' => 'ã', - 'Ä' => 'ä', - 'Å' => 'å', - 'Æ' => 'æ', - 'Ç' => 'ç', - 'È' => 'è', - 'É' => 'é', - 'Ê' => 'ê', - 'Ë' => 'ë', - 'Ì' => 'ì', - 'Í' => 'í', - 'Î' => 'î', - 'Ï' => 'ï', - 'Ð' => 'ð', - 'Ñ' => 'ñ', - 'Ò' => 'ò', - 'Ó' => 'ó', - 'Ô' => 'ô', - 'Õ' => 'õ', - 'Ö' => 'ö', - 'Ø' => 'ø', - 'Ù' => 'ù', - 'Ú' => 'ú', - 'Û' => 'û', - 'Ü' => 'ü', - 'Ý' => 'ý', - 'Þ' => 'þ', - 'Ā' => 'ā', - 'Ă' => 'ă', - 'Ą' => 'ą', - 'Ć' => 'ć', - 'Ĉ' => 'ĉ', - 'Ċ' => 'ċ', - 'Č' => 'č', - 'Ď' => 'ď', - 'Đ' => 'đ', - 'Ē' => 'ē', - 'Ĕ' => 'ĕ', - 'Ė' => 'ė', - 'Ę' => 'ę', - 'Ě' => 'ě', - 'Ĝ' => 'ĝ', - 'Ğ' => 'ğ', - 'Ġ' => 'ġ', - 'Ģ' => 'ģ', - 'Ĥ' => 'ĥ', - 'Ħ' => 'ħ', - 'Ĩ' => 'ĩ', - 'Ī' => 'ī', - 'Ĭ' => 'ĭ', - 'Į' => 'į', - 'İ' => 'i', - 'IJ' => 'ij', - 'Ĵ' => 'ĵ', - 'Ķ' => 'ķ', - 'Ĺ' => 'ĺ', - 'Ļ' => 'ļ', - 'Ľ' => 'ľ', - 'Ŀ' => 'ŀ', - 'Ł' => 'ł', - 'Ń' => 'ń', - 'Ņ' => 'ņ', - 'Ň' => 'ň', - 'Ŋ' => 'ŋ', - 'Ō' => 'ō', - 'Ŏ' => 'ŏ', - 'Ő' => 'ő', - 'Œ' => 'œ', - 'Ŕ' => 'ŕ', - 'Ŗ' => 'ŗ', - 'Ř' => 'ř', - 'Ś' => 'ś', - 'Ŝ' => 'ŝ', - 'Ş' => 'ş', - 'Š' => 'š', - 'Ţ' => 'ţ', - 'Ť' => 'ť', - 'Ŧ' => 'ŧ', - 'Ũ' => 'ũ', - 'Ū' => 'ū', - 'Ŭ' => 'ŭ', - 'Ů' => 'ů', - 'Ű' => 'ű', - 'Ų' => 'ų', - 'Ŵ' => 'ŵ', - 'Ŷ' => 'ŷ', - 'Ÿ' => 'ÿ', - 'Ź' => 'ź', - 'Ż' => 'ż', - 'Ž' => 'ž', - 'Ɓ' => 'ɓ', - 'Ƃ' => 'ƃ', - 'Ƅ' => 'ƅ', - 'Ɔ' => 'ɔ', - 'Ƈ' => 'ƈ', - 'Ɖ' => 'ɖ', - 'Ɗ' => 'ɗ', - 'Ƌ' => 'ƌ', - 'Ǝ' => 'ǝ', - 'Ə' => 'ə', - 'Ɛ' => 'ɛ', - 'Ƒ' => 'ƒ', - 'Ɠ' => 'ɠ', - 'Ɣ' => 'ɣ', - 'Ɩ' => 'ɩ', - 'Ɨ' => 'ɨ', - 'Ƙ' => 'ƙ', - 'Ɯ' => 'ɯ', - 'Ɲ' => 'ɲ', - 'Ɵ' => 'ɵ', - 'Ơ' => 'ơ', - 'Ƣ' => 'ƣ', - 'Ƥ' => 'ƥ', - 'Ʀ' => 'ʀ', - 'Ƨ' => 'ƨ', - 'Ʃ' => 'ʃ', - 'Ƭ' => 'ƭ', - 'Ʈ' => 'ʈ', - 'Ư' => 'ư', - 'Ʊ' => 'ʊ', - 'Ʋ' => 'ʋ', - 'Ƴ' => 'ƴ', - 'Ƶ' => 'ƶ', - 'Ʒ' => 'ʒ', - 'Ƹ' => 'ƹ', - 'Ƽ' => 'ƽ', - 'DŽ' => 'dž', - 'Dž' => 'dž', - 'LJ' => 'lj', - 'Lj' => 'lj', - 'NJ' => 'nj', - 'Nj' => 'nj', - 'Ǎ' => 'ǎ', - 'Ǐ' => 'ǐ', - 'Ǒ' => 'ǒ', - 'Ǔ' => 'ǔ', - 'Ǖ' => 'ǖ', - 'Ǘ' => 'ǘ', - 'Ǚ' => 'ǚ', - 'Ǜ' => 'ǜ', - 'Ǟ' => 'ǟ', - 'Ǡ' => 'ǡ', - 'Ǣ' => 'ǣ', - 'Ǥ' => 'ǥ', - 'Ǧ' => 'ǧ', - 'Ǩ' => 'ǩ', - 'Ǫ' => 'ǫ', - 'Ǭ' => 'ǭ', - 'Ǯ' => 'ǯ', - 'DZ' => 'dz', - 'Dz' => 'dz', - 'Ǵ' => 'ǵ', - 'Ƕ' => 'ƕ', - 'Ƿ' => 'ƿ', - 'Ǹ' => 'ǹ', - 'Ǻ' => 'ǻ', - 'Ǽ' => 'ǽ', - 'Ǿ' => 'ǿ', - 'Ȁ' => 'ȁ', - 'Ȃ' => 'ȃ', - 'Ȅ' => 'ȅ', - 'Ȇ' => 'ȇ', - 'Ȉ' => 'ȉ', - 'Ȋ' => 'ȋ', - 'Ȍ' => 'ȍ', - 'Ȏ' => 'ȏ', - 'Ȑ' => 'ȑ', - 'Ȓ' => 'ȓ', - 'Ȕ' => 'ȕ', - 'Ȗ' => 'ȗ', - 'Ș' => 'ș', - 'Ț' => 'ț', - 'Ȝ' => 'ȝ', - 'Ȟ' => 'ȟ', - 'Ƞ' => 'ƞ', - 'Ȣ' => 'ȣ', - 'Ȥ' => 'ȥ', - 'Ȧ' => 'ȧ', - 'Ȩ' => 'ȩ', - 'Ȫ' => 'ȫ', - 'Ȭ' => 'ȭ', - 'Ȯ' => 'ȯ', - 'Ȱ' => 'ȱ', - 'Ȳ' => 'ȳ', - 'Ⱥ' => 'ⱥ', - 'Ȼ' => 'ȼ', - 'Ƚ' => 'ƚ', - 'Ⱦ' => 'ⱦ', - 'Ɂ' => 'ɂ', - 'Ƀ' => 'ƀ', - 'Ʉ' => 'ʉ', - 'Ʌ' => 'ʌ', - 'Ɇ' => 'ɇ', - 'Ɉ' => 'ɉ', - 'Ɋ' => 'ɋ', - 'Ɍ' => 'ɍ', - 'Ɏ' => 'ɏ', - 'Ͱ' => 'ͱ', - 'Ͳ' => 'ͳ', - 'Ͷ' => 'ͷ', - 'Ϳ' => 'ϳ', - 'Ά' => 'ά', - 'Έ' => 'έ', - 'Ή' => 'ή', - 'Ί' => 'ί', - 'Ό' => 'ό', - 'Ύ' => 'ύ', - 'Ώ' => 'ώ', - 'Α' => 'α', - 'Β' => 'β', - 'Γ' => 'γ', - 'Δ' => 'δ', - 'Ε' => 'ε', - 'Ζ' => 'ζ', - 'Η' => 'η', - 'Θ' => 'θ', - 'Ι' => 'ι', - 'Κ' => 'κ', - 'Λ' => 'λ', - 'Μ' => 'μ', - 'Ν' => 'ν', - 'Ξ' => 'ξ', - 'Ο' => 'ο', - 'Π' => 'π', - 'Ρ' => 'ρ', - 'Σ' => 'σ', - 'Τ' => 'τ', - 'Υ' => 'υ', - 'Φ' => 'φ', - 'Χ' => 'χ', - 'Ψ' => 'ψ', - 'Ω' => 'ω', - 'Ϊ' => 'ϊ', - 'Ϋ' => 'ϋ', - 'Ϗ' => 'ϗ', - 'Ϙ' => 'ϙ', - 'Ϛ' => 'ϛ', - 'Ϝ' => 'ϝ', - 'Ϟ' => 'ϟ', - 'Ϡ' => 'ϡ', - 'Ϣ' => 'ϣ', - 'Ϥ' => 'ϥ', - 'Ϧ' => 'ϧ', - 'Ϩ' => 'ϩ', - 'Ϫ' => 'ϫ', - 'Ϭ' => 'ϭ', - 'Ϯ' => 'ϯ', - 'ϴ' => 'θ', - 'Ϸ' => 'ϸ', - 'Ϲ' => 'ϲ', - 'Ϻ' => 'ϻ', - 'Ͻ' => 'ͻ', - 'Ͼ' => 'ͼ', - 'Ͽ' => 'ͽ', - 'Ѐ' => 'ѐ', - 'Ё' => 'ё', - 'Ђ' => 'ђ', - 'Ѓ' => 'ѓ', - 'Є' => 'є', - 'Ѕ' => 'ѕ', - 'І' => 'і', - 'Ї' => 'ї', - 'Ј' => 'ј', - 'Љ' => 'љ', - 'Њ' => 'њ', - 'Ћ' => 'ћ', - 'Ќ' => 'ќ', - 'Ѝ' => 'ѝ', - 'Ў' => 'ў', - 'Џ' => 'џ', - 'А' => 'а', - 'Б' => 'б', - 'В' => 'в', - 'Г' => 'г', - 'Д' => 'д', - 'Е' => 'е', - 'Ж' => 'ж', - 'З' => 'з', - 'И' => 'и', - 'Й' => 'й', - 'К' => 'к', - 'Л' => 'л', - 'М' => 'м', - 'Н' => 'н', - 'О' => 'о', - 'П' => 'п', - 'Р' => 'р', - 'С' => 'с', - 'Т' => 'т', - 'У' => 'у', - 'Ф' => 'ф', - 'Х' => 'х', - 'Ц' => 'ц', - 'Ч' => 'ч', - 'Ш' => 'ш', - 'Щ' => 'щ', - 'Ъ' => 'ъ', - 'Ы' => 'ы', - 'Ь' => 'ь', - 'Э' => 'э', - 'Ю' => 'ю', - 'Я' => 'я', - 'Ѡ' => 'ѡ', - 'Ѣ' => 'ѣ', - 'Ѥ' => 'ѥ', - 'Ѧ' => 'ѧ', - 'Ѩ' => 'ѩ', - 'Ѫ' => 'ѫ', - 'Ѭ' => 'ѭ', - 'Ѯ' => 'ѯ', - 'Ѱ' => 'ѱ', - 'Ѳ' => 'ѳ', - 'Ѵ' => 'ѵ', - 'Ѷ' => 'ѷ', - 'Ѹ' => 'ѹ', - 'Ѻ' => 'ѻ', - 'Ѽ' => 'ѽ', - 'Ѿ' => 'ѿ', - 'Ҁ' => 'ҁ', - 'Ҋ' => 'ҋ', - 'Ҍ' => 'ҍ', - 'Ҏ' => 'ҏ', - 'Ґ' => 'ґ', - 'Ғ' => 'ғ', - 'Ҕ' => 'ҕ', - 'Җ' => 'җ', - 'Ҙ' => 'ҙ', - 'Қ' => 'қ', - 'Ҝ' => 'ҝ', - 'Ҟ' => 'ҟ', - 'Ҡ' => 'ҡ', - 'Ң' => 'ң', - 'Ҥ' => 'ҥ', - 'Ҧ' => 'ҧ', - 'Ҩ' => 'ҩ', - 'Ҫ' => 'ҫ', - 'Ҭ' => 'ҭ', - 'Ү' => 'ү', - 'Ұ' => 'ұ', - 'Ҳ' => 'ҳ', - 'Ҵ' => 'ҵ', - 'Ҷ' => 'ҷ', - 'Ҹ' => 'ҹ', - 'Һ' => 'һ', - 'Ҽ' => 'ҽ', - 'Ҿ' => 'ҿ', - 'Ӏ' => 'ӏ', - 'Ӂ' => 'ӂ', - 'Ӄ' => 'ӄ', - 'Ӆ' => 'ӆ', - 'Ӈ' => 'ӈ', - 'Ӊ' => 'ӊ', - 'Ӌ' => 'ӌ', - 'Ӎ' => 'ӎ', - 'Ӑ' => 'ӑ', - 'Ӓ' => 'ӓ', - 'Ӕ' => 'ӕ', - 'Ӗ' => 'ӗ', - 'Ә' => 'ә', - 'Ӛ' => 'ӛ', - 'Ӝ' => 'ӝ', - 'Ӟ' => 'ӟ', - 'Ӡ' => 'ӡ', - 'Ӣ' => 'ӣ', - 'Ӥ' => 'ӥ', - 'Ӧ' => 'ӧ', - 'Ө' => 'ө', - 'Ӫ' => 'ӫ', - 'Ӭ' => 'ӭ', - 'Ӯ' => 'ӯ', - 'Ӱ' => 'ӱ', - 'Ӳ' => 'ӳ', - 'Ӵ' => 'ӵ', - 'Ӷ' => 'ӷ', - 'Ӹ' => 'ӹ', - 'Ӻ' => 'ӻ', - 'Ӽ' => 'ӽ', - 'Ӿ' => 'ӿ', - 'Ԁ' => 'ԁ', - 'Ԃ' => 'ԃ', - 'Ԅ' => 'ԅ', - 'Ԇ' => 'ԇ', - 'Ԉ' => 'ԉ', - 'Ԋ' => 'ԋ', - 'Ԍ' => 'ԍ', - 'Ԏ' => 'ԏ', - 'Ԑ' => 'ԑ', - 'Ԓ' => 'ԓ', - 'Ԕ' => 'ԕ', - 'Ԗ' => 'ԗ', - 'Ԙ' => 'ԙ', - 'Ԛ' => 'ԛ', - 'Ԝ' => 'ԝ', - 'Ԟ' => 'ԟ', - 'Ԡ' => 'ԡ', - 'Ԣ' => 'ԣ', - 'Ԥ' => 'ԥ', - 'Ԧ' => 'ԧ', - 'Ԩ' => 'ԩ', - 'Ԫ' => 'ԫ', - 'Ԭ' => 'ԭ', - 'Ԯ' => 'ԯ', - 'Ա' => 'ա', - 'Բ' => 'բ', - 'Գ' => 'գ', - 'Դ' => 'դ', - 'Ե' => 'ե', - 'Զ' => 'զ', - 'Է' => 'է', - 'Ը' => 'ը', - 'Թ' => 'թ', - 'Ժ' => 'ժ', - 'Ի' => 'ի', - 'Լ' => 'լ', - 'Խ' => 'խ', - 'Ծ' => 'ծ', - 'Կ' => 'կ', - 'Հ' => 'հ', - 'Ձ' => 'ձ', - 'Ղ' => 'ղ', - 'Ճ' => 'ճ', - 'Մ' => 'մ', - 'Յ' => 'յ', - 'Ն' => 'ն', - 'Շ' => 'շ', - 'Ո' => 'ո', - 'Չ' => 'չ', - 'Պ' => 'պ', - 'Ջ' => 'ջ', - 'Ռ' => 'ռ', - 'Ս' => 'ս', - 'Վ' => 'վ', - 'Տ' => 'տ', - 'Ր' => 'ր', - 'Ց' => 'ց', - 'Ւ' => 'ւ', - 'Փ' => 'փ', - 'Ք' => 'ք', - 'Օ' => 'օ', - 'Ֆ' => 'ֆ', - 'Ⴀ' => 'ⴀ', - 'Ⴁ' => 'ⴁ', - 'Ⴂ' => 'ⴂ', - 'Ⴃ' => 'ⴃ', - 'Ⴄ' => 'ⴄ', - 'Ⴅ' => 'ⴅ', - 'Ⴆ' => 'ⴆ', - 'Ⴇ' => 'ⴇ', - 'Ⴈ' => 'ⴈ', - 'Ⴉ' => 'ⴉ', - 'Ⴊ' => 'ⴊ', - 'Ⴋ' => 'ⴋ', - 'Ⴌ' => 'ⴌ', - 'Ⴍ' => 'ⴍ', - 'Ⴎ' => 'ⴎ', - 'Ⴏ' => 'ⴏ', - 'Ⴐ' => 'ⴐ', - 'Ⴑ' => 'ⴑ', - 'Ⴒ' => 'ⴒ', - 'Ⴓ' => 'ⴓ', - 'Ⴔ' => 'ⴔ', - 'Ⴕ' => 'ⴕ', - 'Ⴖ' => 'ⴖ', - 'Ⴗ' => 'ⴗ', - 'Ⴘ' => 'ⴘ', - 'Ⴙ' => 'ⴙ', - 'Ⴚ' => 'ⴚ', - 'Ⴛ' => 'ⴛ', - 'Ⴜ' => 'ⴜ', - 'Ⴝ' => 'ⴝ', - 'Ⴞ' => 'ⴞ', - 'Ⴟ' => 'ⴟ', - 'Ⴠ' => 'ⴠ', - 'Ⴡ' => 'ⴡ', - 'Ⴢ' => 'ⴢ', - 'Ⴣ' => 'ⴣ', - 'Ⴤ' => 'ⴤ', - 'Ⴥ' => 'ⴥ', - 'Ⴧ' => 'ⴧ', - 'Ⴭ' => 'ⴭ', - 'Ḁ' => 'ḁ', - 'Ḃ' => 'ḃ', - 'Ḅ' => 'ḅ', - 'Ḇ' => 'ḇ', - 'Ḉ' => 'ḉ', - 'Ḋ' => 'ḋ', - 'Ḍ' => 'ḍ', - 'Ḏ' => 'ḏ', - 'Ḑ' => 'ḑ', - 'Ḓ' => 'ḓ', - 'Ḕ' => 'ḕ', - 'Ḗ' => 'ḗ', - 'Ḙ' => 'ḙ', - 'Ḛ' => 'ḛ', - 'Ḝ' => 'ḝ', - 'Ḟ' => 'ḟ', - 'Ḡ' => 'ḡ', - 'Ḣ' => 'ḣ', - 'Ḥ' => 'ḥ', - 'Ḧ' => 'ḧ', - 'Ḩ' => 'ḩ', - 'Ḫ' => 'ḫ', - 'Ḭ' => 'ḭ', - 'Ḯ' => 'ḯ', - 'Ḱ' => 'ḱ', - 'Ḳ' => 'ḳ', - 'Ḵ' => 'ḵ', - 'Ḷ' => 'ḷ', - 'Ḹ' => 'ḹ', - 'Ḻ' => 'ḻ', - 'Ḽ' => 'ḽ', - 'Ḿ' => 'ḿ', - 'Ṁ' => 'ṁ', - 'Ṃ' => 'ṃ', - 'Ṅ' => 'ṅ', - 'Ṇ' => 'ṇ', - 'Ṉ' => 'ṉ', - 'Ṋ' => 'ṋ', - 'Ṍ' => 'ṍ', - 'Ṏ' => 'ṏ', - 'Ṑ' => 'ṑ', - 'Ṓ' => 'ṓ', - 'Ṕ' => 'ṕ', - 'Ṗ' => 'ṗ', - 'Ṙ' => 'ṙ', - 'Ṛ' => 'ṛ', - 'Ṝ' => 'ṝ', - 'Ṟ' => 'ṟ', - 'Ṡ' => 'ṡ', - 'Ṣ' => 'ṣ', - 'Ṥ' => 'ṥ', - 'Ṧ' => 'ṧ', - 'Ṩ' => 'ṩ', - 'Ṫ' => 'ṫ', - 'Ṭ' => 'ṭ', - 'Ṯ' => 'ṯ', - 'Ṱ' => 'ṱ', - 'Ṳ' => 'ṳ', - 'Ṵ' => 'ṵ', - 'Ṷ' => 'ṷ', - 'Ṹ' => 'ṹ', - 'Ṻ' => 'ṻ', - 'Ṽ' => 'ṽ', - 'Ṿ' => 'ṿ', - 'Ẁ' => 'ẁ', - 'Ẃ' => 'ẃ', - 'Ẅ' => 'ẅ', - 'Ẇ' => 'ẇ', - 'Ẉ' => 'ẉ', - 'Ẋ' => 'ẋ', - 'Ẍ' => 'ẍ', - 'Ẏ' => 'ẏ', - 'Ẑ' => 'ẑ', - 'Ẓ' => 'ẓ', - 'Ẕ' => 'ẕ', - 'ẞ' => 'ß', - 'Ạ' => 'ạ', - 'Ả' => 'ả', - 'Ấ' => 'ấ', - 'Ầ' => 'ầ', - 'Ẩ' => 'ẩ', - 'Ẫ' => 'ẫ', - 'Ậ' => 'ậ', - 'Ắ' => 'ắ', - 'Ằ' => 'ằ', - 'Ẳ' => 'ẳ', - 'Ẵ' => 'ẵ', - 'Ặ' => 'ặ', - 'Ẹ' => 'ẹ', - 'Ẻ' => 'ẻ', - 'Ẽ' => 'ẽ', - 'Ế' => 'ế', - 'Ề' => 'ề', - 'Ể' => 'ể', - 'Ễ' => 'ễ', - 'Ệ' => 'ệ', - 'Ỉ' => 'ỉ', - 'Ị' => 'ị', - 'Ọ' => 'ọ', - 'Ỏ' => 'ỏ', - 'Ố' => 'ố', - 'Ồ' => 'ồ', - 'Ổ' => 'ổ', - 'Ỗ' => 'ỗ', - 'Ộ' => 'ộ', - 'Ớ' => 'ớ', - 'Ờ' => 'ờ', - 'Ở' => 'ở', - 'Ỡ' => 'ỡ', - 'Ợ' => 'ợ', - 'Ụ' => 'ụ', - 'Ủ' => 'ủ', - 'Ứ' => 'ứ', - 'Ừ' => 'ừ', - 'Ử' => 'ử', - 'Ữ' => 'ữ', - 'Ự' => 'ự', - 'Ỳ' => 'ỳ', - 'Ỵ' => 'ỵ', - 'Ỷ' => 'ỷ', - 'Ỹ' => 'ỹ', - 'Ỻ' => 'ỻ', - 'Ỽ' => 'ỽ', - 'Ỿ' => 'ỿ', - 'Ἀ' => 'ἀ', - 'Ἁ' => 'ἁ', - 'Ἂ' => 'ἂ', - 'Ἃ' => 'ἃ', - 'Ἄ' => 'ἄ', - 'Ἅ' => 'ἅ', - 'Ἆ' => 'ἆ', - 'Ἇ' => 'ἇ', - 'Ἐ' => 'ἐ', - 'Ἑ' => 'ἑ', - 'Ἒ' => 'ἒ', - 'Ἓ' => 'ἓ', - 'Ἔ' => 'ἔ', - 'Ἕ' => 'ἕ', - 'Ἠ' => 'ἠ', - 'Ἡ' => 'ἡ', - 'Ἢ' => 'ἢ', - 'Ἣ' => 'ἣ', - 'Ἤ' => 'ἤ', - 'Ἥ' => 'ἥ', - 'Ἦ' => 'ἦ', - 'Ἧ' => 'ἧ', - 'Ἰ' => 'ἰ', - 'Ἱ' => 'ἱ', - 'Ἲ' => 'ἲ', - 'Ἳ' => 'ἳ', - 'Ἴ' => 'ἴ', - 'Ἵ' => 'ἵ', - 'Ἶ' => 'ἶ', - 'Ἷ' => 'ἷ', - 'Ὀ' => 'ὀ', - 'Ὁ' => 'ὁ', - 'Ὂ' => 'ὂ', - 'Ὃ' => 'ὃ', - 'Ὄ' => 'ὄ', - 'Ὅ' => 'ὅ', - 'Ὑ' => 'ὑ', - 'Ὓ' => 'ὓ', - 'Ὕ' => 'ὕ', - 'Ὗ' => 'ὗ', - 'Ὠ' => 'ὠ', - 'Ὡ' => 'ὡ', - 'Ὢ' => 'ὢ', - 'Ὣ' => 'ὣ', - 'Ὤ' => 'ὤ', - 'Ὥ' => 'ὥ', - 'Ὦ' => 'ὦ', - 'Ὧ' => 'ὧ', - 'ᾈ' => 'ᾀ', - 'ᾉ' => 'ᾁ', - 'ᾊ' => 'ᾂ', - 'ᾋ' => 'ᾃ', - 'ᾌ' => 'ᾄ', - 'ᾍ' => 'ᾅ', - 'ᾎ' => 'ᾆ', - 'ᾏ' => 'ᾇ', - 'ᾘ' => 'ᾐ', - 'ᾙ' => 'ᾑ', - 'ᾚ' => 'ᾒ', - 'ᾛ' => 'ᾓ', - 'ᾜ' => 'ᾔ', - 'ᾝ' => 'ᾕ', - 'ᾞ' => 'ᾖ', - 'ᾟ' => 'ᾗ', - 'ᾨ' => 'ᾠ', - 'ᾩ' => 'ᾡ', - 'ᾪ' => 'ᾢ', - 'ᾫ' => 'ᾣ', - 'ᾬ' => 'ᾤ', - 'ᾭ' => 'ᾥ', - 'ᾮ' => 'ᾦ', - 'ᾯ' => 'ᾧ', - 'Ᾰ' => 'ᾰ', - 'Ᾱ' => 'ᾱ', - 'Ὰ' => 'ὰ', - 'Ά' => 'ά', - 'ᾼ' => 'ᾳ', - 'Ὲ' => 'ὲ', - 'Έ' => 'έ', - 'Ὴ' => 'ὴ', - 'Ή' => 'ή', - 'ῌ' => 'ῃ', - 'Ῐ' => 'ῐ', - 'Ῑ' => 'ῑ', - 'Ὶ' => 'ὶ', - 'Ί' => 'ί', - 'Ῠ' => 'ῠ', - 'Ῡ' => 'ῡ', - 'Ὺ' => 'ὺ', - 'Ύ' => 'ύ', - 'Ῥ' => 'ῥ', - 'Ὸ' => 'ὸ', - 'Ό' => 'ό', - 'Ὼ' => 'ὼ', - 'Ώ' => 'ώ', - 'ῼ' => 'ῳ', - 'Ω' => 'ω', - 'K' => 'k', - 'Å' => 'å', - 'Ⅎ' => 'ⅎ', - 'Ⅰ' => 'ⅰ', - 'Ⅱ' => 'ⅱ', - 'Ⅲ' => 'ⅲ', - 'Ⅳ' => 'ⅳ', - 'Ⅴ' => 'ⅴ', - 'Ⅵ' => 'ⅵ', - 'Ⅶ' => 'ⅶ', - 'Ⅷ' => 'ⅷ', - 'Ⅸ' => 'ⅸ', - 'Ⅹ' => 'ⅹ', - 'Ⅺ' => 'ⅺ', - 'Ⅻ' => 'ⅻ', - 'Ⅼ' => 'ⅼ', - 'Ⅽ' => 'ⅽ', - 'Ⅾ' => 'ⅾ', - 'Ⅿ' => 'ⅿ', - 'Ↄ' => 'ↄ', - 'Ⓐ' => 'ⓐ', - 'Ⓑ' => 'ⓑ', - 'Ⓒ' => 'ⓒ', - 'Ⓓ' => 'ⓓ', - 'Ⓔ' => 'ⓔ', - 'Ⓕ' => 'ⓕ', - 'Ⓖ' => 'ⓖ', - 'Ⓗ' => 'ⓗ', - 'Ⓘ' => 'ⓘ', - 'Ⓙ' => 'ⓙ', - 'Ⓚ' => 'ⓚ', - 'Ⓛ' => 'ⓛ', - 'Ⓜ' => 'ⓜ', - 'Ⓝ' => 'ⓝ', - 'Ⓞ' => 'ⓞ', - 'Ⓟ' => 'ⓟ', - 'Ⓠ' => 'ⓠ', - 'Ⓡ' => 'ⓡ', - 'Ⓢ' => 'ⓢ', - 'Ⓣ' => 'ⓣ', - 'Ⓤ' => 'ⓤ', - 'Ⓥ' => 'ⓥ', - 'Ⓦ' => 'ⓦ', - 'Ⓧ' => 'ⓧ', - 'Ⓨ' => 'ⓨ', - 'Ⓩ' => 'ⓩ', - 'Ⰰ' => 'ⰰ', - 'Ⰱ' => 'ⰱ', - 'Ⰲ' => 'ⰲ', - 'Ⰳ' => 'ⰳ', - 'Ⰴ' => 'ⰴ', - 'Ⰵ' => 'ⰵ', - 'Ⰶ' => 'ⰶ', - 'Ⰷ' => 'ⰷ', - 'Ⰸ' => 'ⰸ', - 'Ⰹ' => 'ⰹ', - 'Ⰺ' => 'ⰺ', - 'Ⰻ' => 'ⰻ', - 'Ⰼ' => 'ⰼ', - 'Ⰽ' => 'ⰽ', - 'Ⰾ' => 'ⰾ', - 'Ⰿ' => 'ⰿ', - 'Ⱀ' => 'ⱀ', - 'Ⱁ' => 'ⱁ', - 'Ⱂ' => 'ⱂ', - 'Ⱃ' => 'ⱃ', - 'Ⱄ' => 'ⱄ', - 'Ⱅ' => 'ⱅ', - 'Ⱆ' => 'ⱆ', - 'Ⱇ' => 'ⱇ', - 'Ⱈ' => 'ⱈ', - 'Ⱉ' => 'ⱉ', - 'Ⱊ' => 'ⱊ', - 'Ⱋ' => 'ⱋ', - 'Ⱌ' => 'ⱌ', - 'Ⱍ' => 'ⱍ', - 'Ⱎ' => 'ⱎ', - 'Ⱏ' => 'ⱏ', - 'Ⱐ' => 'ⱐ', - 'Ⱑ' => 'ⱑ', - 'Ⱒ' => 'ⱒ', - 'Ⱓ' => 'ⱓ', - 'Ⱔ' => 'ⱔ', - 'Ⱕ' => 'ⱕ', - 'Ⱖ' => 'ⱖ', - 'Ⱗ' => 'ⱗ', - 'Ⱘ' => 'ⱘ', - 'Ⱙ' => 'ⱙ', - 'Ⱚ' => 'ⱚ', - 'Ⱛ' => 'ⱛ', - 'Ⱜ' => 'ⱜ', - 'Ⱝ' => 'ⱝ', - 'Ⱞ' => 'ⱞ', - 'Ⱡ' => 'ⱡ', - 'Ɫ' => 'ɫ', - 'Ᵽ' => 'ᵽ', - 'Ɽ' => 'ɽ', - 'Ⱨ' => 'ⱨ', - 'Ⱪ' => 'ⱪ', - 'Ⱬ' => 'ⱬ', - 'Ɑ' => 'ɑ', - 'Ɱ' => 'ɱ', - 'Ɐ' => 'ɐ', - 'Ɒ' => 'ɒ', - 'Ⱳ' => 'ⱳ', - 'Ⱶ' => 'ⱶ', - 'Ȿ' => 'ȿ', - 'Ɀ' => 'ɀ', - 'Ⲁ' => 'ⲁ', - 'Ⲃ' => 'ⲃ', - 'Ⲅ' => 'ⲅ', - 'Ⲇ' => 'ⲇ', - 'Ⲉ' => 'ⲉ', - 'Ⲋ' => 'ⲋ', - 'Ⲍ' => 'ⲍ', - 'Ⲏ' => 'ⲏ', - 'Ⲑ' => 'ⲑ', - 'Ⲓ' => 'ⲓ', - 'Ⲕ' => 'ⲕ', - 'Ⲗ' => 'ⲗ', - 'Ⲙ' => 'ⲙ', - 'Ⲛ' => 'ⲛ', - 'Ⲝ' => 'ⲝ', - 'Ⲟ' => 'ⲟ', - 'Ⲡ' => 'ⲡ', - 'Ⲣ' => 'ⲣ', - 'Ⲥ' => 'ⲥ', - 'Ⲧ' => 'ⲧ', - 'Ⲩ' => 'ⲩ', - 'Ⲫ' => 'ⲫ', - 'Ⲭ' => 'ⲭ', - 'Ⲯ' => 'ⲯ', - 'Ⲱ' => 'ⲱ', - 'Ⲳ' => 'ⲳ', - 'Ⲵ' => 'ⲵ', - 'Ⲷ' => 'ⲷ', - 'Ⲹ' => 'ⲹ', - 'Ⲻ' => 'ⲻ', - 'Ⲽ' => 'ⲽ', - 'Ⲿ' => 'ⲿ', - 'Ⳁ' => 'ⳁ', - 'Ⳃ' => 'ⳃ', - 'Ⳅ' => 'ⳅ', - 'Ⳇ' => 'ⳇ', - 'Ⳉ' => 'ⳉ', - 'Ⳋ' => 'ⳋ', - 'Ⳍ' => 'ⳍ', - 'Ⳏ' => 'ⳏ', - 'Ⳑ' => 'ⳑ', - 'Ⳓ' => 'ⳓ', - 'Ⳕ' => 'ⳕ', - 'Ⳗ' => 'ⳗ', - 'Ⳙ' => 'ⳙ', - 'Ⳛ' => 'ⳛ', - 'Ⳝ' => 'ⳝ', - 'Ⳟ' => 'ⳟ', - 'Ⳡ' => 'ⳡ', - 'Ⳣ' => 'ⳣ', - 'Ⳬ' => 'ⳬ', - 'Ⳮ' => 'ⳮ', - 'Ⳳ' => 'ⳳ', - 'Ꙁ' => 'ꙁ', - 'Ꙃ' => 'ꙃ', - 'Ꙅ' => 'ꙅ', - 'Ꙇ' => 'ꙇ', - 'Ꙉ' => 'ꙉ', - 'Ꙋ' => 'ꙋ', - 'Ꙍ' => 'ꙍ', - 'Ꙏ' => 'ꙏ', - 'Ꙑ' => 'ꙑ', - 'Ꙓ' => 'ꙓ', - 'Ꙕ' => 'ꙕ', - 'Ꙗ' => 'ꙗ', - 'Ꙙ' => 'ꙙ', - 'Ꙛ' => 'ꙛ', - 'Ꙝ' => 'ꙝ', - 'Ꙟ' => 'ꙟ', - 'Ꙡ' => 'ꙡ', - 'Ꙣ' => 'ꙣ', - 'Ꙥ' => 'ꙥ', - 'Ꙧ' => 'ꙧ', - 'Ꙩ' => 'ꙩ', - 'Ꙫ' => 'ꙫ', - 'Ꙭ' => 'ꙭ', - 'Ꚁ' => 'ꚁ', - 'Ꚃ' => 'ꚃ', - 'Ꚅ' => 'ꚅ', - 'Ꚇ' => 'ꚇ', - 'Ꚉ' => 'ꚉ', - 'Ꚋ' => 'ꚋ', - 'Ꚍ' => 'ꚍ', - 'Ꚏ' => 'ꚏ', - 'Ꚑ' => 'ꚑ', - 'Ꚓ' => 'ꚓ', - 'Ꚕ' => 'ꚕ', - 'Ꚗ' => 'ꚗ', - 'Ꚙ' => 'ꚙ', - 'Ꚛ' => 'ꚛ', - 'Ꜣ' => 'ꜣ', - 'Ꜥ' => 'ꜥ', - 'Ꜧ' => 'ꜧ', - 'Ꜩ' => 'ꜩ', - 'Ꜫ' => 'ꜫ', - 'Ꜭ' => 'ꜭ', - 'Ꜯ' => 'ꜯ', - 'Ꜳ' => 'ꜳ', - 'Ꜵ' => 'ꜵ', - 'Ꜷ' => 'ꜷ', - 'Ꜹ' => 'ꜹ', - 'Ꜻ' => 'ꜻ', - 'Ꜽ' => 'ꜽ', - 'Ꜿ' => 'ꜿ', - 'Ꝁ' => 'ꝁ', - 'Ꝃ' => 'ꝃ', - 'Ꝅ' => 'ꝅ', - 'Ꝇ' => 'ꝇ', - 'Ꝉ' => 'ꝉ', - 'Ꝋ' => 'ꝋ', - 'Ꝍ' => 'ꝍ', - 'Ꝏ' => 'ꝏ', - 'Ꝑ' => 'ꝑ', - 'Ꝓ' => 'ꝓ', - 'Ꝕ' => 'ꝕ', - 'Ꝗ' => 'ꝗ', - 'Ꝙ' => 'ꝙ', - 'Ꝛ' => 'ꝛ', - 'Ꝝ' => 'ꝝ', - 'Ꝟ' => 'ꝟ', - 'Ꝡ' => 'ꝡ', - 'Ꝣ' => 'ꝣ', - 'Ꝥ' => 'ꝥ', - 'Ꝧ' => 'ꝧ', - 'Ꝩ' => 'ꝩ', - 'Ꝫ' => 'ꝫ', - 'Ꝭ' => 'ꝭ', - 'Ꝯ' => 'ꝯ', - 'Ꝺ' => 'ꝺ', - 'Ꝼ' => 'ꝼ', - 'Ᵹ' => 'ᵹ', - 'Ꝿ' => 'ꝿ', - 'Ꞁ' => 'ꞁ', - 'Ꞃ' => 'ꞃ', - 'Ꞅ' => 'ꞅ', - 'Ꞇ' => 'ꞇ', - 'Ꞌ' => 'ꞌ', - 'Ɥ' => 'ɥ', - 'Ꞑ' => 'ꞑ', - 'Ꞓ' => 'ꞓ', - 'Ꞗ' => 'ꞗ', - 'Ꞙ' => 'ꞙ', - 'Ꞛ' => 'ꞛ', - 'Ꞝ' => 'ꞝ', - 'Ꞟ' => 'ꞟ', - 'Ꞡ' => 'ꞡ', - 'Ꞣ' => 'ꞣ', - 'Ꞥ' => 'ꞥ', - 'Ꞧ' => 'ꞧ', - 'Ꞩ' => 'ꞩ', - 'Ɦ' => 'ɦ', - 'Ɜ' => 'ɜ', - 'Ɡ' => 'ɡ', - 'Ɬ' => 'ɬ', - 'Ʞ' => 'ʞ', - 'Ʇ' => 'ʇ', - 'A' => 'a', - 'B' => 'b', - 'C' => 'c', - 'D' => 'd', - 'E' => 'e', - 'F' => 'f', - 'G' => 'g', - 'H' => 'h', - 'I' => 'i', - 'J' => 'j', - 'K' => 'k', - 'L' => 'l', - 'M' => 'm', - 'N' => 'n', - 'O' => 'o', - 'P' => 'p', - 'Q' => 'q', - 'R' => 'r', - 'S' => 's', - 'T' => 't', - 'U' => 'u', - 'V' => 'v', - 'W' => 'w', - 'X' => 'x', - 'Y' => 'y', - 'Z' => 'z', - '𐐀' => '𐐨', - '𐐁' => '𐐩', - '𐐂' => '𐐪', - '𐐃' => '𐐫', - '𐐄' => '𐐬', - '𐐅' => '𐐭', - '𐐆' => '𐐮', - '𐐇' => '𐐯', - '𐐈' => '𐐰', - '𐐉' => '𐐱', - '𐐊' => '𐐲', - '𐐋' => '𐐳', - '𐐌' => '𐐴', - '𐐍' => '𐐵', - '𐐎' => '𐐶', - '𐐏' => '𐐷', - '𐐐' => '𐐸', - '𐐑' => '𐐹', - '𐐒' => '𐐺', - '𐐓' => '𐐻', - '𐐔' => '𐐼', - '𐐕' => '𐐽', - '𐐖' => '𐐾', - '𐐗' => '𐐿', - '𐐘' => '𐑀', - '𐐙' => '𐑁', - '𐐚' => '𐑂', - '𐐛' => '𐑃', - '𐐜' => '𐑄', - '𐐝' => '𐑅', - '𐐞' => '𐑆', - '𐐟' => '𐑇', - '𐐠' => '𐑈', - '𐐡' => '𐑉', - '𐐢' => '𐑊', - '𐐣' => '𐑋', - '𐐤' => '𐑌', - '𐐥' => '𐑍', - '𐐦' => '𐑎', - '𐐧' => '𐑏', - '𑢠' => '𑣀', - '𑢡' => '𑣁', - '𑢢' => '𑣂', - '𑢣' => '𑣃', - '𑢤' => '𑣄', - '𑢥' => '𑣅', - '𑢦' => '𑣆', - '𑢧' => '𑣇', - '𑢨' => '𑣈', - '𑢩' => '𑣉', - '𑢪' => '𑣊', - '𑢫' => '𑣋', - '𑢬' => '𑣌', - '𑢭' => '𑣍', - '𑢮' => '𑣎', - '𑢯' => '𑣏', - '𑢰' => '𑣐', - '𑢱' => '𑣑', - '𑢲' => '𑣒', - '𑢳' => '𑣓', - '𑢴' => '𑣔', - '𑢵' => '𑣕', - '𑢶' => '𑣖', - '𑢷' => '𑣗', - '𑢸' => '𑣘', - '𑢹' => '𑣙', - '𑢺' => '𑣚', - '𑢻' => '𑣛', - '𑢼' => '𑣜', - '𑢽' => '𑣝', - '𑢾' => '𑣞', - '𑢿' => '𑣟', -); - -$result =& $data; -unset($data); - -return $result; - 'A', - 'b' => 'B', - 'c' => 'C', - 'd' => 'D', - 'e' => 'E', - 'f' => 'F', - 'g' => 'G', - 'h' => 'H', - 'i' => 'I', - 'j' => 'J', - 'k' => 'K', - 'l' => 'L', - 'm' => 'M', - 'n' => 'N', - 'o' => 'O', - 'p' => 'P', - 'q' => 'Q', - 'r' => 'R', - 's' => 'S', - 't' => 'T', - 'u' => 'U', - 'v' => 'V', - 'w' => 'W', - 'x' => 'X', - 'y' => 'Y', - 'z' => 'Z', - 'µ' => 'Μ', - 'à' => 'À', - 'á' => 'Á', - 'â' => 'Â', - 'ã' => 'Ã', - 'ä' => 'Ä', - 'å' => 'Å', - 'æ' => 'Æ', - 'ç' => 'Ç', - 'è' => 'È', - 'é' => 'É', - 'ê' => 'Ê', - 'ë' => 'Ë', - 'ì' => 'Ì', - 'í' => 'Í', - 'î' => 'Î', - 'ï' => 'Ï', - 'ð' => 'Ð', - 'ñ' => 'Ñ', - 'ò' => 'Ò', - 'ó' => 'Ó', - 'ô' => 'Ô', - 'õ' => 'Õ', - 'ö' => 'Ö', - 'ø' => 'Ø', - 'ù' => 'Ù', - 'ú' => 'Ú', - 'û' => 'Û', - 'ü' => 'Ü', - 'ý' => 'Ý', - 'þ' => 'Þ', - 'ÿ' => 'Ÿ', - 'ā' => 'Ā', - 'ă' => 'Ă', - 'ą' => 'Ą', - 'ć' => 'Ć', - 'ĉ' => 'Ĉ', - 'ċ' => 'Ċ', - 'č' => 'Č', - 'ď' => 'Ď', - 'đ' => 'Đ', - 'ē' => 'Ē', - 'ĕ' => 'Ĕ', - 'ė' => 'Ė', - 'ę' => 'Ę', - 'ě' => 'Ě', - 'ĝ' => 'Ĝ', - 'ğ' => 'Ğ', - 'ġ' => 'Ġ', - 'ģ' => 'Ģ', - 'ĥ' => 'Ĥ', - 'ħ' => 'Ħ', - 'ĩ' => 'Ĩ', - 'ī' => 'Ī', - 'ĭ' => 'Ĭ', - 'į' => 'Į', - 'ı' => 'I', - 'ij' => 'IJ', - 'ĵ' => 'Ĵ', - 'ķ' => 'Ķ', - 'ĺ' => 'Ĺ', - 'ļ' => 'Ļ', - 'ľ' => 'Ľ', - 'ŀ' => 'Ŀ', - 'ł' => 'Ł', - 'ń' => 'Ń', - 'ņ' => 'Ņ', - 'ň' => 'Ň', - 'ŋ' => 'Ŋ', - 'ō' => 'Ō', - 'ŏ' => 'Ŏ', - 'ő' => 'Ő', - 'œ' => 'Œ', - 'ŕ' => 'Ŕ', - 'ŗ' => 'Ŗ', - 'ř' => 'Ř', - 'ś' => 'Ś', - 'ŝ' => 'Ŝ', - 'ş' => 'Ş', - 'š' => 'Š', - 'ţ' => 'Ţ', - 'ť' => 'Ť', - 'ŧ' => 'Ŧ', - 'ũ' => 'Ũ', - 'ū' => 'Ū', - 'ŭ' => 'Ŭ', - 'ů' => 'Ů', - 'ű' => 'Ű', - 'ų' => 'Ų', - 'ŵ' => 'Ŵ', - 'ŷ' => 'Ŷ', - 'ź' => 'Ź', - 'ż' => 'Ż', - 'ž' => 'Ž', - 'ſ' => 'S', - 'ƀ' => 'Ƀ', - 'ƃ' => 'Ƃ', - 'ƅ' => 'Ƅ', - 'ƈ' => 'Ƈ', - 'ƌ' => 'Ƌ', - 'ƒ' => 'Ƒ', - 'ƕ' => 'Ƕ', - 'ƙ' => 'Ƙ', - 'ƚ' => 'Ƚ', - 'ƞ' => 'Ƞ', - 'ơ' => 'Ơ', - 'ƣ' => 'Ƣ', - 'ƥ' => 'Ƥ', - 'ƨ' => 'Ƨ', - 'ƭ' => 'Ƭ', - 'ư' => 'Ư', - 'ƴ' => 'Ƴ', - 'ƶ' => 'Ƶ', - 'ƹ' => 'Ƹ', - 'ƽ' => 'Ƽ', - 'ƿ' => 'Ƿ', - 'Dž' => 'DŽ', - 'dž' => 'DŽ', - 'Lj' => 'LJ', - 'lj' => 'LJ', - 'Nj' => 'NJ', - 'nj' => 'NJ', - 'ǎ' => 'Ǎ', - 'ǐ' => 'Ǐ', - 'ǒ' => 'Ǒ', - 'ǔ' => 'Ǔ', - 'ǖ' => 'Ǖ', - 'ǘ' => 'Ǘ', - 'ǚ' => 'Ǚ', - 'ǜ' => 'Ǜ', - 'ǝ' => 'Ǝ', - 'ǟ' => 'Ǟ', - 'ǡ' => 'Ǡ', - 'ǣ' => 'Ǣ', - 'ǥ' => 'Ǥ', - 'ǧ' => 'Ǧ', - 'ǩ' => 'Ǩ', - 'ǫ' => 'Ǫ', - 'ǭ' => 'Ǭ', - 'ǯ' => 'Ǯ', - 'Dz' => 'DZ', - 'dz' => 'DZ', - 'ǵ' => 'Ǵ', - 'ǹ' => 'Ǹ', - 'ǻ' => 'Ǻ', - 'ǽ' => 'Ǽ', - 'ǿ' => 'Ǿ', - 'ȁ' => 'Ȁ', - 'ȃ' => 'Ȃ', - 'ȅ' => 'Ȅ', - 'ȇ' => 'Ȇ', - 'ȉ' => 'Ȉ', - 'ȋ' => 'Ȋ', - 'ȍ' => 'Ȍ', - 'ȏ' => 'Ȏ', - 'ȑ' => 'Ȑ', - 'ȓ' => 'Ȓ', - 'ȕ' => 'Ȕ', - 'ȗ' => 'Ȗ', - 'ș' => 'Ș', - 'ț' => 'Ț', - 'ȝ' => 'Ȝ', - 'ȟ' => 'Ȟ', - 'ȣ' => 'Ȣ', - 'ȥ' => 'Ȥ', - 'ȧ' => 'Ȧ', - 'ȩ' => 'Ȩ', - 'ȫ' => 'Ȫ', - 'ȭ' => 'Ȭ', - 'ȯ' => 'Ȯ', - 'ȱ' => 'Ȱ', - 'ȳ' => 'Ȳ', - 'ȼ' => 'Ȼ', - 'ȿ' => 'Ȿ', - 'ɀ' => 'Ɀ', - 'ɂ' => 'Ɂ', - 'ɇ' => 'Ɇ', - 'ɉ' => 'Ɉ', - 'ɋ' => 'Ɋ', - 'ɍ' => 'Ɍ', - 'ɏ' => 'Ɏ', - 'ɐ' => 'Ɐ', - 'ɑ' => 'Ɑ', - 'ɒ' => 'Ɒ', - 'ɓ' => 'Ɓ', - 'ɔ' => 'Ɔ', - 'ɖ' => 'Ɖ', - 'ɗ' => 'Ɗ', - 'ə' => 'Ə', - 'ɛ' => 'Ɛ', - 'ɜ' => 'Ɜ', - 'ɠ' => 'Ɠ', - 'ɡ' => 'Ɡ', - 'ɣ' => 'Ɣ', - 'ɥ' => 'Ɥ', - 'ɦ' => 'Ɦ', - 'ɨ' => 'Ɨ', - 'ɩ' => 'Ɩ', - 'ɫ' => 'Ɫ', - 'ɬ' => 'Ɬ', - 'ɯ' => 'Ɯ', - 'ɱ' => 'Ɱ', - 'ɲ' => 'Ɲ', - 'ɵ' => 'Ɵ', - 'ɽ' => 'Ɽ', - 'ʀ' => 'Ʀ', - 'ʃ' => 'Ʃ', - 'ʇ' => 'Ʇ', - 'ʈ' => 'Ʈ', - 'ʉ' => 'Ʉ', - 'ʊ' => 'Ʊ', - 'ʋ' => 'Ʋ', - 'ʌ' => 'Ʌ', - 'ʒ' => 'Ʒ', - 'ʞ' => 'Ʞ', - 'ͅ' => 'Ι', - 'ͱ' => 'Ͱ', - 'ͳ' => 'Ͳ', - 'ͷ' => 'Ͷ', - 'ͻ' => 'Ͻ', - 'ͼ' => 'Ͼ', - 'ͽ' => 'Ͽ', - 'ά' => 'Ά', - 'έ' => 'Έ', - 'ή' => 'Ή', - 'ί' => 'Ί', - 'α' => 'Α', - 'β' => 'Β', - 'γ' => 'Γ', - 'δ' => 'Δ', - 'ε' => 'Ε', - 'ζ' => 'Ζ', - 'η' => 'Η', - 'θ' => 'Θ', - 'ι' => 'Ι', - 'κ' => 'Κ', - 'λ' => 'Λ', - 'μ' => 'Μ', - 'ν' => 'Ν', - 'ξ' => 'Ξ', - 'ο' => 'Ο', - 'π' => 'Π', - 'ρ' => 'Ρ', - 'ς' => 'Σ', - 'σ' => 'Σ', - 'τ' => 'Τ', - 'υ' => 'Υ', - 'φ' => 'Φ', - 'χ' => 'Χ', - 'ψ' => 'Ψ', - 'ω' => 'Ω', - 'ϊ' => 'Ϊ', - 'ϋ' => 'Ϋ', - 'ό' => 'Ό', - 'ύ' => 'Ύ', - 'ώ' => 'Ώ', - 'ϐ' => 'Β', - 'ϑ' => 'Θ', - 'ϕ' => 'Φ', - 'ϖ' => 'Π', - 'ϗ' => 'Ϗ', - 'ϙ' => 'Ϙ', - 'ϛ' => 'Ϛ', - 'ϝ' => 'Ϝ', - 'ϟ' => 'Ϟ', - 'ϡ' => 'Ϡ', - 'ϣ' => 'Ϣ', - 'ϥ' => 'Ϥ', - 'ϧ' => 'Ϧ', - 'ϩ' => 'Ϩ', - 'ϫ' => 'Ϫ', - 'ϭ' => 'Ϭ', - 'ϯ' => 'Ϯ', - 'ϰ' => 'Κ', - 'ϱ' => 'Ρ', - 'ϲ' => 'Ϲ', - 'ϳ' => 'Ϳ', - 'ϵ' => 'Ε', - 'ϸ' => 'Ϸ', - 'ϻ' => 'Ϻ', - 'а' => 'А', - 'б' => 'Б', - 'в' => 'В', - 'г' => 'Г', - 'д' => 'Д', - 'е' => 'Е', - 'ж' => 'Ж', - 'з' => 'З', - 'и' => 'И', - 'й' => 'Й', - 'к' => 'К', - 'л' => 'Л', - 'м' => 'М', - 'н' => 'Н', - 'о' => 'О', - 'п' => 'П', - 'р' => 'Р', - 'с' => 'С', - 'т' => 'Т', - 'у' => 'У', - 'ф' => 'Ф', - 'х' => 'Х', - 'ц' => 'Ц', - 'ч' => 'Ч', - 'ш' => 'Ш', - 'щ' => 'Щ', - 'ъ' => 'Ъ', - 'ы' => 'Ы', - 'ь' => 'Ь', - 'э' => 'Э', - 'ю' => 'Ю', - 'я' => 'Я', - 'ѐ' => 'Ѐ', - 'ё' => 'Ё', - 'ђ' => 'Ђ', - 'ѓ' => 'Ѓ', - 'є' => 'Є', - 'ѕ' => 'Ѕ', - 'і' => 'І', - 'ї' => 'Ї', - 'ј' => 'Ј', - 'љ' => 'Љ', - 'њ' => 'Њ', - 'ћ' => 'Ћ', - 'ќ' => 'Ќ', - 'ѝ' => 'Ѝ', - 'ў' => 'Ў', - 'џ' => 'Џ', - 'ѡ' => 'Ѡ', - 'ѣ' => 'Ѣ', - 'ѥ' => 'Ѥ', - 'ѧ' => 'Ѧ', - 'ѩ' => 'Ѩ', - 'ѫ' => 'Ѫ', - 'ѭ' => 'Ѭ', - 'ѯ' => 'Ѯ', - 'ѱ' => 'Ѱ', - 'ѳ' => 'Ѳ', - 'ѵ' => 'Ѵ', - 'ѷ' => 'Ѷ', - 'ѹ' => 'Ѹ', - 'ѻ' => 'Ѻ', - 'ѽ' => 'Ѽ', - 'ѿ' => 'Ѿ', - 'ҁ' => 'Ҁ', - 'ҋ' => 'Ҋ', - 'ҍ' => 'Ҍ', - 'ҏ' => 'Ҏ', - 'ґ' => 'Ґ', - 'ғ' => 'Ғ', - 'ҕ' => 'Ҕ', - 'җ' => 'Җ', - 'ҙ' => 'Ҙ', - 'қ' => 'Қ', - 'ҝ' => 'Ҝ', - 'ҟ' => 'Ҟ', - 'ҡ' => 'Ҡ', - 'ң' => 'Ң', - 'ҥ' => 'Ҥ', - 'ҧ' => 'Ҧ', - 'ҩ' => 'Ҩ', - 'ҫ' => 'Ҫ', - 'ҭ' => 'Ҭ', - 'ү' => 'Ү', - 'ұ' => 'Ұ', - 'ҳ' => 'Ҳ', - 'ҵ' => 'Ҵ', - 'ҷ' => 'Ҷ', - 'ҹ' => 'Ҹ', - 'һ' => 'Һ', - 'ҽ' => 'Ҽ', - 'ҿ' => 'Ҿ', - 'ӂ' => 'Ӂ', - 'ӄ' => 'Ӄ', - 'ӆ' => 'Ӆ', - 'ӈ' => 'Ӈ', - 'ӊ' => 'Ӊ', - 'ӌ' => 'Ӌ', - 'ӎ' => 'Ӎ', - 'ӏ' => 'Ӏ', - 'ӑ' => 'Ӑ', - 'ӓ' => 'Ӓ', - 'ӕ' => 'Ӕ', - 'ӗ' => 'Ӗ', - 'ә' => 'Ә', - 'ӛ' => 'Ӛ', - 'ӝ' => 'Ӝ', - 'ӟ' => 'Ӟ', - 'ӡ' => 'Ӡ', - 'ӣ' => 'Ӣ', - 'ӥ' => 'Ӥ', - 'ӧ' => 'Ӧ', - 'ө' => 'Ө', - 'ӫ' => 'Ӫ', - 'ӭ' => 'Ӭ', - 'ӯ' => 'Ӯ', - 'ӱ' => 'Ӱ', - 'ӳ' => 'Ӳ', - 'ӵ' => 'Ӵ', - 'ӷ' => 'Ӷ', - 'ӹ' => 'Ӹ', - 'ӻ' => 'Ӻ', - 'ӽ' => 'Ӽ', - 'ӿ' => 'Ӿ', - 'ԁ' => 'Ԁ', - 'ԃ' => 'Ԃ', - 'ԅ' => 'Ԅ', - 'ԇ' => 'Ԇ', - 'ԉ' => 'Ԉ', - 'ԋ' => 'Ԋ', - 'ԍ' => 'Ԍ', - 'ԏ' => 'Ԏ', - 'ԑ' => 'Ԑ', - 'ԓ' => 'Ԓ', - 'ԕ' => 'Ԕ', - 'ԗ' => 'Ԗ', - 'ԙ' => 'Ԙ', - 'ԛ' => 'Ԛ', - 'ԝ' => 'Ԝ', - 'ԟ' => 'Ԟ', - 'ԡ' => 'Ԡ', - 'ԣ' => 'Ԣ', - 'ԥ' => 'Ԥ', - 'ԧ' => 'Ԧ', - 'ԩ' => 'Ԩ', - 'ԫ' => 'Ԫ', - 'ԭ' => 'Ԭ', - 'ԯ' => 'Ԯ', - 'ա' => 'Ա', - 'բ' => 'Բ', - 'գ' => 'Գ', - 'դ' => 'Դ', - 'ե' => 'Ե', - 'զ' => 'Զ', - 'է' => 'Է', - 'ը' => 'Ը', - 'թ' => 'Թ', - 'ժ' => 'Ժ', - 'ի' => 'Ի', - 'լ' => 'Լ', - 'խ' => 'Խ', - 'ծ' => 'Ծ', - 'կ' => 'Կ', - 'հ' => 'Հ', - 'ձ' => 'Ձ', - 'ղ' => 'Ղ', - 'ճ' => 'Ճ', - 'մ' => 'Մ', - 'յ' => 'Յ', - 'ն' => 'Ն', - 'շ' => 'Շ', - 'ո' => 'Ո', - 'չ' => 'Չ', - 'պ' => 'Պ', - 'ջ' => 'Ջ', - 'ռ' => 'Ռ', - 'ս' => 'Ս', - 'վ' => 'Վ', - 'տ' => 'Տ', - 'ր' => 'Ր', - 'ց' => 'Ց', - 'ւ' => 'Ւ', - 'փ' => 'Փ', - 'ք' => 'Ք', - 'օ' => 'Օ', - 'ֆ' => 'Ֆ', - 'ᵹ' => 'Ᵹ', - 'ᵽ' => 'Ᵽ', - 'ḁ' => 'Ḁ', - 'ḃ' => 'Ḃ', - 'ḅ' => 'Ḅ', - 'ḇ' => 'Ḇ', - 'ḉ' => 'Ḉ', - 'ḋ' => 'Ḋ', - 'ḍ' => 'Ḍ', - 'ḏ' => 'Ḏ', - 'ḑ' => 'Ḑ', - 'ḓ' => 'Ḓ', - 'ḕ' => 'Ḕ', - 'ḗ' => 'Ḗ', - 'ḙ' => 'Ḙ', - 'ḛ' => 'Ḛ', - 'ḝ' => 'Ḝ', - 'ḟ' => 'Ḟ', - 'ḡ' => 'Ḡ', - 'ḣ' => 'Ḣ', - 'ḥ' => 'Ḥ', - 'ḧ' => 'Ḧ', - 'ḩ' => 'Ḩ', - 'ḫ' => 'Ḫ', - 'ḭ' => 'Ḭ', - 'ḯ' => 'Ḯ', - 'ḱ' => 'Ḱ', - 'ḳ' => 'Ḳ', - 'ḵ' => 'Ḵ', - 'ḷ' => 'Ḷ', - 'ḹ' => 'Ḹ', - 'ḻ' => 'Ḻ', - 'ḽ' => 'Ḽ', - 'ḿ' => 'Ḿ', - 'ṁ' => 'Ṁ', - 'ṃ' => 'Ṃ', - 'ṅ' => 'Ṅ', - 'ṇ' => 'Ṇ', - 'ṉ' => 'Ṉ', - 'ṋ' => 'Ṋ', - 'ṍ' => 'Ṍ', - 'ṏ' => 'Ṏ', - 'ṑ' => 'Ṑ', - 'ṓ' => 'Ṓ', - 'ṕ' => 'Ṕ', - 'ṗ' => 'Ṗ', - 'ṙ' => 'Ṙ', - 'ṛ' => 'Ṛ', - 'ṝ' => 'Ṝ', - 'ṟ' => 'Ṟ', - 'ṡ' => 'Ṡ', - 'ṣ' => 'Ṣ', - 'ṥ' => 'Ṥ', - 'ṧ' => 'Ṧ', - 'ṩ' => 'Ṩ', - 'ṫ' => 'Ṫ', - 'ṭ' => 'Ṭ', - 'ṯ' => 'Ṯ', - 'ṱ' => 'Ṱ', - 'ṳ' => 'Ṳ', - 'ṵ' => 'Ṵ', - 'ṷ' => 'Ṷ', - 'ṹ' => 'Ṹ', - 'ṻ' => 'Ṻ', - 'ṽ' => 'Ṽ', - 'ṿ' => 'Ṿ', - 'ẁ' => 'Ẁ', - 'ẃ' => 'Ẃ', - 'ẅ' => 'Ẅ', - 'ẇ' => 'Ẇ', - 'ẉ' => 'Ẉ', - 'ẋ' => 'Ẋ', - 'ẍ' => 'Ẍ', - 'ẏ' => 'Ẏ', - 'ẑ' => 'Ẑ', - 'ẓ' => 'Ẓ', - 'ẕ' => 'Ẕ', - 'ẛ' => 'Ṡ', - 'ạ' => 'Ạ', - 'ả' => 'Ả', - 'ấ' => 'Ấ', - 'ầ' => 'Ầ', - 'ẩ' => 'Ẩ', - 'ẫ' => 'Ẫ', - 'ậ' => 'Ậ', - 'ắ' => 'Ắ', - 'ằ' => 'Ằ', - 'ẳ' => 'Ẳ', - 'ẵ' => 'Ẵ', - 'ặ' => 'Ặ', - 'ẹ' => 'Ẹ', - 'ẻ' => 'Ẻ', - 'ẽ' => 'Ẽ', - 'ế' => 'Ế', - 'ề' => 'Ề', - 'ể' => 'Ể', - 'ễ' => 'Ễ', - 'ệ' => 'Ệ', - 'ỉ' => 'Ỉ', - 'ị' => 'Ị', - 'ọ' => 'Ọ', - 'ỏ' => 'Ỏ', - 'ố' => 'Ố', - 'ồ' => 'Ồ', - 'ổ' => 'Ổ', - 'ỗ' => 'Ỗ', - 'ộ' => 'Ộ', - 'ớ' => 'Ớ', - 'ờ' => 'Ờ', - 'ở' => 'Ở', - 'ỡ' => 'Ỡ', - 'ợ' => 'Ợ', - 'ụ' => 'Ụ', - 'ủ' => 'Ủ', - 'ứ' => 'Ứ', - 'ừ' => 'Ừ', - 'ử' => 'Ử', - 'ữ' => 'Ữ', - 'ự' => 'Ự', - 'ỳ' => 'Ỳ', - 'ỵ' => 'Ỵ', - 'ỷ' => 'Ỷ', - 'ỹ' => 'Ỹ', - 'ỻ' => 'Ỻ', - 'ỽ' => 'Ỽ', - 'ỿ' => 'Ỿ', - 'ἀ' => 'Ἀ', - 'ἁ' => 'Ἁ', - 'ἂ' => 'Ἂ', - 'ἃ' => 'Ἃ', - 'ἄ' => 'Ἄ', - 'ἅ' => 'Ἅ', - 'ἆ' => 'Ἆ', - 'ἇ' => 'Ἇ', - 'ἐ' => 'Ἐ', - 'ἑ' => 'Ἑ', - 'ἒ' => 'Ἒ', - 'ἓ' => 'Ἓ', - 'ἔ' => 'Ἔ', - 'ἕ' => 'Ἕ', - 'ἠ' => 'Ἠ', - 'ἡ' => 'Ἡ', - 'ἢ' => 'Ἢ', - 'ἣ' => 'Ἣ', - 'ἤ' => 'Ἤ', - 'ἥ' => 'Ἥ', - 'ἦ' => 'Ἦ', - 'ἧ' => 'Ἧ', - 'ἰ' => 'Ἰ', - 'ἱ' => 'Ἱ', - 'ἲ' => 'Ἲ', - 'ἳ' => 'Ἳ', - 'ἴ' => 'Ἴ', - 'ἵ' => 'Ἵ', - 'ἶ' => 'Ἶ', - 'ἷ' => 'Ἷ', - 'ὀ' => 'Ὀ', - 'ὁ' => 'Ὁ', - 'ὂ' => 'Ὂ', - 'ὃ' => 'Ὃ', - 'ὄ' => 'Ὄ', - 'ὅ' => 'Ὅ', - 'ὑ' => 'Ὑ', - 'ὓ' => 'Ὓ', - 'ὕ' => 'Ὕ', - 'ὗ' => 'Ὗ', - 'ὠ' => 'Ὠ', - 'ὡ' => 'Ὡ', - 'ὢ' => 'Ὢ', - 'ὣ' => 'Ὣ', - 'ὤ' => 'Ὤ', - 'ὥ' => 'Ὥ', - 'ὦ' => 'Ὦ', - 'ὧ' => 'Ὧ', - 'ὰ' => 'Ὰ', - 'ά' => 'Ά', - 'ὲ' => 'Ὲ', - 'έ' => 'Έ', - 'ὴ' => 'Ὴ', - 'ή' => 'Ή', - 'ὶ' => 'Ὶ', - 'ί' => 'Ί', - 'ὸ' => 'Ὸ', - 'ό' => 'Ό', - 'ὺ' => 'Ὺ', - 'ύ' => 'Ύ', - 'ὼ' => 'Ὼ', - 'ώ' => 'Ώ', - 'ᾀ' => 'ᾈ', - 'ᾁ' => 'ᾉ', - 'ᾂ' => 'ᾊ', - 'ᾃ' => 'ᾋ', - 'ᾄ' => 'ᾌ', - 'ᾅ' => 'ᾍ', - 'ᾆ' => 'ᾎ', - 'ᾇ' => 'ᾏ', - 'ᾐ' => 'ᾘ', - 'ᾑ' => 'ᾙ', - 'ᾒ' => 'ᾚ', - 'ᾓ' => 'ᾛ', - 'ᾔ' => 'ᾜ', - 'ᾕ' => 'ᾝ', - 'ᾖ' => 'ᾞ', - 'ᾗ' => 'ᾟ', - 'ᾠ' => 'ᾨ', - 'ᾡ' => 'ᾩ', - 'ᾢ' => 'ᾪ', - 'ᾣ' => 'ᾫ', - 'ᾤ' => 'ᾬ', - 'ᾥ' => 'ᾭ', - 'ᾦ' => 'ᾮ', - 'ᾧ' => 'ᾯ', - 'ᾰ' => 'Ᾰ', - 'ᾱ' => 'Ᾱ', - 'ᾳ' => 'ᾼ', - 'ι' => 'Ι', - 'ῃ' => 'ῌ', - 'ῐ' => 'Ῐ', - 'ῑ' => 'Ῑ', - 'ῠ' => 'Ῠ', - 'ῡ' => 'Ῡ', - 'ῥ' => 'Ῥ', - 'ῳ' => 'ῼ', - 'ⅎ' => 'Ⅎ', - 'ⅰ' => 'Ⅰ', - 'ⅱ' => 'Ⅱ', - 'ⅲ' => 'Ⅲ', - 'ⅳ' => 'Ⅳ', - 'ⅴ' => 'Ⅴ', - 'ⅵ' => 'Ⅵ', - 'ⅶ' => 'Ⅶ', - 'ⅷ' => 'Ⅷ', - 'ⅸ' => 'Ⅸ', - 'ⅹ' => 'Ⅹ', - 'ⅺ' => 'Ⅺ', - 'ⅻ' => 'Ⅻ', - 'ⅼ' => 'Ⅼ', - 'ⅽ' => 'Ⅽ', - 'ⅾ' => 'Ⅾ', - 'ⅿ' => 'Ⅿ', - 'ↄ' => 'Ↄ', - 'ⓐ' => 'Ⓐ', - 'ⓑ' => 'Ⓑ', - 'ⓒ' => 'Ⓒ', - 'ⓓ' => 'Ⓓ', - 'ⓔ' => 'Ⓔ', - 'ⓕ' => 'Ⓕ', - 'ⓖ' => 'Ⓖ', - 'ⓗ' => 'Ⓗ', - 'ⓘ' => 'Ⓘ', - 'ⓙ' => 'Ⓙ', - 'ⓚ' => 'Ⓚ', - 'ⓛ' => 'Ⓛ', - 'ⓜ' => 'Ⓜ', - 'ⓝ' => 'Ⓝ', - 'ⓞ' => 'Ⓞ', - 'ⓟ' => 'Ⓟ', - 'ⓠ' => 'Ⓠ', - 'ⓡ' => 'Ⓡ', - 'ⓢ' => 'Ⓢ', - 'ⓣ' => 'Ⓣ', - 'ⓤ' => 'Ⓤ', - 'ⓥ' => 'Ⓥ', - 'ⓦ' => 'Ⓦ', - 'ⓧ' => 'Ⓧ', - 'ⓨ' => 'Ⓨ', - 'ⓩ' => 'Ⓩ', - 'ⰰ' => 'Ⰰ', - 'ⰱ' => 'Ⰱ', - 'ⰲ' => 'Ⰲ', - 'ⰳ' => 'Ⰳ', - 'ⰴ' => 'Ⰴ', - 'ⰵ' => 'Ⰵ', - 'ⰶ' => 'Ⰶ', - 'ⰷ' => 'Ⰷ', - 'ⰸ' => 'Ⰸ', - 'ⰹ' => 'Ⰹ', - 'ⰺ' => 'Ⰺ', - 'ⰻ' => 'Ⰻ', - 'ⰼ' => 'Ⰼ', - 'ⰽ' => 'Ⰽ', - 'ⰾ' => 'Ⰾ', - 'ⰿ' => 'Ⰿ', - 'ⱀ' => 'Ⱀ', - 'ⱁ' => 'Ⱁ', - 'ⱂ' => 'Ⱂ', - 'ⱃ' => 'Ⱃ', - 'ⱄ' => 'Ⱄ', - 'ⱅ' => 'Ⱅ', - 'ⱆ' => 'Ⱆ', - 'ⱇ' => 'Ⱇ', - 'ⱈ' => 'Ⱈ', - 'ⱉ' => 'Ⱉ', - 'ⱊ' => 'Ⱊ', - 'ⱋ' => 'Ⱋ', - 'ⱌ' => 'Ⱌ', - 'ⱍ' => 'Ⱍ', - 'ⱎ' => 'Ⱎ', - 'ⱏ' => 'Ⱏ', - 'ⱐ' => 'Ⱐ', - 'ⱑ' => 'Ⱑ', - 'ⱒ' => 'Ⱒ', - 'ⱓ' => 'Ⱓ', - 'ⱔ' => 'Ⱔ', - 'ⱕ' => 'Ⱕ', - 'ⱖ' => 'Ⱖ', - 'ⱗ' => 'Ⱗ', - 'ⱘ' => 'Ⱘ', - 'ⱙ' => 'Ⱙ', - 'ⱚ' => 'Ⱚ', - 'ⱛ' => 'Ⱛ', - 'ⱜ' => 'Ⱜ', - 'ⱝ' => 'Ⱝ', - 'ⱞ' => 'Ⱞ', - 'ⱡ' => 'Ⱡ', - 'ⱥ' => 'Ⱥ', - 'ⱦ' => 'Ⱦ', - 'ⱨ' => 'Ⱨ', - 'ⱪ' => 'Ⱪ', - 'ⱬ' => 'Ⱬ', - 'ⱳ' => 'Ⱳ', - 'ⱶ' => 'Ⱶ', - 'ⲁ' => 'Ⲁ', - 'ⲃ' => 'Ⲃ', - 'ⲅ' => 'Ⲅ', - 'ⲇ' => 'Ⲇ', - 'ⲉ' => 'Ⲉ', - 'ⲋ' => 'Ⲋ', - 'ⲍ' => 'Ⲍ', - 'ⲏ' => 'Ⲏ', - 'ⲑ' => 'Ⲑ', - 'ⲓ' => 'Ⲓ', - 'ⲕ' => 'Ⲕ', - 'ⲗ' => 'Ⲗ', - 'ⲙ' => 'Ⲙ', - 'ⲛ' => 'Ⲛ', - 'ⲝ' => 'Ⲝ', - 'ⲟ' => 'Ⲟ', - 'ⲡ' => 'Ⲡ', - 'ⲣ' => 'Ⲣ', - 'ⲥ' => 'Ⲥ', - 'ⲧ' => 'Ⲧ', - 'ⲩ' => 'Ⲩ', - 'ⲫ' => 'Ⲫ', - 'ⲭ' => 'Ⲭ', - 'ⲯ' => 'Ⲯ', - 'ⲱ' => 'Ⲱ', - 'ⲳ' => 'Ⲳ', - 'ⲵ' => 'Ⲵ', - 'ⲷ' => 'Ⲷ', - 'ⲹ' => 'Ⲹ', - 'ⲻ' => 'Ⲻ', - 'ⲽ' => 'Ⲽ', - 'ⲿ' => 'Ⲿ', - 'ⳁ' => 'Ⳁ', - 'ⳃ' => 'Ⳃ', - 'ⳅ' => 'Ⳅ', - 'ⳇ' => 'Ⳇ', - 'ⳉ' => 'Ⳉ', - 'ⳋ' => 'Ⳋ', - 'ⳍ' => 'Ⳍ', - 'ⳏ' => 'Ⳏ', - 'ⳑ' => 'Ⳑ', - 'ⳓ' => 'Ⳓ', - 'ⳕ' => 'Ⳕ', - 'ⳗ' => 'Ⳗ', - 'ⳙ' => 'Ⳙ', - 'ⳛ' => 'Ⳛ', - 'ⳝ' => 'Ⳝ', - 'ⳟ' => 'Ⳟ', - 'ⳡ' => 'Ⳡ', - 'ⳣ' => 'Ⳣ', - 'ⳬ' => 'Ⳬ', - 'ⳮ' => 'Ⳮ', - 'ⳳ' => 'Ⳳ', - 'ⴀ' => 'Ⴀ', - 'ⴁ' => 'Ⴁ', - 'ⴂ' => 'Ⴂ', - 'ⴃ' => 'Ⴃ', - 'ⴄ' => 'Ⴄ', - 'ⴅ' => 'Ⴅ', - 'ⴆ' => 'Ⴆ', - 'ⴇ' => 'Ⴇ', - 'ⴈ' => 'Ⴈ', - 'ⴉ' => 'Ⴉ', - 'ⴊ' => 'Ⴊ', - 'ⴋ' => 'Ⴋ', - 'ⴌ' => 'Ⴌ', - 'ⴍ' => 'Ⴍ', - 'ⴎ' => 'Ⴎ', - 'ⴏ' => 'Ⴏ', - 'ⴐ' => 'Ⴐ', - 'ⴑ' => 'Ⴑ', - 'ⴒ' => 'Ⴒ', - 'ⴓ' => 'Ⴓ', - 'ⴔ' => 'Ⴔ', - 'ⴕ' => 'Ⴕ', - 'ⴖ' => 'Ⴖ', - 'ⴗ' => 'Ⴗ', - 'ⴘ' => 'Ⴘ', - 'ⴙ' => 'Ⴙ', - 'ⴚ' => 'Ⴚ', - 'ⴛ' => 'Ⴛ', - 'ⴜ' => 'Ⴜ', - 'ⴝ' => 'Ⴝ', - 'ⴞ' => 'Ⴞ', - 'ⴟ' => 'Ⴟ', - 'ⴠ' => 'Ⴠ', - 'ⴡ' => 'Ⴡ', - 'ⴢ' => 'Ⴢ', - 'ⴣ' => 'Ⴣ', - 'ⴤ' => 'Ⴤ', - 'ⴥ' => 'Ⴥ', - 'ⴧ' => 'Ⴧ', - 'ⴭ' => 'Ⴭ', - 'ꙁ' => 'Ꙁ', - 'ꙃ' => 'Ꙃ', - 'ꙅ' => 'Ꙅ', - 'ꙇ' => 'Ꙇ', - 'ꙉ' => 'Ꙉ', - 'ꙋ' => 'Ꙋ', - 'ꙍ' => 'Ꙍ', - 'ꙏ' => 'Ꙏ', - 'ꙑ' => 'Ꙑ', - 'ꙓ' => 'Ꙓ', - 'ꙕ' => 'Ꙕ', - 'ꙗ' => 'Ꙗ', - 'ꙙ' => 'Ꙙ', - 'ꙛ' => 'Ꙛ', - 'ꙝ' => 'Ꙝ', - 'ꙟ' => 'Ꙟ', - 'ꙡ' => 'Ꙡ', - 'ꙣ' => 'Ꙣ', - 'ꙥ' => 'Ꙥ', - 'ꙧ' => 'Ꙧ', - 'ꙩ' => 'Ꙩ', - 'ꙫ' => 'Ꙫ', - 'ꙭ' => 'Ꙭ', - 'ꚁ' => 'Ꚁ', - 'ꚃ' => 'Ꚃ', - 'ꚅ' => 'Ꚅ', - 'ꚇ' => 'Ꚇ', - 'ꚉ' => 'Ꚉ', - 'ꚋ' => 'Ꚋ', - 'ꚍ' => 'Ꚍ', - 'ꚏ' => 'Ꚏ', - 'ꚑ' => 'Ꚑ', - 'ꚓ' => 'Ꚓ', - 'ꚕ' => 'Ꚕ', - 'ꚗ' => 'Ꚗ', - 'ꚙ' => 'Ꚙ', - 'ꚛ' => 'Ꚛ', - 'ꜣ' => 'Ꜣ', - 'ꜥ' => 'Ꜥ', - 'ꜧ' => 'Ꜧ', - 'ꜩ' => 'Ꜩ', - 'ꜫ' => 'Ꜫ', - 'ꜭ' => 'Ꜭ', - 'ꜯ' => 'Ꜯ', - 'ꜳ' => 'Ꜳ', - 'ꜵ' => 'Ꜵ', - 'ꜷ' => 'Ꜷ', - 'ꜹ' => 'Ꜹ', - 'ꜻ' => 'Ꜻ', - 'ꜽ' => 'Ꜽ', - 'ꜿ' => 'Ꜿ', - 'ꝁ' => 'Ꝁ', - 'ꝃ' => 'Ꝃ', - 'ꝅ' => 'Ꝅ', - 'ꝇ' => 'Ꝇ', - 'ꝉ' => 'Ꝉ', - 'ꝋ' => 'Ꝋ', - 'ꝍ' => 'Ꝍ', - 'ꝏ' => 'Ꝏ', - 'ꝑ' => 'Ꝑ', - 'ꝓ' => 'Ꝓ', - 'ꝕ' => 'Ꝕ', - 'ꝗ' => 'Ꝗ', - 'ꝙ' => 'Ꝙ', - 'ꝛ' => 'Ꝛ', - 'ꝝ' => 'Ꝝ', - 'ꝟ' => 'Ꝟ', - 'ꝡ' => 'Ꝡ', - 'ꝣ' => 'Ꝣ', - 'ꝥ' => 'Ꝥ', - 'ꝧ' => 'Ꝧ', - 'ꝩ' => 'Ꝩ', - 'ꝫ' => 'Ꝫ', - 'ꝭ' => 'Ꝭ', - 'ꝯ' => 'Ꝯ', - 'ꝺ' => 'Ꝺ', - 'ꝼ' => 'Ꝼ', - 'ꝿ' => 'Ꝿ', - 'ꞁ' => 'Ꞁ', - 'ꞃ' => 'Ꞃ', - 'ꞅ' => 'Ꞅ', - 'ꞇ' => 'Ꞇ', - 'ꞌ' => 'Ꞌ', - 'ꞑ' => 'Ꞑ', - 'ꞓ' => 'Ꞓ', - 'ꞗ' => 'Ꞗ', - 'ꞙ' => 'Ꞙ', - 'ꞛ' => 'Ꞛ', - 'ꞝ' => 'Ꞝ', - 'ꞟ' => 'Ꞟ', - 'ꞡ' => 'Ꞡ', - 'ꞣ' => 'Ꞣ', - 'ꞥ' => 'Ꞥ', - 'ꞧ' => 'Ꞧ', - 'ꞩ' => 'Ꞩ', - 'a' => 'A', - 'b' => 'B', - 'c' => 'C', - 'd' => 'D', - 'e' => 'E', - 'f' => 'F', - 'g' => 'G', - 'h' => 'H', - 'i' => 'I', - 'j' => 'J', - 'k' => 'K', - 'l' => 'L', - 'm' => 'M', - 'n' => 'N', - 'o' => 'O', - 'p' => 'P', - 'q' => 'Q', - 'r' => 'R', - 's' => 'S', - 't' => 'T', - 'u' => 'U', - 'v' => 'V', - 'w' => 'W', - 'x' => 'X', - 'y' => 'Y', - 'z' => 'Z', - '𐐨' => '𐐀', - '𐐩' => '𐐁', - '𐐪' => '𐐂', - '𐐫' => '𐐃', - '𐐬' => '𐐄', - '𐐭' => '𐐅', - '𐐮' => '𐐆', - '𐐯' => '𐐇', - '𐐰' => '𐐈', - '𐐱' => '𐐉', - '𐐲' => '𐐊', - '𐐳' => '𐐋', - '𐐴' => '𐐌', - '𐐵' => '𐐍', - '𐐶' => '𐐎', - '𐐷' => '𐐏', - '𐐸' => '𐐐', - '𐐹' => '𐐑', - '𐐺' => '𐐒', - '𐐻' => '𐐓', - '𐐼' => '𐐔', - '𐐽' => '𐐕', - '𐐾' => '𐐖', - '𐐿' => '𐐗', - '𐑀' => '𐐘', - '𐑁' => '𐐙', - '𐑂' => '𐐚', - '𐑃' => '𐐛', - '𐑄' => '𐐜', - '𐑅' => '𐐝', - '𐑆' => '𐐞', - '𐑇' => '𐐟', - '𐑈' => '𐐠', - '𐑉' => '𐐡', - '𐑊' => '𐐢', - '𐑋' => '𐐣', - '𐑌' => '𐐤', - '𐑍' => '𐐥', - '𐑎' => '𐐦', - '𐑏' => '𐐧', - '𑣀' => '𑢠', - '𑣁' => '𑢡', - '𑣂' => '𑢢', - '𑣃' => '𑢣', - '𑣄' => '𑢤', - '𑣅' => '𑢥', - '𑣆' => '𑢦', - '𑣇' => '𑢧', - '𑣈' => '𑢨', - '𑣉' => '𑢩', - '𑣊' => '𑢪', - '𑣋' => '𑢫', - '𑣌' => '𑢬', - '𑣍' => '𑢭', - '𑣎' => '𑢮', - '𑣏' => '𑢯', - '𑣐' => '𑢰', - '𑣑' => '𑢱', - '𑣒' => '𑢲', - '𑣓' => '𑢳', - '𑣔' => '𑢴', - '𑣕' => '𑢵', - '𑣖' => '𑢶', - '𑣗' => '𑢷', - '𑣘' => '𑢸', - '𑣙' => '𑢹', - '𑣚' => '𑢺', - '𑣛' => '𑢻', - '𑣜' => '𑢼', - '𑣝' => '𑢽', - '𑣞' => '𑢾', - '𑣟' => '𑢿', -); - -$result =& $data; -unset($data); - -return $result; - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -use Symfony\Polyfill\Mbstring as p; - -if (!function_exists('mb_strlen')) { - define('MB_CASE_UPPER', 0); - define('MB_CASE_LOWER', 1); - define('MB_CASE_TITLE', 2); - - function mb_convert_encoding($s, $to, $from = null) { return p\Mbstring::mb_convert_encoding($s, $to, $from); } - function mb_decode_mimeheader($s) { return p\Mbstring::mb_decode_mimeheader($s); } - function mb_encode_mimeheader($s, $charset = null, $transferEnc = null, $lf = null, $indent = null) { return p\Mbstring::mb_encode_mimeheader($s, $charset, $transferEnc, $lf, $indent); } - function mb_convert_case($s, $mode, $enc = null) { return p\Mbstring::mb_convert_case($s, $mode, $enc); } - function mb_internal_encoding($enc = null) { return p\Mbstring::mb_internal_encoding($enc); } - function mb_language($lang = null) { return p\Mbstring::mb_language($lang); } - function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } - function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } - function mb_check_encoding($var = null, $encoding = null) { return p\Mbstring::mb_check_encoding($var, $encoding); } - function mb_detect_encoding($str, $encodingList = null, $strict = false) { return p\Mbstring::mb_detect_encoding($str, $encodingList, $strict); } - function mb_detect_order($encodingList = null) { return p\Mbstring::mb_detect_order($encodingList); } - function mb_parse_str($s, &$result = array()) { parse_str($s, $result); } - function mb_strlen($s, $enc = null) { return p\Mbstring::mb_strlen($s, $enc); } - function mb_strpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strpos($s, $needle, $offset, $enc); } - function mb_strtolower($s, $enc = null) { return p\Mbstring::mb_strtolower($s, $enc); } - function mb_strtoupper($s, $enc = null) { return p\Mbstring::mb_strtoupper($s, $enc); } - function mb_substitute_character($char = null) { return p\Mbstring::mb_substitute_character($char); } - function mb_substr($s, $start, $length = 2147483647, $enc = null) { return p\Mbstring::mb_substr($s, $start, $length, $enc); } - function mb_stripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_stripos($s, $needle, $offset, $enc); } - function mb_stristr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_stristr($s, $needle, $part, $enc); } - function mb_strrchr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrchr($s, $needle, $part, $enc); } - function mb_strrichr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrichr($s, $needle, $part, $enc); } - function mb_strripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strripos($s, $needle, $offset, $enc); } - function mb_strrpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strrpos($s, $needle, $offset, $enc); } - function mb_strstr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strstr($s, $needle, $part, $enc); } - function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } - function mb_http_output($enc = null) { return p\Mbstring::mb_http_output($enc); } - function mb_strwidth($s, $enc = null) { return p\Mbstring::mb_strwidth($s, $enc); } - function mb_substr_count($haystack, $needle, $enc = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $enc); } - function mb_output_handler($contents, $status) { return p\Mbstring::mb_output_handler($contents, $status); } - function mb_http_input($type = '') { return p\Mbstring::mb_http_input($type); } - function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) { return p\Mbstring::mb_convert_variables($toEncoding, $fromEncoding, $a, $b, $c, $d, $e, $f); } -} -if (!function_exists('mb_chr')) { - function mb_ord($s, $enc = null) { return p\Mbstring::mb_ord($s, $enc); } - function mb_chr($code, $enc = null) { return p\Mbstring::mb_chr($code, $enc); } - function mb_scrub($s, $enc = null) { $enc = null === $enc ? mb_internal_encoding() : $enc; return mb_convert_encoding($s, $enc, $enc); } -} - - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * * Neither the name of Arne Blankerts nor the names of contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * - * @category PHP - * @package TheSeer\fDOM - * @author Arne Blankerts - * @copyright Arne Blankerts , All rights reserved. - * @license http://www.opensource.org/licenses/bsd-license.php BSD License - * @link http://github.com/theseer/fdomdocument - * - */ - -namespace TheSeer\fDOM { - - /** - * Class XPathQuery - * - * @package TheSeer\fDOM - */ - class XPathQuery { - - /** - * @var string - */ - private $query; - - /** - * Key-value Map for bound values - * - * @var array - */ - private $values = array(); - - /** - * @param string $query - */ - public function __construct($query) { - $this->setQuery($query); - } - - /** - * Set Query. - * - * @param string $query - */ - private function setQuery($query) { - $this->query = $query; - $res = preg_match_all('/(:(\w*))/', $query, $matches); - if ($res > 0) { - $this->values = array_fill_keys($matches[2], ''); - } - } - - /** - * Returns keys. - * - * @return array - */ - public function getKeys() { - return array_keys($this->values); - } - - /** - * Bind value to key. - * - * @param string $key - * @param string $value - * - * @throws XPathQueryException - */ - public function bind($key, $value) { - if (!array_key_exists($key, $this->values)) { - throw new XPathQueryException("'$key' not found in query'", XPathQueryException::KeyNotFound ); - } - $this->values[$key] = $value; - } - - /** - * Generate query. - * - * @param \DOMNode $ctx - * @param array $values - * - * @return string - */ - public function generate(\DOMNode $ctx, array $values = NULL) { - return $this->buildQuery($this->getXPathObjectFor($ctx), $values); - } - - /** - * Evaluate Query. - * - * @param \DOMNode $ctx - * @param array $values - * @param bool $registerNodeNS - * - * @throws fDOMException - * - * @return mixed - */ - public function evaluate(\DOMNode $ctx, array $values = NULL, $registerNodeNS = TRUE) { - $xp = $this->getXPathObjectFor($ctx); - return $xp->evaluate($this->buildQuery($xp, $values), $ctx, $registerNodeNS); - } - - /** - * Execute Query. - * - * @param \DOMNode $ctx - * @param array $values - * @param bool $registerNodeNS - * - * @throws fDOMException - * - * @return mixed - */ - public function query(\DOMNode $ctx, array $values = NULL, $registerNodeNS = TRUE) { - $xp = $this->getXPathObjectFor($ctx); - return $xp->evaluate($this->buildQuery($xp, $values), $ctx, $registerNodeNS); - } - - /** - * Execute Query and return first result. - * - * @param \DOMNode $ctx - * @param array $values - * @param bool $registerNodeNS - * - * @return \DOMNode - */ - public function queryOne(\DOMNode $ctx, array $values = NULL, $registerNodeNS = TRUE) { - $xp = $this->getXPathObjectFor($ctx); - return $xp->queryOne($this->buildQuery($xp, $values), $ctx, $registerNodeNS); - } - - /** - * Return xPath for node - * - * @param \DOMNode $ctx - * - * @throws fDOMException - * - * @return fDOMXPath - */ - private function getXPathObjectFor(\DOMNode $ctx) { - $dom = $ctx instanceof \DOMDocument ? $ctx : $ctx->ownerDocument; - if ($dom instanceOf fDOMDocument) { - return $dom->getDOMXPath(); - } - return new fDOMXPath($dom); - } - - /** - * Build query using values. - * - * @param fDOMXPath $xp - * @param array $values - * - * @throws XPathQueryException - * - * @return string - */ - private function buildQuery(fDOMXPath $xp, array $values = NULL) { - $backup = $this->values; - if (is_array($values) && count($values) > 0) { - foreach($values as $k => $v) { - $this->bind($k, $v); - } - } - $query = $xp->prepare($this->query, $this->values); - $this->values = $backup; - return $query; - } - } - -} - - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * * Neither the name of Arne Blankerts nor the names of contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * - * @category PHP - * @package TheSeer\fDOM - * @author Arne Blankerts - * @copyright Arne Blankerts , All rights reserved. - * @license http://www.opensource.org/licenses/bsd-license.php BSD License - * @link http://github.com/theseer/fdomdocument - * - */ - -namespace TheSeer\fDOM { - - class XPathQueryException extends \Exception { - const KeyNotFound = 1; - } - -} - '/css/DollarEqualRule.php', - 'theseer\\fdom\\css\\notrule' => '/css/NotRule.php', - 'theseer\\fdom\\css\\nthchildrule' => '/css/NthChildRule.php', - 'theseer\\fdom\\css\\regexrule' => '/css/RegexRule.php', - 'theseer\\fdom\\css\\ruleinterface' => '/css/RuleInterface.php', - 'theseer\\fdom\\css\\translator' => '/css/Translator.php', - 'theseer\\fdom\\fdomdocument' => '/fDOMDocument.php', - 'theseer\\fdom\\fdomdocumentfragment' => '/fDOMDocumentFragment.php', - 'theseer\\fdom\\fdomelement' => '/fDOMElement.php', - 'theseer\\fdom\\fdomexception' => '/fDOMException.php', - 'theseer\\fdom\\fdomnode' => '/fDOMNode.php', - 'theseer\\fdom\\fdomxpath' => '/fDOMXPath.php', - 'theseer\\fdom\\xpathquery' => '/XPathQuery.php', - 'theseer\\fdom\\xpathqueryexception' => '/XPathQueryException.php' - ); - } - $cn = strtolower($class); - if (isset($classes[$cn])) { - require __DIR__ . $classes[$cn]; - } - } -); -// @codeCoverageIgnoreEnd -translator = $translator; - } - - /** - * @param $selector - * - * @return string - */ - public function apply($selector) { - return preg_replace_callback( - '/([a-zA-Z0-9\_\-\*]+):not\(([^\)]*)\)/', - array($this, 'callback'), - $selector - ); - } - - /** - * @param array $matches - * - * @return string - */ - private function callback(array $matches) { - $subresult = preg_replace( - '/^[^\[]+\[([^\]]*)\].*$/', - '$1', - $this->translator->translate($matches[2]) - ); - return $matches[1] . '[not(' . $subresult . ')]'; - } - - } - -} -=0]/self::' . $matches[1]; - } - case 'odd': { - return $matches[1] . '[(count(preceding-sibling::*) + 1) mod 2=1]'; - } - default: { - $b = !isset($matches[2]) || empty($matches[2]) ? '0' : $matches[2]; - $b = preg_replace('/^([0-9]*)n.*?([0-9]*)$/', '$1+$2', $b); - $b = explode('+', $b); - if (!isset($b[1])) { - $b[1] = '0'; - } - return '*[(position()-' . $b[1] . ') mod ' . $b[0] . '=0 and position()>=' . $b[1] . ']/self::' . $matches[1]; - } - } - } - - } - -} -regex = $regex; - $this->replacement = $replacement; - } - - /** - * @param $selector - * - * @return string - */ - public function apply($selector) { - return preg_replace($this->regex, $this->replacement, $selector); - } - - } - -} - - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * * Neither the name of Arne Blankerts nor the names of contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * - * @category PHP - * @package TheSeer\fDOM - * @author Arne Blankerts - * @copyright Arne Blankerts , All rights reserved. - * @license http://www.opensource.org/licenses/bsd-license.php BSD License - * @link http://github.com/theseer/fdomdocument - * - */ - -namespace TheSeer\fDOM\CSS { - - /** - * Class Translator - * - * The regular expressions used in this class are heavily inspired by and mostly adopted from - * the css2xpath.js code by Andrea Giammarchi (http://code.google.com/p/css2xpath/). - * The JavaScript version (css2xpath.js) is licensed under the MIT License - * - */ - class Translator { - - /** - * @var array - */ - private $rules; - - /** - * @param string $selector A CSS Selector string - * - * @return string - */ - public function translate($selector) { - foreach($this->getRules() as $rule) { - /** @var RuleInterface $rule */ - $selector = $rule->apply($selector); - } - return '//' . $selector; - } - - /** - * @return array - */ - private function getRules() { - if ($this->rules != NULL) { - return $this->rules; - } - - $this->rules = array( - - // prefix|name - new RegexRule('/([a-zA-Z0-9\_\-\*]+)\|([a-zA-Z0-9\_\-\*]+)/', '$1:$2'), - - // add @ for attribs - new RegexRule("/\[([^\]~\$\*\^\|\!]+)(=[^\]]+)?\]/", '[@$1$2]'), - - // multiple queries - new RegexRule("/\s*,\s*/", '|'), - - // , + ~ > - new RegexRule("/\s*(\+|~|>)\s*/", '$1'), - - //* ~ + > - new RegexRule("/([a-zA-Z0-9\_\-\*])~([a-zA-Z0-9\_\-\*])/", '$1/following-sibling::$2'), - new RegexRule("/([a-zA-Z0-9\_\-\*])\+([a-zA-Z0-9\_\-\*])/", '$1/following-sibling::*[1]/self::$2'), - new RegexRule("/([a-zA-Z0-9\_\-\*])>([a-zA-Z0-9\_\-\*])/", '$1/$2'), - - // all unescaped stuff escaped - new RegexRule("/\[([^=]+)=([^'|'][^\]]*)\]/", '[$1="$2"]'), - - // all descendant or self to // - new RegexRule("/(^|[^a-zA-Z0-9\_\-\*])(#|\.)([a-zA-Z0-9\_\-]+)/", '$1*$2$3'), - new RegexRule("/([\>\+\|\~\,\s])([a-zA-Z\*]+)/", '$1//$2'), - new RegexRule("/\s+\/\//", '//'), - - // :first-child - new RegexRule("/([a-zA-Z0-9\_\-\*]+):first-child/", '*[1]/self::$1'), - - // :last-child - new RegexRule("/([a-zA-Z0-9\_\-\*]+):last-child/", '$1[not(following-sibling::*)]'), - - // :only-child - new RegexRule("/([a-zA-Z0-9\_\-\*]+):only-child/", '*[last()=1]/self::$1'), - - // :empty - new RegexRule("/([a-zA-Z0-9\_\-\*]+):empty/", '$1[not(*) and not(normalize-space())]'), - - // :not - new NotRule($this), - - // :nth-child - new NthChildRule(), - - // :contains(selectors) - new RegexRule('/:contains\(([^\)]*)\)/', '[contains(string(.),"$1")]'), - - // |= attrib - new RegexRule("/\[([a-zA-Z0-9\_\-]+)\|=([^\]]+)\]/", '[@$1=$2 or starts-with(@$1,concat($2,"-"))]'), - - // *= attrib - new RegexRule("/\[([a-zA-Z0-9\_\-]+)\*=([^\]]+)\]/", '[contains(@$1,$2)]'), - - // ~= attrib - new RegexRule("/\[([a-zA-Z0-9\_\-]+)~=([^\]]+)\]/", '[contains(concat(" ",normalize-space(@$1)," "),concat(" ",$2," "))]'), - - // ^= attrib - new RegexRule("/\[([a-zA-Z0-9\_\-]+)\^=([^\]]+)\]/", '[starts-with(@$1,$2)]'), - - // $= attrib - new DollarEqualRule(), - - // != attrib - new RegexRule("/\[([a-zA-Z0-9\_\-]+)\!=([^\]]+)\]/", '[not(@$1) or @$1!=$2]'), - - // ids and classes - new RegexRule("/#([a-zA-Z0-9\_\-]+)/", '[@id="$1"]'), - new RegexRule("/\.([a-zA-Z0-9\_\-]+)/", '[contains(concat(" ",normalize-space(@class)," ")," $1 ")]'), - - // normalize multiple filters - new RegexRule("/\]\[([^\]]+)/", ' and ($1)') - - ); - return $this->rules; - } - } - -} - - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * * Neither the name of Arne Blankerts nor the names of contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * - * @category PHP - * @package TheSeer\fDOM - * @author Arne Blankerts - * @copyright Arne Blankerts , All rights reserved. - * @license http://www.opensource.org/licenses/bsd-license.php BSD License - * @link http://github.com/theseer/fdomdocument - * - */ - -namespace TheSeer\fDOM { - use TheSeer\fDOM\CSS\Translator; - - /** - * fDOMDocument extension to PHP's DOMDocument. - * This class adds various convenience methods to simplify APIs - * It is set to final since further extending it would even more - * break the Object structure after use of registerNodeClass. - * - * @category PHP - * @package TheSeer\fDOM - * @author Arne Blankerts - * @access public - * @property fDOMDocument $ownerDocument - * - */ - class fDOMDocument extends \DOMDocument { - - /** - * XPath Object instance - * - * @var fDOMXPath - */ - private $xp = NULL; - - /** - * List of registered prefixes and their namespace uri - * @var array - */ - private $prefixes = array(); - - /** - * Extended DOMDocument constructor - * - * @param string $version XML Version, should be 1.0 - * @param string $encoding Encoding, defaults to utf-8 - * @param array $streamOptions optional stream options array - * - * @return fDOMDocument - */ - public function __construct($version = '1.0', $encoding = 'utf-8', $streamOptions = NULL) { - if (!is_null($streamOptions)) { - $this->setStreamContext($streamOptions); - } - - libxml_use_internal_errors(TRUE); - $rc = parent::__construct($version, $encoding); - - $this->registerNodeClasses(); - - return $rc; - } - - /** - * Reset XPath object so the clone gets a new instance when needed - */ - public function __clone() { - $this->registerNodeClasses(); - $this->xp = new fDOMXPath($this); - foreach($this->prefixes as $prefix => $uri) { - $this->xp->registerNamespace($prefix, $uri); - } - } - - /** - * @return string - */ - public function __toString() { - return $this->C14N(); - } - - /** - * Set Stream context options - * - * @param array $options Stream context options - * - * @return boolean true on success, false on failure - */ - public function setStreamContext(array $options) { - if (!count($options)) { - return FALSE; - } - $context = stream_context_create($options); - libxml_set_streams_context($context); - return TRUE; - } - - /** - * Wrapper to DOMDocument load with exception handling - * Returns true on success to satisfy the compatibilty of the original DOM Api - * - * @param string $fname File to load - * @param int|null $options LibXML Flags to pass - * - * @throws fDOMException - * - * @return bool|mixed - */ - public function load($fname, $options = LIBXML_NONET) { - if ($fname === '') { - throw new fDOMException('empty filename is not allowed', fDOMException::ParseError); - } - $this->xp = NULL; - $tmp = parent :: load($fname, $options); - if (!$tmp || libxml_get_last_error()) { - throw new fDOMException("loading file '$fname' failed.", fDOMException::LoadError); - } - $this->registerNodeClasses(); - return TRUE; - } - - /** - * Wrapper to DOMDocument loadXML with exception handling - * Returns true on success to satisfy the compatibilty of the original DOM Api - * - * @param string $source XML source code - * @param integer $options LibXML option flags - * - * @throws fDOMException - * - * @return boolean - */ - public function loadXML($source, $options = LIBXML_NONET) { - if ($source === '') { - throw new fDOMException('empty string not allowed', fDOMException::ParseError); - } - $this->xp = NULL; - $tmp = parent :: loadXML($source, $options); - if (!$tmp || libxml_get_last_error()) { - throw new fDOMException('parsing string failed', fDOMException::ParseError); - } - $this->registerNodeClasses(); - return TRUE; - } - - /** - * Wrapper to DOMDocument loadHTMLFile with exception handling. - * Returns true on success to satisfy the compatibilty of the original DOM Api - * - * @param string $fname html file to load - * @param integer $options Options bitmask (@see DOMDocument::loadHTMLFile) - * - * @throws fDOMException - * - * @return boolean - */ - public function loadHTMLFile($fname, $options = NULL) { - if ($fname === '') { - throw new fDOMException('empty filename is not allowed', fDOMException::ParseError); - } - $this->xp = NULL; - if (version_compare(PHP_VERSION, '5.4.0', '<')) { - if ($options !== NULL) { - throw new fDOMException('Passing options requires PHP 5.4.0+', fDOMException::LoadError); - } - $tmp = parent :: loadHTMLFile($fname); - } else { - $tmp = parent :: loadHTMLFile($fname, $options); - } - if (!$tmp || libxml_get_last_error()) { - throw new fDOMException("loading html file '$fname' failed", fDOMException::LoadError); - } - $this->registerNodeClasses(); - return TRUE; - } - - /** - * Wrapper to DOMDocument loadHTML with exception handling - * Returns true on success to satisfy the compatibilty of the original DOM Api - * - * @param string $source html source code - * @param integer $options Options bitmask (@see DOMDocument::loadHTML) - * - * @throws fDOMException - * - * @return boolean - */ - public function loadHTML($source, $options = NULL) { - if ($source === '') { - throw new fDOMException('empty string not allowed', fDOMException::ParseError); - } - $this->xp = NULL; - if (version_compare(PHP_VERSION, '5.4.0', '<')) { - if ($options !== NULL) { - throw new fDOMException('Passing options requires PHP 5.4.0+', fDOMException::LoadError); - } - $tmp = parent :: loadHTML($source); - } else { - $tmp = parent :: loadHTML($source, $options); - } - if (!$tmp || libxml_get_last_error()) { - throw new fDOMException('parsing html string failed', fDOMException::ParseError); - } - $this->registerNodeClasses(); - return TRUE; - } - - /** - * Wrapper to DOMDocument::save with exception handling - * - * @param string $filename filename to save to - * @param integer $options Options bitmask (@see DOMDocument::save) - * - * @throws fDOMException - * - * @return integer bytes saved - */ - public function save($filename, $options = NULL) { - $tmp = parent::save($filename, $options); - if (!$tmp) { - throw new fDOMException("Saving XML to file '$filename' failed", fDOMException::SaveError); - } - return $tmp; - } - - /** - * Wrapper to DOMDocument::saveHTML with exception handling - * - * @param \DOMNode|null $node Context DOMNode (optional) - * - * @throws fDOMException - * - * @return string html content - */ - public function saveHTML(\DOMNode $node = NULL) { - if (version_compare(PHP_VERSION, '5.3.6', '<') && $node !== NULL) { - throw new fDOMException('Passing a context node requires PHP 5.3.6+', fDOMException::SaveError); - } - $tmp = parent::saveHTML($node); - if (!$tmp) { - throw new fDOMException('Serializing to HTML failed', fDOMException::SaveError); - } - return $tmp; - } - - /** - * Wrapper to DOMDocument::saveHTMLfile with exception handling - * - * @param string $filename filename to save to - * @param integer $options Options bitmask (@see DOMDocument::saveHTMLFile) - * - * @throws fDOMException - * - * @return integer bytes saved - */ - public function saveHTMLFile($filename, $options = NULL) { - $tmp = parent::saveHTMLFile($filename, $options); - if (!$tmp) { - throw new fDOMException("Saving HTML to file '$filename' failed", fDOMException::SaveError); - } - return $tmp; - } - - /** - * Wrapper to DOMDocument::saveXML with exception handling - * - * @param \DOMNode $node node to start serializing at - * @param integer $options options flags as bitmask - * - * @throws fDOMException - * - * @return string serialized XML - */ - public function saveXML(\DOMNode $node = NULL, $options = NULL) { - try { - $tmp = parent::saveXML($node, $options); - if (!$tmp) { - throw new fDOMException('Serializing to XML failed', fDOMException::SaveError); - } - return $tmp; - } catch (\Exception $e) { - if (!$e instanceof fDOMException) { - throw new fDOMException($e->getMessage(), fDOMException::SaveError, $e); - } - throw $e; - } - } - - /** - * get Instance of DOMXPath Object for current DOM - * - * @throws fDOMException - * - * @return fDOMXPath - */ - public function getDOMXPath() { - if (is_null($this->xp)) { - $this->xp = new fDOMXPath($this); - } - if (!$this->xp) { - throw new fDOMException('creating DOMXPath object failed.', fDOMException::NoDOMXPath); - } - return $this->xp; - } - - /** - * Convert a given DOMNodeList into a DOMFragment - * - * @param \DOMNodeList $list The Nodelist to process - * @param boolean $move Signale if nodes are to be moved into fragment or not - * - * @return fDOMDocumentFragment - */ - public function nodeList2Fragment(\DOMNodeList $list, $move=FALSE) { - $frag = $this->createDocumentFragment(); - /** @var fDOMNode $node */ - foreach($list as $node) { - $frag->appendChild($move ? $node : $node->cloneNode(TRUE)); - } - return $this->ensureIntance($frag); - } - - /** - * Perform an xpath query - * - * @param String $q query string containing xpath - * @param \DOMNode|null $ctx (optional) Context DOMNode - * @param boolean $registerNodeNS Register flag pass through - * - * @return \DOMNodeList - */ - public function query($q, \DOMNode $ctx = NULL, $registerNodeNS = TRUE) { - if (is_null($this->xp)) { - $this->getDOMXPath(); - } - return $this->xp->evaluate($q, $ctx, $registerNodeNS); - } - - /** - * Perform an xpath query and return only the 1st match - * - * @param String $q query string containing xpath - * @param \DOMNode $ctx (optional) Context DOMNode - * @param boolean $registerNodeNS Register flag pass thru - * - * @return fDOMNode - */ - public function queryOne($q, \DOMNode $ctx = NULL, $registerNodeNS = TRUE) { - if (is_null($this->xp)) { - $this->getDOMXPath(); - } - return $this->xp->queryOne($q, $ctx, $registerNodeNS); - } - - /** - * Forwarder to fDOMXPath's prepare method allowing for easy and secure - * placeholder replacement comparable to sql's prepared statements - * . - * @param string $xpath String containing xpath with :placeholder markup - * @param array $valueMap array containing keys (:placeholder) and value pairs to be quoted - * - * @return string - */ - public function prepareQuery($xpath, array $valueMap) { - if (is_null($this->xp)) { - $this->getDOMXPath(); - } - return $this->xp->prepare($xpath, $valueMap); - } - - /** - * Use a CSS Level 3 Selector string to query select nodes - * - * @param string $selector A CSS Level 3 Selector string - * @param \DOMNode $ctx - * @param bool $registerNodeNS - * - * @return \DOMNodeList - */ - public function select($selector, \DOMNode $ctx = NULL, $registerNodeNS = TRUE) { - $translator = new Translator(); - $xpath = $translator->translate($selector); - if ($ctx !== NULL) { - $xpath = '.' . $xpath; - } - return $this->query($xpath, $ctx, $registerNodeNS); - } - - /** - * Forward to DOMXPath->registerNamespace() - * - * @param string $prefix The prefix to use - * @param string $uri The uri to assign to this prefix - * - * @throws fDOMException - * - * @return void - */ - public function registerNamespace($prefix, $uri) { - if (is_null($this->xp)) { - $this->getDOMXPath(); - } - if (!$this->xp->registerNamespace($prefix, $uri)) { - throw new fDOMException("Registering namespace '$uri' with prefix '$prefix' failed.", fDOMException::RegistrationFailed); - } - $this->prefixes[$prefix] = $uri; - } - - /** - * Forward to DOMXPath->registerPHPFunctions() - * - * @param mixed $restrict array of function names or string with functionname to restrict callabilty to - * - * @throws fDOMException - * - * @return void - */ - public function registerPHPFunctions($restrict = NULL) { - if (is_null($this->xp)) { - $this->getDOMXPath(); - } - $this->xp->registerPHPFunctions($restrict); - if (libxml_get_last_error()) { - throw new fDOMException("Registering php functions failed.", fDOMException::RegistrationFailed); - } - } - - /** - * Create a new element in namespace defined by given prefix - * - * @param string $prefix Namespace prefix for node to create - * @param string $name Name of not element to create - * @param string $content Optional content to be set - * @param bool $asTextNode Create content as textNode rather then setting nodeValue - * - * @throws fDOMException - * - * @return fDOMElement Reference to created fDOMElement - */ - public function createElementPrefix($prefix, $name, $content = NULL, $asTextNode = FALSE) { - if (!isset($this->prefixes[$prefix])) { - throw new fDOMException("'$prefix' not bound", fDOMException::UnboundPrefix); - } - return $this->createElementNS($this->prefixes[$prefix], $prefix.':'.$name, $content, $asTextNode); - } - - /** - * Create a new fDOMElement and return it, optionally set content - * - * @param string $name Name of node to create - * @param null $content Content to set (optional) - * @param bool $asTextnode Create content as textNode rather then setting nodeValue - * - * @throws fDOMException - * - * @return fDOMElement Reference to created fDOMElement - */ - public function createElement($name, $content = NULL, $asTextnode = FALSE) { - try { - $node = parent::createElement($name); - if (!$node) { - throw new fDOMException("Creating element with name '$name' failed", fDOMException::NameInvalid); - } - if ($content !== NULL) { - if ($asTextnode) { - $node->appendChild($this->createTextnode($content)); - } else { - $node->nodeValue = $content; - } - if (libxml_get_errors()) { - throw new fDOMException("Setting content value failed", fDOMException::SetFailedError); - } - } - return $this->ensureIntance($node); - } catch (\DOMException $e) { - throw new fDOMException("Creating elemnt with name '$name' failed", 0, $e); - } - - } - - /** - * Create a new fDOMElement within given namespace and return it - * - * @param string $namespace Namespace URI for node to create - * @param string $name Name of node to create - * @param string $content Content to set (optional) - * @param bool $asTextNode Create content as textNode rather then setting nodeValue - * - * @throws fDOMException - * - * @return fDOMElement - */ - public function createElementNS($namespace, $name, $content = NULL, $asTextNode = FALSE) { - $node = parent::createElementNS($namespace, $name); - if (!$node) { - throw new fDOMException("Creating element with name '$name' failed", fDOMException::NameInvalid); - } - if ($content !== NULL) { - if ($asTextNode) { - $node->appendChild($this->createTextnode($content)); - } else { - $node->nodeValue = $content; - } - if (libxml_get_errors()) { - throw new fDOMException("Setting content value failed", fDOMException::SetFailedError); - } - } - return $this->ensureIntance($node); - } - - /** - * @return fDOMDocumentFragment - */ - public function createDocumentFragment() { - return $this->ensureIntance(parent::createDocumentFragment()); - } - - /** - * Check if the given node is in the same document - * - * @param \DOMNode $node Node to compare with - * - * @return boolean true on match, false if they differ - * - */ - public function inSameDocument(\DOMNode $node) { - if ($node instanceof \DOMDocument) { - return $this->isSameNode($node); - } - return $this->isSameNode($node->ownerDocument); - } - - /** - * Create a new element and append it as documentElement - * - * @param string $name Name of not element to create - * @param string $content Optional content to be set - * @param bool $asTextNode - * - * @return fDOMElement Reference to created fDOMElement - */ - public function appendElement($name, $content = NULL, $asTextNode = FALSE) { - return $this->appendChild( - $this->createElement($name, $content, $asTextNode) - ); - } - - /** - * Create a new element in given namespace and append it as documentElement - * - * @param string $ns Namespace of node to create - * @param string $name Name of not element to create - * @param string $content Optional content to be set - * @param bool $asTextNode - * - * @return fDOMElement Reference to created fDOMElement - */ - public function appendElementNS($ns, $name, $content = NULL, $asTextNode = FALSE) { - return $this->appendChild( - $this->createElementNS($ns, $name, $content, $asTextNode) - ); - } - - /** - * This is a workaround for hhvm's broken registerNodeClass handling - * (https://github.com/facebook/hhvm/issues/1848) - * - * @param \DOMNode $node - * - * @return \DOMNode - */ - private function ensureIntance(\DOMNode $node) { - if ($node instanceof fDOMNode || $node instanceof fDOMElement || $node instanceof fDOMDocumentFragment) { - return $node; - } - return $this->importNode($node, TRUE); - } - - /** - * Register replacements - * - * Called from constructor and, as a workaround for (https://github.com/facebook/hhvm/issues/5412), - * after load(), loadXML(), loadHTML() and loadHTMLFile() - */ - private function registerNodeClasses() { - $this->registerNodeClass('DOMDocument', get_called_class()); - $this->registerNodeClass('DOMNode', 'TheSeer\fDOM\fDOMNode'); - $this->registerNodeClass('DOMElement', 'TheSeer\fDOM\fDOMElement'); - $this->registerNodeClass('DOMDocumentFragment', 'TheSeer\fDOM\fDOMDocumentFragment'); - } - - } // fDOMDocument - -} - - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * * Neither the name of Arne Blankerts nor the names of contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * - * @category PHP - * @package TheSeer\fDOM - * @author Arne Blankerts - * @copyright Arne Blankerts , All rights reserved. - * @license http://www.opensource.org/licenses/bsd-license.php BSD License - * @link http://github.com/theseer/fdomdocument - * - */ - -namespace TheSeer\fDOM { - - /** - * fDOMDocumentFragment - * - * @category PHP - * @package TheSeer\fDOM - * @author Arne Blankerts - * @access public - * @property fDOMDocument $ownerDocument - * - */ - class fDOMDocumentFragment extends \DOMDocumentFragment { - - /** - * @return string - */ - public function __toString() { - return $this->ownerDocument->saveXML($this); - } - - /** - * Wrapper to standard method with exception support - * - * @param string $str Data string to parse and append - * - * @throws fDOMException - * - * @return bool true on success - */ - public function appendXML($str) { - if (!parent::appendXML($str)) { - throw new fDOMException('Appending xml string failed', fDOMException::ParseError); - } - return true; - } - - /** - * Create a new element and append it - * - * @param string $name Name of not element to create - * @param string $content Optional content to be set - * - * @return fDOMElement Reference to created fDOMElement - */ - public function appendElement($name, $content = null) { - $node = $this->ownerDocument->createElement($name, $content); - $this->appendChild($node); - return $node; - } - - /** - * Create a new element in given namespace and append it - * - * @param string $ns Namespace of node to create - * @param string $name Name of not element to create - * @param string $content Optional content to be set - * - * @return fDOMElement Reference to created fDOMElement - */ - public function appendElementNS($ns, $name, $content = null) { - $node = $this->ownerDocument->createElementNS($ns, $name, $content); - $this->appendChild($node); - return $node; - } - - /** - * Create a new element in given namespace and append it - * - * @param string $prefix Namespace prefix for node to create - * @param string $name Name of not element to create - * @param string $content Optional content to be set - * @param bool $asTextnode Create content as textNode rather then setting nodeValue - * - * @return fDOMElement Reference to created fDOMElement - */ - public function appendElementPrefix($prefix, $name, $content = null, $asTextnode = FALSE) { - $node = $this->ownerDocument->createElementPrefix($prefix, $name, $content, $asTextnode); - $this->appendChild($node); - return $node; - } - - /** - * Create a new text node and append it - * - * @param string $content Text content to be added - * - * @return \DOMText - */ - public function appendTextNode($content) { - $text = $this->ownerDocument->createTextNode($content); - $this->appendChild($text); - return $text; - } - - /** - * Check if the given node is in the same document - * - * @param \DOMNode $node Node to compare with - * - * @return boolean true on match, false if they differ - * - */ - public function inSameDocument(\DOMNode $node) { - return $this->ownerDocument->inSameDocument($node); - } - - - /** - * Forward to fDomDocument->query() - * - * @param string $q XPath to use - * @param \DOMNode $ctx \DOMNode to overwrite context - * @param boolean $registerNodeNS Register flag pass thru - * - * @return \DomNodeList - */ - public function query($q, \DOMNode $ctx = null, $registerNodeNS = true) { - return $this->ownerDocument->query($q, $ctx ? $ctx : $this, $registerNodeNS); - } - - /** - * Forward to fDomDocument->queryOne() - * - * @param string $q XPath to use - * @param \DOMNode $ctx (optional) \DOMNode to overwrite context - * @param boolean $registerNodeNS Register flag pass thru - * - * @return mixed - */ - public function queryOne($q, \DOMNode $ctx = null, $registerNodeNS = true) { - return $this->ownerDocument->queryOne($q, $ctx ? $ctx : $this, $registerNodeNS); - } - - /** - * Forward to fDomDocument->select() - * - * @param string $selector A CSS Level 3 Selector string - * @param \DOMNode $ctx - * @param bool $registerNodeNS - * - * @return \DOMNodeList - */ - public function select($selector, \DOMNode $ctx = null, $registerNodeNS = true) { - return $this->ownerDocument->select($selector, $ctx ? $ctx : $this, $registerNodeNS); - } - - } // fDOMDocumentFragment - -} - - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * * Neither the name of Arne Blankerts nor the names of contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * - * @category PHP - * @package TheSeer\fDOM - * @author Arne Blankerts - * @copyright Arne Blankerts , All rights reserved. - * @license http://www.opensource.org/licenses/bsd-license.php BSD License - * @link http://github.com/theseer/fdomdocument - * - */ - -namespace TheSeer\fDOM { - - /** - * fDomElement - * - * @category PHP - * @package TheSeer\fDOM - * @author Arne Blankerts - * @access public - * @property fDOMDocument $ownerDocument - * - */ - class fDOMElement extends \DOMElement { - - /** - * @return string - */ - public function __toString() { - return $this->C14N(); - } - - /** - * Forward to fDomDocument->query() - * - * @param string $q XPath to use - * @param \DOMNode $ctx \DOMNode to overwrite context - * @param boolean $registerNodeNS Register flag pass thru - * - * @return \DomNodeList - */ - public function query($q, \DOMNode $ctx = null, $registerNodeNS = true) { - return $this->ownerDocument->query($q, $ctx ? $ctx : $this, $registerNodeNS); - } - - /** - * Forward to fDomDocument->queryOne() - * - * @param string $q XPath to use - * @param \DOMNode $ctx (optional) \DOMNode to overwrite context - * @param boolean $registerNodeNS Register flag pass thru - * - * @return mixed - */ - public function queryOne($q, \DOMNode $ctx = null, $registerNodeNS = true) { - return $this->ownerDocument->queryOne($q, $ctx ? $ctx : $this, $registerNodeNS); - } - - /** - * Forward to fDomDocument->select() - * - * @param string $selector A CSS Level 3 Selector string - * @param \DOMNode $ctx - * @param bool $registerNodeNS - * - * @return \DOMNodeList - */ - - public function select($selector, \DOMNode $ctx = null, $registerNodeNS = true) { - return $this->ownerDocument->select($selector, $ctx, $registerNodeNS); - } - - /** - * Parse and append XML String to node - * - * @param String $str string to process - * - * @return fDomDocumentFragment Reference to the created Fragment - */ - public function appendXML($str) { - $frag = $this->ownerDocument->createDocumentFragment(); - $frag->appendXML($str); - $this->appendChild($frag); - return $frag; - } - - /** - * Create a new element and append it - * - * @param string $name Name of not element to create - * @param string $content Optional content to be set - * @param bool $asTextnode Create content as textNode rather then setting nodeValue - * - * @return fDOMElement Reference to created fDOMElement - */ - public function appendElement($name, $content = null, $asTextnode = FALSE) { - $node = $this->ownerDocument->createElement($name, $content, $asTextnode); - $this->appendChild($node); - return $node; - } - - /** - * Create a new element in given namespace and append it - * - * @param string $ns Namespace of node to create - * @param string $name Name of not element to create - * @param string $content Optional content to be set - * @param bool $asTextnode Create content as textNode rather then setting nodeValue - * - * @return fDOMElement Reference to created fDOMElement - */ - public function appendElementNS($ns, $name, $content = null, $asTextnode = FALSE) { - $node = $this->ownerDocument->createElementNS($ns, $name, $content, $asTextnode); - $this->appendChild($node); - return $node; - } - - /** - * Create a new element in given namespace and append it - * - * @param string $prefix Namespace prefix for node to create - * @param string $name Name of not element to create - * @param string $content Optional content to be set - * @param bool $asTextnode Create content as textNode rather then setting nodeValue - * - * @return fDOMElement Reference to created fDOMElement - */ - public function appendElementPrefix($prefix, $name, $content = null, $asTextnode = FALSE) { - $node = $this->ownerDocument->createElementPrefix($prefix, $name, $content, $asTextnode); - $this->appendChild($node); - return $node; - } - - /** - * Create a new text node and append it - * - * @param string $content Text content to be added - * - * @return \DOMText - */ - public function appendTextNode($content) { - $text = $this->ownerDocument->createTextNode($content); - $this->appendChild($text); - return $text; - } - - /** - * Create a new fDOMElement - * - * @see fDOMDocument::createElement - * - * @param string $name - * @param string $content - * @param bool $asTextnode - * - * @return fDOMElement - */ - public function createElement($name, $content = NULL, $asTextnode = FALSE) { - return $this->ownerDocument->createElement($name, $content, $asTextnode); - } - - /** - * Create a new fDOMElement in namespace defined by prefix - * - * @see fDOMDocument::createElementPrefix - * - * @param string $prefix - * @param string $name - * @param string $content - * @param bool $asTextNode - * - * @return fDOMElement - */ - public function createElementPrefix($prefix, $name, $content = NULL, $asTextNode = FALSE) { - return $this->ownerDocument->createElementPrefix($prefix, $name, $content, $asTextNode); - } - - /** - * Create a new fDOMElement within given namespace and return it - * - * @param string $namespace Namespace URI for node to create - * @param string $name Name of node to create - * @param null $content Content to set (optional) - * @param bool $asTextNode Create content as textNode rather then setting nodeValue - * - * @throws fDOMException - * - * @return fDOMElement - */ - public function createElementNS($namespace, $name, $content = NULL, $asTextNode = FALSE) { - return $this->ownerDocument->createElementNS($namespace, $name, $content, $asTextNode); - } - - /** - * Wrapper to DomElement->getAttribute with default value option - * - * Note: A set but emptry attribute does NOT trigger use of the default - * - * @param string $attr Attribute to access - * @param string $default Default value to use if the attribute is not set - * - * @return string - */ - public function getAttribute($attr, $default='') { - return $this->hasAttribute($attr) ? parent::getAttribute($attr) : $default; - } - - /** - * Wrapper to DomElement->getAttributeNS with default value option - * - * Note: A set but empty attribute does NOT trigger use of the default - * - * @param string $ns Namespace of attribute - * @param string $attr Attribute to access - * @param string $default Default value to use if the attribute is not set - * - * @return string - */ - public function getAttributeNS($ns, $attr, $default='') { - return $this->hasAttributeNS($ns, $attr) ? parent::getAttributeNS($ns, $attr) : $default; - } - - /** - * Wrapper to DOMElement::setAttribute with additional entities support - * - * @param string $attr Attribute name to set - * @param string $value Value to set attribute to - * @param bool $keepEntities Flag to signale if entities should be kept - * - * @throws fDOMException - * - * @return \DOMAttr - * - * @see DOMElement::setAttribute() - */ - public function setAttribute($attr, $value, $keepEntities=false) { - if ($keepEntities === true) { - $attrNode = $this->ownerDocument->createAttribute($attr); - if (!$attrNode) { - throw new fDOMException("Setting attribute '$attr' failed.", fDOMException::SetFailedError); - } - $attrNode->value = $value; - $this->appendChild($attrNode); - return $attrNode; - } - return parent::setAttribute($attr, $value); - } - - /** - * Wrapper to namespace aware DOMElement::setAttributeNS with additional entities support - * - * @param string $ns namespace attribute should be in - * @param string $attr Attribute name to set - * @param string $value Value to set attribute to - * @param bool $keepEntities Flag to signale if entities should be kept - * - * @throws fDOMException - * - * @return \DOMAttr|null - * @see DOMElement::setAttribute() - */ - public function setAttributeNS($ns, $attr, $value, $keepEntities=false) { - if ($keepEntities === true) { - $attrNode = $this->ownerDocument->createAttributeNS($ns, $attr); - if (!$attrNode) { - throw new fDOMException("Setting attribute '$attr' failed.", fDOMException::SetFailedError); - } - $attrNode->value = $value; - $this->appendChild($attrNode); - return $attrNode; - } - return parent::setAttributeNS($ns, $attr, $value); - } - - /** - * Helper to add multiple attributes to an element - * - * @param array $attr Attributes to add as key-value pair - * @param bool $keepEntities Flag wether to keep entities - * - * @return array List with references to created DOMAttr - */ - public function setAttributes(array $attr, $keepEntities=false) { - $attList = array(); - foreach($attr as $name => $value) { - $attList[] = $this->setAttribute($name, $value, $keepEntities); - } - return $attList; - } - - /** - * Helper to add multiple attributes with the given namespace and prefix - * - * @param string $ns Namespace of attribute - * @param string $prefix Namespace prefix for attribute to create - * @param array $attr Attributes to add - * @param bool $keepEntities Flag wether to keep entities - * - * @return void - */ - public function setAttributesNS($ns, $prefix, array $attr, $keepEntities=false) { - foreach($attr as $name => $value) { - $this->setAttributeNS($ns, $prefix.':'.$name, $value, $keepEntities); - } - } - - /** - * Helper method to get children by name - * - * @param string $tagName tagname to search for - * - * @return \DOMNodeList - */ - public function getChildrenByTagName($tagName) { - return $this->query("*[local-name()='$tagName']"); - } - - /** - * Helper method to get children by name and namespace - * - * @param string $ns namespace nodes have to be in - * @param string $tagName tagname to search for - * - * @return \DOMNodeList - */ - public function getChildrenByTagNameNS($ns, $tagName) { - return $this->query("*[local-name()='$tagName' and namespace-uri()='$ns']"); - } - - /** - * Check if the given node is in the same document - * - * @param \DomNode $node Node to compare with - * - * @return boolean true on match, false if they differ - * - */ - public function inSameDocument(\DomNode $node) { - return $this->ownerDocument->inSameDocument($node); - } - - /** - * Wrapper to DomDocument::saveXML() with current node as context - * - * @return string - */ - public function saveXML() { - return $this->ownerDocument->saveXML($this); - } - - /** - * Wrapper to DomDocument::saveHTML() with current node as context - * - * @return string - */ - public function saveHTML() { - return $this->ownerDocument->saveHTML($this); - } - - } // fDOMElement - -} - - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * * Neither the name of Arne Blankerts nor the names of contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * @package fDOM - * @author Arne Blankerts - * @copyright Arne Blankerts , All rights reserved. - * @license BSD License - */ - -namespace TheSeer\fDOM { - - /** - * fDOMException - * - * @category PHP - * @package TheSeer\fDOM - * @author Arne Blankerts - * @access public - * - */ - class fDOMException extends \Exception { - - const LoadError = 1; - const ParseError = 2; - const SaveError = 3; - const QueryError = 4; - const RegistrationFailed = 5; - const NoDOMXPath = 6; - const UnboundPrefix = 7; - const SetFailedError = 8; - const NameInvalid = 9; - - /** - * List of libxml error objects - * - * @var array - */ - private $errorList; - - - /** - * Full Error message - * - * @var string - */ - private $fullMessage = null; - - - /** - * Short Error Message - * - * @var string - */ - private $shortMessage = null; - - - private static $fullMesageMode = true; - - /** - * Constructor - * - * @param string $message Exception message - * @param integer $code Exception code - * @param \Exception $chain optional chained exception - * - */ - public function __construct($message, $code = 0, \Exception $chain = NULL) { - $this->shortMessage = $message; - $this->errorList = libxml_get_errors(); - libxml_clear_errors(); - parent :: __construct($message, $code, $chain); - - $this->fullMessage = $message."\n\n"; - - foreach ($this->errorList as $error) { - // hack, skip "attempt to load external pseudo error" - if ($error->code=='1543') { - continue; - } - - if (empty($error->file)) { - $this->fullMessage .= '[XML-STRING] '; - } else { - $this->fullMessage .= '['.$error->file.'] '; - } - - $this->fullMessage .= '[Line: '.$error->line.' - Column: '.$error->column.'] '; - - switch ($error->level) { - case LIBXML_ERR_WARNING: - $this->fullMessage .= "Warning $error->code: "; - break; - case LIBXML_ERR_ERROR: - $this->fullMessage .= "Error $error->code: "; - break; - case LIBXML_ERR_FATAL: - $this->fullMessage .= "Fatal Error $error->code: "; - break; - } - - $this->fullMessage .= str_replace("\n", '', $error->message)."\n"; - - if (self::$fullMesageMode) { - $this->message = $this->fullMessage; - } - - } - } - - /** - * Accessor to fullMessage - * - * @return string - */ - public function getFullMessage() { - return $this->fullMessage; - } - - /** - * Access to shortMessage - * - * @return string - */ - public function getShortMessage() { - return $this->shortMessage; - } - - /** - * Accessor to errorList objets - * - * @return array - */ - public function getErrorList() { - return $this->errorList; - } - - /** - * Toggle wehter getMessage() should return full or only exception message - * - * @param boolean $full Flag to enable or disable full message output - * - * @return void - */ - public function toggleFullMessage($full = true) { - $this->message = $full ? $this->fullMessage : $this->shortMessage; - } - - /** - * Magic method for string context - * - * @return string - */ - public function __toString() { - return $this->fullMessage; - } - - } // fDOMException - -} - - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * * Neither the name of Arne Blankerts nor the names of contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * - * @category PHP - * @package TheSeer\fDOM - * @author Arne Blankerts - * @copyright Arne Blankerts , All rights reserved. - * @license http://www.opensource.org/licenses/bsd-license.php BSD License - * @link http://github.com/theseer/fdomdocument - * - */ - -namespace TheSeer\fDOM { - - /** - * fDomNode - * - * @category PHP - * @package TheSeer\fDOM - * @author Arne Blankerts - * @access public - * @property fDOMDocument $ownerDocument - * - */ - class fDOMNode extends \DOMNode { - - /** - * @return string - */ - public function __toString() { - return $this->C14N(); - } - - /** - * Create a new fDOMElement - * - * @see fDOMDocument::createElement - * - * @param string $name - * @param string $content - * @param bool $asTextnode - * - * @return fDOMElement - */ - public function createElement($name, $content = NULL, $asTextnode = FALSE) { - return $this->ownerDocument->createElement($name, $content, $asTextnode); - } - - /** - * Create a new fDOMElement in namespace defined by prefix - * - * @see fDOMDocument::createElementPrefix - * - * @param string $prefix - * @param string $name - * @param string $content - * @param bool $asTextNode - * - * @return fDOMElement - */ - public function createElementPrefix($prefix, $name, $content = NULL, $asTextNode = FALSE) { - return $this->ownerDocument->createElementPrefix($prefix, $name, $content, $asTextNode); - } - - /** - * Create a new fDOMElement within given namespace and return it - * - * @param string $namespace Namespace URI for node to create - * @param string $name Name of node to create - * @param null $content Content to set (optional) - * @param bool $asTextNode Create content as textNode rather then setting nodeValue - * - * @throws fDOMException - * - * @return fDOMElement - */ - public function createElementNS($namespace, $name, $content = NULL, $asTextNode = FALSE) { - return $this->ownerDocument->createElementNS($namespace, $name, $content, $asTextNode); - } - - /** - * Forward to fDomDocument->query() - * - * @param string $q XPath to use - * @param \DOMNode $ctx \DOMNode to overwrite context - * @param boolean $registerNodeNS Register flag pass thru - * - * @return \DomNodeList - */ - public function query($q, \DOMNode $ctx = null, $registerNodeNS = true) { - return $this->ownerDocument->query($q, $ctx ? $ctx : $this, $registerNodeNS); - } - - /** - * Forward to fDomDocument->queryOne() - * - * @param string $q XPath to use - * @param \DOMNode $ctx (optional) \DOMNode to overwrite context - * @param boolean $registerNodeNS Register flag pass thru - * - * @return mixed - */ - public function queryOne($q, \DOMNode $ctx = null, $registerNodeNS = true) { - return $this->ownerDocument->queryOne($q, $ctx ? $ctx : $this, $registerNodeNS); - } - - /** - * Forward to fDomDocument->select() - * - * @param string $selector A CSS Level 3 Selector string - * @param \DOMNode $ctx - * @param bool $registerNodeNS - * - * @return \DOMNodeList - */ - - public function select($selector, \DOMNode $ctx = null, $registerNodeNS = true) { - return $this->ownerDocument->select($selector, $ctx, $registerNodeNS); - } - - /** - * Check if the given node is in the same document - * - * @param \DomNode $node Node to compare with - * - * @return boolean true on match, false if they differ - * - */ - public function inSameDocument(\DOMNode $node) { - return $this->ownerDocument->inSameDocument($node); - } - - /** - * Wrapper to DomDocument::saveXML() with current node as context - * - * @return string - */ - public function saveXML() { - return $this->ownerDocument->saveXML($this); - } - - /** - * Wrapper to DomDocument::saveHTML() with current node as context - * - * @return string - */ - public function saveHTML() { - return $this->ownerDocument->saveHTML($this); - } - - - - } // fDOMNode - -} - - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * * Neither the name of Arne Blankerts nor the names of contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * - * @category PHP - * @package TheSeer\fDOM - * @author Arne Blankerts - * @copyright Arne Blankerts , All rights reserved. - * @license http://www.opensource.org/licenses/bsd-license.php BSD License - * @link http://github.com/theseer/fdomdocument - * - */ - -namespace TheSeer\fDOM { - - /** - * fDOMXPath extension to PHP's DOMXPath. - * - * @category PHP - * @package TheSeer\fDOM - * @author Arne Blankerts - * @access public - * - */ - class fDOMXPath extends \DOMXPath { - - /** - * @var \DOMDocument - */ - protected $doc; - - /** - * @param \DOMDocument $doc - */ - public function __construct(\DOMDocument $doc) { - parent::__construct($doc); - $this->doc = $doc; - } - - /** - * @param string $xpath - * @param array $valueMap - * - * @return string - */ - public function prepare($xpath, array $valueMap) { - if (count($valueMap)==0) { - return $xpath; - } - foreach($valueMap as $key => $value) { - $xpath = str_replace(':'.$key, $this->quote($value), $xpath); - } - return $xpath; - } - - /** - * @param string $q - * @param \DOMNode $ctx - * @param bool $registerNodeNS - * - * @throws fDOMException - * - * @return \DOMNodeList - */ - public function query($q, \DOMNode $ctx = null, $registerNodeNS = true) { - libxml_clear_errors(); - if (version_compare(PHP_VERSION, '5.3.3', '<') || strpos(PHP_VERSION, 'hiphop') || strpos(PHP_VERSION, 'hhvm')) { - $rc = parent::query($q, ($ctx instanceof \DOMNode) ? $ctx : $this->doc->documentElement); - } else { - $rc = parent::query($q, ($ctx instanceof \DOMNode) ? $ctx : $this->doc->documentElement, $registerNodeNS); - } - - if (libxml_get_last_error()) { - throw new fDOMException('evaluating xpath expression failed.', fDOMException::QueryError); - } - return $rc; - } - - /** - * @param string $q - * @param \DOMNode $ctx - * @param bool $registerNodeNS - * - * @throws fDOMException - * - * @return mixed - */ - public function evaluate($q, \DOMNode $ctx = null, $registerNodeNS = true) { - libxml_clear_errors(); - if (version_compare(PHP_VERSION, '5.3.3', '<') || strpos(PHP_VERSION, 'hiphop') || strpos(PHP_VERSION, 'hhvm')) { - $rc = parent::evaluate($q, ($ctx instanceof \DOMNode) ? $ctx : $this->doc->documentElement); - } else { - $rc = parent::evaluate($q, ($ctx instanceof \DOMNode) ? $ctx : $this->doc->documentElement, $registerNodeNS); - } - if (libxml_get_last_error()) { - throw new fDOMException('evaluating xpath expression failed.', fDOMException::QueryError); - } - return $rc; - } - - /** - * @param string $q - * @param \DOMNode $ctx - * @param bool $registerNodeNS - * - * @throws fDOMException - * - * @return \DOMNode|mixed - */ - public function queryOne($q, \DOMNode $ctx = null, $registerNodeNS = true) { - $rc = $this->evaluate($q, $ctx, $registerNodeNS); - if ($rc instanceof \DOMNodelist) { - return $rc->item(0); - } - return $rc; - } - - /** - * @param string $str - * - * @return string - */ - public function quote($str) { - if (strpos($str, '"') === false) { - return '"'.$str.'"'; - } - $parts = explode('"', $str); - return 'concat("' . join('",\'"\',"', $parts).'")'; - } - } -} -phploc/phploc: 4.0.1 -psr/log: 1.0.2 -sebastian/finder-facade: 1.2.1 -sebastian/version: 2.0.1 -symfony/console: v3.3.13 -symfony/debug: v3.3.13 -symfony/finder: v3.3.13 -symfony/polyfill-mbstring: v1.6.0 -theseer/fdomdocument: 1.6.6 -�D'm:��rHY���S� � -l ��%�ȥ����Љ��u�B��!�0w�f��q �G�GBMB \ No newline at end of file diff --git a/tools/phpstan b/tools/phpstan new file mode 120000 index 00000000000..5e15af476e4 --- /dev/null +++ b/tools/phpstan @@ -0,0 +1 @@ +.phpstan/vendor/bin/phpstan \ No newline at end of file diff --git a/tools/psalm b/tools/psalm deleted file mode 100755 index 81abe7e181c..00000000000 Binary files a/tools/psalm and /dev/null differ diff --git a/tools/roave-backward-compatibility-check b/tools/roave-backward-compatibility-check deleted file mode 100755 index f6fc5ec02f7..00000000000 Binary files a/tools/roave-backward-compatibility-check and /dev/null differ