diff --git a/.gitattributes b/.gitattributes index aece42002b..66460898b5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,6 +7,5 @@ /.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.pre-commit-config.yaml export-ignore /TESTING.md export-ignore /TROUBLESHOOTING.md export-ignore diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ff838cd55d..1de997ffa7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,12 +4,16 @@ updates: directory: "/" schedule: interval: "daily" + commit-message: + prefix: ⬆ versioning-strategy: "increase" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" + commit-message: + prefix: ⬆ labels: - "cleanup-no-release-required" - "dependencies" @@ -19,4 +23,6 @@ updates: directory: "/" schedule: interval: "daily" + commit-message: + prefix: ⬆ versioning-strategy: "increase" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb6ae594a4..8c4b3522e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,9 +20,10 @@ jobs: - '8.1' - '8.2' - '8.3' + - '8.4' future-release: [false] include: - - php-version: '8.4' + - php-version: '8.5' future-release: true fail-fast: false name: PHP ${{ matrix.php-version }} @@ -40,11 +41,7 @@ jobs: persist-credentials: false - name: Test - run: bash tests/ci.sh + run: bash tests/run.sh env: CI_PHP_VERSION: ${{ matrix.php-version }} CI_PHP_FUTURE_RELEASE: ${{ matrix.future-release }} - - - name: Static analysis - run: 'vendor/bin/psalm --config="tests/psalm.xml"' - if: ${{ !matrix.future-release }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 048143a3d0..cf77ee245f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -30,7 +30,7 @@ jobs: - name: flake8 changed files if: steps.changed-files.outputs.any_changed == 'true' run: | - flake8 ${{ steps.changed-files.outputs.all_changed_files }} + flake8 --config="tests/setup.cfg" ${{ steps.changed-files.outputs.all_changed_files }} - name: isort changed files if: steps.changed-files.outputs.any_changed == 'true' diff --git a/.github/workflows/pre-commit-auto-update.yml b/.github/workflows/pre-commit-auto-update.yml index 74f8c58b55..726704f501 100644 --- a/.github/workflows/pre-commit-auto-update.yml +++ b/.github/workflows/pre-commit-auto-update.yml @@ -20,7 +20,7 @@ jobs: set -x python -m pip install --upgrade pip pip install pre-commit - pre-commit autoupdate + pre-commit autoupdate --config="tests/.pre-commit-config.yaml" - name: Gather changes id: gather-changes @@ -34,7 +34,7 @@ jobs: - name: Create or update pull request id: cpr - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v7 with: commit-message: Update pre-commit dependencies labels: cleanup-no-release-required, dependencies, github_actions diff --git a/CHANGELOG.md b/CHANGELOG.md index f3d148c534..7674b80db2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ backwards-incompatible changes that will affect existing usage. +## 11.0.1 - 2025-01-13 + +- Increase Psalm strictness ([#909](https://github.com/php-curl-class/php-curl-class/pull/909)) +- Increase PHPStan strictness ([#908](https://github.com/php-curl-class/php-curl-class/pull/908)) + ## 11.0.0 - 2024-08-22 - Drop support for PHP 7.3 ([#889](https://github.com/php-curl-class/php-curl-class/pull/889)) diff --git a/README.md b/README.md index b706f557a4..78d47a8ab3 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # PHP Curl Class: HTTP requests made easy -[![](https://img.shields.io/github/release/php-curl-class/php-curl-class.svg?style=flat-square&sort=semver)](https://github.com/php-curl-class/php-curl-class/releases/) -[![](https://img.shields.io/github/license/php-curl-class/php-curl-class.svg?style=flat-square)](https://github.com/php-curl-class/php-curl-class/blob/master/LICENSE) -[![](https://img.shields.io/github/actions/workflow/status/php-curl-class/php-curl-class/ci.yml?style=flat-square&label=build&branch=master)](https://github.com/php-curl-class/php-curl-class/actions/workflows/ci.yml) -[![](https://img.shields.io/github/actions/workflow/status/php-curl-class/php-curl-class/release.yml?style=flat-square&label=release&branch=master)](https://github.com/php-curl-class/php-curl-class/releases/) -[![](https://img.shields.io/packagist/dt/php-curl-class/php-curl-class.svg?style=flat-square)](https://github.com/php-curl-class/php-curl-class/releases/) +[![](https://img.shields.io/github/release/php-curl-class/php-curl-class.svg?style=for-the-badge&sort=semver)](https://github.com/php-curl-class/php-curl-class/releases/) +[![](https://img.shields.io/github/license/php-curl-class/php-curl-class.svg?style=for-the-badge)](https://github.com/php-curl-class/php-curl-class/blob/master/LICENSE) +[![](https://img.shields.io/github/actions/workflow/status/php-curl-class/php-curl-class/ci.yml?style=for-the-badge&label=build&branch=master)](https://github.com/php-curl-class/php-curl-class/actions/workflows/ci.yml) +[![](https://img.shields.io/github/actions/workflow/status/php-curl-class/php-curl-class/release.yml?style=for-the-badge&label=release&branch=master)](https://github.com/php-curl-class/php-curl-class/releases/) +[![](https://img.shields.io/packagist/dt/php-curl-class/php-curl-class.svg?style=for-the-badge)](https://github.com/php-curl-class/php-curl-class/releases/) PHP Curl Class makes it easy to send HTTP requests and integrate with web APIs. @@ -12,18 +12,18 @@ PHP Curl Class makes it easy to send HTTP requests and integrate with web APIs. --- -- [Installation](#installation) -- [Requirements](#requirements) -- [Quick Start and Examples](#quick-start-and-examples) -- [Available Methods](#available-methods) -- [Security](#security) -- [Troubleshooting](#troubleshooting) -- [Testing](#testing) -- [Contributing](#contributing) +- [⚙️ Installation](#%EF%B8%8F-installation) +- [📋 Requirements](#-requirements) +- [🚀 Quick Start and Examples](#-quick-start-and-examples) +- [📖 Available Methods](#-available-methods) +- [🔒 Security](#-security) +- [🛠️ Troubleshooting](#%EF%B8%8F-troubleshooting) +- [🧪 Testing](#-testing) +- [🤝 Contributing](#-contributing) --- -### Installation +### ⚙️ Installation To install PHP Curl Class, run the following command: @@ -35,11 +35,11 @@ To install the latest commit version: Installation instructions to use the `composer` command can be found on https://github.com/composer/composer. -### Requirements +### 📋 Requirements -PHP Curl Class works with PHP 8.3, 8.2, 8.1, 8.0, and 7.4. +PHP Curl Class works with PHP 8.4, 8.3, 8.2, 8.1, 8.0, and 7.4. -### Quick Start and Examples +### 🚀 Quick Start and Examples More examples are available under [/examples](https://github.com/php-curl-class/php-curl-class/tree/master/examples). @@ -194,7 +194,7 @@ $multi_curl->start(); // Blocks until all items in the queue have been processed More examples are available under [/examples](https://github.com/php-curl-class/php-curl-class/tree/master/examples). -### Available Methods +### 📖 Available Methods ```php Curl::__construct($base_url = null, $options = []) Curl::__destruct() @@ -329,6 +329,7 @@ MultiCurl::close() MultiCurl::complete($callback) MultiCurl::disableTimeout() MultiCurl::error($callback) +MultiCurl::getActiveCurls() MultiCurl::getOpt($option) MultiCurl::removeHeader($key) MultiCurl::setAutoReferer($auto_referer = true) @@ -376,19 +377,19 @@ MultiCurl::unsetProxy() MultiCurl::verbose($on = true, $output = 'STDERR') ``` -### Security +### 🔒 Security See [SECURITY](https://github.com/php-curl-class/php-curl-class/blob/master/SECURITY.md) for security considerations. -### Troubleshooting +### 🛠️ Troubleshooting See [TROUBLESHOOTING](https://github.com/php-curl-class/php-curl-class/blob/master/TROUBLESHOOTING.md) for help troubleshooting. -### Testing +### 🧪 Testing See [TESTING](https://github.com/php-curl-class/php-curl-class/blob/master/TESTING.md) for testing information. -### Contributing +### 🤝 Contributing 1. Check for open issues or open a new issue to start a discussion around a bug or feature. 1. Fork the repository on GitHub to start making your changes. diff --git a/TESTING.md b/TESTING.md index 2516486bb3..aeaf8f627a 100644 --- a/TESTING.md +++ b/TESTING.md @@ -30,4 +30,4 @@ cd php-curl-class/ ### Continuous Integration Tests -Continuous integration runs [tests/ci.sh](https://github.com/php-curl-class/php-curl-class/blob/master/tests/ci.sh) on supported PHP versions and is configured with [.github/workflows/ci.yml](https://github.com/php-curl-class/php-curl-class/blob/master/.github/workflows/ci.yml). +Continuous integration runs [tests/run.sh](https://github.com/php-curl-class/php-curl-class/blob/master/tests/run.sh) on supported PHP versions and is configured with [.github/workflows/ci.yml](https://github.com/php-curl-class/php-curl-class/blob/master/.github/workflows/ci.yml). diff --git a/composer.json b/composer.json index 78222991bc..8eb824cf50 100644 --- a/composer.json +++ b/composer.json @@ -27,9 +27,9 @@ "friendsofphp/php-cs-fixer": "*", "phpcompatibility/php-compatibility": "dev-develop", "phpcsstandards/phpcsutils": "@alpha", + "phpstan/phpstan": "*", "phpunit/phpunit": "*", - "squizlabs/php_codesniffer": "*", - "vimeo/psalm": ">=5.25.0" + "squizlabs/php_codesniffer": "*" }, "suggest": { "ext-mbstring": "*" diff --git a/scripts/bump_major_version.php b/scripts/bump_major_version.php index 7ca2f6c2c5..77b03069a4 100755 --- a/scripts/bump_major_version.php +++ b/scripts/bump_major_version.php @@ -5,7 +5,7 @@ $current_version = Curl\Curl::VERSION; list($major, $_, $__) = explode('.', $current_version); -$new_version = implode('.', [(string)((int)$major += 1), '0', '0']); +$new_version = implode('.', [(string)((int)$major + 1), '0', '0']); foreach ( [ diff --git a/scripts/bump_minor_version.php b/scripts/bump_minor_version.php index a960450689..0de25185e0 100755 --- a/scripts/bump_minor_version.php +++ b/scripts/bump_minor_version.php @@ -5,7 +5,7 @@ $current_version = Curl\Curl::VERSION; list($major, $minor, $_) = explode('.', $current_version); -$new_version = implode('.', [$major, (string)((int)$minor += 1), '0']); +$new_version = implode('.', [$major, (string)((int)$minor + 1), '0']); foreach ( [ diff --git a/scripts/bump_patch_version.php b/scripts/bump_patch_version.php index df57314295..0b6804ae3a 100755 --- a/scripts/bump_patch_version.php +++ b/scripts/bump_patch_version.php @@ -5,7 +5,7 @@ $current_version = Curl\Curl::VERSION; list($major, $minor, $patch) = explode('.', $current_version); -$new_version = implode('.', [$major, $minor, (string)((int)$patch += 1)]); +$new_version = implode('.', [$major, $minor, (string)((int)$patch + 1)]); foreach ( [ diff --git a/scripts/make_release_requirements.txt b/scripts/make_release_requirements.txt index e325a4d70a..73dcd7ed81 100644 --- a/scripts/make_release_requirements.txt +++ b/scripts/make_release_requirements.txt @@ -12,20 +12,20 @@ cffi==1.16.0 # pynacl charset-normalizer==3.3.2 # via requests -cryptography==42.0.5 +cryptography==43.0.1 # via pyjwt deprecated==1.2.14 # via pygithub gitdb==4.0.11 # via gitpython -gitpython==3.1.43 - # via -r make_release_requirements.in +gitpython==3.1.44 + # via -r scripts/make_release_requirements.in idna==3.7 # via requests pycparser==2.22 # via cffi -pygithub==2.3.0 - # via -r make_release_requirements.in +pygithub==2.5.0 + # via -r scripts/make_release_requirements.in pyjwt[crypto]==2.8.0 # via pygithub pynacl==1.5.0 diff --git a/scripts/update_readme_methods.sh b/scripts/update_readme_methods.sh index 4a658c161e..5c8574513e 100755 --- a/scripts/update_readme_methods.sh +++ b/scripts/update_readme_methods.sh @@ -4,11 +4,11 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "${SCRIPT_DIR}/.." before=$(head -n $( - grep --context="0" --line-number --max-count="1" "### Available Methods" "README.md" | + grep --context="0" --line-number --max-count="1" "### 📖 Available Methods" "README.md" | perl -pe 's/^(\d+):.*/\1/') "README.md") after=$(tail -n +$( - grep --context="0" --line-number --max-count="1" "### Security" "README.md" | + grep --context="0" --line-number --max-count="1" "### 🔒 Security" "README.md" | perl -pe 's/^(\d+):.*/\1/') "README.md") echo "${before}" > "README.md" @@ -48,10 +48,23 @@ echo "${after}" >> "README.md" # Update table of contents. script=$(cat <<'EOF' $data = file_get_contents('README.md'); - preg_match_all('/^### ([\w ]+)/m', $data, $matches); + preg_match_all('/^### (.*)/m', $data, $matches); $toc = []; foreach ($matches['1'] as $match) { - $href = '#' . str_replace(' ', '-', strtolower($match)); + $slug = urlencode( + strtolower( + str_replace( + ' ', + '-', + preg_replace( + '/[^A-Za-z\x{FE0F} ]/u', + '', + $match, + ), + ) + ) + ); + $href = '#' . $slug; $toc[] = '- [' . $match . '](' . $href . ')'; } $toc = implode("\n", $toc); diff --git a/src/Curl/CaseInsensitiveArray.php b/src/Curl/CaseInsensitiveArray.php index e12901775e..72b757a7e7 100644 --- a/src/Curl/CaseInsensitiveArray.php +++ b/src/Curl/CaseInsensitiveArray.php @@ -88,7 +88,7 @@ public function offsetSet($offset, $value) #[\ReturnTypeWillChange] public function offsetExists($offset) { - return (bool) array_key_exists(strtolower($offset), $this->data); + return array_key_exists(strtolower($offset), $this->data); } /** @@ -129,20 +129,18 @@ public function offsetGet($offset) /** * Count * - * @param void * @return int The number of elements stored in the array. * @see https://secure.php.net/manual/en/countable.count.php */ #[\ReturnTypeWillChange] public function count() { - return (int) count($this->data); + return count($this->data); } /** * Current * - * @param void * @return mixed Data at the current position. * @see https://secure.php.net/manual/en/iterator.current.php */ @@ -155,7 +153,6 @@ public function current() /** * Next * - * @param void * @return void * @see https://secure.php.net/manual/en/iterator.next.php */ @@ -168,7 +165,6 @@ public function next() /** * Key * - * @param void * @return mixed Case-sensitive key at current position. * @see https://secure.php.net/manual/en/iterator.key.php */ @@ -188,13 +184,12 @@ public function key() #[\ReturnTypeWillChange] public function valid() { - return (bool) (key($this->data) !== null); + return (key($this->data) !== null); } /** * Rewind * - * @param void * @return void * @see https://secure.php.net/manual/en/iterator.rewind.php */ diff --git a/src/Curl/Curl.php b/src/Curl/Curl.php index 739177c214..d71118f1c9 100644 --- a/src/Curl/Curl.php +++ b/src/Curl/Curl.php @@ -6,7 +6,7 @@ class Curl extends BaseCurl { - public const VERSION = '11.0.0'; + public const VERSION = '11.0.1'; public const DEFAULT_TIMEOUT = 30; public $curl = null; @@ -1812,9 +1812,11 @@ private function buildCookies() if (count($this->cookies)) { // Avoid using http_build_query() as unnecessary encoding is performed. // http_build_query($this->cookies, '', '; '); - $this->setOpt(CURLOPT_COOKIE, implode('; ', array_map(function ($k, $v) { - return $k . '=' . $v; - }, array_keys($this->cookies), array_values($this->cookies)))); + $cookies = []; + foreach ($this->cookies as $key => $value) { + $cookies[] = $key . '=' . $value; + } + $this->setOpt(CURLOPT_COOKIE, implode('; ', $cookies)); } } diff --git a/src/Curl/Decoder.php b/src/Curl/Decoder.php index 5f9821973a..b2aa0417db 100644 --- a/src/Curl/Decoder.php +++ b/src/Curl/Decoder.php @@ -18,7 +18,7 @@ public static function decodeJson() { $args = func_get_args(); $response = call_user_func_array('json_decode', $args); - if ($response === null) { + if ($response === null && isset($args['0'])) { $response = $args['0']; } return $response; @@ -37,7 +37,7 @@ public static function decodeXml() { $args = func_get_args(); $response = @call_user_func_array('simplexml_load_string', $args); - if ($response === false) { + if ($response === false && array_key_exists('0', $args)) { $response = $args['0']; } return $response; diff --git a/src/Curl/MultiCurl.php b/src/Curl/MultiCurl.php index 5f3d9f617c..f6c00a8c86 100644 --- a/src/Curl/MultiCurl.php +++ b/src/Curl/MultiCurl.php @@ -578,6 +578,7 @@ public function setRateLimit($rate_limit) $unit = strtolower($matches['3']); // Convert interval to seconds based on unit. + $interval_seconds = ''; if ($unit === 's') { $interval_seconds = $interval * 1; } elseif ($unit === 'm') { @@ -925,7 +926,7 @@ private function hasRequestQuota() */ private function waitUntilRequestQuotaAvailable() { - $sleep_until = $this->currentStartTime + $this->intervalSeconds; + $sleep_until = (float)($this->currentStartTime + $this->intervalSeconds); $sleep_seconds = $sleep_until - microtime(true); // Avoid using time_sleep_until() as it appears to be less precise and not sleep long enough. diff --git a/.pre-commit-config.yaml b/tests/.pre-commit-config.yaml similarity index 83% rename from .pre-commit-config.yaml rename to tests/.pre-commit-config.yaml index 0966ee676d..c177426968 100644 --- a/.pre-commit-config.yaml +++ b/tests/.pre-commit-config.yaml @@ -6,13 +6,13 @@ # $ brew install pre-commit # # 2. Install hooks -# $ pre-commit install +# $ pre-commit install --config="tests/.pre-commit-config.yaml" # # 3. Optionally, enable automatic updates -# $ pre-commit autoupdate +# $ pre-commit autoupdate --config="tests/.pre-commit-config.yaml" repos: - repo: https://github.com/psf/black - rev: 24.8.0 + rev: 24.10.0 hooks: - id: black name: black @@ -21,6 +21,7 @@ repos: rev: 7.1.1 hooks: - id: flake8 + args: ["--config", "tests/setup.cfg"] - repo: https://github.com/pycqa/isort rev: 5.13.2 hooks: diff --git a/tests/PHPCurlClass/UrlTest.php b/tests/PHPCurlClass/UrlTest.php index 1d445f77ea..397793565e 100644 --- a/tests/PHPCurlClass/UrlTest.php +++ b/tests/PHPCurlClass/UrlTest.php @@ -11,8 +11,12 @@ class UrlTest extends \PHPUnit\Framework\TestCase public function testUrlPaths() { $urls_file = gzopen(__DIR__ . '/urls.csv.gz', 'r'); - fgetcsv($urls_file); // header - while (($test = fgetcsv($urls_file)) !== false) { + $length = null; + $separator = ","; + $enclosure = "\""; + $escape = "\\"; + fgetcsv($urls_file, $length, $separator, $enclosure, $escape); // header + while (($test = fgetcsv($urls_file, $length, $separator, $enclosure, $escape)) !== false) { $url = new Url($test[0], $test[1]); $actual_url = (string)$url; $expected_url = $test[2]; diff --git a/tests/check_static_analysis.sh b/tests/check_static_analysis.sh deleted file mode 100755 index 2b1198fc52..0000000000 --- a/tests/check_static_analysis.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "${SCRIPT_DIR}" - -# Run commands from the project root directory. -cd .. - -set -x - -vendor/bin/psalm --config="tests/psalm.xml" diff --git a/tests/display_errors.inc.sh b/tests/display_errors.inc.sh new file mode 100644 index 0000000000..3ae4ce73c5 --- /dev/null +++ b/tests/display_errors.inc.sh @@ -0,0 +1,11 @@ +error_count="${#errors[@]}" +if [[ "${error_count}" -ge 1 ]]; then + echo -e "\nErrors found: ${error_count}" + + iter=0 + for value in "${errors[@]}"; do + ((iter++)) + echo -e "\nError ${iter} of ${error_count}:" + echo "${value}" | perl -pe 's/^(.*)$/\t\1/' + done +fi diff --git a/tests/phpstan-baseline.neon b/tests/phpstan-baseline.neon new file mode 100644 index 0000000000..cde552e0fc --- /dev/null +++ b/tests/phpstan-baseline.neon @@ -0,0 +1,13 @@ +parameters: + ignoreErrors: + - + message: '#^Class CurlHandle not found\.$#' + identifier: class.notFound + count: 2 + path: ../src/Curl/Curl.php + + - + message: '#^Class CurlMultiHandle not found\.$#' + identifier: class.notFound + count: 2 + path: ../src/Curl/MultiCurl.php diff --git a/tests/phpstan.neon b/tests/phpstan.neon new file mode 100644 index 0000000000..dafd9478e0 --- /dev/null +++ b/tests/phpstan.neon @@ -0,0 +1,15 @@ +includes: + - phpstan-baseline.neon + +parameters: + reportUnmatchedIgnoredErrors: false + + # TODO: Increase rule level to be more strict. + level: 3 + + # TODO: Remove all exclusions except vendor/ and fix related errors. + excludePaths: + - ../examples/* + - ../tests/* + - ../vendor/* + - ../www/* diff --git a/tests/psalm-baseline.xml b/tests/psalm-baseline.xml new file mode 100644 index 0000000000..867a21c21d --- /dev/null +++ b/tests/psalm-baseline.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/tests/psalm.xml b/tests/psalm.xml index 79b8a77f32..d56152c69a 100644 --- a/tests/psalm.xml +++ b/tests/psalm.xml @@ -1,10 +1,12 @@ + diff --git a/tests/run.sh b/tests/run.sh index ca4c2ca7f1..9b98225765 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -1,63 +1,25 @@ #!/usr/bin/env bash -remove_expectWarning() { - # Fix "Call to undefined method CurlTest\CurlTest::expectWarning()". - if sed v < /dev/null 2> /dev/null; then - sed -i"" -e "/->expectWarning(/d" "./PHPCurlClass/PHP"* - else - sed -i "" -e "/->expectWarning(/d" "./PHPCurlClass/PHP"* - fi -} - -replace_assertStringContainsString() { - # -->assertStringContainsString( - # +->assertContains( - find='->assertStringContainsString(' - replace='->assertContains(' - if sed v < /dev/null 2> /dev/null; then - sed -i"" -e "s/${find}/${replace}/" "./PHPCurlClass/PHP"* - else - sed -i "" -e "s/${find}/${replace}/" "./PHPCurlClass/PHP"* - fi -} - -replace_assertMatchesRegularExpression() { - # -->assertMatchesRegularExpression( - # +->assertRegExp( - find='->assertMatchesRegularExpression(' - replace='->assertRegExp(' - if sed v < /dev/null 2> /dev/null; then - sed -i"" -e"s/${find}/${replace}/" "./PHPCurlClass/PHP"* - else - sed -i "" -e"s/${find}/${replace}/" "./PHPCurlClass/PHP"* - fi -} - -phpunit_v6_5_shim() { - remove_expectWarning - replace_assertMatchesRegularExpression - replace_assertStringContainsString -} - -phpunit_v7_5_shim() { - remove_expectWarning - replace_assertMatchesRegularExpression -} - -phpunit_v8_5_shim() { - remove_expectWarning - replace_assertMatchesRegularExpression -} +set -x -phpunit_v9_shim() { - replace_assertMatchesRegularExpression -} +if [[ "${CI}" == "true" ]]; then + composer self-update + + # TODO: Add "vimeo/psalm" back into composer.json under "require-dev" when + # vimeo/psalm supports PHP 8.4 (https://github.com/vimeo/psalm/issues/11107): + # "require-dev": { + # "vimeo/psalm": ">=5.26.1" + # }, + # + # TODO: Remove this workaround that only installs vimeo/psalm on PHP < 8.4 when + # vimeo/psalm supports PHP 8.4 (https://github.com/vimeo/psalm/issues/11107): + if [[ $(echo "${CI_PHP_VERSION} < 8.4" | bc -l) -eq 1 ]]; then + composer require --dev "vimeo/psalm:>=5.26.1" + fi -phpunit_v10_shim() { - remove_expectWarning -} + composer install --prefer-source --no-interaction +fi -set -x # Use composer's phpunit and phpcs by adding composer bin directory to the path environment variable. export PATH="${PWD}/vendor/bin:${PATH}" @@ -65,88 +27,26 @@ export PATH="${PWD}/vendor/bin:${PATH}" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "${SCRIPT_DIR}" +source "set_vars.inc.sh" + echo "CI_PHP_VERSION: ${CI_PHP_VERSION}" echo "CI_PHP_FUTURE_RELEASE: ${CI_PHP_FUTURE_RELEASE}" php -r "var_dump(phpversion());" php -r "var_dump(curl_version());" -# Let test server know we should allow testing. -export PHP_CURL_CLASS_TEST_MODE_ENABLED="yes" - -# Start test servers. Run servers on different ports to allow simultaneous -# requests without blocking. -server_count=7 -declare -A pids -for i in $(seq 0 $(("${server_count}" - 1))); do - port=8000 - (( port += $i )) - - php -S "127.0.0.1:${port}" server.php &> /dev/null & - pids["${i}"]="${!}" -done - errors=() -source "check_syntax.sh" - -# Determine which phpunit to use. -if [[ -f "../vendor/bin/phpunit" ]]; then - phpunit_to_use="../vendor/bin/phpunit" -else - phpunit_to_use="phpunit" -fi - -phpunit_version="$("${phpunit_to_use}" --version | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+")" -echo "phpunit_version: ${phpunit_version}" - -extra_args="${@}" -if [[ "${phpunit_version}" == "6.5."* ]]; then - phpunit_v6_5_shim - phpunit_args=" --debug --verbose --fail-on-risky ${extra_args}" -elif [[ "${phpunit_version}" == "7.5."* ]]; then - phpunit_v7_5_shim - phpunit_args=" --debug --verbose --fail-on-risky ${extra_args}" -elif [[ "${phpunit_version}" == "8.5."* ]]; then - phpunit_v8_5_shim - phpunit_args=" --debug --verbose --fail-on-risky ${extra_args}" -elif [[ "${phpunit_version}" == "9."* ]]; then - phpunit_v9_shim - phpunit_args=" --debug --verbose --fail-on-risky ${extra_args}" -elif [[ "${phpunit_version}" == "10."* ]]; then - phpunit_v10_shim - phpunit_args=" --display-incomplete --display-skipped --display-deprecations --display-errors --display-notices --display-warnings --fail-on-risky ${extra_args}" -fi +source "run_syntax_check.sh" -# Run tests. -"${phpunit_to_use}" --version -"${phpunit_to_use}" \ - --configuration "phpunit.xml" \ - ${phpunit_args} -if [[ "${?}" -ne 0 ]]; then - echo "Error: phpunit command failed" - errors+=("phpunit command failed") -fi +source "run_coding_standards_check.sh" -source "check_coding_standards.sh" +source "run_phpunit.sh" -set +x +source "run_static_analysis_check_phpstan.sh" -error_count="${#errors[@]}" -if [[ "${error_count}" -ge 1 ]]; then - echo -e "\nErrors found: ${error_count}" - - iter=0 - for value in "${errors[@]}"; do - ((iter++)) - echo -e "\nError ${iter} of ${error_count}:" - echo "${value}" | perl -pe 's/^(.*)$/\t\1/' - done -fi +source "run_static_analysis_check_psalm.sh" -# Stop test servers. -for pid in "${pids[@]}"; do - kill "${pid}" &> /dev/null & -done +source "display_errors.inc.sh" if [[ "${CI_PHP_FUTURE_RELEASE}" != "true" ]]; then exit "${#errors[@]}" diff --git a/tests/check_coding_standards.sh b/tests/run_coding_standards_check.sh similarity index 93% rename from tests/check_coding_standards.sh rename to tests/run_coding_standards_check.sh index b1ab3a65bc..f3f76f1197 100755 --- a/tests/check_coding_standards.sh +++ b/tests/run_coding_standards_check.sh @@ -1,8 +1,12 @@ +#!/usr/bin/env bash + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "${SCRIPT_DIR}" +source "set_vars.inc.sh" + # Run commands from the project root directory. -cd .. +pushd .. # Enforce line ending consistency in php files. crlf_file=$(find . -type "f" -iname "*.php" ! -path "*/vendor/*" -exec grep --color=always --files-with-matches $'\r' {} \;) @@ -114,6 +118,11 @@ if [[ "${?}" -ne 0 ]]; then errors+=("found PHP_CodeSniffer coding standard violation(s)") fi +# TODO: Remove this workaround that only runs php-cs-fixer on PHP < 8.4 when +# php-cs-fixer supports PHP 8.4. +# https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/pull/8300 +if [[ $(echo "${CI_PHP_VERSION} < 8.4" | bc -l) -eq 1 ]]; then + # Run PHP-CS-Fixer. vendor/bin/php-cs-fixer --version vendor/bin/php-cs-fixer fix --ansi --config="tests/.php-cs-fixer.php" --diff --dry-run @@ -121,3 +130,7 @@ if [[ "${?}" -ne 0 ]]; then echo "Error: found PHP-CS-Fixer coding standard violation(s)" errors+=("found PHP-CS-Fixer coding standard violation(s)") fi + +fi + +popd diff --git a/tests/ci.sh b/tests/run_phpunit.sh similarity index 73% rename from tests/ci.sh rename to tests/run_phpunit.sh index 6b5489cc1d..01b8bd2b7d 100755 --- a/tests/ci.sh +++ b/tests/run_phpunit.sh @@ -27,9 +27,9 @@ replace_assertMatchesRegularExpression() { find='->assertMatchesRegularExpression(' replace='->assertRegExp(' if sed v < /dev/null 2> /dev/null; then - sed -i"" -e"s/${find}/${replace}/" "./PHPCurlClass/PHP"* + sed -i"" -e "s/${find}/${replace}/" "./PHPCurlClass/PHP"* else - sed -i "" -e"s/${find}/${replace}/" "./PHPCurlClass/PHP"* + sed -i "" -e "s/${find}/${replace}/" "./PHPCurlClass/PHP"* fi } @@ -57,22 +57,9 @@ phpunit_v10_shim() { remove_expectWarning } -set -x - -composer self-update -composer install --prefer-source --no-interaction - -# Use composer's phpunit and phpcs by adding composer bin directory to the path environment variable. -export PATH="${PWD}/vendor/bin:${PATH}" - SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "${SCRIPT_DIR}" -echo "CI_PHP_VERSION: ${CI_PHP_VERSION}" -echo "CI_PHP_FUTURE_RELEASE: ${CI_PHP_FUTURE_RELEASE}" -php -r "var_dump(phpversion());" -php -r "var_dump(curl_version());" - # Let test server know we should allow testing. export PHP_CURL_CLASS_TEST_MODE_ENABLED="yes" @@ -88,10 +75,6 @@ for i in $(seq 0 $(("${server_count}" - 1))); do pids["${i}"]="${!}" done -errors=() - -source "check_syntax.sh" - # Determine which phpunit to use. if [[ -f "../vendor/bin/phpunit" ]]; then phpunit_to_use="../vendor/bin/phpunit" @@ -130,29 +113,7 @@ if [[ "${?}" -ne 0 ]]; then errors+=("phpunit command failed") fi -source "check_coding_standards.sh" - -set +x - -error_count="${#errors[@]}" -if [[ "${error_count}" -ge 1 ]]; then - echo -e "\nErrors found: ${error_count}" - - iter=0 - for value in "${errors[@]}"; do - ((iter++)) - echo -e "\nError ${iter} of ${error_count}:" - echo "${value}" | perl -pe 's/^(.*)$/\t\1/' - done -fi - # Stop test servers. for pid in "${pids[@]}"; do - kill "${pid}" &> /dev/null & + kill "${pid}" &> /dev/null & done - -if [[ "${CI_PHP_FUTURE_RELEASE}" != "true" ]]; then - exit "${#errors[@]}" -elif [[ "${#errors[@]}" -ne 0 ]]; then - echo "One or more tests failed, but allowed as the CI_PHP_FUTURE_RELEASE flag is on for PHP version ${CI_PHP_VERSION}." -fi diff --git a/tests/run_static_analysis_check_phpstan.sh b/tests/run_static_analysis_check_phpstan.sh new file mode 100755 index 0000000000..59121951af --- /dev/null +++ b/tests/run_static_analysis_check_phpstan.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${SCRIPT_DIR}" + +source "set_vars.inc.sh" + +# Run commands from the project root directory. +pushd .. + +set -x + +if [[ $(echo "${CI_PHP_VERSION} >= 7.4" | bc -l) -eq 1 ]]; then + vendor/bin/phpstan analyse --ansi --configuration="tests/phpstan.neon" . + if [[ "${?}" -ne 0 ]]; then + echo "Error: phpstan static analysis check failed" + errors+=("phpstan static analysis check failed") + fi +else + echo "Skipped running phpstan check" +fi + +popd diff --git a/tests/run_static_analysis_check_psalm.sh b/tests/run_static_analysis_check_psalm.sh new file mode 100755 index 0000000000..9cfae37163 --- /dev/null +++ b/tests/run_static_analysis_check_psalm.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${SCRIPT_DIR}" + +source "set_vars.inc.sh" + +# Run commands from the project root directory. +pushd .. + +set -x + +# TODO: Remove exclusion that skips running psalm on PHP 8.4 when psalm +# supports PHP 8.4 (https://github.com/vimeo/psalm/issues/11107). +if [[ $(echo "${CI_PHP_VERSION} < 8.4" | bc -l) -eq 1 ]]; then + vendor/bin/psalm --config="tests/psalm.xml" + if [[ "${?}" -ne 0 ]]; then + echo "Error: psalm static analysis check failed" + errors+=("psalm static analysis check failed") + fi +else + echo "Skipped running psalm check" +fi + +popd diff --git a/tests/check_syntax.sh b/tests/run_syntax_check.sh similarity index 66% rename from tests/check_syntax.sh rename to tests/run_syntax_check.sh index 223cb522f4..ae0b8cc409 100755 --- a/tests/check_syntax.sh +++ b/tests/run_syntax_check.sh @@ -1,9 +1,16 @@ +#!/usr/bin/env bash + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "${SCRIPT_DIR}" +# Run commands from the project root directory. +pushd .. + # Check syntax in php files. Use `xargs' over `find -exec' as xargs exits with a value of 1 when any command errors. -find .. -type "f" -iname "*.php" ! -path "*/vendor/*" | xargs -L "1" php -l +find . -type "f" -iname "*.php" ! -path "*/vendor/*" | xargs -L "1" php -l if [[ "${?}" -ne 0 ]]; then echo "Error: php syntax checks failed" errors+=("php syntax checks failed") fi + +popd diff --git a/tests/set_vars.inc.sh b/tests/set_vars.inc.sh new file mode 100644 index 0000000000..dc33f7b01e --- /dev/null +++ b/tests/set_vars.inc.sh @@ -0,0 +1,4 @@ +# Use installed version when variable not set. +if [[ -z "${CI_PHP_VERSION}" ]]; then + CI_PHP_VERSION="$(php -r "echo preg_replace('/^([0-9]+\.[0-9]+)\.[0-9]+/', '\$1', phpversion());")" +fi diff --git a/setup.cfg b/tests/setup.cfg similarity index 100% rename from setup.cfg rename to tests/setup.cfg diff --git a/tests/skip_slow_tests.sh b/tests/skip_slow_tests.sh index 59b4b9b935..9ba74745ee 100755 --- a/tests/skip_slow_tests.sh +++ b/tests/skip_slow_tests.sh @@ -1,3 +1,5 @@ +#!/usr/bin/env bash + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "${SCRIPT_DIR}" diff --git a/tests/test_all.sh b/tests/test_all.sh index 11b09b3512..118e747ecc 100755 --- a/tests/test_all.sh +++ b/tests/test_all.sh @@ -1,3 +1,5 @@ +#!/usr/bin/env bash + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "${SCRIPT_DIR}"